Skip to content

Instantly share code, notes, and snippets.

@derekmc
Last active April 24, 2025 17:31
Show Gist options
  • Save derekmc/1ca2a78e450d379190d6fd76ae9843e6 to your computer and use it in GitHub Desktop.
Save derekmc/1ca2a78e450d379190d6fd76ae9843e6 to your computer and use it in GitHub Desktop.
<html>
<head>
<meta name="viewport" content="width=device-width, initial-scale=1">
<style>
/* CSS */
#status-div{
font-family: monospace;
text-align: center;
font-size: 12px;
}
.log-out{
font-family: monospace;
text-align: justify;
font-size: 11px;
white-space: pre-wrap;
}
body{
font-family: monospace;
}
/* END */
</style>
</head>
<body>
<div class="markdown-src">
<!-- HTML -->
<div id='status-div'></div>
<button onclick="clearLogs()">Clear</button>
<hr>
<div id='text-div' class='log-out'></div>
<hr>
<div id='log2-div' class='log-out'></div>
<hr>
<div id='log1-div' class='log-out'></div>
<!-- END -->
</div>
<div style="display: none;"></div>
<script>
let datakey = "__HTML_NOTES__:notedataid=x7Pa3QckWb"
let inframe = (window!=window.top)
window.Note = {}
window.Note.data =
/* JSON */
{"virtualKeys":[74,75,76,186,85,73,79,80,32,222],"virtualKeyCount":10,"layout":"\n : r n o e a t i l space shift\n r: XX right s u up * c h esc _\n n: left XX m d pgup = p x + -\n o: s m XX / w g : $ { }\n e: u d \\ XX f k [ ] alt select\n a: down pgdn w f XX q j v ~ !\n t: < > g k z XX b y ( )\n i: c p ^ # j b XX ` ' \"\n l: h x & | v y % 0 @\n space: ; ctrl , . tab back5 back enter XX ?\n shift: 1 2 3 4 5 6 7 8 9 XX","textout":"^^to be or not to be that isthe atuquestion.[enter][enter]^^this irr[back] i[back][back][back]is a test. function main(t^^[back][back]"};
/* END */
(function(){
window.Note.save = saveData
window.Note.load = loadData
window.Note.autoSave = autoSave
function getNoteModule(){
if(!window['Note']) window.Note = {}
return window.Note
}
function loadData(){
let note = getNoteModule()
if(!inframe){
try{
let savedata = localStorage.getItem(datakey)
if(savedata && savedata.length){
note.data = JSON.parse(savedata)
}
window.addEventListener("message", onMessage)
} catch(e){
console.log("No local data")
console.log(e)
}
} else {
return note.data
}
}
let firstLogCall = true
let originalLogFunc = console.log
function log(x, ...rest){
if(firstLogCall)
document.body.appendChild(document.createElement("hr"))
else
document.body.appendChild(document.createElement("br"))
document.body.appendChild(
document.createTextNode(x + " " + rest.join(" ")))
firstLogCall = false
originalLogFunc(x, ...rest)
}
console.log = log
let SaveInterval = 500
window.addEventListener("load", ()=>{
convertMarkdown()
autoSave()
loadData() // dont wait for async
})
let saveIntervalRef = null
function autoSave(enable){
if(enable === undefined) enable = true
clearInterval(saveIntervalRef)
if(enable){
saveIntervalRef = window.setInterval(saveData, SaveInterval)
}
}
// this is preferred over a 'localstorage' polyfill for a frame,
// so the programmer doesn't assume this is the browsers localstorage
function saveData(){
let note = getNoteModule()
if(inframe){
let msg_obj = {
action: "saveData",
data: JSON.stringify(note['data'])
}
let message = JSON.stringify(msg_obj)
window.parent.postMessage(message)
} else {
localStorage.setItem(datakey, JSON.stringify(note['data']))
}
}
let md_subs = [
/(\n|^)\s*######\s([^\s#].*)\n*/g, "\n<h6 id=\"$2\">$2</h6>\n",
/(\n|^)\s*#####\s([^\s#].*)\n*/g, "\n<h5 id=\"$2\">$2</h5>\n",
/(\n|^)\s*####\s*([^\s#].*)\n*/g, "\n<h4 id=\"$2\">$2</h4>\n",
/(\n|^)\s*###\s*([^\s#].*)\n*/g, "\n<h3 id=\"$2\">$2</h3>\n",
/(\n|^)\s*##\s*([^\s#].*)\n*/g, "\n<h2 id=\"$2\">$2</h2>\n",
/(\n|^)\s*#\s*([^\s#].*)\n*/g, "\n<h1 id=\"$2\">$2</h1>\n",
/\n(\s*)[\*\-](.*)/g, '\n<ul><li>$2</li></ul>',
/\n+\n(?=[^#\n])/g, "\n\n<br><br>",
/\n+\n/g, "\n",
/__([^_\n]*)__/g, "<b>$1</b>",
/\*\*([^_\n]*)\*\*/g, "<b>$1</b>",
/_([^_\n]*)_/g, "<i>$1</i>",
/\*([^_\n]*)\*/g, "<i>$1</i>",
/\!\[([^\]\n]*)\]\(([^\)\n]*)\)/g, "<img src=\"$2\" alt=\"$1\"></img>",
/\[([^\]\n]*)\]\(([^\)\n]*)\)/g, "<a href=\"$2\" target=\"_blank\">$1</a>",
]
function onMessage(){
try{
let message = JSON.parse(e.data)
// console.log('received message', message)
if(message.hasOwnProperty("event")){
if(message.event == "keydown" && typeof keydown != "undefined"){
keydown(message)
}
if(message.event == "keyup" && typeof keyup != "undefined"){
keydown(message)
}
}
} catch(e){
console.warn("error processing message: " + e.data)
}
}
function convertMarkdown(){
let containers = document.getElementsByClassName("markdown-src")
for(let j=0; j<containers.length; ++j){
let container = containers[j]
let src = container.innerHTML
for(var i=0; i<md_subs.length-1; i += 2){
var search = md_subs[i]
var replace = md_subs[i+1]
src = src.replace(search, replace)
}
container.innerHTML = src
}
}
})()
//window.addEventListener('load', htmlNotesMainFunc)
//function htmlNotesMainFunc(){
/* JS */
/*
Commas separate simultanous actions
Bichord Keyboard
Algorithm flow chart
1. serialize key actions
2. map physical keys to virtual keys
3. interpret chord sequence
4. translate chord sequence
Step 1
+65 +67 -88,-65 +99
*/
let data = Note.data
let env = {} // constants
let temp = {} // reset
let ref = {} // dont reset
function getTemp(name, _default){
if(!temp.hasOwnProperty(name))
temp[name] = _default
return temp[name]
}
let defaultLayout = `
: r n o e a t i l space shift
r: XX right s u up * c h esc _
n: left XX m d pgup = p x + -
o: s m XX / w g : $ { }
e: u d \\ XX f k [ ] alt select
a: down pgdn w f XX q j v ~ !
t: < > g k z XX b y ( )
i: c p ^ # j b XX \` ' "
l: h x & | v y % 0 @
space: ; ctrl , . tab back5 back enter XX ?
shift: 1 2 3 4 5 6 7 8 9 XX`
function reset(){
temp = {}
}
main()
function json(x){
return JSON.stringify(x)
}
function clearLogs(){
temp.keylog = ""
temp.virtlog = ""
}
function statusText(){
if(data.virtualKeys.length < data.virtualKeyCount){
return `Map key #${data.virtualKeys.length + 1}`
}
return getTemp("lastMessage", "")
}
function statusUpdate(){
let div = id('status-div')
div.innerHTML = ""
let txt = document.createTextNode(statusText())
div.appendChild(txt)
}
function logUpdate(){
let div = id('log1-div')
div.innerHTML = ""
let txt = document.createTextNode(
" KeyLog: " + getTemp("keylog", ""))
div.appendChild(txt)
div = id('log2-div')
div.innerHTML = ""
txt = document.createTextNode(
"VirtualLog: " + getTemp("virtlog", ""))
div.appendChild(txt)
div = id('text-div')
data.textout = replaceChords(
getTemp("chordSubs", []), temp.virtlog)
div.innerHTML = ""
txt = document.createTextNode(
" Text: " + data.textout)
div.appendChild(txt)
}
function init(){
ref.logCall = multiFilter([
logKey('keylog'), virtualKeyFilter(logKey('virtlog'))])
data.virtualKeys ??= []
data.virtualKeyCount ??= 10
data.layout ??= defaultLayout
//console.log(data.layout)
temp.lastMessage = "init called"
setInterval(statusUpdate, 300)
setInterval(logUpdate, 300)
temp.chordMap = parseLayout(data.layout)
temp.chordSubs = chordSubs(temp.chordMap)
//console.info("chordMap", temp.chordMap)
//console.info("chordSubs", json(temp.chordSubs))
//console.info(json(allOrders([1,2,3,4])))
//window.addEventListener('keydown', keydown)
//window.addEventListener('keyup', keyup)
//setInterval(process, 1000)
}
function main(){
init()
window.addEventListener('keydown', keydown)
window.addEventListener('keyup', keyup)
}
function getKeyId(e){
return e.keyCode
}
function logKey(logname){
return function(action, keyid){
if(action != "+" && action != "-"){
throw new Error('invalid keylog action: ' + action)}
getTemp('keylog', "")
temp[logname] += " " + action + keyid + ";"
}
}
function keydown(e){
let keyid = getKeyId(e)
if(getTemp('activeKeys', {})[keyid]) return
if(data.virtualKeys.length < data.virtualKeyCount){
data.virtualKeys.push(keyid)
return
}
temp.activeKeys[keyid] = true
ref.logCall('+', keyid)
}
function keyup(e){
let keyid = getKeyId(e)
getTemp('activeKeys', {})
if(!temp.activeKeys.hasOwnProperty(keyid)){
return }
ref.logCall('-', keyid)
delete temp.activeKeys[keyid]
}
function virtualKeyFilter(callback){
return function(action, keyid){
let virtid = mapKey(keyid)
if(virtid > 0) callback(action, virtid)
}
}
function multiFilter(callbacks){
return function(action, keyid){
callbacks.forEach(f=>f(action,keyid))}
}
// premute the array
function allOrders(array, results, prefix){
if(!results) results = []
if(!prefix) prefix = []
for(let i=0; i<array.length; ++i){
let rest = array.slice(0)
rest.splice(i, 1)
let prefix2 = prefix.slice(0)
prefix2.push(array[i])
if(array.length == 1) results.push(prefix2)
else allOrders(rest, results, prefix2)
}
return results
}
function parseLayout(s){
let keymap = {}
let keynames = []
keymap.__keylist = []
let lines = s.split('\n')
let unordered = false
for(let i=0; i<lines.length; ++i){
let line = lines[i].trim()
let j = line.indexOf(":")
if(j < 0){
if(line.toLowerCase() == "!undordered"){
unordered = true }
continue
}
let prefix = line.substring(0, j).trim().split(/\s+/).join(' ')
let rest = line.substring(j+1).trim().split(/\s+/)
//console.log(prefix, rest)
if(prefix.length == 0){
keynames = rest
keymap.__keylist = keymap.__keylist.concat(keynames)
} else {
for(let k=0; k<rest.length; ++k){
let chord = prefix + ' ' + keynames[k]
let value = rest[k].trim()
//if(!unordered){
keymap[chord] = value
//if(unordered){
// let orders = allOrders(chord.split(/\s+/)).map(x=>x.join(' '))
// for(let i=0; i<orders.length; ++i){
// keymap[orders[i]] = value }
//}
}
}
}
return keymap
}
function chordSubs(chordmap){
let result = []
for(let input in chordmap){
let output = chordmap[input]
if(output == "XX") continue
if(input[0] == '_') continue
let sequence = input.split(/\s+/)
let prefix = ""
let suffix = ""
for(let i=0; i<sequence.length; ++i){
let char = sequence[i]
let j = chordmap.__keylist.indexOf(char) + 1
if(j < 0){
prefix = ""
break
}
if(prefix.length) prefix += '; '
if(suffix.length) suffix = '; ' + suffix
prefix += '+' + j
suffix = '-' + j + suffix
}
let pattern = " " + prefix + "; " + suffix + ";"
if(prefix.length) result.push([pattern, output])
}
chordmap.__keylist.forEach((ch,j)=>
result.push([' +' + (j+1) + ";", ch]))
return result
}
function emit(chord){
let k = chord.join(' ')
let keyaction = temp.chordMap[k]
}
function mapKey(x){
let i = data.virtualKeys.indexOf(x)
return i+1
}
function replaceChords(subs, log){
subs.forEach(([pattern, result])=>{
log = log.replaceAll(pattern, " ~" + result + ";")
})
log = log.replaceAll(/ -[0-9]+;/g, "")
log = log.replace(/ ~space;/g, " ")
log = log.replace(/ ~shift;/g, "^^")
log = log.replace(/ ~(.);/g, "$1")
log = log.replace(/ ~(.[^;]*);/g, "[$1]")
//log = log.replace(/. ~back;/g, "")
//log = log.replace(/..... ~back5;/g, "")
return log
}
// event handler
function keyaction(action, keyid){
let actionMap = {'down': '+', 'up': '-'}
let a = actionMap[action]
let virtualkey = mapKey(keyid)
keyQueue.push(a + virtualkey)
}
function id(x){
return document.getElementById(x)
}
/* END */
//}
</script>
</body>
<html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment