1 # This file constructs VimFx’s options UI in the Add-ons Manager.
3 defaults = require('./defaults')
4 prefs = require('./prefs')
5 prefsBulk = require('./prefs-bulk')
6 translate = require('./translate')
7 utils = require('./utils')
15 observe = (options) ->
16 observer = new Observer(options)
17 utils.observe('vimfx-options-displayed', observer)
18 utils.observe('vimfx-options-hidden', observer)
23 # Generalized observer.
25 constructor: (@options) ->
32 listen: (element, event, action) ->
33 element.addEventListener(event, action, @useCapture)
34 @listeners.push([element, event, action, @useCapture])
37 for [element, event, action, useCapture] in @listeners
38 element.removeEventListener(event, action, useCapture)
41 type: (value) -> TYPE_MAP[typeof value]
45 appendSetting: (attributes) ->
46 outer = @document.createElement('div')
47 outer.classList.add('setting')
48 outer.classList.add('first-row') if attributes['first-row']
49 outer.classList.add(attributes.class)
50 outer.setAttribute('data-pref', attributes.pref)
51 outer.setAttribute('data-type', attributes.type)
52 label = @document.createElement('label')
53 title = @document.createElement('span')
54 title.className = 'title'
55 title.innerText = attributes.title
56 label.appendChild(title)
57 help = @document.createElement('span')
58 help.className = 'desc'
59 help.innerText = attributes.desc or ''
60 input = @document.createElement('input')
62 switch attributes.type
64 input.type = 'checkbox'
65 input.checked = prefs.root.get(attributes.pref)
66 input.addEventListener('change', ->
67 prefs.root.set(attributes.pref, input.checked)
69 prefobserver = prefs.root.observe(attributes.pref, ->
70 input.checked = prefs.root.get(attributes.pref)
72 @document.defaultView.addEventListener('unload', ->
73 prefs?.root.unobserve(attributes.pref, prefobserver)
77 input.value = prefs.root.get(attributes.pref)
78 input.addEventListener('input', ->
79 prefs.root.set(attributes.pref, parseInt(input.value, 10))
81 prefobserver = prefs.root.observe(attributes.pref, ->
82 input.value = prefs.root.get(attributes.pref)
84 @document.defaultView.addEventListener('unload', ->
85 prefs?.root.unobserve(attributes.pref, prefobserver)
89 input.value = prefs.root.get(attributes.pref)
90 input.addEventListener('input', ->
91 prefs.root.set(attributes.pref, input.value)
92 if outer.classList.contains('is-shortcut')
93 _this.refreshShortcutErrors()
95 prefobserver = prefs.root.observe(attributes.pref, ->
96 input.value = prefs.root.get(attributes.pref)
98 @document.defaultView.addEventListener('unload', ->
99 prefs?.root.unobserve(attributes.pref, prefobserver)
101 when 'control' # injectHeader special case
102 control = @document.createElement('span')
103 control.className = 'control'
104 # can't use <label> when control has multiple buttons:
105 label = @document.createElement('span')
106 label.appendChild(title)
107 label.appendChild(control)
108 label.appendChild(input) if attributes.pref # some nodes are just headlines
109 outer.appendChild(label)
110 outer.appendChild(help) # needed even if empty, as validator puts error here
111 @container.appendChild(outer)
114 observe: (@document, topic, addonId) ->
116 when 'vimfx-options-displayed'
118 when 'vimfx-options-hidden'
122 @container = @document.getElementById('detail-rows')
128 # VimFx specific observer.
129 class Observer extends BaseObserver
130 constructor: (@vimfx) ->
131 super({id: @vimfx.id})
139 if @vimfx.goToCommand
140 utils.nextTick(@document.ownerGlobal, =>
141 {pref} = @vimfx.goToCommand
142 setting = @container.querySelector("[data-pref='#{pref}']")
143 setting.scrollIntoView()
144 setting.querySelector('input').select()
145 @vimfx.goToCommand = null
149 setting = @appendSetting({
151 title: translate('prefs.instructions.title')
153 'prefs.instructions.desc',
154 @vimfx.options['options.key.quote'],
155 @vimfx.options['options.key.insert_default'],
156 @vimfx.options['options.key.reset_default'],
161 setting.id = 'header'
163 href = "#{@vimfx.info.homepageURL}/tree/master/documentation#contents"
164 docsLink = @document.createElement('a')
165 docsLink.innerText = translate('prefs.documentation')
166 utils.setAttributes(docsLink, {
170 setting.querySelector('.control').appendChild(docsLink)
172 for key, fn of BUTTONS
173 button = @document.createElement('button')
174 button.innerText = translate("prefs.#{key}.label")
175 button.onclick = runWithVim.bind(null, @vimfx, fn)
176 setting.querySelector('.control').appendChild(button)
181 for key, value of defaults.options
183 pref: "#{defaults.BRANCH}#{key}"
185 title: translate("pref.#{key}.title")
186 desc: translate("pref.#{key}.desc")
191 for mode in @vimfx.getGroupedCommands()
198 for category in mode.categories
206 for {command} in category.commands
210 title: command.description
211 desc: @generateErrorMessage(command.pref)
217 generateErrorMessage: (pref) ->
218 commandErrors = @vimfx.errors[pref] ? []
219 return commandErrors.map(({id, context, subject}) ->
220 return translate("error.#{id}", context ? subject, subject)
224 # Note that `setting = event.originalTarget` does _not_ return the correct
225 # element in these listeners!
227 @listen(@container, 'keydown', (event) =>
229 isString = (input.type == 'text')
231 setting = input.closest('.setting')
232 pref = setting.getAttribute('data-pref')
233 keyString = @vimfx.stringifyKeyEvent(event)
235 # Some shortcuts only make sense for string settings. We still match
236 # those shortcuts and suppress the default behavior for _all_ types of
237 # settings for consistency. For example, pressing <c-d> in a number input
238 # (which looks like a text input) would otherwise bookmark the page, and
239 # <c-q> would close the window!
244 break unless isString
245 utils.insertText(input, keyString)
246 prefs.root.set(pref, input.value)
248 when keyString == @vimfx.options['options.key.quote']
249 break unless isString
251 # Override `<force>` commands (such as `<escape>` and `<tab>`).
252 return unless vim = @vimfx.getCurrentVim(utils.getCurrentWindow())
253 @vimfx.modes.normal.commands.quote.run({vim, count: 1})
254 when keyString == @vimfx.options['options.key.insert_default']
255 break unless isString
256 utils.insertText(input, prefs.root.default.get(pref))
257 prefs.root.set(pref, input.value)
258 when keyString == @vimfx.options['options.key.reset_default']
259 prefs.root.set(pref, null)
263 event.preventDefault()
264 @refreshShortcutErrors()
266 @listen(@container, 'blur', -> quote = false)
268 refreshShortcutErrors: ->
269 for setting in @container.getElementsByClassName('is-shortcut')
270 setting.querySelector('.desc').innerText =
271 @generateErrorMessage(setting.getAttribute('data-pref'))
274 resetAllPrefs = (vim) ->
275 vim._modal('confirm', [translate('prefs.reset.enter')], (ok) ->
278 vim.notify(translate('prefs.reset.success'))
281 exportAllPrefs = (vim) ->
282 exported = prefsBulk.exportAll()
283 if Object.keys(exported).length == 0
284 vim.notify(translate('prefs.export.none'))
286 utils.writeToClipboard(JSON.stringify(exported, null, 2))
287 vim.notify(translate('prefs.export.success'))
289 importExportedPrefs = (vim) ->
290 vim._modal('prompt', [translate('prefs.import.enter')], (input) ->
291 return if input == null or input.trim() == ''
292 result = prefsBulk.importExported(input.trim())
293 if result.errors.length == 0
294 vim.notify(translate('prefs.import.success'))
296 vim._modal('alert', [prefsBulk.createImportErrorReport(result)])
299 runWithVim = (vimfx, fn) ->
300 return unless vim = vimfx.getCurrentVim(utils.getCurrentWindow())
304 export: exportAllPrefs
305 import: importExportedPrefs