@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 +15 -0
- package/package.json +1 -1
- package/src/index.js +586 -402
- package/src/updateText.js +120 -47
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
package/src/index.js
CHANGED
@@ -1,479 +1,663 @@
|
|
1
1
|
/*global CustomEvent, navigator*/
|
2
|
-
import observer from
|
3
|
-
import crdt from
|
4
|
-
import cursors from
|
5
|
-
import uuid from
|
6
|
-
import { getAttributes, getAttributeNames, checkValue } from
|
7
|
-
import { updateDom } from
|
8
|
-
import {
|
9
|
-
|
10
|
-
|
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
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
26
|
+
let elements = document.querySelectorAll(selectors);
|
27
|
+
initElements(elements);
|
28
|
+
_crdtUpdateListener();
|
29
|
+
initDocument(document);
|
21
30
|
}
|
22
31
|
|
23
32
|
function initElements(elements) {
|
24
|
-
|
25
|
-
initElement(element);
|
33
|
+
for (let element of elements) initElement(element);
|
26
34
|
}
|
27
35
|
|
28
36
|
function initElement(element) {
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
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
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
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
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
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
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
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
|
-
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
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
|
-
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
|
198
|
-
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
|
204
|
-
|
205
|
-
|
206
|
-
|
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
|
-
|
211
|
-
|
212
|
-
|
213
|
-
|
214
|
-
|
215
|
-
|
216
|
-
|
217
|
-
|
218
|
-
|
219
|
-
|
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
|
-
|
224
|
-
|
225
|
-
|
226
|
-
|
227
|
-
|
228
|
-
|
229
|
-
|
230
|
-
|
231
|
-
|
232
|
-
|
233
|
-
|
234
|
-
|
235
|
-
|
236
|
-
|
237
|
-
|
238
|
-
|
239
|
-
|
240
|
-
|
241
|
-
|
242
|
-
|
243
|
-
|
244
|
-
|
245
|
-
|
246
|
-
|
247
|
-
|
248
|
-
|
249
|
-
|
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
|
-
|
255
|
-
|
256
|
-
|
257
|
-
|
258
|
-
|
259
|
-
|
260
|
-
|
261
|
-
|
262
|
-
|
263
|
-
|
264
|
-
|
265
|
-
|
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
|
-
|
271
|
-
|
272
|
-
|
273
|
-
|
329
|
+
if (event.stopCCText) return;
|
330
|
+
if (event.data) {
|
331
|
+
eventObj = event;
|
332
|
+
}
|
274
333
|
}
|
275
334
|
|
276
335
|
function _removeEventListeners(element) {
|
277
|
-
|
278
|
-
|
279
|
-
|
280
|
-
|
281
|
-
|
282
|
-
|
283
|
-
|
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
|
-
|
289
|
-
|
290
|
-
|
291
|
-
|
292
|
-
|
293
|
-
|
294
|
-
|
295
|
-
|
296
|
-
|
297
|
-
|
298
|
-
|
299
|
-
|
300
|
-
|
301
|
-
|
302
|
-
|
303
|
-
|
304
|
-
|
305
|
-
|
306
|
-
|
307
|
-
|
308
|
-
|
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
|
-
|
313
|
-
|
314
|
-
|
315
|
-
|
316
|
-
|
317
|
-
|
318
|
-
|
319
|
-
|
320
|
-
|
321
|
-
|
322
|
-
|
323
|
-
|
324
|
-
|
325
|
-
|
326
|
-
|
327
|
-
|
328
|
-
|
329
|
-
|
330
|
-
|
331
|
-
|
332
|
-
|
333
|
-
|
334
|
-
|
335
|
-
|
336
|
-
|
337
|
-
|
338
|
-
|
339
|
-
|
340
|
-
|
341
|
-
|
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
|
-
|
347
|
-
|
348
|
-
|
426
|
+
window.addEventListener("cocreate-crdt-update", function (event) {
|
427
|
+
updateElements({ ...event.detail });
|
428
|
+
});
|
349
429
|
}
|
350
430
|
|
351
|
-
function updateElements({
|
352
|
-
|
353
|
-
|
354
|
-
|
355
|
-
|
356
|
-
|
357
|
-
|
358
|
-
|
359
|
-
|
360
|
-
|
361
|
-
|
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({
|
366
|
-
|
367
|
-
|
368
|
-
|
369
|
-
|
370
|
-
|
371
|
-
|
372
|
-
|
373
|
-
|
374
|
-
|
375
|
-
|
376
|
-
|
377
|
-
|
378
|
-
|
379
|
-
|
380
|
-
|
381
|
-
|
382
|
-
|
383
|
-
|
384
|
-
|
385
|
-
|
386
|
-
|
387
|
-
|
388
|
-
|
389
|
-
|
390
|
-
|
391
|
-
|
392
|
-
|
393
|
-
|
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
|
-
|
402
|
-
|
403
|
-
|
404
|
-
|
405
|
-
|
406
|
-
|
407
|
-
|
408
|
-
|
409
|
-
|
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(
|
414
|
-
|
415
|
-
|
416
|
-
|
417
|
-
|
418
|
-
|
419
|
-
|
420
|
-
|
421
|
-
|
422
|
-
|
423
|
-
|
424
|
-
|
425
|
-
|
426
|
-
|
427
|
-
|
428
|
-
|
429
|
-
|
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
|
-
|
434
|
-
|
435
|
-
|
436
|
-
|
437
|
-
|
438
|
-
|
439
|
-
|
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
|
-
|
445
|
-
|
446
|
-
|
447
|
-
|
448
|
-
|
449
|
-
|
450
|
-
|
451
|
-
|
452
|
-
|
453
|
-
|
454
|
-
|
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
|
-
|
461
|
-
|
462
|
-
|
463
|
-
|
464
|
-
|
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
|
-
|
470
|
-
|
471
|
-
|
472
|
-
|
473
|
-
|
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 {
|
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
|
2
|
-
import { getAttributes } from
|
3
|
-
import { getStringPosition } from
|
1
|
+
import crdt from "@cocreate/crdt";
|
2
|
+
import { getAttributes } from "@cocreate/utils";
|
3
|
+
import { getStringPosition } from "@cocreate/selection";
|
4
4
|
|
5
|
-
export function insertAdjacentElement({
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
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
|
-
|
13
|
-
|
22
|
+
elementValue = domTextEditor.htmlString.substring(
|
23
|
+
remove.start,
|
24
|
+
remove.end
|
25
|
+
);
|
26
|
+
}
|
14
27
|
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
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
|
-
|
54
|
+
updateDomText({ domTextEditor, target });
|
28
55
|
}
|
29
56
|
|
30
57
|
export function setInnerText({ domTextEditor, target, value, start, end }) {
|
31
|
-
|
58
|
+
updateDomText({ domTextEditor, target, value, pos: { start, end } });
|
32
59
|
}
|
33
60
|
|
34
61
|
export function setClass({ domTextEditor, target, value }) {
|
35
|
-
|
62
|
+
updateDomText({ domTextEditor, target, attribute: "class", value });
|
36
63
|
}
|
37
64
|
export function removeClass({ domTextEditor, target, value }) {
|
38
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
95
|
+
updateDomText({ domTextEditor, target, attribute: name, value });
|
51
96
|
}
|
52
97
|
|
53
98
|
export function removeAttribute({ domTextEditor, target, name }) {
|
54
|
-
|
99
|
+
updateDomText({ domTextEditor, target, attribute: name, remove: "true" });
|
55
100
|
}
|
56
101
|
|
57
102
|
export function replaceInnerText({ domTextEditor, target, value }) {
|
58
|
-
|
103
|
+
updateDomText({ domTextEditor, target, value });
|
59
104
|
}
|
60
105
|
|
61
|
-
export function updateDomText({
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
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
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
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
|
}
|