File modules/editor/codemirror/src/input/TextareaInput.min.js

Last commit: Tue May 22 22:39:52 2018 +0200	Jan Dankert	Fix für PHP 7.2: 'Object' darf nun nicht mehr als Klassennamen verwendet werden. AUCH NICHT IN EINEM NAMESPACE! WTF, wozu habe ich das in einen verfickten Namespace gepackt? Wozu soll der sonst da sein??? Amateure. Daher nun notgedrungen unbenannt in 'BaseObject'.
1 import { operation, runInOp } from "../display/operations.js" 2 import { prepareSelection } from "../display/selection.js" 3 import { applyTextInput, copyableRanges, handlePaste, hiddenTextarea, setLastCopied } from "./input.js" 4 import { cursorCoords, posFromMouse } from "../measurement/position_measurement.js" 5 import { eventInWidget } from "../measurement/widgets.js" 6 import { simpleSelection } from "../model/selection.js" 7 import { selectAll, setSelection } from "../model/selection_updates.js" 8 import { captureRightClick, ie, ie_version, ios, mac, mobile, presto, webkit } from "../util/browser.js" 9 import { activeElt, removeChildrenAndAdd, selectInput } from "../util/dom.js" 10 import { e_preventDefault, e_stop, off, on, signalDOMEvent } from "../util/event.js" 11 import { hasSelection } from "../util/feature_detection.js" 12 import { Delayed, sel_dontScroll } from "../util/misc.js" 13 14 // TEXTAREA INPUT STYLE 15 16 export default class TextareaInput { 17 constructor(cm) { 18 this.cm = cm 19 // See input.poll and input.reset 20 this.prevInput = "" 21 22 // Flag that indicates whether we expect input to appear real soon 23 // now (after some event like 'keypress' or 'input') and are 24 // polling intensively. 25 this.pollingFast = false 26 // Self-resetting timeout for the poller 27 this.polling = new Delayed() 28 // Used to work around IE issue with selection being forgotten when focus moves away from textarea 29 this.hasSelection = false 30 this.composing = null 31 } 32 33 init(display) { 34 let input = this, cm = this.cm 35 36 // Wraps and hides input textarea 37 let div = this.wrapper = hiddenTextarea() 38 // The semihidden textarea that is focused when the editor is 39 // focused, and receives input. 40 let te = this.textarea = div.firstChild 41 display.wrapper.insertBefore(div, display.wrapper.firstChild) 42 43 // Needed to hide big blue blinking cursor on Mobile Safari (doesn't seem to work in iOS 8 anymore) 44 if (ios) te.style.width = "0px" 45 46 on(te, "input", () => { 47 if (ie && ie_version >= 9 && this.hasSelection) this.hasSelection = null 48 input.poll() 49 }) 50 51 on(te, "paste", e => { 52 if (signalDOMEvent(cm, e) || handlePaste(e, cm)) return 53 54 cm.state.pasteIncoming = true 55 input.fastPoll() 56 }) 57 58 function prepareCopyCut(e) { 59 if (signalDOMEvent(cm, e)) return 60 if (cm.somethingSelected()) { 61 setLastCopied({lineWise: false, text: cm.getSelections()}) 62 } else if (!cm.options.lineWiseCopyCut) { 63 return 64 } else { 65 let ranges = copyableRanges(cm) 66 setLastCopied({lineWise: true, text: ranges.text}) 67 if (e.type == "cut") { 68 cm.setSelections(ranges.ranges, null, sel_dontScroll) 69 } else { 70 input.prevInput = "" 71 te.value = ranges.text.join("\n") 72 selectInput(te) 73 } 74 } 75 if (e.type == "cut") cm.state.cutIncoming = true 76 } 77 on(te, "cut", prepareCopyCut) 78 on(te, "copy", prepareCopyCut) 79 80 on(display.scroller, "paste", e => { 81 if (eventInWidget(display, e) || signalDOMEvent(cm, e)) return 82 cm.state.pasteIncoming = true 83 input.focus() 84 }) 85 86 // Prevent normal selection in the editor (we handle our own) 87 on(display.lineSpace, "selectstart", e => { 88 if (!eventInWidget(display, e)) e_preventDefault(e) 89 }) 90 91 on(te, "compositionstart", () => { 92 let start = cm.getCursor("from") 93 if (input.composing) input.composing.range.clear() 94 input.composing = { 95 start: start, 96 range: cm.markText(start, cm.getCursor("to"), {className: "CodeMirror-composing"}) 97 } 98 }) 99 on(te, "compositionend", () => { 100 if (input.composing) { 101 input.poll() 102 input.composing.range.clear() 103 input.composing = null 104 } 105 }) 106 } 107 108 prepareSelection() { 109 // Redraw the selection and/or cursor 110 let cm = this.cm, display = cm.display, doc = cm.doc 111 let result = prepareSelection(cm) 112 113 // Move the hidden textarea near the cursor to prevent scrolling artifacts 114 if (cm.options.moveInputWithCursor) { 115 let headPos = cursorCoords(cm, doc.sel.primary().head, "div") 116 let wrapOff = display.wrapper.getBoundingClientRect(), lineOff = display.lineDiv.getBoundingClientRect() 117 result.teTop = Math.max(0, Math.min(display.wrapper.clientHeight - 10, 118 headPos.top + lineOff.top - wrapOff.top)) 119 result.teLeft = Math.max(0, Math.min(display.wrapper.clientWidth - 10, 120 headPos.left + lineOff.left - wrapOff.left)) 121 } 122 123 return result 124 } 125 126 showSelection(drawn) { 127 let cm = this.cm, display = cm.display 128 removeChildrenAndAdd(display.cursorDiv, drawn.cursors) 129 removeChildrenAndAdd(display.selectionDiv, drawn.selection) 130 if (drawn.teTop != null) { 131 this.wrapper.style.top = drawn.teTop + "px" 132 this.wrapper.style.left = drawn.teLeft + "px" 133 } 134 } 135 136 // Reset the input to correspond to the selection (or to be empty, 137 // when not typing and nothing is selected) 138 reset(typing) { 139 if (this.contextMenuPending || this.composing) return 140 let cm = this.cm 141 if (cm.somethingSelected()) { 142 this.prevInput = "" 143 let content = cm.getSelection() 144 this.textarea.value = content 145 if (cm.state.focused) selectInput(this.textarea) 146 if (ie && ie_version >= 9) this.hasSelection = content 147 } else if (!typing) { 148 this.prevInput = this.textarea.value = "" 149 if (ie && ie_version >= 9) this.hasSelection = null 150 } 151 } 152 153 getField() { return this.textarea } 154 155 supportsTouch() { return false } 156 157 focus() { 158 if (this.cm.options.readOnly != "nocursor" && (!mobile || activeElt() != this.textarea)) { 159 try { this.textarea.focus() } 160 catch (e) {} // IE8 will throw if the textarea is display: none or not in DOM 161 } 162 } 163 164 blur() { this.textarea.blur() } 165 166 resetPosition() { 167 this.wrapper.style.top = this.wrapper.style.left = 0 168 } 169 170 receivedFocus() { this.slowPoll() } 171 172 // Poll for input changes, using the normal rate of polling. This 173 // runs as long as the editor is focused. 174 slowPoll() { 175 if (this.pollingFast) return 176 this.polling.set(this.cm.options.pollInterval, () => { 177 this.poll() 178 if (this.cm.state.focused) this.slowPoll() 179 }) 180 } 181 182 // When an event has just come in that is likely to add or change 183 // something in the input textarea, we poll faster, to ensure that 184 // the change appears on the screen quickly. 185 fastPoll() { 186 let missed = false, input = this 187 input.pollingFast = true 188 function p() { 189 let changed = input.poll() 190 if (!changed && !missed) {missed = true; input.polling.set(60, p)} 191 else {input.pollingFast = false; input.slowPoll()} 192 } 193 input.polling.set(20, p) 194 } 195 196 // Read input from the textarea, and update the document to match. 197 // When something is selected, it is present in the textarea, and 198 // selected (unless it is huge, in which case a placeholder is 199 // used). When nothing is selected, the cursor sits after previously 200 // seen text (can be empty), which is stored in prevInput (we must 201 // not reset the textarea when typing, because that breaks IME). 202 poll() { 203 let cm = this.cm, input = this.textarea, prevInput = this.prevInput 204 // Since this is called a *lot*, try to bail out as cheaply as 205 // possible when it is clear that nothing happened. hasSelection 206 // will be the case when there is a lot of text in the textarea, 207 // in which case reading its value would be expensive. 208 if (this.contextMenuPending || !cm.state.focused || 209 (hasSelection(input) && !prevInput && !this.composing) || 210 cm.isReadOnly() || cm.options.disableInput || cm.state.keySeq) 211 return false 212 213 let text = input.value 214 // If nothing changed, bail. 215 if (text == prevInput && !cm.somethingSelected()) return false 216 // Work around nonsensical selection resetting in IE9/10, and 217 // inexplicable appearance of private area unicode characters on 218 // some key combos in Mac (#2689). 219 if (ie && ie_version >= 9 && this.hasSelection === text || 220 mac && /[\uf700-\uf7ff]/.test(text)) { 221 cm.display.input.reset() 222 return false 223 } 224 225 if (cm.doc.sel == cm.display.selForContextMenu) { 226 let first = text.charCodeAt(0) 227 if (first == 0x200b && !prevInput) prevInput = "\u200b" 228 if (first == 0x21da) { this.reset(); return this.cm.execCommand("undo") } 229 } 230 // Find the part of the input that is actually new 231 let same = 0, l = Math.min(prevInput.length, text.length) 232 while (same < l && prevInput.charCodeAt(same) == text.charCodeAt(same)) ++same 233 234 runInOp(cm, () => { 235 applyTextInput(cm, text.slice(same), prevInput.length - same, 236 null, this.composing ? "*compose" : null) 237 238 // Don't leave long text in the textarea, since it makes further polling slow 239 if (text.length > 1000 || text.indexOf("\n") > -1) input.value = this.prevInput = "" 240 else this.prevInput = text 241 242 if (this.composing) { 243 this.composing.range.clear() 244 this.composing.range = cm.markText(this.composing.start, cm.getCursor("to"), 245 {className: "CodeMirror-composing"}) 246 } 247 }) 248 return true 249 } 250 251 ensurePolled() { 252 if (this.pollingFast && this.poll()) this.pollingFast = false 253 } 254 255 onKeyPress() { 256 if (ie && ie_version >= 9) this.hasSelection = null 257 this.fastPoll() 258 } 259 260 onContextMenu(e) { 261 let input = this, cm = input.cm, display = cm.display, te = input.textarea 262 let pos = posFromMouse(cm, e), scrollPos = display.scroller.scrollTop 263 if (!pos || presto) return // Opera is difficult. 264 265 // Reset the current text selection only if the click is done outside of the selection 266 // and 'resetSelectionOnContextMenu' option is true. 267 let reset = cm.options.resetSelectionOnContextMenu 268 if (reset && cm.doc.sel.contains(pos) == -1) 269 operation(cm, setSelection)(cm.doc, simpleSelection(pos), sel_dontScroll) 270 271 let oldCSS = te.style.cssText, oldWrapperCSS = input.wrapper.style.cssText 272 input.wrapper.style.cssText = "position: absolute" 273 let wrapperBox = input.wrapper.getBoundingClientRect() 274 te.style.cssText = `position: absolute; width: 30px; height: 30px; 275 top: ${e.clientY - wrapperBox.top - 5}px; left: ${e.clientX - wrapperBox.left - 5}px; 276 z-index: 1000; background: ${ie ? "rgba(255, 255, 255, .05)" : "transparent"}; 277 outline: none; border-width: 0; outline: none; overflow: hidden; opacity: .05; filter: alpha(opacity=5);` 278 let oldScrollY 279 if (webkit) oldScrollY = window.scrollY // Work around Chrome issue (#2712) 280 display.input.focus() 281 if (webkit) window.scrollTo(null, oldScrollY) 282 display.input.reset() 283 // Adds "Select all" to context menu in FF 284 if (!cm.somethingSelected()) te.value = input.prevInput = " " 285 input.contextMenuPending = true 286 display.selForContextMenu = cm.doc.sel 287 clearTimeout(display.detectingSelectAll) 288 289 // Select-all will be greyed out if there's nothing to select, so 290 // this adds a zero-width space so that we can later check whether 291 // it got selected. 292 function prepareSelectAllHack() { 293 if (te.selectionStart != null) { 294 let selected = cm.somethingSelected() 295 let extval = "\u200b" + (selected ? te.value : "") 296 te.value = "\u21da" // Used to catch context-menu undo 297 te.value = extval 298 input.prevInput = selected ? "" : "\u200b" 299 te.selectionStart = 1; te.selectionEnd = extval.length 300 // Re-set this, in case some other handler touched the 301 // selection in the meantime. 302 display.selForContextMenu = cm.doc.sel 303 } 304 } 305 function rehide() { 306 input.contextMenuPending = false 307 input.wrapper.style.cssText = oldWrapperCSS 308 te.style.cssText = oldCSS 309 if (ie && ie_version < 9) display.scrollbars.setScrollTop(display.scroller.scrollTop = scrollPos) 310 311 // Try to detect the user choosing select-all 312 if (te.selectionStart != null) { 313 if (!ie || (ie && ie_version < 9)) prepareSelectAllHack() 314 let i = 0, poll = () => { 315 if (display.selForContextMenu == cm.doc.sel && te.selectionStart == 0 && 316 te.selectionEnd > 0 && input.prevInput == "\u200b") { 317 operation(cm, selectAll)(cm) 318 } else if (i++ < 10) { 319 display.detectingSelectAll = setTimeout(poll, 500) 320 } else { 321 display.selForContextMenu = null 322 display.input.reset() 323 } 324 } 325 display.detectingSelectAll = setTimeout(poll, 200) 326 } 327 } 328 329 if (ie && ie_version >= 9) prepareSelectAllHack() 330 if (captureRightClick) { 331 e_stop(e) 332 let mouseup = () => { 333 off(window, "mouseup", mouseup) 334 setTimeout(rehide, 20) 335 } 336 on(window, "mouseup", mouseup) 337 } else { 338 setTimeout(rehide, 50) 339 } 340 } 341 342 readOnlyChanged(val) { 343 if (!val) this.reset() 344 this.textarea.disabled = val == "nocursor" 345 } 346 347 setUneditable() {} 348 } 349 350 TextareaInput.prototype.needsContentAttribute = false
Download modules/editor/codemirror/src/input/TextareaInput.min.js
History Tue, 22 May 2018 22:39:52 +0200 Jan Dankert Fix für PHP 7.2: 'Object' darf nun nicht mehr als Klassennamen verwendet werden. AUCH NICHT IN EINEM NAMESPACE! WTF, wozu habe ich das in einen verfickten Namespace gepackt? Wozu soll der sonst da sein??? Amateure. Daher nun notgedrungen unbenannt in 'BaseObject'.