@cocreate/text 1.28.0 → 1.28.2

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.
package/CHANGELOG.md CHANGED
@@ -1,3 +1,18 @@
1
+ ## [1.28.2](https://github.com/CoCreate-app/CoCreate-text/compare/v1.28.1...v1.28.2) (2025-04-11)
2
+
3
+
4
+ ### Bug Fixes
5
+
6
+ * [realtime added to the selector] ([8a54245](https://github.com/CoCreate-app/CoCreate-text/commit/8a54245f7479813e89480cc4c2b1aea0804d7864))
7
+ * update observer observe param to type and and attributeName to attributeFilter ([70ed9c7](https://github.com/CoCreate-app/CoCreate-text/commit/70ed9c780b72948993472ef9e03d53fa75897e53))
8
+
9
+ ## [1.28.1](https://github.com/CoCreate-app/CoCreate-text/compare/v1.28.0...v1.28.1) (2024-12-14)
10
+
11
+
12
+ ### Bug Fixes
13
+
14
+ * try catchblock ([ca984f1](https://github.com/CoCreate-app/CoCreate-text/commit/ca984f1276cfed24ee34c21e874905a39b4bfac7))
15
+
1
16
  # [1.28.0](https://github.com/CoCreate-app/CoCreate-text/compare/v1.27.1...v1.28.0) (2024-11-04)
2
17
 
3
18
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cocreate/text",
3
- "version": "1.28.0",
3
+ "version": "1.28.2",
4
4
  "description": "A simple text component in vanilla javascript. Easily configured using HTML5 attributes and/or JavaScript API.",
5
5
  "keywords": [
6
6
  "text",
package/src/index.js CHANGED
@@ -1,479 +1,663 @@
1
1
  /*global CustomEvent, navigator*/
2
- import observer from '@cocreate/observer';
3
- import crdt from '@cocreate/crdt';
4
- import cursors from '@cocreate/cursors';
5
- import uuid from '@cocreate/uuid';
6
- import { getAttributes, getAttributeNames, checkValue } from '@cocreate/utils';
7
- import { updateDom } from './updateDom';
8
- import { insertAdjacentElement, removeElement, setInnerText, setAttribute, removeAttribute, setClass, setStyle, replaceInnerText } from './updateText';
9
- import { getSelection, processSelection } from '@cocreate/selection';
10
- import action from '@cocreate/actions';
2
+ import observer from "@cocreate/observer";
3
+ import crdt from "@cocreate/crdt";
4
+ import cursors from "@cocreate/cursors";
5
+ import uuid from "@cocreate/uuid";
6
+ import { getAttributes, getAttributeNames, checkValue } from "@cocreate/utils";
7
+ import { updateDom } from "./updateDom";
8
+ import {
9
+ insertAdjacentElement,
10
+ removeElement,
11
+ setInnerText,
12
+ setAttribute,
13
+ removeAttribute,
14
+ setClass,
15
+ setStyle,
16
+ replaceInnerText
17
+ } from "./updateText";
18
+ import { getSelection, processSelection } from "@cocreate/selection";
19
+ import action from "@cocreate/actions";
11
20
 
12
21
  let eventObj;
13
- let selector = `[array][object][key]`;
22
+ let selector = `[realtime][array][object][key]`;
14
23
  let selectors = `input${selector}, textarea${selector}, [contenteditable]${selector}:not([contenteditable='false'])`;
15
24
 
16
25
  function init() {
17
- let elements = document.querySelectorAll(selectors);
18
- initElements(elements);
19
- _crdtUpdateListener();
20
- initDocument(document);
26
+ let elements = document.querySelectorAll(selectors);
27
+ initElements(elements);
28
+ _crdtUpdateListener();
29
+ initDocument(document);
21
30
  }
22
31
 
23
32
  function initElements(elements) {
24
- for (let element of elements)
25
- initElement(element);
33
+ for (let element of elements) initElement(element);
26
34
  }
27
35
 
28
36
  function initElement(element) {
29
- let { array, object, key, isRealtime, isCrdt, isCrud, isSave, isRead } = getAttributes(element);
30
- if (!array || !object || !key)
31
- return
32
- if (object == 'pending') {
33
- element.pendingObject = true
34
- return
35
- }
36
- if (['_id', 'organization_id', 'storage', 'database', 'array'].includes(key))
37
- return
38
- if (!isRealtime || isCrdt == "false" || isRealtime == "false" || element.type == 'number' || element.type == 'file' || element.getAttribute('type') === 'file')
39
- return
40
- if (!checkValue(array) || !checkValue(object) || !checkValue(key))
41
- return
42
- if (key && key.startsWith("$"))
43
- return
44
-
45
- if (element.tagName === "INPUT" && ["text", "tel", "url"].includes(element.type) || element.tagName === "TEXTAREA" || element.hasAttribute('contenteditable')) {
46
-
47
- if (!isCrdt) {
48
- if (element.tagName == 'IFRAME') {
49
- if (isCrdt != 'true')
50
- _addEventListeners(element.contentDocument.documentElement);
51
- let Document = element.contentDocument;
52
- initDocument(Document);
53
- } else if (isCrdt != 'true') {
54
- _addEventListeners(element);
55
- }
56
- }
57
- element.setAttribute('crdt', 'true');
58
- element.crdt = { init: true };
59
-
60
- // TODO: newObject name consideration. its value is used for setting or overwriting existing value
61
- let newObject = ''
62
- if (element.pendingObject) {
63
-
64
- let value;
65
- if (element.hasAttribute('contenteditable'))
66
- value = element.innerHTML;
67
- else
68
- value = element.value;
69
- if (value)
70
- newObject = value
71
-
72
- delete element.pendingObject
73
- }
74
-
75
- crdt.getText({ array, object, key, crud: isCrud, save: isSave, read: isRead, newObject }).then(response => {
76
- if (response === undefined)
77
- return;
78
- if (!response) {
79
- // if (element.pendingObject) {
80
- // isRead = 'true'
81
- // delete element.pendingObject
82
- // }
83
-
84
- let value;
85
- if (element.hasAttribute('contenteditable')) {
86
- value = element.innerHTML;
87
- } else {
88
- value = element.value;
89
- }
90
-
91
- if (value)
92
- crdt.replaceText({ array, object, key, value, crud: isCrud, save: isSave, read: isRead });
93
- } else {
94
- if (element.hasAttribute('contenteditable')) {
95
- element.innerHTML = '';
96
- } else {
97
- element.value = '';
98
- }
99
- updateElement({ element, array, object, key, value: response, start: 0 })
100
- }
101
- });
102
-
103
- element.getValue = async () => {
104
- return await crdt.getText({ array, object, key })
105
- }
106
- element.setValue = (value) => {
107
- crdt.replaceText({ array, object, key, value, crud: isCrud, save: isSave, read: isRead });
108
-
109
- }
110
- }
37
+ let { array, object, key, isRealtime, isCrdt, isCrud, isSave, isRead } =
38
+ getAttributes(element);
39
+ if (!array || !object || !key) return;
40
+ if (object == "pending") {
41
+ element.pendingObject = true;
42
+ return;
43
+ }
44
+ if (
45
+ ["_id", "organization_id", "storage", "database", "array"].includes(key)
46
+ )
47
+ return;
48
+ if (
49
+ !isRealtime ||
50
+ isCrdt == "false" ||
51
+ isRealtime == "false" ||
52
+ element.type == "number" ||
53
+ element.type == "file" ||
54
+ element.getAttribute("type") === "file"
55
+ )
56
+ return;
57
+ if (!checkValue(array) || !checkValue(object) || !checkValue(key)) return;
58
+ if (key && key.startsWith("$")) return;
59
+
60
+ if (
61
+ (element.tagName === "INPUT" &&
62
+ ["text", "search", "tel", "url", "email"].includes(element.type)) ||
63
+ element.tagName === "TEXTAREA" ||
64
+ element.hasAttribute("contenteditable")
65
+ ) {
66
+ if (!isCrdt) {
67
+ if (element.tagName == "IFRAME") {
68
+ if (isCrdt != "true")
69
+ _addEventListeners(element.contentDocument.documentElement);
70
+ let Document = element.contentDocument;
71
+ initDocument(Document);
72
+ } else if (isCrdt != "true") {
73
+ _addEventListeners(element);
74
+ }
75
+ }
76
+ element.setAttribute("crdt", "true");
77
+ element.crdt = { init: true };
78
+
79
+ // TODO: newObject name consideration. its value is used for setting or overwriting existing value
80
+ let newObject = "";
81
+ if (element.pendingObject) {
82
+ let value;
83
+ if (element.hasAttribute("contenteditable"))
84
+ value = element.innerHTML;
85
+ else value = element.value;
86
+ if (value) newObject = value;
87
+
88
+ delete element.pendingObject;
89
+ }
90
+
91
+ crdt.getText({
92
+ array,
93
+ object,
94
+ key,
95
+ crud: isCrud,
96
+ save: isSave,
97
+ read: isRead,
98
+ newObject
99
+ }).then((response) => {
100
+ if (response === undefined) return;
101
+ if (!response) {
102
+ // if (element.pendingObject) {
103
+ // isRead = 'true'
104
+ // delete element.pendingObject
105
+ // }
106
+
107
+ let value;
108
+ if (element.hasAttribute("contenteditable")) {
109
+ value = element.innerHTML;
110
+ } else {
111
+ value = element.value;
112
+ }
113
+
114
+ if (value)
115
+ crdt.replaceText({
116
+ array,
117
+ object,
118
+ key,
119
+ value,
120
+ crud: isCrud,
121
+ save: isSave,
122
+ read: isRead
123
+ });
124
+ } else {
125
+ if (element.hasAttribute("contenteditable")) {
126
+ element.innerHTML = "";
127
+ } else {
128
+ element.value = "";
129
+ }
130
+ updateElement({
131
+ element,
132
+ array,
133
+ object,
134
+ key,
135
+ value: response,
136
+ start: 0
137
+ });
138
+ }
139
+ });
140
+
141
+ element.getValue = async () => {
142
+ return await crdt.getText({ array, object, key });
143
+ };
144
+ element.setValue = (value) => {
145
+ crdt.replaceText({
146
+ array,
147
+ object,
148
+ key,
149
+ value,
150
+ crud: isCrud,
151
+ save: isSave,
152
+ read: isRead
153
+ });
154
+ };
155
+ }
111
156
  }
112
157
 
113
158
  function initDocument(doc) {
114
- let documents;
115
- try {
116
- documents = window.top.textDocuments;
117
- } catch (e) {
118
- console.log('cross-origin failed')
119
- }
120
-
121
- if (!documents) {
122
- documents = new Map();
123
- try {
124
- window.top.textDocuments = documents;
125
- } catch (e) {
126
- console.log('cross-origin failed')
127
- }
128
- }
129
- if (!documents.has(doc)) {
130
- documents.set(doc);
131
- doc.addEventListener('selectionchange', (e) => {
132
- let element = doc.activeElement;
133
- let { isRealtime, isCrdt } = getAttributes(element);
134
-
135
- if (isRealtime && isCrdt) {
136
- doc.activeSelection = getSelection(element);
137
- sendPosition(element);
138
- }
139
- });
140
- }
159
+ let documents;
160
+ try {
161
+ documents = window.top.textDocuments;
162
+ } catch (e) {
163
+ console.log("cross-origin failed");
164
+ }
165
+
166
+ if (!documents) {
167
+ documents = new Map();
168
+ try {
169
+ window.top.textDocuments = documents;
170
+ } catch (e) {
171
+ console.log("cross-origin failed");
172
+ }
173
+ }
174
+ if (!documents.has(doc)) {
175
+ documents.set(doc);
176
+ doc.addEventListener("selectionchange", (e) => {
177
+ let element = doc.activeElement;
178
+ let { isRealtime, isCrdt } = getAttributes(element);
179
+
180
+ if (isRealtime && isCrdt) {
181
+ doc.activeSelection = getSelection(element);
182
+ sendPosition(element);
183
+ }
184
+ });
185
+ }
141
186
  }
142
187
 
143
188
  export function _addEventListeners(element) {
144
- element.addEventListener('mousedown', _mousedown);
145
- element.addEventListener('blur', _blur);
146
- element.addEventListener('cut', _cut);
147
- element.addEventListener('paste', _paste);
148
- element.addEventListener('keydown', _keydown);
149
- element.addEventListener('beforeinput', _beforeinput);
150
- element.addEventListener('input', _input);
189
+ element.addEventListener("mousedown", _mousedown);
190
+ element.addEventListener("blur", _blur);
191
+ element.addEventListener("cut", _cut);
192
+ element.addEventListener("paste", _paste);
193
+ element.addEventListener("keydown", _keydown);
194
+ element.addEventListener("beforeinput", _beforeinput);
195
+ element.addEventListener("input", _input);
151
196
  }
152
197
 
153
198
  function _mousedown(event) {
154
- let domTextEditor = event.currentTarget;
155
- if (domTextEditor.tagName === "INPUT" || domTextEditor.tagName === "TEXTAREA") return;
156
- let target = event.target;
157
- // const path = event.path || (event.composedPath && event.composedPath());
158
- // console.log(path)
159
- if (!target.id) {
160
- let isEid = domTextEditor.getAttribute('eid');
161
- if (isEid != 'false' && isEid != null && isEid != undefined) {
162
- let eid = target.getAttribute('eid');
163
- if (!eid) {
164
- eid = uuid.generate(6);
165
- setAttribute({ domTextEditor, target, name: 'eid', value: eid });
166
- }
167
- }
168
- }
169
- let contentEditable = target.closest('[array][object][key]');
170
- if (contentEditable) {
171
- target = contentEditable;
172
- const { array, object, key } = getAttributes(target);
173
- if (array && object && key && !target.hasAttribute('contenteditable'))
174
- target.setAttribute('contenteditable', 'true');
175
- }
176
- sendPosition(domTextEditor)
199
+ let domTextEditor = event.currentTarget;
200
+ if (
201
+ domTextEditor.tagName === "INPUT" ||
202
+ domTextEditor.tagName === "TEXTAREA"
203
+ )
204
+ return;
205
+ let target = event.target;
206
+ // const path = event.path || (event.composedPath && event.composedPath());
207
+ // console.log(path)
208
+ if (!target.id) {
209
+ let isEid = domTextEditor.getAttribute("eid");
210
+ if (isEid != "false" && isEid != null && isEid != undefined) {
211
+ let eid = target.getAttribute("eid");
212
+ if (!eid) {
213
+ eid = uuid.generate(6);
214
+ setAttribute({
215
+ domTextEditor,
216
+ target,
217
+ name: "eid",
218
+ value: eid
219
+ });
220
+ }
221
+ }
222
+ }
223
+ let contentEditable = target.closest("[array][object][key]");
224
+ if (contentEditable) {
225
+ target = contentEditable;
226
+ const { array, object, key } = getAttributes(target);
227
+ if (array && object && key && !target.hasAttribute("contenteditable"))
228
+ target.setAttribute("contenteditable", "true");
229
+ }
230
+ sendPosition(domTextEditor);
177
231
  }
178
232
 
179
233
  function _blur(event) {
180
- let element = event.currentTarget;
181
- const { array, object, key } = getAttributes(element);
182
- let start = null;
183
- let end = null;
184
- cursors.sendPosition({ array, object, key, start, end });
234
+ let element = event.currentTarget;
235
+ const { array, object, key } = getAttributes(element);
236
+ let start = null;
237
+ let end = null;
238
+ cursors.sendPosition({ array, object, key, start, end });
185
239
  }
186
240
 
187
241
  function _cut(event) {
188
- let element = event.currentTarget;
189
- if (element.getAttribute('crdt') == 'false')
190
- return;
191
- const { start, end, range } = getSelection(element);
192
- const selection = document.getSelection();
193
- console.log(selection.toString());
194
- if (event.clipboardData) {
195
- event.clipboardData.setData('text/plain', selection.toString());
196
- } else {
197
- navigator.clipboard.writeText(selection.toString()).then(function () {
198
- /* clipboard successfully set */
199
- }, function () {
200
- /* clipboard write failed */
201
- });
202
- }
203
- if (start != end) {
204
- updateText({ element, start, end, range });
205
- }
206
- event.preventDefault();
242
+ let element = event.currentTarget;
243
+ if (element.getAttribute("crdt") == "false") return;
244
+ const { start, end, range } = getSelection(element);
245
+ const selection = document.getSelection();
246
+ console.log(selection.toString());
247
+ if (event.clipboardData) {
248
+ event.clipboardData.setData("text/plain", selection.toString());
249
+ } else {
250
+ navigator.clipboard.writeText(selection.toString()).then(
251
+ function () {
252
+ /* clipboard successfully set */
253
+ },
254
+ function () {
255
+ /* clipboard write failed */
256
+ }
257
+ );
258
+ }
259
+ if (start != end) {
260
+ updateText({ element, start, end, range });
261
+ }
262
+ event.preventDefault();
207
263
  }
208
264
 
209
265
  function _paste(event) {
210
- let element = event.currentTarget;
211
- if (element.getAttribute('crdt') == 'false')
212
- return;
213
- let value = event.clipboardData.getData('text/plain').replace(/(\r\n|\r)/gm, "\n");;
214
- const { start, end, range } = getSelection(element);
215
- if (start != end) {
216
- updateText({ element, start, end, range });
217
- }
218
- updateText({ element, value, start, range });
219
- event.preventDefault();
266
+ let element = event.currentTarget;
267
+ if (element.getAttribute("crdt") == "false") return;
268
+ let value = event.clipboardData
269
+ .getData("text/plain")
270
+ .replace(/(\r\n|\r)/gm, "\n");
271
+ const { start, end, range } = getSelection(element);
272
+ if (start != end) {
273
+ updateText({ element, start, end, range });
274
+ }
275
+ updateText({ element, value, start, range });
276
+ event.preventDefault();
220
277
  }
221
278
 
222
279
  function _keydown(event) {
223
- if (event.stopCCText) return;
224
- let element = event.currentTarget;
225
- if (element.getAttribute('crdt') == 'false')
226
- return;
227
- const { start, end, range } = getSelection(element);
228
- if (event.key == "Backspace" || event.key == "Tab" || event.key == "Enter") {
229
- eventObj = event;
230
- if (start != end) {
231
- updateText({ element, start, end, range });
232
- }
233
-
234
- if (event.key == "Backspace" && start == end) {
235
- updateText({ element, start: start - 1, end, range });
236
- } else if (event.key == 'Tab') {
237
- updateText({ element, value: "\t", start, range });
238
- } else if (event.key == "Enter") {
239
- updateText({ element, value: "\n", start, range });
240
- }
241
- event.preventDefault();
242
- } else if (event.ctrlKey) {
243
- if (event.keyCode == 90) {
244
- updateText({ element, range, undoRedo: 'undo' });
245
- } else if (event.keyCode == 89) {
246
- updateText({ element, range, undoRedo: 'redo' });
247
- }
248
- } else {
249
- sendPosition(element)
250
- }
280
+ if (event.stopCCText) return;
281
+ let element = event.currentTarget;
282
+ if (element.getAttribute("crdt") == "false") return;
283
+ const { start, end, range } = getSelection(element);
284
+ if (
285
+ event.key == "Backspace" ||
286
+ event.key == "Tab" ||
287
+ event.key == "Enter"
288
+ ) {
289
+ eventObj = event;
290
+ if (start != end) {
291
+ updateText({ element, start, end, range });
292
+ }
293
+
294
+ if (event.key == "Backspace" && start == end) {
295
+ updateText({ element, start: start - 1, end, range });
296
+ } else if (event.key == "Tab") {
297
+ updateText({ element, value: "\t", start, range });
298
+ } else if (event.key == "Enter") {
299
+ updateText({ element, value: "\n", start, range });
300
+ }
301
+ event.preventDefault();
302
+ } else if (event.ctrlKey) {
303
+ if (event.keyCode == 90) {
304
+ updateText({ element, range, undoRedo: "undo" });
305
+ } else if (event.keyCode == 89) {
306
+ updateText({ element, range, undoRedo: "redo" });
307
+ }
308
+ } else {
309
+ sendPosition(element);
310
+ }
251
311
  }
252
312
 
253
313
  function _beforeinput(event) {
254
- if (event.stopCCText) return;
255
- let element = event.currentTarget;
256
- if (element.getAttribute('crdt') == 'false')
257
- return;
258
- let { start, end, range } = getSelection(element);
259
- if (event.data) {
260
- if (start != end) {
261
- updateText({ element, start, end, range });
262
- }
263
- eventObj = event;
264
- updateText({ element, value: event.data, start, range });
265
- event.preventDefault();
266
- }
314
+ if (event.stopCCText) return;
315
+ let element = event.currentTarget;
316
+ if (element.getAttribute("crdt") == "false") return;
317
+ let { start, end, range } = getSelection(element);
318
+ if (event.data) {
319
+ if (start != end) {
320
+ updateText({ element, start, end, range });
321
+ }
322
+ eventObj = event;
323
+ updateText({ element, value: event.data, start, range });
324
+ event.preventDefault();
325
+ }
267
326
  }
268
327
 
269
328
  function _input(event) {
270
- if (event.stopCCText) return;
271
- if (event.data) {
272
- eventObj = event;
273
- }
329
+ if (event.stopCCText) return;
330
+ if (event.data) {
331
+ eventObj = event;
332
+ }
274
333
  }
275
334
 
276
335
  function _removeEventListeners(element) {
277
- element.removeEventListener('mousedown', _mousedown);
278
- element.removeEventListener('blur', _blur);
279
- element.removeEventListener('cut', _cut);
280
- element.removeEventListener('paste', _paste);
281
- element.removeEventListener('keydown', _keydown);
282
- element.removeEventListener('beforeinput', _beforeinput);
283
- element.removeEventListener('input', _input);
336
+ element.removeEventListener("mousedown", _mousedown);
337
+ element.removeEventListener("blur", _blur);
338
+ element.removeEventListener("cut", _cut);
339
+ element.removeEventListener("paste", _paste);
340
+ element.removeEventListener("keydown", _keydown);
341
+ element.removeEventListener("beforeinput", _beforeinput);
342
+ element.removeEventListener("input", _input);
284
343
  }
285
344
 
286
345
  let previousPosition = {};
287
346
  export function sendPosition(element) {
288
- // if (!element) return;
289
- const { start, end, range } = getSelection(element);
290
- if (range) {
291
- if (range.element) {
292
- element = range.element;
293
- }
294
- if (element.tagName == 'HTML' && !element.hasAttribute('array') || !element.hasAttribute('array')) {
295
- element = element.ownerDocument.defaultView.frameElement;
296
- }
297
- }
298
- if (!element) return;
299
- const { array, object, key, isCrdt } = getAttributes(element);
300
- if (isCrdt == 'false' || !array || !object || !key) return;
301
- let currentPosition = { array, object, key, start, end };
302
- if (JSON.stringify(currentPosition) === JSON.stringify(previousPosition))
303
- return;
304
- previousPosition = currentPosition;
305
- // console.log('activeElement: ', element)
306
- element.activeElement = element;
307
- window.activeElement = element;
308
- cursors.sendPosition({ array, object, key, start, end });
347
+ // if (!element) return;
348
+ const { start, end, range } = getSelection(element);
349
+ if (range) {
350
+ if (range.element) {
351
+ element = range.element;
352
+ }
353
+ if (
354
+ (element.tagName == "HTML" && !element.hasAttribute("array")) ||
355
+ !element.hasAttribute("array")
356
+ ) {
357
+ element = element.ownerDocument.defaultView.frameElement;
358
+ }
359
+ }
360
+ if (!element) return;
361
+ const { array, object, key, isCrdt } = getAttributes(element);
362
+ if (isCrdt == "false" || !array || !object || !key) return;
363
+ let currentPosition = { array, object, key, start, end };
364
+ if (JSON.stringify(currentPosition) === JSON.stringify(previousPosition))
365
+ return;
366
+ previousPosition = currentPosition;
367
+ // console.log('activeElement: ', element)
368
+ element.activeElement = element;
369
+ window.activeElement = element;
370
+ cursors.sendPosition({ array, object, key, start, end });
309
371
  }
310
372
 
311
373
  function updateText({ element, value, start, end, range, undoRedo }) {
312
- if (range) {
313
- if (range.element)
314
- element = range.element;
315
-
316
- if (element.tagName == 'HTML' && !element.hasAttribute('array'))
317
- element = element.ownerDocument.defaultView.frameElement;
318
- }
319
-
320
- if (!element) return
321
-
322
- const { array, object, key, isCrud, isCrdt, isSave } = getAttributes(element);
323
- if (isCrdt == "false" || !array || !object || !key) return;
324
-
325
- if (undoRedo == 'undo')
326
- return crdt.undoText({ array, object, key, isCrud, isCrdt, isSave })
327
- if (undoRedo == 'redo')
328
- return crdt.redoText({ array, object, key, isCrud, isCrdt, isSave })
329
-
330
- let length = end - start;
331
- if (element.tagName === "INPUT" || element.tagName === "TEXTAREA") {
332
- crdt.updateText({ array, object, key, value, start, length, crud: isCrud, save: isSave });
333
- } else {
334
- let startEl = range.startContainer.parentElement;
335
- let endEl = range.endContainer.parentElement;
336
- if (startEl != endEl) {
337
- // target = range.commonAncestorContainer;
338
- // // value = target.innerHTML;
339
- // // replaceInnerText(domTextEditor, target, value)
340
- }
341
- crdt.updateText({ array, object, value, key, start, length, crud: isCrud, save: isSave });
342
- }
374
+ if (range) {
375
+ if (range.element) element = range.element;
376
+
377
+ if (element.tagName == "HTML" && !element.hasAttribute("array"))
378
+ element = element.ownerDocument.defaultView.frameElement;
379
+ }
380
+
381
+ if (!element) return;
382
+
383
+ const { array, object, key, isCrud, isCrdt, isSave } =
384
+ getAttributes(element);
385
+ if (isCrdt == "false" || !array || !object || !key) return;
386
+
387
+ if (undoRedo == "undo")
388
+ return crdt.undoText({ array, object, key, isCrud, isCrdt, isSave });
389
+ if (undoRedo == "redo")
390
+ return crdt.redoText({ array, object, key, isCrud, isCrdt, isSave });
391
+
392
+ let length = end - start;
393
+ if (element.tagName === "INPUT" || element.tagName === "TEXTAREA") {
394
+ crdt.updateText({
395
+ array,
396
+ object,
397
+ key,
398
+ value,
399
+ start,
400
+ length,
401
+ crud: isCrud,
402
+ save: isSave
403
+ });
404
+ } else {
405
+ let startEl = range.startContainer.parentElement;
406
+ let endEl = range.endContainer.parentElement;
407
+ if (startEl != endEl) {
408
+ // target = range.commonAncestorContainer;
409
+ // // value = target.innerHTML;
410
+ // // replaceInnerText(domTextEditor, target, value)
411
+ }
412
+ crdt.updateText({
413
+ array,
414
+ object,
415
+ value,
416
+ key,
417
+ start,
418
+ length,
419
+ crud: isCrud,
420
+ save: isSave
421
+ });
422
+ }
343
423
  }
344
424
 
345
425
  function _crdtUpdateListener() {
346
- window.addEventListener('cocreate-crdt-update', function (event) {
347
- updateElements({ ...event.detail });
348
- });
426
+ window.addEventListener("cocreate-crdt-update", function (event) {
427
+ updateElements({ ...event.detail });
428
+ });
349
429
  }
350
430
 
351
- function updateElements({ elements, array, object, key, value, start, length, string }) {
352
- if (!elements) {
353
- let selectors = `[array='${array}'][object='${object}'][key='${key}']`;
354
- elements = document.querySelectorAll(`input${selectors}, textarea${selectors}, [contenteditable]${selectors}, [editor='dom']${selectors}`);
355
- }
356
-
357
- elements.forEach((element) => {
358
- let isCrdt = element.getAttribute('crdt');
359
- if (!element.hasAttribute('contenteditable') && isCrdt == 'false') return;
360
-
361
- updateElement({ element, array, object, key, value, start, length, string });
362
- });
431
+ function updateElements({
432
+ elements,
433
+ array,
434
+ object,
435
+ key,
436
+ value,
437
+ start,
438
+ length,
439
+ string
440
+ }) {
441
+ if (!elements) {
442
+ let selectors = `[array='${array}'][object='${object}'][key='${key}']`;
443
+ elements = document.querySelectorAll(
444
+ `input${selectors}, textarea${selectors}, [contenteditable]${selectors}, [editor='dom']${selectors}`
445
+ );
446
+ }
447
+
448
+ elements.forEach((element) => {
449
+ let isCrdt = element.getAttribute("crdt");
450
+ if (!element.hasAttribute("contenteditable") && isCrdt == "false")
451
+ return;
452
+
453
+ updateElement({
454
+ element,
455
+ array,
456
+ object,
457
+ key,
458
+ value,
459
+ start,
460
+ length,
461
+ string
462
+ });
463
+ });
363
464
  }
364
465
 
365
- async function updateElement({ element, array, object, key, value, start, length, string }) {
366
- if (element.tagName == 'IFRAME') {
367
- let eid = element.getAttribute('eid')
368
- element = element.contentDocument.documentElement;
369
- if (eid != 'false' && eid != null && eid != undefined)
370
- element.setAttribute('eid', eid)
371
- if (element.contenteditable != 'false')
372
- element.contentEditable = true;
373
- }
374
- if (value || length) {
375
- if (element.tagName === "INPUT" || element.tagName === "TEXTAREA") {
376
- if (length) {
377
- _updateElementText(element, "", start, start + length);
378
- }
379
- if (value) {
380
- _updateElementText(element, value, start, start);
381
- }
382
- } else {
383
- let domTextEditor = element;
384
- if (string == undefined)
385
- string = await crdt.getText({ array, object, key });
386
- let html = string;
387
- if (length) {
388
- let end = start + length;
389
- updateDom({ domTextEditor, start, end, html });
390
- }
391
- if (value) {
392
- if (element.innerHTML != value) {
393
- updateDom({ domTextEditor, value, start, end: start, html });
394
- }
395
- }
396
- }
397
- }
466
+ async function updateElement({
467
+ element,
468
+ array,
469
+ object,
470
+ key,
471
+ value,
472
+ start,
473
+ length,
474
+ string
475
+ }) {
476
+ if (element.tagName == "IFRAME") {
477
+ let eid = element.getAttribute("eid");
478
+ element = element.contentDocument.documentElement;
479
+ if (eid != "false" && eid != null && eid != undefined)
480
+ element.setAttribute("eid", eid);
481
+ if (element.contenteditable != "false") element.contentEditable = true;
482
+ }
483
+ if (value || length) {
484
+ if (element.tagName === "INPUT" || element.tagName === "TEXTAREA") {
485
+ if (length) {
486
+ _updateElementText(element, "", start, start + length);
487
+ }
488
+ if (value) {
489
+ _updateElementText(element, value, start, start);
490
+ }
491
+ } else {
492
+ let domTextEditor = element;
493
+ if (string == undefined)
494
+ string = await crdt.getText({ array, object, key });
495
+ let html = string;
496
+ if (length) {
497
+ let end = start + length;
498
+ updateDom({ domTextEditor, start, end, html });
499
+ }
500
+ if (value) {
501
+ if (element.innerHTML != value) {
502
+ updateDom({
503
+ domTextEditor,
504
+ value,
505
+ start,
506
+ end: start,
507
+ html
508
+ });
509
+ }
510
+ }
511
+ }
512
+ }
398
513
  }
399
514
 
400
515
  function _updateElementText(element, value, start, end) {
401
- if (element.tagName === "INPUT" && ["text", "tel", "url"].includes(element.type) || element.tagName === "TEXTAREA") {
402
- let prev_start = element.selectionStart;
403
- let prev_end = element.selectionEnd;
404
- let activeElement = element.ownerDocument.activeElement;
405
- element.setRangeText(value, start, end, "end");
406
- let p = processSelection(element, value, prev_start, prev_end, start, end);
407
- if (activeElement == element)
408
- sendPosition(element);
409
- _dispatchInputEvent(element, p.value, p.start, p.end, p.prev_start, p.prev_end);
410
- }
516
+ if (
517
+ (element.tagName === "INPUT" &&
518
+ ["text", "tel", "url"].includes(element.type)) ||
519
+ element.tagName === "TEXTAREA"
520
+ ) {
521
+ let prev_start = element.selectionStart;
522
+ let prev_end = element.selectionEnd;
523
+ let activeElement = element.ownerDocument.activeElement;
524
+ element.setRangeText(value, start, end, "end");
525
+ let p = processSelection(
526
+ element,
527
+ value,
528
+ prev_start,
529
+ prev_end,
530
+ start,
531
+ end
532
+ );
533
+ if (activeElement == element) sendPosition(element);
534
+ _dispatchInputEvent(
535
+ element,
536
+ p.value,
537
+ p.start,
538
+ p.end,
539
+ p.prev_start,
540
+ p.prev_end
541
+ );
542
+ }
411
543
  }
412
544
 
413
- export function _dispatchInputEvent(element, content, start, end, prev_start, prev_end) {
414
- let detail = { value: content, start, end, prev_start, prev_end, skip: true };
415
- let activeElement = element.ownerDocument.activeElement;
416
- if (activeElement == element)
417
- detail.skip = false;
418
- if (eventObj) {
419
- let event = new CustomEvent(eventObj.type, { bubbles: true });
420
- Object.defineProperty(event, 'stopCCText', { writable: false, value: true });
421
- Object.defineProperty(event, 'target', { writable: false, value: element });
422
- Object.defineProperty(event, 'detail', { writable: false, value: detail });
423
- element.dispatchEvent(event);
424
- }
425
- let inputEvent = new CustomEvent('input', { bubbles: true });
426
- Object.defineProperty(inputEvent, 'stopCCText', { writable: false, value: true });
427
- Object.defineProperty(inputEvent, 'target', { writable: false, value: element });
428
- Object.defineProperty(inputEvent, 'detail', { writable: false, value: detail });
429
- element.dispatchEvent(inputEvent);
545
+ export function _dispatchInputEvent(
546
+ element,
547
+ content,
548
+ start,
549
+ end,
550
+ prev_start,
551
+ prev_end
552
+ ) {
553
+ let detail = {
554
+ value: content,
555
+ start,
556
+ end,
557
+ prev_start,
558
+ prev_end,
559
+ skip: true
560
+ };
561
+ let activeElement = element.ownerDocument.activeElement;
562
+ if (activeElement == element) detail.skip = false;
563
+ if (eventObj) {
564
+ let event = new CustomEvent(eventObj.type, { bubbles: true });
565
+ Object.defineProperty(event, "stopCCText", {
566
+ writable: false,
567
+ value: true
568
+ });
569
+ Object.defineProperty(event, "target", {
570
+ writable: false,
571
+ value: element
572
+ });
573
+ Object.defineProperty(event, "detail", {
574
+ writable: false,
575
+ value: detail
576
+ });
577
+ element.dispatchEvent(event);
578
+ }
579
+ let inputEvent = new CustomEvent("input", { bubbles: true });
580
+ Object.defineProperty(inputEvent, "stopCCText", {
581
+ writable: false,
582
+ value: true
583
+ });
584
+ Object.defineProperty(inputEvent, "target", {
585
+ writable: false,
586
+ value: element
587
+ });
588
+ Object.defineProperty(inputEvent, "detail", {
589
+ writable: false,
590
+ value: detail
591
+ });
592
+ element.dispatchEvent(inputEvent);
430
593
  }
431
594
 
432
595
  observer.init({
433
- name: 'CoCreateTextAddedNodes',
434
- observe: ['addedNodes'],
435
- selector: selectors,
436
- callback(mutation) {
437
- let isCrdt = mutation.target.getAttribute('crdt');
438
- if (isCrdt) return;
439
- initElement(mutation.target);
440
- }
596
+ name: "CoCreateTextAddedNodes",
597
+ types: ["addedNodes"],
598
+ selector: selectors,
599
+ callback(mutation) {
600
+ let isCrdt = mutation.target.getAttribute("crdt");
601
+ if (isCrdt) return;
602
+ initElement(mutation.target);
603
+ }
441
604
  });
442
605
 
443
606
  observer.init({
444
- name: 'CoCreateTextAttribtes',
445
- observe: ['attributes'],
446
- attributeName: [...getAttributeNames(['array', 'object', 'key']), 'contenteditable'],
447
- selector: selectors,
448
- callback(mutation) {
449
- let _id = mutation.target.getAttribute('object')
450
- if (!_id) {
451
- _removeEventListeners(mutation.target)
452
- mutation.target.removeAttribute('crdt')
453
- } else {
454
- initElement(mutation.target);
455
- }
456
- }
607
+ name: "CoCreateTextAttribtes",
608
+ types: ["attributes"],
609
+ attributeFilter: [
610
+ ...getAttributeNames(["array", "object", "key"]),
611
+ "contenteditable"
612
+ ],
613
+ selector: selectors,
614
+ callback(mutation) {
615
+ let _id = mutation.target.getAttribute("object");
616
+ if (!_id) {
617
+ _removeEventListeners(mutation.target);
618
+ mutation.target.removeAttribute("crdt");
619
+ } else {
620
+ initElement(mutation.target);
621
+ }
622
+ }
457
623
  });
458
624
 
459
625
  action.init({
460
- name: "undo",
461
- endEvent: "undo",
462
- callback: (data) => {
463
- const { array, object, key, isCrud, isCrdt, isSave } = getAttributes(data.element);
464
- crdt.undoText({ array, object, key, isCrud, isCrdt, isSave })
465
- }
626
+ name: "undo",
627
+ endEvent: "undo",
628
+ callback: (data) => {
629
+ const { array, object, key, isCrud, isCrdt, isSave } = getAttributes(
630
+ data.element
631
+ );
632
+ crdt.undoText({ array, object, key, isCrud, isCrdt, isSave });
633
+ }
466
634
  });
467
635
 
468
636
  action.init({
469
- name: "redo",
470
- endEvent: "redo",
471
- callback: (data) => {
472
- const { array, object, key, isCrud, isCrdt, isSave } = getAttributes(data.element);
473
- crdt.redoText({ array, object, key, isCrud, isCrdt, isSave })
474
- }
637
+ name: "redo",
638
+ endEvent: "redo",
639
+ callback: (data) => {
640
+ const { array, object, key, isCrud, isCrdt, isSave } = getAttributes(
641
+ data.element
642
+ );
643
+ crdt.redoText({ array, object, key, isCrud, isCrdt, isSave });
644
+ }
475
645
  });
476
646
 
477
647
  init();
478
648
 
479
- export default { initElements, initElement, updateText, updateElement, _addEventListeners, insertAdjacentElement, removeElement, setInnerText, setAttribute, removeAttribute, setClass, setStyle, replaceInnerText };
649
+ export default {
650
+ initElements,
651
+ initElement,
652
+ updateText,
653
+ updateElement,
654
+ _addEventListeners,
655
+ insertAdjacentElement,
656
+ removeElement,
657
+ setInnerText,
658
+ setAttribute,
659
+ removeAttribute,
660
+ setClass,
661
+ setStyle,
662
+ replaceInnerText
663
+ };
package/src/updateText.js CHANGED
@@ -1,82 +1,155 @@
1
- import crdt from '@cocreate/crdt';
2
- import { getAttributes } from '@cocreate/utils';
3
- import { getStringPosition } from '@cocreate/selection';
1
+ import crdt from "@cocreate/crdt";
2
+ import { getAttributes } from "@cocreate/utils";
3
+ import { getStringPosition } from "@cocreate/selection";
4
4
 
5
- export function insertAdjacentElement({ domTextEditor, target, position, element, elementValue }) {
6
- let remove;
7
- if (element && !elementValue) {
8
- remove = getStringPosition({ string: domTextEditor.htmlString, target: element });
9
- if (!remove || !remove.start && !remove.end)
10
- throw new Error('insertAdjacentElement: element not found');
5
+ export function insertAdjacentElement({
6
+ domTextEditor,
7
+ target,
8
+ position,
9
+ element,
10
+ elementValue
11
+ }) {
12
+ try {
13
+ let remove;
14
+ if (element && !elementValue) {
15
+ remove = getStringPosition({
16
+ string: domTextEditor.htmlString,
17
+ target: element
18
+ });
19
+ if (!remove || (!remove.start && !remove.end))
20
+ throw new Error("insertAdjacentElement: element not found");
11
21
 
12
- elementValue = domTextEditor.htmlString.substring(remove.start, remove.end);
13
- }
22
+ elementValue = domTextEditor.htmlString.substring(
23
+ remove.start,
24
+ remove.end
25
+ );
26
+ }
14
27
 
15
- let { start } = getStringPosition({ string: domTextEditor.htmlString, target, position, value: elementValue });
16
- if (remove)
17
- _updateText({ domTextEditor, start: remove.start, end: remove.end });
18
- if (remove && remove.start < start) {
19
- let length = remove.end - remove.start;
20
- _updateText({ domTextEditor, value: elementValue, start: start - length });
21
- }
22
- else
23
- _updateText({ domTextEditor, value: elementValue, start });
28
+ let { start } = getStringPosition({
29
+ string: domTextEditor.htmlString,
30
+ target,
31
+ position,
32
+ value: elementValue
33
+ });
34
+ if (remove)
35
+ _updateText({
36
+ domTextEditor,
37
+ start: remove.start,
38
+ end: remove.end
39
+ });
40
+ if (remove && remove.start < start) {
41
+ let length = remove.end - remove.start;
42
+ _updateText({
43
+ domTextEditor,
44
+ value: elementValue,
45
+ start: start - length
46
+ });
47
+ } else _updateText({ domTextEditor, value: elementValue, start });
48
+ } catch (error) {
49
+ console.error(error);
50
+ }
24
51
  }
25
52
 
26
53
  export function removeElement({ domTextEditor, target }) {
27
- updateDomText({ domTextEditor, target });
54
+ updateDomText({ domTextEditor, target });
28
55
  }
29
56
 
30
57
  export function setInnerText({ domTextEditor, target, value, start, end }) {
31
- updateDomText({ domTextEditor, target, value, pos: { start, end } });
58
+ updateDomText({ domTextEditor, target, value, pos: { start, end } });
32
59
  }
33
60
 
34
61
  export function setClass({ domTextEditor, target, value }) {
35
- updateDomText({ domTextEditor, target, attribute: 'class', value });
62
+ updateDomText({ domTextEditor, target, attribute: "class", value });
36
63
  }
37
64
  export function removeClass({ domTextEditor, target, value }) {
38
- updateDomText({ domTextEditor, target, attribute: 'class', value, remove: true });
65
+ updateDomText({
66
+ domTextEditor,
67
+ target,
68
+ attribute: "class",
69
+ value,
70
+ remove: true
71
+ });
39
72
  }
40
73
 
41
74
  export function setStyle({ domTextEditor, target, property, value }) {
42
- updateDomText({ domTextEditor, target, attribute: 'style', property, value });
75
+ updateDomText({
76
+ domTextEditor,
77
+ target,
78
+ attribute: "style",
79
+ property,
80
+ value
81
+ });
43
82
  }
44
83
 
45
84
  export function removeStyle({ domTextEditor, target, property }) {
46
- updateDomText({ domTextEditor, target, attribute: 'style', property, remove: true });
85
+ updateDomText({
86
+ domTextEditor,
87
+ target,
88
+ attribute: "style",
89
+ property,
90
+ remove: true
91
+ });
47
92
  }
48
93
 
49
94
  export function setAttribute({ domTextEditor, target, name, value }) {
50
- updateDomText({ domTextEditor, target, attribute: name, value });
95
+ updateDomText({ domTextEditor, target, attribute: name, value });
51
96
  }
52
97
 
53
98
  export function removeAttribute({ domTextEditor, target, name }) {
54
- updateDomText({ domTextEditor, target, attribute: name, remove: 'true' });
99
+ updateDomText({ domTextEditor, target, attribute: name, remove: "true" });
55
100
  }
56
101
 
57
102
  export function replaceInnerText({ domTextEditor, target, value }) {
58
- updateDomText({ domTextEditor, target, value });
103
+ updateDomText({ domTextEditor, target, value });
59
104
  }
60
105
 
61
- export function updateDomText({ domTextEditor, target, position, element, elementValue, attribute, value, property, pos, remove }) {
62
- let selection = getStringPosition({ string: domTextEditor.htmlString, target, attribute, property, value, remove });
63
- if (!selection) return
64
- let { start, end, newValue } = selection;
65
- if (pos) {
66
- start += pos.start;
67
- end += pos.end;
68
- }
69
- if (start != end)
70
- _updateText({ domTextEditor, start, end });
71
- if (attribute && remove != 'true' || attribute && value)
72
- _updateText({ domTextEditor, value: ` ${attribute}="${newValue}"`, start });
73
- else if (value)
74
- _updateText({ domTextEditor, value, start });
106
+ export function updateDomText({
107
+ domTextEditor,
108
+ target,
109
+ position,
110
+ element,
111
+ elementValue,
112
+ attribute,
113
+ value,
114
+ property,
115
+ pos,
116
+ remove
117
+ }) {
118
+ let selection = getStringPosition({
119
+ string: domTextEditor.htmlString,
120
+ target,
121
+ attribute,
122
+ property,
123
+ value,
124
+ remove
125
+ });
126
+ if (!selection) return;
127
+ let { start, end, newValue } = selection;
128
+ if (pos) {
129
+ start += pos.start;
130
+ end += pos.end;
131
+ }
132
+ if (start != end) _updateText({ domTextEditor, start, end });
133
+ if ((attribute && remove != "true") || (attribute && value))
134
+ _updateText({
135
+ domTextEditor,
136
+ value: ` ${attribute}="${newValue}"`,
137
+ start
138
+ });
139
+ else if (value) _updateText({ domTextEditor, value, start });
75
140
  }
76
141
 
77
142
  function _updateText({ domTextEditor, value, start, end }) {
78
- if (domTextEditor.tagName == 'HTML')
79
- domTextEditor = domTextEditor.ownerDocument.defaultView.frameElement;
80
- const { array, object, key, isCrud } = getAttributes(domTextEditor);
81
- crdt.updateText({ array, object, key, value, start, length: end - start, crud: isCrud });
143
+ if (domTextEditor.tagName == "HTML")
144
+ domTextEditor = domTextEditor.ownerDocument.defaultView.frameElement;
145
+ const { array, object, key, isCrud } = getAttributes(domTextEditor);
146
+ crdt.updateText({
147
+ array,
148
+ object,
149
+ key,
150
+ value,
151
+ start,
152
+ length: end - start,
153
+ crud: isCrud
154
+ });
82
155
  }