Last active
March 5, 2025 22:17
-
-
Save derekmc/9f6c4fd784de9296183bf972889a607b to your computer and use it in GitHub Desktop.
A basic editor for the "simple chord" chorded keyboard.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<html> | |
<head> | |
<meta name="viewport" content="width=device-width, initial-scale=1"> | |
<style> | |
/* CSS */ | |
/* The switch - the box around the slider */ | |
h1, h2, h3, h4{ | |
display: inline-block; | |
} | |
.h4{ | |
font-weight: bold; | |
font-size: 18px; | |
height: 200px; | |
margin: 5px 10px; | |
} | |
.checkgroup{ | |
white-space: nowrap; | |
} | |
button{ | |
border: 2px solid #888; | |
background: #ccc; | |
font-size: 14px; | |
padding: 3px 10px; | |
font-weight: bold; | |
} | |
body{ | |
font-family: sans-serif; | |
overflow: hidden; | |
user-select:none; | |
margin: 8px; | |
margin-top: 20px; | |
} | |
label{ | |
cursor: pointer; | |
} | |
#textout::selection{ | |
background: #fff; | |
color: #008; | |
} | |
#textout::-webkit-scrollbar{ | |
display: none; | |
} | |
#textout:focus{ | |
border-top: 3pxss #bdf; | |
border-bottom: 3px solid #bdf; | |
outline: none; | |
} | |
#textout{ | |
margin-top: 10px; | |
border: none; | |
border-top: 3px solid #bdf; | |
border-bottom: 3px solid #bdf; | |
background: #46f; | |
color: #fff; | |
padding: 6px calc(2vw + 8px); | |
margin-left: -9px; | |
margin-right: -12px; | |
width: calc(100vw + 8px); | |
box-sizing: border-box; | |
height: calc(98vh - 90px); | |
} | |
@media only screen and (max-width: 630px) { | |
#textout{ | |
height: calc(98vh - 140px); | |
} | |
.controls, #typingstatus{ | |
margin-top: 10px; | |
} | |
} | |
@media only screen and (max-width: 400px) { | |
#textout{ | |
height: calc(98vh - 200px); | |
} | |
} | |
:root{ | |
--switch-width: 31px; | |
--switch-height: 19px; | |
--switch-slide: 13px; | |
--switch-slide-neg: -13px; | |
--switch-inner: 13px; | |
} | |
var | |
body{ | |
font-family: sans-serif; | |
} | |
.switch { | |
position: relative; | |
display: inline-block; | |
width: var(--switch-width); | |
height: var(--switch-height); | |
} | |
/* Hide default HTML checkbox */ | |
.switch input { | |
opacity: 0; | |
width: 0; | |
height: 0; | |
} | |
/* The slider */ | |
.slider { | |
position: absolute; | |
cursor: pointer; | |
top: 0; | |
left: 0; | |
right: 0; | |
bottom: 0; | |
background-color: #ccc; | |
-webkit-transition: .4s; | |
transition: .4s; | |
} | |
.slider:before { | |
position: absolute; | |
content: ""; | |
height: var(--switch-inner); | |
width: var(--switch-inner); | |
left: 3px; | |
bottom: 3px; | |
background-color: white; | |
-webkit-transition: .4s; | |
transition: .4s; | |
} | |
input:checked + .slider { | |
background-color: #2196F3; | |
} | |
input:focus + .slider { | |
box-shadow: 0 0 1px #2196F3; | |
} | |
input:checked + .slider:before { | |
-webkit-transform: translateX(var(--switch-slide)); | |
-ms-transform: translateX(var(--switch-slide)); | |
transform: translateX(var(--switch-slide)); | |
} | |
/* Rounded sliders */ | |
.slider.round { | |
border-radius: var(--switch-inner); | |
} | |
.slider.round:before { | |
border-radius: 50%; | |
} | |
.checkgroup, select{ | |
margin: 4px 8px; | |
} | |
select{ | |
font-family: monospace; | |
font-weight: bold; | |
border: 2px solid black; | |
} | |
/* END */ | |
</style> | |
</head> | |
<body> | |
<div class="markdown-src"> | |
<!-- HTML --> | |
<span class='h4'>Chord2020 Editor</span> | |
<a href="https://gist.github.com/derekmc/158c0c4474a099bec653a10afc04155f" | |
title="Open Documentation" style="color:blue" target="_blank">Help</a> | |
<div class="controls" style='display: inline-block; float: right;'> | |
<span class='checkgroup' title='"Ctrl + Space"'> | |
<label class="switch"> | |
<input id="show_codes" type="checkbox" checked onchange="WriteText(''); | |
document.getElementById('textout').focus()"> | |
<span class="slider round"></span> | |
</label> | |
<label for="show_codes">Show Codes</label> | |
</span> | |
<span class='checkgroup' title='"Ctrl + Enter"'> | |
<label class="switch"> | |
<input id="enable_chords" type="checkbox" checked | |
onchange="document.getElementById('textout').focus()"> | |
<span class="slider round"></span> | |
</label> | |
<label for="enable_chords">Chording</label> | |
</span> | |
<select id='layout_select' onchange="UpdateLayout()" title='"Ctrl + B"'> | |
<option value='qwerty'>qwerty</option> | |
<option value='dvorak'>dvorak</option> | |
<option value='colemak'>colemak</option> | |
<option value='colemakdh'>colemak-dh</option> | |
<option value='graphite'>graphite</option> | |
<option value='gallium'>gallium</option> | |
<option value='custom'>custom</option> | |
</select> | |
</div> | |
<textarea id='textout' readonly spellcheck="false"></textarea> | |
<button onclick="CutAction()">Cut</button> | |
<button onclick="CopyAction()">Copy</button> | |
<button onclick="PasteAction()">Paste</button> | |
| |
<span id='status' style="font-size: 14px;"></span> | |
<span id='typingstatus' style="float: right; font-size: 12px;"></span> | |
<!-- END --> | |
</div> | |
<div style="display: none;"></div> | |
<script> | |
let datakey = "__HTML_NOTES__:notedataid=AAMxyGuOHr" | |
let inframe = (window!=window.top) | |
window.Note = {} | |
window.Note.data = | |
/* JSON */ | |
{"exclude":["re","pm","jan","uk","non","rss","faq","feb","sep","et","st","aug","apr","en","jul","ny","eur","usr","nj","em","rw","ne","du","nt","es","gb","pr","fr","aa","var","mt","usd","mg","les","ch","sd","rs","avg","src","pda","dsl","sql","ss","ap","nm","mn","nd","op","acc","tn","ce","der","tm","sp","nh","mysql","pdt","db","ia","pt","ds","und","lg","nw","ff","vat","ky","dont","br","lo","ml","res","cs","que","sf","ut","si","mc","qty","lp","ag","vi","edt","pmid","cr","pg","ee","ing","ks","ftp","sw","hd","er","gcc","asp","nv","su","exp","nr","mp","def","nl","tr","bb","nz","te","av","nsw","pci","cst","twiki","ec","rm","pdas","cf","vt","urw","nec","gm","ri","rt","cp","dd","pl","crm","rf","ak","td","sb","sm","wv","ns","bs","hrs","img","rpm","ll","cl","ieee","ae","hs","yr","ic","mx","gr","xhtml","ext","ts","ge","pe","tt","dv","val","fy","rr","ef","jp","bt","rfc","sl","ins","hb","tc","oem","zdnet","oclc","plc","msg","cb","fc","fw","gs","aaa","bp","std","oo","fs","nn","kde","vb","dl","ls","phpbb","je","cms","sg","nav","ist","lc","sys","icq","scsi","cu","pty","ws","np","tft","jvc","dt","gc","ci","yn","vii","cfr","pmc","nb","rx","ddr","this","dec","ii","ca","oct","nov","jun","mon","fri","cnet","ltd","hp","ip","tue","thu","tx","iii","gmt","fl","il","az","th","pp","au","nc","va","rd","sc","se","ga","sa","wi","ct","eq","jd","oe","wy","pd","uc","ld","mf","mw","nu","dp","ht","za","ve","rh","fg","bk","dx","sk","yu","oc","wp","jm","cn","rn","cg","ix","tb","bm","rj","ka","rp","hc","fp","jj","rl","gl","dh","df","pf","uw","bd","hh","fd","wm","pk","wb","sn","dk","fu","wn","sv","zu","dg","vg","ul","js","pn","wt","mv","rb","mh","ww","bw","lf","wr","rg","bl","wx","bg","kw","lm","hl","zus","cet","ppc","ons","tgp","ati","ict","asn","qld","llp","sbjct","apnic"],"wordIndex":1350,"text":"The quick brown fox jumped over the lazy dogs.","cursor":46,"dictionary":"a from look say time about get make see to all give man she two also go many so up and have me some use as he more take very at her my tell want be here new than we because him no that well but his not the what by how now their when can I of them which come if on then who could in one there why day into onto these will do it or they with even its other thing would find just our think year first know out this you for like people those your is was are had word were said an each way write long has did number sound most over water call may down side been any work part place made live where after back little only round came show every good under name through form sentence great help low line differ turn cause much mean before move right boy old too same does set three air play small end put home read hand port large spell add land must big high such follow act ask men change went light kind off need house picture try us again animal point mother world near build self earth father head stand own page should country found answer school grow study still learn plant cover food sun four between state keep eye never last let thought city tree cross farm hard start might story saw far sea draw left late run while press close night real life few north open seem together next white children begin got walk example ease paper group always music both mark often letter until mile river car feet care second book carry took science eat room friend began idea fish mountain stop once base hear horse cut sure watch color face wood main enough plain girl usual young ready above ever red list though feel talk bird soon body dog family direct pose leave song measure door product black short numeral class wind question happen complete ship area half rock order fire south problem piece told knew pass since top whole king space heard best hour better true during hundred five remember step early hold west ground interest reach fast verb sing listen six table travel less morning ten simple several vowel toward war lay against pattern slow center love person money serve appear road map rain rule govern pull cold notice voice unit power town fine certain fly fall lead cry dark machine note wait plan figure star box noun field rest correct able pound done beauty drive stood contain front teach week final gave green oh quick develop ocean warm free minute strong special mind behind clear tail produce fact street inch multiply nothing course stay wheel full force blue object decide surface deep moon island foot system busy test record boat common gold possible plane stead dry wonder laugh thousand ago ran check game shape equate hot miss brought heat snow tire bring yes distant fill east paint language among grand ball yet wave drop heart am present heavy dance engine position arm wide sail material size vary settle speak weight general ice matter circle pair include divide syllable felt perhaps pick sudden count square reason length represent art subject region energy hunt probable bed brother egg ride cell believe fraction forest sit race window store summer train sleep prove lone leg exercise wall catch mount wish sky board joy winter sat written wild instrument kept glass grass cow job edge sign visit past soft fun bright gas weather month million bear finish happy hope flower clothe strange gone jump baby eight village meet root buy raise solve metal whether push seven paragraph third shall held hair describe cook floor either result burn hill safe cat century consider type law bit coast copy phrase silent tall sand soil roll temperature finger industry value fight lie beat excite natural view sense ear else quite broke case middle kill son lake moment scale loud spring observe child straight consonant nation dictionary milk speed method organ pay age section dress cloud surprise quiet stone tiny climb cool design poor lot experiment bottom key iron single stick flat twenty skin smile crease hole trade melody trip office receive row mouth exact symbol die least trouble shout except wrote seed tone join suggest clean break lady yard rise bad blow oil blood touch grew cent mix team wire cost lost brown wear garden equal sent choose fell fit flow fair bank collect save control decimal gentle woman captain practice separate difficult doctor please protect noon whose locate ring character insect caught period indicate radio spoke atom human history effect electric expect crop modern element hit student corner party supply bone rail imagine provide agree thus capital chair danger fruit rich thick soldier process operate guess necessary sharp wing create neighbor wash bat rather crowd corn compare poem string bell depend meat rub tube famous dollar stream fear sight thin triangle planet hurry chief colony clock mine tie enter major fresh search send yellow gun allow print dead spot desert suit current lift rose continue block chart hat sell success company subtract event particular deal swim term opposite wife shoe shoulder spread arrange camp invent cotton born determine quart nine truck noise level chance gather shop stretch throw shine property column molecule select wrong gray repeat require broad prepare salt nose plural anger claim continent oxygen sugar death pretty skill women season solution magnet silver thank branch match suffix especially fig afraid huge sister steel discuss forward similar guide experience score apple bought led pitch coat mass card band rope slip win dream evening condition feed tool total basic smell valley nor double seat arrive master track parent shore division sheet substance favor connect post spend chord fat glad original share station dad bread charge proper bar offer segment slave duck instant market degree populate chick dear enemy reply drink occur support speech nature range steam motion path liquid log meant quotient teeth shell neck information site news contact business web pm online services click service price date email health re used products data policy available copyright message software jan video info rights public books links review years privacy items user de research university january mail reviews program games days management united hotel item international ebay comments development report member details terms hotels local using results education national posted internet address community within states phone dvd shipping reserved forum based code prices website index being file link today technology project pages uk version sports related security county american photo members network computer systems following download without per access resources posts media pictures personal including directory location text rating rate government usa return students shopping account times sites digital profile previous events john hours image department title description non insurance another cd quality listing content private tools customer december movies college article york jobs source author different sale around canada teen stock training credit categories advanced sales english estate conditions windows photos gay thread category gallery register however june october november library really action series model features provided tv required accessories movie forums march la september questions july yahoo going medical dec server pc application cart staff articles san feedback looking issues april users topic comment financial things working standard tax below mobile blog payment equipment login programs offers legal recent park stores memory performance social august quote options rates america important ii activities club girls additional password latest something gift changes ca texas oct poker status browse issue building seller court february audio nov groups al easy given files release analysis request fax china making needs professional areas future committee cards problems london washington meeting rss become id california schools added reference companies listed learning delivery net popular film stories computers journal reports co welcome central images president council away includes australia discussion archive others entertainment agreement format society months safety friends faq edition cars messages marketing further updated association having provides david already studies specific feb living sep collection called arts display limited powered solutions means director daily beach due et electronics upon planning database says official mar average technical france pro microsoft conference environment records st district calendar costs style url statement update parts aug downloads miles resource applications document works bill apr federal hosting rules adult tickets centre requirements via cheap kids finance minutes gifts europe reading topics individual tips plus auto usually edit videos percent function getting global tech economic en player projects lyrics subscribe submit germany amount included risk thanks everything deals various words linux jul production commercial james advertising received treatment newsletter archives points knowledge magazine error camera jun currently construction toys registered golf domain methods chapter makes protection policies loan manager india taken sort listings models michael known cases engineering florida none wireless license paul friday annual published later sony shows corporate google church purchase customers active response hardware materials holiday chat designed along writing html countries loss brand discount higher effects created standards political increase advertise kingdom environmental stuff french storage japan doing loans shoes entry orders availability africa summary growth notes agency monday european activity although drug pics western income cash employment overall bay commission ad package contents seen players album regional supplies started administration institute views plans screen exchange types sponsored lines electronic across benefits needed apply someone ny anything printer effective organization asked eur sunday selection casino pdf tour menu volume anyone mortgage corporation inside mature role weeks addition usr executive running lower union jewelry according dc clothing mon com names robert homepage skills bush islands advice career military rental decision british teens pre facilities zip bid sellers cable opportunities taking values coming tuesday lesbian appropriate logo actually nice statistics client ok returns sample investment shown saturday christmas england culture flash ms george choice starting registration fri thursday courses consumer hi airport foreign artist outside furniture levels channel mode phones ideas wednesday structure fund contract button releases wed homes super male custom virginia almost located multiple asian distribution editor inn industrial potential cnet ltd los hp focus featured rooms female responsible inc communications associated thomas primary cancer numbers browser foundation eg friendly schedule documents communication purpose feature comes police everyone independent ip approach cameras physical operating maps medicine ratings chicago forms tue smith wanted developed unique survey prior telephone sport sources mexico population pa regular secure navigation operations","layoutName":"qwerty","fingerKeys":["de4","fgvc","sw3","q1","ki8","jhnm","lo9","p-"],"dictLoaded":true,"correctLastKey":false,"customLayout":"\n qwert yuiop[]\\\n asdfg hjkl;'\n zxcvb nm,./\n "}; | |
/* 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 */ | |
window.addEventListener('keydown', keydownWindow) | |
async function CopyAction(){ | |
localClipboard = data.text | |
if(navigator.clipboard){ | |
await navigator.clipboard.writeText(localClipboard) | |
} | |
status('Copy') | |
} | |
async function PasteAction(){ | |
if(navigator.clipboard){ | |
localClipboard = await navigator.clipboard.readText() | |
} | |
WriteText(localClipboard) | |
status('Paste') | |
} | |
async function CutAction(){ | |
CopyAction() | |
data.text = '' | |
WriteText('') | |
} | |
async function keydownWindow(e){ | |
if(e.ctrlKey && e.key == 'Enter'){ | |
let boxes = [id('enable_chords')] | |
for(let i=0; i<boxes.length; ++i){ | |
let box = boxes[i] | |
box.checked = !box.checked | |
if(box.checked) break | |
} | |
WriteText('') | |
e.preventDefault() | |
e.stopPropagation() | |
} | |
if(e.ctrlKey && e.key == ' '){ | |
// count through checkboxes in binary | |
id('show_codes').checked = !id('show_codes').checked | |
WriteText('') | |
e.preventDefault() | |
e.stopPropagation() | |
} | |
if(e.ctrlKey && e.key.toLowerCase() == 'x'){ | |
await CutAction() | |
} | |
if(e.ctrlKey && e.key.toLowerCase() == 'c'){ | |
await CopyAction() | |
} | |
if(e.ctrlKey && e.key.toLowerCase() == 'v'){ | |
await PasteAction() | |
} | |
if(e.ctrlKey && e.key.toLowerCase() == 'b'){ | |
NextLayout() | |
} | |
} | |
// start with this dictionary | |
let dict0 = qw` | |
a from look say time about get make see to | |
all give man she two also go many so up | |
and have me some use as he more take very | |
at her my tell want be here new than we | |
because him no that well but his not the what | |
by how now their when can I of them which | |
come if on then who could in one there why | |
day into onto these will do it or they with | |
even its other thing would find just our think year | |
first know out this you for like people those your | |
` | |
// additional dictionaries | |
// the first few dictionaries are sorted by frequency (deekayen, first20hours) | |
// the later dictionaries should be sorted by word length | |
// sort mode | |
let SORT_NONE = 0, SORT_LENGTH = 1 | |
let DICT_CACHE_KEY_PREFIX = "__DICTIONARY_CACHE__" | |
let dictionaryURLs = [ | |
[SORT_NONE, 'https://gist.githubusercontent.com/deekayen/4148741/raw/98d35708fa344717d8eee15d11987de6c8e26d7d/1-1000.txt'], | |
[SORT_NONE, 'https://raw.githubusercontent.com/first20hours/google-10000-english/refs/heads/master/google-10000-english-no-swears.txt'], | |
[SORT_NONE, 'https://raw.githubusercontent.com/first20hours/google-10000-english/refs/heads/master/20k.txt'], | |
[SORT_LENGTH, 'https://raw.githubusercontent.com/dolph/dictionary/refs/heads/master/popular.txt'], | |
[SORT_LENGTH, 'https://raw.githubusercontent.com/dolph/dictionary/refs/heads/master/ospd.txt'], | |
] | |
let shiftMap = `\`~1!2@3#4$5%6^7&8*9(0)-_=+ | |
[{]}\\|;:'",<.>/?`.replaceAll(/\s+/g, '') | |
let singleLayouts = { | |
qwerty: ` | |
qwert yuiop[]\\ | |
asdfg hjkl;' | |
zxcvb nm,./ | |
`, | |
dvorak: ` | |
',.py fgcrl/=\\ | |
aoeui dhtns- | |
;qjkx bmwvz`, | |
colemak: ` | |
qwfpg jluy;[]\\ | |
arstd hneio' | |
zxcvb km,./`, | |
colemakdh: ` | |
qwfpb jluy;[]\\ | |
arstg mneio' | |
xcdvz kh,./`, | |
graphite: ` | |
bldwz 'fouj;=\\ | |
nrtsg yhaei, | |
qxmcv kp.-/`, | |
gallium:` | |
bldcv jfou,[]\\ | |
nrtsg yhaei- | |
xqmwz kp';.` | |
} | |
singleLayouts.custom = singleLayouts.qwerty | |
function UpdateLayout(){ | |
data.layoutName = id('layout_select').value | |
} | |
function NextLayout(){ | |
let names = Object.keys(singleLayouts) | |
let index = names.indexOf(data.layoutName) | |
if(index < 0) index = 0 | |
else index = (index + 1) % names.length | |
while(names[index].length == 0) | |
index = (index + 1) % names.length | |
data.layoutName = names[index] | |
id('layout_select').value = data.layoutName | |
status('Layout: ' + data.layoutName) | |
} | |
function MapLayout(name, ch){ | |
if(name == 'qwerty') | |
return ch | |
if(ch.length > 1) | |
return ch | |
let layout | |
if(data.layoutName =="custom") | |
layout = data.customLayout.replace(/\s/g, '') | |
else | |
layout = singleLayouts[name].replace(/\s/g, '') | |
let qwerty = singleLayouts.qwerty.replace(/\s/g, '') | |
if(!layout) return ch | |
let shiftCh = MapUnshift(ch) | |
if(shiftCh != ch){ | |
let index = qwerty.indexOf(shiftCh) | |
return MapShift(layout[index]) | |
} | |
let index = qwerty.indexOf(ch) | |
if(index < 0) return ch | |
return layout[index] | |
} | |
function MapUnshift(ch){ | |
let index = shiftMap.indexOf(ch) | |
if(index < 0) return ch.toLowerCase() | |
if(index%2 == 0) return ch | |
return shiftMap[index - 1] | |
} | |
function MapShift(ch){ | |
let index = shiftMap.indexOf(ch) | |
if(index < 0) return ch ? ch.toUpperCase() : '' | |
if(index % 2==1) return ch | |
return shiftMap[index+1] | |
} | |
//let data = {} | |
let data = Note.data | |
if(!data.text) data.text = '' | |
if(!data.cursor) data.cursor = data.text.length | |
if(!data.dictionary) data.dictionary = [] | |
if(!data.layoutName) data.layoutName = 'qwerty' | |
if(!data.fingerKeys) data.fingerKeys = qw`de4 fgvc sw3 q1 ki8 jhnm lo9 p-` | |
if(!data.dictLoaded) data.dictLoaded = false | |
if(data.customLayout) singleLayouts.custom = data.customLayout | |
if(!data.hasOwnProperty('correctLastKey')) data.correctLastKey = false | |
let dict = dict0.slice() | |
let dictLookup = {} | |
let chordOrder = [] | |
// keyAddress is how chords are made, | |
// an address is the possible finger positions for | |
// a given finger. | |
let keyAddress = {} | |
let fingerCount = 0 | |
let fingerState = [0,0,0,0, 0,0,0,0] | |
let holdState = [0,0,0,0, 0,0,0,0] // to enable rolling through chords | |
let freezeState = [0,0,0,0, 0,0,0,0] | |
let shiftPress = false | |
let maxFingers = 0 | |
let lastKey = null | |
let ctrlShortcuts = [' ', 'c', 'v', 'x', 'b', '\n', 'enter'] | |
let nonAlphanums = /[^\p{N}\p{L}']+/u | |
let localClipboard = 'clipboard' | |
let textPreview = '' | |
let textCodes = '' | |
let base36 = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ' | |
let cursorDownMove = 25 | |
// the 2 and 3 key combos are created first, | |
// so that you can still use on limited keyboards. | |
let maxKeys = 6 | |
//extra banned chords | |
let banList = qw`` | |
// manual substitutions, removals and additions | |
let exclude_words = qw` | |
re pm jan uk non rss faq feb sep et st aug apr en jul ny eur | |
usr nj em rw ne du nt es gb pr fr aa var mt usd mg les ch sd | |
rs avg src pda dsl sql ss ap nm mn nd op acc tn ce der tm sp | |
nh mysql pdt db ia pt ds und lg nw ff ky dont br ml res cs sf | |
ut ag edt pmid cr pg ee ing ks sw hd er gcc asp nv su exp nr mp | |
def nl tr bb nz te av nsw pci cst twiki ec rm pdas cf vt urw nec | |
gm ri rt cp dd pl crm rf ak td sb sm wv ns bs hrs img rpm ll cl | |
ieee ae hs yr ic mx gr xhtml ext ts ge pe tt dv val fy rr ef jp | |
bt rfc sl ins hb tc oem zdnet oclc plc msg cb fc fw gs bp std oo | |
fs nn kde vb dl ls phpbb je cms sg nav ist lc sys icq scsi cu | |
pty ws np tft jvc dt gc ci yn vii cfr pmc nb rx ddr dec ii | |
ca oct nov jun mon fri cnet ltd hp ip tue thu tx iii gmt fl il | |
az th pp au nc va rd sc se ga sa wi ct eq jd oe wy pd uc ld mf | |
mw nu dp ht za ve rh fg bk dx sk yu oc wp jm cn rn cg ix tb bm | |
rj ka rp hc fp jj rl gl dh df pf uw bd hh fd wm pk wb sn dk fu | |
wn sv zu dg vg ul js pn wt mv rb mh ww bw lf wr rg bl wx bg kw | |
lm hl zus cet ppc ons tgp ati ict asn qld llp sbjct apnic cdna | |
qt dts pgp cz ob aaa mediawiki | |
wto ccd aud fcc eds gpl ac af gmc gtk loc oxley arxiv ru lu tg | |
pv vw dw jl mk wl sz bf sj ij xd wh xr bn wk jb jw xt fb ua kc | |
kt tw gw ao tl lr nf pw wg lv hf wd lj ke lx bv tk gf vm jh uu | |
kr jt dn hw xm fn ib gh gn yy gu ay jf kk bh kl pu px cy lh kd | |
xv ji hj sx rk ux vu sy ik hx ov cx fh ej dy qb jg xf hn vv wf | |
oa iu vf zz kj kv kx eo lw ju vo xy uf kh vn wj lq hy ih nk ud | |
lk kp vx gk hv aq gx fj gj gz tz xa ue kn py vl dq pq ub xu bx | |
jn dz zh kz cq xb qd iz gv ck ki bj ku vid bbw wal rrp ooo ste | |
tba boc tmp ala cio enb ata wma cir dsc wan sen dat ind qui slr | |
rel ign att ffl ppp ies xsl ctr vcd mst dmx str obj auf pkg sch | |
mfg blk bsd adv imc pcr mmc rms ont hsn sgh een rdf liu gta abt | |
svn twp atx bst nsf dcr kvm sts goa thr emi dst msa pcb crc chf | |
clr dba cdr agp exc fra rcw aps iis jsp dlp afp nfs lac edi pid | |
xii emc aix psc icc iec esa uml spi ppl hcl abn mfr afb tcl qos | |
dmc asc nsu wks rbi ics apa phe ses bsc bbb nws ngc kos psu ogg | |
tyr lsu uid umd gdb rpc thb idg dds cpc alr cme ecc crs ssi aac | |
cbd rfp sle scr pdb svc dcp pte ors cps fte stl gsa otc ssa itu | |
mpi mps nwt ths hgh oss wmv mgm csa nrc chr scm fno spd adc ubc | |
csu fsa xiv sgd ftd smb bse itk kms thn kpx spp mfc nmr mso htm | |
dhs msu fsb cns omb cpl sbs swf ipb tsn bdd csc gln stp avr cts | |
wmd cfs ffi tcm prc afc srl thx xvi ttl bmp dss ssk dsm spc pcg | |
snp adm snr pps ntp opp lps pdp gpo cpp crn mmf rtf bmc csr dvb | |
cdp mev sdn mta esd gnd osu utp jpy ccm bmg jbl pcm dtd hpa pvt | |
atp tec doi dsp dll iss cmp ich sbc dei gsm buf pts sst teh acct | |
adsl cdrw cdma dmoz divx embl exif hklm incl msdn mpls ngos staa | |
uefa undp wolverhampton commentsblog verzeichnis viewpicture | |
christchurch oscommerce viewsonic ret xtc addr attr ostg xnxx | |
xxxx xvid xslt xiii yyyy devel endif fomit bizkit bibtex fulfil | |
xenical zoofilia gcse engl mgmt odbc wwii sublimedirectory | |
` | |
let include_words = qw` | |
I'm you're there's he's she's we're can't won't wouldn't | |
couldn't I'd I'll isn't hadn't hasn't haven't he'd he'll | |
weren't let's didn't doesn't don't he'd she'd they'll that's | |
they'd who's you've you're you'll we've I've that's we're | |
lawns populous kennels obscurely recesses groping booming | |
hallow dedicate consecrated consecrate perish detract nobly | |
artesian hairless housemaids yelped fearful puget interlacing | |
boughs brooms mops ysabel stables pastures orchards arbors | |
foostep footsteps resided stalking elmo imperiously northland | |
terriers gravelled glimpses plunged rambles wintry grandsons | |
paddocks insular tubbing steamship sated aristocrat kindred | |
klondike besetted gruffly conveyance grunted growled menacingly | |
besetting damnation outreached vilely futilely chinked jolted | |
outhouses poplars grappled unbridled wolfish vicarious | |
assailants combatants tumbled intentness yelping onlooking | |
trampled defless cunningly deathless sorely buckles bristling | |
draught wagged appeasingly appeasement inoffensive antagonist | |
confines consternation disconsolate glowed warmly unmolested | |
reassured arduous confidently wriggled squirmed placatingly | |
diabolically introspective leaped ignominiously forlorn harking | |
spasmodically forlorn especial drooping ascended ventured | |
gladdened eagerness trice bristled snarled whirled roused | |
forbiddingly goldseekers snowdrifts snowdrift forebears webbed | |
fringed fastidiousness adaptability southland retrogression | |
toiled blunderer farthest loathsome indigestible unsuspected | |
heralded acuteness leeward cadences howled wolflike pasiveness | |
unconcern malingerer accommodated decivilization quickened | |
primeval heredity stoutest nutriment stiffness passiveness | |
trouncing comradeship fiercely | |
peculiarity surged clamor wrestled perpendicular sheltering | |
divined unheeded goaded lashings coverings limped whimpered | |
roared pandemonium resounding rampant forgetfulness swiftly | |
skulking marauders mornful contemplation treacherously overthrowing | |
resolutely thrusting crackled brutes frothing unpursued | |
harnessing worthily prospered harnesses daunted frowning fierceness | |
dubiously moccasins tamed deliberateness masterful preeminently | |
precipitate rashness stiffened braced warily greviously shrill | |
skirted wailings ordained insubordination softened unresisting | |
rejoinder appealingly shirks descended blundered uncontent | |
unnumbered moaned sobbed craftily borealis snowfall unswerving | |
chuckling overthrow curving bubbling enveloped joyfully shirked | |
quarrelled bickered unaltered squabbling barbarous faithfully | |
slyly ploughed snowshoe strove countered clashed bubbled grievously | |
mournful quarrelling jangling overthrown prostrate harked gulped | |
swaggering gripped purposed recuperated journeyers freighted | |
antagonists inexorable climes quivered keenly practised brooded | |
ghostly shrewdly hurled abjectly whined exultantly flitting | |
overhanging shrieked solidly leaden writhed expectant churned | |
remnant floundered sleds lingered uneasily gleefully scruff | |
threateningly whereupon clubbing undervalued dreamily alertness | |
infrequent stoppages stringy knotty uttered peered clutching | |
springiness resiliency catlike mightily excelled monotonous oftener | |
triumphantly unapproachable perplexed convulsive grinned sheepishly | |
deluged worshipful mushers contend roundly aspired busters seeming | |
contend involuntarily halted tinkled mournfully retraced hurriedly | |
whips unfastened boomed skyward sagged trembled locomotives | |
tormentor wrappings throttled grumbled tormentors unkempt assailed | |
sullenly parched fanned surcharged enthusiastically fearlessly | |
patted pounded fiercer uncouth exclamations yawned underhand | |
culprit bucked incurious throbbed propeller pervaded leashed | |
onlookers excitedly uproariously meditated growls perches gingerly | |
consignment growling splintering fetching oppressed | |
` | |
function MakeDictSubs(){ | |
let xcount = 0 | |
for(let i=0, j=0; i < exclude_words.length && j < include_words.length; ++i, ++j){ | |
let word1 = exclude_words[i].toLowerCase() | |
let word2 = include_words[j] | |
let index1 = dictLookup[word1] | |
let index2 = dictLookup[word2.toLowerCase()] | |
let skip1 = index1 === undefined | |
let skip2 = index2 !== undefined | |
if(skip1 && !skip2){ | |
--j | |
} | |
if(!skip1 && skip2){ | |
--i | |
} | |
if(!skip1 && !skip2){ | |
delete dictLookup[word1] | |
dictLookup[word2.toLowerCase()] = index1 | |
dict[index1] = word2 | |
xcount = j + 1 | |
} | |
} | |
let lastindex = chordOrder.length - 1 | |
for(let j=xcount; j<exclude_words.length; ++j){ | |
let newword = dict[++lastindex] | |
let oldword = exclude_words[j] | |
let index = dictLookup[oldword] | |
delete dictLookup[oldword] | |
dictLookup[newword] = index | |
dict[index] = newword | |
} | |
//console.info('him her his wrestled', | |
// (qw`him her his wrestled`).map(x=>chordOrder[dictLookup[x]])) | |
} | |
window.addEventListener("load", Main) | |
async function Main(){ | |
window.addEventListener('keydown', keydown) | |
window.addEventListener('keyup', keyup) | |
await Init() | |
FingerStatus() | |
id('textout').focus() | |
//id('noscript').innerHTML = '' | |
} | |
function wordFilter(word){ | |
if(word.length == 1){ | |
word = word.toLowerCase() | |
return word=='i' || word=='a' | |
} | |
return true | |
} | |
function AddDictLookup(dict, start){ | |
if(!start) start = 0 | |
for(let i=start; i<dict.length; ++i){ | |
dictLookup[dict[i].toLowerCase()] = i | |
} | |
} | |
// functions that access global variables (or singleton class variables) | |
// should begin with a capital letter. | |
// TODO fallback on cached dictionaries | |
async function LoadDictionaries(){ | |
dict = dict0.slice() | |
dictLookup = {} // clear dict lookup | |
AddDictLookup(dict, 0) | |
let actions = [] | |
let loaded_dicts = [] | |
// try to load dictionaries from localStorage cache first | |
for(let i=0; i<dictionaryURLs.length; ++i){ | |
try{ | |
let url = dictionaryURLs[i][1] | |
let cache_key = DICT_CACHE_KEY_PREFIX + url | |
let dict_text = localStorage.getItem(cache_key) | |
if(!dict_text) continue | |
let d = dict_text.trim().split(nonAlphanums) | |
d = d.filter(wordFilter) | |
let sortlength = dictionaryURLs[i][0] == SORT_LENGTH | |
if(sortlength) | |
d = d.sort((a,b)=>a.length - b.length) | |
loaded_dicts[i] = d | |
}catch(e){ | |
console.error(e) | |
} | |
} | |
// load dictionaries from remote source | |
for(let i=0; i<dictionaryURLs.length; ++i){ | |
actions.push(fetch(dictionaryURLs[i][1]).then(res=>res.text())) | |
} | |
let textvals = await Promise.all(actions) | |
for(let i=0; i<textvals.length; ++i){ | |
let text = textvals[i] | |
let sortlength = dictionaryURLs[i][0] == SORT_LENGTH | |
if(text){ | |
try{ | |
let d; | |
let n = dict.length | |
let url = dictionaryURLs[i][1] | |
let cache_key = DICT_CACHE_KEY_PREFIX + url | |
localStorage.setItem(cache_key, text) | |
d = text.trim().split(nonAlphanums) | |
d = d.filter(wordFilter) | |
if(sortlength) | |
d = d.sort((a,b)=>a.length - b.length) | |
loaded_dicts[i] = d | |
}catch(e){ | |
console.error(e) | |
} | |
} | |
} | |
for(let i=0; i<loaded_dicts.length; ++i){ | |
let sortlength = dictionaryURLs[i][0] == SORT_LENGTH | |
let n = dict.length | |
let d = loaded_dicts[i] | |
if(d && d.length > 0){ | |
for(let j=0; j<d.length; ++j){ | |
let word = d[j].toLowerCase() | |
if(include_words.indexOf(word) == -1 && !dictLookup.hasOwnProperty(word)){ | |
dict.push(word)} | |
} | |
AddDictLookup(dict, n) | |
} | |
} | |
data.dictLoaded = true | |
if(data.hasOwnProperty('dictionary') && typeof(data.dictionary) == 'string'){ | |
let saved = data.dictionary.split(' ') | |
if(saved.length > dict.length){ | |
dict = saved } | |
} | |
} | |
function EnumerateChordOrder(){ | |
console.info("Enumerating Chords") | |
let bans = banList | |
chordOrder = [] | |
let threechords = 999 // count the number of 2 and 3 length chords | |
let fingerstates = [] | |
let {fingerKeys} = data | |
let finger_base = fingerKeys.map(x=>x.length + 1) | |
// max dictionary length | |
let n0 = finger_base.reduce(((x,y)=>x*y),1) | |
- finger_base.reduce((x,y)=>x+y-1) - 1 | |
n0 = Math.min(n0, dict.length) | |
let maxscore = 1 + | |
finger_base[0]*finger_base[1]*finger_base[2]*finger_base[3] + | |
finger_base[4]*finger_base[5]*finger_base[6]*finger_base[7] | |
let m0 = 10*n0 // prevent infinite loop | |
// create 2 and 3 key combinations in the first pass, | |
// 4 key in the second pass, etc. | |
for(let mink=2, maxk=3; maxk <= maxKeys; mink = ++maxk){ | |
//console.info(`Enumerating chords ${mink} to ${maxk}`) | |
let n = n0 - chordOrder.length, m = m0 | |
// goes over chords diagonally | |
// counting up on the left hand and down on the right | |
let diagscore = 2 | |
let diagindex = -1 | |
while(n>0 && m > 0){ | |
if(++diagindex == diagscore+1){ | |
if(++diagscore > maxscore) break | |
diagindex = 0 | |
} | |
let b = diagindex | |
let a = diagscore - b | |
let state = fingerKeys.map(x=>0) | |
let f = Math.floor | |
for(let j=0; j<4; ++j){ | |
let c = finger_base[j] | |
let d = finger_base[j + 4] | |
state[j] += a % c | |
state[j + 4] += b % d | |
a = f(a/c) | |
b = f(b/d) | |
} | |
// the | |
let excess = a > 0 || b > 0 | |
let keycount = state.reduce((x,y)=>x + (y? 1 : 0), 0) | |
if(!excess && keycount >= mink && keycount <= maxk){ | |
fingerstates.push(state) | |
let code = ChordCode(state) | |
chordOrder.push(code) | |
--n | |
} | |
else --m | |
} | |
if(maxk == 3) threechords = chordOrder.length | |
console.info(`${chordOrder.length} <= ${maxk}-key chords.`) | |
} | |
let fingermap = x => [x[3], x[2], x[0], x[1], x[5], x[4], x[6], x[7]] | |
let header = ' #n keys fingers code word\n================================================\n' | |
let chordmap = chordOrder.map((x, i)=> '' + | |
('' + (i + 1)).padStart(6,' ') + ' ' | |
+ fingermap(fingerstates[i].map((x, j)=> | |
('_' + fingerKeys[j])[x])).join('') | |
+ ' ' + fingermap(fingerstates[i]).map( | |
(x,i)=>(i==4?' ':'') + x).join('') | |
+ ' ' + x + (x.length ==2? ' ' : '') + ' ' + dict[i]) | |
let maptext = header + chordmap.join('\n') | |
//console.info('chord map\n' + maptext) | |
//let out = id('chordListOut') | |
//out.innerHTML = '' | |
//out.appendChild(document.createTextNode(maptext)) | |
// only save threechord words to local dictionary. | |
data.dictionary = dict.slice(0, threechords).join(' ') | |
} | |
async function Init(){ | |
let {fingerKeys} = data | |
for(let i=0; i<fingerKeys.length; ++i){ | |
//id('fing' + (i+1)).value = fingerKeys[i] | |
} | |
//id('maxKeysInput').value = maxKeys | |
if(true || !data.dictLoaded) | |
await LoadDictionaries() | |
EnumerateChordOrder() | |
MakeDictSubs() | |
keyAddress = {} | |
for(let i=0; i<fingerKeys.length; ++i){ | |
let s = fingerKeys[i] | |
for(let j=0; j<s.length; ++j){ | |
keyAddress[s[j]] = [i, j+1] | |
} | |
} | |
if(!data.customLayout) data.customLayout = singleLayouts.qwerty | |
id('layout_select').value = data.layoutName | |
status('init') | |
} | |
function ChordCode(state){ | |
let {fingerKeys} = data | |
assert(fingerKeys.length >= 8, 'need 8 fingerkey sets') | |
let result = "" | |
let r = fingerKeys[0].length + 1, | |
s = fingerKeys[2].length + 1, | |
t = fingerKeys[4].length + 1, | |
u = fingerKeys[6].length + 1; | |
let a = base36[state[0] + r*state[1]] | |
let b = base36[state[2] + s*state[3]] | |
let c = base36[state[4] + t*state[5]] | |
let d = base36[state[6] + u*state[7]] | |
if(b=='0' && d=='0') | |
return a + c | |
else | |
return a + b + c + d | |
} | |
function TranslateTextToCodes(text){ | |
let alphanum = /[\p{N}\p{L}']/u | |
let result = '' | |
let word = '' | |
let extrach = 0 // additional letters in the original text vs translated text | |
let extraMax = 0 | |
for(let i=0; i<text.length; ++i){ | |
let ch = text[i] | |
if(ch == '\n'){ | |
if(extrach > extraMax){ | |
extrach = extraMax } | |
} | |
if(ch.match(alphanum)) word += ch | |
else{ | |
if(word.length){ | |
let translated = TranslateWordToCodes(word) | |
extrach += word.length - translated.length | |
result += translated | |
while(extrach > extraMax){ | |
result += '_'; --extrach} | |
word = '' | |
} | |
result += ch | |
} | |
} | |
if(word.length){ | |
result += TranslateWordToCodes(word) | |
} | |
return result | |
} | |
//translates a word to one or more codes | |
function TranslateWordToCodes(word){ | |
if(word == 'i') return word // dont translate a lowercase i to a code. | |
let compoundWords = false | |
let getIndex = (w)=>{ | |
let i = dictLookup[w.toLowerCase()] | |
return i < chordOrder.length? i : -1 | |
} | |
let getChord = (i)=>chordOrder[i] | |
let i = getIndex(word) | |
if(i > -1) return getChord(i) | |
if(!compoundWords) return word | |
let result = word | |
let score = word.length + 2 | |
// try split | |
for(let j=1; j<word.length - 1; ++j){ | |
let a = word.substring(0, j) | |
let b = word.substring(j) | |
let k = getIndex(a) | |
let l = getIndex(b) | |
if(k > -1 && l < 0){ | |
if(score > b.length + 4){ | |
score = b.length + 4 | |
result = getChord(k) + b | |
} | |
} | |
if(k < 0 && l > -1){ | |
if(score > a.length + 4){ | |
score = a.length + 4 | |
result = a + getChord(l) | |
} | |
} | |
if(k > -1 && l > -1){ | |
if(score >= 8){ | |
score = 8 | |
result = getChord(k) + "|" + getChord(l) | |
} | |
} | |
} | |
return result | |
} | |
function FingerStatus(){ | |
let fs = fingerState | |
fs = '' + fs[3] + fs[2] + fs[0] + fs[1] + fs[5] + fs[4] + fs[6] + fs[7] | |
let code = ChordCode(fingerState.map((x,i)=>x>0? x : holdState[i])) | |
let freezecode = ChordCode(freezeState) | |
let i = chordOrder.indexOf(code) | |
let j = chordOrder.indexOf(freezecode) | |
let word = i >= 0 ? dict[i] : '' | |
if(word.length && shiftPress) | |
word = word[0].toUpperCase() + word.slice(1) | |
let freezeword = j >= 0? dict[j] : '' | |
if(freezeword.length && shiftPress) | |
freezeword = freezeword[0].toUpperCase() + freezeword.slice(1) | |
let lastword = GetLastWord() | |
s = '' | |
s += ` Active word: [${code=='00-00'? '' : code} ${word}]` | |
s +=' Fingers: ' + fs.substr(0,4) + ' ' + fs.substr(4) | |
s += ` Last word: [${LookupWordCode(lastword)} ${lastword}]` | |
id('typingstatus').innerHTML = s | |
let non_alphanum = /[^\p{N}\p{L}']/u | |
let count = | |
holdState.reduce(((x,y)=>x + (y>0? 1:0)), 0) + | |
fingerState.reduce(((x,y)=>x + (y>0? 1:0)), 0) | |
if(count > 1){ | |
let lastspace = data.text.length == 0 || data.text[data.text.length - 1].match(non_alphanum) | |
textPreview = (lastspace? ' ' : '') + '[' + freezecode + ' ' + freezeword + ']' | |
} else { | |
textPreview = '|' | |
} | |
WriteText('') | |
} | |
function keydown(e){ | |
let {key} = e | |
let immediate = ["Backspace", "ArrowLeft", "ArrowRight", "ArrowUp", "ArrowDown", "Home", "End"] | |
let noevent = [" ", "ArrowLeft", "ArrowRight", "ArrowUp", "ArrowDown"] | |
if(noevent.indexOf(key) != -1){ | |
e.preventDefault() | |
e.stopPropagation() | |
} | |
if(e.ctrlKey && ctrlShortcuts.indexOf(key.toLowerCase()) != -1){ | |
return | |
} | |
if(immediate.indexOf(key) != -1 || !id('enable_chords').checked){ | |
enter('textout') | |
return Input(key) | |
} | |
let upper = key.length == 1 && key.match(/[A-Z]/) | |
if(upper) key = key.toUpperCase() | |
if(e.shiftKey){ | |
shiftPress = true | |
status('^^^') | |
} | |
let k = key.toLowerCase() | |
lastKey = k | |
if(k in keyAddress){ | |
enter('textout') | |
e.preventDefault() | |
e.stopPropagation() | |
let [i, j] = keyAddress[k] | |
if(fingerState[i] == 0) ++fingerCount | |
fingerState[i] = j | |
} | |
let holdcount = holdState.reduce( | |
((x,y)=>x + (y>0? 1:0)), 0) | |
if(fingerCount + holdcount >= maxFingers){ | |
freezeState = fingerState.map((x,i)=> | |
x>0? x : holdState[i]) | |
maxFingers = fingerCount + holdcount | |
} | |
FingerStatus() | |
} | |
let statusTimeout = 8000 | |
let clearStatus = null | |
function status(x){ | |
clearTimeout(clearStatus) | |
let y = id('status') | |
y.innerHTML = '' | |
y.appendChild(document.createTextNode(x)) | |
clearStatus = setTimeout(()=>status(''), statusTimeout) | |
} | |
function keyup(e){ | |
let {key} = e | |
if(e.ctrlKey && ctrlShortcuts.indexOf(key.toLowerCase()) != -1){ | |
return | |
} | |
if(!id('enable_chords').checked){ | |
return | |
} | |
let upper = key.length == 1 && key.match(/[A-Z]/) | |
if(upper) key = key.toUpperCase() | |
if(e.shiftKey){ | |
shiftPress = true | |
status('^^^') | |
} | |
if(key == 'Shift') return | |
let k = key.toLowerCase() | |
if(k in keyAddress){ | |
let [i, j] = keyAddress[k] | |
fingerState[i] = 0 | |
if(!data.correctLastKey || k != lastKey){ | |
holdState[i] = j} | |
--fingerCount | |
if(fingerCount == 0){ | |
if(maxFingers > 1){ | |
Emit(freezeState, shiftPress) | |
freezeState = fingerState.slice(0) | |
shiftPress = false | |
status('') | |
}else{ | |
Input(key) | |
shiftPress = false | |
status('') | |
} | |
holdState = fingerState.slice(0) | |
maxFingers = 0 | |
} | |
} else { | |
let exclude = ["Backspace", "ArrowLeft", "ArrowRight", "ArrowUp", "ArrowDown"] | |
if(fingerCount == 0 && maxFingers <= 1 && exclude.indexOf(key) == -1) | |
Input(key) | |
shiftPress = false | |
status('') | |
} | |
FingerStatus() | |
} | |
function WriteText(x){ | |
if(data.cursor > data.text.length) | |
data.cursor = data.text.length | |
if(data.cursor < 0) | |
data.cursor = 0 | |
let before = data.text.substr(0, data.cursor) | |
let after = data.text.substr(data.cursor) | |
data.text = before + x + after | |
data.cursor += x.length | |
before += x | |
let text = before + textPreview + after | |
let translate = TranslateTextToCodes | |
let preword = '' // before and after cursor words | |
let postword = '' | |
for(let i=before.length - 1; i>=0; --i){ | |
if(before[i].match(nonAlphanums)) break | |
preword = before[i] + preword | |
before = before.substr(0, i) | |
} | |
while(after.length > 0){ | |
if(after[0].match(nonAlphanums)) break | |
postword += after[0] | |
after = after.substr(1) | |
} | |
let text2 = translate(before) + preword + textPreview + postword + translate(after) | |
let out = id('textout') | |
out.innerHTML = '' | |
let showCodes = id('show_codes').checked | |
out.appendChild(document.createTextNode(showCodes? text2 : text)) | |
} | |
function Emit(state, shift){ | |
textPreview = '' | |
let a = ChordCode(state) | |
let i = chordOrder.indexOf(a) | |
if(i<0) return | |
let b = dict[i] | |
if(shift) | |
b = b[0].toUpperCase() + b.slice(1) | |
let alphanum = /[\p{N}\p{L}']/u | |
if(data.cursor > 0 && !data.text[data.cursor - 1].match(/[\s]/)) | |
b = ' ' + b | |
WriteText(b + ' ') | |
} | |
function Input(key){ | |
if(key.length == 1){ | |
if(shiftPress) key = MapShift(key) | |
shiftPress = false | |
key = MapLayout(data.layoutName, key) | |
if(key != ' ' && key.match(nonAlphanums) && | |
id('enable_chords').checked && | |
data.cursor > 0 && data.text[data.cursor - 1] == ' '){ | |
Input('Backspace') | |
} | |
WriteText(key) | |
}else{ | |
if(key == 'Home'){ | |
data.cursor = 0 | |
WriteText('') | |
} | |
if(key == 'End'){ | |
data.cursor = data.text.length | |
WriteText('') | |
} | |
if(key == 'ArrowUp'){ | |
if(data.cursor > 0) | |
for(let i=0; i<cursorDownMove && data.cursor > 0; ++i) | |
--data.cursor | |
WriteText('') | |
} | |
if(key == 'ArrowDown'){ | |
if(data.cursor < data.text.length) | |
for(let i=0; i<cursorDownMove && data.cursor < data.text.length; ++i) | |
++data.cursor | |
WriteText('') | |
} | |
if(key == 'ArrowLeft'){ | |
if(data.cursor > 0) --data.cursor | |
WriteText('') | |
} | |
if(key == 'ArrowRight'){ | |
if(data.cursor < data.text.length) ++data.cursor | |
WriteText('') | |
} | |
if(key == 'Backspace'){ | |
let n = data.cursor | |
let before = data.text.substr(0, n) | |
let after = data.text.substr(n) | |
let remove = 1 | |
let alphanum = /[\p{N}\p{L}']/u | |
if(n > 0 && id('enable_chords').checked && data.text[n - 1].match(alphanum)) | |
while(n - remove > 0 && data.text[n - remove - 1].match(alphanum)) | |
++remove | |
n = Math.max(0, n - remove) | |
data.cursor -= remove | |
data.text = before.substring(0, n) + after | |
WriteText('') | |
} | |
if(key=='Enter'){ | |
WriteText('\n') | |
} | |
if(key=="Shift"){ | |
shiftPress = true | |
status('^^^') | |
} | |
} | |
//console.log(key) | |
} | |
function assert(x, msg){ | |
if(x) return | |
if(!msg) msg = "Assertion failed." | |
else msg = "Assertion failed: " + msg | |
console.error(msg) | |
throw new Error(msg) | |
} | |
function GetLastWord(){ | |
let b = data.text.length | |
let alphanum = /[\p{N}\p{L}']/u | |
while(--b >= 0 && !data.text[b].match(alphanum)); | |
let a = b | |
while(--a >= 0 && data.text[a].match(alphanum)); | |
return data.text.substring(a+1, b+1) | |
} | |
function LookupWordCode(s){ | |
//console.log(dictLookup) | |
let i = dictLookup[s.toLowerCase()] | |
if(isNaN(i) || i >=chordOrder.length) return '' | |
return chordOrder[i] | |
} | |
function qw(parts){ | |
return parts.join('').trim().split(/\s+/) | |
} | |
function id(x){ | |
return document.getElementById(x) | |
} | |
function enter(x){ | |
id(x).focus() | |
} | |
/* END */ | |
//} | |
</script> | |
</body> | |
<html> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment