@cocreate/cursors 1.23.4 → 1.23.5

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.23.5](https://github.com/CoCreate-app/CoCreate-cursors/compare/v1.23.4...v1.23.5) (2024-06-12)
2
+
3
+
4
+ ### Bug Fixes
5
+
6
+ * bump cocreate dependencies ([004badc](https://github.com/CoCreate-app/CoCreate-cursors/commit/004badc58b9eaec4b3b7c9e7f4a6933da5420121))
7
+ * contenteditable element selections ([8f16a74](https://github.com/CoCreate-app/CoCreate-cursors/commit/8f16a7465c027e78b63fa450675877ac827fcdc8))
8
+ * cursor selections on contenteditable elements ([8fc58e0](https://github.com/CoCreate-app/CoCreate-cursors/commit/8fc58e0dce113844876858244160de6c1ed3efaf))
9
+ * fix cursors and selection between contenteditable and textareas ([2e12fec](https://github.com/CoCreate-app/CoCreate-cursors/commit/2e12fece9f37a54d82731ef59bf03536b845a3b0))
10
+ * improved handling of contenteditable cursors using innerHtml ([36b0b40](https://github.com/CoCreate-app/CoCreate-cursors/commit/36b0b4073eb46763dfec1d955aae2cdcf707430c))
11
+ * improved whitespace handling ([0fe857e](https://github.com/CoCreate-app/CoCreate-cursors/commit/0fe857ef526b19e65f524ca2b0adc88bc6f807eb))
12
+ * set transparent!important ([e728c56](https://github.com/CoCreate-app/CoCreate-cursors/commit/e728c5692d5022d839e41375339089e93da3bb3c))
13
+ * svg icon class ([b7512a5](https://github.com/CoCreate-app/CoCreate-cursors/commit/b7512a5d272dfb62e0a4537c96c426f879b73340))
14
+ * use framId to determin if should continue element loop ([bb865cf](https://github.com/CoCreate-app/CoCreate-cursors/commit/bb865cf859a0ae319f45330bec9ba8532ab5cd91))
15
+
1
16
  ## [1.23.4](https://github.com/CoCreate-app/CoCreate-cursors/compare/v1.23.3...v1.23.4) (2024-04-29)
2
17
 
3
18
 
package/demo/index.html CHANGED
@@ -39,9 +39,7 @@
39
39
 
40
40
  <button
41
41
  class="submitBtn position:fixed bottom:15px right:15px bg-blue grow-hover round">
42
- <i
43
- class="height:20px fill:#505050"
44
- src="/assets/svg/save.svg"></i>
42
+ <i src="/assets/svg/save.svg"></i>
45
43
  </button>
46
44
  </form>
47
45
 
package/docs/index.html CHANGED
@@ -72,41 +72,31 @@
72
72
  href="https://github.com/CoCreate-app/CoCreate-cursor"
73
73
  target="_blank"
74
74
  class="margin-right:15px"
75
- ><i
76
- class="height:20px fill:#505050"
77
- src="/assets/svg/github.svg"></i
75
+ ><i src="/assets/svg/github.svg"></i
78
76
  ></a>
79
77
  <a
80
78
  class="margin-right:15px share"
81
79
  share-network="twitter"
82
80
  title="Share on twitter"
83
- ><i
84
- class="height:20px fill:#505050"
85
- src="/assets/svg/twitter.svg"></i
81
+ ><i src="/assets/svg/twitter.svg"></i
86
82
  ></a>
87
83
  <a
88
84
  class="margin-right:15px share"
89
85
  share-network="facebook"
90
86
  title="Share on Facebook"
91
- ><i
92
- class="height:20px fill:#505050"
93
- src="/assets/svg/facebook.svg"></i
87
+ ><i src="/assets/svg/facebook.svg"></i
94
88
  ></a>
95
89
  <a
96
90
  class="margin-right:15px share"
97
91
  share-network="instagram"
98
92
  title="Share on instagram"
99
- ><i
100
- class="height:20px fill:#505050"
101
- src="/assets/svg/instagram.svg"></i
93
+ ><i src="/assets/svg/instagram.svg"></i
102
94
  ></a>
103
95
  <a
104
96
  class="margin-right:15px share"
105
97
  share-network="share"
106
98
  title="Share on share"
107
- ><i
108
- class="height:20px fill:#505050"
109
- src="/assets/svg/share-alt.svg"></i
99
+ ><i src="/assets/svg/share-alt.svg"></i
110
100
  ></a>
111
101
  </div>
112
102
  </div>
@@ -223,7 +213,7 @@
223
213
  toggle="code-height"
224
214
  toggle-selector="#demo-code"
225
215
  ><i
226
- class="height:18px fill:#505050"
216
+ class="height:18px"
227
217
  src="/assets/svg/eye.svg"></i
228
218
  ></a>
229
219
  <a
@@ -234,18 +224,14 @@
234
224
  hide="#eye-slash"
235
225
  toggle="code-height"
236
226
  toggle-selector="#demo-code"
237
- ><i
238
- class="height:20px fill:#505050"
239
- src="/assets/svg/eye-slash.svg"></i
227
+ ><i src="/assets/svg/eye-slash.svg"></i
240
228
  ></a>
241
229
  <a
242
230
  class="margin-right:10px"
243
231
  id="code"
244
232
  show="#code-slash"
245
233
  hide="#code, #demo-code"
246
- ><i
247
- class="height:20px fill:#505050"
248
- src="/assets/svg/code.svg"></i
234
+ ><i src="/assets/svg/code.svg"></i
249
235
  ></a>
250
236
  <a
251
237
  class="margin-right:10px"
@@ -254,7 +240,7 @@
254
240
  show="#code, #demo-code"
255
241
  hide="#code-slash"
256
242
  ><i
257
- class="display:flex height:18px fill:#505050"
243
+ class="height:18px"
258
244
  src="/assets/svg/code.svg"></i
259
245
  ></a>
260
246
  <a
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cocreate/cursors",
3
- "version": "1.23.4",
3
+ "version": "1.23.5",
4
4
  "description": "Collaborative user cursor position for inputs, textarea's and contenteditable elements.",
5
5
  "keywords": [
6
6
  "cursors",
@@ -59,12 +59,12 @@
59
59
  "webpack-log": "^3.0.1"
60
60
  },
61
61
  "dependencies": {
62
- "@cocreate/local-storage": "^1.14.3",
63
- "@cocreate/observer": "^1.16.0",
64
- "@cocreate/random-color": "^1.10.0",
65
- "@cocreate/selection": "^1.12.1",
66
- "@cocreate/socket-client": "^1.39.0",
67
- "@cocreate/utils": "^1.33.6",
68
- "@cocreate/uuid": "^1.11.1"
62
+ "@cocreate/local-storage": "^1.15.0",
63
+ "@cocreate/observer": "^1.16.1",
64
+ "@cocreate/random-color": "^1.10.1",
65
+ "@cocreate/selection": "^1.12.2",
66
+ "@cocreate/socket-client": "^1.39.1",
67
+ "@cocreate/utils": "^1.33.7",
68
+ "@cocreate/uuid": "^1.11.2"
69
69
  }
70
70
  }
package/src/index.css CHANGED
@@ -3,6 +3,7 @@ cursor {
3
3
  display: inline-block;
4
4
  width: 2px;
5
5
  background-color: dodgerblue;
6
+ white-space: pre-wrap
6
7
  /*z-index:10;*/
7
8
  }
8
9
 
@@ -36,11 +37,18 @@ selected-text {
36
37
  }
37
38
 
38
39
  .mirror_color {
39
- color: transparent;
40
- pointer-events: none;
40
+ color: transparent!important;
41
41
  }
42
42
 
43
43
  .mirror_scroll::-webkit-scrollbar {
44
44
  background: rgba(0, 0, 0, 0.00);
45
45
  width: 0px;
46
46
  }
47
+
48
+ [mirror_id][contenteditable] {
49
+ white-space: pre-wrap
50
+ }
51
+
52
+ selected-text * {
53
+ background-color: inherit;
54
+ }
package/src/index.js CHANGED
@@ -83,16 +83,16 @@ function drawCursors(selection) {
83
83
  const array = selection['array'];
84
84
  const object = selection['object'];
85
85
  const key = selection['key'];
86
- let start = selection['start'];
87
- let end = selection['end'];
88
86
  let elements = selection.element;
89
87
  if (!elements) {
90
88
  let selector = '[array="' + array + '"][object="' + object + '"][key="' + key + '"]';
91
- selector += ':not(.codemirror, .monaco, [actions])';
89
+ selector += ':not([actions])';
92
90
  elements = document.querySelectorAll(selector);
93
91
  }
94
92
  for (let element of elements) {
95
- if (window.activeElement == element && socket.has(selection.socketId)) {
93
+ let start = selection['start']
94
+ let end = selection['end']
95
+ if (window.activeElement == element && socket.frameId === selection.frameId) {
96
96
  continue;
97
97
  }
98
98
  let realtime = element.getAttribute('realtime');
@@ -100,29 +100,33 @@ function drawCursors(selection) {
100
100
  let contenteditable = element.getAttribute('contenteditable');
101
101
  if (!realtime || realtime == 'false' || cursors == 'false') continue;
102
102
  if (element.tagName != 'INPUT' && element.tagName != 'TEXTAREA')
103
- if (contenteditable == 'false' || contenteditable == null)
103
+ if (contenteditable == 'false' || contenteditable === null) {
104
104
  if (cursors != 'true') continue;
105
+ }
105
106
 
106
107
  if (element.hasAttribute('contenteditable')) {
107
- let domTextEditor = element;
108
+ contenteditable = true
109
+ // let domTextEditor = element;
108
110
  if (element.tagName == 'IFRAME') {
109
111
  // let frameClientId = element.contentDocument.defaultView.CoCreateSockets.id;
110
112
  // if (frameClientId == selection.socketId) continue;
111
- domTextEditor = element.contentDocument.documentElement;
113
+ element = element.contentDocument.documentElement;
112
114
  }
113
- if (!domTextEditor.htmlString)
115
+ if (!element.htmlString)
114
116
  continue
115
- let pos = getElementPosition(domTextEditor.htmlString, start, end);
116
- if (pos.start) {
117
- if (pos.path)
118
- element = domTextEditor.querySelector(pos.path);
119
- let endPos = end - start;
120
- if (endPos > 0)
121
- end = pos.start + endPos;
122
- else
123
- end = pos.start;
124
- start = pos.start;
125
- }
117
+ // let pos = getElementPosition(domTextEditor.htmlString, start, end);
118
+
119
+ // if (pos.path) {
120
+ // element = domTextEditor.querySelector(pos.path);
121
+ // if (pos.start) {
122
+ // let endPos = end - start;
123
+ // if (endPos > 0)
124
+ // end = pos.start + endPos;
125
+ // else
126
+ // end = pos.start;
127
+ // start = pos.start;
128
+ // }
129
+ // }
126
130
  }
127
131
 
128
132
  if (!element) continue;
@@ -150,10 +154,10 @@ function drawCursors(selection) {
150
154
 
151
155
  _initEvents(element);
152
156
  initDocument(document);
153
- }
154
- else
157
+ } else
155
158
  mirrorDiv = document.getElementById(id_mirror);
156
159
 
160
+
157
161
  let computed = getComputedStyle(element);
158
162
  let rect = element.getBoundingClientRect();
159
163
 
@@ -166,16 +170,15 @@ function drawCursors(selection) {
166
170
  style.position = 'absolute';
167
171
  style.width = rect.width + 'px';
168
172
  style.height = rect.height + 'px';
173
+ style.margin = '0px';
174
+ style.borderColor = 'transparent';
175
+ style.outlineColor = 'transparent';
176
+
169
177
  style.overflowX = computed['overflowX'];
170
178
  style.overflowY = computed['overflowY'];
171
- style.margin = '0px';
172
179
  style.padding = computed['padding'];
173
180
  style.border = computed['border'];
174
- style.borderColor = 'transparent';
175
181
  style.outline = computed['outline'];
176
- style.outlineColor = 'transparent';
177
- style.boxSizing = computed['boxSizing'];
178
- style.lineHeight = computed['lineHeight'];
179
182
 
180
183
  if (element.parentElement.style['display'] == 'inline') {
181
184
  style.top = element.clientTop + 'px';
@@ -185,56 +188,97 @@ function drawCursors(selection) {
185
188
  style.left = element.offsetLeft + 'px';
186
189
  }
187
190
 
188
- if (element.nodeName !== 'INPUT') {
189
- style.wordWrap = 'break-word';
190
- style.whiteSpace = 'pre-wrap';
191
+ if (contenteditable) {
192
+ const actualStyles = getActualStyles(element);
193
+ for (const [prop, value] of Object.entries(actualStyles)) {
194
+ style[prop] = value;
195
+ }
191
196
  } else {
192
- style.whiteSpace = 'pre';
193
- }
194
-
195
- let properties = ['fontStyle', 'fontVariant', 'fontWeight', 'fontStretch', 'fontSize', 'fontFamily', 'letterSpacing', 'lineHeight', 'textAlign', 'textTransform', 'textIndent', 'textDecoration', 'textRendering', 'textTransform', 'textIndent', 'overflowWrap', 'tabSize', 'webkitWritingMode', 'wordSpacing'];
196
-
197
- properties.forEach(function (prop) {
198
- if (['left', 'top'].indexOf(prop.toLowerCase()) === -1)
197
+ let properties = ['boxSizing', 'fontStyle', 'fontVariant', 'fontWeight', 'fontStretch', 'fontSize', 'fontFamily', 'letterSpacing', 'lineHeight', 'textAlign', 'textTransform', 'textIndent', 'textDecoration', 'textRendering', 'textTransform', 'textIndent', 'overflowWrap', 'tabSize', 'webkitWritingMode', 'whiteSpace', 'wordSpacing'];
198
+ properties.forEach(function (prop) {
199
199
  style[prop] = computed[prop];
200
- });
200
+ });
201
201
 
202
- let spanText = mirrorDiv.querySelector('span-text');
203
- if (!spanText)
204
- spanText = document.createElement('span-text');
205
- spanText.style['display'] = 'block';
206
- let value_element = (['TEXTAREA', 'INPUT'].indexOf(element.nodeName) == -1) ? element.innerHTML : element.value;
202
+ let paddingBottom = parseFloat(computed['padding-bottom'].replace('px', ''))
203
+ style.height = rect.height - paddingBottom + 'px';
204
+
205
+ if (element.nodeName === 'INPUT')
206
+ style.whiteSpace = 'pre';
207
+ }
207
208
 
208
- spanText.textContent = value_element;
209
- if (element.nodeName === 'INPUT')
210
- spanText.textContent = mirrorDiv.textContent.replace(/\s/g, " ");
211
- mirrorDiv.appendChild(spanText);
212
209
  let cursor, cursorFlag;
213
210
  let details = { array, object, key };
214
211
  if (socket_id) {
215
- let userSelection = mirrorDiv.querySelector(`selection[socket_id="${socket_id}"]`);
216
- if (userSelection && userSelection.details != details)
217
- userSelection.remove();
218
212
  let selection_user = mirrorDiv.querySelector(`selection[socket_id="${socket_id}"]`);
219
- if (!selection_user) {
220
- selection_user = document.createElement('selection');
221
- selection_user.setAttribute('socket_id', socket_id);
222
- selection_user.style["position"] = "absolute";
223
- let style_mirror = getComputedStyle(mirrorDiv);
224
- selection_user.style["top"] = style_mirror.paddingTop;
225
- selection_user.style["left"] = style_mirror.paddingLeft;
226
- selection_user.style["width"] = mirrorDiv.clientWidth - parseInt(computed['padding-left']) - parseInt(computed['padding-right']) - scrollBarWidth + 'px';
227
- selection_user.details = { array, object, key };
228
-
229
- mirrorDiv.appendChild(selection_user);
230
- }
213
+ if (selection_user && selection_user.details != details)
214
+ selection_user.remove();
215
+
216
+ selection_user = document.createElement('selection');
217
+ selection_user.setAttribute('socket_id', socket_id);
218
+ selection_user.style["position"] = "absolute";
219
+ let style_mirror = getComputedStyle(mirrorDiv);
220
+ selection_user.style["top"] = style_mirror.paddingTop;
221
+ selection_user.style["left"] = style_mirror.paddingLeft;
222
+ selection_user.style["width"] = mirrorDiv.clientWidth - parseInt(computed['padding-left']) - parseInt(computed['padding-right']) - scrollBarWidth + 'px';
223
+ selection_user.details = { array, object, key };
231
224
 
232
225
  let value_element = (['TEXTAREA', 'INPUT'].indexOf(element.nodeName) == -1) ? element.innerHTML : element.value;
233
- selection_user.textContent = value_element.substring(0, start);
226
+ if (contenteditable) {
227
+ selection_user.innerHTML = value_element.substring(0, start);
228
+ } else
229
+ selection_user.textContent = value_element.substring(0, start);
230
+
231
+ let selectionElement
232
+ if (contenteditable) {
233
+ selectionElement = getElementPosition(element.htmlString, start, end);
234
+ }
235
+
236
+ let lastChildTagName, lastChildElement
237
+ if (selectionElement && selectionElement.element && selectionElement.position !== 'afterend') {
238
+ lastChildElement = selection_user.lastElementChild;
239
+ lastChildTagName = lastChildElement.tagName.toLowerCase()
240
+ }
241
+
242
+ let selectedText = document.createElement('selected-text');
243
+ selectedText.style["backgroundColor"] = selection.background;
244
+
245
+ let setCursorInLastChild = false
246
+ if (contenteditable) {
247
+ if (lastChildTagName && start !== end) {
248
+ let selectedTextFrag = document.createElement('selected-text');
249
+ selectedTextFrag.style["backgroundColor"] = selection.background;
250
+
251
+ const closingTagRegex = new RegExp("</\\s*" + lastChildTagName + "\\s*>", "i");
252
+ let secondHalfString = value_element.substring(start, end) || '';
253
+ const closingTagMatch = secondHalfString.match(closingTagRegex);
254
+ // console.log('secondHalfString', secondHalfString)
255
+
256
+ let closingTagEnd
257
+ if (closingTagMatch) {
258
+ closingTagEnd = closingTagMatch.index + closingTagMatch[0].length;
259
+ // console.log('closingTagEnd', closingTagEnd)
260
+
261
+ selectedTextFrag.innerHTML = secondHalfString.substring(0, closingTagEnd) || '';
262
+ lastChildElement.appendChild(selectedTextFrag);
263
+ // console.log('selectedTextFrag', selectedTextFrag)
264
+
265
+ selectedText.innerHTML = value_element.substring(start + closingTagEnd, end) || '';
266
+ // console.log('selectedText', selectedText)
267
+
268
+ } else {
269
+ selectedText.innerHTML = value_element.substring(start, end) || '';
270
+ setCursorInLastChild = true
271
+ }
272
+ } else {
273
+ if (lastChildTagName)
274
+ setCursorInLastChild = true
275
+
276
+ selectedText.innerHTML = value_element.substring(start, end) || '';
277
+ }
278
+ } else {
279
+ selectedText.textContent = value_element.substring(start, end) || '';
280
+ }
234
281
 
235
- let value_end = value_element.substring(end) || '';
236
- let span_end = document.createElement('span-end');
237
- span_end.textContent = value_end;
238
282
 
239
283
  cursor = document.createElement('cursor');
240
284
  cursor.setAttribute('socket_id', socket_id);
@@ -245,13 +289,7 @@ function drawCursors(selection) {
245
289
  cursor.setAttribute('user-color', selection.color);
246
290
  cursor.style["background"] = selection.background;
247
291
  cursor.details = { array, object, key };
248
- cursor.textContent = "1";
249
- span_end.prepend(cursor);
250
-
251
- let selectedText = document.createElement('selected-text');
252
- selectedText.style["backgroundColor"] = selection.background;
253
- selectedText.textContent = value_element.substring(start, end) || '';
254
- span_end.prepend(selectedText);
292
+ cursor.textContent = " ";
255
293
 
256
294
  cursorFlag = document.createElement('cursor-flag');
257
295
  cursorFlag.setAttribute('array', 'users');
@@ -261,12 +299,57 @@ function drawCursors(selection) {
261
299
  cursorFlag.innerHTML = selection.userName;
262
300
  cursor.appendChild(cursorFlag);
263
301
 
302
+ if (setCursorInLastChild) {
303
+ lastChildElement.appendChild(selectedText);
304
+ lastChildElement.appendChild(cursor);
305
+ } else {
306
+ selection_user.appendChild(selectedText);
307
+ selection_user.appendChild(cursor);
308
+ }
309
+
310
+ let span_end = document.createElement('span-end');
311
+ let value_end = value_element.substring(end) || '';
312
+ if (contenteditable)
313
+ span_end.innerHTML = value_end
314
+ else
315
+ span_end.textContent = value_end
316
+
264
317
  selection_user.appendChild(span_end);
318
+
319
+ mirrorDiv.appendChild(selection_user);
265
320
  }
266
321
  scrollMirror(element);
267
322
  }
268
323
  }
269
324
 
325
+ function getActualStyles(element) {
326
+ const actualStyles = {};
327
+
328
+ // Copy styles that are directly applied to the element (inline styles)
329
+ for (const prop in element.style) {
330
+ if (element.style[prop]) {
331
+ actualStyles[prop] = element.style[prop];
332
+ }
333
+ }
334
+
335
+ // Get styles from classes, IDs, attributes, and tag names
336
+ for (const sheet of document.styleSheets) {
337
+ try {
338
+ for (const rule of sheet.cssRules) {
339
+ if (element.matches(rule.selectorText)) {
340
+ for (const prop of rule.style) {
341
+ actualStyles[prop] = rule.style.getPropertyValue(prop);
342
+ }
343
+ }
344
+ }
345
+ } catch (e) {
346
+ console.warn("Unable to access stylesheet:", e);
347
+ }
348
+ }
349
+
350
+ return actualStyles;
351
+ }
352
+
270
353
  function updateCursors(element) {
271
354
  let mirrorDivs = [];
272
355
  let id_mirror = element.getAttribute('mirror_id');
@@ -274,10 +357,10 @@ function updateCursors(element) {
274
357
  let mirrorDiv = element.ownerDocument.getElementById(id_mirror);
275
358
  if (mirrorDiv)
276
359
  mirrorDivs = [mirrorDiv];
277
- }
278
- else {
360
+ } else {
279
361
  mirrorDivs = element.querySelectorAll('.mirror');
280
362
  }
363
+
281
364
  for (let mirrorDiv of mirrorDivs) {
282
365
  element = mirrorDiv.element;
283
366
  let cursors = mirrorDiv.querySelectorAll('cursor');
@@ -368,6 +451,7 @@ socket.listen('cursor', function (response) {
368
451
  // if (socket.has(selection.socketId)) return;
369
452
  let selection = response.data
370
453
  selection.socketId = response.socketId
454
+ selection.frameId = response.frameId
371
455
  if (selection.start != null && selection.end != null)
372
456
  drawCursors(selection);
373
457
  else