Skip to content

Instantly share code, notes, and snippets.

@gutchom
Created March 3, 2020 16:13
Show Gist options
  • Save gutchom/2b98b82f72f83fa38117c92813e034b5 to your computer and use it in GitHub Desktop.
Save gutchom/2b98b82f72f83fa38117c92813e034b5 to your computer and use it in GitHub Desktop.
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<title>Lyric Translation Builder</title>
<style>
body {
text-align: center;
}
h1 input {
border: none;
font-size: inherit;
font-weight: bold;
text-align: center;
}
rt.kana input {
width: 2em;
min-width: 2em;
max-width: 3em;
}
section.lyrics p {
font-size: 18px;
}
rt.kana,
rtc {
user-select: none;
}
rtc {
font-size: 75%;
}
rtc input {
width: 20em;
}
</style>
</head>
<body>
<article>
<h1 id="title"><input value="今宵の月のように"></h1>
<section id="lyrics" lang="ja-JP" translate="no"></section>
</article>
<section>
<textarea title="lyrics" id="textarea" cols="80" rows="20">
くだらねえとつぶやいて
醒めたつらして歩く
いつの日か輝くだろう
あふれる熱い涙
いつまでも続くのか
吐きすてて寝転んだ
俺もまた輝くだろう
今宵の月のようにAh… Ah…
夕暮れ過ぎて きらめく町の灯りは
悲しい色に 染まって揺れた
君がいつかくれた 思い出のかけら集めて
真夏の夜空 ひとり見上げた
新しい季節の始まりは
夏の風 町に吹くのさ
今日もまたどこへ行く
愛を探しに行こう
いつの日か輝くだろう
あふれる熱い涙 Ah… Ah… Oh yeah…
ポケットに手を つっこんで歩く
いつかの電車に乗って いつかの町まで
君のおもかげ きらりと光る 夜空に
涙も出ない 声も聞こえない
もう二度と戻らない日々を
俺たちは走り続ける
明日もまたどこへ行く
愛を探しに行こう
いつの日か輝くだろう
あふれる熱い涙
明日もまたどこへ行く
愛を探しに行こう
見慣れてる町の空に
輝く月一つ
いつの日か輝くだろう
今宵の月のように Ah… Ah…
</textarea>
<button>決定</button>
</section>
<script>
const press = {}
window.addEventListener('keydown', handleKeyDown)
window.addEventListener('keyup', handleKeyUp)
function handleKeyDown(e) {
press[e.key] = true
}
function handleKeyUp(e) {
press[e.key] = false
}
document.querySelector('button').onclick = function(e) {
document.getElementById('lyrics').innerHTML = parse(e.target.previousElementSibling.value)
document.querySelectorAll('rb').forEach(el => el.onclick = handleRbClick)
document.querySelectorAll('rt').forEach(el => el.onclick = handleRtClick)
document.querySelectorAll('ruby.sentence').forEach(el => el.onclick = handleSentenceClick)
e.target.parentElement.remove()
}
function handleRbClick(e) {
e.stopPropagation()
appendInput(e.target.nextElementSibling)
}
function handleRtClick(e) {
e.stopPropagation()
appendInput(e.target)
}
// TODO: 現在開いている行以降の行をクリックした際に一旦入力欄が閉じる挙動を、連続的にフォーカスが映るようにする
function handleSentenceClick(e) {
appendInput(e.currentTarget.lastElementChild)
}
function appendInput(element) {
const input = document.createElement('input')
input.value = element.innerText
element.innerHTML = ''
element.appendChild(input)
input.onblur = handleInputBlur
input.onkeydown = handleInputKeyDown
input.onclick = (e) => e.stopPropagation()
input.focus()
}
function handleInputBlur(e) {
e.target.parentElement.innerHTML = e.target.value
}
function handleInputKeyDown(e) {
switch (e.key) {
case 'Escape':
e.target.blur()
break
case 'Enter':
e.preventDefault()
/*
case 'Enter':
const sentence = find(e.target, el => el.classList.contains('sentence'))
appendInput(e.target.parentElement.tagName === 'RTC' ? sentence.nextElementSibling.lastElementChild : sentence.lastElementChild)
break
*/
case 'Tab':
e.preventDefault()
const elements = [...document.querySelectorAll('#lyrics rt')]
const index = elements.indexOf(e.currentTarget.parentElement) + (press['Shift'] ? -1 : 1)
if (-1 < index && index < elements.length) {
appendInput(elements[index])
}
break
}
}
function find(element, test) {
if (test(element)) {
return element
} else if (element.parentElement) {
return find(element.parentElement, test)
}
}
function parse(text) {
const markup = (head, tail, middle) => pattern => {
let last = false
return char => [head + char, char, middle || char, tail + char][last * 2 - (last = pattern.test(char)) + 1]
}
const kanji = markup('<ruby><rb>', '</rb><rt class="kana"></rt></ruby>')(/(?:[々〇〻\u2E80-\u2FDF\u3400-\u4DBF\u4E00-\u9FFF\uF900-\uFAFF]|[\uD840-\uD87F][\uDC00-\uDFFF])/)
const block = markup('<br>\n<rtc lang="en-US"></rtc>\n</ruby>\n<br>', '<ruby class="sentence">\n', '</p>\n<p>')(/\n/)
return text.split('').reduce((markup, char) => markup + block(kanji(char)), '\n<p>\n<ruby class="sentence">\n') + '</p>\n'
}
function getSelectionText(lang) {
const frag = window.getSelection().getRangeAt(0).cloneContents()
const translations = frag.querySelectorAll(`rtc:lang(${lang})`)
console.log(translations)
return [...(translations.length > 0 ? translations : removeByQuery(frag , 'rtc', lang === 'kana' ? 'rb' : 'rt.kana').children)].map(el => el.textContent.replace(/\s{2,}/g, '')).filter(sentence => sentence.length > 0).join('\n')
function removeByQuery(fragment, ...selectors) {
selectors.forEach(selector => fragment.querySelectorAll(selector).forEach(el => el.remove()))
return fragment
}
}
</script>
</body>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment