diff --git a/static/shortcuts.js b/static/shortcuts.js new file mode 100644 index 0000000..012bcb1 --- /dev/null +++ b/static/shortcuts.js @@ -0,0 +1,94 @@ +const $ = document.querySelector.bind(document); +const $$ = document.querySelectorAll.bind(document); + +function keyEventToShortcut(event) { + let elideShift = event.key.toUpperCase() === event.key && event.shiftKey; + return (event.ctrlKey ? 'Ctrl+' : '') + + (event.altKey ? 'Alt+' : '') + + (event.metaKey ? 'Meta+' : '') + + (!elideShift && event.shiftKey ? 'Shift+' : '') + + event.key; +} + +function isTextField(element) { + let name = element.nodeName.toLowerCase(); + return name === 'textarea' || + name === 'select' || + (name === 'input' && !['submit', 'reset', 'checkbox', 'radio'].includes(element.type)) || + element.isContentEditable; +} + +class ShortcutHandler { + constructor(element, filter = () => {}) { + this.element = element; + this.map = {}; + this.active = this.map; + this.filter = filter; + this.timeout = null; + + this.handleKeyDown = this.handleKeyDown.bind(this); + this.resetActive = this.resetActive.bind(this); + this.addEventListeners(); + } + + addEventListeners() { + this.element.addEventListener('keydown', this.handleKeyDown); + } + + add(text, action) { + let shortcuts = text.split(',').map(shortcut => shortcut.trim().split(' ')); + + for (let shortcut of shortcuts) { + let node = this.map; + for (let key of shortcut) { + if (!node[key]) { + node[key] = {}; + } + node = node[key]; + if (node.action) { + delete node.action; + } + } + + node.action = action; + } + } + + handleKeyDown(event) { + if (event.defaultPrevented) return; + if (['Control', 'Alt', 'Shift', 'Meta'].includes(event.key)) return; + if (!this.filter(event)) return; + + let shortcut = keyEventToShortcut(event); + + if (!this.active[shortcut]) { + this.resetActive(); + return; + } + + this.active = this.active[shortcut]; + if (this.active.action) { + this.active.action(event); + this.resetActive(); + return; + } + + if (this.timeout) clearTimeout(this.timeout); + this.timeout = window.setTimeout(this.resetActive, 1500); + } + + resetActive() { + this.active = this.map; + if (this.timeout) { + clearTimeout(this.timeout) + this.timeout = null; + } + } +} + +let notFormField = event => !(event.target instanceof Node && isTextField(event.target)); +let globalShortcuts = new ShortcutHandler(document, notFormField); + +globalShortcuts.add('p', () => alert('hello p')); +globalShortcuts.add('h', () => alert('hi h!')); +globalShortcuts.add('g h', () => alert('hi g h!!!')); \ No newline at end of file diff --git a/views/stuff.qtpl b/views/stuff.qtpl index f3af9e1..0e75d94 100644 --- a/views/stuff.qtpl +++ b/views/stuff.qtpl @@ -12,7 +12,8 @@ {%s title %} - + + {% for _, el := range headElements %}{%s= el %}{% endfor %} diff --git a/views/stuff.qtpl.go b/views/stuff.qtpl.go index 8ab0478..5a255bb 100644 --- a/views/stuff.qtpl.go +++ b/views/stuff.qtpl.go @@ -47,15 +47,16 @@ func StreamBaseHTML(qw422016 *qt422016.Writer, title, body string, u *user.User, //line views/stuff.qtpl:13 qw422016.N().S(` - + + `) -//line views/stuff.qtpl:16 +//line views/stuff.qtpl:17 for _, el := range headElements { -//line views/stuff.qtpl:16 +//line views/stuff.qtpl:17 qw422016.N().S(el) -//line views/stuff.qtpl:16 +//line views/stuff.qtpl:17 } -//line views/stuff.qtpl:16 +//line views/stuff.qtpl:17 qw422016.N().S(` @@ -63,81 +64,81 @@ func StreamBaseHTML(qw422016 *qt422016.Writer, title, body string, u *user.User, `) -//line views/stuff.qtpl:29 +//line views/stuff.qtpl:30 qw422016.N().S(body) -//line views/stuff.qtpl:29 +//line views/stuff.qtpl:30 qw422016.N().S(` `) -//line views/stuff.qtpl:30 +//line views/stuff.qtpl:31 streamomnipresentScripts(qw422016) -//line views/stuff.qtpl:30 +//line views/stuff.qtpl:31 qw422016.N().S(` `) -//line views/stuff.qtpl:33 +//line views/stuff.qtpl:34 } -//line views/stuff.qtpl:33 +//line views/stuff.qtpl:34 func WriteBaseHTML(qq422016 qtio422016.Writer, title, body string, u *user.User, headElements ...string) { -//line views/stuff.qtpl:33 +//line views/stuff.qtpl:34 qw422016 := qt422016.AcquireWriter(qq422016) -//line views/stuff.qtpl:33 +//line views/stuff.qtpl:34 StreamBaseHTML(qw422016, title, body, u, headElements...) -//line views/stuff.qtpl:33 +//line views/stuff.qtpl:34 qt422016.ReleaseWriter(qw422016) -//line views/stuff.qtpl:33 +//line views/stuff.qtpl:34 } -//line views/stuff.qtpl:33 +//line views/stuff.qtpl:34 func BaseHTML(title, body string, u *user.User, headElements ...string) string { -//line views/stuff.qtpl:33 +//line views/stuff.qtpl:34 qb422016 := qt422016.AcquireByteBuffer() -//line views/stuff.qtpl:33 +//line views/stuff.qtpl:34 WriteBaseHTML(qb422016, title, body, u, headElements...) -//line views/stuff.qtpl:33 +//line views/stuff.qtpl:34 qs422016 := string(qb422016.B) -//line views/stuff.qtpl:33 +//line views/stuff.qtpl:34 qt422016.ReleaseByteBuffer(qb422016) -//line views/stuff.qtpl:33 +//line views/stuff.qtpl:34 return qs422016 -//line views/stuff.qtpl:33 +//line views/stuff.qtpl:34 } -//line views/stuff.qtpl:35 +//line views/stuff.qtpl:36 func StreamUserListHTML(qw422016 *qt422016.Writer) { -//line views/stuff.qtpl:35 +//line views/stuff.qtpl:36 qw422016.N().S(`

List of users

`) -//line views/stuff.qtpl:40 +//line views/stuff.qtpl:41 var ( admins = make([]string, 0) moderators = make([]string, 0) @@ -154,303 +155,303 @@ func StreamUserListHTML(qw422016 *qt422016.Writer) { } } -//line views/stuff.qtpl:55 +//line views/stuff.qtpl:56 qw422016.N().S(`

Admins

    `) -//line views/stuff.qtpl:58 +//line views/stuff.qtpl:59 for _, name := range admins { -//line views/stuff.qtpl:58 +//line views/stuff.qtpl:59 qw422016.N().S(`
  1. `) -//line views/stuff.qtpl:59 +//line views/stuff.qtpl:60 qw422016.E().S(name) -//line views/stuff.qtpl:59 +//line views/stuff.qtpl:60 qw422016.N().S(`
  2. `) -//line views/stuff.qtpl:60 +//line views/stuff.qtpl:61 } -//line views/stuff.qtpl:60 +//line views/stuff.qtpl:61 qw422016.N().S(`

Moderators

    `) -//line views/stuff.qtpl:64 +//line views/stuff.qtpl:65 for _, name := range moderators { -//line views/stuff.qtpl:64 +//line views/stuff.qtpl:65 qw422016.N().S(`
  1. `) -//line views/stuff.qtpl:65 +//line views/stuff.qtpl:66 qw422016.E().S(name) -//line views/stuff.qtpl:65 +//line views/stuff.qtpl:66 qw422016.N().S(`
  2. `) -//line views/stuff.qtpl:66 +//line views/stuff.qtpl:67 } -//line views/stuff.qtpl:66 +//line views/stuff.qtpl:67 qw422016.N().S(`

Editors

    `) -//line views/stuff.qtpl:70 +//line views/stuff.qtpl:71 for _, name := range editors { -//line views/stuff.qtpl:70 +//line views/stuff.qtpl:71 qw422016.N().S(`
  1. `) -//line views/stuff.qtpl:71 +//line views/stuff.qtpl:72 qw422016.E().S(name) -//line views/stuff.qtpl:71 +//line views/stuff.qtpl:72 qw422016.N().S(`
  2. `) -//line views/stuff.qtpl:72 +//line views/stuff.qtpl:73 } -//line views/stuff.qtpl:72 +//line views/stuff.qtpl:73 qw422016.N().S(`
`) -//line views/stuff.qtpl:76 +//line views/stuff.qtpl:77 } -//line views/stuff.qtpl:76 +//line views/stuff.qtpl:77 func WriteUserListHTML(qq422016 qtio422016.Writer) { -//line views/stuff.qtpl:76 +//line views/stuff.qtpl:77 qw422016 := qt422016.AcquireWriter(qq422016) -//line views/stuff.qtpl:76 +//line views/stuff.qtpl:77 StreamUserListHTML(qw422016) -//line views/stuff.qtpl:76 +//line views/stuff.qtpl:77 qt422016.ReleaseWriter(qw422016) -//line views/stuff.qtpl:76 +//line views/stuff.qtpl:77 } -//line views/stuff.qtpl:76 +//line views/stuff.qtpl:77 func UserListHTML() string { -//line views/stuff.qtpl:76 +//line views/stuff.qtpl:77 qb422016 := qt422016.AcquireByteBuffer() -//line views/stuff.qtpl:76 +//line views/stuff.qtpl:77 WriteUserListHTML(qb422016) -//line views/stuff.qtpl:76 +//line views/stuff.qtpl:77 qs422016 := string(qb422016.B) -//line views/stuff.qtpl:76 +//line views/stuff.qtpl:77 qt422016.ReleaseByteBuffer(qb422016) -//line views/stuff.qtpl:76 +//line views/stuff.qtpl:77 return qs422016 -//line views/stuff.qtpl:76 +//line views/stuff.qtpl:77 } -//line views/stuff.qtpl:78 +//line views/stuff.qtpl:79 func StreamHyphaListHTML(qw422016 *qt422016.Writer) { -//line views/stuff.qtpl:78 +//line views/stuff.qtpl:79 qw422016.N().S(`

List of hyphae

This wiki has `) -//line views/stuff.qtpl:82 +//line views/stuff.qtpl:83 qw422016.N().D(hyphae.Count()) -//line views/stuff.qtpl:82 +//line views/stuff.qtpl:83 qw422016.N().S(` hyphae.

`) -//line views/stuff.qtpl:95 +//line views/stuff.qtpl:96 } -//line views/stuff.qtpl:95 +//line views/stuff.qtpl:96 func WriteHyphaListHTML(qq422016 qtio422016.Writer) { -//line views/stuff.qtpl:95 +//line views/stuff.qtpl:96 qw422016 := qt422016.AcquireWriter(qq422016) -//line views/stuff.qtpl:95 +//line views/stuff.qtpl:96 StreamHyphaListHTML(qw422016) -//line views/stuff.qtpl:95 +//line views/stuff.qtpl:96 qt422016.ReleaseWriter(qw422016) -//line views/stuff.qtpl:95 +//line views/stuff.qtpl:96 } -//line views/stuff.qtpl:95 +//line views/stuff.qtpl:96 func HyphaListHTML() string { -//line views/stuff.qtpl:95 +//line views/stuff.qtpl:96 qb422016 := qt422016.AcquireByteBuffer() -//line views/stuff.qtpl:95 +//line views/stuff.qtpl:96 WriteHyphaListHTML(qb422016) -//line views/stuff.qtpl:95 +//line views/stuff.qtpl:96 qs422016 := string(qb422016.B) -//line views/stuff.qtpl:95 +//line views/stuff.qtpl:96 qt422016.ReleaseByteBuffer(qb422016) -//line views/stuff.qtpl:95 +//line views/stuff.qtpl:96 return qs422016 -//line views/stuff.qtpl:95 +//line views/stuff.qtpl:96 } -//line views/stuff.qtpl:97 +//line views/stuff.qtpl:98 func StreamAboutHTML(qw422016 *qt422016.Writer) { -//line views/stuff.qtpl:97 +//line views/stuff.qtpl:98 qw422016.N().S(`

About `) -//line views/stuff.qtpl:101 +//line views/stuff.qtpl:102 qw422016.E().S(cfg.WikiName) -//line views/stuff.qtpl:101 +//line views/stuff.qtpl:102 qw422016.N().S(`

See /list for information about hyphae on this wiki.

`) -//line views/stuff.qtpl:119 +//line views/stuff.qtpl:120 } -//line views/stuff.qtpl:119 +//line views/stuff.qtpl:120 func WriteAboutHTML(qq422016 qtio422016.Writer) { -//line views/stuff.qtpl:119 +//line views/stuff.qtpl:120 qw422016 := qt422016.AcquireWriter(qq422016) -//line views/stuff.qtpl:119 +//line views/stuff.qtpl:120 StreamAboutHTML(qw422016) -//line views/stuff.qtpl:119 +//line views/stuff.qtpl:120 qt422016.ReleaseWriter(qw422016) -//line views/stuff.qtpl:119 +//line views/stuff.qtpl:120 } -//line views/stuff.qtpl:119 +//line views/stuff.qtpl:120 func AboutHTML() string { -//line views/stuff.qtpl:119 +//line views/stuff.qtpl:120 qb422016 := qt422016.AcquireByteBuffer() -//line views/stuff.qtpl:119 +//line views/stuff.qtpl:120 WriteAboutHTML(qb422016) -//line views/stuff.qtpl:119 +//line views/stuff.qtpl:120 qs422016 := string(qb422016.B) -//line views/stuff.qtpl:119 +//line views/stuff.qtpl:120 qt422016.ReleaseByteBuffer(qb422016) -//line views/stuff.qtpl:119 +//line views/stuff.qtpl:120 return qs422016 -//line views/stuff.qtpl:119 +//line views/stuff.qtpl:120 } -//line views/stuff.qtpl:121 +//line views/stuff.qtpl:122 func StreamAdminPanelHTML(qw422016 *qt422016.Writer) { -//line views/stuff.qtpl:121 +//line views/stuff.qtpl:122 qw422016.N().S(`
@@ -487,80 +488,80 @@ func StreamAdminPanelHTML(qw422016 *qt422016.Writer) {
`) -//line views/stuff.qtpl:156 +//line views/stuff.qtpl:157 } -//line views/stuff.qtpl:156 +//line views/stuff.qtpl:157 func WriteAdminPanelHTML(qq422016 qtio422016.Writer) { -//line views/stuff.qtpl:156 +//line views/stuff.qtpl:157 qw422016 := qt422016.AcquireWriter(qq422016) -//line views/stuff.qtpl:156 +//line views/stuff.qtpl:157 StreamAdminPanelHTML(qw422016) -//line views/stuff.qtpl:156 +//line views/stuff.qtpl:157 qt422016.ReleaseWriter(qw422016) -//line views/stuff.qtpl:156 +//line views/stuff.qtpl:157 } -//line views/stuff.qtpl:156 +//line views/stuff.qtpl:157 func AdminPanelHTML() string { -//line views/stuff.qtpl:156 +//line views/stuff.qtpl:157 qb422016 := qt422016.AcquireByteBuffer() -//line views/stuff.qtpl:156 +//line views/stuff.qtpl:157 WriteAdminPanelHTML(qb422016) -//line views/stuff.qtpl:156 +//line views/stuff.qtpl:157 qs422016 := string(qb422016.B) -//line views/stuff.qtpl:156 +//line views/stuff.qtpl:157 qt422016.ReleaseByteBuffer(qb422016) -//line views/stuff.qtpl:156 +//line views/stuff.qtpl:157 return qs422016 -//line views/stuff.qtpl:156 +//line views/stuff.qtpl:157 } -//line views/stuff.qtpl:158 +//line views/stuff.qtpl:159 func streamomnipresentScripts(qw422016 *qt422016.Writer) { -//line views/stuff.qtpl:158 +//line views/stuff.qtpl:159 qw422016.N().S(` `) -//line views/stuff.qtpl:159 +//line views/stuff.qtpl:160 for _, scriptPath := range cfg.OmnipresentScripts { -//line views/stuff.qtpl:159 +//line views/stuff.qtpl:160 qw422016.N().S(` `) -//line views/stuff.qtpl:161 +//line views/stuff.qtpl:162 } -//line views/stuff.qtpl:161 +//line views/stuff.qtpl:162 qw422016.N().S(` `) -//line views/stuff.qtpl:162 +//line views/stuff.qtpl:163 } -//line views/stuff.qtpl:162 +//line views/stuff.qtpl:163 func writeomnipresentScripts(qq422016 qtio422016.Writer) { -//line views/stuff.qtpl:162 +//line views/stuff.qtpl:163 qw422016 := qt422016.AcquireWriter(qq422016) -//line views/stuff.qtpl:162 +//line views/stuff.qtpl:163 streamomnipresentScripts(qw422016) -//line views/stuff.qtpl:162 +//line views/stuff.qtpl:163 qt422016.ReleaseWriter(qw422016) -//line views/stuff.qtpl:162 +//line views/stuff.qtpl:163 } -//line views/stuff.qtpl:162 +//line views/stuff.qtpl:163 func omnipresentScripts() string { -//line views/stuff.qtpl:162 +//line views/stuff.qtpl:163 qb422016 := qt422016.AcquireByteBuffer() -//line views/stuff.qtpl:162 +//line views/stuff.qtpl:163 writeomnipresentScripts(qb422016) -//line views/stuff.qtpl:162 +//line views/stuff.qtpl:163 qs422016 := string(qb422016.B) -//line views/stuff.qtpl:162 +//line views/stuff.qtpl:163 qt422016.ReleaseByteBuffer(qb422016) -//line views/stuff.qtpl:162 +//line views/stuff.qtpl:163 return qs422016 -//line views/stuff.qtpl:162 +//line views/stuff.qtpl:163 }