etherpad-webcomponents 0.0.5 → 0.0.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (145) hide show
  1. package/dist/EpDropdown.d.ts +3 -0
  2. package/dist/EpDropdown.d.ts.map +1 -1
  3. package/dist/EpDropdown.js +30 -4
  4. package/dist/EpDropdown.js.map +1 -1
  5. package/dist/EpEditor.d.ts +108 -0
  6. package/dist/EpEditor.d.ts.map +1 -0
  7. package/dist/EpEditor.js +288 -0
  8. package/dist/EpEditor.js.map +1 -0
  9. package/dist/editor/AceEditor.d.ts +204 -0
  10. package/dist/editor/AceEditor.d.ts.map +1 -0
  11. package/dist/editor/AceEditor.js +2847 -0
  12. package/dist/editor/AceEditor.js.map +1 -0
  13. package/dist/editor/AttributeManager.d.ts +25 -0
  14. package/dist/editor/AttributeManager.d.ts.map +1 -0
  15. package/dist/editor/AttributeManager.js +236 -0
  16. package/dist/editor/AttributeManager.js.map +1 -0
  17. package/dist/editor/AttributeMap.d.ts +13 -0
  18. package/dist/editor/AttributeMap.d.ts.map +1 -0
  19. package/dist/editor/AttributeMap.js +37 -0
  20. package/dist/editor/AttributeMap.js.map +1 -0
  21. package/dist/editor/AttributePool.d.ts +22 -0
  22. package/dist/editor/AttributePool.d.ts.map +1 -0
  23. package/dist/editor/AttributePool.js +101 -0
  24. package/dist/editor/AttributePool.js.map +1 -0
  25. package/dist/editor/Builder.d.ts +15 -0
  26. package/dist/editor/Builder.d.ts.map +1 -0
  27. package/dist/editor/Builder.js +47 -0
  28. package/dist/editor/Builder.js.map +1 -0
  29. package/dist/editor/Changeset.d.ts +61 -0
  30. package/dist/editor/Changeset.d.ts.map +1 -0
  31. package/dist/editor/Changeset.js +942 -0
  32. package/dist/editor/Changeset.js.map +1 -0
  33. package/dist/editor/ChangesetUtils.d.ts +11 -0
  34. package/dist/editor/ChangesetUtils.d.ts.map +1 -0
  35. package/dist/editor/ChangesetUtils.js +30 -0
  36. package/dist/editor/ChangesetUtils.js.map +1 -0
  37. package/dist/editor/MergingOpAssembler.d.ts +13 -0
  38. package/dist/editor/MergingOpAssembler.d.ts.map +1 -0
  39. package/dist/editor/MergingOpAssembler.js +60 -0
  40. package/dist/editor/MergingOpAssembler.js.map +1 -0
  41. package/dist/editor/Op.d.ts +10 -0
  42. package/dist/editor/Op.d.ts.map +1 -0
  43. package/dist/editor/Op.js +18 -0
  44. package/dist/editor/Op.js.map +1 -0
  45. package/dist/editor/OpAssembler.d.ts +9 -0
  46. package/dist/editor/OpAssembler.d.ts.map +1 -0
  47. package/dist/editor/OpAssembler.js +16 -0
  48. package/dist/editor/OpAssembler.js.map +1 -0
  49. package/dist/editor/OpIter.d.ts +9 -0
  50. package/dist/editor/OpIter.d.ts.map +1 -0
  51. package/dist/editor/OpIter.js +22 -0
  52. package/dist/editor/OpIter.js.map +1 -0
  53. package/dist/editor/SmartOpAssembler.d.ts +21 -0
  54. package/dist/editor/SmartOpAssembler.d.ts.map +1 -0
  55. package/dist/editor/SmartOpAssembler.js +71 -0
  56. package/dist/editor/SmartOpAssembler.js.map +1 -0
  57. package/dist/editor/StringAssembler.d.ts +7 -0
  58. package/dist/editor/StringAssembler.d.ts.map +1 -0
  59. package/dist/editor/StringAssembler.js +15 -0
  60. package/dist/editor/StringAssembler.js.map +1 -0
  61. package/dist/editor/StringIterator.d.ts +13 -0
  62. package/dist/editor/StringIterator.d.ts.map +1 -0
  63. package/dist/editor/StringIterator.js +29 -0
  64. package/dist/editor/StringIterator.js.map +1 -0
  65. package/dist/editor/TextLinesMutator.d.ts +31 -0
  66. package/dist/editor/TextLinesMutator.d.ts.map +1 -0
  67. package/dist/editor/TextLinesMutator.js +198 -0
  68. package/dist/editor/TextLinesMutator.js.map +1 -0
  69. package/dist/editor/ace2_common.d.ts +9 -0
  70. package/dist/editor/ace2_common.d.ts.map +1 -0
  71. package/dist/editor/ace2_common.js +31 -0
  72. package/dist/editor/ace2_common.js.map +1 -0
  73. package/dist/editor/attributes.d.ts +20 -0
  74. package/dist/editor/attributes.d.ts.map +1 -0
  75. package/dist/editor/attributes.js +54 -0
  76. package/dist/editor/attributes.js.map +1 -0
  77. package/dist/editor/browser_flags.d.ts +7 -0
  78. package/dist/editor/browser_flags.d.ts.map +1 -0
  79. package/dist/editor/browser_flags.js +8 -0
  80. package/dist/editor/browser_flags.js.map +1 -0
  81. package/dist/editor/changesettracker.d.ts +54 -0
  82. package/dist/editor/changesettracker.d.ts.map +1 -0
  83. package/dist/editor/changesettracker.js +193 -0
  84. package/dist/editor/changesettracker.js.map +1 -0
  85. package/dist/editor/colorutils.d.ts +22 -0
  86. package/dist/editor/colorutils.d.ts.map +1 -0
  87. package/dist/editor/colorutils.js +81 -0
  88. package/dist/editor/colorutils.js.map +1 -0
  89. package/dist/editor/contentcollector.d.ts +52 -0
  90. package/dist/editor/contentcollector.d.ts.map +1 -0
  91. package/dist/editor/contentcollector.js +692 -0
  92. package/dist/editor/contentcollector.js.map +1 -0
  93. package/dist/editor/core/EventBus.d.ts +187 -0
  94. package/dist/editor/core/EventBus.d.ts.map +1 -0
  95. package/dist/editor/core/EventBus.js +169 -0
  96. package/dist/editor/core/EventBus.js.map +1 -0
  97. package/dist/editor/cssmanager.d.ts +6 -0
  98. package/dist/editor/cssmanager.d.ts.map +1 -0
  99. package/dist/editor/cssmanager.js +35 -0
  100. package/dist/editor/cssmanager.js.map +1 -0
  101. package/dist/editor/domline.d.ts +35 -0
  102. package/dist/editor/domline.d.ts.map +1 -0
  103. package/dist/editor/domline.js +267 -0
  104. package/dist/editor/domline.js.map +1 -0
  105. package/dist/editor/html_escape.d.ts +3 -0
  106. package/dist/editor/html_escape.d.ts.map +1 -0
  107. package/dist/editor/html_escape.js +8 -0
  108. package/dist/editor/html_escape.js.map +1 -0
  109. package/dist/editor/linestylefilter.d.ts +22 -0
  110. package/dist/editor/linestylefilter.d.ts.map +1 -0
  111. package/dist/editor/linestylefilter.js +298 -0
  112. package/dist/editor/linestylefilter.js.map +1 -0
  113. package/dist/editor/skiplist.d.ts +51 -0
  114. package/dist/editor/skiplist.d.ts.map +1 -0
  115. package/dist/editor/skiplist.js +327 -0
  116. package/dist/editor/skiplist.js.map +1 -0
  117. package/dist/editor/types/AText.d.ts +5 -0
  118. package/dist/editor/types/AText.d.ts.map +1 -0
  119. package/dist/editor/types/AText.js +2 -0
  120. package/dist/editor/types/AText.js.map +1 -0
  121. package/dist/editor/types/Attribute.d.ts +2 -0
  122. package/dist/editor/types/Attribute.d.ts.map +1 -0
  123. package/dist/editor/types/Attribute.js +2 -0
  124. package/dist/editor/types/Attribute.js.map +1 -0
  125. package/dist/editor/types/ChangeSet.d.ts +7 -0
  126. package/dist/editor/types/ChangeSet.d.ts.map +1 -0
  127. package/dist/editor/types/ChangeSet.js +2 -0
  128. package/dist/editor/types/ChangeSet.js.map +1 -0
  129. package/dist/editor/types/ChangeSetBuilder.d.ts +7 -0
  130. package/dist/editor/types/ChangeSetBuilder.d.ts.map +1 -0
  131. package/dist/editor/types/ChangeSetBuilder.js +2 -0
  132. package/dist/editor/types/ChangeSetBuilder.js.map +1 -0
  133. package/dist/editor/types/RepModel.d.ts +25 -0
  134. package/dist/editor/types/RepModel.d.ts.map +1 -0
  135. package/dist/editor/types/RepModel.js +2 -0
  136. package/dist/editor/types/RepModel.js.map +1 -0
  137. package/dist/editor/undomodule.d.ts +30 -0
  138. package/dist/editor/undomodule.d.ts.map +1 -0
  139. package/dist/editor/undomodule.js +266 -0
  140. package/dist/editor/undomodule.js.map +1 -0
  141. package/dist/index.d.ts +7 -0
  142. package/dist/index.d.ts.map +1 -1
  143. package/dist/index.js +4 -0
  144. package/dist/index.js.map +1 -1
  145. package/package.json +7 -2
