var tung = "" var topToggle = false function TopToggle(show = null) { topToggle = show === null ? !topToggle : show if (topToggle) { $('#tung').show() } else { $('#tung').hide() } layout() } const sfx = new Audio('sfx-click.ogg') $(document).ready(() => { if (localStorage.getItem('tung') === null || localStorage.getItem('tung') == "") { tung = "201C 2014 00D7 00B0 00B7 2022 221E 00B1 2023 201D" localStorage.setItem('tung', tung) } else { tung = localStorage.getItem('tung') } $('#tung').html(tung) layout() // persistent text if (localStorage.getItem('text') === null || localStorage.getItem('text') == "") { localStorage.setItem('text', "") } else { $('#text').html(localStorage.getItem('text')) } // $('body').on('keydown', '[contenteditable]', function(e) { // if (e.keyCode === 13) { // e.preventDefault() // prevent the default newline insertion // var range = window.getSelection().getRangeAt(0) // get the current selection range // var newline = document.createTextNode("\n") // create a new text node with the newline character // range.insertNode(newline) // insert the new text node at the current cursor position // range.setStartAfter(newline) // set the cursor after the new text node // range.setEndAfter(newline) // set the end of the selection after the new text node // window.getSelection().removeAllRanges() // remove the old selection // window.getSelection().addRange(range) // set the new selection to the updated range // layout() // } // console.log(e.keyCode) // }); let url = '/tungs.txt' $.get(url, function (data) { lines = data.split('\n') nav() }) }) var lines = null var indexPath = [ 0 ] function indentation(line) { let lineIndent = 0 while (line.charAt(lineIndent) == '\t') { lineIndent++ } return lineIndent } function nav() { let rootIndex = indexPath[indexPath.length - 1] let root = lines[rootIndex] let rootIndent = indentation(root) let html = `` for (let i = rootIndex; i < lines.length; i++) { let line = lines[i] let lineIndent = indentation(line) if (lineIndent < rootIndent) { break } if (lineIndent > rootIndent) { continue } let option = line.trim() let pick = false if (option.includes(':')) { option = option.split(':')[0].trim() pick = true } let disabled = false // if next line has doesn't have a greater indent, disable the option and make it gray if (!pick && i + 1 < lines.length) { let nextLineIndent = indentation(lines[i + 1]) if (nextLineIndent <= rootIndent) { disabled = true } } let classes = `class="option ${disabled ? 'disabled' : ''} ${pick ? 'pick' : ''} ${(selected != null && i == selected) ? 'selected' : ''} "` let click = `${disabled ? '' : `onclick="select(${i}, ${pick})"`}` html += `
${option}
` } let top = indexPath.length < 2 let from = top ? 'tungs' : lines[rootIndex - 1].trim() let back = `
${from}
contribute
` let head = `
tung-tap
` let links = ` ` $('#nav').html(links + back + html) } function back() { if (indexPath.length == 1) { $('#nav').toggle() return } indexPath.pop() nav() } var selected = null function select(index, pick) { let line = lines[index] if (pick) { $('#tung').html(line.split(':')[1].trim()) layout() selected = index } else { indexPath.push(index + 1) } nav() } function getLines(divId) { var div = document.getElementById(divId) var lineHeight = parseInt(window.getComputedStyle(div).getPropertyValue('line-height')) var height = div.getBoundingClientRect().height var lineCount = Math.floor(height / lineHeight) var newLines = [] for (var i = 0; i < lineCount; i++) { // var lineTop = i * lineHeight // var lineBottom = (i + 1) * lineHeight var lineText = div.innerText.substring( div.getClientRects()[i].left - div.getClientRects()[0].left, div.getClientRects()[i].right - div.getClientRects()[0].left ) newLines.push(lineText) } return newLines } function layout() { // keymap (tung is the proprietary name) let tungText = $('#tung').html() //.replace('\r\n', '\n') tungText = tungText.replace(/
/g, '\n').replace(/<\/div>/g, '') if (tung != tungText) { tung = tungText localStorage.setItem('tung', tung) } // console.log(tungText) let html = `` let rows = tung.split('\n') rows.forEach(row => { row = row.trim() if (row.length > 0) { html += `
\n` let keys = row.split(' ') keys.forEach(key => { // print the newkey string // and the unicode character html += `
${key} ${String.fromCharCode("0x" + key)}
\n` }) html += `
\n` } }) $('#keyboard').html(html) $('.key').click(key => { // override default behavior key.preventDefault() // if text is not focused then set the cursor to the end of $('#text') let textEl = $('#text') if (document.activeElement != textEl[0] && window.getSelection().rangeCount == 0) { let selection = window.getSelection() let range = document.createRange() range.selectNodeContents(textEl[0]) range.collapse(false) selection.removeAllRanges() selection.addRange(range) } textEl.focus() // insert key character into contenteditable div $('#text') at cursor position let char = String.fromCharCode("0x" + $(key.target).attr('charcode')) insertTextAtCursor(char) // navigator.vibrate(20); // sfx.play() }) } function insertTextAtCursor(text) { let selection = window.getSelection() let range = selection.getRangeAt(0) range.deleteContents() let node = document.createTextNode(text) range.insertNode(node) range.setStartAfter(node) range.setEndAfter(node) selection.removeAllRanges() selection.addRange(range) } function copyToClipboard(id) { var textEl = $(id) // select all the text on the contenteditable div textEl to show it was copied let selection = window.getSelection() let range = document.createRange() range.selectNodeContents(textEl[0]) selection.removeAllRanges() selection.addRange(range) if (!navigator.clipboard) { console.error('Clipboard API not supported'); return; } navigator.clipboard.writeText(textEl.html()) .then(() => { console.log('Text copied to clipboard') }) .catch((err) => { console.error('Failed to copy text: ', err) }) } // const newPosition = toolbar.getBoundingClientRect().top // toolbar.classList.add('down') function getVisibleHeight() { var pixelRatio = window.devicePixelRatio || 1 var viewportHeight = window.visualViewport.height * pixelRatio var windowHeight = window.innerHeight * pixelRatio var keyboardHeight = viewportHeight - windowHeight // $('#tung').html(`viewport ${viewportHeight}px | window ${windowHeight}px | keyboard ${keyboardHeight}px`) return Math.abs(keyboardHeight) / pixelRatio || 0 } var out = 400; function loop(timestamp) { var delta = timestamp - lastRender time += delta let height = getVisibleHeight() // use the bottom of the #tung element position to use for the body top // to be able to toggle the top of the page let top = $('#tung').outerHeight(true) //+ $('#links').outerHeight(true) // + $('#home').outerHeight(true) if (topToggle) top *= 0 // lerp out to top if (time > 100) { out = lerp(out, top, 0.01 * delta) } // 'bottom': height + 'px', // $('body').css({ // 'top': -out + 'px' // }) // $('#keyboard').css({ // 'bottom': height + 'px', // }) // check if need to backup the text let text = $('#text').html() if (text != lastText) { localStorage.setItem('text', text) lastText = text } lastRender = timestamp window.requestAnimationFrame(loop) } var lastText = '' var lastRender = 0 var time = 0 window.requestAnimationFrame(loop) let ua = navigator.userAgent.toLowerCase() let safari = ua.indexOf('safari') != -1 && !(ua.indexOf('chrome') > -1) // dev toggle override // safari = !safari $('html').css('--bg', safari ? '214, 216, 221' : '16, 16, 16') $('html').css('--fg', safari ? '214, 216, 221' : '240, 240, 240') $('html').css('--key', safari ? '255, 255, 255' : '48, 48, 48') $('html').css('--key-height', safari ? '40px' : '48px') $('html').css('--key-radius', safari ? '5px' : '8px') $('html').css('--txt', safari ? '0, 0, 0' : '255, 255, 255') $('html').css('--txt-size', safari ? '22px' : '26px') $('html').css('--field', safari ? '255, 255, 255' : '0, 0, 0') $('html').css('--gap', safari ? '6px' : '5px') window.addEventListener("beforeinstallprompt", (e) => { console.log('beforeinstallprompt') // // Prevent Chrome 67 and earlier from automatically showing the prompt // e.preventDefault() // // Stash the event so it can be triggered later. // deferredPrompt = e // // // Update UI to notify the user they can add to home screen // // addBtn.style.display = "block" // // addBtn.addEventListener("click", (e) => { // // // hide our user interface that shows our A2HS button // // addBtn.style.display = "none" // // }) // // Show the prompt // deferredPrompt.prompt() // // Wait for the user to respond to the prompt // deferredPrompt.userChoice.then((choiceResult) => { // if (choiceResult.outcome === "accepted") { // console.log("User accepted the A2HS prompt") // } else { // console.log("User dismissed the A2HS prompt") // } // deferredPrompt = null // }) }) function lerp(a, b, n) { return (1 - n) * a + n * b }