@cocreate/cursors
Advanced tools
Comparing version 1.23.4 to 1.23.5
{ | ||
"name": "@cocreate/cursors", | ||
"version": "1.23.4", | ||
"version": "1.23.5", | ||
"description": "Collaborative user cursor position for inputs, textarea's and contenteditable elements.", | ||
@@ -62,10 +62,10 @@ "keywords": [ | ||
"dependencies": { | ||
"@cocreate/local-storage": "^1.14.3", | ||
"@cocreate/observer": "^1.16.0", | ||
"@cocreate/random-color": "^1.10.0", | ||
"@cocreate/selection": "^1.12.1", | ||
"@cocreate/socket-client": "^1.39.0", | ||
"@cocreate/utils": "^1.33.6", | ||
"@cocreate/uuid": "^1.11.1" | ||
"@cocreate/local-storage": "^1.15.0", | ||
"@cocreate/observer": "^1.16.1", | ||
"@cocreate/random-color": "^1.10.1", | ||
"@cocreate/selection": "^1.12.2", | ||
"@cocreate/socket-client": "^1.39.1", | ||
"@cocreate/utils": "^1.33.7", | ||
"@cocreate/uuid": "^1.11.2" | ||
} | ||
} |
230
src/index.js
@@ -86,12 +86,12 @@ /******************************************************************************** | ||
const key = selection['key']; | ||
let start = selection['start']; | ||
let end = selection['end']; | ||
let elements = selection.element; | ||
if (!elements) { | ||
let selector = '[array="' + array + '"][object="' + object + '"][key="' + key + '"]'; | ||
selector += ':not(.codemirror, .monaco, [actions])'; | ||
selector += ':not([actions])'; | ||
elements = document.querySelectorAll(selector); | ||
} | ||
for (let element of elements) { | ||
if (window.activeElement == element && socket.has(selection.socketId)) { | ||
let start = selection['start'] | ||
let end = selection['end'] | ||
if (window.activeElement == element && socket.frameId === selection.frameId) { | ||
continue; | ||
@@ -104,25 +104,29 @@ } | ||
if (element.tagName != 'INPUT' && element.tagName != 'TEXTAREA') | ||
if (contenteditable == 'false' || contenteditable == null) | ||
if (contenteditable == 'false' || contenteditable === null) { | ||
if (cursors != 'true') continue; | ||
} | ||
if (element.hasAttribute('contenteditable')) { | ||
let domTextEditor = element; | ||
contenteditable = true | ||
// let domTextEditor = element; | ||
if (element.tagName == 'IFRAME') { | ||
// let frameClientId = element.contentDocument.defaultView.CoCreateSockets.id; | ||
// if (frameClientId == selection.socketId) continue; | ||
domTextEditor = element.contentDocument.documentElement; | ||
element = element.contentDocument.documentElement; | ||
} | ||
if (!domTextEditor.htmlString) | ||
if (!element.htmlString) | ||
continue | ||
let pos = getElementPosition(domTextEditor.htmlString, start, end); | ||
if (pos.start) { | ||
if (pos.path) | ||
element = domTextEditor.querySelector(pos.path); | ||
let endPos = end - start; | ||
if (endPos > 0) | ||
end = pos.start + endPos; | ||
else | ||
end = pos.start; | ||
start = pos.start; | ||
} | ||
// let pos = getElementPosition(domTextEditor.htmlString, start, end); | ||
// if (pos.path) { | ||
// element = domTextEditor.querySelector(pos.path); | ||
// if (pos.start) { | ||
// let endPos = end - start; | ||
// if (endPos > 0) | ||
// end = pos.start + endPos; | ||
// else | ||
// end = pos.start; | ||
// start = pos.start; | ||
// } | ||
// } | ||
} | ||
@@ -155,6 +159,6 @@ | ||
initDocument(document); | ||
} | ||
else | ||
} else | ||
mirrorDiv = document.getElementById(id_mirror); | ||
let computed = getComputedStyle(element); | ||
@@ -172,12 +176,11 @@ let rect = element.getBoundingClientRect(); | ||
style.height = rect.height + 'px'; | ||
style.margin = '0px'; | ||
style.borderColor = 'transparent'; | ||
style.outlineColor = 'transparent'; | ||
style.overflowX = computed['overflowX']; | ||
style.overflowY = computed['overflowY']; | ||
style.margin = '0px'; | ||
style.padding = computed['padding']; | ||
style.border = computed['border']; | ||
style.borderColor = 'transparent'; | ||
style.outline = computed['outline']; | ||
style.outlineColor = 'transparent'; | ||
style.boxSizing = computed['boxSizing']; | ||
style.lineHeight = computed['lineHeight']; | ||
@@ -192,53 +195,94 @@ if (element.parentElement.style['display'] == 'inline') { | ||
if (element.nodeName !== 'INPUT') { | ||
style.wordWrap = 'break-word'; | ||
style.whiteSpace = 'pre-wrap'; | ||
if (contenteditable) { | ||
const actualStyles = getActualStyles(element); | ||
for (const [prop, value] of Object.entries(actualStyles)) { | ||
style[prop] = value; | ||
} | ||
} else { | ||
style.whiteSpace = 'pre'; | ||
} | ||
let properties = ['boxSizing', 'fontStyle', 'fontVariant', 'fontWeight', 'fontStretch', 'fontSize', 'fontFamily', 'letterSpacing', 'lineHeight', 'textAlign', 'textTransform', 'textIndent', 'textDecoration', 'textRendering', 'textTransform', 'textIndent', 'overflowWrap', 'tabSize', 'webkitWritingMode', 'whiteSpace', 'wordSpacing']; | ||
properties.forEach(function (prop) { | ||
style[prop] = computed[prop]; | ||
}); | ||
let properties = ['fontStyle', 'fontVariant', 'fontWeight', 'fontStretch', 'fontSize', 'fontFamily', 'letterSpacing', 'lineHeight', 'textAlign', 'textTransform', 'textIndent', 'textDecoration', 'textRendering', 'textTransform', 'textIndent', 'overflowWrap', 'tabSize', 'webkitWritingMode', 'wordSpacing']; | ||
let paddingBottom = parseFloat(computed['padding-bottom'].replace('px', '')) | ||
style.height = rect.height - paddingBottom + 'px'; | ||
properties.forEach(function (prop) { | ||
if (['left', 'top'].indexOf(prop.toLowerCase()) === -1) | ||
style[prop] = computed[prop]; | ||
}); | ||
if (element.nodeName === 'INPUT') | ||
style.whiteSpace = 'pre'; | ||
} | ||
let spanText = mirrorDiv.querySelector('span-text'); | ||
if (!spanText) | ||
spanText = document.createElement('span-text'); | ||
spanText.style['display'] = 'block'; | ||
let value_element = (['TEXTAREA', 'INPUT'].indexOf(element.nodeName) == -1) ? element.innerHTML : element.value; | ||
spanText.textContent = value_element; | ||
if (element.nodeName === 'INPUT') | ||
spanText.textContent = mirrorDiv.textContent.replace(/\s/g, " "); | ||
mirrorDiv.appendChild(spanText); | ||
let cursor, cursorFlag; | ||
let details = { array, object, key }; | ||
if (socket_id) { | ||
let userSelection = mirrorDiv.querySelector(`selection[socket_id="${socket_id}"]`); | ||
if (userSelection && userSelection.details != details) | ||
userSelection.remove(); | ||
let selection_user = mirrorDiv.querySelector(`selection[socket_id="${socket_id}"]`); | ||
if (!selection_user) { | ||
selection_user = document.createElement('selection'); | ||
selection_user.setAttribute('socket_id', socket_id); | ||
selection_user.style["position"] = "absolute"; | ||
let style_mirror = getComputedStyle(mirrorDiv); | ||
selection_user.style["top"] = style_mirror.paddingTop; | ||
selection_user.style["left"] = style_mirror.paddingLeft; | ||
selection_user.style["width"] = mirrorDiv.clientWidth - parseInt(computed['padding-left']) - parseInt(computed['padding-right']) - scrollBarWidth + 'px'; | ||
selection_user.details = { array, object, key }; | ||
if (selection_user && selection_user.details != details) | ||
selection_user.remove(); | ||
mirrorDiv.appendChild(selection_user); | ||
} | ||
selection_user = document.createElement('selection'); | ||
selection_user.setAttribute('socket_id', socket_id); | ||
selection_user.style["position"] = "absolute"; | ||
let style_mirror = getComputedStyle(mirrorDiv); | ||
selection_user.style["top"] = style_mirror.paddingTop; | ||
selection_user.style["left"] = style_mirror.paddingLeft; | ||
selection_user.style["width"] = mirrorDiv.clientWidth - parseInt(computed['padding-left']) - parseInt(computed['padding-right']) - scrollBarWidth + 'px'; | ||
selection_user.details = { array, object, key }; | ||
let value_element = (['TEXTAREA', 'INPUT'].indexOf(element.nodeName) == -1) ? element.innerHTML : element.value; | ||
selection_user.textContent = value_element.substring(0, start); | ||
if (contenteditable) { | ||
selection_user.innerHTML = value_element.substring(0, start); | ||
} else | ||
selection_user.textContent = value_element.substring(0, start); | ||
let value_end = value_element.substring(end) || ''; | ||
let span_end = document.createElement('span-end'); | ||
span_end.textContent = value_end; | ||
let selectionElement | ||
if (contenteditable) { | ||
selectionElement = getElementPosition(element.htmlString, start, end); | ||
} | ||
let lastChildTagName, lastChildElement | ||
if (selectionElement && selectionElement.element && selectionElement.position !== 'afterend') { | ||
lastChildElement = selection_user.lastElementChild; | ||
lastChildTagName = lastChildElement.tagName.toLowerCase() | ||
} | ||
let selectedText = document.createElement('selected-text'); | ||
selectedText.style["backgroundColor"] = selection.background; | ||
let setCursorInLastChild = false | ||
if (contenteditable) { | ||
if (lastChildTagName && start !== end) { | ||
let selectedTextFrag = document.createElement('selected-text'); | ||
selectedTextFrag.style["backgroundColor"] = selection.background; | ||
const closingTagRegex = new RegExp("</\\s*" + lastChildTagName + "\\s*>", "i"); | ||
let secondHalfString = value_element.substring(start, end) || ''; | ||
const closingTagMatch = secondHalfString.match(closingTagRegex); | ||
// console.log('secondHalfString', secondHalfString) | ||
let closingTagEnd | ||
if (closingTagMatch) { | ||
closingTagEnd = closingTagMatch.index + closingTagMatch[0].length; | ||
// console.log('closingTagEnd', closingTagEnd) | ||
selectedTextFrag.innerHTML = secondHalfString.substring(0, closingTagEnd) || ''; | ||
lastChildElement.appendChild(selectedTextFrag); | ||
// console.log('selectedTextFrag', selectedTextFrag) | ||
selectedText.innerHTML = value_element.substring(start + closingTagEnd, end) || ''; | ||
// console.log('selectedText', selectedText) | ||
} else { | ||
selectedText.innerHTML = value_element.substring(start, end) || ''; | ||
setCursorInLastChild = true | ||
} | ||
} else { | ||
if (lastChildTagName) | ||
setCursorInLastChild = true | ||
selectedText.innerHTML = value_element.substring(start, end) || ''; | ||
} | ||
} else { | ||
selectedText.textContent = value_element.substring(start, end) || ''; | ||
} | ||
cursor = document.createElement('cursor'); | ||
@@ -253,10 +297,4 @@ cursor.setAttribute('socket_id', socket_id); | ||
cursor.details = { array, object, key }; | ||
cursor.textContent = "1"; | ||
span_end.prepend(cursor); | ||
cursor.textContent = " "; | ||
let selectedText = document.createElement('selected-text'); | ||
selectedText.style["backgroundColor"] = selection.background; | ||
selectedText.textContent = value_element.substring(start, end) || ''; | ||
span_end.prepend(selectedText); | ||
cursorFlag = document.createElement('cursor-flag'); | ||
@@ -270,3 +308,20 @@ cursorFlag.setAttribute('array', 'users'); | ||
if (setCursorInLastChild) { | ||
lastChildElement.appendChild(selectedText); | ||
lastChildElement.appendChild(cursor); | ||
} else { | ||
selection_user.appendChild(selectedText); | ||
selection_user.appendChild(cursor); | ||
} | ||
let span_end = document.createElement('span-end'); | ||
let value_end = value_element.substring(end) || ''; | ||
if (contenteditable) | ||
span_end.innerHTML = value_end | ||
else | ||
span_end.textContent = value_end | ||
selection_user.appendChild(span_end); | ||
mirrorDiv.appendChild(selection_user); | ||
} | ||
@@ -277,2 +332,30 @@ scrollMirror(element); | ||
function getActualStyles(element) { | ||
const actualStyles = {}; | ||
// Copy styles that are directly applied to the element (inline styles) | ||
for (const prop in element.style) { | ||
if (element.style[prop]) { | ||
actualStyles[prop] = element.style[prop]; | ||
} | ||
} | ||
// Get styles from classes, IDs, attributes, and tag names | ||
for (const sheet of document.styleSheets) { | ||
try { | ||
for (const rule of sheet.cssRules) { | ||
if (element.matches(rule.selectorText)) { | ||
for (const prop of rule.style) { | ||
actualStyles[prop] = rule.style.getPropertyValue(prop); | ||
} | ||
} | ||
} | ||
} catch (e) { | ||
console.warn("Unable to access stylesheet:", e); | ||
} | ||
} | ||
return actualStyles; | ||
} | ||
function updateCursors(element) { | ||
@@ -285,6 +368,6 @@ let mirrorDivs = []; | ||
mirrorDivs = [mirrorDiv]; | ||
} | ||
else { | ||
} else { | ||
mirrorDivs = element.querySelectorAll('.mirror'); | ||
} | ||
for (let mirrorDiv of mirrorDivs) { | ||
@@ -380,2 +463,3 @@ element = mirrorDiv.element; | ||
selection.socketId = response.socketId | ||
selection.frameId = response.frameId | ||
if (selection.start != null && selection.end != null) | ||
@@ -382,0 +466,0 @@ drawCursors(selection); |
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
162543
586
Updated@cocreate/observer@^1.16.1
Updated@cocreate/selection@^1.12.2
Updated@cocreate/utils@^1.33.7
Updated@cocreate/uuid@^1.11.2