-
Notifications
You must be signed in to change notification settings - Fork 3.3k
/
keyboard.coffee
129 lines (117 loc) · 4.64 KB
/
keyboard.coffee
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
Quill = require('../quill')
_ = Quill.require('lodash')
dom = Quill.require('dom')
Delta = Quill.require('delta')
class Keyboard
@hotkeys:
BOLD: { key: 'B', metaKey: true }
INDENT: { key: dom.KEYS.TAB }
ITALIC: { key: 'I', metaKey: true }
OUTDENT: { key: dom.KEYS.TAB, shiftKey: true }
UNDERLINE: { key: 'U', metaKey: true }
constructor: (@quill, options) ->
@hotkeys = {}
this._initListeners()
this._initHotkeys()
@quill.onModuleLoad('toolbar', (toolbar) =>
@toolbar = toolbar
)
addHotkey: (hotkeys, callback) ->
hotkeys = [hotkeys] unless Array.isArray(hotkeys)
_.each(hotkeys, (hotkey) =>
hotkey = if _.isObject(hotkey) then _.clone(hotkey) else { key: hotkey }
hotkey.callback = callback
which = if _.isNumber(hotkey.key) then hotkey.key else hotkey.key.toUpperCase().charCodeAt(0)
@hotkeys[which] ?= []
@hotkeys[which].push(hotkey)
)
toggleFormat: (range, format) ->
if range.isCollapsed()
delta = @quill.getContents(Math.max(0, range.start-1), range.end)
else
delta = @quill.getContents(range)
value = delta.ops.length == 0 or !_.all(delta.ops, (op) ->
return op.attributes?[format]
)
if range.isCollapsed()
@quill.prepareFormat(format, value, Quill.sources.USER)
else
@quill.formatText(range, format, value, Quill.sources.USER)
@toolbar.setActive(format, value) if @toolbar?
_initEnter: ->
keys = [
{ key: dom.KEYS.ENTER }
{ key: dom.KEYS.ENTER, shiftKey: true }
]
this.addHotkey(keys, (range, hotkey) =>
return true unless range?
[line, offset] = @quill.editor.doc.findLineAt(range.start)
[leaf, offset] = line.findLeafAt(offset)
delta = new Delta().retain(range.start).insert('\n', line.formats).delete(range.end - range.start)
@quill.updateContents(delta, Quill.sources.USER)
_.each(leaf.formats, (value, format) =>
@quill.prepareFormat(format, value)
@toolbar.setActive(format, value) if @toolbar?
)
return false
)
_initDeletes: ->
this.addHotkey([dom.KEYS.DELETE, dom.KEYS.BACKSPACE], (range, hotkey) =>
if range? and @quill.getLength() > 0
if range.start != range.end
@quill.deleteText(range.start, range.end, Quill.sources.USER)
else
if hotkey.key == dom.KEYS.BACKSPACE
[line, offset] = @quill.editor.doc.findLineAt(range.start)
if offset == 0 and (line.formats.bullet or line.formats.list)
format = if line.formats.bullet then 'bullet' else 'list'
@quill.formatLine(range.start, range.start, format, false)
else if range.start > 0
@quill.deleteText(range.start - 1, range.start, Quill.sources.USER)
else if range.start < @quill.getLength() - 1
@quill.deleteText(range.start, range.start + 1, Quill.sources.USER)
return false
)
_initHotkeys: ->
this.addHotkey(Keyboard.hotkeys.INDENT, (range) =>
this._onTab(range, false)
return false
)
this.addHotkey(Keyboard.hotkeys.OUTDENT, (range) =>
# TODO implement when we implement multiline tabs
return false
)
_.each(['bold', 'italic', 'underline'], (format) =>
this.addHotkey(Keyboard.hotkeys[format.toUpperCase()], (range) =>
this.toggleFormat(range, format)
return false
)
)
this._initDeletes()
this._initEnter()
_initListeners: ->
dom(@quill.root).on('keydown', (event) =>
prevent = false
_.each(@hotkeys[event.which], (hotkey) =>
metaKey = if dom.isMac() then event.metaKey else event.metaKey or event.ctrlKey
return if !!hotkey.metaKey != !!metaKey
return if !!hotkey.shiftKey != !!event.shiftKey
return if !!hotkey.altKey != !!event.altKey
prevent = hotkey.callback(@quill.getSelection(), hotkey, event) == false or prevent
return true
)
return !prevent
)
_onTab: (range, shift = false) ->
# TODO implement multiline tab behavior
# Behavior according to Google Docs + Word
# When tab on one line, regardless if shift is down, delete selection and insert a tab
# When tab on multiple lines, indent each line if possible, outdent if shift is down
delta = new Delta().retain(range.start)
.insert("\t")
.delete(range.end - range.start)
.retain(@quill.getLength() - range.end)
@quill.updateContents(delta, Quill.sources.USER)
@quill.setSelection(range.start + 1, range.start + 1)
Quill.registerModule('keyboard', Keyboard)
module.exports = Keyboard