annotate .cms/lib/codemirror/src/input/ContentEditableInput.js @ 0:78edf6b517a0 draft

24.10
author Coffee CMS <info@coffee-cms.ru>
date Fri, 11 Oct 2024 22:40:23 +0000
parents
children
Ignore whitespace changes - Everywhere: Within whitespace: At end of lines:
rev   line source
0
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
1 import { operation, runInOp } from "../display/operations.js"
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
2 import { prepareSelection } from "../display/selection.js"
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
3 import { regChange } from "../display/view_tracking.js"
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
4 import { applyTextInput, copyableRanges, disableBrowserMagic, handlePaste, hiddenTextarea, lastCopied, setLastCopied } from "./input.js"
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
5 import { cmp, maxPos, minPos, Pos } from "../line/pos.js"
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
6 import { getBetween, getLine, lineNo } from "../line/utils_line.js"
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
7 import { findViewForLine, findViewIndex, mapFromLineView, nodeAndOffsetInLineMap } from "../measurement/position_measurement.js"
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
8 import { replaceRange } from "../model/changes.js"
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
9 import { simpleSelection } from "../model/selection.js"
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
10 import { setSelection } from "../model/selection_updates.js"
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
11 import { getBidiPartAt, getOrder } from "../util/bidi.js"
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
12 import { android, chrome, gecko, ie_version } from "../util/browser.js"
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
13 import { activeElt, contains, range, removeChildrenAndAdd, selectInput, rootNode } from "../util/dom.js"
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
14 import { on, signalDOMEvent } from "../util/event.js"
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
15 import { Delayed, lst, sel_dontScroll } from "../util/misc.js"
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
16
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
17 // CONTENTEDITABLE INPUT STYLE
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
18
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
19 export default class ContentEditableInput {
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
20 constructor(cm) {
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
21 this.cm = cm
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
22 this.lastAnchorNode = this.lastAnchorOffset = this.lastFocusNode = this.lastFocusOffset = null
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
23 this.polling = new Delayed()
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
24 this.composing = null
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
25 this.gracePeriod = false
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
26 this.readDOMTimeout = null
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
27 }
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
28
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
29 init(display) {
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
30 let input = this, cm = input.cm
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
31 let div = input.div = display.lineDiv
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
32 div.contentEditable = true
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
33 disableBrowserMagic(div, cm.options.spellcheck, cm.options.autocorrect, cm.options.autocapitalize)
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
34
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
35 function belongsToInput(e) {
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
36 for (let t = e.target; t; t = t.parentNode) {
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
37 if (t == div) return true
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
38 if (/\bCodeMirror-(?:line)?widget\b/.test(t.className)) break
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
39 }
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
40 return false
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
41 }
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
42
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
43 on(div, "paste", e => {
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
44 if (!belongsToInput(e) || signalDOMEvent(cm, e) || handlePaste(e, cm)) return
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
45 // IE doesn't fire input events, so we schedule a read for the pasted content in this way
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
46 if (ie_version <= 11) setTimeout(operation(cm, () => this.updateFromDOM()), 20)
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
47 })
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
48
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
49 on(div, "compositionstart", e => {
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
50 this.composing = {data: e.data, done: false}
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
51 })
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
52 on(div, "compositionupdate", e => {
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
53 if (!this.composing) this.composing = {data: e.data, done: false}
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
54 })
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
55 on(div, "compositionend", e => {
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
56 if (this.composing) {
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
57 if (e.data != this.composing.data) this.readFromDOMSoon()
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
58 this.composing.done = true
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
59 }
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
60 })
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
61
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
62 on(div, "touchstart", () => input.forceCompositionEnd())
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
63
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
64 on(div, "input", () => {
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
65 if (!this.composing) this.readFromDOMSoon()
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
66 })
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
67
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
68 function onCopyCut(e) {
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
69 if (!belongsToInput(e) || signalDOMEvent(cm, e)) return
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
70 if (cm.somethingSelected()) {
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
71 setLastCopied({lineWise: false, text: cm.getSelections()})
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
72 if (e.type == "cut") cm.replaceSelection("", null, "cut")
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
73 } else if (!cm.options.lineWiseCopyCut) {
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
74 return
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
75 } else {
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
76 let ranges = copyableRanges(cm)
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
77 setLastCopied({lineWise: true, text: ranges.text})
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
78 if (e.type == "cut") {
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
79 cm.operation(() => {
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
80 cm.setSelections(ranges.ranges, 0, sel_dontScroll)
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
81 cm.replaceSelection("", null, "cut")
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
82 })
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
83 }
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
84 }
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
85 if (e.clipboardData) {
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
86 e.clipboardData.clearData()
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
87 let content = lastCopied.text.join("\n")
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
88 // iOS exposes the clipboard API, but seems to discard content inserted into it
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
89 e.clipboardData.setData("Text", content)
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
90 if (e.clipboardData.getData("Text") == content) {
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
91 e.preventDefault()
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
92 return
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
93 }
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
94 }
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
95 // Old-fashioned briefly-focus-a-textarea hack
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
96 let kludge = hiddenTextarea(), te = kludge.firstChild
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
97 disableBrowserMagic(te)
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
98 cm.display.lineSpace.insertBefore(kludge, cm.display.lineSpace.firstChild)
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
99 te.value = lastCopied.text.join("\n")
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
100 let hadFocus = activeElt(rootNode(div))
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
101 selectInput(te)
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
102 setTimeout(() => {
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
103 cm.display.lineSpace.removeChild(kludge)
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
104 hadFocus.focus()
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
105 if (hadFocus == div) input.showPrimarySelection()
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
106 }, 50)
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
107 }
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
108 on(div, "copy", onCopyCut)
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
109 on(div, "cut", onCopyCut)
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
110 }
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
111
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
112 screenReaderLabelChanged(label) {
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
113 // Label for screenreaders, accessibility
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
114 if(label) {
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
115 this.div.setAttribute('aria-label', label)
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
116 } else {
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
117 this.div.removeAttribute('aria-label')
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
118 }
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
119 }
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
120
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
121 prepareSelection() {
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
122 let result = prepareSelection(this.cm, false)
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
123 result.focus = activeElt(rootNode(this.div)) == this.div
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
124 return result
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
125 }
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
126
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
127 showSelection(info, takeFocus) {
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
128 if (!info || !this.cm.display.view.length) return
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
129 if (info.focus || takeFocus) this.showPrimarySelection()
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
130 this.showMultipleSelections(info)
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
131 }
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
132
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
133 getSelection() {
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
134 return this.cm.display.wrapper.ownerDocument.getSelection()
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
135 }
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
136
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
137 showPrimarySelection() {
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
138 let sel = this.getSelection(), cm = this.cm, prim = cm.doc.sel.primary()
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
139 let from = prim.from(), to = prim.to()
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
140
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
141 if (cm.display.viewTo == cm.display.viewFrom || from.line >= cm.display.viewTo || to.line < cm.display.viewFrom) {
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
142 sel.removeAllRanges()
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
143 return
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
144 }
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
145
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
146 let curAnchor = domToPos(cm, sel.anchorNode, sel.anchorOffset)
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
147 let curFocus = domToPos(cm, sel.focusNode, sel.focusOffset)
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
148 if (curAnchor && !curAnchor.bad && curFocus && !curFocus.bad &&
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
149 cmp(minPos(curAnchor, curFocus), from) == 0 &&
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
150 cmp(maxPos(curAnchor, curFocus), to) == 0)
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
151 return
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
152
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
153 let view = cm.display.view
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
154 let start = (from.line >= cm.display.viewFrom && posToDOM(cm, from)) ||
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
155 {node: view[0].measure.map[2], offset: 0}
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
156 let end = to.line < cm.display.viewTo && posToDOM(cm, to)
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
157 if (!end) {
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
158 let measure = view[view.length - 1].measure
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
159 let map = measure.maps ? measure.maps[measure.maps.length - 1] : measure.map
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
160 end = {node: map[map.length - 1], offset: map[map.length - 2] - map[map.length - 3]}
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
161 }
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
162
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
163 if (!start || !end) {
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
164 sel.removeAllRanges()
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
165 return
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
166 }
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
167
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
168 let old = sel.rangeCount && sel.getRangeAt(0), rng
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
169 try { rng = range(start.node, start.offset, end.offset, end.node) }
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
170 catch(e) {} // Our model of the DOM might be outdated, in which case the range we try to set can be impossible
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
171 if (rng) {
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
172 if (!gecko && cm.state.focused) {
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
173 sel.collapse(start.node, start.offset)
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
174 if (!rng.collapsed) {
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
175 sel.removeAllRanges()
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
176 sel.addRange(rng)
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
177 }
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
178 } else {
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
179 sel.removeAllRanges()
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
180 sel.addRange(rng)
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
181 }
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
182 if (old && sel.anchorNode == null) sel.addRange(old)
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
183 else if (gecko) this.startGracePeriod()
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
184 }
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
185 this.rememberSelection()
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
186 }
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
187
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
188 startGracePeriod() {
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
189 clearTimeout(this.gracePeriod)
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
190 this.gracePeriod = setTimeout(() => {
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
191 this.gracePeriod = false
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
192 if (this.selectionChanged())
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
193 this.cm.operation(() => this.cm.curOp.selectionChanged = true)
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
194 }, 20)
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
195 }
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
196
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
197 showMultipleSelections(info) {
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
198 removeChildrenAndAdd(this.cm.display.cursorDiv, info.cursors)
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
199 removeChildrenAndAdd(this.cm.display.selectionDiv, info.selection)
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
200 }
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
201
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
202 rememberSelection() {
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
203 let sel = this.getSelection()
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
204 this.lastAnchorNode = sel.anchorNode; this.lastAnchorOffset = sel.anchorOffset
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
205 this.lastFocusNode = sel.focusNode; this.lastFocusOffset = sel.focusOffset
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
206 }
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
207
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
208 selectionInEditor() {
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
209 let sel = this.getSelection()
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
210 if (!sel.rangeCount) return false
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
211 let node = sel.getRangeAt(0).commonAncestorContainer
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
212 return contains(this.div, node)
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
213 }
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
214
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
215 focus() {
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
216 if (this.cm.options.readOnly != "nocursor") {
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
217 if (!this.selectionInEditor() || activeElt(rootNode(this.div)) != this.div)
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
218 this.showSelection(this.prepareSelection(), true)
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
219 this.div.focus()
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
220 }
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
221 }
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
222 blur() { this.div.blur() }
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
223 getField() { return this.div }
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
224
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
225 supportsTouch() { return true }
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
226
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
227 receivedFocus() {
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
228 let input = this
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
229 if (this.selectionInEditor())
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
230 setTimeout(() => this.pollSelection(), 20)
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
231 else
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
232 runInOp(this.cm, () => input.cm.curOp.selectionChanged = true)
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
233
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
234 function poll() {
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
235 if (input.cm.state.focused) {
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
236 input.pollSelection()
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
237 input.polling.set(input.cm.options.pollInterval, poll)
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
238 }
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
239 }
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
240 this.polling.set(this.cm.options.pollInterval, poll)
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
241 }
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
242
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
243 selectionChanged() {
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
244 let sel = this.getSelection()
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
245 return sel.anchorNode != this.lastAnchorNode || sel.anchorOffset != this.lastAnchorOffset ||
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
246 sel.focusNode != this.lastFocusNode || sel.focusOffset != this.lastFocusOffset
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
247 }
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
248
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
249 pollSelection() {
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
250 if (this.readDOMTimeout != null || this.gracePeriod || !this.selectionChanged()) return
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
251 let sel = this.getSelection(), cm = this.cm
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
252 // On Android Chrome (version 56, at least), backspacing into an
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
253 // uneditable block element will put the cursor in that element,
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
254 // and then, because it's not editable, hide the virtual keyboard.
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
255 // Because Android doesn't allow us to actually detect backspace
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
256 // presses in a sane way, this code checks for when that happens
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
257 // and simulates a backspace press in this case.
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
258 if (android && chrome && this.cm.display.gutterSpecs.length && isInGutter(sel.anchorNode)) {
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
259 this.cm.triggerOnKeyDown({type: "keydown", keyCode: 8, preventDefault: Math.abs})
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
260 this.blur()
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
261 this.focus()
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
262 return
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
263 }
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
264 if (this.composing) return
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
265 this.rememberSelection()
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
266 let anchor = domToPos(cm, sel.anchorNode, sel.anchorOffset)
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
267 let head = domToPos(cm, sel.focusNode, sel.focusOffset)
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
268 if (anchor && head) runInOp(cm, () => {
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
269 setSelection(cm.doc, simpleSelection(anchor, head), sel_dontScroll)
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
270 if (anchor.bad || head.bad) cm.curOp.selectionChanged = true
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
271 })
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
272 }
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
273
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
274 pollContent() {
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
275 if (this.readDOMTimeout != null) {
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
276 clearTimeout(this.readDOMTimeout)
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
277 this.readDOMTimeout = null
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
278 }
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
279
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
280 let cm = this.cm, display = cm.display, sel = cm.doc.sel.primary()
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
281 let from = sel.from(), to = sel.to()
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
282 if (from.ch == 0 && from.line > cm.firstLine())
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
283 from = Pos(from.line - 1, getLine(cm.doc, from.line - 1).length)
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
284 if (to.ch == getLine(cm.doc, to.line).text.length && to.line < cm.lastLine())
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
285 to = Pos(to.line + 1, 0)
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
286 if (from.line < display.viewFrom || to.line > display.viewTo - 1) return false
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
287
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
288 let fromIndex, fromLine, fromNode
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
289 if (from.line == display.viewFrom || (fromIndex = findViewIndex(cm, from.line)) == 0) {
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
290 fromLine = lineNo(display.view[0].line)
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
291 fromNode = display.view[0].node
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
292 } else {
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
293 fromLine = lineNo(display.view[fromIndex].line)
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
294 fromNode = display.view[fromIndex - 1].node.nextSibling
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
295 }
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
296 let toIndex = findViewIndex(cm, to.line)
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
297 let toLine, toNode
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
298 if (toIndex == display.view.length - 1) {
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
299 toLine = display.viewTo - 1
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
300 toNode = display.lineDiv.lastChild
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
301 } else {
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
302 toLine = lineNo(display.view[toIndex + 1].line) - 1
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
303 toNode = display.view[toIndex + 1].node.previousSibling
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
304 }
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
305
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
306 if (!fromNode) return false
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
307 let newText = cm.doc.splitLines(domTextBetween(cm, fromNode, toNode, fromLine, toLine))
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
308 let oldText = getBetween(cm.doc, Pos(fromLine, 0), Pos(toLine, getLine(cm.doc, toLine).text.length))
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
309 while (newText.length > 1 && oldText.length > 1) {
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
310 if (lst(newText) == lst(oldText)) { newText.pop(); oldText.pop(); toLine-- }
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
311 else if (newText[0] == oldText[0]) { newText.shift(); oldText.shift(); fromLine++ }
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
312 else break
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
313 }
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
314
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
315 let cutFront = 0, cutEnd = 0
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
316 let newTop = newText[0], oldTop = oldText[0], maxCutFront = Math.min(newTop.length, oldTop.length)
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
317 while (cutFront < maxCutFront && newTop.charCodeAt(cutFront) == oldTop.charCodeAt(cutFront))
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
318 ++cutFront
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
319 let newBot = lst(newText), oldBot = lst(oldText)
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
320 let maxCutEnd = Math.min(newBot.length - (newText.length == 1 ? cutFront : 0),
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
321 oldBot.length - (oldText.length == 1 ? cutFront : 0))
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
322 while (cutEnd < maxCutEnd &&
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
323 newBot.charCodeAt(newBot.length - cutEnd - 1) == oldBot.charCodeAt(oldBot.length - cutEnd - 1))
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
324 ++cutEnd
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
325 // Try to move start of change to start of selection if ambiguous
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
326 if (newText.length == 1 && oldText.length == 1 && fromLine == from.line) {
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
327 while (cutFront && cutFront > from.ch &&
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
328 newBot.charCodeAt(newBot.length - cutEnd - 1) == oldBot.charCodeAt(oldBot.length - cutEnd - 1)) {
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
329 cutFront--
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
330 cutEnd++
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
331 }
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
332 }
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
333
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
334 newText[newText.length - 1] = newBot.slice(0, newBot.length - cutEnd).replace(/^\u200b+/, "")
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
335 newText[0] = newText[0].slice(cutFront).replace(/\u200b+$/, "")
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
336
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
337 let chFrom = Pos(fromLine, cutFront)
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
338 let chTo = Pos(toLine, oldText.length ? lst(oldText).length - cutEnd : 0)
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
339 if (newText.length > 1 || newText[0] || cmp(chFrom, chTo)) {
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
340 replaceRange(cm.doc, newText, chFrom, chTo, "+input")
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
341 return true
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
342 }
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
343 }
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
344
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
345 ensurePolled() {
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
346 this.forceCompositionEnd()
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
347 }
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
348 reset() {
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
349 this.forceCompositionEnd()
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
350 }
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
351 forceCompositionEnd() {
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
352 if (!this.composing) return
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
353 clearTimeout(this.readDOMTimeout)
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
354 this.composing = null
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
355 this.updateFromDOM()
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
356 this.div.blur()
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
357 this.div.focus()
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
358 }
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
359 readFromDOMSoon() {
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
360 if (this.readDOMTimeout != null) return
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
361 this.readDOMTimeout = setTimeout(() => {
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
362 this.readDOMTimeout = null
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
363 if (this.composing) {
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
364 if (this.composing.done) this.composing = null
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
365 else return
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
366 }
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
367 this.updateFromDOM()
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
368 }, 80)
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
369 }
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
370
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
371 updateFromDOM() {
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
372 if (this.cm.isReadOnly() || !this.pollContent())
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
373 runInOp(this.cm, () => regChange(this.cm))
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
374 }
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
375
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
376 setUneditable(node) {
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
377 node.contentEditable = "false"
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
378 }
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
379
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
380 onKeyPress(e) {
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
381 if (e.charCode == 0 || this.composing) return
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
382 e.preventDefault()
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
383 if (!this.cm.isReadOnly())
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
384 operation(this.cm, applyTextInput)(this.cm, String.fromCharCode(e.charCode == null ? e.keyCode : e.charCode), 0)
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
385 }
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
386
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
387 readOnlyChanged(val) {
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
388 this.div.contentEditable = String(val != "nocursor")
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
389 }
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
390
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
391 onContextMenu() {}
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
392 resetPosition() {}
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
393 }
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
394
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
395 ContentEditableInput.prototype.needsContentAttribute = true
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
396
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
397 function posToDOM(cm, pos) {
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
398 let view = findViewForLine(cm, pos.line)
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
399 if (!view || view.hidden) return null
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
400 let line = getLine(cm.doc, pos.line)
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
401 let info = mapFromLineView(view, line, pos.line)
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
402
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
403 let order = getOrder(line, cm.doc.direction), side = "left"
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
404 if (order) {
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
405 let partPos = getBidiPartAt(order, pos.ch)
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
406 side = partPos % 2 ? "right" : "left"
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
407 }
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
408 let result = nodeAndOffsetInLineMap(info.map, pos.ch, side)
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
409 result.offset = result.collapse == "right" ? result.end : result.start
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
410 return result
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
411 }
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
412
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
413 function isInGutter(node) {
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
414 for (let scan = node; scan; scan = scan.parentNode)
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
415 if (/CodeMirror-gutter-wrapper/.test(scan.className)) return true
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
416 return false
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
417 }
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
418
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
419 function badPos(pos, bad) { if (bad) pos.bad = true; return pos }
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
420
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
421 function domTextBetween(cm, from, to, fromLine, toLine) {
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
422 let text = "", closing = false, lineSep = cm.doc.lineSeparator(), extraLinebreak = false
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
423 function recognizeMarker(id) { return marker => marker.id == id }
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
424 function close() {
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
425 if (closing) {
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
426 text += lineSep
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
427 if (extraLinebreak) text += lineSep
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
428 closing = extraLinebreak = false
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
429 }
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
430 }
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
431 function addText(str) {
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
432 if (str) {
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
433 close()
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
434 text += str
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
435 }
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
436 }
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
437 function walk(node) {
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
438 if (node.nodeType == 1) {
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
439 let cmText = node.getAttribute("cm-text")
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
440 if (cmText) {
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
441 addText(cmText)
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
442 return
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
443 }
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
444 let markerID = node.getAttribute("cm-marker"), range
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
445 if (markerID) {
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
446 let found = cm.findMarks(Pos(fromLine, 0), Pos(toLine + 1, 0), recognizeMarker(+markerID))
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
447 if (found.length && (range = found[0].find(0)))
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
448 addText(getBetween(cm.doc, range.from, range.to).join(lineSep))
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
449 return
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
450 }
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
451 if (node.getAttribute("contenteditable") == "false") return
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
452 let isBlock = /^(pre|div|p|li|table|br)$/i.test(node.nodeName)
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
453 if (!/^br$/i.test(node.nodeName) && node.textContent.length == 0) return
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
454
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
455 if (isBlock) close()
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
456 for (let i = 0; i < node.childNodes.length; i++)
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
457 walk(node.childNodes[i])
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
458
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
459 if (/^(pre|p)$/i.test(node.nodeName)) extraLinebreak = true
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
460 if (isBlock) closing = true
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
461 } else if (node.nodeType == 3) {
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
462 addText(node.nodeValue.replace(/\u200b/g, "").replace(/\u00a0/g, " "))
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
463 }
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
464 }
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
465 for (;;) {
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
466 walk(from)
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
467 if (from == to) break
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
468 from = from.nextSibling
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
469 extraLinebreak = false
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
470 }
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
471 return text
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
472 }
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
473
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
474 function domToPos(cm, node, offset) {
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
475 let lineNode
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
476 if (node == cm.display.lineDiv) {
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
477 lineNode = cm.display.lineDiv.childNodes[offset]
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
478 if (!lineNode) return badPos(cm.clipPos(Pos(cm.display.viewTo - 1)), true)
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
479 node = null; offset = 0
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
480 } else {
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
481 for (lineNode = node;; lineNode = lineNode.parentNode) {
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
482 if (!lineNode || lineNode == cm.display.lineDiv) return null
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
483 if (lineNode.parentNode && lineNode.parentNode == cm.display.lineDiv) break
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
484 }
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
485 }
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
486 for (let i = 0; i < cm.display.view.length; i++) {
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
487 let lineView = cm.display.view[i]
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
488 if (lineView.node == lineNode)
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
489 return locateNodeInLineView(lineView, node, offset)
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
490 }
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
491 }
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
492
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
493 function locateNodeInLineView(lineView, node, offset) {
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
494 let wrapper = lineView.text.firstChild, bad = false
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
495 if (!node || !contains(wrapper, node)) return badPos(Pos(lineNo(lineView.line), 0), true)
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
496 if (node == wrapper) {
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
497 bad = true
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
498 node = wrapper.childNodes[offset]
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
499 offset = 0
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
500 if (!node) {
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
501 let line = lineView.rest ? lst(lineView.rest) : lineView.line
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
502 return badPos(Pos(lineNo(line), line.text.length), bad)
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
503 }
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
504 }
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
505
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
506 let textNode = node.nodeType == 3 ? node : null, topNode = node
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
507 if (!textNode && node.childNodes.length == 1 && node.firstChild.nodeType == 3) {
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
508 textNode = node.firstChild
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
509 if (offset) offset = textNode.nodeValue.length
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
510 }
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
511 while (topNode.parentNode != wrapper) topNode = topNode.parentNode
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
512 let measure = lineView.measure, maps = measure.maps
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
513
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
514 function find(textNode, topNode, offset) {
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
515 for (let i = -1; i < (maps ? maps.length : 0); i++) {
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
516 let map = i < 0 ? measure.map : maps[i]
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
517 for (let j = 0; j < map.length; j += 3) {
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
518 let curNode = map[j + 2]
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
519 if (curNode == textNode || curNode == topNode) {
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
520 let line = lineNo(i < 0 ? lineView.line : lineView.rest[i])
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
521 let ch = map[j] + offset
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
522 if (offset < 0 || curNode != textNode) ch = map[j + (offset ? 1 : 0)]
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
523 return Pos(line, ch)
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
524 }
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
525 }
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
526 }
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
527 }
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
528 let found = find(textNode, topNode, offset)
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
529 if (found) return badPos(found, bad)
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
530
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
531 // FIXME this is all really shaky. might handle the few cases it needs to handle, but likely to cause problems
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
532 for (let after = topNode.nextSibling, dist = textNode ? textNode.nodeValue.length - offset : 0; after; after = after.nextSibling) {
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
533 found = find(after, after.firstChild, 0)
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
534 if (found)
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
535 return badPos(Pos(found.line, found.ch - dist), bad)
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
536 else
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
537 dist += after.textContent.length
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
538 }
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
539 for (let before = topNode.previousSibling, dist = offset; before; before = before.previousSibling) {
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
540 found = find(before, before.firstChild, -1)
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
541 if (found)
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
542 return badPos(Pos(found.line, found.ch + dist), bad)
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
543 else
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
544 dist += before.textContent.length
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
545 }
Coffee CMS <info@coffee-cms.ru>
parents:
diff changeset
546 }