Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
f89064c
fix(VE-4232): implement data-cslp mutation observer to manage unique …
sairajchouhan Apr 14, 2025
f513fe5
Merge branch 'main' of https://github.com/contentstack/live-preview-s…
sairajchouhan Apr 14, 2025
dcc3a10
chore: merge branch main into develop_v3
ZuhairAhmed-cs Apr 14, 2025
781d7ec
Merge pull request #421 from contentstack/VE-4232
sairaj-cs Apr 15, 2025
5dc41a2
fix: custom space key handling when a button element is on the compos…
faraazb-contentstack Apr 10, 2025
32a80bc
Merge pull request #419 from contentstack/VE-5803-space-key-element-i…
faraazb Apr 17, 2025
fd461a4
fix: sync field after custom space handling
faraazb-contentstack Apr 21, 2025
fd886ac
test: udpate handleFieldKeyDown tests for space handling in button
faraazb-contentstack Apr 21, 2025
332cebb
Merge pull request #427 from contentstack/VE-5803-space-key-element-i…
faraazb Apr 21, 2025
d2d6d54
fix: set unique ID on element click
faraazb-contentstack Apr 22, 2025
0c8990b
test: mock randomUUID from crypto module
sairajchouhan Apr 22, 2025
a16dfe3
chore: better names
faraazb-contentstack Apr 22, 2025
2ebf858
Merge pull request #429 from contentstack/multiple-elements-cslp
faraazb Apr 22, 2025
063f81a
fix: use uuid v4
faraazb-contentstack Apr 22, 2025
ae31c57
Merge pull request #428 from contentstack/develop_v3
faraazb Apr 22, 2025
1909b81
Merge branch 'main' into stage_v3
faraazb Apr 22, 2025
8710ac5
fix(collab): add optional chaining for tree shaking and fix unknown u…
ZuhairAhmed-cs Apr 23, 2025
26e63f9
fix: ensure process is defined
faraazb-contentstack Apr 23, 2025
918b374
Merge pull request #431 from contentstack/VE-6102
hiteshshetty-dev Apr 23, 2025
18c36c2
3.2.1
faraazb-contentstack Apr 23, 2025
dab1ddd
Merge branch 'main' into stage_v3
faraazb Apr 24, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
196 changes: 86 additions & 110 deletions CHANGELOG.md

Large diffs are not rendered by default.

4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,8 @@ npm install @contentstack/live-preview-utils
Alternatively, if you want to include the package directly in your website HTML code, use the following command:

