@datocms/content-link
Advanced tools
+1
-1
| { | ||
| "name": "@datocms/content-link", | ||
| "version": "0.3.14", | ||
| "version": "0.3.15", | ||
| "description": "DatoCMS visual editing overlays without Vercel dependencies.", | ||
@@ -5,0 +5,0 @@ "type": "module", |
+2
-1
@@ -77,3 +77,3 @@ # DatoCMS Content Link | ||
| - `root?: ParentNode`: Limit scanning to a specific container (default: `document`) | ||
| - `stripStega?: boolean`: Whether to strip stega-encoded invisible characters from text content after stamping (default: `false`) | ||
| - `stripStega?: boolean`: Whether to strip stega-encoded invisible characters from text content after stamping (default: `false`). Stega embeds invisible, zero-width UTF-8 characters into text content to encode editing metadata. | ||
| - When `false` (default): Stega encoding remains in the DOM, allowing controllers to be disposed and recreated on the same page. The invisible characters don't affect display but preserve the source of truth. | ||
@@ -372,2 +372,3 @@ - When `true`: Stega encoding is permanently removed from text nodes, providing clean `textContent` for programmatic access. However, recreating a controller on the same page won't detect elements since the encoding is lost. | ||
| - **Controller recreation issues**: If you dispose and recreate a controller on the same page, the second controller will only find elements if `stripStega: false` (the default). If you previously used `stripStega: true`, the stega encoding was permanently removed and cannot be recovered. In this case, you'll need to reload the page or re-fetch the content. | ||
| - **Layout issues caused by stega encoding**: The invisible zero-width characters can cause unexpected letter-spacing or text breaking out of containers. To fix this, either use `stripStega: true`, or use CSS: `[data-datocms-contains-stega] { letter-spacing: 0 !important; }`. This attribute is automatically added to elements with stega-encoded content when `stripStega: false` (the default). | ||
@@ -374,0 +375,0 @@ ## License |
@@ -18,4 +18,4 @@ /** | ||
| import { | ||
| AUTOMATIC_STAMP_ATTRIBUTE, | ||
| MANUAL_STAMP_ATTRIBUTE, | ||
| AUTOMATIC_TARGET_STAMP_ATTRIBUTE, | ||
| MANUAL_TARGET_STAMP_ATTRIBUTE, | ||
| STAMPED_ELEMENTS_SELECTOR, | ||
@@ -220,4 +220,4 @@ } from './domStamping/constants.js'; | ||
| const url = | ||
| element.getAttribute(MANUAL_STAMP_ATTRIBUTE) || | ||
| element.getAttribute(AUTOMATIC_STAMP_ATTRIBUTE); | ||
| element.getAttribute(MANUAL_TARGET_STAMP_ATTRIBUTE) || | ||
| element.getAttribute(AUTOMATIC_TARGET_STAMP_ATTRIBUTE); | ||
| if (url) { | ||
@@ -224,0 +224,0 @@ editUrls.add(url); |
@@ -6,5 +6,5 @@ /** | ||
| import { | ||
| AUTOMATIC_STAMP_ATTRIBUTE, | ||
| MANUAL_STAMP_ATTRIBUTE, | ||
| STAMPED_ELEMENTS_SELECTOR | ||
| AUTOMATIC_TARGET_STAMP_ATTRIBUTE, | ||
| MANUAL_TARGET_STAMP_ATTRIBUTE, | ||
| STAMPED_ELEMENTS_SELECTOR, | ||
| } from '../domStamping/constants.js'; | ||
@@ -17,3 +17,5 @@ | ||
| export function findEditableTarget(from: EventTarget | Element | null): EditableTarget | null { | ||
| export function findEditableTarget( | ||
| from: EventTarget | Element | null, | ||
| ): EditableTarget | null { | ||
| if (!from || !(from instanceof Element)) { | ||
@@ -28,3 +30,5 @@ return null; | ||
| const url = el.getAttribute(MANUAL_STAMP_ATTRIBUTE) || el.getAttribute(AUTOMATIC_STAMP_ATTRIBUTE); | ||
| const url = | ||
| el.getAttribute(MANUAL_TARGET_STAMP_ATTRIBUTE) || | ||
| el.getAttribute(AUTOMATIC_TARGET_STAMP_ATTRIBUTE); | ||
@@ -31,0 +35,0 @@ if (!url) { |
@@ -6,4 +6,6 @@ /** | ||
| */ | ||
| export const AUTOMATIC_STAMP_ATTRIBUTE = 'data-datocms-stega'; | ||
| export const MANUAL_STAMP_ATTRIBUTE = 'data-datocms-content-link-url'; | ||
| export const AUTOMATIC_TARGET_STAMP_ATTRIBUTE = | ||
| 'data-datocms-auto-content-link-url'; | ||
| export const AUTOMATIC_STEGA_STAMP_ATTRIBUTE = 'data-datocms-contains-stega'; | ||
| export const MANUAL_TARGET_STAMP_ATTRIBUTE = 'data-datocms-content-link-url'; | ||
| export const GROUP_ATTRIBUTE = 'data-datocms-content-link-group'; | ||
@@ -13,2 +15,2 @@ export const GROUP_BOUNDARY_ATTRIBUTE = 'data-datocms-content-link-boundary'; | ||
| export const STAMPED_ELEMENTS_SELECTOR = `[${MANUAL_STAMP_ATTRIBUTE}], [${AUTOMATIC_STAMP_ATTRIBUTE}]`; | ||
| export const STAMPED_ELEMENTS_SELECTOR = `[${MANUAL_TARGET_STAMP_ATTRIBUTE}], [${AUTOMATIC_TARGET_STAMP_ATTRIBUTE}]`; |
@@ -12,6 +12,7 @@ /** | ||
| import { | ||
| AUTOMATIC_STAMP_ATTRIBUTE, | ||
| AUTOMATIC_STEGA_STAMP_ATTRIBUTE, | ||
| AUTOMATIC_TARGET_STAMP_ATTRIBUTE, | ||
| GROUP_ATTRIBUTE, | ||
| GROUP_BOUNDARY_ATTRIBUTE, | ||
| SOURCE_STAMP_ATTRIBUTE | ||
| SOURCE_STAMP_ATTRIBUTE, | ||
| } from './constants.js'; | ||
@@ -22,3 +23,5 @@ | ||
| private readonly pendingElementsToStamp = new Set<ParentNode>(); | ||
| private readonly scheduleStamping = createScheduler(() => this.instantStampPendingElements()); | ||
| private readonly scheduleStamping = createScheduler(() => | ||
| this.instantStampPendingElements(), | ||
| ); | ||
@@ -28,5 +31,7 @@ constructor( | ||
| private readonly onStamp: (summary: StampSummary) => void, | ||
| private readonly stripStega: boolean = false | ||
| private readonly stripStega: boolean = false, | ||
| ) { | ||
| this.observer = new MutationObserver((mutations) => this.handleMutations(mutations)); | ||
| this.observer = new MutationObserver((mutations) => | ||
| this.handleMutations(mutations), | ||
| ); | ||
@@ -38,3 +43,3 @@ this.observer.observe(this.root, { | ||
| attributes: true, | ||
| attributeFilter: ['alt', SOURCE_STAMP_ATTRIBUTE] | ||
| attributeFilter: ['alt', SOURCE_STAMP_ATTRIBUTE], | ||
| }); | ||
@@ -49,6 +54,8 @@ | ||
| const nodes = this.root.querySelectorAll<HTMLElement>(`[${AUTOMATIC_STAMP_ATTRIBUTE}]`); | ||
| const nodes = this.root.querySelectorAll<HTMLElement>( | ||
| `[${AUTOMATIC_TARGET_STAMP_ATTRIBUTE}]`, | ||
| ); | ||
| for (const el of nodes) { | ||
| el.removeAttribute(AUTOMATIC_STAMP_ATTRIBUTE); | ||
| el.removeAttribute(AUTOMATIC_TARGET_STAMP_ATTRIBUTE); | ||
| } | ||
@@ -63,12 +70,24 @@ } | ||
| const node = mutation.target as Node; | ||
| const parent = (node.parentElement ?? node.parentNode ?? this.root) as ParentNode; | ||
| const parent = (node.parentElement ?? | ||
| node.parentNode ?? | ||
| this.root) as ParentNode; | ||
| this.pendingElementsToStamp.add(parent); | ||
| hasChanges = true; | ||
| } else if (mutation.type === 'attributes' && mutation.attributeName === 'alt') { | ||
| } else if ( | ||
| mutation.type === 'attributes' && | ||
| mutation.attributeName === 'alt' | ||
| ) { | ||
| const element = mutation.target as Element; | ||
| this.pendingElementsToStamp.add((element.parentElement ?? this.root) as ParentNode); | ||
| this.pendingElementsToStamp.add( | ||
| (element.parentElement ?? this.root) as ParentNode, | ||
| ); | ||
| hasChanges = true; | ||
| } else if (mutation.type === 'attributes' && mutation.attributeName === SOURCE_STAMP_ATTRIBUTE) { | ||
| } else if ( | ||
| mutation.type === 'attributes' && | ||
| mutation.attributeName === SOURCE_STAMP_ATTRIBUTE | ||
| ) { | ||
| const element = mutation.target as Element; | ||
| this.pendingElementsToStamp.add((element.parentElement ?? this.root) as ParentNode); | ||
| this.pendingElementsToStamp.add( | ||
| (element.parentElement ?? this.root) as ParentNode, | ||
| ); | ||
| hasChanges = true; | ||
@@ -119,3 +138,3 @@ } else if (mutation.type === 'childList') { | ||
| }, new Map<Element, string>()), | ||
| scope: this.root | ||
| scope: this.root, | ||
| }; | ||
@@ -145,3 +164,3 @@ | ||
| appliedStamps: new Map(), | ||
| scope: element | ||
| scope: element, | ||
| }; | ||
@@ -174,3 +193,3 @@ } | ||
| parent, | ||
| appliedStamps | ||
| appliedStamps, | ||
| ); | ||
@@ -192,3 +211,3 @@ | ||
| img, | ||
| appliedStamps | ||
| appliedStamps, | ||
| ); | ||
@@ -202,3 +221,5 @@ | ||
| // Third pass: inspect elements with data-datocms-content-link-source attribute | ||
| for (const el of element.querySelectorAll<HTMLElement>(`[${SOURCE_STAMP_ATTRIBUTE}]`)) { | ||
| for (const el of element.querySelectorAll<HTMLElement>( | ||
| `[${SOURCE_STAMP_ATTRIBUTE}]`, | ||
| )) { | ||
| const sourceValue = el.getAttribute(SOURCE_STAMP_ATTRIBUTE); | ||
@@ -209,3 +230,3 @@ | ||
| el, | ||
| appliedStamps | ||
| appliedStamps, | ||
| ); | ||
@@ -221,3 +242,3 @@ | ||
| appliedStamps: appliedStamps, | ||
| scope: element | ||
| scope: element, | ||
| }; | ||
@@ -231,3 +252,3 @@ | ||
| elementWithStega: Element | null, | ||
| appliedStamps: Map<Element, string> | ||
| appliedStamps: Map<Element, string>, | ||
| ): string | undefined { | ||
@@ -273,9 +294,16 @@ if (!value || !elementWithStega) { | ||
| // Stamp the attribute if it changed | ||
| const existingEditUrl = target.getAttribute(AUTOMATIC_STAMP_ATTRIBUTE); | ||
| const existingEditUrl = target.getAttribute( | ||
| AUTOMATIC_TARGET_STAMP_ATTRIBUTE, | ||
| ); | ||
| if (existingEditUrl !== decoded.href) { | ||
| target.setAttribute(AUTOMATIC_STAMP_ATTRIBUTE, decoded.href); | ||
| target.setAttribute(AUTOMATIC_TARGET_STAMP_ATTRIBUTE, decoded.href); | ||
| appliedStamps.set(target, decoded.href); | ||
| } | ||
| // When not stripping stega, mark the element that directly contains the stega data | ||
| if (!this.stripStega) { | ||
| elementWithStega.setAttribute(AUTOMATIC_STEGA_STAMP_ATTRIBUTE, ''); | ||
| } | ||
| return split.cleaned; | ||
@@ -289,3 +317,3 @@ } | ||
| incomingEl: Element, | ||
| incomingUrl: string | ||
| incomingUrl: string, | ||
| ) { | ||
@@ -292,0 +320,0 @@ const message = `[@datocms/content-link] Multiple stega-encoded payloads resolved to the same DOM element. Previous URL: ${originalUrl}. Incoming URL: ${incomingUrl}. Wrap each encoded block in its own element (for example by adding ${GROUP_ATTRIBUTE}).`; |
| import { HighlightOverlay } from '../../utils/HighlightOverlay.js'; | ||
| import { maybeScrollToNearestTarget, sleep, waitTwoRafs } from '../../utils/dom.js'; | ||
| import { | ||
| maybeScrollToNearestTarget, | ||
| sleep, | ||
| waitTwoRafs, | ||
| } from '../../utils/dom.js'; | ||
| import { extractInfo } from '../../utils/editUrl.js'; | ||
| import { | ||
| AUTOMATIC_STAMP_ATTRIBUTE, | ||
| MANUAL_STAMP_ATTRIBUTE, | ||
| STAMPED_ELEMENTS_SELECTOR | ||
| AUTOMATIC_TARGET_STAMP_ATTRIBUTE, | ||
| MANUAL_TARGET_STAMP_ATTRIBUTE, | ||
| STAMPED_ELEMENTS_SELECTOR, | ||
| } from '../domStamping/constants.js'; | ||
@@ -19,3 +23,3 @@ import { STAGGER_DELAY } from './FlashAllManager.js'; | ||
| private readonly itemId: string, | ||
| private readonly editUrlRegExp: RegExp | ||
| private readonly editUrlRegExp: RegExp, | ||
| ) {} | ||
@@ -33,4 +37,5 @@ | ||
| if (this.disposed) return; | ||
| const stampedElements = | ||
| this.wrapperElement.querySelectorAll<HTMLElement>(STAMPED_ELEMENTS_SELECTOR); | ||
| const stampedElements = this.wrapperElement.querySelectorAll<HTMLElement>( | ||
| STAMPED_ELEMENTS_SELECTOR, | ||
| ); | ||
@@ -40,4 +45,4 @@ const targetsSet = new Set<HTMLElement>(); | ||
| const editUrl = | ||
| element.getAttribute(MANUAL_STAMP_ATTRIBUTE) || | ||
| element.getAttribute(AUTOMATIC_STAMP_ATTRIBUTE); | ||
| element.getAttribute(MANUAL_TARGET_STAMP_ATTRIBUTE) || | ||
| element.getAttribute(AUTOMATIC_TARGET_STAMP_ATTRIBUTE); | ||
| if (editUrl) { | ||
@@ -88,4 +93,4 @@ // Filter by itemId - parse editUrl to extract itemId | ||
| this.overlays.map((overlay, index) => | ||
| overlay.disposeWithFadeOut(index * STAGGER_DELAY, abortController) | ||
| ) | ||
| overlay.disposeWithFadeOut(index * STAGGER_DELAY, abortController), | ||
| ), | ||
| ); | ||
@@ -92,0 +97,0 @@ |
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is too big to display
665733
0.56%6502
1.21%376
0.27%