@contentstack/live-preview-utils
Version:
Contentstack provides the Live Preview SDK to establish a communication channel between the various Contentstack SDKs and your website, transmitting live changes to the preview pane.
442 lines (441 loc) • 14.9 kB
JavaScript
import "../../chunk-5WRI5ZAA.js";
// src/visualBuilder/generators/generateThread.tsx
import { render } from "preact";
import { css } from "goober";
import CollabIndicator from "../components/Collab/CollabIndicator.js";
import Config from "../../configManager/configManager.js";
import visualBuilderPostMessage from "../utils/visualBuilderPostMessage.js";
import { VisualBuilderPostMessageEvents } from "../utils/types/postMessage.types.js";
import { adjustPositionToViewport } from "../utils/collabUtils.js";
import { jsx } from "preact/jsx-runtime";
var popupTopOffset = 43;
var popupLeftOffset = 9;
var hiddenClass = css`
display: none;
`;
function createPopupContainer(resolvedXPath, relativeX, relativeY, top, left, updateConfig, hidden, payload) {
const popupContainer = document.createElement("div");
popupContainer.setAttribute("field-path", resolvedXPath);
popupContainer.setAttribute("relative", `x: ${relativeX}, y: ${relativeY}`);
popupContainer.style.position = "absolute";
popupContainer.style.top = `${top - popupTopOffset}px`;
popupContainer.style.left = `${left - popupLeftOffset}px`;
popupContainer.style.zIndex = updateConfig ? "1000" : "999";
popupContainer.style.cursor = "pointer";
popupContainer.className = "collab-thread";
if (hidden) popupContainer.classList.add(hiddenClass);
if (payload?._id) popupContainer.setAttribute("threaduid", payload._id);
return popupContainer;
}
function appendPopupContainer(popupContainer) {
const visualBuilderContainer = document.querySelector(
".visual-builder__container"
);
if (visualBuilderContainer) {
let highlightCommentWrapper = visualBuilderContainer.querySelector(
".visual-builder__collab-wrapper"
);
if (!highlightCommentWrapper) {
highlightCommentWrapper = document.createElement("div");
highlightCommentWrapper.className = "visual-builder__collab-wrapper";
visualBuilderContainer.appendChild(highlightCommentWrapper);
}
highlightCommentWrapper.appendChild(popupContainer);
} else {
document.body.appendChild(popupContainer);
}
}
function generateThread(payload, options = {}) {
const {
isNewThread = false,
updateConfig = false,
hidden = false
} = options;
const config = Config.get?.();
let relativeX, relativeY, resolvedXPath;
if (isNewThread) {
({ relativeX, relativeY, xpath: resolvedXPath } = payload);
} else {
const { position, elementXPath } = payload;
({ x: relativeX, y: relativeY } = position);
resolvedXPath = elementXPath;
}
if (payload?._id) {
const existingThread = document.querySelector(
`div[threaduid='${payload._id}']`
);
if (existingThread) {
return void 0;
}
}
const element = getElementByXpath(resolvedXPath);
if (!element) {
return payload._id;
}
const rect = element.getBoundingClientRect();
let top = rect.top + window.scrollY + relativeY * rect.height;
let left = rect.left + window.scrollX + relativeX * rect.width;
const adjustedPosition = adjustPositionToViewport({ top, left });
top = adjustedPosition.top;
left = adjustedPosition.left;
const popupContainer = createPopupContainer(
resolvedXPath,
relativeX,
relativeY,
top,
left,
updateConfig,
hidden,
payload
);
if (updateConfig && config?.collab?.enable) {
if (config?.collab.isFeedbackMode) {
Config.set("collab.isFeedbackMode", false);
}
}
render(
/* @__PURE__ */ jsx(
CollabIndicator,
{
activeThread: !isNewThread ? payload : void 0,
newThread: isNewThread
}
),
popupContainer
);
appendPopupContainer(popupContainer);
return void 0;
}
function updateCollabIconPosition() {
const icons = document.querySelectorAll(
".visual-builder__collab-wrapper .collab-thread"
);
const config = Config.get?.();
if (config?.collab?.pauseFeedback) return;
icons.forEach((icon) => {
if (!(icon instanceof HTMLElement)) return;
const path = icon.getAttribute("field-path");
const relative = icon.getAttribute("relative");
if (!path || !relative) {
console.error("Missing field-path or relative attribute.");
return;
}
const match = relative.match(/x: ([\d.]+), y: ([\d.]+)/);
if (!match) {
console.error("Invalid relative attribute format.");
return;
}
const relativeX = parseFloat(match[1]);
const relativeY = parseFloat(match[2]);
const targetElement = getElementByXpath(path);
if (!targetElement) {
icon.classList.add(hiddenClass);
return;
}
const rect = targetElement.getBoundingClientRect();
let left = rect.left + rect.width * relativeX + window.scrollX;
let top = rect.top + rect.height * relativeY + window.scrollY;
const adjustedPosition = adjustPositionToViewport({ top, left });
top = adjustedPosition.top;
left = adjustedPosition.left;
icon.style.top = `${top - popupTopOffset}px`;
icon.style.left = `${left - popupLeftOffset}px`;
icon.classList.remove(hiddenClass);
});
}
function updatePopupPositions() {
const popups = document.querySelectorAll(
".visual-builder__collab-wrapper .collab-thread .collab-popup"
);
const config = Config.get?.();
if (config?.collab?.pauseFeedback) return;
popups.forEach((popup) => {
if (popup && popup instanceof HTMLElement) {
const parent = popup.closest(
".visual-builder__collab-wrapper .collab-thread"
);
if (!parent) {
console.error(
"Parent element with class 'collab-thread' not found."
);
return;
}
const button = parent.querySelector(
".visual-builder__collab-wrapper .collab-thread .collab-indicator"
);
if (!button || !(button instanceof HTMLElement)) {
console.error(
"Button with class 'collab-indicator' not found."
);
return;
}
calculatePopupPosition(button, popup);
}
});
}
function updateSuggestionListPosition() {
const suggestionLists = document.querySelectorAll(
".collab-thread-body--input--textarea--suggestionsList"
);
if (!suggestionLists.length) return;
suggestionLists.forEach((list) => {
if (!(list instanceof HTMLElement)) return;
const textarea = document.querySelector(
".collab-thread-body--input--textarea"
);
if (!textarea) return;
const positionData = list.getAttribute("data-position");
const parsedData = positionData ? JSON.parse(positionData) : null;
const showAbove = window.getComputedStyle(list).bottom !== "auto";
const textareaRect = textarea.getBoundingClientRect();
if (showAbove) {
const lineHeight = parseInt(window.getComputedStyle(textarea).lineHeight) || 20;
const paddingTop = parseInt(window.getComputedStyle(textarea).paddingTop) || 8;
const cursorLineY = parsedData?.cursorLineY || paddingTop + lineHeight;
list.style.position = "fixed";
list.style.bottom = `${window.innerHeight - textareaRect.top - cursorLineY + lineHeight}px`;
list.style.top = "auto";
} else {
const lineHeight = parseInt(window.getComputedStyle(textarea).lineHeight) || 20;
const paddingTop = parseInt(window.getComputedStyle(textarea).paddingTop) || 8;
const cursorLineY = parsedData?.cursorLineY || paddingTop + lineHeight;
list.style.position = "fixed";
list.style.top = `${textareaRect.top + cursorLineY}px`;
list.style.bottom = "auto";
}
if (!positionData && textareaRect) {
const lineHeight = parseInt(window.getComputedStyle(textarea).lineHeight) || 20;
const paddingTop = parseInt(window.getComputedStyle(textarea).paddingTop) || 8;
const positionInfo = {
showAbove,
cursorLineY: paddingTop + lineHeight
};
list.setAttribute("data-position", JSON.stringify(positionInfo));
}
const listRect = list.getBoundingClientRect();
if (!showAbove && listRect.bottom > window.innerHeight) {
const lineHeight = parseInt(window.getComputedStyle(textarea).lineHeight) || 20;
const paddingTop = parseInt(window.getComputedStyle(textarea).paddingTop) || 8;
const cursorLineY = parsedData?.cursorLineY || paddingTop + lineHeight;
list.style.bottom = `${window.innerHeight - textareaRect.top - cursorLineY + lineHeight}px`;
list.style.top = "auto";
if (positionData) {
const updatedData = JSON.parse(positionData);
updatedData.showAbove = true;
list.setAttribute("data-position", JSON.stringify(updatedData));
}
} else if (showAbove && listRect.top < 0) {
const lineHeight = parseInt(window.getComputedStyle(textarea).lineHeight) || 20;
const paddingTop = parseInt(window.getComputedStyle(textarea).paddingTop) || 8;
const cursorLineY = parsedData?.cursorLineY || paddingTop + lineHeight;
list.style.top = `${textareaRect.top + cursorLineY}px`;
list.style.bottom = "auto";
if (positionData) {
const updatedData = JSON.parse(positionData);
updatedData.showAbove = false;
list.setAttribute("data-position", JSON.stringify(updatedData));
}
}
});
}
function calculatePopupPosition(button, popup) {
const buttonRect = button.getBoundingClientRect();
const viewportHeight = window.innerHeight;
const viewportWidth = window.innerWidth;
let popupHeight = popup.offsetHeight || 198;
let popupWidth = popup.offsetWidth || 334;
const spaceAbove = buttonRect.top;
const spaceBelow = viewportHeight - buttonRect.bottom;
let top, left;
if (spaceAbove >= popupHeight) {
top = buttonRect.top - popupHeight - 8;
} else if (spaceBelow >= popupHeight) {
top = buttonRect.bottom + 8;
} else {
top = spaceBelow > spaceAbove ? buttonRect.bottom + 8 : Math.max(buttonRect.top - popupHeight - 8, 0);
}
left = buttonRect.left + buttonRect.width / 2 - popupWidth / 2;
top = Math.max(top, 0);
left = Math.max(left, 0);
left = Math.min(left, viewportWidth - popupWidth);
popup.style.top = `${top}px`;
popup.style.left = `${left}px`;
requestAnimationFrame(() => {
const newPopupHeight = popup.offsetHeight;
if (newPopupHeight !== popupHeight) {
calculatePopupPosition(button, popup);
}
});
}
function removeAllCollabIcons() {
const icons = document.querySelectorAll(
".visual-builder__collab-wrapper .collab-thread"
);
icons?.forEach((icon) => icon?.remove());
}
function hideAllCollabIcons() {
const icons = document.querySelectorAll(
".visual-builder__collab-wrapper .collab-thread"
);
icons?.forEach((icon) => icon?.classList.add(hiddenClass));
toggleCollabPopup({ threadUid: "", action: "close" });
}
function showAllCollabIcons() {
const icons = document.querySelectorAll(
".visual-builder__collab-wrapper .collab-thread"
);
icons?.forEach((icon) => icon?.classList.remove(hiddenClass));
}
function removeCollabIcon(threadUid) {
const thread = document.querySelector(`div[threaduid='${threadUid}']`);
thread?.remove();
}
function toggleCollabPopup({
threadUid = "",
action
}) {
document.dispatchEvent(
new CustomEvent("toggleCollabPopup", {
detail: { threadUid, action }
})
);
}
function HighlightThread(threadUid) {
toggleCollabPopup({ threadUid, action: "open" });
}
function isCollabThread(target) {
return Array.from(target.classList).some(
(className) => className.startsWith("collab")
);
}
function handleMissingThreads(payload) {
visualBuilderPostMessage?.send(
VisualBuilderPostMessageEvents.COLLAB_MISSING_THREADS,
payload
);
}
function handleEmptyThreads() {
const icons = document.querySelectorAll(
".visual-builder__collab-wrapper .collab-thread"
);
icons?.forEach((icon) => {
if (!icon.hasAttribute("threaduid")) {
icon.remove();
}
});
}
var retryConfig = {
maxRetries: 5,
retryDelay: 1e3
};
var isProcessingThreads = false;
var threadRenderStatus = /* @__PURE__ */ new Map();
function delay(ms) {
return new Promise((resolve) => setTimeout(resolve, ms));
}
function getRenderStatus(threadId) {
if (!threadRenderStatus.has(threadId)) {
threadRenderStatus.set(threadId, {
threadId,
attempts: 0,
isRendered: false
});
}
return threadRenderStatus.get(threadId);
}
function updateRenderStatus(threadId, isRendered) {
const status = getRenderStatus(threadId);
status.isRendered = isRendered;
threadRenderStatus.set(threadId, status);
}
function clearThreadStatus(threadId) {
threadRenderStatus.delete(threadId);
}
function clearAllThreadStatus() {
threadRenderStatus.clear();
isProcessingThreads = false;
}
async function processThread(thread) {
let status = getRenderStatus(thread._id);
while (status.attempts < retryConfig.maxRetries) {
try {
const result = generateThread(thread);
if (result === void 0) {
updateRenderStatus(thread._id, true);
return void 0;
}
status.attempts++;
updateRenderStatus(thread._id, false);
if (status.attempts < retryConfig.maxRetries) {
await delay(retryConfig.retryDelay);
}
} catch (error) {
console.error(`Error rendering thread ${thread._id}:`, error);
status.attempts++;
if (status.attempts >= retryConfig.maxRetries) {
break;
}
await delay(retryConfig.retryDelay);
}
}
return thread._id;
}
async function processThreadsBatch(threads) {
if (isProcessingThreads) return [];
try {
isProcessingThreads = true;
const unrenderedThreads = filterUnrenderedThreads(threads);
if (unrenderedThreads.length === 0) return [];
const missingThreadIds = (await Promise.all(
unrenderedThreads.map((thread) => processThread(thread))
)).filter(Boolean);
missingThreadIds.forEach(clearThreadStatus);
return missingThreadIds;
} finally {
isProcessingThreads = false;
}
}
function filterUnrenderedThreads(threads) {
return threads.filter((thread) => {
const existingThread = document.querySelector(
`[threaduid="${thread._id}"]`
);
if (existingThread) {
updateRenderStatus(thread._id, true);
return false;
}
return true;
});
}
function getElementByXpath(xpath) {
const result = document.evaluate(
xpath,
document,
null,
XPathResult.FIRST_ORDERED_NODE_TYPE,
null
);
return result.singleNodeValue;
}
export {
HighlightThread,
calculatePopupPosition,
clearAllThreadStatus,
clearThreadStatus,
filterUnrenderedThreads,
generateThread,
handleEmptyThreads,
handleMissingThreads,
hideAllCollabIcons,
isCollabThread,
processThreadsBatch,
removeAllCollabIcons,
removeCollabIcon,
showAllCollabIcons,
threadRenderStatus,
toggleCollabPopup,
updateCollabIconPosition,
updatePopupPositions,
updateSuggestionListPosition
};
//# sourceMappingURL=generateThread.js.map