@cocreate/cursors 1.23.5 → 1.24.1

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,25 @@
1
+ ## [1.24.1](https://github.com/CoCreate-app/CoCreate-cursors/compare/v1.24.0...v1.24.1) (2025-04-11)
2
+
3
+
4
+ ### Bug Fixes
5
+
6
+ * update observer observe param to type and and attributeName to attributeFilter ([924d448](https://github.com/CoCreate-app/CoCreate-cursors/commit/924d448b2e575cefc01aead3640f53e7095b34f0))
7
+
8
+ # [1.24.0](https://github.com/CoCreate-app/CoCreate-cursors/compare/v1.23.5...v1.24.0) (2024-11-04)
9
+
10
+
11
+ ### Bug Fixes
12
+
13
+ * frameELement handlning ([d6e22b2](https://github.com/CoCreate-app/CoCreate-cursors/commit/d6e22b2629f6cbbdee62d74737e1a167cecad371))
14
+ * mirror_id specificity ([af25adb](https://github.com/CoCreate-app/CoCreate-cursors/commit/af25adbe2399534c95dec3b22d0c9c135eeff945))
15
+ * observer taget has been renamed to selector ([69a331b](https://github.com/CoCreate-app/CoCreate-cursors/commit/69a331bde18eb94ff038e025cb3bf2a59a33dc8d))
16
+ * pretier.config.js and file formating ([0424309](https://github.com/CoCreate-app/CoCreate-cursors/commit/042430940a6fc0fd3bdaa758de39058f4cf4b7d3))
17
+
18
+
19
+ ### Features
20
+
21
+ * add prettier.config.js and format files ([592805d](https://github.com/CoCreate-app/CoCreate-cursors/commit/592805d42049e75c5ffa8a6ef74ef64960bd0be3))
22
+
1
23
  ## [1.23.5](https://github.com/CoCreate-app/CoCreate-cursors/compare/v1.23.4...v1.23.5) (2024-06-12)
2
24
 
3
25
 
@@ -0,0 +1,168 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8" />
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
+ <style>
7
+ .highlight {
8
+ position: absolute;
9
+ background-color: yellow;
10
+ opacity: 0.2;
11
+ pointer-events: none;
12
+ z-index: 1000;
13
+ }
14
+ </style>
15
+ <title>Highlight Selected Text</title>
16
+ </head>
17
+ <body>
18
+ <div class="markdown-body padding:0_20px width:100%">
19
+ <h2>Introduction</h2>
20
+ <p>
21
+ Choosing a career is one of the most significant decisions we
22
+ face in our lives. It's not just about picking a job—it's about
23
+ finding a path that aligns with our passions, strengths, and
24
+ values. However, with so many options and paths available, the
25
+ process can be overwhelming. In this blog post, we'll delve into
26
+ strategies for finding your career path, from self-reflection to
27
+ taking concrete action.
28
+ </p>
29
+ <h2>Self-Reflection: Know Thyself</h2>
30
+ <h3>Assess Your Interests and Skills</h3>
31
+ <p>
32
+ Start by asking yourself what you enjoy doing. What are your
33
+ hobbies? What subjects do you find fascinating? Also, consider
34
+ your skills. Are there tasks you excel at or that come naturally
35
+ to you? Make a list of your interests and skills to guide your
36
+ career exploration.
37
+ </p>
38
+ <h3>Understand Your Values</h3>
39
+ <p>
40
+ Your values are the principles that matter most to you in life.
41
+ Do you value work-life balance, or is career progression your
42
+ top priority? Do you want to make a difference in the world, or
43
+ is financial security more important? Knowing what you value can
44
+ help you find careers that align with your principles.
45
+ </p>
46
+ <h3>Consider Your Personality</h3>
47
+ <p>
48
+ Personality can play a big role in job satisfaction. Are you an
49
+ extrovert who thrives in social environments, or are you more
50
+ introverted and prefer solitary work? Personality assessments,
51
+ like the Myers-Briggs Type Indicator or the Big Five, can
52
+ provide insights into careers that might suit you.
53
+ </p>
54
+ <h2>Exploration: Broaden Your Horizons</h2>
55
+ <h3>Research Potential Careers</h3>
56
+ <p>
57
+ Once you have a better understanding of your interests, skills,
58
+ and values, start researching careers that align with them. Look
59
+ into job descriptions, required qualifications, and potential
60
+ career paths. Informational interviews with professionals in the
61
+ field can also provide valuable insights.
62
+ </p>
63
+ <h3>Get Experience</h3>
64
+ <p>
65
+ Internships, part-time jobs, and volunteer opportunities are
66
+ excellent ways to gain experience and learn more about different
67
+ careers. These experiences can help you understand what you like
68
+ and don't like in a work environment.
69
+ </p>
70
+ <h3>Education and Training</h3>
71
+ <p>
72
+ Sometimes, the career you're interested in requires specific
73
+ education or training. Research the necessary qualifications and
74
+ consider whether you're willing to undertake the required
75
+ education or certification processes.
76
+ </p>
77
+ <h2>Evaluating Options: Making the Choice</h2>
78
+ <h3>Pros and Cons</h3>
79
+ <p>
80
+ For each career option you're considering, list the pros and
81
+ cons. This can help you visualize which career aligns best with
82
+ your interests, skills, and values.
83
+ </p>
84
+ <h3>Long-Term Outlook</h3>
85
+ <p>
86
+ Consider the long-term prospects of each career. Is the field
87
+ growing, or is it in decline? Will there be opportunities for
88
+ advancement? A career with a positive outlook can offer more
89
+ stability and growth potential.
90
+ </p>
91
+ <h3>Work Environment</h3>
92
+ <p>
93
+ Think about the type of work environment you thrive in. Do you
94
+ prefer a structured corporate setting, a creative agency, or
95
+ perhaps the flexibility of freelancing? Make sure your chosen
96
+ career can provide the environment you're looking for.
97
+ </p>
98
+ <h2>Taking Action: Moving Forward</h2>
99
+ <h3>Set Goals</h3>
100
+ <p>
101
+ Once you've decided on a career path, set short-term and
102
+ long-term goals. What steps do you need to take to enter your
103
+ chosen field? This could include further education, networking,
104
+ or gaining more experience.
105
+ </p>
106
+ <h3>Create a Plan</h3>
107
+ <p>
108
+ Develop a detailed plan of how you'll reach your goals. Break
109
+ down your plan into manageable steps, and set deadlines to keep
110
+ yourself accountable.
111
+ </p>
112
+ <h3>Stay Flexible</h3>
113
+ <p>
114
+ Remember that career paths can change. Stay open to new
115
+ opportunities, and don't be afraid to pivot if your interests or
116
+ circumstances change.
117
+ </p>
118
+ <h2>Conclusion</h2>
119
+ <p>
120
+ Choosing a career is a journey that requires introspection,
121
+ exploration, and a willingness to adapt. By understanding
122
+ yourself, researching options, evaluating potential paths, and
123
+ taking action, you can find a career that is fulfilling and
124
+ rewarding. Remember that it's okay to change directions, and the
125
+ most important thing is to find a path that feels right for you.
126
+ </p>
127
+
128
+ <input value="hello today good sir" />
129
+ </div>
130
+ <script>
131
+ document.addEventListener("selectionchange", () => {
132
+ // Remove existing highlights
133
+ document
134
+ .querySelectorAll(".highlight")
135
+ .forEach((el) => el.remove());
136
+
137
+ // Get the selection object
138
+ const selection = window.getSelection();
139
+
140
+ // Check if there's any text selected
141
+ if (!selection.isCollapsed) {
142
+ // Iterate over all ranges in the selection
143
+ for (let i = 0; i < selection.rangeCount; i++) {
144
+ const range = selection.getRangeAt(i);
145
+ const rects = range.getClientRects();
146
+
147
+ // Iterate over all rects in the current range
148
+ for (let rect of rects) {
149
+ const highlightSpan = document.createElement("div");
150
+ highlightSpan.className = "highlight";
151
+
152
+ highlightSpan.style.left = `${
153
+ rect.left + window.scrollX
154
+ }px`;
155
+ highlightSpan.style.top = `${
156
+ rect.top + window.scrollY
157
+ }px`;
158
+ highlightSpan.style.width = `${rect.width}px`;
159
+ highlightSpan.style.height = `${rect.height}px`;
160
+
161
+ document.body.appendChild(highlightSpan);
162
+ }
163
+ }
164
+ }
165
+ });
166
+ </script>
167
+ </body>
168
+ </html>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cocreate/cursors",
3
- "version": "1.23.5",
3
+ "version": "1.24.1",
4
4
  "description": "Collaborative user cursor position for inputs, textarea's and contenteditable elements.",
5
5
  "keywords": [
6
6
  "cursors",
@@ -0,0 +1,16 @@
1
+ module.exports = {
2
+ tabWidth: 4,
3
+ semi: true,
4
+ trailingComma: "none",
5
+ bracketSameLine: true,
6
+ useTabs: true,
7
+ overrides: [
8
+ {
9
+ files: ["*.json", "*.yml", "*.yaml"],
10
+ options: {
11
+ tabWidth: 2,
12
+ useTabs: false
13
+ },
14
+ }
15
+ ],
16
+ };
package/src/index.css CHANGED
@@ -45,7 +45,7 @@ selected-text {
45
45
  width: 0px;
46
46
  }
47
47
 
48
- [mirror_id][contenteditable] {
48
+ .mirror[mirror_id][contenteditable] {
49
49
  white-space: pre-wrap
50
50
  }
51
51
 
package/src/index.js CHANGED
@@ -21,14 +21,14 @@
21
21
  // For details, visit <https://cocreate.app/licenses/> or contact us at sales@cocreate.app.
22
22
 
23
23
  /*globals ResizeObserver*/
24
- import observer from '@cocreate/observer';
25
- import { getAttributeNames } from '@cocreate/utils';
26
- import socket from '@cocreate/socket-client';
27
- import uuid from '@cocreate/uuid';
28
- import localStorage from '@cocreate/local-storage';
29
- import { getElementPosition } from '@cocreate/selection';
30
- import { randomColor } from '@cocreate/random-color';
31
- import './index.css';
24
+ import observer from "@cocreate/observer";
25
+ import { getAttributeNames } from "@cocreate/utils";
26
+ import socket from "@cocreate/socket-client";
27
+ import uuid from "@cocreate/uuid";
28
+ import localStorage from "@cocreate/local-storage";
29
+ import { getElementPosition } from "@cocreate/selection";
30
+ import { randomColor } from "@cocreate/random-color";
31
+ import "./index.css";
32
32
 
33
33
  const clientId = socket.clientId || uuid.generate(12);
34
34
  const cursorBackground = randomColor();
@@ -38,442 +38,534 @@ let environment_prod = true;
38
38
  let selector = "[array][object][key]:not([contentEditable='false'])";
39
39
 
40
40
  function init() {
41
- let elements = document.querySelectorAll(selector);
42
- initElements(elements);
41
+ let elements = document.querySelectorAll(selector);
42
+ initElements(elements);
43
43
  }
44
44
 
45
45
  function initElements(elements) {
46
- for (let element of elements)
47
- initElement(element);
46
+ for (let element of elements) initElement(element);
48
47
  }
49
48
 
50
49
  function initElement(element) {
51
- let realtime = element.getAttribute('realtime');
52
- let cursors = element.getAttribute('cursors');
53
- if (!realtime || realtime === 'false' || cursors === 'false') return;
54
- if (element.hasAttribute('actions')) return;
55
- if (element.cursor) return;
56
- element.cursor = true;
50
+ let realtime = element.getAttribute("realtime");
51
+ let cursors = element.getAttribute("cursors");
52
+ if (!realtime || realtime === "false" || cursors === "false") return;
53
+ if (element.hasAttribute("actions")) return;
54
+ if (element.cursor) return;
55
+ element.cursor = true;
57
56
  }
58
57
 
59
58
  function initDocument(doc) {
60
- let documents;
61
- try {
62
- documents = window.top.cursorDocuments;
63
- } catch (e) {
64
- console.log('cross-origin failed')
65
- }
66
-
67
- if (!documents) {
68
- documents = new Map();
69
- try {
70
- window.top.cursorDocuments = documents;
71
- } catch (e) {
72
- console.log('cross-origin failed')
73
- }
74
- }
75
- if (!documents.has(doc)) {
76
- initResizeObserver(doc.documentElement);
77
- documents.set(doc);
78
- }
59
+ let documents;
60
+ try {
61
+ documents = window.top.cursorDocuments;
62
+ } catch (e) {
63
+ console.log("cross-origin failed");
64
+ }
65
+
66
+ if (!documents) {
67
+ documents = new Map();
68
+ try {
69
+ window.top.cursorDocuments = documents;
70
+ } catch (e) {
71
+ console.log("cross-origin failed");
72
+ }
73
+ }
74
+ if (!documents.has(doc)) {
75
+ initResizeObserver(doc.documentElement);
76
+ documents.set(doc);
77
+ }
79
78
  }
80
79
 
81
80
  function drawCursors(selection) {
82
- const socket_id = selection['socketId'];
83
- const array = selection['array'];
84
- const object = selection['object'];
85
- const key = selection['key'];
86
- let elements = selection.element;
87
- if (!elements) {
88
- let selector = '[array="' + array + '"][object="' + object + '"][key="' + key + '"]';
89
- selector += ':not([actions])';
90
- elements = document.querySelectorAll(selector);
91
- }
92
- for (let element of elements) {
93
- let start = selection['start']
94
- let end = selection['end']
95
- if (window.activeElement == element && socket.frameId === selection.frameId) {
96
- continue;
97
- }
98
- let realtime = element.getAttribute('realtime');
99
- let cursors = element.getAttribute('cursors');
100
- let contenteditable = element.getAttribute('contenteditable');
101
- if (!realtime || realtime == 'false' || cursors == 'false') continue;
102
- if (element.tagName != 'INPUT' && element.tagName != 'TEXTAREA')
103
- if (contenteditable == 'false' || contenteditable === null) {
104
- if (cursors != 'true') continue;
105
- }
106
-
107
- if (element.hasAttribute('contenteditable')) {
108
- contenteditable = true
109
- // let domTextEditor = element;
110
- if (element.tagName == 'IFRAME') {
111
- // let frameClientId = element.contentDocument.defaultView.CoCreateSockets.id;
112
- // if (frameClientId == selection.socketId) continue;
113
- element = element.contentDocument.documentElement;
114
- }
115
- if (!element.htmlString)
116
- continue
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
- // }
130
- }
131
-
132
- if (!element) continue;
133
- let document = element.ownerDocument;
134
- let mirrorDiv;
135
- let id_mirror = element.getAttribute('mirror_id');
136
- if (!id_mirror) {
137
- id_mirror = uuid.generate(8);
138
- element.setAttribute('mirror_id', id_mirror);
139
-
140
- mirrorDiv = document.createElement('cursor-container');
141
- mirrorDiv.id = id_mirror;
142
- mirrorDiv.className = (environment_prod) ? 'mirror mirror_scroll mirror_color' : 'mirror mirror_scroll';
143
- mirrorDiv.contentEditable = false;
144
- mirrorDiv.element = element;
145
- element.insertAdjacentElement('afterend', mirrorDiv);
146
- // let parent = element.parentElement;
147
- // let parentComputed = getComputedStyle(parent);
148
- // let parentStylePosition = parentComputed['position'];
149
- // if (parentStylePosition == 'static')
150
- // parent.style.position = 'relative';
151
- // let parentStyleDisplay = parentComputed['display'];
152
- // if (parentStyleDisplay == 'inline')
153
- // parent.style.display = 'inline';
154
-
155
- _initEvents(element);
156
- initDocument(document);
157
- } else
158
- mirrorDiv = document.getElementById(id_mirror);
159
-
160
-
161
- let computed = getComputedStyle(element);
162
- let rect = element.getBoundingClientRect();
163
-
164
- let borderWidth = parseInt(computed['border-width']) * 2;
165
- let scrollBarWidth = 0;
166
- if (element.clientWidth != 0)
167
- scrollBarWidth = element.offsetWidth - element.clientWidth - borderWidth;
168
-
169
- let style = mirrorDiv.style;
170
- style.position = 'absolute';
171
- style.width = rect.width + 'px';
172
- style.height = rect.height + 'px';
173
- style.margin = '0px';
174
- style.borderColor = 'transparent';
175
- style.outlineColor = 'transparent';
176
-
177
- style.overflowX = computed['overflowX'];
178
- style.overflowY = computed['overflowY'];
179
- style.padding = computed['padding'];
180
- style.border = computed['border'];
181
- style.outline = computed['outline'];
182
-
183
- if (element.parentElement.style['display'] == 'inline') {
184
- style.top = element.clientTop + 'px';
185
- style.left = element.clientLeft + 'px';
186
- } else {
187
- style.top = element.offsetTop + 'px';
188
- style.left = element.offsetLeft + 'px';
189
- }
190
-
191
- if (contenteditable) {
192
- const actualStyles = getActualStyles(element);
193
- for (const [prop, value] of Object.entries(actualStyles)) {
194
- style[prop] = value;
195
- }
196
- } else {
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
- style[prop] = computed[prop];
200
- });
201
-
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
- }
208
-
209
- let cursor, cursorFlag;
210
- let details = { array, object, key };
211
- if (socket_id) {
212
- let selection_user = mirrorDiv.querySelector(`selection[socket_id="${socket_id}"]`);
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 };
224
-
225
- let value_element = (['TEXTAREA', 'INPUT'].indexOf(element.nodeName) == -1) ? element.innerHTML : element.value;
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
- }
281
-
282
-
283
- cursor = document.createElement('cursor');
284
- cursor.setAttribute('socket_id', socket_id);
285
- cursor.dataset.start = start;
286
- cursor.dataset.end = end;
287
- cursor.setAttribute('user-name', selection.userName);
288
- cursor.setAttribute('user-background', selection.background);
289
- cursor.setAttribute('user-color', selection.color);
290
- cursor.style["background"] = selection.background;
291
- cursor.details = { array, object, key };
292
- cursor.textContent = " ";
293
-
294
- cursorFlag = document.createElement('cursor-flag');
295
- cursorFlag.setAttribute('array', 'users');
296
- cursorFlag.setAttribute('object', selection.user_id);
297
- cursorFlag.setAttribute('key', 'name');
298
- cursorFlag.style["background"] = selection.background;
299
- cursorFlag.innerHTML = selection.userName;
300
- cursor.appendChild(cursorFlag);
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
-
317
- selection_user.appendChild(span_end);
318
-
319
- mirrorDiv.appendChild(selection_user);
320
- }
321
- scrollMirror(element);
322
- }
81
+ const socket_id = selection["socketId"];
82
+ const array = selection["array"];
83
+ const object = selection["object"];
84
+ const key = selection["key"];
85
+ let elements = selection.element;
86
+ if (!elements) {
87
+ let selector =
88
+ '[array="' +
89
+ array +
90
+ '"][object="' +
91
+ object +
92
+ '"][key="' +
93
+ key +
94
+ '"]';
95
+ selector += ":not([actions])";
96
+ elements = document.querySelectorAll(selector);
97
+ }
98
+ for (let element of elements) {
99
+ let start = selection["start"];
100
+ let end = selection["end"];
101
+ if (
102
+ window.activeElement == element &&
103
+ socket.frameId === selection.frameId
104
+ ) {
105
+ continue;
106
+ }
107
+ let realtime = element.getAttribute("realtime");
108
+ let cursors = element.getAttribute("cursors");
109
+ let contenteditable = element.getAttribute("contenteditable");
110
+ if (!realtime || realtime == "false" || cursors == "false") continue;
111
+ if (element.tagName != "INPUT" && element.tagName != "TEXTAREA")
112
+ if (contenteditable == "false" || contenteditable === null) {
113
+ if (cursors != "true") continue;
114
+ }
115
+
116
+ let domTextEditor = element;
117
+
118
+ if (element.hasAttribute("contenteditable")) {
119
+ contenteditable = true;
120
+ // let domTextEditor = element;
121
+ if (element.tagName == "IFRAME") {
122
+ // let frameClientId = element.contentDocument.defaultView.CoCreateSockets.id;
123
+ // if (frameClientId == selection.socketId) continue;
124
+ domTextEditor = element =
125
+ element.contentDocument.documentElement;
126
+
127
+ let pos = getElementPosition(
128
+ domTextEditor.htmlString,
129
+ start,
130
+ end
131
+ );
132
+
133
+ if (pos.path) {
134
+ element = element.querySelector(pos.path);
135
+ if (pos.start) {
136
+ let endPos = end - start;
137
+ if (endPos > 0) end = pos.start + endPos;
138
+ else end = pos.start;
139
+ start = pos.start;
140
+ }
141
+ }
142
+ }
143
+
144
+ if (!domTextEditor.htmlString) continue;
145
+
146
+ // let pos = getElementPosition(domTextEditor.htmlString, start, end);
147
+
148
+ // if (pos.path) {
149
+ // element = domTextEditor.querySelector(pos.path);
150
+ // if (pos.start) {
151
+ // let endPos = end - start;
152
+ // if (endPos > 0)
153
+ // end = pos.start + endPos;
154
+ // else
155
+ // end = pos.start;
156
+ // start = pos.start;
157
+ // }
158
+ // }
159
+ }
160
+
161
+ if (!element) continue;
162
+ let document = element.ownerDocument;
163
+ let mirrorDiv;
164
+ let id_mirror = element.getAttribute("mirror_id");
165
+ if (!id_mirror) {
166
+ id_mirror = uuid.generate(8);
167
+ element.setAttribute("mirror_id", id_mirror);
168
+
169
+ mirrorDiv = document.createElement("cursor-container");
170
+ mirrorDiv.id = id_mirror;
171
+ mirrorDiv.className = environment_prod
172
+ ? "mirror mirror_scroll mirror_color"
173
+ : "mirror mirror_scroll";
174
+ mirrorDiv.contentEditable = false;
175
+ mirrorDiv.element = element;
176
+ if (element.tagName === "HTML")
177
+ element.insertAdjacentElement("beforeend", mirrorDiv);
178
+ else element.insertAdjacentElement("afterend", mirrorDiv);
179
+ // let parent = element.parentElement;
180
+ // let parentComputed = getComputedStyle(parent);
181
+ // let parentStylePosition = parentComputed['position'];
182
+ // if (parentStylePosition == 'static')
183
+ // parent.style.position = 'relative';
184
+ // let parentStyleDisplay = parentComputed['display'];
185
+ // if (parentStyleDisplay == 'inline')
186
+ // parent.style.display = 'inline';
187
+
188
+ _initEvents(element);
189
+ initDocument(document);
190
+ } else mirrorDiv = document.getElementById(id_mirror);
191
+
192
+ let computed = getComputedStyle(element);
193
+ let rect = element.getBoundingClientRect();
194
+
195
+ let borderWidth = parseInt(computed["border-width"]) * 2;
196
+ let scrollBarWidth = 0;
197
+ if (element.clientWidth != 0)
198
+ scrollBarWidth =
199
+ element.offsetWidth - element.clientWidth - borderWidth;
200
+
201
+ let style = mirrorDiv.style;
202
+ style.position = "absolute";
203
+ style.width = rect.width + "px";
204
+ style.height = rect.height + "px";
205
+ style.margin = "0px";
206
+ style.borderColor = "transparent";
207
+ style.outlineColor = "transparent";
208
+
209
+ style.overflowX = computed["overflowX"];
210
+ style.overflowY = computed["overflowY"];
211
+ style.padding = computed["padding"];
212
+ style.border = computed["border"];
213
+ style.outline = computed["outline"];
214
+
215
+ if (
216
+ element.parentElement &&
217
+ element.parentElement.style["display"] == "inline"
218
+ ) {
219
+ style.top = element.clientTop + "px";
220
+ style.left = element.clientLeft + "px";
221
+ } else {
222
+ style.top = element.offsetTop + "px";
223
+ style.left = element.offsetLeft + "px";
224
+ }
225
+
226
+ if (contenteditable) {
227
+ const actualStyles = getActualStyles(element);
228
+ for (const [prop, value] of Object.entries(actualStyles)) {
229
+ style[prop] = value;
230
+ }
231
+ } else {
232
+ let properties = [
233
+ "boxSizing",
234
+ "fontStyle",
235
+ "fontVariant",
236
+ "fontWeight",
237
+ "fontStretch",
238
+ "fontSize",
239
+ "fontFamily",
240
+ "letterSpacing",
241
+ "lineHeight",
242
+ "textAlign",
243
+ "textTransform",
244
+ "textIndent",
245
+ "textDecoration",
246
+ "textRendering",
247
+ "textTransform",
248
+ "textIndent",
249
+ "overflowWrap",
250
+ "tabSize",
251
+ "webkitWritingMode",
252
+ "whiteSpace",
253
+ "wordSpacing"
254
+ ];
255
+ properties.forEach(function (prop) {
256
+ style[prop] = computed[prop];
257
+ });
258
+
259
+ let paddingBottom = parseFloat(
260
+ computed["padding-bottom"].replace("px", "")
261
+ );
262
+ style.height = rect.height - paddingBottom + "px";
263
+
264
+ if (element.nodeName === "INPUT") style.whiteSpace = "pre";
265
+ }
266
+
267
+ let cursor, cursorFlag;
268
+ let details = { array, object, key };
269
+ if (socket_id) {
270
+ let selection_user = mirrorDiv.querySelector(
271
+ `selection[socket_id="${socket_id}"]`
272
+ );
273
+ if (selection_user && selection_user.details != details)
274
+ selection_user.remove();
275
+
276
+ selection_user = document.createElement("selection");
277
+ selection_user.setAttribute("socket_id", socket_id);
278
+ selection_user.style["position"] = "absolute";
279
+ let style_mirror = getComputedStyle(mirrorDiv);
280
+ selection_user.style["top"] = style_mirror.paddingTop;
281
+ selection_user.style["left"] = style_mirror.paddingLeft;
282
+ selection_user.style["width"] =
283
+ mirrorDiv.clientWidth -
284
+ parseInt(computed["padding-left"]) -
285
+ parseInt(computed["padding-right"]) -
286
+ scrollBarWidth +
287
+ "px";
288
+ selection_user.details = { array, object, key };
289
+
290
+ let value_element =
291
+ ["TEXTAREA", "INPUT"].indexOf(element.nodeName) == -1
292
+ ? element.innerHTML
293
+ : element.value;
294
+ if (contenteditable) {
295
+ selection_user.innerHTML = value_element.substring(0, start);
296
+ } else
297
+ selection_user.textContent = value_element.substring(0, start);
298
+
299
+ let selectionElement;
300
+ if (contenteditable) {
301
+ selectionElement = getElementPosition(
302
+ domTextEditor.htmlString,
303
+ selection.start,
304
+ selection.end
305
+ );
306
+ }
307
+
308
+ let lastChildTagName, lastChildElement;
309
+ if (
310
+ selectionElement &&
311
+ selectionElement.element &&
312
+ selectionElement.position !== "afterend"
313
+ ) {
314
+ lastChildElement = selection_user.lastElementChild;
315
+ if (lastChildElement)
316
+ lastChildTagName = lastChildElement.tagName.toLowerCase();
317
+ }
318
+
319
+ let selectedText = document.createElement("selected-text");
320
+ selectedText.style["backgroundColor"] = selection.background;
321
+
322
+ let setCursorInLastChild = false;
323
+ if (contenteditable) {
324
+ if (lastChildTagName && start !== end) {
325
+ let selectedTextFrag =
326
+ document.createElement("selected-text");
327
+ selectedTextFrag.style["backgroundColor"] =
328
+ selection.background;
329
+
330
+ const closingTagRegex = new RegExp(
331
+ "</\\s*" + lastChildTagName + "\\s*>",
332
+ "i"
333
+ );
334
+ let secondHalfString =
335
+ value_element.substring(start, end) || "";
336
+ const closingTagMatch =
337
+ secondHalfString.match(closingTagRegex);
338
+ // console.log('secondHalfString', secondHalfString)
339
+
340
+ let closingTagEnd;
341
+ if (closingTagMatch) {
342
+ closingTagEnd =
343
+ closingTagMatch.index + closingTagMatch[0].length;
344
+ // console.log('closingTagEnd', closingTagEnd)
345
+
346
+ selectedTextFrag.innerHTML =
347
+ secondHalfString.substring(0, closingTagEnd) || "";
348
+ lastChildElement.appendChild(selectedTextFrag);
349
+ // console.log('selectedTextFrag', selectedTextFrag)
350
+
351
+ selectedText.innerHTML =
352
+ value_element.substring(
353
+ start + closingTagEnd,
354
+ end
355
+ ) || "";
356
+ // console.log('selectedText', selectedText)
357
+ } else {
358
+ selectedText.innerHTML =
359
+ value_element.substring(start, end) || "";
360
+ setCursorInLastChild = true;
361
+ }
362
+ } else {
363
+ if (lastChildTagName) setCursorInLastChild = true;
364
+
365
+ selectedText.innerHTML =
366
+ value_element.substring(start, end) || "";
367
+ }
368
+ } else {
369
+ selectedText.textContent =
370
+ value_element.substring(start, end) || "";
371
+ }
372
+
373
+ cursor = document.createElement("cursor");
374
+ cursor.setAttribute("socket_id", socket_id);
375
+ cursor.dataset.start = start;
376
+ cursor.dataset.end = end;
377
+ cursor.setAttribute("user-name", selection.userName);
378
+ cursor.setAttribute("user-background", selection.background);
379
+ cursor.setAttribute("user-color", selection.color);
380
+ cursor.style["background"] = selection.background;
381
+ cursor.details = { array, object, key };
382
+ cursor.textContent = " ";
383
+
384
+ cursorFlag = document.createElement("cursor-flag");
385
+ cursorFlag.setAttribute("array", "users");
386
+ cursorFlag.setAttribute("object", selection.user_id);
387
+ cursorFlag.setAttribute("key", "name");
388
+ cursorFlag.style["background"] = selection.background;
389
+ cursorFlag.innerHTML = selection.userName;
390
+ cursor.appendChild(cursorFlag);
391
+
392
+ if (setCursorInLastChild) {
393
+ lastChildElement.appendChild(selectedText);
394
+ lastChildElement.appendChild(cursor);
395
+ } else {
396
+ selection_user.appendChild(selectedText);
397
+ selection_user.appendChild(cursor);
398
+ }
399
+
400
+ let span_end = document.createElement("span-end");
401
+ let value_end = value_element.substring(end) || "";
402
+ if (contenteditable) span_end.innerHTML = value_end;
403
+ else span_end.textContent = value_end;
404
+
405
+ selection_user.appendChild(span_end);
406
+
407
+ mirrorDiv.appendChild(selection_user);
408
+ }
409
+ scrollMirror(element);
410
+ }
323
411
  }
324
412
 
325
413
  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;
414
+ const actualStyles = {};
415
+
416
+ // Copy styles that are directly applied to the element (inline styles)
417
+ for (const prop in element.style) {
418
+ if (element.style[prop]) {
419
+ actualStyles[prop] = element.style[prop];
420
+ }
421
+ }
422
+
423
+ // Get styles from classes, IDs, attributes, and tag names
424
+ for (const sheet of document.styleSheets) {
425
+ try {
426
+ for (const rule of sheet.cssRules) {
427
+ if (element.matches(rule.selectorText)) {
428
+ for (const prop of rule.style) {
429
+ actualStyles[prop] = rule.style.getPropertyValue(prop);
430
+ }
431
+ }
432
+ }
433
+ } catch (e) {
434
+ console.warn("Unable to access stylesheet:", e);
435
+ }
436
+ }
437
+
438
+ return actualStyles;
351
439
  }
352
440
 
353
441
  function updateCursors(element) {
354
- let mirrorDivs = [];
355
- let id_mirror = element.getAttribute('mirror_id');
356
- if (id_mirror) {
357
- let mirrorDiv = element.ownerDocument.getElementById(id_mirror);
358
- if (mirrorDiv)
359
- mirrorDivs = [mirrorDiv];
360
- } else {
361
- mirrorDivs = element.querySelectorAll('.mirror');
362
- }
363
-
364
- for (let mirrorDiv of mirrorDivs) {
365
- element = mirrorDiv.element;
366
- let cursors = mirrorDiv.querySelectorAll('cursor');
367
-
368
- cursors.forEach(function (child_cursor, index, array) {
369
- let socketId = child_cursor.getAttribute('socket_id');
370
- let userName = child_cursor.getAttribute('user-name');
371
- let background = child_cursor.getAttribute('user-background');
372
- let color = child_cursor.getAttribute('user-color');
373
- let start = child_cursor.getAttribute('data-start');
374
- let end = child_cursor.getAttribute('data-end');
375
- let selection = {
376
- element: [element],
377
- start,
378
- end,
379
- socketId,
380
- userName,
381
- background,
382
- color
383
- };
384
- drawCursors(selection);
385
- });
386
- }
442
+ let mirrorDivs = [];
443
+ let id_mirror = element.getAttribute("mirror_id");
444
+ if (id_mirror) {
445
+ let mirrorDiv = element.ownerDocument.getElementById(id_mirror);
446
+ if (mirrorDiv) mirrorDivs = [mirrorDiv];
447
+ } else {
448
+ mirrorDivs = element.querySelectorAll(".mirror");
449
+ }
450
+
451
+ for (let mirrorDiv of mirrorDivs) {
452
+ element = mirrorDiv.element;
453
+ let cursors = mirrorDiv.querySelectorAll("cursor");
454
+
455
+ cursors.forEach(function (child_cursor, index, array) {
456
+ let socketId = child_cursor.getAttribute("socket_id");
457
+ let userName = child_cursor.getAttribute("user-name");
458
+ let background = child_cursor.getAttribute("user-background");
459
+ let color = child_cursor.getAttribute("user-color");
460
+ let start = child_cursor.getAttribute("data-start");
461
+ let end = child_cursor.getAttribute("data-end");
462
+ let selection = {
463
+ element: [element],
464
+ start,
465
+ end,
466
+ socketId,
467
+ userName,
468
+ background,
469
+ color
470
+ };
471
+ drawCursors(selection);
472
+ });
473
+ }
387
474
  }
388
475
 
389
476
  function removeCursor(socketId) {
390
- let elements = document.querySelectorAll(`cursor[socket_id="${socketId}"]`);
391
- elements.forEach(function (element, index, array) {
392
- element.parentNode.removeChild(element);
393
- });
394
-
395
- let sel_elements = document.querySelectorAll(`selection[socket_id="${socketId}"]`);
396
- sel_elements.forEach(function (sel_element, index, array) {
397
- sel_element.parentNode.removeChild(sel_element);
398
- });
477
+ let elements = document.querySelectorAll(`cursor[socket_id="${socketId}"]`);
478
+ elements.forEach(function (element, index, array) {
479
+ element.parentNode.removeChild(element);
480
+ });
481
+
482
+ let sel_elements = document.querySelectorAll(
483
+ `selection[socket_id="${socketId}"]`
484
+ );
485
+ sel_elements.forEach(function (sel_element, index, array) {
486
+ sel_element.parentNode.removeChild(sel_element);
487
+ });
399
488
  }
400
489
 
401
490
  function _initEvents(element) {
402
- element.addEventListener('keydown', function (event) {
403
- scrollMirror(element);
404
- });
491
+ element.addEventListener("keydown", function (event) {
492
+ scrollMirror(element);
493
+ });
405
494
 
406
- element.addEventListener('scroll', function () {
407
- scrollMirror(element);
408
- });
495
+ element.addEventListener("scroll", function () {
496
+ scrollMirror(element);
497
+ });
409
498
 
410
- initResizeObserver(element);
499
+ initResizeObserver(element);
411
500
  }
412
501
 
413
502
  function scrollMirror(element) {
414
- let id_mirror = element.getAttribute('mirror_id');
415
- let elementMirror = document.getElementById(id_mirror);
416
- if (elementMirror)
417
- elementMirror.scrollTo(element.scrollLeft, element.scrollTop);
503
+ let id_mirror = element.getAttribute("mirror_id");
504
+ let elementMirror = document.getElementById(id_mirror);
505
+ if (elementMirror)
506
+ elementMirror.scrollTo(element.scrollLeft, element.scrollTop);
418
507
  }
419
508
 
420
-
421
509
  function initResizeObserver(element) {
422
- const watch = new ResizeObserver(() => updateCursors(element));
423
- watch.observe(element);
510
+ const watch = new ResizeObserver(() => updateCursors(element));
511
+ watch.observe(element);
424
512
  }
425
513
 
426
514
  function sendPosition(info) {
427
- try {
428
- socket.send({
429
- method: "cursor",
430
- data: {
431
- array: info.array,
432
- object: info.object,
433
- key: info.key,
434
- start: info.start,
435
- end: info.end,
436
- color: info.color || localStorage.getItem("cursorColor"),
437
- background: info.background || localStorage.getItem("cursorBackground") || cursorBackground,
438
- userName: info.userName || localStorage.getItem("userName") || clientId,
439
- user_id: info.user_id || localStorage.getItem("user_id") || clientId
440
- },
441
- broadcastBrowser: true
442
- });
443
- }
444
- catch (e) {
445
- console.error(e);
446
- }
515
+ try {
516
+ socket.send({
517
+ method: "cursor",
518
+ data: {
519
+ array: info.array,
520
+ object: info.object,
521
+ key: info.key,
522
+ start: info.start,
523
+ end: info.end,
524
+ color: info.color || localStorage.getItem("cursorColor"),
525
+ background:
526
+ info.background ||
527
+ localStorage.getItem("cursorBackground") ||
528
+ cursorBackground,
529
+ userName:
530
+ info.userName ||
531
+ localStorage.getItem("userName") ||
532
+ clientId,
533
+ user_id:
534
+ info.user_id || localStorage.getItem("user_id") || clientId
535
+ },
536
+ broadcastBrowser: true
537
+ });
538
+ } catch (e) {
539
+ console.error(e);
540
+ }
447
541
  }
448
542
 
449
-
450
- socket.listen('cursor', function (response) {
451
- // if (socket.has(selection.socketId)) return;
452
- let selection = response.data
453
- selection.socketId = response.socketId
454
- selection.frameId = response.frameId
455
- if (selection.start != null && selection.end != null)
456
- drawCursors(selection);
457
- else
458
- removeCursor(selection.socketId);
543
+ socket.listen("cursor", function (response) {
544
+ // if (socket.has(selection.socketId)) return;
545
+ let selection = response.data;
546
+ selection.socketId = response.socketId;
547
+ selection.frameId = response.frameId;
548
+ if (selection.start != null && selection.end != null)
549
+ drawCursors(selection);
550
+ else removeCursor(selection.socketId);
459
551
  });
460
552
 
461
553
  observer.init({
462
- name: 'CoCreateCursor',
463
- observe: ['addedNodes'],
464
- target: '[array][object][key]',
465
- callback: function (mutation) {
466
- initElement(mutation.target);
467
- }
554
+ name: "CoCreateCursor",
555
+ types: ["addedNodes"],
556
+ selector: "[array][object][key]",
557
+ callback: function (mutation) {
558
+ initElement(mutation.target);
559
+ }
468
560
  });
469
561
 
470
562
  observer.init({
471
- name: 'CoCreateCursorAtt',
472
- observe: ['attributes'],
473
- attributeName: getAttributeNames(['array', 'object', 'key']),
474
- callback: function (mutation) {
475
- initElement(mutation.target);
476
- }
563
+ name: "CoCreateCursorAtt",
564
+ types: ["attributes"],
565
+ attributeFilter: getAttributeNames(["array", "object", "key"]),
566
+ callback: function (mutation) {
567
+ initElement(mutation.target);
568
+ }
477
569
  });
478
570
 
479
571
  init();