]> git.gir.st - VimFx.git/blob - extension/lib/help.coffee
Use normal cursor in help dialog
[VimFx.git] / extension / lib / help.coffee
1 ###
2 # Copyright Simon Lydell 2015.
3 #
4 # This file is part of VimFx.
5 #
6 # VimFx is free software: you can redistribute it and/or modify
7 # it under the terms of the GNU General Public License as published by
8 # the Free Software Foundation, either version 3 of the License, or
9 # (at your option) any later version.
10 #
11 # VimFx is distributed in the hope that it will be useful,
12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 # GNU General Public License for more details.
15 #
16 # You should have received a copy of the GNU General Public License
17 # along with VimFx. If not, see <http://www.gnu.org/licenses/>.
18 ###
19
20 # This file creates VimFx’s Keyboard Shortcuts help screen.
21
22 translate = require('./l10n')
23 utils = require('./utils')
24
25 CONTAINER_ID = 'VimFxHelpDialogContainer'
26 MAX_FONT_SIZE = 20
27 SEARCH_MATCH_CLASS = 'search-match'
28 SEARCH_NON_MATCH_CLASS = 'search-non-match'
29 SEARCH_HIGHLIGHT_CLASS = 'search-highlight'
30
31 injectHelp = (window, vimfx) ->
32 removeHelp(window)
33
34 {document} = window
35
36 container = utils.createBox(document)
37 container.id = CONTAINER_ID
38
39 wrapper = utils.createBox(document, 'wrapper', container)
40
41 header = createHeader(document, vimfx)
42 wrapper.appendChild(header)
43
44 content = createContent(document, vimfx)
45 wrapper.appendChild(content)
46
47 searchInput = document.createElement('textbox')
48 utils.setAttributes(searchInput, {
49 class: 'search-input'
50 placeholder: translate('help.search')
51 })
52 searchInput.oninput = -> search(content, searchInput.value.trim())
53 container.appendChild(searchInput)
54
55 window.gBrowser.mCurrentBrowser.parentNode.appendChild(container)
56 searchInput.focus()
57
58 # The font size of menu items is used by default, which is usually quite
59 # small. Try to increase it without causing a scrollbar.
60 computedStyle = window.getComputedStyle(container)
61 fontSize = originalFontSize =
62 parseFloat(computedStyle.getPropertyValue('font-size'))
63 while wrapper.clientHeight < container.clientHeight and
64 fontSize <= MAX_FONT_SIZE
65 fontSize++
66 container.style.fontSize = "#{fontSize}px"
67 container.style.fontSize = "#{Math.max(fontSize - 1, originalFontSize)}px"
68
69 # Uncomment this line if you want to use `gulp help.html`!
70 # utils.writeToClipboard(container.outerHTML)
71
72 removeHelp = (window) ->
73 window.document.getElementById(CONTAINER_ID)?.remove()
74
75 createHeader = (document, vimfx) ->
76 $ = utils.createBox.bind(null, document)
77
78 header = $('header')
79
80 mainHeading = $('heading-main', header)
81 $('logo', mainHeading) # Content is added by CSS.
82 $('title', mainHeading, translate('help.title'))
83
84 closeButton = $('close-button', header, '×')
85 closeButton.onclick = removeHelp.bind(null, document.ownerGlobal)
86
87 return header
88
89 createContent = (document, vimfx) ->
90 $ = utils.createBox.bind(null, document)
91
92 content = $('content')
93
94 for mode in vimfx.getGroupedCommands({enabledOnly: true})
95 modeHeading = $('heading-mode search-item', null, mode.name)
96
97 for category, index in mode.categories
98 categoryContainer = $('category', content)
99 # `data-` attributes are currently unused by VimFx, but provide a great
100 # way to customize the help dialog with custom CSS.
101 utils.setAttributes(categoryContainer, {
102 'data-mode': mode._name
103 'data-category': category._name
104 })
105
106 # Append the mode heading inside the first category container, rather than
107 # before it, for layout purposes.
108 if index == 0
109 categoryContainer.appendChild(modeHeading)
110 categoryContainer.classList.add('first')
111
112 if category.name
113 $('heading-category search-item', categoryContainer, category.name)
114
115 for {command, name, enabledSequences} in category.commands
116 commandContainer = $('command search-item', categoryContainer)
117 utils.setAttributes(commandContainer, {'data-command': command.name})
118 commandContainer.setAttribute('data-command', name)
119 for sequence in enabledSequences
120 keySequence = $('key-sequence', commandContainer)
121 [specialKeys, rest] = splitSequence(sequence, vimfx.SPECIAL_KEYS)
122 $('key-sequence-special-keys', keySequence, specialKeys)
123 $('key-sequence-rest search-text', keySequence, rest)
124 $('description search-text', commandContainer, command.description())
125
126 return content
127
128 splitSequence = (sequence, specialKeys) ->
129 specialKeyEnds = specialKeys.map((key) ->
130 pos = sequence.lastIndexOf(key)
131 return if pos == -1 then 0 else pos + key.length
132 )
133 splitPos = Math.max(specialKeyEnds...)
134 return [sequence[0...splitPos], sequence[splitPos..]]
135
136 search = (content, term) ->
137 document = content.ownerDocument
138 ignoreCase = (term == term.toLowerCase())
139 regex = RegExp("(#{utils.regexEscape(term)})", if ignoreCase then 'i' else '')
140 clear = (term == '')
141
142 for item in content.querySelectorAll('.search-item')
143 texts = item.querySelectorAll('.search-text')
144 texts = [item] if texts.length == 0
145 className = SEARCH_NON_MATCH_CLASS
146
147 for element in texts
148 {textContent} = element
149 # Clear the previous highlighting. This is possible to do for non-matches
150 # as well, but too slow.
151 if item.classList.contains(SEARCH_MATCH_CLASS)
152 element.textContent = textContent
153
154 continue if clear or not regex.test(textContent)
155
156 className = SEARCH_MATCH_CLASS
157 element.textContent = '' # Empty the element.
158 for part, index in textContent.split(regex)
159 # Even indices are surrounding text, odd ones are matches.
160 if index % 2 == 0
161 element.appendChild(document.createTextNode(part))
162 else
163 utils.createBox(document, SEARCH_HIGHLIGHT_CLASS, element, part)
164
165 item.classList.remove(SEARCH_MATCH_CLASS, SEARCH_NON_MATCH_CLASS)
166 item.classList.add(className) unless clear
167
168 return
169
170 module.exports = {
171 injectHelp
172 removeHelp
173 }
Imprint / Impressum