payload-better-editor 1.0.2 → 1.0.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -81,12 +81,14 @@ import { useLatestRef } from './useLatestRef';
81
81
  // Fresh iframes report readyState='complete' on their initial
82
82
  // `about:blank` document before `src` has navigated. Skip and wait
83
83
  // for the real `load` event so we don't bind to an empty body.
84
- const href = doc.location.href;
85
- if (!href || href === 'about:blank') return;
84
+ // `doc.URL` is a non-nullable string on Document — safer than
85
+ // `doc.location.href`, which Firefox can briefly expose as null.
86
+ if (!doc.URL || doc.URL === 'about:blank') return;
86
87
  onLoadingChangeRef.current(false);
87
88
  bindToDocument(doc);
88
89
  };
89
- if (iframe.contentDocument && iframe.contentDocument.readyState === 'complete') {
90
+ const initialDoc = getSameOriginDocument(iframe);
91
+ if (initialDoc && initialDoc.readyState === 'complete') {
90
92
  onLoad();
91
93
  }
92
94
  iframe.addEventListener('load', onLoad);
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/hooks/usePreviewBinding.ts"],"sourcesContent":["'use client'\n\nimport { useCallback, useEffect, useRef, type RefObject } from 'react'\nimport { HoverToolbarController } from '../preview/HoverToolbarController'\nimport { installClickToFocus } from '../preview/installClickToFocus'\nimport { installHoverStyles } from '../preview/installHoverStyles'\nimport type { BlockActionMessage } from '../preview/protocol'\nimport { BLOCK_ID_SELECTOR } from '../internal/dom'\nimport { getSameOriginDocument } from '../internal/iframe'\nimport { useLatestRef } from './useLatestRef'\n\nexport type PreviewBindingSettings = {\n hoverColorTopLevel: string\n hoverColorNested: string\n hoverOutlineWidth: number\n showHoverToolbar: boolean\n hoverToolbarPosition: import('../internal/constants').HoverToolbarPosition\n}\n\nexport type UsePreviewBindingArgs = {\n iframeRef: RefObject<HTMLIFrameElement | null>\n settings: PreviewBindingSettings\n interactModeRef: RefObject<boolean>\n onFocusBlock: (id: string) => void\n onBlockAction: (id: string, action: BlockActionMessage['action']) => void\n onLoadingChange: (loading: boolean) => void\n}\n\nexport type UsePreviewBindingReturn = {\n controllerRef: RefObject<HoverToolbarController | null>\n isBoundRef: RefObject<boolean>\n}\n\n/**\n * Owns the iframe load → install styles + click handler + hover toolbar\n * lifecycle. Idempotent: tears down previous bindings before installing\n * new ones, and unbinds on unmount.\n */\nexport const usePreviewBinding = ({\n iframeRef,\n settings,\n interactModeRef,\n onFocusBlock,\n onBlockAction,\n onLoadingChange,\n}: UsePreviewBindingArgs): UsePreviewBindingReturn => {\n const teardownRef = useRef<(() => void) | null>(null)\n const controllerRef = useRef<HoverToolbarController | null>(null)\n const isBoundRef = useRef(false)\n // One-shot flags so dev-only console warnings don't repeat on every\n // iframe re-load during a single editor session.\n const warnedMissingBlocksRef = useRef(false)\n const warnedCrossOriginRef = useRef(false)\n const settingsRef = useLatestRef(settings)\n const onFocusBlockRef = useLatestRef(onFocusBlock)\n const onBlockActionRef = useLatestRef(onBlockAction)\n const onLoadingChangeRef = useLatestRef(onLoadingChange)\n\n const bindToDocument = useCallback(\n (doc: Document) => {\n teardownRef.current?.()\n controllerRef.current?.destroy()\n controllerRef.current = null\n\n const s = settingsRef.current\n\n const removeStyles = installHoverStyles(doc, {\n topColor: s.hoverColorTopLevel,\n nestedColor: s.hoverColorNested,\n outlineWidth: s.hoverOutlineWidth,\n })\n const removeClick = installClickToFocus(doc, (id) => onFocusBlockRef.current(id), {\n isEnabled: () => !interactModeRef.current,\n })\n\n if (s.showHoverToolbar) {\n controllerRef.current = new HoverToolbarController(doc, {\n position: s.hoverToolbarPosition,\n outlineWidth: s.hoverOutlineWidth,\n onAction: (id, action) => onBlockActionRef.current(id, action),\n })\n }\n\n // Dev-only sanity check: zero [data-better-editor-id] elements means\n // the consumer forgot to spread getBlockProps() on their block wrappers\n // (or the page just has no blocks yet — both look identical from here).\n // Warn at most once per editor session to avoid console spam.\n if (process.env.NODE_ENV !== 'production' && !warnedMissingBlocksRef.current) {\n const blockCount = doc.querySelectorAll(BLOCK_ID_SELECTOR).length\n if (blockCount === 0) {\n warnedMissingBlocksRef.current = true\n console.warn(\n \"[better-editor] no [data-better-editor-id] elements found in the preview iframe — if your page has blocks, wrap them with `getBlockProps(block)` from 'payload-better-editor/client' so click-to-edit works.\",\n )\n }\n }\n\n isBoundRef.current = true\n teardownRef.current = () => {\n removeStyles()\n removeClick()\n controllerRef.current?.destroy()\n controllerRef.current = null\n isBoundRef.current = false\n }\n },\n // eslint-disable-next-line react-hooks/exhaustive-deps -- refs are stable\n [],\n )\n\n // Bind once on mount; teardown on unmount. All inputs flow through\n // stable refs, so the load listener doesn't need to re-attach.\n useEffect(() => {\n const iframe = iframeRef.current\n if (!iframe) return\n\n const onLoad = () => {\n const doc = getSameOriginDocument(iframe)\n if (!doc) {\n onLoadingChangeRef.current(false)\n if (process.env.NODE_ENV !== 'production' && !warnedCrossOriginRef.current) {\n warnedCrossOriginRef.current = true\n console.warn(\n '[better-editor] preview iframe is cross-origin — click-to-edit, hover styles, and the in-iframe toolbar are disabled. Serve your preview URL from the same origin as the Payload admin.',\n )\n }\n return\n }\n // Fresh iframes report readyState='complete' on their initial\n // `about:blank` document before `src` has navigated. Skip and wait\n // for the real `load` event so we don't bind to an empty body.\n const href = doc.location.href\n if (!href || href === 'about:blank') return\n onLoadingChangeRef.current(false)\n bindToDocument(doc)\n }\n\n if (iframe.contentDocument && iframe.contentDocument.readyState === 'complete') {\n onLoad()\n }\n iframe.addEventListener('load', onLoad)\n\n return () => {\n iframe.removeEventListener('load', onLoad)\n teardownRef.current?.()\n teardownRef.current = null\n controllerRef.current?.destroy()\n controllerRef.current = null\n }\n // eslint-disable-next-line react-hooks/exhaustive-deps -- refs are stable\n }, [])\n\n return { controllerRef, isBoundRef }\n}\n"],"names":["useCallback","useEffect","useRef","HoverToolbarController","installClickToFocus","installHoverStyles","BLOCK_ID_SELECTOR","getSameOriginDocument","useLatestRef","usePreviewBinding","iframeRef","settings","interactModeRef","onFocusBlock","onBlockAction","onLoadingChange","teardownRef","controllerRef","isBoundRef","warnedMissingBlocksRef","warnedCrossOriginRef","settingsRef","onFocusBlockRef","onBlockActionRef","onLoadingChangeRef","bindToDocument","doc","current","destroy","s","removeStyles","topColor","hoverColorTopLevel","nestedColor","hoverColorNested","outlineWidth","hoverOutlineWidth","removeClick","id","isEnabled","showHoverToolbar","position","hoverToolbarPosition","onAction","action","process","env","NODE_ENV","blockCount","querySelectorAll","length","console","warn","iframe","onLoad","href","location","contentDocument","readyState","addEventListener","removeEventListener"],"mappings":"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"}
1
+ {"version":3,"sources":["../../src/hooks/usePreviewBinding.ts"],"sourcesContent":["'use client'\n\nimport { useCallback, useEffect, useRef, type RefObject } from 'react'\nimport { HoverToolbarController } from '../preview/HoverToolbarController'\nimport { installClickToFocus } from '../preview/installClickToFocus'\nimport { installHoverStyles } from '../preview/installHoverStyles'\nimport type { BlockActionMessage } from '../preview/protocol'\nimport { BLOCK_ID_SELECTOR } from '../internal/dom'\nimport { getSameOriginDocument } from '../internal/iframe'\nimport { useLatestRef } from './useLatestRef'\n\nexport type PreviewBindingSettings = {\n hoverColorTopLevel: string\n hoverColorNested: string\n hoverOutlineWidth: number\n showHoverToolbar: boolean\n hoverToolbarPosition: import('../internal/constants').HoverToolbarPosition\n}\n\nexport type UsePreviewBindingArgs = {\n iframeRef: RefObject<HTMLIFrameElement | null>\n settings: PreviewBindingSettings\n interactModeRef: RefObject<boolean>\n onFocusBlock: (id: string) => void\n onBlockAction: (id: string, action: BlockActionMessage['action']) => void\n onLoadingChange: (loading: boolean) => void\n}\n\nexport type UsePreviewBindingReturn = {\n controllerRef: RefObject<HoverToolbarController | null>\n isBoundRef: RefObject<boolean>\n}\n\n/**\n * Owns the iframe load → install styles + click handler + hover toolbar\n * lifecycle. Idempotent: tears down previous bindings before installing\n * new ones, and unbinds on unmount.\n */\nexport const usePreviewBinding = ({\n iframeRef,\n settings,\n interactModeRef,\n onFocusBlock,\n onBlockAction,\n onLoadingChange,\n}: UsePreviewBindingArgs): UsePreviewBindingReturn => {\n const teardownRef = useRef<(() => void) | null>(null)\n const controllerRef = useRef<HoverToolbarController | null>(null)\n const isBoundRef = useRef(false)\n // One-shot flags so dev-only console warnings don't repeat on every\n // iframe re-load during a single editor session.\n const warnedMissingBlocksRef = useRef(false)\n const warnedCrossOriginRef = useRef(false)\n const settingsRef = useLatestRef(settings)\n const onFocusBlockRef = useLatestRef(onFocusBlock)\n const onBlockActionRef = useLatestRef(onBlockAction)\n const onLoadingChangeRef = useLatestRef(onLoadingChange)\n\n const bindToDocument = useCallback(\n (doc: Document) => {\n teardownRef.current?.()\n controllerRef.current?.destroy()\n controllerRef.current = null\n\n const s = settingsRef.current\n\n const removeStyles = installHoverStyles(doc, {\n topColor: s.hoverColorTopLevel,\n nestedColor: s.hoverColorNested,\n outlineWidth: s.hoverOutlineWidth,\n })\n const removeClick = installClickToFocus(doc, (id) => onFocusBlockRef.current(id), {\n isEnabled: () => !interactModeRef.current,\n })\n\n if (s.showHoverToolbar) {\n controllerRef.current = new HoverToolbarController(doc, {\n position: s.hoverToolbarPosition,\n outlineWidth: s.hoverOutlineWidth,\n onAction: (id, action) => onBlockActionRef.current(id, action),\n })\n }\n\n // Dev-only sanity check: zero [data-better-editor-id] elements means\n // the consumer forgot to spread getBlockProps() on their block wrappers\n // (or the page just has no blocks yet — both look identical from here).\n // Warn at most once per editor session to avoid console spam.\n if (process.env.NODE_ENV !== 'production' && !warnedMissingBlocksRef.current) {\n const blockCount = doc.querySelectorAll(BLOCK_ID_SELECTOR).length\n if (blockCount === 0) {\n warnedMissingBlocksRef.current = true\n console.warn(\n \"[better-editor] no [data-better-editor-id] elements found in the preview iframe — if your page has blocks, wrap them with `getBlockProps(block)` from 'payload-better-editor/client' so click-to-edit works.\",\n )\n }\n }\n\n isBoundRef.current = true\n teardownRef.current = () => {\n removeStyles()\n removeClick()\n controllerRef.current?.destroy()\n controllerRef.current = null\n isBoundRef.current = false\n }\n },\n // eslint-disable-next-line react-hooks/exhaustive-deps -- refs are stable\n [],\n )\n\n // Bind once on mount; teardown on unmount. All inputs flow through\n // stable refs, so the load listener doesn't need to re-attach.\n useEffect(() => {\n const iframe = iframeRef.current\n if (!iframe) return\n\n const onLoad = () => {\n const doc = getSameOriginDocument(iframe)\n if (!doc) {\n onLoadingChangeRef.current(false)\n if (process.env.NODE_ENV !== 'production' && !warnedCrossOriginRef.current) {\n warnedCrossOriginRef.current = true\n console.warn(\n '[better-editor] preview iframe is cross-origin — click-to-edit, hover styles, and the in-iframe toolbar are disabled. Serve your preview URL from the same origin as the Payload admin.',\n )\n }\n return\n }\n // Fresh iframes report readyState='complete' on their initial\n // `about:blank` document before `src` has navigated. Skip and wait\n // for the real `load` event so we don't bind to an empty body.\n // `doc.URL` is a non-nullable string on Document — safer than\n // `doc.location.href`, which Firefox can briefly expose as null.\n if (!doc.URL || doc.URL === 'about:blank') return\n onLoadingChangeRef.current(false)\n bindToDocument(doc)\n }\n\n const initialDoc = getSameOriginDocument(iframe)\n if (initialDoc && initialDoc.readyState === 'complete') {\n onLoad()\n }\n iframe.addEventListener('load', onLoad)\n\n return () => {\n iframe.removeEventListener('load', onLoad)\n teardownRef.current?.()\n teardownRef.current = null\n controllerRef.current?.destroy()\n controllerRef.current = null\n }\n // eslint-disable-next-line react-hooks/exhaustive-deps -- refs are stable\n }, [])\n\n return { controllerRef, isBoundRef }\n}\n"],"names":["useCallback","useEffect","useRef","HoverToolbarController","installClickToFocus","installHoverStyles","BLOCK_ID_SELECTOR","getSameOriginDocument","useLatestRef","usePreviewBinding","iframeRef","settings","interactModeRef","onFocusBlock","onBlockAction","onLoadingChange","teardownRef","controllerRef","isBoundRef","warnedMissingBlocksRef","warnedCrossOriginRef","settingsRef","onFocusBlockRef","onBlockActionRef","onLoadingChangeRef","bindToDocument","doc","current","destroy","s","removeStyles","topColor","hoverColorTopLevel","nestedColor","hoverColorNested","outlineWidth","hoverOutlineWidth","removeClick","id","isEnabled","showHoverToolbar","position","hoverToolbarPosition","onAction","action","process","env","NODE_ENV","blockCount","querySelectorAll","length","console","warn","iframe","onLoad","URL","initialDoc","readyState","addEventListener","removeEventListener"],"mappings":"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"}
package/dist/index.js CHANGED
@@ -4,7 +4,24 @@ export { VERSION } from './version';
4
4
  const DEFAULT_BLOCKS_FIELD = 'layout';
5
5
  const TOGGLE_COMPONENT_PATH = 'payload-better-editor/client#LiveEditorToggle';
6
6
  const isDev = process.env.NODE_ENV !== 'production';
7
- const hasBlocksField = (fields, name)=>Array.isArray(fields) && fields.some((f)=>'name' in f && f.name === name);
7
+ /**
8
+ * Checks whether a field with the given `name` exists at the document's
9
+ * top-level data path. Recurses into presentational containers that do
10
+ * not introduce a path segment (`tabs` without a name, `row`, `collapsible`)
11
+ * but stops at `group` and named tabs, since those namespace their children.
12
+ */ const hasBlocksField = (fields, name)=>{
13
+ if (!Array.isArray(fields)) return false;
14
+ return fields.some((field)=>{
15
+ if ('name' in field && field.name === name) return true;
16
+ if (field.type === 'row' || field.type === 'collapsible') {
17
+ return hasBlocksField(field.fields, name);
18
+ }
19
+ if (field.type === 'tabs') {
20
+ return field.tabs.some((tab)=>'name' in tab && tab.name ? false : hasBlocksField(tab.fields, name));
21
+ }
22
+ return false;
23
+ });
24
+ };
8
25
  const withToggleInjected = (entity, slot, clientProps)=>{
9
26
  const admin = {
10
27
  ...entity.admin ?? {}
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts"],"sourcesContent":["import type { CollectionConfig, Config, Field, GlobalConfig } from 'payload'\nimport type { BetterEditorConfig } from './types'\nimport { BETTER_EDITOR_SETTINGS_BANNER_FIELD, betterEditorSettingsGlobal } from './global'\n\nexport type { BetterEditorConfig }\nexport type { BetterEditorSettings, HoverToolbarPosition } from './state/useBetterEditorSettings'\nexport type { SidebarPosition } from './internal/constants'\nexport { BETTER_EDITOR_SETTINGS_SLUG } from './global'\n\n/** Plugin signature — handy for typing plugin lists in consumer code. */\nexport type BetterEditorPlugin = (config: Config) => Config\n\nexport { VERSION } from './version'\n\nconst DEFAULT_BLOCKS_FIELD = 'layout'\nconst TOGGLE_COMPONENT_PATH = 'payload-better-editor/client#LiveEditorToggle'\nconst isDev = process.env.NODE_ENV !== 'production'\n\nconst hasBlocksField = (fields: Field[] | undefined, name: string): boolean =>\n Array.isArray(fields) && fields.some((f) => 'name' in f && f.name === name)\n\ntype ToggleSlot = 'edit' | 'elements'\n\ntype ToggleClientProps = {\n blocksField: string\n adminPortalSelector?: string\n storageNamespace?: string\n}\n\nconst withToggleInjected = <T extends CollectionConfig | GlobalConfig>(\n entity: T,\n slot: ToggleSlot,\n clientProps: ToggleClientProps,\n): T => {\n const admin = { ...(entity.admin ?? {}) } as NonNullable<T['admin']>\n const components = { ...(admin.components ?? {}) } as Record<string, unknown>\n const target = { ...((components[slot] as Record<string, unknown>) ?? {}) }\n const before = (target.beforeDocumentControls as unknown[]) ?? []\n return {\n ...entity,\n admin: {\n ...admin,\n components: {\n ...components,\n [slot]: {\n ...target,\n beforeDocumentControls: [\n ...before,\n { path: TOGGLE_COMPONENT_PATH, clientProps },\n ],\n },\n },\n },\n }\n}\n\nconst warnMissingBlocksField = (kind: 'collection' | 'global', slug: string, blocksField: string) => {\n\n console.warn(\n `[better-editor] ${kind} \"${slug}\" has no top-level field named \"${blocksField}\" — the sidebar Blocks tab will be empty. Set \\`blocksField\\` to the actual blocks field name.`,\n )\n}\n\n/**\n * Payload CMS plugin factory for the Better Editor overlay. Adds an\n * \"Open Better Editor\" toggle to the configured collections / globals\n * and registers a `BetterEditorSettings` global with editor-wide options.\n *\n * @example\n * import { betterEditor } from 'payload-better-editor'\n *\n * export default buildConfig({\n * plugins: [betterEditor({ collections: ['pages'] })],\n * // ...\n * })\n *\n * @see {@link BetterEditorConfig} for all options.\n */\nexport const betterEditor =\n (pluginOptions?: BetterEditorConfig): BetterEditorPlugin =>\n (config: Config): Config => {\n if (pluginOptions?.disabled) return config\n\n const collectionSlugs = new Set(pluginOptions?.collections ?? [])\n const globalSlugs = new Set(pluginOptions?.globals ?? [])\n const blocksField = pluginOptions?.blocksField || DEFAULT_BLOCKS_FIELD\n const clientProps: ToggleClientProps = {\n blocksField,\n adminPortalSelector: pluginOptions?.adminPortalSelector,\n storageNamespace: pluginOptions?.storageNamespace,\n }\n\n const showBanner = pluginOptions?.showSettingsBanner !== false\n const settingsGlobal: GlobalConfig = showBanner\n ? betterEditorSettingsGlobal\n : {\n ...betterEditorSettingsGlobal,\n fields: betterEditorSettingsGlobal.fields.filter(\n (f) => !('name' in f && f.name === BETTER_EDITOR_SETTINGS_BANNER_FIELD),\n ),\n }\n\n const existingGlobals = config.globals ?? []\n const hasSettingsGlobal = existingGlobals.some((g) => g.slug === settingsGlobal.slug)\n config.globals = hasSettingsGlobal\n ? existingGlobals\n : [...existingGlobals, settingsGlobal]\n\n if (collectionSlugs.size === 0 && globalSlugs.size === 0) {\n if (isDev) {\n\n console.warn(\n '[better-editor] plugin loaded with empty `collections` and `globals` — toggle button will not appear anywhere. Pass `collections: [\"pages\"]` (or similar) to BetterEditorConfig.',\n )\n }\n return config\n }\n\n if (collectionSlugs.size > 0 && config.collections) {\n config.collections = config.collections.map((collection) => {\n if (!collectionSlugs.has(collection.slug)) return collection\n if (isDev && !hasBlocksField(collection.fields, blocksField)) {\n warnMissingBlocksField('collection', collection.slug, blocksField)\n }\n return withToggleInjected(collection, 'edit', clientProps)\n })\n }\n\n if (globalSlugs.size > 0) {\n config.globals = (config.globals ?? []).map((global) => {\n if (!globalSlugs.has(global.slug)) return global\n if (isDev && !hasBlocksField(global.fields, blocksField)) {\n warnMissingBlocksField('global', global.slug, blocksField)\n }\n return withToggleInjected(global, 'elements', clientProps)\n })\n }\n\n return config\n }\n"],"names":["BETTER_EDITOR_SETTINGS_BANNER_FIELD","betterEditorSettingsGlobal","BETTER_EDITOR_SETTINGS_SLUG","VERSION","DEFAULT_BLOCKS_FIELD","TOGGLE_COMPONENT_PATH","isDev","process","env","NODE_ENV","hasBlocksField","fields","name","Array","isArray","some","f","withToggleInjected","entity","slot","clientProps","admin","components","target","before","beforeDocumentControls","path","warnMissingBlocksField","kind","slug","blocksField","console","warn","betterEditor","pluginOptions","config","disabled","collectionSlugs","Set","collections","globalSlugs","globals","adminPortalSelector","storageNamespace","showBanner","showSettingsBanner","settingsGlobal","filter","existingGlobals","hasSettingsGlobal","g","size","map","collection","has","global"],"mappings":"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"}
1
+ {"version":3,"sources":["../src/index.ts"],"sourcesContent":["import type { CollectionConfig, Config, Field, GlobalConfig } from 'payload'\nimport type { BetterEditorConfig } from './types'\nimport { BETTER_EDITOR_SETTINGS_BANNER_FIELD, betterEditorSettingsGlobal } from './global'\n\nexport type { BetterEditorConfig }\nexport type { BetterEditorSettings, HoverToolbarPosition } from './state/useBetterEditorSettings'\nexport type { SidebarPosition } from './internal/constants'\nexport { BETTER_EDITOR_SETTINGS_SLUG } from './global'\n\n/** Plugin signature — handy for typing plugin lists in consumer code. */\nexport type BetterEditorPlugin = (config: Config) => Config\n\nexport { VERSION } from './version'\n\nconst DEFAULT_BLOCKS_FIELD = 'layout'\nconst TOGGLE_COMPONENT_PATH = 'payload-better-editor/client#LiveEditorToggle'\nconst isDev = process.env.NODE_ENV !== 'production'\n\n/**\n * Checks whether a field with the given `name` exists at the document's\n * top-level data path. Recurses into presentational containers that do\n * not introduce a path segment (`tabs` without a name, `row`, `collapsible`)\n * but stops at `group` and named tabs, since those namespace their children.\n */\nconst hasBlocksField = (fields: Field[] | undefined, name: string): boolean => {\n if (!Array.isArray(fields)) return false\n return fields.some((field) => {\n if ('name' in field && field.name === name) return true\n if (field.type === 'row' || field.type === 'collapsible') {\n return hasBlocksField(field.fields, name)\n }\n if (field.type === 'tabs') {\n return field.tabs.some((tab) =>\n 'name' in tab && tab.name ? false : hasBlocksField(tab.fields, name),\n )\n }\n return false\n })\n}\n\ntype ToggleSlot = 'edit' | 'elements'\n\ntype ToggleClientProps = {\n blocksField: string\n adminPortalSelector?: string\n storageNamespace?: string\n}\n\nconst withToggleInjected = <T extends CollectionConfig | GlobalConfig>(\n entity: T,\n slot: ToggleSlot,\n clientProps: ToggleClientProps,\n): T => {\n const admin = { ...(entity.admin ?? {}) } as NonNullable<T['admin']>\n const components = { ...(admin.components ?? {}) } as Record<string, unknown>\n const target = { ...((components[slot] as Record<string, unknown>) ?? {}) }\n const before = (target.beforeDocumentControls as unknown[]) ?? []\n return {\n ...entity,\n admin: {\n ...admin,\n components: {\n ...components,\n [slot]: {\n ...target,\n beforeDocumentControls: [\n ...before,\n { path: TOGGLE_COMPONENT_PATH, clientProps },\n ],\n },\n },\n },\n }\n}\n\nconst warnMissingBlocksField = (kind: 'collection' | 'global', slug: string, blocksField: string) => {\n\n console.warn(\n `[better-editor] ${kind} \"${slug}\" has no top-level field named \"${blocksField}\" — the sidebar Blocks tab will be empty. Set \\`blocksField\\` to the actual blocks field name.`,\n )\n}\n\n/**\n * Payload CMS plugin factory for the Better Editor overlay. Adds an\n * \"Open Better Editor\" toggle to the configured collections / globals\n * and registers a `BetterEditorSettings` global with editor-wide options.\n *\n * @example\n * import { betterEditor } from 'payload-better-editor'\n *\n * export default buildConfig({\n * plugins: [betterEditor({ collections: ['pages'] })],\n * // ...\n * })\n *\n * @see {@link BetterEditorConfig} for all options.\n */\nexport const betterEditor =\n (pluginOptions?: BetterEditorConfig): BetterEditorPlugin =>\n (config: Config): Config => {\n if (pluginOptions?.disabled) return config\n\n const collectionSlugs = new Set(pluginOptions?.collections ?? [])\n const globalSlugs = new Set(pluginOptions?.globals ?? [])\n const blocksField = pluginOptions?.blocksField || DEFAULT_BLOCKS_FIELD\n const clientProps: ToggleClientProps = {\n blocksField,\n adminPortalSelector: pluginOptions?.adminPortalSelector,\n storageNamespace: pluginOptions?.storageNamespace,\n }\n\n const showBanner = pluginOptions?.showSettingsBanner !== false\n const settingsGlobal: GlobalConfig = showBanner\n ? betterEditorSettingsGlobal\n : {\n ...betterEditorSettingsGlobal,\n fields: betterEditorSettingsGlobal.fields.filter(\n (f) => !('name' in f && f.name === BETTER_EDITOR_SETTINGS_BANNER_FIELD),\n ),\n }\n\n const existingGlobals = config.globals ?? []\n const hasSettingsGlobal = existingGlobals.some((g) => g.slug === settingsGlobal.slug)\n config.globals = hasSettingsGlobal\n ? existingGlobals\n : [...existingGlobals, settingsGlobal]\n\n if (collectionSlugs.size === 0 && globalSlugs.size === 0) {\n if (isDev) {\n\n console.warn(\n '[better-editor] plugin loaded with empty `collections` and `globals` — toggle button will not appear anywhere. Pass `collections: [\"pages\"]` (or similar) to BetterEditorConfig.',\n )\n }\n return config\n }\n\n if (collectionSlugs.size > 0 && config.collections) {\n config.collections = config.collections.map((collection) => {\n if (!collectionSlugs.has(collection.slug)) return collection\n if (isDev && !hasBlocksField(collection.fields, blocksField)) {\n warnMissingBlocksField('collection', collection.slug, blocksField)\n }\n return withToggleInjected(collection, 'edit', clientProps)\n })\n }\n\n if (globalSlugs.size > 0) {\n config.globals = (config.globals ?? []).map((global) => {\n if (!globalSlugs.has(global.slug)) return global\n if (isDev && !hasBlocksField(global.fields, blocksField)) {\n warnMissingBlocksField('global', global.slug, blocksField)\n }\n return withToggleInjected(global, 'elements', clientProps)\n })\n }\n\n return config\n }\n"],"names":["BETTER_EDITOR_SETTINGS_BANNER_FIELD","betterEditorSettingsGlobal","BETTER_EDITOR_SETTINGS_SLUG","VERSION","DEFAULT_BLOCKS_FIELD","TOGGLE_COMPONENT_PATH","isDev","process","env","NODE_ENV","hasBlocksField","fields","name","Array","isArray","some","field","type","tabs","tab","withToggleInjected","entity","slot","clientProps","admin","components","target","before","beforeDocumentControls","path","warnMissingBlocksField","kind","slug","blocksField","console","warn","betterEditor","pluginOptions","config","disabled","collectionSlugs","Set","collections","globalSlugs","globals","adminPortalSelector","storageNamespace","showBanner","showSettingsBanner","settingsGlobal","filter","f","existingGlobals","hasSettingsGlobal","g","size","map","collection","has","global"],"mappings":"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"}
@@ -1,5 +1,9 @@
1
1
  /**
2
2
  * Reads `iframe.contentDocument` defensively. Throws cross-origin —
3
3
  * returning `null` lets callers bail out without try/catch boilerplate.
4
+ *
5
+ * Firefox can hand back a Document whose `location` is `null` during the
6
+ * transient pre-navigation phase of a freshly mounted iframe. Treat that
7
+ * as "not ready" so callers don't trip a TypeError on `doc.location.href`.
4
8
  */
5
9
  export declare const getSameOriginDocument: (iframe: HTMLIFrameElement) => Document | null;
@@ -1,9 +1,15 @@
1
1
  /**
2
2
  * Reads `iframe.contentDocument` defensively. Throws cross-origin —
3
3
  * returning `null` lets callers bail out without try/catch boilerplate.
4
+ *
5
+ * Firefox can hand back a Document whose `location` is `null` during the
6
+ * transient pre-navigation phase of a freshly mounted iframe. Treat that
7
+ * as "not ready" so callers don't trip a TypeError on `doc.location.href`.
4
8
  */ export const getSameOriginDocument = (iframe)=>{
5
9
  try {
6
- return iframe.contentDocument;
10
+ const doc = iframe.contentDocument;
11
+ if (!doc || !doc.location) return null;
12
+ return doc;
7
13
  } catch {
8
14
  return null;
9
15
  }
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/internal/iframe.ts"],"sourcesContent":["/**\n * Reads `iframe.contentDocument` defensively. Throws cross-origin —\n * returning `null` lets callers bail out without try/catch boilerplate.\n */\nexport const getSameOriginDocument = (iframe: HTMLIFrameElement): Document | null => {\n try {\n return iframe.contentDocument\n } catch {\n return null\n }\n}\n"],"names":["getSameOriginDocument","iframe","contentDocument"],"mappings":"AAAA;;;CAGC,GACD,OAAO,MAAMA,wBAAwB,CAACC;IACpC,IAAI;QACF,OAAOA,OAAOC,eAAe;IAC/B,EAAE,OAAM;QACN,OAAO;IACT;AACF,EAAC"}
1
+ {"version":3,"sources":["../../src/internal/iframe.ts"],"sourcesContent":["/**\n * Reads `iframe.contentDocument` defensively. Throws cross-origin —\n * returning `null` lets callers bail out without try/catch boilerplate.\n *\n * Firefox can hand back a Document whose `location` is `null` during the\n * transient pre-navigation phase of a freshly mounted iframe. Treat that\n * as \"not ready\" so callers don't trip a TypeError on `doc.location.href`.\n */\nexport const getSameOriginDocument = (iframe: HTMLIFrameElement): Document | null => {\n try {\n const doc = iframe.contentDocument\n if (!doc || !doc.location) return null\n return doc\n } catch {\n return null\n }\n}\n"],"names":["getSameOriginDocument","iframe","doc","contentDocument","location"],"mappings":"AAAA;;;;;;;CAOC,GACD,OAAO,MAAMA,wBAAwB,CAACC;IACpC,IAAI;QACF,MAAMC,MAAMD,OAAOE,eAAe;QAClC,IAAI,CAACD,OAAO,CAACA,IAAIE,QAAQ,EAAE,OAAO;QAClC,OAAOF;IACT,EAAE,OAAM;QACN,OAAO;IACT;AACF,EAAC"}
package/dist/version.d.ts CHANGED
@@ -1 +1 @@
1
- export declare const VERSION = "1.0.2";
1
+ export declare const VERSION = "1.0.4";
package/dist/version.js CHANGED
@@ -1,6 +1,6 @@
1
1
  // Single source of truth — re-exported from index.ts as `VERSION` and used
2
2
  // by client components (e.g. the settings footer) without dragging the
3
3
  // whole server entry into the client bundle.
4
- export const VERSION = '1.0.2';
4
+ export const VERSION = '1.0.4';
5
5
 
6
6
  //# sourceMappingURL=version.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/version.ts"],"sourcesContent":["// Single source of truth — re-exported from index.ts as `VERSION` and used\n// by client components (e.g. the settings footer) without dragging the\n// whole server entry into the client bundle.\nexport const VERSION = '1.0.2'\n"],"names":["VERSION"],"mappings":"AAAA,2EAA2E;AAC3E,uEAAuE;AACvE,6CAA6C;AAC7C,OAAO,MAAMA,UAAU,QAAO"}
1
+ {"version":3,"sources":["../src/version.ts"],"sourcesContent":["// Single source of truth — re-exported from index.ts as `VERSION` and used\n// by client components (e.g. the settings footer) without dragging the\n// whole server entry into the client bundle.\nexport const VERSION = '1.0.4'\n"],"names":["VERSION"],"mappings":"AAAA,2EAA2E;AAC3E,uEAAuE;AACvE,6CAA6C;AAC7C,OAAO,MAAMA,UAAU,QAAO"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "payload-better-editor",
3
- "version": "1.0.2",
3
+ "version": "1.0.4",
4
4
  "description": "Block editor plugin for Payload CMS that adds a side-by-side live-preview iframe and sidebar to the edit view.",
5
5
  "license": "MIT",
6
6
  "author": "scorpio-99",