```html
<script type='module' integrity='sha384-Q1kNLAr9TbfZ9lsUUXH&#x2F;vW79LERzfbYJc7aLNSYjKFLzBZQDp&#x2F;Jk09+ADHo9mhEq' crossorigin="anonymous">
import ContentstackLivePreview from 'https://esm.sh/@contentstack/live-preview-utils@3.2.0';
<script type='module' integrity='sha384-A8av+t5rI9uSevzozgzaoIRdBTs5pn1tt7F&#x2F;ekJQ7NAE1l3RenCKeJeMUTdrbtcM' crossorigin="anonymous">
import ContentstackLivePreview from 'https://esm.sh/@contentstack/live-preview-utils@3.2.1';

ContentstackLivePreview.init({
stackDetails: {
Expand Down
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@contentstack/live-preview-utils",
"version": "3.2.0",
"version": "3.2.1",
"description": "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.",
"type": "module",
"types": "dist/legacy/index.d.ts",
Expand Down
4 changes: 3 additions & 1 deletion src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,9 @@ class LightLivePreviewHoC {
}

const ContentstackLivePreview =
process.env.PURGE_PREVIEW_SDK || process.env.REACT_APP_PURGE_PREVIEW_SDK
typeof process !== "undefined" &&
(process?.env?.PURGE_PREVIEW_SDK === "true" ||
process?.env?.REACT_APP_PURGE_PREVIEW_SDK === "true")
? LightLivePreviewHoC
: ContentstackLivePreviewHOC;

Expand Down
5 changes: 4 additions & 1 deletion src/logger/logger.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,10 @@ export class PublicLogger {
| Console["info"],
message: any[]
): void {
if (process?.env?.NODE_ENV !== "test") {
if (
typeof process !== "undefined" &&
process?.env?.NODE_ENV !== "test"
) {
logCallback("Live_Preview_SDK:", ...message);
}
}
Expand Down
1 change: 1 addition & 0 deletions src/visualBuilder/__test__/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ vi.mock("../utils/visualBuilderPostMessage", async () => {
Object.defineProperty(globalThis, "crypto", {
value: {
getRandomValues: (arr: Array<any>) => crypto.randomBytes(arr.length),
randomUUID: () => crypto.randomUUID()
},
});
// Increase the timeout for the test
Expand Down
1 change: 1 addition & 0 deletions src/visualBuilder/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -296,6 +296,7 @@ export class VisualBuilder {
if (!config.enable || config.mode < ILivePreviewModeConfig.BUILDER) {
return;
}

visualBuilderPostMessage
?.send<IVisualBuilderInitEvent>("init", {
isSSR: config.ssr,
Expand Down
17 changes: 17 additions & 0 deletions src/visualBuilder/listeners/mouseClick.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ import { generateThread } from "../generators/generateThread";
import { isCollabThread } from "../generators/generateThread";
import { toggleCollabPopup } from "../generators/generateThread";
import { fixSvgXPath } from "../utils/collabUtils";
import { v4 as uuidV4 } from "uuid";

type HandleBuilderInteractionParams = Omit<
EventListenerHandlerParams,
Expand Down Expand Up @@ -79,6 +80,22 @@ async function handleBuilderInteraction(
(eventTarget.hasAttribute("data-cslp") ||
eventTarget.closest("[data-cslp]"));

// if multiple elements with the same cslp element are found,
// assign a unique ID to each element which we can use to identify
// them in updateFocussedState and other places where we
// would have queried the element by data-cslp
const duplicates = document.querySelectorAll(
`[data-cslp="${eventTarget?.getAttribute("data-cslp")}"]`
);
if (duplicates.length > 1) {
duplicates.forEach((ele) => {
if (!ele.hasAttribute("data-cslp-unique-id")) {
const uniqueId = `cslp-${uuidV4()}`;
ele.setAttribute("data-cslp-unique-id", uniqueId);
}
});
}

// if the target element is a studio-ui element, return
// this is currently used for the "Edit in Studio" button
if (eventTarget?.getAttribute("data-studio-ui") === "true") {
Expand Down
67 changes: 59 additions & 8 deletions src/visualBuilder/utils/__test__/handleFieldMouseDown.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -128,26 +128,36 @@ describe("handle numeric field key down", () => {
});

describe("handle keydown in button contenteditable", () => {
let button: HTMLButtonElement | undefined;
let spiedPreventDefault: MockInstance<(e: []) => void> | undefined;
let spiedInsertSpaceAtCursor:
| MockInstance<(typeof insertSpaceAtCursor)["insertSpaceAtCursor"]>
| undefined;
let spiedSendFieldEvent: MockInstance<() => void> | undefined;

beforeEach(() => {
spiedSendFieldEvent = vi
.spyOn(generateOverlay, "sendFieldEvent")
.mockImplementation(() => {});
const visualBuilderContainer = document.createElement("div");
visualBuilderContainer.classList.add("visual-builder__container");
document.body.appendChild(visualBuilderContainer);
});

afterEach(() => {
vi.clearAllMocks();
});

test("should insert space in button content-editable", () => {
let spiedPreventDefault: MockInstance<(e: []) => void> | undefined;
vi.spyOn(window, "getSelection").mockReturnValue({
// @ts-ignore
// @ts-expect-error mocking only required properties
getRangeAt: (n: number) => ({
startOffset: 0,
endOffset: 0,
}),
});
spiedInsertSpaceAtCursor = vi.spyOn(
const spiedInsertSpaceAtCursor = vi.spyOn(
insertSpaceAtCursor,
"insertSpaceAtCursor"
);

button = document.createElement("button");
const button = document.createElement("button");
button.innerHTML = "Test";
button.setAttribute("contenteditable", "true");
button.setAttribute(
Expand All @@ -169,6 +179,47 @@ describe("handle keydown in button contenteditable", () => {

expect(spiedPreventDefault).toHaveBeenCalledTimes(1);
expect(spiedInsertSpaceAtCursor).toHaveBeenCalledWith(button);
expect(spiedSendFieldEvent).toHaveBeenCalled();
});

test("should insert space in span content-editable inside button", () => {
let spiedPreventDefault: MockInstance<(e: []) => void> | undefined;
vi.spyOn(window, "getSelection").mockReturnValue({
// @ts-expect-error mocking only required properties
getRangeAt: (n: number) => ({
startOffset: 0,
endOffset: 0,
}),
});
const spiedInsertSpaceAtCursor = vi.spyOn(
insertSpaceAtCursor,
"insertSpaceAtCursor"
);

const button = document.createElement("button");
const span = document.createElement("span");
button.appendChild(span);
span.setAttribute("contenteditable", "true");
span.setAttribute(
VISUAL_BUILDER_FIELD_TYPE_ATTRIBUTE_KEY,
"single_line"
);

span.addEventListener("keydown", (e) => {
spiedPreventDefault = vi.spyOn(e, "preventDefault");
handleFieldKeyDown(e);
});

const keyDownEvent = new KeyboardEvent("keydown", {
bubbles: true,
key: "Space",
code: "Space",
});
span.dispatchEvent(keyDownEvent);

expect(spiedPreventDefault).toHaveBeenCalledTimes(1);
expect(spiedInsertSpaceAtCursor).toHaveBeenCalledWith(span);
expect(spiedSendFieldEvent).toHaveBeenCalled();
});
});

Expand Down
10 changes: 7 additions & 3 deletions src/visualBuilder/utils/collabUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -90,13 +90,17 @@ export const getMessageWithDisplayName = (

let tempText = sanitizeData(comment.message).replace(/<[^>]*>/g, "");

comment?.toUsers?.forEach((user) => {
comment.toUsers?.forEach((user) => {
const userPattern = new RegExp(`{{${user}}}`, "g");
const userData = userState.userMap[user];
const displayName = userData
? userData.display || getUserName(userData)
: `unknown user`;

const replacement =
profile === "html"
? `<b class="collab-thread-comment--message">@${userData.display || getUserName(userData)}</b>`
: `@${userData.display || getUserName(userData)}`;
? `<b class="collab-thread-comment--message">@${displayName}</b>`
: `@${displayName}`;
tempText = tempText.replace(userPattern, replacement);
});

Expand Down
13 changes: 11 additions & 2 deletions src/visualBuilder/utils/handleFieldMouseDown.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,15 @@ export function handleFieldKeyDown(e: Event): void {
VISUAL_BUILDER_FIELD_TYPE_ATTRIBUTE_KEY
) as FieldDataType | null;

if (targetElement.tagName === "BUTTON") {
if (
event
.composedPath()
.some(
(element) =>
element instanceof Element && element.tagName === "BUTTON"
)
) {
// custom space handling when a button is involved
handleKeyDownOnButton(event);
}
if (fieldType === FieldDataType.NUMBER) {
Expand All @@ -56,11 +64,12 @@ export function handleFieldKeyDown(e: Event): void {

// spaces do not work inside a button content-editable
// this adds a space and moves the cursor ahead, the
// button press event is also prevented
// button press event is also prevented, finally syncs the field
function handleKeyDownOnButton(e: KeyboardEvent) {
if (e.code === "Space" && e.target) {
e.preventDefault();
insertSpaceAtCursor(e.target as HTMLElement);
throttledFieldSync();
}
}

Expand Down
26 changes: 17 additions & 9 deletions src/visualBuilder/utils/updateFocussedState.ts
Original file line number Diff line number Diff line change
Expand Up @@ -105,11 +105,16 @@ export function updateFocussedState({
return;
}

const previousSelectedElementCslp =
previousSelectedEditableDOM?.getAttribute("data-cslp");
const newPreviousSelectedElement = document.querySelector(
`[data-cslp="${previousSelectedElementCslp}"]`
);
// prefer data-cslp-unique-id when available else use data-cslp.
// unique ID is added on click when multiple elements with same
// data-cslp are found.
const previousSelectedElementCslp = editableElement?.getAttribute("data-cslp") || "";
const previousSelectedElementCslpUniqueId =
previousSelectedEditableDOM?.getAttribute("data-cslp-unique-id");
const newPreviousSelectedElement =
document.querySelector(
`[data-cslp-unique-id="${previousSelectedElementCslpUniqueId}"]`
) || document.querySelector(`[data-cslp="${previousSelectedElementCslp}"]`);
if (!newPreviousSelectedElement && resizeObserver) {
hideFocusOverlay({
visualBuilderOverlayWrapper: overlayWrapper,
Expand Down Expand Up @@ -148,8 +153,7 @@ export function updateFocussedState({
psuedoEditableElement.style.visibility = "visible";
}

const cslp = editableElement?.getAttribute("data-cslp") || "";
const fieldMetadata = extractDetailsFromCslp(cslp);
const fieldMetadata = extractDetailsFromCslp(previousSelectedElementCslp);

const targetElementDimension = editableElement.getBoundingClientRect();
if (targetElementDimension.width && targetElementDimension.height) {
Expand Down Expand Up @@ -228,9 +232,13 @@ export function updateFocussedStateOnMutation(
if (!selectedElement) return;

const selectedElementCslp = selectedElement?.getAttribute("data-cslp");
const newSelectedElement = document.querySelector(
`[data-cslp="${selectedElementCslp}"]`
const selectedElementCslpUniqueId = selectedElement?.getAttribute(
"data-cslp-unique-id"
);
const newSelectedElement =
document.querySelector(
`[data-cslp-unique-id="${selectedElementCslpUniqueId}"]`
) || document.querySelector(`[data-cslp="${selectedElementCslp}"]`);
if (!newSelectedElement && resizeObserver) {
hideFocusOverlay({
visualBuilderOverlayWrapper: focusOverlayWrapper,
Expand Down
Loading