@@ -0,0 +1,2847 @@
1
+ /**
2
+ * AceEditor - Standalone editor engine adapted from Etherpad's ace2_inner.ts
3
+ *
4
+ * This is the core editing engine that works without iframes or collaboration
5
+ * dependencies. It uses a simple container element (a <div contenteditable="true">)
6
+ * inside Shadow DOM.
7
+ *
8
+ * Copyright 2009 Google Inc.
9
+ * Copyright 2020 John McLear - The Etherpad Foundation.
10
+ * Copyright 2025 - Adapted for standalone use.
11
+ *
12
+ * Licensed under the Apache License, Version 2.0 (the "License");
13
+ * you may not use this file except in compliance with the License.
14
+ * You may obtain a copy of the License at
15
+ *
16
+ * http://www.apache.org/licenses/LICENSE-2.0
17
+ *
18
+ * Unless required by applicable law or agreed to in writing, software
19
+ * distributed under the License is distributed on an "AS-IS" BASIS,
20
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
21
+ * See the License for the specific language governing permissions and
22
+ * limitations under the License.
23
+ */
24
+ import { Builder } from "./Builder.js";
25
+ import AttributeMap from './AttributeMap.js';
26
+ import { browserFlags as browser } from './browser_flags.js';
27
+ import * as Ace2Common from './ace2_common.js';
28
+ import { characterRangeFollow, checkRep, cloneAText, compose, deserializeOps, filterAttribNumbers, inverse, isIdentity, makeAText, makeAttribution, mapAttribNumbers, moveOpsToNewPool, mutateAttributionLines, mutateTextLines, oldLen, opsFromAText, pack, splitAttributionLines, } from './Changeset.js';
29
+ import { colorutils } from './colorutils.js';
30
+ import { makeContentCollector } from './contentcollector.js';
31
+ import { domline } from './domline.js';
32
+ import { linestylefilter } from './linestylefilter.js';
33
+ import { undoModule } from './undomodule.js';
34
+ import AttributeManager from './AttributeManager.js';
35
+ import { editorBus } from './core/EventBus.js';
36
+ import SkipList from "./skiplist.js";
37
+ import AttribPool from './AttributePool.js';
38
+ import { SmartOpAssembler } from "./SmartOpAssembler.js";
39
+ import Op from "./Op.js";
40
+ import { buildKeepRange, buildKeepToStartOfRange, buildRemoveRange } from './ChangesetUtils.js';
41
+ import { makeCSSManager } from './cssmanager.js';
42
+ import { makeChangesetTracker } from './changesettracker.js';
43
+ const isNodeText = Ace2Common.isNodeText;
44
+ const getAssoc = Ace2Common.getAssoc;
45
+ const setAssoc = Ace2Common.setAssoc;
46
+ export class AceEditor {
47
+ // -----------------------------------------------------------------------
48
+ // Constructor
49
+ // -----------------------------------------------------------------------
50
+ constructor(container) {
51
+ /** Callback invoked when the document text changes. */
52
+ this.onContentChanged = null;
53
+ /** Callback invoked when the selection changes. */
54
+ this.onSelectionChanged = null;
55
+ this.changesetTracker = null;
56
+ this.onKeyPressHandler = null;
57
+ this.onKeyDownHandler = null;
58
+ this.notifyDirtyHandler = null;
59
+ // -----------------------------------------------------------------------
60
+ // Private - List Operations
61
+ // -----------------------------------------------------------------------
62
+ this.listAttributeName = 'list';
63
+ this.targetBody = container;
64
+ this.targetDoc = container.ownerDocument;
65
+ const rn = container.getRootNode();
66
+ this.rootNode = (rn instanceof ShadowRoot) ? rn : this.targetDoc;
67
+ this.disposed = false;
68
+ this.isEditable = true;
69
+ this.doesWrap = true;
70
+ this.isStyled = true;
71
+ this.thisAuthor = '';
72
+ this.currentCallStack = null;
73
+ this._nextId = 1;
74
+ this.inInternationalComposition = null;
75
+ this.thisKeyDoesntTriggerNormalize = false;
76
+ this.authorInfos = {};
77
+ this.rep = {
78
+ lines: new SkipList(),
79
+ selStart: null,
80
+ selEnd: null,
81
+ selFocusAtStart: false,
82
+ alltext: '',
83
+ alines: [],
84
+ apool: new AttribPool(),
85
+ };
86
+ if (undoModule.enabled) {
87
+ undoModule.apool = this.rep.apool;
88
+ }
89
+ this.clearObservedChanges();
90
+ // Set up CSS manager
91
+ this.cssManager = this.createCSSManager();
92
+ // Init documentAttributeManager
93
+ this.documentAttributeManager = new AttributeManager(this.rep, (cs) => this.performDocumentApplyChangeset(cs));
94
+ // Create idle work timer
95
+ this.idleWorkTimer = this.makeIdleAction(() => {
96
+ if (this.inInternationalComposition) {
97
+ this.idleWorkTimer.atLeast(500);
98
+ return;
99
+ }
100
+ this.inCallStackIfNecessary('idleWorkTimer', () => {
101
+ const isTimeUp = this.newTimeLimit(250);
102
+ let finishedImportantWork = false;
103
+ let finishedWork = false;
104
+ try {
105
+ this.incorporateUserChanges();
106
+ if (isTimeUp())
107
+ return;
108
+ finishedImportantWork = true;
109
+ finishedWork = true;
110
+ }
111
+ finally {
112
+ if (finishedWork) {
113
+ this.idleWorkTimer.atMost(1000);
114
+ }
115
+ else if (finishedImportantWork) {
116
+ this.idleWorkTimer.atMost(500);
117
+ }
118
+ else {
119
+ let timeToWait = Math.round(isTimeUp.elapsed() / 2);
120
+ if (timeToWait < 100)
121
+ timeToWait = 100;
122
+ this.idleWorkTimer.atMost(timeToWait);
123
+ }
124
+ }
125
+ });
126
+ });
127
+ }
128
+ // -----------------------------------------------------------------------
129
+ // Lifecycle
130
+ // -----------------------------------------------------------------------
131
+ async init() {
132
+ this.inCallStack('setup', () => {
133
+ if (browser.firefox)
134
+ this.targetBody.classList.add('mozilla');
135
+ if (browser.safari)
136
+ this.targetBody.classList.add('safari');
137
+ this.targetBody.classList.toggle('authorColors', true);
138
+ this.targetBody.classList.toggle('doesWrap', this.doesWrap);
139
+ this.enforceEditability();
140
+ // Set up dom and rep
141
+ while (this.targetBody.firstChild) {
142
+ this.targetBody.removeChild(this.targetBody.firstChild);
143
+ }
144
+ const oneEntry = this.createDomLineEntry('');
145
+ this.doRepLineSplice(0, this.rep.lines.length(), [oneEntry]);
146
+ this.insertDomLines(null, [oneEntry.domInfo]);
147
+ this.rep.alines = splitAttributionLines(makeAttribution('\n'), '\n');
148
+ this.bindTheEventHandlers();
149
+ });
150
+ // Initialize changeset tracker for collaboration support
151
+ this.changesetTracker = makeChangesetTracker(window, this.rep.apool, {
152
+ withCallbacks: (operationName, f) => {
153
+ this.inCallStackIfNecessary(operationName, () => {
154
+ this.fastIncorp(1);
155
+ f({
156
+ setDocumentAttributedText: (atext) => {
157
+ this.setDocAText(atext);
158
+ },
159
+ applyChangesetToDocument: (changeset, preferInsertionAfterCaret) => {
160
+ const oldEventType = this.currentCallStack.editEvent.eventType;
161
+ this.currentCallStack.startNewEvent('nonundoable');
162
+ this.performDocumentApplyChangeset(changeset, preferInsertionAfterCaret);
163
+ this.currentCallStack.startNewEvent(oldEventType);
164
+ },
165
+ });
166
+ });
167
+ },
168
+ }, () => this.thisAuthor);
169
+ // Note: editor:ace:initialized is NOT emitted here.
170
+ // When used within etherpad, ace.ts emits this event with the shared info object
171
+ // that holds ace_* prefixed methods for plugin compatibility.
172
+ }
173
+ dispose() {
174
+ this.disposed = true;
175
+ if (this.idleWorkTimer)
176
+ this.idleWorkTimer.never();
177
+ }
178
+ // -----------------------------------------------------------------------
179
+ // Public API - Text
180
+ // -----------------------------------------------------------------------
181
+ getText() {
182
+ const alltext = this.rep.alltext;
183
+ let len = alltext.length;
184
+ if (len > 0)
185
+ len--; // final extra newline
186
+ return alltext.substring(0, len);
187
+ }
188
+ setText(text) {
189
+ this.importText(text, false, false);
190
+ }
191
+ getAttributedText() {
192
+ return {
193
+ text: this.rep.alltext,
194
+ attribs: this.rep.alines.join(''),
195
+ pool: this.rep.apool,
196
+ };
197
+ }
198
+ setAttributedText(atext, apoolJsonObj) {
199
+ this.importAText(atext, apoolJsonObj, false);
200
+ }
201
+ // -----------------------------------------------------------------------
202
+ // Public API - Focus / Editable
203
+ // -----------------------------------------------------------------------
204
+ focus() {
205
+ this.targetBody.focus();
206
+ }
207
+ setEditable(val) {
208
+ this.isEditable = val;
209
+ this.targetBody.contentEditable = this.isEditable ? 'true' : 'false';
210
+ this.targetBody.classList.toggle('static', !this.isEditable);
211
+ }
212
+ // -----------------------------------------------------------------------
213
+ // Public API - Formatting
214
+ // -----------------------------------------------------------------------
215
+ toggleAttribute(name) {
216
+ this.inCallStackIfNecessary('toggleAttribute', () => {
217
+ this.fastIncorp(13);
218
+ this.toggleAttributeOnSelection(name);
219
+ });
220
+ }
221
+ setAttribute(name, value) {
222
+ this.inCallStackIfNecessary('setAttribute', () => {
223
+ this.fastIncorp(13);
224
+ this.setAttributeOnSelection(name, value);
225
+ });
226
+ }
227
+ getAttribute(name) {
228
+ if (!(this.rep.selStart && this.rep.selEnd))
229
+ return false;
230
+ return !!this.getAttributeOnSelection(name);
231
+ }
232
+ // -----------------------------------------------------------------------
233
+ // Public API - Lists
234
+ // -----------------------------------------------------------------------
235
+ setLineListType(lineNum, listType) {
236
+ this.inCallStackIfNecessary('setLineListType', () => {
237
+ this.fastIncorp(9);
238
+ this._setLineListType(lineNum, listType);
239
+ });
240
+ }
241
+ indentOutdent(isOut) {
242
+ this.inCallStackIfNecessary('indentOutdent', () => {
243
+ this.fastIncorp(9);
244
+ this.doIndentOutdent(isOut);
245
+ });
246
+ }
247
+ insertUnorderedList() {
248
+ this.inCallStackIfNecessary('insertUnorderedList', () => {
249
+ this.fastIncorp(9);
250
+ this.doInsertUnorderedList();
251
+ });
252
+ }
253
+ insertOrderedList() {
254
+ this.inCallStackIfNecessary('insertOrderedList', () => {
255
+ this.fastIncorp(9);
256
+ this.doInsertOrderedList();
257
+ });
258
+ }
259
+ // -----------------------------------------------------------------------
260
+ // Public API - Undo / Redo
261
+ // -----------------------------------------------------------------------
262
+ undo() {
263
+ this.inCallStackIfNecessary('undo', () => {
264
+ this.fastIncorp(6);
265
+ this.doUndoRedo('undo');
266
+ });
267
+ }
268
+ redo() {
269
+ this.inCallStackIfNecessary('redo', () => {
270
+ this.fastIncorp(10);
271
+ this.doUndoRedo('redo');
272
+ });
273
+ }
274
+ // -----------------------------------------------------------------------
275
+ // Public API - Changeset (for external collaboration)
276
+ // -----------------------------------------------------------------------
277
+ applyChangeset(cs, _optAuthor, apoolJsonObj) {
278
+ this.inCallStackIfNecessary('applyChangeset', () => {
279
+ this.fastIncorp(1);
280
+ if (apoolJsonObj) {
281
+ const wireApool = (new AttribPool()).fromJsonable(apoolJsonObj);
282
+ cs = moveOpsToNewPool(cs, wireApool, this.rep.apool);
283
+ }
284
+ const oldEventType = this.currentCallStack.editEvent.eventType;
285
+ this.currentCallStack.startNewEvent('nonundoable');
286
+ this.performDocumentApplyChangeset(cs);
287
+ this.currentCallStack.startNewEvent(oldEventType);
288
+ });
289
+ }
290
+ prepareUserChangeset() {
291
+ if (!this.rep || !this.rep.apool)
292
+ return null;
293
+ // Incorporate any pending user changes first
294
+ if (this.currentCallStack) {
295
+ this.fastIncorp(1);
296
+ }
297
+ else {
298
+ this.inCallStackIfNecessary('prepareUserChangeset', () => {
299
+ this.fastIncorp(1);
300
+ });
301
+ }
302
+ if (this.changesetTracker) {
303
+ return this.changesetTracker.prepareUserChangeset();
304
+ }
305
+ return {
306
+ changeset: null,
307
+ apool: this.rep.apool.toJsonable(),
308
+ };
309
+ }
310
+ // -----------------------------------------------------------------------
311
+ // Public API - Author
312
+ // -----------------------------------------------------------------------
313
+ setAuthor(authorId) {
314
+ this.thisAuthor = String(authorId);
315
+ if (this.documentAttributeManager) {
316
+ this.documentAttributeManager.author = this.thisAuthor;
317
+ }
318
+ }
319
+ setAuthorInfo(author, info) {
320
+ if (!author)
321
+ return;
322
+ if (typeof author !== 'string') {
323
+ throw new Error(`setAuthorInfo: author (${author}) is not a string`);
324
+ }
325
+ if (!info) {
326
+ delete this.authorInfos[author];
327
+ }
328
+ else {
329
+ this.authorInfos[author] = info;
330
+ }
331
+ this.setAuthorStyle(author, info);
332
+ }
333
+ // -----------------------------------------------------------------------
334
+ // Public API - Collaboration (changesetTracker delegation)
335
+ // -----------------------------------------------------------------------
336
+ setBaseText(txt) {
337
+ this.changesetTracker?.setBaseText(txt);
338
+ }
339
+ setBaseAttributedText(atxt, apoolJsonObj) {
340
+ this.changesetTracker?.setBaseAttributedText(atxt, apoolJsonObj);
341
+ }
342
+ applyChangesToBase(c, optAuthor, apoolJsonObj) {
343
+ this.changesetTracker?.applyChangesToBase(c, optAuthor, apoolJsonObj);
344
+ }
345
+ applyPreparedChangesetToBase() {
346
+ this.changesetTracker?.applyPreparedChangesetToBase();
347
+ }
348
+ setUserChangeNotificationCallback(f) {
349
+ this.changesetTracker?.setUserChangeNotificationCallback(f);
350
+ }
351
+ // -----------------------------------------------------------------------
352
+ // Public API - Etherpad Compatibility
353
+ // -----------------------------------------------------------------------
354
+ setProperty(key, value) {
355
+ switch (key) {
356
+ case 'wraps':
357
+ this.setWraps(value);
358
+ break;
359
+ case 'showsauthorcolors':
360
+ this.targetBody.classList.toggle('authorColors', !!value);
361
+ break;
362
+ case 'showsuserselections':
363
+ this.targetBody.classList.toggle('userSelections', !!value);
364
+ break;
365
+ case 'userauthor':
366
+ this.setAuthor(value);
367
+ break;
368
+ case 'styled':
369
+ this.setStyled(value);
370
+ break;
371
+ case 'textface':
372
+ this.targetBody.style.fontFamily = value || '';
373
+ break;
374
+ case 'rtlIsTrue':
375
+ this.targetBody.dir = value ? 'rtl' : 'ltr';
376
+ break;
377
+ case 'showslinenumbers':
378
+ // Line numbers are not managed by the editor itself
379
+ break;
380
+ }
381
+ }
382
+ exportText() {
383
+ return this.getText();
384
+ }
385
+ getInInternationalComposition() {
386
+ return this.inInternationalComposition;
387
+ }
388
+ setOnKeyPress(handler) {
389
+ this.onKeyPressHandler = handler;
390
+ }
391
+ setOnKeyDown(handler) {
392
+ this.onKeyDownHandler = handler;
393
+ }
394
+ setNotifyDirty(handler) {
395
+ this.notifyDirtyHandler = handler;
396
+ }
397
+ callWithAce(fn, callStack, normalize) {
398
+ let wrapper = () => fn(this);
399
+ if (normalize !== undefined) {
400
+ const inner = wrapper;
401
+ wrapper = () => {
402
+ this.fastIncorp(9);
403
+ return inner();
404
+ };
405
+ }
406
+ if (callStack !== undefined) {
407
+ return this.inCallStackIfNecessary(callStack, wrapper);
408
+ }
409
+ return wrapper();
410
+ }
411
+ // -----------------------------------------------------------------------
412
+ // Public API - Selection info
413
+ // -----------------------------------------------------------------------
414
+ isCaret() {
415
+ return !!(this.rep.selStart && this.rep.selEnd &&
416
+ this.rep.selStart[0] === this.rep.selEnd[0] &&
417
+ this.rep.selStart[1] === this.rep.selEnd[1]);
418
+ }
419
+ getCaretLine() {
420
+ if (!this.rep.selStart)
421
+ return -1;
422
+ return this.rep.selStart[0];
423
+ }
424
+ getCaretColumn() {
425
+ if (!this.rep.selStart)
426
+ return -1;
427
+ return this.rep.selStart[1];
428
+ }
429
+ // -----------------------------------------------------------------------
430
+ // Public API - Misc
431
+ // -----------------------------------------------------------------------
432
+ replaceRange(start, end, text) {
433
+ this.inCallStackIfNecessary('replaceRange', () => {
434
+ this.fastIncorp(9);
435
+ this.performDocumentReplaceRange(start, end, text);
436
+ });
437
+ }
438
+ execCommand(cmd, ...args) {
439
+ cmd = cmd.toLowerCase();
440
+ const cmds = {
441
+ clearauthorship: (prompt) => {
442
+ if (!(this.rep.selStart && this.rep.selEnd) || this.isCaret()) {
443
+ if (prompt) {
444
+ prompt();
445
+ }
446
+ else {
447
+ this.performDocumentApplyAttributesToCharRange(0, this.rep.alltext.length, [
448
+ ['author', ''],
449
+ ]);
450
+ }
451
+ }
452
+ else {
453
+ this.setAttributeOnSelection('author', '');
454
+ }
455
+ },
456
+ };
457
+ if (cmds[cmd]) {
458
+ this.inCallStackIfNecessary(cmd, () => {
459
+ this.fastIncorp(9);
460
+ cmds[cmd](...args);
461
+ });
462
+ }
463
+ }
464
+ setWraps(newVal) {
465
+ this.doesWrap = newVal;
466
+ this.targetBody.classList.toggle('doesWrap', this.doesWrap);
467
+ setTimeout(() => {
468
+ this.inCallStackIfNecessary('setWraps', () => {
469
+ this.fastIncorp(7);
470
+ this.recreateDOM();
471
+ });
472
+ }, 0);
473
+ }
474
+ setStyled(newVal) {
475
+ const oldVal = this.isStyled;
476
+ this.isStyled = !!newVal;
477
+ if (newVal !== oldVal) {
478
+ if (!newVal) {
479
+ this.inCallStackIfNecessary('setStyled', () => {
480
+ this.fastIncorp(12);
481
+ const clearStyles = [];
482
+ for (const k of Object.keys(AceEditor.STYLE_ATTRIBS)) {
483
+ clearStyles.push([k, '']);
484
+ }
485
+ this.performDocumentApplyAttributesToCharRange(0, this.rep.alltext.length, clearStyles);
486
+ });
487
+ }
488
+ }
489
+ }
490
+ // -----------------------------------------------------------------------
491
+ // Private - CSS Manager
492
+ // -----------------------------------------------------------------------
493
+ createCSSManager() {
494
+ const styleElement = this.targetDoc.createElement('style');
495
+ // If the container is inside a shadow root, append there; otherwise append to head
496
+ const root = this.targetBody.getRootNode();
497
+ if (root instanceof ShadowRoot) {
498
+ root.appendChild(styleElement);
499
+ }
500
+ else if (this.targetBody.parentNode) {
501
+ this.targetBody.parentNode.insertBefore(styleElement, this.targetBody);
502
+ }
503
+ else {
504
+ this.targetDoc.head.appendChild(styleElement);
505
+ }
506
+ return makeCSSManager(styleElement.sheet);
507
+ }
508
+ // -----------------------------------------------------------------------
509
+ // Private - Author Styling
510
+ // -----------------------------------------------------------------------
511
+ getAuthorClassName(author) {
512
+ return `author-${author.replace(/[^a-y0-9]/g, (c) => {
513
+ if (c === '.')
514
+ return '-';
515
+ return `z${c.charCodeAt(0)}z`;
516
+ })}`;
517
+ }
518
+ className2Author(className) {
519
+ if (className.substring(0, 7) === 'author-') {
520
+ return className.substring(7).replace(/[a-y0-9]+|-|z.+?z/g, (cc) => {
521
+ if (cc === '-')
522
+ return '.';
523
+ if (cc.charAt(0) === 'z')
524
+ return String.fromCharCode(Number(cc.slice(1, -1)));
525
+ return cc;
526
+ });
527
+ }
528
+ return null;
529
+ }
530
+ getAuthorColorClassSelector(oneClassName) {
531
+ return `.authorColors .${oneClassName}`;
532
+ }
533
+ fadeColor(colorCSS, fadeFrac) {
534
+ let color = colorutils.css2triple(colorCSS);
535
+ color = colorutils.blend(color, [1, 1, 1], fadeFrac);
536
+ return colorutils.triple2css(color);
537
+ }
538
+ setAuthorStyle(author, info) {
539
+ const authorSelector = this.getAuthorColorClassSelector(this.getAuthorClassName(author));
540
+ if (!info) {
541
+ this.cssManager.removeSelectorStyle(authorSelector);
542
+ }
543
+ else if (info.bgcolor) {
544
+ let bgcolor = info.bgcolor;
545
+ if (typeof info.fade === 'number') {
546
+ bgcolor = this.fadeColor(bgcolor, info.fade);
547
+ }
548
+ const textColor = colorutils.textColorFromBackgroundColor(bgcolor, 'default');
549
+ const style = this.cssManager.selectorStyle(authorSelector);
550
+ style.backgroundColor = bgcolor;
551
+ style.color = textColor;
552
+ style['padding-top'] = '3px';
553
+ style['padding-bottom'] = '4px';
554
+ }
555
+ }
556
+ // -----------------------------------------------------------------------
557
+ // Private - Call Stack Management
558
+ // -----------------------------------------------------------------------
559
+ inCallStack(type, action) {
560
+ if (this.disposed)
561
+ return;
562
+ const newEditEvent = (eventType) => ({
563
+ eventType,
564
+ backset: null,
565
+ });
566
+ const submitOldEvent = (evt) => {
567
+ if (this.rep.selStart && this.rep.selEnd) {
568
+ const selStartChar = this.rep.lines.offsetOfIndex(this.rep.selStart[0]) +
569
+ this.rep.selStart[1];
570
+ const selEndChar = this.rep.lines.offsetOfIndex(this.rep.selEnd[0]) +
571
+ this.rep.selEnd[1];
572
+ evt.selStart = selStartChar;
573
+ evt.selEnd = selEndChar;
574
+ evt.selFocusAtStart = this.rep.selFocusAtStart;
575
+ }
576
+ if (undoModule.enabled) {
577
+ let undoWorked = false;
578
+ try {
579
+ if (this.isPadLoading(evt.eventType)) {
580
+ undoModule.clearHistory();
581
+ }
582
+ else if (evt.eventType === 'nonundoable') {
583
+ if (evt.changeset) {
584
+ undoModule.reportExternalChange(evt.changeset);
585
+ }
586
+ }
587
+ else {
588
+ undoModule.reportEvent(evt);
589
+ }
590
+ undoWorked = true;
591
+ }
592
+ finally {
593
+ if (!undoWorked) {
594
+ undoModule.enabled = false;
595
+ }
596
+ }
597
+ }
598
+ };
599
+ const startNewEvent = (eventType, dontSubmitOld) => {
600
+ const oldEvent = this.currentCallStack.editEvent;
601
+ if (!dontSubmitOld) {
602
+ submitOldEvent(oldEvent);
603
+ }
604
+ this.currentCallStack.editEvent = newEditEvent(eventType);
605
+ return oldEvent;
606
+ };
607
+ this.currentCallStack = {
608
+ type,
609
+ docTextChanged: false,
610
+ selectionAffected: false,
611
+ userChangedSelection: false,
612
+ domClean: false,
613
+ isUserChange: false,
614
+ repChanged: false,
615
+ editEvent: newEditEvent(type),
616
+ startNewEvent,
617
+ };
618
+ let cleanExit = false;
619
+ let result;
620
+ try {
621
+ result = action();
622
+ editorBus.emit('editor:content:changed', { text: this.rep.alltext });
623
+ if (this.onContentChanged)
624
+ this.onContentChanged(this.rep.alltext);
625
+ if (this.notifyDirtyHandler)
626
+ this.notifyDirtyHandler();
627
+ cleanExit = true;
628
+ }
629
+ finally {
630
+ const cs = this.currentCallStack;
631
+ if (cleanExit) {
632
+ submitOldEvent(cs.editEvent);
633
+ if (cs.domClean && cs.type !== 'setup') {
634
+ if (cs.selectionAffected) {
635
+ this.updateBrowserSelectionFromRep();
636
+ }
637
+ if (cs.docTextChanged && cs.type.indexOf('importText') < 0) {
638
+ // Document changed notification
639
+ }
640
+ }
641
+ }
642
+ else if (this.currentCallStack.type === 'idleWorkTimer') {
643
+ this.idleWorkTimer.atLeast(1000);
644
+ }
645
+ this.currentCallStack = null;
646
+ }
647
+ return result;
648
+ }
649
+ inCallStackIfNecessary(type, action) {
650
+ if (!this.currentCallStack) {
651
+ return this.inCallStack(type, action);
652
+ }
653
+ else {
654
+ return action();
655
+ }
656
+ }
657
+ // -----------------------------------------------------------------------
658
+ // Private - Timers
659
+ // -----------------------------------------------------------------------
660
+ now() {
661
+ return Date.now();
662
+ }
663
+ newTimeLimit(ms) {
664
+ const startTime = this.now();
665
+ let exceededAlready = false;
666
+ const isTimeUp = () => {
667
+ if (exceededAlready)
668
+ return true;
669
+ const elapsed = this.now() - startTime;
670
+ if (elapsed > ms) {
671
+ exceededAlready = true;
672
+ return true;
673
+ }
674
+ return false;
675
+ };
676
+ isTimeUp.elapsed = () => this.now() - startTime;
677
+ return isTimeUp;
678
+ }
679
+ makeIdleAction(func) {
680
+ let scheduledTimeout = null;
681
+ let scheduledTime = 0;
682
+ const unschedule = () => {
683
+ if (scheduledTimeout) {
684
+ clearTimeout(scheduledTimeout);
685
+ scheduledTimeout = null;
686
+ }
687
+ };
688
+ const reschedule = (time) => {
689
+ unschedule();
690
+ scheduledTime = time;
691
+ let delay = time - this.now();
692
+ if (delay < 0)
693
+ delay = 0;
694
+ scheduledTimeout = setTimeout(callback, delay);
695
+ };
696
+ const callback = () => {
697
+ scheduledTimeout = null;
698
+ func();
699
+ };
700
+ return {
701
+ atMost: (ms) => {
702
+ const latestTime = this.now() + ms;
703
+ if (!scheduledTimeout || scheduledTime > latestTime) {
704
+ reschedule(latestTime);
705
+ }
706
+ },
707
+ atLeast: (ms) => {
708
+ const earliestTime = this.now() + ms;
709
+ if (!scheduledTimeout || scheduledTime < earliestTime) {
710
+ reschedule(earliestTime);
711
+ }
712
+ },
713
+ never: () => {
714
+ unschedule();
715
+ },
716
+ };
717
+ }
718
+ // -----------------------------------------------------------------------
719
+ // Private - Document Operations
720
+ // -----------------------------------------------------------------------
721
+ fastIncorp(_n) {
722
+ this.incorporateUserChanges();
723
+ }
724
+ setDocText(text) {
725
+ this.setDocAText(makeAText(text));
726
+ }
727
+ setDocAText(atext) {
728
+ if (atext.text === '') {
729
+ atext.text = '\n';
730
+ }
731
+ this.fastIncorp(8);
732
+ if (this.rep.lines.length() === 0) {
733
+ // No lines yet - bootstrap the initial empty line directly
734
+ const oneEntry = this.createDomLineEntry('');
735
+ oneEntry.width = 1; // newline char
736
+ this.rep.lines.splice(0, 0, [oneEntry]);
737
+ this.rep.alltext = '\n';
738
+ this.rep.alines = splitAttributionLines(makeAttribution('\n'), '\n');
739
+ this.insertDomLines(null, [oneEntry.domInfo]);
740
+ }
741
+ // Reset the edit event so the full doc replacement is captured cleanly for undo.
742
+ // fastIncorp above may have created a partial backset from incorporateUserChanges
743
+ // that has mismatched lengths with the changeset we're about to build.
744
+ if (this.currentCallStack && this.currentCallStack.editEvent) {
745
+ this.currentCallStack.editEvent.backset = null;
746
+ this.currentCallStack.editEvent.changeset = null;
747
+ }
748
+ const currentOldLen = this.rep.lines.totalWidth();
749
+ const numLines = this.rep.lines.length();
750
+ const upToLastLine = this.rep.lines.offsetOfIndex(numLines - 1);
751
+ const lastLineLength = this.rep.lines.atIndex(numLines - 1).text.length;
752
+ const assem = new SmartOpAssembler();
753
+ const o = new Op('-');
754
+ o.chars = upToLastLine;
755
+ o.lines = numLines - 1;
756
+ assem.append(o);
757
+ o.chars = lastLineLength;
758
+ o.lines = 0;
759
+ assem.append(o);
760
+ for (const op of opsFromAText(atext))
761
+ assem.append(op);
762
+ const newLen = currentOldLen + assem.getLengthChange();
763
+ const changeset = checkRep(pack(currentOldLen, newLen, assem.toString(), atext.text.slice(0, -1)));
764
+ this.performDocumentApplyChangeset(changeset);
765
+ this.performSelectionChange([0, this.rep.lines.atIndex(0).lineMarker], [0, this.rep.lines.atIndex(0).lineMarker]);
766
+ this.idleWorkTimer.atMost(100);
767
+ if (this.rep.alltext !== atext.text) {
768
+ throw new Error('mismatch error setting raw text in setDocAText');
769
+ }
770
+ }
771
+ importText(text, undoable, dontProcess) {
772
+ let lines;
773
+ if (dontProcess) {
774
+ if (text.charAt(text.length - 1) !== '\n') {
775
+ throw new Error('new raw text must end with newline');
776
+ }
777
+ if (/[\r\t\xa0]/.exec(text)) {
778
+ throw new Error('new raw text must not contain CR, tab, or nbsp');
779
+ }
780
+ lines = text.substring(0, text.length - 1).split('\n');
781
+ }
782
+ else {
783
+ lines = text.split('\n').map((s) => this.textify(s));
784
+ }
785
+ let newText = '\n';
786
+ if (lines.length > 0) {
787
+ newText = `${lines.join('\n')}\n`;
788
+ }
789
+ this.inCallStackIfNecessary(`importText${undoable ? 'Undoable' : ''}`, () => {
790
+ this.setDocText(newText);
791
+ });
792
+ if (dontProcess && this.rep.alltext !== text) {
793
+ throw new Error('mismatch error setting raw text in importText');
794
+ }
795
+ }
796
+ importAText(atext, apoolJsonObj, undoable) {
797
+ atext = cloneAText(atext);
798
+ if (apoolJsonObj) {
799
+ const wireApool = (new AttribPool()).fromJsonable(apoolJsonObj);
800
+ atext.attribs = moveOpsToNewPool(atext.attribs, wireApool, this.rep.apool);
801
+ }
802
+ this.inCallStackIfNecessary(`importText${undoable ? 'Undoable' : ''}`, () => {
803
+ this.setDocAText(atext);
804
+ });
805
+ }
806
+ performDocumentReplaceRange(start, end, newText) {
807
+ if (start === undefined)
808
+ start = this.rep.selStart;
809
+ if (end === undefined)
810
+ end = this.rep.selEnd;
811
+ const builder = new Builder(this.rep.lines.totalWidth());
812
+ buildKeepToStartOfRange(this.rep, builder, start);
813
+ buildRemoveRange(this.rep, builder, start, end);
814
+ builder.insert(newText, [
815
+ ['author', this.thisAuthor],
816
+ ], this.rep.apool);
817
+ const cs = builder.toString();
818
+ this.performDocumentApplyChangeset(cs);
819
+ }
820
+ performDocumentReplaceSelection(newText) {
821
+ if (!(this.rep.selStart && this.rep.selEnd))
822
+ return;
823
+ this.performDocumentReplaceRange(this.rep.selStart, this.rep.selEnd, newText);
824
+ }
825
+ performDocumentReplaceCharRange(startChar, endChar, newText) {
826
+ if (startChar === endChar && newText.length === 0)
827
+ return;
828
+ if (endChar === this.rep.alltext.length) {
829
+ if (startChar === endChar) {
830
+ startChar--;
831
+ endChar--;
832
+ newText = `\n${newText.substring(0, newText.length - 1)}`;
833
+ }
834
+ else if (newText.length === 0) {
835
+ startChar--;
836
+ endChar--;
837
+ }
838
+ else {
839
+ endChar--;
840
+ newText = newText.substring(0, newText.length - 1);
841
+ }
842
+ }
843
+ this.performDocumentReplaceRange(this.lineAndColumnFromChar(startChar), this.lineAndColumnFromChar(endChar), newText);
844
+ }
845
+ performDocumentApplyAttributesToCharRange(start, end, attribs) {
846
+ end = Math.min(end, this.rep.alltext.length - 1);
847
+ this.documentAttributeManager.setAttributesOnRange(this.lineAndColumnFromChar(start), this.lineAndColumnFromChar(end), attribs);
848
+ }
849
+ performDocumentApplyChangeset(changes, insertsAfterSelection) {
850
+ const domAndRepSplice = (startLine, deleteCount, newLineStrings) => {
851
+ const keysToDelete = [];
852
+ if (deleteCount > 0) {
853
+ let entryToDelete = this.rep.lines.atIndex(startLine);
854
+ for (let i = 0; i < deleteCount; i++) {
855
+ keysToDelete.push(entryToDelete.key);
856
+ entryToDelete = this.rep.lines.next(entryToDelete);
857
+ }
858
+ }
859
+ const lineEntries = newLineStrings.map((s) => this.createDomLineEntry(s));
860
+ this.doRepLineSplice(startLine, deleteCount, lineEntries);
861
+ let nodeToAddAfter;
862
+ if (startLine > 0) {
863
+ nodeToAddAfter = this.getCleanNodeByKey(this.rep.lines.atIndex(startLine - 1).key);
864
+ }
865
+ else {
866
+ nodeToAddAfter = null;
867
+ }
868
+ this.insertDomLines(nodeToAddAfter, lineEntries.map((entry) => entry.domInfo));
869
+ for (const k of keysToDelete) {
870
+ const n = this.rootNode.getElementById(k);
871
+ if (n && n.parentNode)
872
+ n.parentNode.removeChild(n);
873
+ }
874
+ if ((this.rep.selStart &&
875
+ this.rep.selStart[0] >= startLine &&
876
+ this.rep.selStart[0] <= startLine + deleteCount) ||
877
+ (this.rep.selEnd &&
878
+ this.rep.selEnd[0] >= startLine &&
879
+ this.rep.selEnd[0] <= startLine + deleteCount)) {
880
+ this.currentCallStack.selectionAffected = true;
881
+ }
882
+ };
883
+ this.doRepApplyChangeset(changes, insertsAfterSelection);
884
+ let requiredSelectionSetting = null;
885
+ if (this.rep.selStart && this.rep.selEnd) {
886
+ const selStartChar = this.rep.lines.offsetOfIndex(this.rep.selStart[0]) +
887
+ this.rep.selStart[1];
888
+ const selEndChar = this.rep.lines.offsetOfIndex(this.rep.selEnd[0]) +
889
+ this.rep.selEnd[1];
890
+ const result = characterRangeFollow(changes, selStartChar, selEndChar, insertsAfterSelection ? 1 : 0);
891
+ requiredSelectionSetting = [result[0], result[1], this.rep.selFocusAtStart];
892
+ }
893
+ const linesMutatee = {
894
+ splice: (start, numRemoved, ...args) => {
895
+ domAndRepSplice(start, numRemoved, args.map((s) => s.slice(0, -1)));
896
+ },
897
+ get: (i) => `${this.rep.lines.atIndex(i).text}\n`,
898
+ length: () => this.rep.lines.length(),
899
+ };
900
+ mutateTextLines(changes, linesMutatee);
901
+ if (requiredSelectionSetting) {
902
+ this.performSelectionChange(this.lineAndColumnFromChar(requiredSelectionSetting[0]), this.lineAndColumnFromChar(requiredSelectionSetting[1]), requiredSelectionSetting[2]);
903
+ }
904
+ }
905
+ doRepApplyChangeset(changes, _insertsAfterSelection) {
906
+ checkRep(changes);
907
+ if (oldLen(changes) !== this.rep.alltext.length) {
908
+ const errMsg = `${oldLen(changes)}/${this.rep.alltext.length}`;
909
+ throw new Error(`doRepApplyChangeset length mismatch: ${errMsg}`);
910
+ }
911
+ const editEvent = this.currentCallStack.editEvent;
912
+ if (editEvent.eventType === 'nonundoable') {
913
+ if (!editEvent.changeset) {
914
+ editEvent.changeset = changes;
915
+ }
916
+ else {
917
+ editEvent.changeset = compose(editEvent.changeset, changes, this.rep.apool);
918
+ }
919
+ }
920
+ else {
921
+ const inverseChangeset = inverse(changes, {
922
+ get: (i) => `${this.rep.lines.atIndex(i).text}\n`,
923
+ length: () => this.rep.lines.length(),
924
+ }, this.rep.alines, this.rep.apool);
925
+ if (!editEvent.backset) {
926
+ editEvent.backset = inverseChangeset;
927
+ }
928
+ else {
929
+ editEvent.backset = compose(inverseChangeset, editEvent.backset, this.rep.apool);
930
+ }
931
+ }
932
+ mutateAttributionLines(changes, this.rep.alines, this.rep.apool);
933
+ // Track user changes for collaboration
934
+ if (this.changesetTracker?.isTracking()) {
935
+ this.changesetTracker.composeUserChangeset(changes);
936
+ }
937
+ }
938
+ // -----------------------------------------------------------------------
939
+ // Private - Line / Char Conversion
940
+ // -----------------------------------------------------------------------
941
+ lineAndColumnFromChar(x) {
942
+ const lineEntry = this.rep.lines.atOffset(x);
943
+ const lineStart = this.rep.lines.offsetOfEntry(lineEntry);
944
+ const lineNum = this.rep.lines.indexOfEntry(lineEntry);
945
+ return [lineNum, x - lineStart];
946
+ }
947
+ // -----------------------------------------------------------------------
948
+ // Private - DOM Rendering
949
+ // -----------------------------------------------------------------------
950
+ doCreateDomLine(nonEmpty) {
951
+ return domline.createDomLine(nonEmpty, this.doesWrap, browser, this.targetDoc);
952
+ }
953
+ createDomLineEntry(lineString) {
954
+ const info = this.doCreateDomLine(lineString.length > 0);
955
+ const newNode = info.node;
956
+ return {
957
+ key: this.uniqueId(newNode),
958
+ text: lineString,
959
+ lineNode: newNode,
960
+ domInfo: info,
961
+ lineMarker: 0,
962
+ };
963
+ }
964
+ insertDomLines(nodeToAddAfter, infoStructs) {
965
+ let lastEntry;
966
+ let lineStartOffset = 0;
967
+ for (const info of infoStructs) {
968
+ const node = info.node;
969
+ const key = this.uniqueId(node);
970
+ let entry;
971
+ if (lastEntry) {
972
+ const next = this.rep.lines.next(lastEntry);
973
+ if (next && next.key === key) {
974
+ entry = next;
975
+ lineStartOffset += lastEntry.width;
976
+ }
977
+ }
978
+ if (!entry) {
979
+ entry = this.rep.lines.atKey(key);
980
+ lineStartOffset = this.rep.lines.offsetOfKey(key);
981
+ }
982
+ lastEntry = entry;
983
+ this.getSpansForLine(entry, (tokenText, tokenClass) => {
984
+ info.appendSpan(tokenText, tokenClass);
985
+ }, lineStartOffset);
986
+ info.prepareForAdd();
987
+ entry.lineMarker = info.lineMarker;
988
+ if (!nodeToAddAfter) {
989
+ this.targetBody.insertBefore(node, this.targetBody.firstChild);
990
+ }
991
+ else {
992
+ this.targetBody.insertBefore(node, nodeToAddAfter.nextSibling);
993
+ }
994
+ nodeToAddAfter = node;
995
+ info.notifyAdded();
996
+ this.markNodeClean(node);
997
+ }
998
+ }
999
+ recolorLinesInRange(startChar, endChar) {
1000
+ if (endChar <= startChar)
1001
+ return;
1002
+ if (startChar < 0 || startChar >= this.rep.lines.totalWidth())
1003
+ return;
1004
+ let lineEntry = this.rep.lines.atOffset(startChar);
1005
+ let lineStart = this.rep.lines.offsetOfEntry(lineEntry);
1006
+ let lineIndex = this.rep.lines.indexOfEntry(lineEntry);
1007
+ let selectionNeedsResetting = false;
1008
+ const tokenFunc = (tokenText, tokenClass) => {
1009
+ lineEntry.domInfo.appendSpan(tokenText, tokenClass);
1010
+ };
1011
+ while (lineEntry && lineStart < endChar) {
1012
+ const lineEnd = lineStart + lineEntry.width;
1013
+ lineEntry.domInfo.clearSpans();
1014
+ this.getSpansForLine(lineEntry, tokenFunc, lineStart);
1015
+ lineEntry.domInfo.finishUpdate();
1016
+ // Sync lineMarker from domInfo (e.g. heading lines have lineMarker=1)
1017
+ lineEntry.lineMarker = lineEntry.domInfo.lineMarker;
1018
+ this.markNodeClean(lineEntry.lineNode);
1019
+ if ((this.rep.selStart && this.rep.selStart[0] === lineIndex) ||
1020
+ (this.rep.selEnd && this.rep.selEnd[0] === lineIndex)) {
1021
+ selectionNeedsResetting = true;
1022
+ }
1023
+ lineStart = lineEnd;
1024
+ lineEntry = this.rep.lines.next(lineEntry);
1025
+ lineIndex++;
1026
+ }
1027
+ if (selectionNeedsResetting) {
1028
+ this.currentCallStack.selectionAffected = true;
1029
+ }
1030
+ }
1031
+ getSpansForLine(lineEntry, textAndClassFunc, lineEntryOffsetHint) {
1032
+ let lineEntryOffset = lineEntryOffsetHint;
1033
+ if (typeof lineEntryOffset !== 'number') {
1034
+ lineEntryOffset = this.rep.lines.offsetOfEntry(lineEntry);
1035
+ }
1036
+ const text = lineEntry.text;
1037
+ if (text.length === 0) {
1038
+ const func = linestylefilter.getLineStyleFilter(0, '', textAndClassFunc, this.rep.apool);
1039
+ func('', '');
1040
+ }
1041
+ else {
1042
+ let filteredFunc = linestylefilter.getFilterStack(text, textAndClassFunc, browser);
1043
+ const lineNum = this.rep.lines.indexOfEntry(lineEntry);
1044
+ const aline = this.rep.alines[lineNum];
1045
+ filteredFunc = linestylefilter.getLineStyleFilter(text.length, aline, filteredFunc, this.rep.apool);
1046
+ filteredFunc(text, '');
1047
+ }
1048
+ }
1049
+ recreateDOM() {
1050
+ this.recolorLinesInRange(0, this.rep.alltext.length);
1051
+ }
1052
+ // -----------------------------------------------------------------------
1053
+ // Private - Rep Splice
1054
+ // -----------------------------------------------------------------------
1055
+ doRepLineSplice(startLine, deleteCount, newLineEntries) {
1056
+ for (const entry of newLineEntries)
1057
+ entry.width = entry.text.length + 1;
1058
+ const startOldChar = this.rep.lines.offsetOfIndex(startLine);
1059
+ const endOldChar = this.rep.lines.offsetOfIndex(startLine + deleteCount);
1060
+ this.rep.lines.splice(startLine, deleteCount, newLineEntries);
1061
+ if (this.currentCallStack) {
1062
+ this.currentCallStack.docTextChanged = true;
1063
+ this.currentCallStack.repChanged = true;
1064
+ }
1065
+ const newText = newLineEntries.map((e) => `${e.text}\n`).join('');
1066
+ this.rep.alltext = this.rep.alltext.substring(0, startOldChar) +
1067
+ newText + this.rep.alltext.substring(endOldChar, this.rep.alltext.length);
1068
+ }
1069
+ doIncorpLineSplice(startLine, deleteCount, newLineEntries, lineAttribs, hints) {
1070
+ const startOldChar = this.rep.lines.offsetOfIndex(startLine);
1071
+ const endOldChar = this.rep.lines.offsetOfIndex(startLine + deleteCount);
1072
+ const oldRegionStart = this.rep.lines.offsetOfIndex(startLine);
1073
+ let selStartHintChar;
1074
+ let selEndHintChar;
1075
+ if (hints && hints.selStart) {
1076
+ selStartHintChar =
1077
+ this.rep.lines.offsetOfIndex(hints.selStart[0]) + hints.selStart[1] - oldRegionStart;
1078
+ }
1079
+ if (hints && hints.selEnd) {
1080
+ selEndHintChar =
1081
+ this.rep.lines.offsetOfIndex(hints.selEnd[0]) + hints.selEnd[1] - oldRegionStart;
1082
+ }
1083
+ const newText = newLineEntries.map((e) => `${e.text}\n`).join('');
1084
+ const oldText = this.rep.alltext.substring(startOldChar, endOldChar);
1085
+ const oldAttribs = this.rep.alines.slice(startLine, startLine + deleteCount).join('');
1086
+ const newAttribs = `${lineAttribs.join('|1+1')}|1+1`;
1087
+ const analysis = this.analyzeChange(oldText, newText, oldAttribs, newAttribs, selStartHintChar, selEndHintChar);
1088
+ const commonStart = analysis[0];
1089
+ let commonEnd = analysis[1];
1090
+ let shortOldText = oldText.substring(commonStart, oldText.length - commonEnd);
1091
+ let shortNewText = newText.substring(commonStart, newText.length - commonEnd);
1092
+ let spliceStart = startOldChar + commonStart;
1093
+ let spliceEnd = endOldChar - commonEnd;
1094
+ let shiftFinalNewlineToBeforeNewText = false;
1095
+ if (shortOldText.charAt(shortOldText.length - 1) === '\n' &&
1096
+ shortNewText.charAt(shortNewText.length - 1) === '\n') {
1097
+ shortOldText = shortOldText.slice(0, -1);
1098
+ shortNewText = shortNewText.slice(0, -1);
1099
+ spliceEnd--;
1100
+ commonEnd++;
1101
+ }
1102
+ if (shortOldText.length === 0 &&
1103
+ spliceStart === this.rep.alltext.length &&
1104
+ shortNewText.length > 0) {
1105
+ spliceStart--;
1106
+ spliceEnd--;
1107
+ shortNewText = `\n${shortNewText.slice(0, -1)}`;
1108
+ shiftFinalNewlineToBeforeNewText = true;
1109
+ }
1110
+ if (spliceEnd === this.rep.alltext.length &&
1111
+ shortOldText.length > 0 &&
1112
+ shortNewText.length === 0) {
1113
+ if (this.rep.alltext.charAt(spliceStart - 1) === '\n') {
1114
+ spliceStart--;
1115
+ spliceEnd--;
1116
+ }
1117
+ }
1118
+ if (!(shortOldText.length === 0 && shortNewText.length === 0)) {
1119
+ const oldDocText = this.rep.alltext;
1120
+ const docOldLen = oldDocText.length;
1121
+ const spliceStartLine = this.rep.lines.indexOfOffset(spliceStart);
1122
+ const spliceStartLineStart = this.rep.lines.offsetOfIndex(spliceStartLine);
1123
+ const startBuilder = () => {
1124
+ const builder = new Builder(docOldLen);
1125
+ builder.keep(spliceStartLineStart, spliceStartLine);
1126
+ builder.keep(spliceStart - spliceStartLineStart);
1127
+ return builder;
1128
+ };
1129
+ const eachAttribRun = (attribs, func) => {
1130
+ let textIndex = 0;
1131
+ const newTextStart = commonStart;
1132
+ const newTextEnd = newText.length - commonEnd -
1133
+ (shiftFinalNewlineToBeforeNewText ? 1 : 0);
1134
+ for (const op of deserializeOps(attribs)) {
1135
+ const nextIndex = textIndex + op.chars;
1136
+ if (!(nextIndex <= newTextStart || textIndex >= newTextEnd)) {
1137
+ func(Math.max(newTextStart, textIndex), Math.min(newTextEnd, nextIndex), op.attribs);
1138
+ }
1139
+ textIndex = nextIndex;
1140
+ }
1141
+ };
1142
+ const justApplyStyles = (shortNewText === shortOldText);
1143
+ let theChangeset;
1144
+ if (justApplyStyles) {
1145
+ const incorpedAttribClearer = this.cachedStrFunc((oldAtts) => mapAttribNumbers(oldAtts, (n) => {
1146
+ const k = this.rep.apool.getAttribKey(n);
1147
+ if (this.isStyleAttribute(k)) {
1148
+ return this.rep.apool.putAttrib([k, '']);
1149
+ }
1150
+ return false;
1151
+ }));
1152
+ const builder1 = startBuilder();
1153
+ if (shiftFinalNewlineToBeforeNewText) {
1154
+ builder1.keep(1, 1);
1155
+ }
1156
+ eachAttribRun(oldAttribs, (start, end, attribs) => {
1157
+ builder1.keepText(newText.substring(start, end), incorpedAttribClearer(attribs));
1158
+ });
1159
+ const clearer = builder1.toString();
1160
+ const builder2 = startBuilder();
1161
+ if (shiftFinalNewlineToBeforeNewText) {
1162
+ builder2.keep(1, 1);
1163
+ }
1164
+ eachAttribRun(newAttribs, (start, end, attribs) => {
1165
+ builder2.keepText(newText.substring(start, end), attribs);
1166
+ });
1167
+ const styler = builder2.toString();
1168
+ theChangeset = compose(clearer, styler, this.rep.apool);
1169
+ }
1170
+ else {
1171
+ const builder = startBuilder();
1172
+ const spliceEndLine = this.rep.lines.indexOfOffset(spliceEnd);
1173
+ const spliceEndLineStart = this.rep.lines.offsetOfIndex(spliceEndLine);
1174
+ if (spliceEndLineStart > spliceStart) {
1175
+ builder.remove(spliceEndLineStart - spliceStart, spliceEndLine - spliceStartLine);
1176
+ builder.remove(spliceEnd - spliceEndLineStart);
1177
+ }
1178
+ else {
1179
+ builder.remove(spliceEnd - spliceStart);
1180
+ }
1181
+ let isNewTextMultiauthor = false;
1182
+ const authorizer = this.cachedStrFunc((oldAtts) => {
1183
+ const attribs = AttributeMap.fromString(oldAtts, this.rep.apool);
1184
+ if (!isNewTextMultiauthor || !attribs.has('author')) {
1185
+ attribs.set('author', this.thisAuthor);
1186
+ }
1187
+ return attribs.toString();
1188
+ });
1189
+ let foundDomAuthor = '';
1190
+ eachAttribRun(newAttribs, (_start, _end, attribs) => {
1191
+ const a = AttributeMap.fromString(attribs, this.rep.apool).get('author');
1192
+ if (a && a !== foundDomAuthor) {
1193
+ if (!foundDomAuthor) {
1194
+ foundDomAuthor = a;
1195
+ }
1196
+ else {
1197
+ isNewTextMultiauthor = true;
1198
+ }
1199
+ }
1200
+ });
1201
+ if (shiftFinalNewlineToBeforeNewText) {
1202
+ builder.insert('\n', authorizer(''));
1203
+ }
1204
+ eachAttribRun(newAttribs, (start, end, attribs) => {
1205
+ builder.insert(newText.substring(start, end), authorizer(attribs));
1206
+ });
1207
+ theChangeset = builder.toString();
1208
+ }
1209
+ this.doRepApplyChangeset(theChangeset);
1210
+ }
1211
+ this.doRepLineSplice(startLine, deleteCount, newLineEntries);
1212
+ }
1213
+ // -----------------------------------------------------------------------
1214
+ // Private - Change Analysis
1215
+ // -----------------------------------------------------------------------
1216
+ analyzeChange(oldText, newText, oldAttribs, newAttribs, _optSelStartHint, optSelEndHint) {
1217
+ const incorpedAttribFilter = (anum) => !this.isDefaultLineAttribute(this.rep.apool.getAttribKey(anum));
1218
+ const attribRuns = (attribs) => {
1219
+ const lengs = [];
1220
+ const atts = [];
1221
+ for (const op of deserializeOps(attribs)) {
1222
+ lengs.push(op.chars);
1223
+ atts.push(op.attribs);
1224
+ }
1225
+ return [lengs, atts];
1226
+ };
1227
+ const attribIterator = (runs, backward) => {
1228
+ const lengs = runs[0];
1229
+ const atts = runs[1];
1230
+ let i = backward ? lengs.length - 1 : 0;
1231
+ let j = 0;
1232
+ const next = () => {
1233
+ while (j >= lengs[i]) {
1234
+ if (backward)
1235
+ i--;
1236
+ else
1237
+ i++;
1238
+ j = 0;
1239
+ }
1240
+ const a = atts[i];
1241
+ j++;
1242
+ return a;
1243
+ };
1244
+ return next;
1245
+ };
1246
+ const oldTextLen = oldText.length;
1247
+ const newTextLen = newText.length;
1248
+ const minLen = Math.min(oldTextLen, newTextLen);
1249
+ const oldARuns = attribRuns(filterAttribNumbers(oldAttribs, incorpedAttribFilter));
1250
+ const newARuns = attribRuns(filterAttribNumbers(newAttribs, incorpedAttribFilter));
1251
+ let commonStart = 0;
1252
+ const oldStartIter = attribIterator(oldARuns, false);
1253
+ const newStartIter = attribIterator(newARuns, false);
1254
+ while (commonStart < minLen) {
1255
+ if (oldText.charAt(commonStart) === newText.charAt(commonStart) &&
1256
+ oldStartIter() === newStartIter()) {
1257
+ commonStart++;
1258
+ }
1259
+ else {
1260
+ break;
1261
+ }
1262
+ }
1263
+ let commonEnd = 0;
1264
+ const oldEndIter = attribIterator(oldARuns, true);
1265
+ const newEndIter = attribIterator(newARuns, true);
1266
+ while (commonEnd < minLen) {
1267
+ if (commonEnd === 0) {
1268
+ oldEndIter();
1269
+ newEndIter();
1270
+ commonEnd++;
1271
+ }
1272
+ else if (oldText.charAt(oldTextLen - 1 - commonEnd) ===
1273
+ newText.charAt(newTextLen - 1 - commonEnd) &&
1274
+ oldEndIter() === newEndIter()) {
1275
+ commonEnd++;
1276
+ }
1277
+ else {
1278
+ break;
1279
+ }
1280
+ }
1281
+ let hintedCommonEnd = -1;
1282
+ if (typeof optSelEndHint === 'number') {
1283
+ hintedCommonEnd = newTextLen - optSelEndHint;
1284
+ }
1285
+ if (commonStart + commonEnd > oldTextLen) {
1286
+ const minCommonEnd = oldTextLen - commonStart;
1287
+ const maxCommonEnd = commonEnd;
1288
+ if (hintedCommonEnd >= minCommonEnd && hintedCommonEnd <= maxCommonEnd) {
1289
+ commonEnd = hintedCommonEnd;
1290
+ }
1291
+ else {
1292
+ commonEnd = minCommonEnd;
1293
+ }
1294
+ commonStart = oldTextLen - commonEnd;
1295
+ }
1296
+ if (commonStart + commonEnd > newTextLen) {
1297
+ const minCommonEnd = newTextLen - commonStart;
1298
+ const maxCommonEnd = commonEnd;
1299
+ if (hintedCommonEnd >= minCommonEnd && hintedCommonEnd <= maxCommonEnd) {
1300
+ commonEnd = hintedCommonEnd;
1301
+ }
1302
+ else {
1303
+ commonEnd = minCommonEnd;
1304
+ }
1305
+ commonStart = newTextLen - commonEnd;
1306
+ }
1307
+ return [commonStart, commonEnd];
1308
+ }
1309
+ cachedStrFunc(func) {
1310
+ const cache = {};
1311
+ return (s) => {
1312
+ if (!cache[s]) {
1313
+ cache[s] = func(s);
1314
+ }
1315
+ return cache[s];
1316
+ };
1317
+ }
1318
+ // -----------------------------------------------------------------------
1319
+ // Private - Content Collection / Change Detection
1320
+ // -----------------------------------------------------------------------
1321
+ incorporateUserChanges() {
1322
+ if (this.currentCallStack && this.currentCallStack.domClean)
1323
+ return false;
1324
+ if (this.currentCallStack) {
1325
+ this.currentCallStack.isUserChange = true;
1326
+ }
1327
+ if (!this.targetBody.firstChild) {
1328
+ this.targetBody.innerHTML = '<div><!-- --></div>';
1329
+ }
1330
+ try {
1331
+ this.observeChangesAroundSelection();
1332
+ this.observeSuspiciousNodes();
1333
+ let dirtyRanges = this.getDirtyRanges();
1334
+ let dirtyRangesCheckOut = true;
1335
+ let j = 0;
1336
+ let a, b;
1337
+ while (j < dirtyRanges.length) {
1338
+ a = dirtyRanges[j][0];
1339
+ b = dirtyRanges[j][1];
1340
+ if (!((a === 0 || this.getCleanNodeByKey(this.rep.lines.atIndex(a - 1).key)) &&
1341
+ (b === this.rep.lines.length() ||
1342
+ this.getCleanNodeByKey(this.rep.lines.atIndex(b).key)))) {
1343
+ dirtyRangesCheckOut = false;
1344
+ break;
1345
+ }
1346
+ j++;
1347
+ }
1348
+ if (!dirtyRangesCheckOut) {
1349
+ for (const bodyNode of this.targetBody.childNodes) {
1350
+ if (bodyNode.tagName &&
1351
+ (!bodyNode.id ||
1352
+ !this.rep.lines.containsKey(bodyNode.id))) {
1353
+ this.observeChangesAroundNode(bodyNode);
1354
+ }
1355
+ }
1356
+ dirtyRanges = this.getDirtyRanges();
1357
+ }
1358
+ this.clearObservedChanges();
1359
+ const selection = this.getSelection();
1360
+ let selStart;
1361
+ let selEnd;
1362
+ let i = 0;
1363
+ const splicesToDo = [];
1364
+ let netNumLinesChangeSoFar = 0;
1365
+ const toDeleteAtEnd = [];
1366
+ const domInsertsNeeded = [];
1367
+ while (i < dirtyRanges.length) {
1368
+ const range = dirtyRanges[i];
1369
+ a = range[0];
1370
+ b = range[1];
1371
+ let firstDirtyNode = ((a === 0 && this.targetBody.firstChild) ||
1372
+ this.getCleanNodeByKey(this.rep.lines.atIndex(a - 1).key)?.nextSibling);
1373
+ firstDirtyNode = (firstDirtyNode && this.isNodeDirty(firstDirtyNode) && firstDirtyNode);
1374
+ let lastDirtyNode = ((b === this.rep.lines.length() && this.targetBody.lastChild) ||
1375
+ this.getCleanNodeByKey(this.rep.lines.atIndex(b).key)?.previousSibling);
1376
+ lastDirtyNode = (lastDirtyNode && this.isNodeDirty(lastDirtyNode) && lastDirtyNode);
1377
+ if (firstDirtyNode && lastDirtyNode) {
1378
+ const cc = makeContentCollector(this.isStyled, browser, this.rep.apool, this.className2Author.bind(this));
1379
+ cc.notifySelection(selection);
1380
+ const dirtyNodes = [];
1381
+ for (let n = firstDirtyNode; n && !(n.previousSibling && n.previousSibling === lastDirtyNode); n = n.nextSibling) {
1382
+ cc.collectContent(n);
1383
+ dirtyNodes.push(n);
1384
+ }
1385
+ cc.notifyNextNode(lastDirtyNode.nextSibling);
1386
+ let lines = cc.getLines();
1387
+ if ((lines.length <= 1 || lines[lines.length - 1] !== '') &&
1388
+ lastDirtyNode.nextSibling) {
1389
+ b++;
1390
+ const cleanLine = lastDirtyNode.nextSibling;
1391
+ cc.collectContent(cleanLine);
1392
+ toDeleteAtEnd.push(cleanLine);
1393
+ cc.notifyNextNode(cleanLine.nextSibling);
1394
+ }
1395
+ const ccData = cc.finish();
1396
+ const ss = ccData.selStart;
1397
+ const se = ccData.selEnd;
1398
+ lines = ccData.lines;
1399
+ const lineAttribs = ccData.lineAttribs;
1400
+ const linesWrapped = ccData.linesWrapped;
1401
+ if (linesWrapped > 0) {
1402
+ // Scroll to left needed to fix Chrome wrapping issue
1403
+ this.targetBody.scrollLeft = 0;
1404
+ }
1405
+ if (ss[0] >= 0)
1406
+ selStart = [ss[0] + a + netNumLinesChangeSoFar, ss[1]];
1407
+ if (se[0] >= 0)
1408
+ selEnd = [se[0] + a + netNumLinesChangeSoFar, se[1]];
1409
+ const entries = [];
1410
+ const nodeToAddAfter = lastDirtyNode;
1411
+ const lineNodeInfos = [];
1412
+ for (const lineString of lines) {
1413
+ const newEntry = this.createDomLineEntry(lineString);
1414
+ entries.push(newEntry);
1415
+ lineNodeInfos.push(newEntry.domInfo);
1416
+ }
1417
+ domInsertsNeeded.push([nodeToAddAfter, lineNodeInfos]);
1418
+ for (const n of dirtyNodes)
1419
+ toDeleteAtEnd.push(n);
1420
+ const spliceHints = {};
1421
+ if (selStart)
1422
+ spliceHints.selStart = selStart;
1423
+ if (selEnd)
1424
+ spliceHints.selEnd = selEnd;
1425
+ splicesToDo.push([
1426
+ a + netNumLinesChangeSoFar, b - a, entries, lineAttribs, spliceHints
1427
+ ]);
1428
+ netNumLinesChangeSoFar += (lines.length - (b - a));
1429
+ }
1430
+ else if (b > a) {
1431
+ splicesToDo.push([a + netNumLinesChangeSoFar, b - a, [], []]);
1432
+ }
1433
+ i++;
1434
+ }
1435
+ const domChanges = splicesToDo.length > 0;
1436
+ for (const splice of splicesToDo)
1437
+ this.doIncorpLineSplice(splice[0], splice[1], splice[2], splice[3], splice[4]);
1438
+ for (const ins of domInsertsNeeded)
1439
+ this.insertDomLines(ins[0], ins[1]);
1440
+ for (const n of toDeleteAtEnd)
1441
+ n.remove();
1442
+ // If selection nodes weren't encountered during content collection,
1443
+ // figure out where those nodes are now.
1444
+ if (selection && !selStart) {
1445
+ selStart = this.getLineAndCharForPoint(selection.startPoint);
1446
+ }
1447
+ if (selection && !selEnd) {
1448
+ selEnd = this.getLineAndCharForPoint(selection.endPoint);
1449
+ }
1450
+ // Cap selection to valid line range
1451
+ const numLines = this.rep.lines.length();
1452
+ if (numLines > 0) {
1453
+ if (selStart && selStart[0] >= numLines) {
1454
+ selStart[0] = numLines - 1;
1455
+ selStart[1] = this.rep.lines.atIndex(selStart[0]).text.length;
1456
+ }
1457
+ if (selEnd && selEnd[0] >= numLines) {
1458
+ selEnd[0] = numLines - 1;
1459
+ selEnd[1] = this.rep.lines.atIndex(selEnd[0]).text.length;
1460
+ }
1461
+ }
1462
+ else {
1463
+ selStart = null;
1464
+ selEnd = null;
1465
+ }
1466
+ if (selection) {
1467
+ this.repSelectionChange(selStart, selEnd, selection && selection.focusAtStart);
1468
+ }
1469
+ if (selection && (domChanges || this.isCaret())) {
1470
+ if (this.currentCallStack) {
1471
+ this.currentCallStack.selectionAffected = true;
1472
+ }
1473
+ }
1474
+ if (this.currentCallStack) {
1475
+ this.currentCallStack.domClean = true;
1476
+ }
1477
+ return domChanges;
1478
+ }
1479
+ catch (e) {
1480
+ // Guard against DOM/rep desync crashes (e.g. node IDs not in SkipList).
1481
+ // Mark clean so the idle timer doesn't spin indefinitely.
1482
+ if (this.currentCallStack) {
1483
+ this.currentCallStack.domClean = true;
1484
+ }
1485
+ return false;
1486
+ }
1487
+ }
1488
+ getDirtyRanges() {
1489
+ const cleanNodeForIndexCache = {};
1490
+ const N = this.rep.lines.length();
1491
+ const cleanNodeForIndex = (i) => {
1492
+ if (cleanNodeForIndexCache[i] === undefined) {
1493
+ let result;
1494
+ if (i < 0 || i >= N) {
1495
+ result = true;
1496
+ }
1497
+ else {
1498
+ const key = this.rep.lines.atIndex(i).key;
1499
+ result = this.getCleanNodeByKey(key) || false;
1500
+ }
1501
+ cleanNodeForIndexCache[i] = result;
1502
+ }
1503
+ return cleanNodeForIndexCache[i];
1504
+ };
1505
+ const isConsecutiveCache = {};
1506
+ const isConsecutive = (i) => {
1507
+ if (isConsecutiveCache[i] === undefined) {
1508
+ isConsecutiveCache[i] = (() => {
1509
+ const a = cleanNodeForIndex(i - 1);
1510
+ const b = cleanNodeForIndex(i);
1511
+ if (!a || !b)
1512
+ return false;
1513
+ if (a === true && b === true)
1514
+ return !this.targetBody.firstChild;
1515
+ if (a === true && b.previousSibling)
1516
+ return false;
1517
+ if (b === true && a.nextSibling)
1518
+ return false;
1519
+ if (a === true || b === true)
1520
+ return true;
1521
+ return a.nextSibling === b;
1522
+ })();
1523
+ }
1524
+ return isConsecutiveCache[i];
1525
+ };
1526
+ const isClean = (i) => !!cleanNodeForIndex(i);
1527
+ const cleanRanges = [[-1, N + 1]];
1528
+ const rangeForLine = (i) => {
1529
+ for (const [idx, r] of cleanRanges.entries()) {
1530
+ if (i < r[0])
1531
+ return -1;
1532
+ if (i < r[1])
1533
+ return idx;
1534
+ }
1535
+ return -1;
1536
+ };
1537
+ const removeLineFromRange = (rng, line) => {
1538
+ const a = cleanRanges[rng][0];
1539
+ const b = cleanRanges[rng][1];
1540
+ if (a + 1 === b)
1541
+ cleanRanges.splice(rng, 1);
1542
+ else if (line === a)
1543
+ cleanRanges[rng][0]++;
1544
+ else if (line === b - 1)
1545
+ cleanRanges[rng][1]--;
1546
+ else
1547
+ cleanRanges.splice(rng, 1, [a, line], [line + 1, b]);
1548
+ };
1549
+ const splitRange = (rng, pt) => {
1550
+ const a = cleanRanges[rng][0];
1551
+ const b = cleanRanges[rng][1];
1552
+ cleanRanges.splice(rng, 1, [a, pt], [pt, b]);
1553
+ };
1554
+ const correctedLines = {};
1555
+ const correctlyAssignLine = (line) => {
1556
+ if (correctedLines[line])
1557
+ return true;
1558
+ correctedLines[line] = true;
1559
+ const rng = rangeForLine(line);
1560
+ const lineClean = isClean(line);
1561
+ if (rng < 0) {
1562
+ return true;
1563
+ }
1564
+ if (!lineClean) {
1565
+ removeLineFromRange(rng, line);
1566
+ return false;
1567
+ }
1568
+ else {
1569
+ const a = cleanRanges[rng][0];
1570
+ const b = cleanRanges[rng][1];
1571
+ let didSomething = false;
1572
+ if (a < line && isClean(line - 1) && !isConsecutive(line)) {
1573
+ splitRange(rng, line);
1574
+ didSomething = true;
1575
+ }
1576
+ if (b > line + 1 && isClean(line + 1) && !isConsecutive(line + 1)) {
1577
+ splitRange(rng, line + 1);
1578
+ didSomething = true;
1579
+ }
1580
+ return !didSomething;
1581
+ }
1582
+ };
1583
+ const detectChangesAroundLine = (line, reqInARow) => {
1584
+ let correctInARow = 0;
1585
+ let currentIndex = line;
1586
+ while (correctInARow < reqInARow && currentIndex >= 0) {
1587
+ if (correctlyAssignLine(currentIndex)) {
1588
+ correctInARow++;
1589
+ }
1590
+ else {
1591
+ correctInARow = 0;
1592
+ }
1593
+ currentIndex--;
1594
+ }
1595
+ correctInARow = 0;
1596
+ currentIndex = line;
1597
+ while (correctInARow < reqInARow && currentIndex < N) {
1598
+ if (correctlyAssignLine(currentIndex)) {
1599
+ correctInARow++;
1600
+ }
1601
+ else {
1602
+ correctInARow = 0;
1603
+ }
1604
+ currentIndex++;
1605
+ }
1606
+ };
1607
+ if (N === 0) {
1608
+ if (!isConsecutive(0)) {
1609
+ splitRange(0, 0);
1610
+ }
1611
+ }
1612
+ else {
1613
+ detectChangesAroundLine(0, 1);
1614
+ detectChangesAroundLine(N - 1, 1);
1615
+ for (const k of Object.keys(this.observedChanges.cleanNodesNearChanges)) {
1616
+ const key = k.substring(1);
1617
+ if (this.rep.lines.containsKey(key)) {
1618
+ const line = this.rep.lines.indexOfKey(key);
1619
+ detectChangesAroundLine(line, 2);
1620
+ }
1621
+ }
1622
+ }
1623
+ const dirtyRanges = [];
1624
+ for (let r = 0; r < cleanRanges.length - 1; r++) {
1625
+ dirtyRanges.push([cleanRanges[r][1], cleanRanges[r + 1][0]]);
1626
+ }
1627
+ return dirtyRanges;
1628
+ }
1629
+ // -----------------------------------------------------------------------
1630
+ // Private - Node Tracking
1631
+ // -----------------------------------------------------------------------
1632
+ uniqueId(n) {
1633
+ const nid = n.id;
1634
+ if (nid)
1635
+ return nid;
1636
+ return (n.id = `magicdomid${this._nextId++}`);
1637
+ }
1638
+ topLevel(n) {
1639
+ if (!n || n === this.targetBody)
1640
+ return null;
1641
+ while (n.parentNode !== this.targetBody) {
1642
+ n = n.parentNode;
1643
+ if (!n)
1644
+ return null;
1645
+ }
1646
+ return n;
1647
+ }
1648
+ markNodeClean(n) {
1649
+ setAssoc(n, 'dirtiness', { nodeId: this.uniqueId(n), knownHTML: n.innerHTML });
1650
+ }
1651
+ isNodeDirty(n) {
1652
+ if (n.parentNode !== this.targetBody)
1653
+ return true;
1654
+ const data = getAssoc(n, 'dirtiness');
1655
+ if (!data)
1656
+ return true;
1657
+ if (n.id !== data.nodeId)
1658
+ return true;
1659
+ if (n.innerHTML !== data.knownHTML)
1660
+ return true;
1661
+ return false;
1662
+ }
1663
+ getCleanNodeByKey(key) {
1664
+ let n = this.rootNode.getElementById(key);
1665
+ while (n && this.isNodeDirty(n)) {
1666
+ n.id = '';
1667
+ n = this.rootNode.getElementById(key);
1668
+ }
1669
+ return n;
1670
+ }
1671
+ clearObservedChanges() {
1672
+ this.observedChanges = {
1673
+ cleanNodesNearChanges: {},
1674
+ };
1675
+ }
1676
+ observeChangesAroundNode(node) {
1677
+ let cleanNode = null;
1678
+ let hasAdjacentDirtyness = false;
1679
+ if (!this.isNodeDirty(node)) {
1680
+ cleanNode = node;
1681
+ const prevSib = cleanNode.previousSibling;
1682
+ const nextSib = cleanNode.nextSibling;
1683
+ hasAdjacentDirtyness = !!((prevSib && this.isNodeDirty(prevSib)) ||
1684
+ (nextSib && this.isNodeDirty(nextSib)));
1685
+ }
1686
+ else {
1687
+ let upNode = node.previousSibling;
1688
+ while (upNode && this.isNodeDirty(upNode)) {
1689
+ upNode = upNode.previousSibling;
1690
+ }
1691
+ if (upNode) {
1692
+ cleanNode = upNode;
1693
+ }
1694
+ else {
1695
+ let downNode = node.nextSibling;
1696
+ while (downNode && this.isNodeDirty(downNode)) {
1697
+ downNode = downNode.nextSibling;
1698
+ }
1699
+ if (downNode) {
1700
+ cleanNode = downNode;
1701
+ }
1702
+ }
1703
+ if (!cleanNode)
1704
+ return;
1705
+ hasAdjacentDirtyness = true;
1706
+ }
1707
+ if (hasAdjacentDirtyness) {
1708
+ this.observedChanges.cleanNodesNearChanges[`$${this.uniqueId(cleanNode)}`] = true;
1709
+ }
1710
+ else {
1711
+ const lineKey = this.uniqueId(cleanNode);
1712
+ if (!this.rep.lines.containsKey(lineKey))
1713
+ return;
1714
+ const prevSib = cleanNode.previousSibling;
1715
+ const nextSib = cleanNode.nextSibling;
1716
+ const actualPrevKey = (prevSib && this.uniqueId(prevSib)) || null;
1717
+ const actualNextKey = (nextSib && this.uniqueId(nextSib)) || null;
1718
+ const repPrevEntry = this.rep.lines.prev(this.rep.lines.atKey(lineKey));
1719
+ const repNextEntry = this.rep.lines.next(this.rep.lines.atKey(lineKey));
1720
+ const repPrevKey = (repPrevEntry && repPrevEntry.key) || null;
1721
+ const repNextKey = (repNextEntry && repNextEntry.key) || null;
1722
+ if (actualPrevKey !== repPrevKey || actualNextKey !== repNextKey) {
1723
+ this.observedChanges.cleanNodesNearChanges[`$${this.uniqueId(cleanNode)}`] = true;
1724
+ }
1725
+ }
1726
+ }
1727
+ observeChangesAroundSelection() {
1728
+ if (this.currentCallStack && this.currentCallStack.observedSelection)
1729
+ return;
1730
+ if (this.currentCallStack)
1731
+ this.currentCallStack.observedSelection = true;
1732
+ const selection = this.getSelection();
1733
+ if (selection) {
1734
+ const node1 = this.topLevel(selection.startPoint.node);
1735
+ const node2 = this.topLevel(selection.endPoint.node);
1736
+ if (node1)
1737
+ this.observeChangesAroundNode(node1);
1738
+ if (node2 && node1 !== node2) {
1739
+ this.observeChangesAroundNode(node2);
1740
+ }
1741
+ }
1742
+ }
1743
+ observeSuspiciousNodes() {
1744
+ if (this.targetBody.getElementsByTagName) {
1745
+ const elts = this.targetBody.getElementsByTagName('style');
1746
+ for (const elt of elts) {
1747
+ const n = this.topLevel(elt);
1748
+ if (n && n.parentNode === this.targetBody) {
1749
+ this.observeChangesAroundNode(n);
1750
+ }
1751
+ }
1752
+ }
1753
+ }
1754
+ // -----------------------------------------------------------------------
1755
+ // Private - Selection
1756
+ // -----------------------------------------------------------------------
1757
+ nodeMaxIndex(nd) {
1758
+ if (isNodeText(nd))
1759
+ return nd.nodeValue.length;
1760
+ return 1;
1761
+ }
1762
+ nodeText(n) {
1763
+ return n.textContent || n.nodeValue || '';
1764
+ }
1765
+ childIndex(n) {
1766
+ let idx = 0;
1767
+ while (n.previousSibling) {
1768
+ idx++;
1769
+ n = n.previousSibling;
1770
+ }
1771
+ return idx;
1772
+ }
1773
+ getDocSelection() {
1774
+ // In Shadow DOM, try shadowRoot.getSelection (Chrome-specific) first,
1775
+ // then fall back to document.getSelection which works for shadow DOM in Chromium.
1776
+ if (this.rootNode instanceof ShadowRoot) {
1777
+ const sr = this.rootNode;
1778
+ if (typeof sr.getSelection === 'function') {
1779
+ return sr.getSelection();
1780
+ }
1781
+ }
1782
+ return this.targetDoc.getSelection();
1783
+ }
1784
+ getSelection() {
1785
+ const browserSelection = this.getDocSelection();
1786
+ if (!browserSelection || browserSelection.type === 'None' ||
1787
+ browserSelection.rangeCount === 0) {
1788
+ return null;
1789
+ }
1790
+ const range = browserSelection.getRangeAt(0);
1791
+ const isInBody = (n) => {
1792
+ while (n) {
1793
+ if (n === this.targetBody)
1794
+ return true;
1795
+ n = n.parentNode;
1796
+ }
1797
+ return false;
1798
+ };
1799
+ const pointFromRangeBound = (container, offset) => {
1800
+ if (!isInBody(container)) {
1801
+ return {
1802
+ node: this.targetBody,
1803
+ index: 0,
1804
+ maxIndex: 1,
1805
+ };
1806
+ }
1807
+ const n = container;
1808
+ const childCount = n.childNodes.length;
1809
+ if (isNodeText(n)) {
1810
+ return {
1811
+ node: n,
1812
+ index: offset,
1813
+ maxIndex: n.nodeValue.length,
1814
+ };
1815
+ }
1816
+ else if (childCount === 0) {
1817
+ return {
1818
+ node: n,
1819
+ index: 0,
1820
+ maxIndex: 1,
1821
+ };
1822
+ }
1823
+ else if (offset === childCount) {
1824
+ const nd = n.childNodes.item(childCount - 1);
1825
+ const max = this.nodeMaxIndex(nd);
1826
+ return {
1827
+ node: nd,
1828
+ index: max,
1829
+ maxIndex: max,
1830
+ };
1831
+ }
1832
+ else {
1833
+ const nd = n.childNodes.item(offset);
1834
+ const max = this.nodeMaxIndex(nd);
1835
+ return {
1836
+ node: nd,
1837
+ index: 0,
1838
+ maxIndex: max,
1839
+ };
1840
+ }
1841
+ };
1842
+ const selection = {
1843
+ startPoint: pointFromRangeBound(range.startContainer, range.startOffset),
1844
+ endPoint: pointFromRangeBound(range.endContainer, range.endOffset),
1845
+ focusAtStart: (range.startContainer !== range.endContainer ||
1846
+ range.startOffset !== range.endOffset) &&
1847
+ browserSelection.anchorNode &&
1848
+ browserSelection.anchorNode === range.endContainer &&
1849
+ browserSelection.anchorOffset === range.endOffset,
1850
+ };
1851
+ if (selection.startPoint.node.ownerDocument !== this.targetDoc) {
1852
+ return null;
1853
+ }
1854
+ return selection;
1855
+ }
1856
+ setSelection(selection) {
1857
+ const copyPoint = (pt) => ({
1858
+ node: pt.node,
1859
+ index: pt.index,
1860
+ maxIndex: pt.maxIndex,
1861
+ });
1862
+ let isCollapsed;
1863
+ const pointToRangeBound = (pt) => {
1864
+ const p = copyPoint(pt);
1865
+ if (isCollapsed) {
1866
+ const diveDeep = () => {
1867
+ while (p.node.childNodes.length > 0) {
1868
+ if (p.index === 0) {
1869
+ p.node = p.node.firstChild;
1870
+ p.maxIndex = this.nodeMaxIndex(p.node);
1871
+ }
1872
+ else if (p.index === p.maxIndex) {
1873
+ p.node = p.node.lastChild;
1874
+ p.maxIndex = this.nodeMaxIndex(p.node);
1875
+ p.index = p.maxIndex;
1876
+ }
1877
+ else {
1878
+ break;
1879
+ }
1880
+ }
1881
+ };
1882
+ if (isNodeText(p.node) && p.index === p.maxIndex) {
1883
+ let n = p.node;
1884
+ while (!n.nextSibling && n !== this.targetBody && n.parentNode !== this.targetBody) {
1885
+ n = n.parentNode;
1886
+ }
1887
+ if (n.nextSibling &&
1888
+ !(typeof n.nextSibling.tagName === 'string' &&
1889
+ n.nextSibling.tagName.toLowerCase() === 'br') &&
1890
+ n !== p.node && n !== this.targetBody && n.parentNode !== this.targetBody) {
1891
+ p.node = n.nextSibling;
1892
+ p.maxIndex = this.nodeMaxIndex(p.node);
1893
+ p.index = 0;
1894
+ diveDeep();
1895
+ }
1896
+ }
1897
+ if (!isNodeText(p.node)) {
1898
+ diveDeep();
1899
+ }
1900
+ }
1901
+ if (isNodeText(p.node)) {
1902
+ return {
1903
+ container: p.node,
1904
+ offset: p.index,
1905
+ };
1906
+ }
1907
+ else {
1908
+ return {
1909
+ container: p.node.parentNode,
1910
+ offset: this.childIndex(p.node) + p.index,
1911
+ };
1912
+ }
1913
+ };
1914
+ const browserSelection = this.getDocSelection();
1915
+ if (browserSelection) {
1916
+ browserSelection.removeAllRanges();
1917
+ if (selection) {
1918
+ isCollapsed = (selection.startPoint.node === selection.endPoint.node &&
1919
+ selection.startPoint.index === selection.endPoint.index);
1920
+ const start = pointToRangeBound(selection.startPoint);
1921
+ const end = pointToRangeBound(selection.endPoint);
1922
+ if (!isCollapsed && selection.focusAtStart &&
1923
+ browserSelection.collapse && browserSelection.extend) {
1924
+ browserSelection.collapse(end.container, end.offset);
1925
+ browserSelection.extend(start.container, start.offset);
1926
+ }
1927
+ else {
1928
+ const range = this.targetDoc.createRange();
1929
+ range.setStart(start.container, start.offset);
1930
+ range.setEnd(end.container, end.offset);
1931
+ browserSelection.removeAllRanges();
1932
+ browserSelection.addRange(range);
1933
+ }
1934
+ }
1935
+ }
1936
+ }
1937
+ getPointForLineAndChar(lineAndChar) {
1938
+ const line = lineAndChar[0];
1939
+ let charsLeft = lineAndChar[1];
1940
+ const lineEntry = this.rep.lines.atIndex(line);
1941
+ charsLeft -= lineEntry.lineMarker;
1942
+ if (charsLeft < 0) {
1943
+ charsLeft = 0;
1944
+ }
1945
+ const lineNode = lineEntry.lineNode;
1946
+ let n = lineNode;
1947
+ let after = false;
1948
+ if (charsLeft === 0) {
1949
+ return {
1950
+ node: lineNode,
1951
+ index: 0,
1952
+ maxIndex: 1,
1953
+ };
1954
+ }
1955
+ while (!(n === lineNode && after)) {
1956
+ if (after) {
1957
+ if (n.nextSibling) {
1958
+ n = n.nextSibling;
1959
+ after = false;
1960
+ }
1961
+ else {
1962
+ n = n.parentNode;
1963
+ }
1964
+ }
1965
+ else if (isNodeText(n)) {
1966
+ const len = n.nodeValue.length;
1967
+ if (charsLeft <= len) {
1968
+ return {
1969
+ node: n,
1970
+ index: charsLeft,
1971
+ maxIndex: len,
1972
+ };
1973
+ }
1974
+ charsLeft -= len;
1975
+ after = true;
1976
+ }
1977
+ else if (n.firstChild) {
1978
+ n = n.firstChild;
1979
+ }
1980
+ else {
1981
+ after = true;
1982
+ }
1983
+ }
1984
+ return {
1985
+ node: lineNode,
1986
+ index: 1,
1987
+ maxIndex: 1,
1988
+ };
1989
+ }
1990
+ getLineAndCharForPoint(point) {
1991
+ const N = this.rep.lines.length();
1992
+ if (N === 0)
1993
+ return [0, 0];
1994
+ if (point.node === this.targetBody) {
1995
+ if (point.index === 0) {
1996
+ return [0, 0];
1997
+ }
1998
+ else {
1999
+ const ln = this.rep.lines.atIndex(N - 1);
2000
+ if (!ln)
2001
+ return [0, 0];
2002
+ return [N - 1, ln.text.length];
2003
+ }
2004
+ }
2005
+ else {
2006
+ let n = point.node;
2007
+ let col = 0;
2008
+ if (isNodeText(n)) {
2009
+ col = point.index;
2010
+ }
2011
+ else if (point.index > 0) {
2012
+ col = this.nodeText(n).length;
2013
+ }
2014
+ let parNode;
2015
+ let prevSib;
2016
+ while (n && (parNode = n.parentNode) && parNode !== this.targetBody) {
2017
+ if ((prevSib = n.previousSibling)) {
2018
+ n = prevSib;
2019
+ col += this.nodeText(n).length;
2020
+ }
2021
+ else {
2022
+ n = parNode;
2023
+ }
2024
+ }
2025
+ if (!n || !n.parentNode) {
2026
+ // Node not found in targetBody, return safe default
2027
+ return [0, 0];
2028
+ }
2029
+ const lineEntry = n.id ? this.rep.lines.atKey(n.id) : null;
2030
+ if (!lineEntry)
2031
+ return [0, 0];
2032
+ // Use the actual lineMarker from rep (set by domline during rendering).
2033
+ // This correctly accounts for plugin block elements (h1-h6, etc.)
2034
+ // without needing a hardcoded list of block element tags.
2035
+ col += lineEntry.lineMarker;
2036
+ const lineNum = this.rep.lines.indexOfEntry(lineEntry);
2037
+ return [lineNum, col];
2038
+ }
2039
+ }
2040
+ performSelectionChange(selectStart, selectEnd, focusAtStart) {
2041
+ if (this.repSelectionChange(selectStart, selectEnd, focusAtStart)) {
2042
+ if (this.currentCallStack) {
2043
+ this.currentCallStack.selectionAffected = true;
2044
+ }
2045
+ }
2046
+ }
2047
+ repSelectionChange(selectStart, selectEnd, focusAtStart) {
2048
+ focusAtStart = !!focusAtStart;
2049
+ const newSelFocusAtStart = focusAtStart && (!selectStart ||
2050
+ !selectEnd ||
2051
+ selectStart[0] !== selectEnd[0] ||
2052
+ selectStart[1] !== selectEnd[1]);
2053
+ if (!this.equalLineAndChars(this.rep.selStart, selectStart) ||
2054
+ !this.equalLineAndChars(this.rep.selEnd, selectEnd) ||
2055
+ this.rep.selFocusAtStart !== newSelFocusAtStart) {
2056
+ this.rep.selStart = selectStart;
2057
+ this.rep.selEnd = selectEnd;
2058
+ this.rep.selFocusAtStart = newSelFocusAtStart;
2059
+ if (this.currentCallStack) {
2060
+ this.currentCallStack.repChanged = true;
2061
+ }
2062
+ // Emit selection changed event
2063
+ if (this.rep.selStart && this.rep.selEnd) {
2064
+ editorBus.emit('editor:selection:changed', {
2065
+ start: [this.rep.selStart[0], this.rep.selStart[1]],
2066
+ end: [this.rep.selEnd[0], this.rep.selEnd[1]],
2067
+ });
2068
+ if (this.onSelectionChanged)
2069
+ this.onSelectionChanged(this.rep.selStart, this.rep.selEnd);
2070
+ }
2071
+ return true;
2072
+ }
2073
+ return false;
2074
+ }
2075
+ updateBrowserSelectionFromRep() {
2076
+ // Don't steal focus from other UI elements (dropdowns, selects, inputs, etc.).
2077
+ // In the old iframe approach this wasn't needed because the editor had its own
2078
+ // document. Now that the editor is in the main document, setting the selection
2079
+ // here would close native <select> dropdowns and blur focused inputs.
2080
+ const active = document.activeElement;
2081
+ if (active && active !== this.targetBody && !this.targetBody.contains(active)) {
2082
+ return;
2083
+ }
2084
+ const selStart = this.rep.selStart;
2085
+ const selEnd = this.rep.selEnd;
2086
+ if (!(selStart && selEnd)) {
2087
+ this.setSelection(null);
2088
+ return;
2089
+ }
2090
+ const selection = {};
2091
+ const ss = [selStart[0], selStart[1]];
2092
+ selection.startPoint = this.getPointForLineAndChar(ss);
2093
+ const se = [selEnd[0], selEnd[1]];
2094
+ selection.endPoint = this.getPointForLineAndChar(se);
2095
+ selection.focusAtStart = !!this.rep.selFocusAtStart;
2096
+ this.setSelection(selection);
2097
+ }
2098
+ equalLineAndChars(a, b) {
2099
+ if (!a)
2100
+ return !b;
2101
+ if (!b)
2102
+ return !a;
2103
+ return a[0] === b[0] && a[1] === b[1];
2104
+ }
2105
+ // -----------------------------------------------------------------------
2106
+ // Private - Input Handling
2107
+ // -----------------------------------------------------------------------
2108
+ handleKeyEvent(evt) {
2109
+ if (!this.isEditable)
2110
+ return;
2111
+ // Invoke external key handlers
2112
+ if (evt.type === 'keypress' && this.onKeyPressHandler) {
2113
+ this.onKeyPressHandler(evt);
2114
+ }
2115
+ if (evt.type === 'keydown' && this.onKeyDownHandler) {
2116
+ this.onKeyDownHandler(evt);
2117
+ }
2118
+ const { type, charCode, keyCode, which } = evt;
2119
+ let altKey = evt.altKey;
2120
+ if (typeof evt.location === 'number') {
2121
+ altKey = altKey && evt.location === KeyboardEvent.DOM_KEY_LOCATION_LEFT;
2122
+ }
2123
+ const isModKey = !charCode && (type === 'keyup' || type === 'keydown') &&
2124
+ (keyCode === 16 || keyCode === 17 || keyCode === 18 ||
2125
+ keyCode === 20 || keyCode === 224 || keyCode === 91);
2126
+ if (isModKey)
2127
+ return;
2128
+ if (keyCode === 13 && browser.opera && type === 'keypress')
2129
+ return;
2130
+ const isTypeForSpecialKey = browser.safari || browser.chrome || browser.firefox
2131
+ ? type === 'keydown' : type === 'keypress';
2132
+ const isTypeForCmdKey = browser.safari || browser.chrome || browser.firefox
2133
+ ? type === 'keydown' : type === 'keypress';
2134
+ let stopped = false;
2135
+ this.inCallStackIfNecessary('handleKeyEvent', () => {
2136
+ if (type === 'keypress' || (isTypeForSpecialKey && keyCode === 13)) {
2137
+ // Default: allow keypress
2138
+ }
2139
+ else if (evt.key === 'Dead') {
2140
+ stopped = true;
2141
+ return true;
2142
+ }
2143
+ let specialHandled = false;
2144
+ if (!stopped) {
2145
+ // Delete/Backspace
2146
+ if (!specialHandled && isTypeForSpecialKey && keyCode === 8) {
2147
+ this.fastIncorp(3);
2148
+ evt.preventDefault();
2149
+ this.doDeleteKey(evt);
2150
+ specialHandled = true;
2151
+ }
2152
+ // Return
2153
+ if (!specialHandled && isTypeForSpecialKey && keyCode === 13) {
2154
+ this.fastIncorp(4);
2155
+ evt.preventDefault();
2156
+ this.doReturnKey();
2157
+ specialHandled = true;
2158
+ }
2159
+ // Escape
2160
+ if (!specialHandled && isTypeForSpecialKey && keyCode === 27) {
2161
+ this.fastIncorp(4);
2162
+ evt.preventDefault();
2163
+ specialHandled = true;
2164
+ }
2165
+ // Tab
2166
+ if (!specialHandled && isTypeForSpecialKey && keyCode === 9 &&
2167
+ !(evt.metaKey || evt.ctrlKey)) {
2168
+ this.fastIncorp(5);
2169
+ evt.preventDefault();
2170
+ this.doTabKey(evt.shiftKey);
2171
+ specialHandled = true;
2172
+ }
2173
+ // Ctrl+Z (undo)
2174
+ if (!specialHandled && isTypeForCmdKey &&
2175
+ (evt.metaKey || evt.ctrlKey) &&
2176
+ String.fromCharCode(which).toLowerCase() === 'z' && !evt.altKey) {
2177
+ this.fastIncorp(6);
2178
+ evt.preventDefault();
2179
+ if (evt.shiftKey) {
2180
+ this.doUndoRedo('redo');
2181
+ }
2182
+ else {
2183
+ this.doUndoRedo('undo');
2184
+ }
2185
+ specialHandled = true;
2186
+ }
2187
+ // Ctrl+Y (redo)
2188
+ if (!specialHandled && isTypeForCmdKey &&
2189
+ (evt.metaKey || evt.ctrlKey) &&
2190
+ String.fromCharCode(which).toLowerCase() === 'y') {
2191
+ this.fastIncorp(10);
2192
+ evt.preventDefault();
2193
+ this.doUndoRedo('redo');
2194
+ specialHandled = true;
2195
+ }
2196
+ // Ctrl+B (bold)
2197
+ if (!specialHandled && isTypeForCmdKey &&
2198
+ (evt.metaKey || evt.ctrlKey) &&
2199
+ String.fromCharCode(which).toLowerCase() === 'b') {
2200
+ this.fastIncorp(13);
2201
+ evt.preventDefault();
2202
+ this.toggleAttributeOnSelection('bold');
2203
+ specialHandled = true;
2204
+ }
2205
+ // Ctrl+I (italic)
2206
+ if (!specialHandled && isTypeForCmdKey &&
2207
+ (evt.metaKey || evt.ctrlKey) &&
2208
+ String.fromCharCode(which).toLowerCase() === 'i') {
2209
+ this.fastIncorp(14);
2210
+ evt.preventDefault();
2211
+ this.toggleAttributeOnSelection('italic');
2212
+ specialHandled = true;
2213
+ }
2214
+ // Ctrl+U (underline)
2215
+ if (!specialHandled && isTypeForCmdKey &&
2216
+ (evt.metaKey || evt.ctrlKey) &&
2217
+ String.fromCharCode(which).toLowerCase() === 'u') {
2218
+ this.fastIncorp(15);
2219
+ evt.preventDefault();
2220
+ this.toggleAttributeOnSelection('underline');
2221
+ specialHandled = true;
2222
+ }
2223
+ // Ctrl+5 (strikethrough)
2224
+ if (!specialHandled && isTypeForCmdKey &&
2225
+ (evt.metaKey || evt.ctrlKey) &&
2226
+ String.fromCharCode(which).toLowerCase() === '5' &&
2227
+ evt.altKey !== true) {
2228
+ this.fastIncorp(13);
2229
+ evt.preventDefault();
2230
+ this.toggleAttributeOnSelection('strikethrough');
2231
+ specialHandled = true;
2232
+ }
2233
+ // Ctrl+Shift+L (unordered list)
2234
+ if (!specialHandled && isTypeForCmdKey &&
2235
+ (evt.metaKey || evt.ctrlKey) &&
2236
+ String.fromCharCode(which).toLowerCase() === 'l' &&
2237
+ evt.shiftKey) {
2238
+ this.fastIncorp(9);
2239
+ evt.preventDefault();
2240
+ this.doInsertUnorderedList();
2241
+ specialHandled = true;
2242
+ }
2243
+ // Ctrl+Shift+N or Ctrl+Shift+1 (ordered list)
2244
+ if (!specialHandled && isTypeForCmdKey &&
2245
+ (evt.metaKey || evt.ctrlKey) && evt.shiftKey &&
2246
+ (String.fromCharCode(which).toLowerCase() === 'n' ||
2247
+ String.fromCharCode(which) === '1')) {
2248
+ this.fastIncorp(9);
2249
+ evt.preventDefault();
2250
+ this.doInsertOrderedList();
2251
+ specialHandled = true;
2252
+ }
2253
+ // Ctrl+Shift+C (clear authorship)
2254
+ if (!specialHandled && isTypeForCmdKey &&
2255
+ (evt.metaKey || evt.ctrlKey) && evt.shiftKey &&
2256
+ String.fromCharCode(which).toLowerCase() === 'c') {
2257
+ this.fastIncorp(9);
2258
+ evt.preventDefault();
2259
+ this.execCommand('clearauthorship');
2260
+ specialHandled = true;
2261
+ }
2262
+ // Ctrl+H (backspace)
2263
+ if (!specialHandled && isTypeForCmdKey &&
2264
+ evt.ctrlKey &&
2265
+ String.fromCharCode(which).toLowerCase() === 'h') {
2266
+ this.fastIncorp(20);
2267
+ evt.preventDefault();
2268
+ this.doDeleteKey();
2269
+ specialHandled = true;
2270
+ }
2271
+ }
2272
+ if (type === 'keydown') {
2273
+ this.idleWorkTimer.atLeast(500);
2274
+ }
2275
+ else if (type === 'keypress') {
2276
+ if (!specialHandled) {
2277
+ this.idleWorkTimer.atMost(0);
2278
+ }
2279
+ else {
2280
+ this.idleWorkTimer.atLeast(500);
2281
+ }
2282
+ }
2283
+ else if (type === 'keyup') {
2284
+ this.idleWorkTimer.atLeast(0);
2285
+ this.idleWorkTimer.atMost(0);
2286
+ }
2287
+ const isFirefoxHalfCharacter = browser.firefox && evt.altKey && charCode === 0 && keyCode === 0;
2288
+ const isSafariHalfCharacter = browser.safari && evt.altKey && keyCode === 229;
2289
+ if (this.thisKeyDoesntTriggerNormalize || isFirefoxHalfCharacter ||
2290
+ isSafariHalfCharacter) {
2291
+ this.idleWorkTimer.atLeast(3000);
2292
+ this.thisKeyDoesntTriggerNormalize = true;
2293
+ }
2294
+ if (!specialHandled && !this.thisKeyDoesntTriggerNormalize &&
2295
+ !this.inInternationalComposition && type !== 'keyup') {
2296
+ this.observeChangesAroundSelection();
2297
+ }
2298
+ if (type === 'keyup') {
2299
+ this.thisKeyDoesntTriggerNormalize = false;
2300
+ }
2301
+ });
2302
+ }
2303
+ doReturnKey() {
2304
+ if (!(this.rep.selStart && this.rep.selEnd))
2305
+ return;
2306
+ const lineNum = this.rep.selStart[0];
2307
+ let listType = this.getLineListType(lineNum);
2308
+ if (listType) {
2309
+ const text = this.rep.lines.atIndex(lineNum).text;
2310
+ const listTypeParsed = /([a-z]+)([0-9]+)/.exec(listType);
2311
+ if (!listTypeParsed)
2312
+ return;
2313
+ const type = listTypeParsed[1];
2314
+ const level = Number(listTypeParsed[2]);
2315
+ if (text === '*' && type !== 'indent') {
2316
+ if (level > 1) {
2317
+ this._setLineListType(lineNum, type + (level - 1));
2318
+ }
2319
+ else {
2320
+ this._setLineListType(lineNum, '');
2321
+ this.renumberList(lineNum + 1);
2322
+ }
2323
+ }
2324
+ else if (lineNum + 1 <= this.rep.lines.length()) {
2325
+ this.performDocumentReplaceSelection('\n');
2326
+ this._setLineListType(lineNum + 1, type + level);
2327
+ }
2328
+ }
2329
+ else {
2330
+ this.performDocumentReplaceSelection('\n');
2331
+ this.handleReturnIndentation();
2332
+ }
2333
+ }
2334
+ handleReturnIndentation() {
2335
+ if (this.isCaret() && this.getCaretColumn() === 0 && this.getCaretLine() > 0) {
2336
+ const lineNum = this.getCaretLine();
2337
+ const thisLine = this.rep.lines.atIndex(lineNum);
2338
+ const prevLine = this.rep.lines.prev(thisLine);
2339
+ if (!prevLine)
2340
+ return;
2341
+ const prevLineText = prevLine.text;
2342
+ let theIndent = /^ *(?:)/.exec(prevLineText)[0];
2343
+ if (/[[(:{]\s*$/.exec(prevLineText)) {
2344
+ theIndent += AceEditor.THE_TAB;
2345
+ }
2346
+ const cs = new Builder(this.rep.lines.totalWidth()).keep(this.rep.lines.offsetOfIndex(lineNum), lineNum).insert(theIndent, [
2347
+ ['author', this.thisAuthor],
2348
+ ], this.rep.apool).toString();
2349
+ this.performDocumentApplyChangeset(cs);
2350
+ this.performSelectionChange([lineNum, theIndent.length], [lineNum, theIndent.length]);
2351
+ }
2352
+ }
2353
+ doDeleteKey(optEvt) {
2354
+ const evt = optEvt || {};
2355
+ let handled = false;
2356
+ if (this.rep.selStart) {
2357
+ if (this.isCaret()) {
2358
+ const lineNum = this.getCaretLine();
2359
+ const col = this.getCaretColumn();
2360
+ const lineEntry = this.rep.lines.atIndex(lineNum);
2361
+ const lineText = lineEntry.text;
2362
+ const lineMarker = lineEntry.lineMarker;
2363
+ if (evt.metaKey && col > lineMarker) {
2364
+ this.performDocumentReplaceRange([lineNum, lineMarker], [lineNum, col], '');
2365
+ handled = true;
2366
+ }
2367
+ else if (/^ +$/.exec(lineText.substring(lineMarker, col))) {
2368
+ const col2 = col - lineMarker;
2369
+ const tabSize = AceEditor.THE_TAB.length;
2370
+ const toDelete = ((col2 - 1) % tabSize) + 1;
2371
+ this.performDocumentReplaceRange([lineNum, col - toDelete], [lineNum, col], '');
2372
+ handled = true;
2373
+ }
2374
+ }
2375
+ if (!handled) {
2376
+ if (this.isCaret()) {
2377
+ const theLine = this.getCaretLine();
2378
+ const lineEntry = this.rep.lines.atIndex(theLine);
2379
+ if (this.getCaretColumn() <= lineEntry.lineMarker) {
2380
+ const prevLineListType = theLine > 0 ? this.getLineListType(theLine - 1) : '';
2381
+ const thisLineListType = this.getLineListType(theLine);
2382
+ const prevLineEntry = theLine > 0 && this.rep.lines.atIndex(theLine - 1);
2383
+ const prevLineBlank = prevLineEntry &&
2384
+ prevLineEntry.text.length === prevLineEntry.lineMarker;
2385
+ const thisLineHasMarker = this.documentAttributeManager.lineHasMarker(theLine);
2386
+ if (thisLineListType) {
2387
+ if (prevLineBlank && !prevLineListType) {
2388
+ this.performDocumentReplaceRange([theLine - 1, prevLineEntry.text.length], [theLine, 0], '');
2389
+ }
2390
+ else {
2391
+ this.performDocumentReplaceRange([theLine, 0], [theLine, lineEntry.lineMarker], '');
2392
+ }
2393
+ }
2394
+ else if (thisLineHasMarker && prevLineEntry) {
2395
+ this.performDocumentReplaceRange([theLine - 1, prevLineEntry.text.length], [theLine, lineEntry.lineMarker], '');
2396
+ }
2397
+ else if (theLine > 0) {
2398
+ this.performDocumentReplaceRange([theLine - 1, prevLineEntry.text.length], [theLine, 0], '');
2399
+ }
2400
+ }
2401
+ else {
2402
+ const docChar = this.caretDocChar();
2403
+ if (docChar > 0) {
2404
+ if (evt.metaKey || evt.ctrlKey || evt.altKey) {
2405
+ let deleteBackTo = docChar - 1;
2406
+ while (deleteBackTo > lineEntry.lineMarker &&
2407
+ this.isWordChar(this.rep.alltext.charAt(deleteBackTo - 1))) {
2408
+ deleteBackTo--;
2409
+ }
2410
+ this.performDocumentReplaceCharRange(deleteBackTo, docChar, '');
2411
+ }
2412
+ else {
2413
+ this.performDocumentReplaceCharRange(docChar - 1, docChar, '');
2414
+ }
2415
+ }
2416
+ }
2417
+ }
2418
+ else {
2419
+ this.performDocumentReplaceSelection('');
2420
+ }
2421
+ }
2422
+ }
2423
+ const line = this.getCaretLine();
2424
+ if (line !== -1 && this.renumberList(line + 1) == null) {
2425
+ this.renumberList(line);
2426
+ }
2427
+ }
2428
+ doTabKey(shiftDown) {
2429
+ if (!this.doIndentOutdent(shiftDown)) {
2430
+ this.performDocumentReplaceSelection(AceEditor.THE_TAB);
2431
+ }
2432
+ }
2433
+ doIndentOutdent(isOut) {
2434
+ if (!((this.rep.selStart && this.rep.selEnd) ||
2435
+ (this.rep.selStart[0] === this.rep.selEnd[0] &&
2436
+ this.rep.selStart[1] === this.rep.selEnd[1] &&
2437
+ this.rep.selEnd[1] > 1)) &&
2438
+ isOut !== true) {
2439
+ return false;
2440
+ }
2441
+ const firstLine = this.rep.selStart[0];
2442
+ const lastLine = Math.max(firstLine, this.rep.selEnd[0] - (this.rep.selEnd[1] === 0 ? 1 : 0));
2443
+ const mods = [];
2444
+ for (let n = firstLine; n <= lastLine; n++) {
2445
+ let listType = this.getLineListType(n);
2446
+ let t = 'indent';
2447
+ let level = 0;
2448
+ if (listType) {
2449
+ const parsed = /([a-z]+)([0-9]+)/.exec(listType);
2450
+ if (parsed) {
2451
+ t = parsed[1];
2452
+ level = Number(parsed[2]);
2453
+ }
2454
+ }
2455
+ const newLevel = Math.max(0, Math.min(AceEditor.MAX_LIST_LEVEL, level + (isOut ? -1 : 1)));
2456
+ if (level !== newLevel) {
2457
+ mods.push([n, newLevel > 0 ? t + newLevel : '']);
2458
+ }
2459
+ }
2460
+ for (const mod of mods)
2461
+ this._setLineListType(mod[0], mod[1]);
2462
+ return true;
2463
+ }
2464
+ handleClick(evt) {
2465
+ this.inCallStackIfNecessary('handleClick', () => {
2466
+ this.idleWorkTimer.atMost(200);
2467
+ });
2468
+ const isLink = (n) => (n.tagName || '').toLowerCase() === 'a' && n.href;
2469
+ if (evt.button !== 2 && evt.button !== 3) {
2470
+ let n = evt.target;
2471
+ while (n && n.parentNode && !isLink(n)) {
2472
+ n = n.parentNode;
2473
+ }
2474
+ if (n && isLink(n)) {
2475
+ try {
2476
+ window.open(n.href, '_blank', 'noopener,noreferrer');
2477
+ if (evt.ctrlKey)
2478
+ window.focus();
2479
+ }
2480
+ catch (e) {
2481
+ // absorb error
2482
+ }
2483
+ evt.preventDefault();
2484
+ }
2485
+ }
2486
+ }
2487
+ // -----------------------------------------------------------------------
2488
+ // Private - Undo / Redo
2489
+ // -----------------------------------------------------------------------
2490
+ doUndoRedo(which) {
2491
+ if (undoModule.enabled) {
2492
+ let whichMethod;
2493
+ if (which === 'undo')
2494
+ whichMethod = 'performUndo';
2495
+ if (which === 'redo')
2496
+ whichMethod = 'performRedo';
2497
+ if (whichMethod) {
2498
+ const oldEventType = this.currentCallStack.editEvent.eventType;
2499
+ this.currentCallStack.startNewEvent(which);
2500
+ undoModule[whichMethod]((backset, selectionInfo) => {
2501
+ if (backset) {
2502
+ this.performDocumentApplyChangeset(backset);
2503
+ }
2504
+ if (selectionInfo) {
2505
+ this.performSelectionChange(this.lineAndColumnFromChar(selectionInfo.selStart), this.lineAndColumnFromChar(selectionInfo.selEnd), selectionInfo.selFocusAtStart);
2506
+ }
2507
+ const oldEvent = this.currentCallStack.startNewEvent(oldEventType, true);
2508
+ return oldEvent;
2509
+ });
2510
+ }
2511
+ }
2512
+ }
2513
+ // -----------------------------------------------------------------------
2514
+ // Private - Formatting
2515
+ // -----------------------------------------------------------------------
2516
+ setAttributeOnSelection(attributeName, attributeValue) {
2517
+ if (!(this.rep.selStart && this.rep.selEnd))
2518
+ return;
2519
+ this.documentAttributeManager.setAttributesOnRange(this.rep.selStart, this.rep.selEnd, [
2520
+ [attributeName, attributeValue],
2521
+ ]);
2522
+ }
2523
+ getAttributeOnSelection(attributeName, prevChar) {
2524
+ if (!(this.rep.selStart && this.rep.selEnd))
2525
+ return false;
2526
+ const isNotSelection = (this.rep.selStart[0] === this.rep.selEnd[0] &&
2527
+ this.rep.selEnd[1] === this.rep.selStart[1]);
2528
+ if (isNotSelection) {
2529
+ if (prevChar) {
2530
+ if (this.rep.selStart[1] !== 0) {
2531
+ this.rep.selStart[1]--;
2532
+ }
2533
+ }
2534
+ }
2535
+ const withIt = new AttributeMap(this.rep.apool).set(attributeName, 'true').toString();
2536
+ const withItRegex = new RegExp(`${withIt.replace(/\*/g, '\\*')}(\\*|$)`);
2537
+ const hasIt = (attribs) => withItRegex.test(attribs);
2538
+ const rangeHasAttrib = (selStart, selEnd) => {
2539
+ if (selStart[1] === selEnd[1] && selStart[0] === selEnd[0])
2540
+ return false;
2541
+ if (selStart[0] !== selEnd[0]) {
2542
+ let hasAttrib = true;
2543
+ hasAttrib = hasAttrib && rangeHasAttrib(selStart, [selStart[0], this.rep.lines.atIndex(selStart[0]).text.length]);
2544
+ for (let n = selStart[0] + 1; n < selEnd[0]; n++) {
2545
+ hasAttrib = hasAttrib &&
2546
+ rangeHasAttrib([n, 0], [n, this.rep.lines.atIndex(n).text.length]);
2547
+ }
2548
+ hasAttrib = hasAttrib && rangeHasAttrib([selEnd[0], 0], [selEnd[0], selEnd[1]]);
2549
+ return hasAttrib;
2550
+ }
2551
+ const lineNum = selStart[0];
2552
+ const start = selStart[1];
2553
+ const end = selEnd[1];
2554
+ let hasAttrib = true;
2555
+ let indexIntoLine = 0;
2556
+ for (const op of deserializeOps(this.rep.alines[lineNum])) {
2557
+ const opStartInLine = indexIntoLine;
2558
+ const opEndInLine = opStartInLine + op.chars;
2559
+ if (!hasIt(op.attribs)) {
2560
+ if (!(opEndInLine <= start || opStartInLine >= end)) {
2561
+ hasAttrib = false;
2562
+ break;
2563
+ }
2564
+ }
2565
+ indexIntoLine = opEndInLine;
2566
+ }
2567
+ return hasAttrib;
2568
+ };
2569
+ return rangeHasAttrib(this.rep.selStart, this.rep.selEnd);
2570
+ }
2571
+ toggleAttributeOnSelection(attributeName) {
2572
+ if (!(this.rep.selStart && this.rep.selEnd))
2573
+ return;
2574
+ let selectionAllHasIt = true;
2575
+ const withIt = new AttributeMap(this.rep.apool).set(attributeName, 'true').toString();
2576
+ const withItRegex = new RegExp(`${withIt.replace(/\*/g, '\\*')}(\\*|$)`);
2577
+ const hasIt = (attribs) => withItRegex.test(attribs);
2578
+ const selStartLine = this.rep.selStart[0];
2579
+ const selEndLine = this.rep.selEnd[0];
2580
+ for (let n = selStartLine; n <= selEndLine; n++) {
2581
+ let indexIntoLine = 0;
2582
+ let selectionStartInLine = 0;
2583
+ if (this.documentAttributeManager.lineHasMarker(n)) {
2584
+ selectionStartInLine = 1;
2585
+ }
2586
+ let selectionEndInLine = this.rep.lines.atIndex(n).text.length;
2587
+ if (n === selStartLine) {
2588
+ selectionStartInLine = this.rep.selStart[1];
2589
+ }
2590
+ if (n === selEndLine) {
2591
+ selectionEndInLine = this.rep.selEnd[1];
2592
+ }
2593
+ for (const op of deserializeOps(this.rep.alines[n])) {
2594
+ const opStartInLine = indexIntoLine;
2595
+ const opEndInLine = opStartInLine + op.chars;
2596
+ if (!hasIt(op.attribs)) {
2597
+ if (!(opEndInLine <= selectionStartInLine || opStartInLine >= selectionEndInLine)) {
2598
+ selectionAllHasIt = false;
2599
+ break;
2600
+ }
2601
+ }
2602
+ indexIntoLine = opEndInLine;
2603
+ }
2604
+ if (!selectionAllHasIt)
2605
+ break;
2606
+ }
2607
+ const attributeValue = selectionAllHasIt ? '' : 'true';
2608
+ this.documentAttributeManager.setAttributesOnRange(this.rep.selStart, this.rep.selEnd, [[attributeName, attributeValue]]);
2609
+ }
2610
+ getLineListType(lineNum) {
2611
+ return this.documentAttributeManager.getAttributeOnLine(lineNum, this.listAttributeName);
2612
+ }
2613
+ _setLineListType(lineNum, listType) {
2614
+ if (listType === '') {
2615
+ this.documentAttributeManager.removeAttributeOnLine(lineNum, this.listAttributeName);
2616
+ this.documentAttributeManager.removeAttributeOnLine(lineNum, 'start');
2617
+ }
2618
+ else {
2619
+ this.documentAttributeManager.setAttributeOnLine(lineNum, this.listAttributeName, listType);
2620
+ }
2621
+ if (this.renumberList(lineNum + 1) == null) {
2622
+ this.renumberList(lineNum);
2623
+ }
2624
+ }
2625
+ renumberList(lineNum) {
2626
+ let type = this.getLineListType(lineNum);
2627
+ if (!type)
2628
+ return null;
2629
+ const parsed = /([a-z]+)[0-9]+/.exec(type);
2630
+ if (!parsed)
2631
+ return null;
2632
+ if (parsed[1] === 'indent')
2633
+ return null;
2634
+ while (lineNum - 1 >= 0 && (type = this.getLineListType(lineNum - 1))) {
2635
+ const p = /([a-z]+)[0-9]+/.exec(type);
2636
+ if (!p || p[1] === 'indent')
2637
+ break;
2638
+ lineNum--;
2639
+ }
2640
+ const builder = new Builder(this.rep.lines.totalWidth());
2641
+ let loc = [0, 0];
2642
+ const applyNumberList = (line, level) => {
2643
+ let position = 1;
2644
+ let curLevel = level;
2645
+ let lt;
2646
+ while ((lt = this.getLineListType(line))) {
2647
+ const ltParsed = /([a-z]+)([0-9]+)/.exec(lt);
2648
+ if (!ltParsed)
2649
+ return line;
2650
+ curLevel = Number(ltParsed[2]);
2651
+ if (isNaN(curLevel) || ltParsed[0] === 'indent') {
2652
+ return line;
2653
+ }
2654
+ else if (curLevel === level) {
2655
+ buildKeepRange(this.rep, builder, loc, (loc = [line, 0]));
2656
+ buildKeepRange(this.rep, builder, loc, (loc = [line, 1]), [
2657
+ ['start', position],
2658
+ ], this.rep.apool);
2659
+ position++;
2660
+ line++;
2661
+ }
2662
+ else if (curLevel < level) {
2663
+ return line;
2664
+ }
2665
+ else {
2666
+ line = applyNumberList(line, level + 1);
2667
+ }
2668
+ }
2669
+ return line;
2670
+ };
2671
+ applyNumberList(lineNum, 1);
2672
+ const cs = builder.toString();
2673
+ if (!isIdentity(cs)) {
2674
+ this.performDocumentApplyChangeset(cs);
2675
+ }
2676
+ return lineNum;
2677
+ }
2678
+ doInsertList(type) {
2679
+ if (!(this.rep.selStart && this.rep.selEnd))
2680
+ return;
2681
+ const firstLine = this.rep.selStart[0];
2682
+ const lastLine = Math.max(firstLine, this.rep.selEnd[0] - (this.rep.selEnd[1] === 0 ? 1 : 0));
2683
+ let allLinesAreList = true;
2684
+ for (let n = firstLine; n <= lastLine; n++) {
2685
+ const lt = this.getLineListType(n);
2686
+ if (!lt || lt.slice(0, type.length) !== type) {
2687
+ allLinesAreList = false;
2688
+ break;
2689
+ }
2690
+ }
2691
+ const mods = [];
2692
+ for (let n = firstLine; n <= lastLine; n++) {
2693
+ let level = 0;
2694
+ let togglingOn = true;
2695
+ const ltStr = this.getLineListType(n);
2696
+ const listTypeParsed = /([a-z]+)([0-9]+)/.exec(ltStr);
2697
+ if (allLinesAreList) {
2698
+ togglingOn = false;
2699
+ }
2700
+ if (listTypeParsed) {
2701
+ level = Number(listTypeParsed[2]);
2702
+ }
2703
+ const t = this.getLineListType(n);
2704
+ if (t === ltStr)
2705
+ togglingOn = false;
2706
+ if (togglingOn) {
2707
+ mods.push([n, allLinesAreList
2708
+ ? `indent${level}`
2709
+ : (t ? type + level : `${type}1`)]);
2710
+ }
2711
+ else {
2712
+ if (level === 1) {
2713
+ this._setLineListType(n, '');
2714
+ }
2715
+ if (level > 1) {
2716
+ this._setLineListType(n, '');
2717
+ this._setLineListType(n, `indent${level}`);
2718
+ }
2719
+ }
2720
+ }
2721
+ for (const mod of mods)
2722
+ this._setLineListType(mod[0], mod[1]);
2723
+ }
2724
+ doInsertUnorderedList() {
2725
+ this.doInsertList('bullet');
2726
+ }
2727
+ doInsertOrderedList() {
2728
+ this.doInsertList('number');
2729
+ }
2730
+ // -----------------------------------------------------------------------
2731
+ // Private - Helper Functions
2732
+ // -----------------------------------------------------------------------
2733
+ caretDocChar() {
2734
+ return this.rep.lines.offsetOfIndex(this.getCaretLine()) + this.getCaretColumn();
2735
+ }
2736
+ isWordChar(c) {
2737
+ return /[\w]/.test(c);
2738
+ }
2739
+ textify(str) {
2740
+ return str.replace(/[\n\r ]/g, ' ').replace(/\xa0/g, ' ').replace(/\t/g, ' ');
2741
+ }
2742
+ /* @internal */ isBlockElement(n) {
2743
+ return !!AceEditor._blockElems[(n.tagName || '').toLowerCase()];
2744
+ }
2745
+ isStyleAttribute(aname) {
2746
+ return !!AceEditor.STYLE_ATTRIBS[aname];
2747
+ }
2748
+ isDefaultLineAttribute(aname) {
2749
+ return AttributeManager.DEFAULT_LINE_ATTRIBUTES.indexOf(aname) !== -1;
2750
+ }
2751
+ isPadLoading(t) {
2752
+ return t === 'setup' || t === 'setBaseText' || t === 'importText';
2753
+ }
2754
+ enforceEditability() {
2755
+ this.setEditable(this.isEditable);
2756
+ }
2757
+ // -----------------------------------------------------------------------
2758
+ // Private - Event Binding
2759
+ // -----------------------------------------------------------------------
2760
+ bindTheEventHandlers() {
2761
+ // Key events
2762
+ this.targetBody.addEventListener('keydown', (e) => this.handleKeyEvent(e));
2763
+ this.targetBody.addEventListener('keypress', (e) => this.handleKeyEvent(e));
2764
+ this.targetBody.addEventListener('keyup', (e) => this.handleKeyEvent(e));
2765
+ // Click events
2766
+ this.targetBody.addEventListener('click', (e) => this.handleClick(e));
2767
+ this.targetBody.addEventListener('mouseup', () => {
2768
+ this.inCallStackIfNecessary('handleClick', () => {
2769
+ this.idleWorkTimer.atMost(200);
2770
+ });
2771
+ });
2772
+ // Input event - schedule incorporateUserChanges
2773
+ this.targetBody.addEventListener('input', () => {
2774
+ this.idleWorkTimer.atMost(0);
2775
+ });
2776
+ // Paste event
2777
+ this.targetBody.addEventListener('paste', (e) => {
2778
+ editorBus.emit('custom:ace:paste', {
2779
+ rep: this.rep,
2780
+ documentAttributeManager: this.documentAttributeManager,
2781
+ e,
2782
+ });
2783
+ this.idleWorkTimer.atMost(0);
2784
+ });
2785
+ // Cut event
2786
+ this.targetBody.addEventListener('cut', () => {
2787
+ this.idleWorkTimer.atMost(0);
2788
+ });
2789
+ // Drop event
2790
+ this.targetBody.addEventListener('drop', (e) => {
2791
+ const isLinkTarget = (target) => target instanceof Element &&
2792
+ (target.localName === 'a' || target.closest('a') != null);
2793
+ if (isLinkTarget(e.target)) {
2794
+ e.preventDefault();
2795
+ }
2796
+ // Mark origin with <style> to make content be observed by incorporateUserChanges
2797
+ const selection = this.getSelection();
2798
+ if (selection) {
2799
+ const firstLineSelected = this.topLevel(selection.startPoint.node);
2800
+ const lastLineSelected = this.topLevel(selection.endPoint.node);
2801
+ if (firstLineSelected && lastLineSelected) {
2802
+ const lineBeforeSelection = firstLineSelected.previousSibling;
2803
+ const lineAfterSelection = lastLineSelected.nextSibling;
2804
+ const neighbor = lineBeforeSelection || lineAfterSelection;
2805
+ if (neighbor) {
2806
+ neighbor.appendChild(this.targetDoc.createElement('style'));
2807
+ }
2808
+ }
2809
+ }
2810
+ this.idleWorkTimer.atMost(0);
2811
+ });
2812
+ // Composition events
2813
+ this.targetBody.addEventListener('compositionstart', () => {
2814
+ if (this.inInternationalComposition)
2815
+ return;
2816
+ this.inInternationalComposition = new Promise((resolve) => {
2817
+ this.targetBody.addEventListener('compositionend', () => {
2818
+ this.inInternationalComposition = null;
2819
+ resolve();
2820
+ // After composition ends, incorporate changes
2821
+ this.idleWorkTimer.atMost(0);
2822
+ }, { once: true });
2823
+ });
2824
+ });
2825
+ }
2826
+ }
2827
+ // -----------------------------------------------------------------------
2828
+ // Constants
2829
+ // -----------------------------------------------------------------------
2830
+ AceEditor.THE_TAB = ' '; // 4 spaces
2831
+ AceEditor.MAX_LIST_LEVEL = 16;
2832
+ AceEditor.STYLE_ATTRIBS = {
2833
+ bold: true,
2834
+ italic: true,
2835
+ underline: true,
2836
+ strikethrough: true,
2837
+ list: true,
2838
+ };
2839
+ AceEditor._blockElems = {
2840
+ div: 1,
2841
+ p: 1,
2842
+ pre: 1,
2843
+ li: 1,
2844
+ ol: 1,
2845
+ ul: 1,
2846
+ };
2847
+ //# sourceMappingURL=AceEditor.js.map