From 89953b0f0f9a2cb9017febb5c523a35a7c113d07 Mon Sep 17 00:00:00 2001 From: Chris Helgert Date: Wed, 4 Oct 2023 08:38:02 +0200 Subject: [PATCH] feat(inspectorMode): move inspectorMode outlines on top of the iframe [] --- README.md | 26 +-- examples/gatsby/gatsby-browser.tsx | 1 - examples/gatsby/package.json | 2 +- .../next-13-app-router-graphql/app/layout.tsx | 1 - .../next-13-app-router-graphql/package.json | 10 +- .../next-13-app-router-ssr/app/layout.tsx | 2 - examples/next-13-app-router-ssr/package.json | 2 +- examples/nextjs-graphql/pages/_app.tsx | 1 - examples/nextjs-rest/package.json | 2 +- examples/nextjs-rest/pages/_app.tsx | 1 - examples/remix/.gitignore | 1 - examples/remix/app/root.tsx | 1 - examples/remix/package.json | 7 +- examples/vanilla-js/README.md | 12 -- examples/vanilla-js/index.html | 5 - examples/vanilla-js/package.json | 2 +- packages/live-preview-sdk/package.json | 7 +- packages/live-preview-sdk/src/constants.ts | 10 -- .../live-preview-sdk/src/fieldTaggingUtils.ts | 30 +++- packages/live-preview-sdk/src/index.ts | 2 - .../live-preview-sdk/src/inspectorMode.ts | 162 +++++++++--------- packages/live-preview-sdk/src/messages.ts | 12 +- packages/live-preview-sdk/src/styles.css | 50 ------ .../live-preview-sdk/style.css/package.json | 13 -- 24 files changed, 137 insertions(+), 225 deletions(-) delete mode 100644 packages/live-preview-sdk/src/styles.css delete mode 100644 packages/live-preview-sdk/style.css/package.json diff --git a/README.md b/README.md index f717def9..eed5c609 100644 --- a/README.md +++ b/README.md @@ -102,11 +102,8 @@ To use the inspector mode, you need to tag fields by adding the live preview dat You can do this in React via our helper function. -The necessary styles for the live edit tags can be found in the `@contentful/live-preview/style.css` file. - ```jsx import { ContentfulLivePreview } from '@contentful/live-preview'; -import '@contentful/live-preview/style.css'; ...

@@ -197,18 +194,13 @@ or npm install @contentful/live-preview ``` -2. Once you've got the data from Contentful, then you can initialize the live preview. You can use the `ContentfulLivePreview` class' [init function](#init-configuration) and add the stylesheet for field tagging as a stylesheet link. +2. Once you've got the data from Contentful, then you can initialize the live preview. You can use the `ContentfulLivePreview` class' [init function](#init-configuration). ```html Live Preview Example - diff --git a/examples/vanilla-js/package.json b/examples/vanilla-js/package.json index 6555e6db..450d8b3c 100644 --- a/examples/vanilla-js/package.json +++ b/examples/vanilla-js/package.json @@ -11,7 +11,7 @@ "author": "", "license": "MIT", "dependencies": { - "@contentful/live-preview": "^2.9.1", + "@contentful/live-preview": "latest", "contentful": "^10.5.0", "dotenv": "^16.3.1" }, diff --git a/packages/live-preview-sdk/package.json b/packages/live-preview-sdk/package.json index 82820b01..be7ba7b0 100644 --- a/packages/live-preview-sdk/package.json +++ b/packages/live-preview-sdk/package.json @@ -11,8 +11,7 @@ "type": "module", "files": [ "dist", - "react", - "style.css" + "react" ], "exports": { ".": { @@ -24,10 +23,6 @@ "import": "./dist/react.js", "require": "./dist/react.cjs", "types": "./dist/react.d.ts" - }, - "./style.css": { - "import": "./dist/style.css", - "require": "./dist/style.css" } }, "repository": { diff --git a/packages/live-preview-sdk/src/constants.ts b/packages/live-preview-sdk/src/constants.ts index 70657b26..30cd2508 100644 --- a/packages/live-preview-sdk/src/constants.ts +++ b/packages/live-preview-sdk/src/constants.ts @@ -1,13 +1,3 @@ -import { TagAttributes } from './types'; - -export const DATA_CURR_FIELD_ID = `current-${TagAttributes.FIELD_ID}`; -export const DATA_CURR_ENTRY_ID = `current-${TagAttributes.ENTRY_ID}`; -export const DATA_CURR_LOCALE = `current-${TagAttributes.LOCALE}`; -export const TOOLTIP_CLASS = 'contentful-tooltip'; - -export const TOOLTIP_HEIGHT = 32; -export const TOOLTIP_PADDING_LEFT = 5; - export const MAX_DEPTH = 10; export const LIVE_PREVIEW_EDITOR_SOURCE = 'live-preview-editor' as const; diff --git a/packages/live-preview-sdk/src/fieldTaggingUtils.ts b/packages/live-preview-sdk/src/fieldTaggingUtils.ts index f3a4e6cf..74f3ca74 100644 --- a/packages/live-preview-sdk/src/fieldTaggingUtils.ts +++ b/packages/live-preview-sdk/src/fieldTaggingUtils.ts @@ -1,12 +1,40 @@ import { TagAttributes } from './types'; +/** + * Parses the necessary information from the element and returns them. + * If **one** of the information is missing it returns null + */ +export function getTaggedInformation( + element: Element, + fallbackLocale?: string +): { fieldId: string; entryId: string; locale: string } | null { + const fieldId = element.getAttribute(TagAttributes.FIELD_ID); + const entryId = element.getAttribute(TagAttributes.ENTRY_ID); + const locale = element.getAttribute(TagAttributes.LOCALE) ?? fallbackLocale; + + if (!fieldId || !entryId || !locale) { + return null; + } + + return { fieldId, entryId, locale }; +} + +/** + * Query the document for all tagged elements + * **Attention:** Can include elements that have not all attributes, + * if you want to have only valid ones check for `getTaggedInformation` + */ +export function getAllTaggedElements(): Element[] { + return [...document.querySelectorAll(`[${TagAttributes.ENTRY_ID}]`)]; +} + /** * Returns a list of tagged entries on the page */ export function getAllTaggedEntries(): string[] { return [ ...new Set( - [...document.querySelectorAll(`[${TagAttributes.ENTRY_ID}]`)] + getAllTaggedElements() .map((element) => element.getAttribute(TagAttributes.ENTRY_ID)) .filter(Boolean) as string[] ), diff --git a/packages/live-preview-sdk/src/index.ts b/packages/live-preview-sdk/src/index.ts index fd1c7f31..c000019f 100644 --- a/packages/live-preview-sdk/src/index.ts +++ b/packages/live-preview-sdk/src/index.ts @@ -1,5 +1,3 @@ -import './styles.css'; - import { type DocumentNode } from 'graphql'; import { version } from '../package.json'; diff --git a/packages/live-preview-sdk/src/inspectorMode.ts b/packages/live-preview-sdk/src/inspectorMode.ts index 87b161bf..2d0d7f06 100644 --- a/packages/live-preview-sdk/src/inspectorMode.ts +++ b/packages/live-preview-sdk/src/inspectorMode.ts @@ -1,39 +1,32 @@ -import { - DATA_CURR_ENTRY_ID, - DATA_CURR_FIELD_ID, - DATA_CURR_LOCALE, - TOOLTIP_CLASS, - TOOLTIP_HEIGHT, - TOOLTIP_PADDING_LEFT, -} from './constants'; +import { getAllTaggedElements, getTaggedInformation } from './fieldTaggingUtils'; +import { sendMessageToEditor } from './helpers'; import { InspectorModeChangedMessage, LivePreviewPostMessageMethods, MessageFromEditor, - openEntryInEditorUtility, + InteractionEventMethods, } from './messages'; -import { TagAttributes } from './types'; export class InspectorMode { - private tooltip: HTMLButtonElement | null = null; // this tooltip scrolls to the correct field in the entry editor - private currentElementBesideTooltip: HTMLElement | null = null; // this element helps to position the tooltip private defaultLocale: string; private targetOrigin: string[]; + private isScrolling: boolean = false; + private scrollTimeout?: NodeJS.Timeout; + private hoveredElement?: HTMLElement; constructor({ locale, targetOrigin }: { locale: string; targetOrigin: string[] }) { - this.tooltip = null; - this.currentElementBesideTooltip = null; this.defaultLocale = locale; this.targetOrigin = targetOrigin; - this.updateTooltipPosition = this.updateTooltipPosition.bind(this); - this.addTooltipOnHover = this.addTooltipOnHover.bind(this); - this.createTooltip = this.createTooltip.bind(this); - this.clickHandler = this.clickHandler.bind(this); + // TODO: we we need this? + this.onMouseOver = this.onMouseOver.bind(this); + this.onScroll = this.onScroll.bind(this); + this.handleElementInteraction = this.handleElementInteraction.bind(this); + this.sendAllElements = this.sendAllElements.bind(this); - this.createTooltip(); - window.addEventListener('scroll', this.updateTooltipPosition); - window.addEventListener('mouseover', this.addTooltipOnHover); + // TODO: on resize do the something similar as onScroll + window.addEventListener('scroll', this.onScroll); + window.addEventListener('mouseover', this.onMouseOver); } // Handles incoming messages from Contentful @@ -42,40 +35,38 @@ export class InspectorMode { ('action' in data && data.action === 'INSPECTOR_MODE_CHANGED') || data.method === LivePreviewPostMessageMethods.INSPECTOR_MODE_CHANGED ) { + const { isInspectorActive } = data as InspectorModeChangedMessage; // Toggle the contentful-inspector--active class on the body element based on the isInspectorActive boolean - document.body.classList.toggle( - 'contentful-inspector--active', - (data as InspectorModeChangedMessage).isInspectorActive - ); - } - } - - // Updates the position of the tooltip - private updateTooltipPosition() { - if (!this.currentElementBesideTooltip || !this.tooltip) return false; - - const currentRectOfElement = this.currentElementBesideTooltip.getBoundingClientRect(); - const currentRectOfParentOfElement = this.tooltip.parentElement?.getBoundingClientRect(); + document.body.classList.toggle('contentful-inspector--active', isInspectorActive); - if (currentRectOfElement && currentRectOfParentOfElement) { - let upperBoundOfTooltip = currentRectOfElement.top - TOOLTIP_HEIGHT; - const left = currentRectOfElement.left - TOOLTIP_PADDING_LEFT; - - if (upperBoundOfTooltip < 0) { - if (currentRectOfElement.top < 0) upperBoundOfTooltip = currentRectOfElement.top; - else upperBoundOfTooltip = 0; + if (isInspectorActive) { + this.sendAllElements(); } + } + } - this.tooltip.style.top = upperBoundOfTooltip + 'px'; - this.tooltip.style.left = left + 'px'; + private onScroll() { + if (!this.isScrolling) { + this.isScrolling = true; + sendMessageToEditor(InteractionEventMethods.SCROLL_START, {} as any, this.targetOrigin); + } - return true; + if (this.scrollTimeout) { + clearTimeout(this.scrollTimeout); } - return false; + this.scrollTimeout = setTimeout(() => { + // No longer scrolling, let's update everything + this.isScrolling = false; + sendMessageToEditor(InteractionEventMethods.SCROLL_END, {} as any, this.targetOrigin); + this.sendAllElements(); + if (this.hoveredElement) { + this.handleElementInteraction(this.hoveredElement); + } + }, 150); } - private addTooltipOnHover(e: MouseEvent) { + private onMouseOver(e: MouseEvent) { const eventTargets = e.composedPath(); for (const eventTarget of eventTargets) { @@ -83,46 +74,57 @@ export class InspectorMode { if (element.nodeName === 'BODY') break; if (typeof element?.getAttribute !== 'function') continue; - const currFieldId = element.getAttribute(TagAttributes.FIELD_ID); - const currEntryId = element.getAttribute(TagAttributes.ENTRY_ID); - const currLocale = element.getAttribute(TagAttributes.LOCALE) ?? this.defaultLocale; - - if (currFieldId && currEntryId && currLocale) { - this.currentElementBesideTooltip = element; - - if (this.updateTooltipPosition()) { - this.tooltip?.setAttribute(DATA_CURR_FIELD_ID, currFieldId); - this.tooltip?.setAttribute(DATA_CURR_ENTRY_ID, currEntryId); - this.tooltip?.setAttribute(DATA_CURR_LOCALE, currLocale); - } - - break; + if (this.handleElementInteraction(element)) { + return; } } - } - private createTooltip() { - if (!document.querySelector(`.${TOOLTIP_CLASS}`)) { - const tooltip = document.createElement('button'); - tooltip.classList.add(TOOLTIP_CLASS); - tooltip.innerHTML = ` - - Edit`; - window.document.body.insertAdjacentElement('beforeend', tooltip); - tooltip.addEventListener('click', this.clickHandler); - this.tooltip = tooltip; - } - this.updateTooltipPosition(); + // Clear if no tagged element is hovered + this.hoveredElement = undefined; + sendMessageToEditor( + InteractionEventMethods.MOUSE_MOVE, + { + hoveredElement: null, + coordinates: null, + } as any, + this.targetOrigin + ); } - // responsible for handling the event when the user clicks on the edit button in the tooltip - private clickHandler() { - const fieldId = this.tooltip?.getAttribute(DATA_CURR_FIELD_ID); - const entryId = this.tooltip?.getAttribute(DATA_CURR_ENTRY_ID); - const locale = this.tooltip?.getAttribute(DATA_CURR_LOCALE) || this.defaultLocale; + private handleElementInteraction(element: HTMLElement): boolean { + const taggedInformation = getTaggedInformation(element, this.defaultLocale); - if (fieldId && entryId && locale) { - openEntryInEditorUtility(fieldId, entryId, locale, this.targetOrigin); + if (!taggedInformation) { + return false; } + + this.hoveredElement = element; + sendMessageToEditor( + InteractionEventMethods.MOUSE_MOVE, + { + hoveredElement: taggedInformation, + coordinates: element.getBoundingClientRect(), + } as any, + this.targetOrigin + ); + + return true; + } + + private sendAllElements() { + const entries = getAllTaggedElements().filter( + (element) => !!getTaggedInformation(element, this.defaultLocale) + ); + + // FIXME: typing + sendMessageToEditor( + 'TAGGED_ELEMENTS' as any, + { + elements: entries.map((e) => ({ + coordinates: e.getBoundingClientRect(), + })), + } as any, + this.targetOrigin + ); } } diff --git a/packages/live-preview-sdk/src/messages.ts b/packages/live-preview-sdk/src/messages.ts index 8bad2f64..8c8c6478 100644 --- a/packages/live-preview-sdk/src/messages.ts +++ b/packages/live-preview-sdk/src/messages.ts @@ -7,6 +7,12 @@ import type { LIVE_PREVIEW_EDITOR_SOURCE, LIVE_PREVIEW_SDK_SOURCE } from './cons import { sendMessageToEditor } from './helpers'; import type { ContentType, EntityReferenceMap } from './types'; +enum InteractionEventMethods { + MOUSE_MOVE = 'MOUSE_MOVE', + SCROLL_START = 'SCROLL_START', + SCROLL_END = 'SCROLL_END', +} + enum LivePreviewPostMessageMethods { CONNECTED = 'CONNECTED', DISCONNECTED = 'DISCONNECTED', @@ -39,8 +45,12 @@ export { LivePreviewPostMessageMethods, RequestEntitiesMessage, RequestedEntitiesMessage, + InteractionEventMethods, }; -export type PostMessageMethods = LivePreviewPostMessageMethods | StorePostMessageMethods; +export type PostMessageMethods = + | LivePreviewPostMessageMethods + | StorePostMessageMethods + | InteractionEventMethods; export type ConnectedMessage = { /** @deprecated use method instead */ diff --git a/packages/live-preview-sdk/src/styles.css b/packages/live-preview-sdk/src/styles.css deleted file mode 100644 index 2e9da646..00000000 --- a/packages/live-preview-sdk/src/styles.css +++ /dev/null @@ -1,50 +0,0 @@ -[data-contentful-field-id][data-contentful-entry-id] { - outline: 1px dashed rgba(64, 160, 255, 0) !important; - transition: outline-color 0.3s ease-in-out; -} - -.contentful-inspector--active [data-contentful-field-id][data-contentful-entry-id] { - outline: 1px dashed rgba(64, 160, 255, 1) !important; -} - -.contentful-inspector--active [data-contentful-field-id][data-contentful-entry-id]:hover { - outline: 2px solid rgba(64, 160, 255, 1) !important; -} - -button.contentful-tooltip { - padding: 0; - display: none; - outline: none; - border: none; - z-index: 999999 !important; - position: fixed; - margin: 0; - height: 32px; - width: 72px; - background: rgb(3, 111, 227); - font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol"; - font-weight: 500 !important; - font-size: 14px !important; - color: #ffffff !important; - transition: background 0.2s; - text-align: center !important; - border-radius: 6px !important; - justify-content: center; - align-items: center; - box-shadow: 0px 1px 0px rgba(17, 27, 43, 0.05); - box-sizing: border-box; - cursor: pointer; - gap: 6px; -} - -button.contentful-tooltip:hover { - background: rgb(0, 89, 200); -} - -button.contentful-tooltip:active:hover { - background: rgb(0, 65, 171); -} - -.contentful-inspector--active button.contentful-tooltip { - display: flex; -} diff --git a/packages/live-preview-sdk/style.css/package.json b/packages/live-preview-sdk/style.css/package.json deleted file mode 100644 index 02e433ff..00000000 --- a/packages/live-preview-sdk/style.css/package.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "name": "@contentful/live-preview/react/style.css", - "version": "0.0.0-determined-by-semantic-release", - "author": "Contentful GmbH", - "license": "MIT", - "description": "Preview SDK for both the field tagging connection + live content updates (style)", - "source": "../src/style.css", - "main": "../dist/style.css", - "style": "../dist/style.css", - "files": [ - "../dist/style.css" - ] -}