From aeb05336e97fd8745239196ab668b784e4c6b61f Mon Sep 17 00:00:00 2001 From: handlerug Date: Sun, 13 Jun 2021 12:40:06 +0700 Subject: [PATCH] Shortcuts handler This class registers shortcuts and processes key presses. It can also register multiple shortcuts and shortcuts with multiple keys pressed sequentially. Perhaps it should also support shortcuts with multiple keys pressed simultaneously for folks at https://klava.wiki ... --- static/shortcuts.js | 94 +++++++++++ views/stuff.qtpl | 3 +- views/stuff.qtpl.go | 373 ++++++++++++++++++++++---------------------- 3 files changed, 283 insertions(+), 187 deletions(-) create mode 100644 static/shortcuts.js 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:84 +//line views/stuff.qtpl:85 for h := range hyphae.YieldExistingHyphae() { -//line views/stuff.qtpl:84 +//line views/stuff.qtpl:85 qw422016.N().S(`
  • `) -//line views/stuff.qtpl:86 +//line views/stuff.qtpl:87 qw422016.E().S(util.BeautifulName(h.Name)) -//line views/stuff.qtpl:86 +//line views/stuff.qtpl:87 qw422016.N().S(` `) -//line views/stuff.qtpl:87 +//line views/stuff.qtpl:88 if h.BinaryPath != "" { -//line views/stuff.qtpl:87 +//line views/stuff.qtpl:88 qw422016.N().S(` `) -//line views/stuff.qtpl:88 +//line views/stuff.qtpl:89 qw422016.E().S(filepath.Ext(h.BinaryPath)[1:]) -//line views/stuff.qtpl:88 +//line views/stuff.qtpl:89 qw422016.N().S(` `) -//line views/stuff.qtpl:89 +//line views/stuff.qtpl:90 } -//line views/stuff.qtpl:89 +//line views/stuff.qtpl:90 qw422016.N().S(`
  • `) -//line views/stuff.qtpl:91 +//line views/stuff.qtpl:92 } -//line views/stuff.qtpl:91 +//line views/stuff.qtpl:92 qw422016.N().S(`
`) -//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 }