diff --git a/packages/live-preview-sdk/src/__tests__/inspectorMode.spec.ts b/packages/live-preview-sdk/src/__tests__/inspectorMode.spec.ts index bc1075d9..e9cee604 100644 --- a/packages/live-preview-sdk/src/__tests__/inspectorMode.spec.ts +++ b/packages/live-preview-sdk/src/__tests__/inspectorMode.spec.ts @@ -57,6 +57,8 @@ describe('InspectorMode', () => { InspectorModeEventMethods.TAGGED_ELEMENTS, { elements: [], + automaticallyTaggedCount: 0, + manuallyTaggedCount: 0, }, targetOrigin, ); diff --git a/packages/live-preview-sdk/src/__tests__/react.spec.tsx b/packages/live-preview-sdk/src/__tests__/react.spec.tsx index ce51b5b9..5684cd7a 100644 --- a/packages/live-preview-sdk/src/__tests__/react.spec.tsx +++ b/packages/live-preview-sdk/src/__tests__/react.spec.tsx @@ -196,7 +196,14 @@ describe('useContentfulInspectorMode', () => { ), }); - expect(result.current({ entryId: '1', locale, fieldId: 'title' })).toEqual({ + expect( + result.current({ + entryId: '1', + locale, + fieldId: 'title', + manuallyTagged: true, + }), + ).toEqual({ 'data-contentful-entry-id': '1', 'data-contentful-field-id': 'title', 'data-contentful-locale': 'en-US', @@ -211,7 +218,13 @@ describe('useContentfulInspectorMode', () => { ), }); - expect(result.current({ locale, fieldId: 'title' })).toEqual({ + expect( + result.current({ + locale, + fieldId: 'title', + manuallyTagged: true, + }), + ).toEqual({ 'data-contentful-entry-id': '1', 'data-contentful-field-id': 'title', 'data-contentful-locale': 'en-US', @@ -226,7 +239,13 @@ describe('useContentfulInspectorMode', () => { ), }); - expect(result.current({ entryId: '1', fieldId: 'title' })).toEqual({ + expect( + result.current({ + entryId: '1', + fieldId: 'title', + manuallyTagged: true, + }), + ).toEqual({ 'data-contentful-entry-id': '1', 'data-contentful-field-id': 'title', 'data-contentful-locale': 'en-US', @@ -241,7 +260,12 @@ describe('useContentfulInspectorMode', () => { ), }); - expect(result.current({ fieldId: 'title' })).toEqual({ + expect( + result.current({ + fieldId: 'title', + manuallyTagged: true, + }), + ).toEqual({ 'data-contentful-entry-id': '1', 'data-contentful-field-id': 'title', 'data-contentful-locale': 'en-US', @@ -256,7 +280,14 @@ describe('useContentfulInspectorMode', () => { ), }); - expect(result.current({ fieldId: 'title', space: '12345', environment: 'develop' })).toEqual({ + expect( + result.current({ + fieldId: 'title', + space: '12345', + environment: 'develop', + manuallyTagged: true, + }), + ).toEqual({ 'data-contentful-entry-id': '1', 'data-contentful-field-id': 'title', 'data-contentful-space': '12345', @@ -272,7 +303,12 @@ describe('useContentfulInspectorMode', () => { ), }); - expect(result.current({ fieldId: 'title' })).toEqual({ + expect( + result.current({ + fieldId: 'title', + manuallyTagged: true, + }), + ).toEqual({ 'data-contentful-entry-id': '1', 'data-contentful-field-id': 'title', 'data-contentful-space': '99999', @@ -290,7 +326,12 @@ describe('useContentfulInspectorMode', () => { ), }); - expect(result.current({ fieldId: 'title' })).toBeNull(); + expect( + result.current({ + fieldId: 'title', + manuallyTagged: true, + }), + ).toBeNull(); }); }); describe('asset', () => { @@ -302,7 +343,14 @@ describe('useContentfulInspectorMode', () => { ), }); - expect(result.current({ assetId: '1', locale, fieldId: 'title' })).toEqual({ + expect( + result.current({ + assetId: '1', + locale, + fieldId: 'title', + manuallyTagged: true, + }), + ).toEqual({ 'data-contentful-asset-id': '1', 'data-contentful-field-id': 'title', 'data-contentful-locale': 'en-US', @@ -317,7 +365,13 @@ describe('useContentfulInspectorMode', () => { ), }); - expect(result.current({ locale, fieldId: 'title' })).toEqual({ + expect( + result.current({ + locale, + fieldId: 'title', + manuallyTagged: true, + }), + ).toEqual({ 'data-contentful-asset-id': '1', 'data-contentful-field-id': 'title', 'data-contentful-locale': 'en-US', @@ -332,7 +386,13 @@ describe('useContentfulInspectorMode', () => { ), }); - expect(result.current({ assetId: '1', fieldId: 'title' })).toEqual({ + expect( + result.current({ + assetId: '1', + fieldId: 'title', + manuallyTagged: true, + }), + ).toEqual({ 'data-contentful-asset-id': '1', 'data-contentful-field-id': 'title', 'data-contentful-locale': 'en-US', @@ -347,7 +407,12 @@ describe('useContentfulInspectorMode', () => { ), }); - expect(result.current({ fieldId: 'title' })).toEqual({ + expect( + result.current({ + fieldId: 'title', + manuallyTagged: true, + }), + ).toEqual({ 'data-contentful-asset-id': '1', 'data-contentful-field-id': 'title', 'data-contentful-locale': 'en-US', @@ -364,7 +429,12 @@ describe('useContentfulInspectorMode', () => { ), }); - expect(result.current({ fieldId: 'title' })).toBeNull(); + expect( + result.current({ + fieldId: 'title', + manuallyTagged: true, + }), + ).toBeNull(); }); }); }); diff --git a/packages/live-preview-sdk/src/index.ts b/packages/live-preview-sdk/src/index.ts index 07042b07..114c67f9 100644 --- a/packages/live-preview-sdk/src/index.ts +++ b/packages/live-preview-sdk/src/index.ts @@ -12,7 +12,7 @@ import { import { isValidMessage } from './helpers/validateMessage.js'; import { InspectorMode } from './inspectorMode/index.js'; import { InspectorModeDataAttributes, type InspectorModeTags } from './inspectorMode/types.js'; -import { getAllTaggedEntries } from './inspectorMode/utils.js'; +import { getAllTaggedElements, getAllTaggedEntries } from './inspectorMode/utils.js'; import { LiveUpdates } from './liveUpdates.js'; import { LivePreviewPostMessageMethods, @@ -190,9 +190,16 @@ export class ContentfulLivePreview { }); // tell the editor that there's a SDK - const taggedElementCount = document.querySelectorAll( - `[${InspectorModeDataAttributes.ENTRY_ID}]`, - ).length; + const { taggedElements, manuallyTaggedCount, automaticallyTaggedCount } = this + .inspectorModeEnabled + ? getAllTaggedElements() + : { + taggedElements: [], + manuallyTaggedCount: 0, + automaticallyTaggedCount: 0, + }; + const taggedElementCount = taggedElements.length; + sendMessageToEditor( LivePreviewPostMessageMethods.CONNECTED, { @@ -203,6 +210,8 @@ export class ContentfulLivePreview { locale: this.locale, isInspectorEnabled: this.inspectorModeEnabled, isLiveUpdatesEnabled: this.liveUpdatesEnabled, + manuallyTaggedElementCount: manuallyTaggedCount, + automaticallyTaggedElementCount: automaticallyTaggedCount, } as ConnectedMessage, this.targetOrigin, ); diff --git a/packages/live-preview-sdk/src/inspectorMode/__tests__/getAllTaggedElements.test.ts b/packages/live-preview-sdk/src/inspectorMode/__tests__/getAllTaggedElements.test.ts index aa64d3d6..a2512b23 100644 --- a/packages/live-preview-sdk/src/inspectorMode/__tests__/getAllTaggedElements.test.ts +++ b/packages/live-preview-sdk/src/inspectorMode/__tests__/getAllTaggedElements.test.ts @@ -45,7 +45,7 @@ describe('getAllTaggedElements', () => { `); - const elements = getAllTaggedElements(dom); + const { taggedElements: elements } = getAllTaggedElements(dom); expect(elements).toEqual([ dom.getElementById('entry-1'), @@ -70,7 +70,7 @@ describe('getAllTaggedElements', () => { `); - const elements = getAllTaggedElements(dom); + const { taggedElements: elements } = getAllTaggedElements(dom); expect(elements).toEqual([dom.getElementById('entry')]); }); @@ -87,7 +87,7 @@ describe('getAllTaggedElements', () => { `); - const elements = getAllTaggedElements(dom); + const { taggedElements: elements } = getAllTaggedElements(dom); expect(elements).toEqual([dom.getElementById('entry')]); }); @@ -101,14 +101,14 @@ describe('getAllTaggedElements', () => { `${combine('Test', createSourceMapFixture('ignore_origin', { origin: 'example.com' }))}`, ); - const elements = getAllTaggedElements(dom); + const { taggedElements: elements } = getAllTaggedElements(dom); expect(elements).toEqual([]); }); it('should recognize auto-tagged elements', () => { const dom = html(`${combine('Test', metadata)}`); - const elements = getAllTaggedElements(dom); + const { taggedElements: elements } = getAllTaggedElements(dom); expect(elements).toHaveLength(1); expect(elements).toEqual([dom.getElementById('entry-1')]); @@ -125,7 +125,7 @@ describe('getAllTaggedElements', () => { `); - const elements = getAllTaggedElements(dom); + const { taggedElements: elements } = getAllTaggedElements(dom); expect(elements).toHaveLength(1); expect(elements).toEqual([dom.getElementById('richtext')]); @@ -141,7 +141,7 @@ describe('getAllTaggedElements', () => { `); - const elements = getAllTaggedElements(dom); + const { taggedElements: elements } = getAllTaggedElements(dom); expect(elements).toHaveLength(1); expect(elements).toEqual([dom.getElementById('richtext')]); @@ -152,7 +152,7 @@ describe('getAllTaggedElements', () => {

${combine('Hello', metadata)}${combine('World', metadata)}!

`); - const elements = getAllTaggedElements(dom); + const { taggedElements: elements } = getAllTaggedElements(dom); expect(elements).toHaveLength(1); expect(elements).toEqual([dom.getElementById('node-1')]); @@ -177,7 +177,7 @@ describe('getAllTaggedElements', () => { `); - const elements = getAllTaggedElements(dom); + const { taggedElements: elements } = getAllTaggedElements(dom); expect(elements).toHaveLength(3); expect(elements).toEqual([ @@ -207,7 +207,7 @@ describe('getAllTaggedElements', () => { `); - const elements = getAllTaggedElements(dom); + const { taggedElements: elements } = getAllTaggedElements(dom); expect(elements).toHaveLength(3); expect(elements).toEqual([ @@ -224,7 +224,7 @@ describe('getAllTaggedElements', () => { `); - const elements = getAllTaggedElements(dom); + const { taggedElements: elements } = getAllTaggedElements(dom); expect(elements.length).toEqual(1); expect(elements[0].getAttribute(InspectorModeDataAttributes.ENTRY_ID)).toEqual( @@ -243,14 +243,15 @@ describe('getAllTaggedElements', () => { `); - const elements = getAllTaggedElements(dom, true); + const { + taggedElements: elements, + autoTaggedElements, + manuallyTaggedCount, + } = getAllTaggedElements(dom, true); expect(elements.length).toEqual(1); - expect(elements[0].getAttribute(InspectorModeDataAttributes.ENTRY_ID)).toEqual( - 'test-entry-id', - ); - expect(elements[0].getAttribute(InspectorModeDataAttributes.FIELD_ID)).toEqual('title'); - expect(elements[0].getAttribute(InspectorModeDataAttributes.LOCALE)).toEqual('en-US'); + expect(autoTaggedElements.length).toEqual(1); + expect(manuallyTaggedCount).toEqual(0); }); }); }); diff --git a/packages/live-preview-sdk/src/inspectorMode/index.ts b/packages/live-preview-sdk/src/inspectorMode/index.ts index f79a409e..5fce0073 100644 --- a/packages/live-preview-sdk/src/inspectorMode/index.ts +++ b/packages/live-preview-sdk/src/inspectorMode/index.ts @@ -5,7 +5,7 @@ import { InspectorModeEventMethods, type InspectorModeChangedMessage, } from './types.js'; -import { getAllTaggedElements, getInspectorModeAttributes } from './utils.js'; +import { AutoTaggedElement, getAllTaggedElements, getInspectorModeAttributes } from './utils.js'; type InspectorModeOptions = { locale: string; @@ -25,6 +25,7 @@ export class InspectorMode { private hoveredElement?: HTMLElement; private taggedElements: Element[] = []; private taggedElementMutationObserver?: MutationObserver; + private autoTaggedElements: AutoTaggedElement[] = []; constructor(private options: InspectorModeOptions) { // Attach interaction listeners @@ -110,7 +111,7 @@ export class InspectorMode { /** Detects DOM changes and sends the tagged elements to the editor */ private addMutationListener = () => { const mutationObserver = new MutationObserver(() => { - const taggedElements = getAllTaggedElements(); + const { taggedElements } = getAllTaggedElements(); if (this.taggedElements?.length !== taggedElements.length) { this.sendAllElements(); @@ -171,10 +172,26 @@ export class InspectorMode { */ private handleTaggedElement = (element: HTMLElement): boolean => { const { targetOrigin, locale, space, environment } = this.options; - const taggedInformation = getInspectorModeAttributes(element, { locale, space, environment }); + let taggedInformation = getInspectorModeAttributes(element, { locale, space, environment }); if (!taggedInformation) { - return false; + const autoTaggedElement = this.autoTaggedElements.find((el) => el.element === element); + + if (!autoTaggedElement) { + return false; + } + + const contentful = autoTaggedElement.sourceMap.contentful; + taggedInformation = { + fieldId: contentful.field, + locale: contentful.locale ?? locale, + environment: contentful.environment ?? environment, + space: contentful.space ?? space, + ...(contentful.entityType === 'Asset' + ? { assetId: contentful.entity } + : { entryId: contentful.entity }), + manuallyTagged: false, + }; } this.hoveredElement = element; @@ -198,9 +215,11 @@ export class InspectorMode { */ private sendAllElements = () => { const { targetOrigin, locale, space, environment } = this.options; - const elements = getAllTaggedElements(); + const { taggedElements, manuallyTaggedCount, automaticallyTaggedCount, autoTaggedElements } = + getAllTaggedElements(); - this.taggedElements = elements; + this.taggedElements = taggedElements; + this.autoTaggedElements = autoTaggedElements; if (this.taggedElementMutationObserver) { this.taggedElementMutationObserver.disconnect(); } @@ -209,10 +228,12 @@ export class InspectorMode { sendMessageToEditor( InspectorModeEventMethods.TAGGED_ELEMENTS, { - elements: elements.map((e) => ({ + elements: taggedElements.map((e) => ({ attributes: getInspectorModeAttributes(e, { locale, space, environment }), coordinates: e.getBoundingClientRect(), })), + automaticallyTaggedCount, + manuallyTaggedCount, }, targetOrigin, ); diff --git a/packages/live-preview-sdk/src/inspectorMode/types.ts b/packages/live-preview-sdk/src/inspectorMode/types.ts index 14771d82..4726792c 100644 --- a/packages/live-preview-sdk/src/inspectorMode/types.ts +++ b/packages/live-preview-sdk/src/inspectorMode/types.ts @@ -39,6 +39,7 @@ type InspectorModeSharedAttributes = { locale: string; space?: string; environment?: string; + manuallyTagged?: boolean; }; export type InspectorModeEntryAttributes = InspectorModeSharedAttributes & { entryId: string; @@ -61,6 +62,8 @@ export type InspectorModeMouseMoveMessage = { }; export type InspectorModeTaggedElementsMessage = { elements: Array; + manuallyTaggedCount: number; + automaticallyTaggedCount: number; }; export type InspectorModeChangedMessage = { diff --git a/packages/live-preview-sdk/src/inspectorMode/utils.ts b/packages/live-preview-sdk/src/inspectorMode/utils.ts index f1a79b06..70456f97 100644 --- a/packages/live-preview-sdk/src/inspectorMode/utils.ts +++ b/packages/live-preview-sdk/src/inspectorMode/utils.ts @@ -3,7 +3,7 @@ import { VERCEL_STEGA_REGEX } from '@vercel/stega'; import { InspectorModeAttributes, InspectorModeDataAttributes } from './types.js'; -type AutoTaggedElement = { +export type AutoTaggedElement = { element: T; sourceMap: SourceMapMetadata; }; @@ -51,6 +51,7 @@ export function getInspectorModeAttributes( environment: element.getAttribute(InspectorModeDataAttributes.ENVIRONMENT) ?? fallbackProps.environment, space: element.getAttribute(InspectorModeDataAttributes.SPACE) ?? fallbackProps.space, + manuallyTagged: true, }; const entryId = element.getAttribute(InspectorModeDataAttributes.ENTRY_ID); @@ -133,7 +134,7 @@ function getParent( return null; } -function findStegaNodes(container: HTMLElement) { +export function findStegaNodes(container: HTMLElement) { let baseArray: HTMLElement[] = []; if (typeof container.matches === 'function' && container.matches('*')) { baseArray = [container]; @@ -175,14 +176,22 @@ function hasTaggedParent(node: HTMLElement, taggedElements: Element[]): boolean /** * Query the document for all tagged elements */ -export function getAllTaggedElements(root = window.document, ignoreManual?: boolean): Element[] { - const manualTagged = ignoreManual +export function getAllTaggedElements( + root = window.document, + ignoreManual?: boolean, +): { + taggedElements: Element[]; + manuallyTaggedCount: number; + automaticallyTaggedCount: number; + autoTaggedElements: AutoTaggedElement[]; +} { + const alreadyTagged = ignoreManual ? [] : root.querySelectorAll( `[${InspectorModeDataAttributes.ASSET_ID}][${InspectorModeDataAttributes.FIELD_ID}], [${InspectorModeDataAttributes.ENTRY_ID}][${InspectorModeDataAttributes.FIELD_ID}]`, ); - const taggedElements: Element[] = [...manualTagged]; + const taggedElements: Element[] = [...alreadyTagged]; const elementsForTagging: AutoTaggedElement[] = []; const stegaNodes = findStegaNodes('body' in root ? root.body : root); @@ -223,23 +232,31 @@ export function getAllTaggedElements(root = window.document, ignoreManual?: bool (el, index) => elementsForTagging.findIndex((et) => isSameElement(el, et)) === index, ); - // Add the data- attributes to the auto-tagged elements - // Do this after the tree walker is finished, otherwise the MutationObserver picks it already up again - // and we have multiple tree walkers running parallel - for (const { element, sourceMap } of uniqElementsForTagging) { - if (sourceMap.contentful.entityType === 'Asset') { - element.setAttribute(InspectorModeDataAttributes.ASSET_ID, sourceMap.contentful.entity); - } else { - element.setAttribute(InspectorModeDataAttributes.ENTRY_ID, sourceMap.contentful.entity); - } - element.setAttribute(InspectorModeDataAttributes.FIELD_ID, sourceMap.contentful.field); - element.setAttribute(InspectorModeDataAttributes.LOCALE, sourceMap.contentful.locale); - element.setAttribute(InspectorModeDataAttributes.SPACE, sourceMap.contentful.space); - element.setAttribute(InspectorModeDataAttributes.ENVIRONMENT, sourceMap.contentful.environment); + // Adding auto tagged elements to the tagged elements list + for (const { element } of uniqElementsForTagging) { taggedElements.push(element); } - return taggedElements; + const autoTaggedCount = taggedElements.filter( + (el) => + ignoreManual || + [ + !el.hasAttribute(InspectorModeDataAttributes.FIELD_ID), + !el.hasAttribute(InspectorModeDataAttributes.ENTRY_ID), + !el.hasAttribute(InspectorModeDataAttributes.ASSET_ID), + !el.hasAttribute(InspectorModeDataAttributes.LOCALE), + !el.hasAttribute(InspectorModeDataAttributes.SPACE), + !el.hasAttribute(InspectorModeDataAttributes.ENVIRONMENT), + // it doesn't have any of the manually tagged attributes + ].every(Boolean), + ).length; + + return { + taggedElements, + manuallyTaggedCount: taggedElements.length - autoTaggedCount, + automaticallyTaggedCount: autoTaggedCount, + autoTaggedElements: uniqElementsForTagging, + }; } /** @@ -249,7 +266,7 @@ export function getAllTaggedEntries(): string[] { return [ ...new Set( getAllTaggedElements() - .map((element) => element.getAttribute(InspectorModeDataAttributes.ENTRY_ID)) + .taggedElements.map((element) => element.getAttribute(InspectorModeDataAttributes.ENTRY_ID)) .filter(Boolean) as string[], ), ]; diff --git a/packages/live-preview-sdk/src/messages.ts b/packages/live-preview-sdk/src/messages.ts index b8767e39..0e717ae9 100644 --- a/packages/live-preview-sdk/src/messages.ts +++ b/packages/live-preview-sdk/src/messages.ts @@ -37,6 +37,8 @@ export type ConnectedMessage = { connected: true; locale: string; taggedElementCount: number; + automaticallyTaggedElementCount: number; + manuallyTaggedElementCount: number; isInspectorEnabled: boolean; isLiveUpdatesEnabled: boolean; };