payload-better-editor 1.2.1 → 1.3.0
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.
- package/dist/admin/ErrorBoundary.d.ts +1 -10
- package/dist/admin/ErrorBoundary.js +14 -5
- package/dist/admin/ErrorBoundary.js.map +1 -1
- package/dist/admin/LiveEditorOverlay.js +4 -2
- package/dist/admin/LiveEditorOverlay.js.map +1 -1
- package/dist/admin/LiveEditorToggle.d.ts +2 -0
- package/dist/admin/LiveEditorToggle.js +14 -9
- package/dist/admin/LiveEditorToggle.js.map +1 -1
- package/dist/admin/PreviewFrame.js +6 -4
- package/dist/admin/PreviewFrame.js.map +1 -1
- package/dist/admin/PreviewToolbar.js +20 -13
- package/dist/admin/PreviewToolbar.js.map +1 -1
- package/dist/admin/SettingsBanner.js +10 -5
- package/dist/admin/SettingsBanner.js.map +1 -1
- package/dist/admin/ViewportToggle.js +30 -25
- package/dist/admin/ViewportToggle.js.map +1 -1
- package/dist/admin/blocks/AddBlockDrawer.js +7 -3
- package/dist/admin/blocks/AddBlockDrawer.js.map +1 -1
- package/dist/admin/blocks/BlockActionsToolbar.js +13 -11
- package/dist/admin/blocks/BlockActionsToolbar.js.map +1 -1
- package/dist/admin/blocks/BlockEmptyState.js +7 -3
- package/dist/admin/blocks/BlockEmptyState.js.map +1 -1
- package/dist/admin/blocks/BlockHeader.d.ts +1 -0
- package/dist/admin/blocks/BlockHeader.js +9 -8
- package/dist/admin/blocks/BlockHeader.js.map +1 -1
- package/dist/admin/blocks/schema.js +38 -7
- package/dist/admin/blocks/schema.js.map +1 -1
- package/dist/admin/sidebar/BlockSettingsTab.js +19 -4
- package/dist/admin/sidebar/BlockSettingsTab.js.map +1 -1
- package/dist/admin/sidebar/DocumentMetaTab.js +6 -2
- package/dist/admin/sidebar/DocumentMetaTab.js.map +1 -1
- package/dist/admin/sidebar/DocumentSettingsTab.js +6 -2
- package/dist/admin/sidebar/DocumentSettingsTab.js.map +1 -1
- package/dist/admin/sidebar/Sidebar.js +21 -16
- package/dist/admin/sidebar/Sidebar.js.map +1 -1
- package/dist/admin/sidebar/ValidationSummary.js +5 -2
- package/dist/admin/sidebar/ValidationSummary.js.map +1 -1
- package/dist/global.js +55 -39
- package/dist/global.js.map +1 -1
- package/dist/hooks/usePreviewBinding.js +6 -2
- package/dist/hooks/usePreviewBinding.js.map +1 -1
- package/dist/hooks/usePreviewSettingsSync.js +8 -3
- package/dist/hooks/usePreviewSettingsSync.js.map +1 -1
- package/dist/i18n/de.d.ts +2 -0
- package/dist/i18n/de.js +135 -0
- package/dist/i18n/de.js.map +1 -0
- package/dist/i18n/en.d.ts +2 -0
- package/dist/i18n/en.js +135 -0
- package/dist/i18n/en.js.map +1 -0
- package/dist/i18n/merge.d.ts +8 -0
- package/dist/i18n/merge.js +22 -0
- package/dist/i18n/merge.js.map +1 -0
- package/dist/i18n/types.d.ts +133 -0
- package/dist/i18n/types.js +3 -0
- package/dist/i18n/types.js.map +1 -0
- package/dist/i18n/useBetterEditorT.d.ts +2 -0
- package/dist/i18n/useBetterEditorT.js +10 -0
- package/dist/i18n/useBetterEditorT.js.map +1 -0
- package/dist/index.d.ts +2 -1
- package/dist/index.js +21 -2
- package/dist/index.js.map +1 -1
- package/dist/preview/HoverToolbar.d.ts +8 -0
- package/dist/preview/HoverToolbar.js +31 -29
- package/dist/preview/HoverToolbar.js.map +1 -1
- package/dist/preview/HoverToolbarController.d.ts +6 -0
- package/dist/preview/HoverToolbarController.js +20 -5
- package/dist/preview/HoverToolbarController.js.map +1 -1
- package/dist/preview/hover-css.d.ts +1 -0
- package/dist/preview/hover-css.js +18 -22
- package/dist/preview/hover-css.js.map +1 -1
- package/dist/styles/blocks-tab.css +1 -7
- package/dist/styles/overlay.css +2 -0
- package/dist/styles/sidebar.css +1 -0
- package/dist/styles/toggle.css +17 -0
- package/dist/types.d.ts +6 -0
- package/dist/types.js.map +1 -1
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/dist/version.js.map +1 -1
- package/package.json +13 -14
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/i18n/useBetterEditorT.ts"],"sourcesContent":["'use client'\n\nimport { useTranslation } from '@payloadcms/ui'\nimport type { BetterEditorTranslations } from './types'\nimport { en } from './en'\n\nexport const useBetterEditorT = (): BetterEditorTranslations => {\n const { i18n } = useTranslation()\n const custom = (i18n.translations as Record<string, unknown>)?.betterEditor\n return (custom as BetterEditorTranslations | undefined) ?? en\n}\n"],"names":["useTranslation","en","useBetterEditorT","i18n","custom","translations","betterEditor"],"mappings":"AAAA;AAEA,SAASA,cAAc,QAAQ,iBAAgB;AAE/C,SAASC,EAAE,QAAQ,OAAM;AAEzB,OAAO,MAAMC,mBAAmB;IAC9B,MAAM,EAAEC,IAAI,EAAE,GAAGH;IACjB,MAAMI,SAAUD,KAAKE,YAAY,EAA8BC;IAC/D,OAAO,AAACF,UAAmDH;AAC7D,EAAC"}
|
package/dist/index.d.ts
CHANGED
|
@@ -3,7 +3,8 @@ import type { BetterEditorConfig } from './types';
|
|
|
3
3
|
export type { BetterEditorConfig };
|
|
4
4
|
export type { BetterEditorSettings, HoverToolbarPosition } from './state/useBetterEditorSettings';
|
|
5
5
|
export type { SidebarPosition } from './internal/constants';
|
|
6
|
-
export {
|
|
6
|
+
export type { BetterEditorTranslations } from './i18n/types';
|
|
7
|
+
export { BETTER_EDITOR_SETTINGS_SLUG, betterEditorSettingsGlobal } from './global';
|
|
7
8
|
/** Plugin signature — handy for typing plugin lists in consumer code. */
|
|
8
9
|
export type BetterEditorPlugin = (config: Config) => Config;
|
|
9
10
|
export { VERSION } from './version';
|
package/dist/index.js
CHANGED
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
import { BETTER_EDITOR_SETTINGS_BANNER_FIELD, betterEditorSettingsGlobal } from './global';
|
|
2
2
|
import { normalizeEntities } from './internal/entities';
|
|
3
|
-
|
|
3
|
+
import { en } from './i18n/en';
|
|
4
|
+
import { de } from './i18n/de';
|
|
5
|
+
import { mergeTranslations } from './i18n/merge';
|
|
6
|
+
export { BETTER_EDITOR_SETTINGS_SLUG, betterEditorSettingsGlobal } from './global';
|
|
4
7
|
export { VERSION } from './version';
|
|
5
8
|
const DEFAULT_BLOCKS_FIELD = 'layout';
|
|
6
9
|
const TOGGLE_COMPONENT_PATH = 'payload-better-editor/client#LiveEditorToggle';
|
|
@@ -79,7 +82,8 @@ const warnMissingBlocksField = (kind, slug, blocksField)=>{
|
|
|
79
82
|
const toggleClientProps = (blocksField)=>({
|
|
80
83
|
blocksField,
|
|
81
84
|
adminPortalSelector: pluginOptions?.adminPortalSelector,
|
|
82
|
-
storageNamespace: pluginOptions?.storageNamespace
|
|
85
|
+
storageNamespace: pluginOptions?.storageNamespace,
|
|
86
|
+
hideToggleLabel: pluginOptions?.hideToggleLabel
|
|
83
87
|
});
|
|
84
88
|
const showBanner = pluginOptions?.showSettingsBanner !== false;
|
|
85
89
|
const settingsGlobal = showBanner ? betterEditorSettingsGlobal : {
|
|
@@ -92,6 +96,21 @@ const warnMissingBlocksField = (kind, slug, blocksField)=>{
|
|
|
92
96
|
...existingGlobals,
|
|
93
97
|
settingsGlobal
|
|
94
98
|
];
|
|
99
|
+
const existingTranslations = config.i18n?.translations ?? {};
|
|
100
|
+
config.i18n = {
|
|
101
|
+
...config.i18n,
|
|
102
|
+
translations: {
|
|
103
|
+
...existingTranslations,
|
|
104
|
+
en: {
|
|
105
|
+
...existingTranslations.en ?? {},
|
|
106
|
+
betterEditor: mergeTranslations(en, existingTranslations.en)
|
|
107
|
+
},
|
|
108
|
+
de: {
|
|
109
|
+
...existingTranslations.de ?? {},
|
|
110
|
+
betterEditor: mergeTranslations(de, existingTranslations.de)
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
};
|
|
95
114
|
if (collectionMap.size === 0 && globalMap.size === 0) {
|
|
96
115
|
if (isDev) {
|
|
97
116
|
console.warn('[better-editor] plugin loaded with empty `collections` and `globals` — toggle button will not appear anywhere. Pass `collections: ["pages"]` (or similar) to BetterEditorConfig.');
|
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'\nimport { normalizeEntities } from './internal/entities'\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 defaultBlocksField = pluginOptions?.blocksField || DEFAULT_BLOCKS_FIELD\n const collectionMap = normalizeEntities(pluginOptions?.collections, defaultBlocksField)\n const globalMap = normalizeEntities(pluginOptions?.globals, defaultBlocksField)\n const toggleClientProps = (blocksField: string): 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 (collectionMap.size === 0 && globalMap.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 (collectionMap.size > 0 && config.collections) {\n config.collections = config.collections.map((collection) => {\n const blocksField = collectionMap.get(collection.slug)\n if (blocksField === undefined) return collection\n if (isDev && !hasBlocksField(collection.fields, blocksField)) {\n warnMissingBlocksField('collection', collection.slug, blocksField)\n }\n return withToggleInjected(collection, 'edit', toggleClientProps(blocksField))\n })\n }\n\n if (globalMap.size > 0) {\n config.globals = (config.globals ?? []).map((global) => {\n const blocksField = globalMap.get(global.slug)\n if (blocksField === undefined) return global\n if (isDev && !hasBlocksField(global.fields, blocksField)) {\n warnMissingBlocksField('global', global.slug, blocksField)\n }\n return withToggleInjected(global, 'elements', toggleClientProps(blocksField))\n })\n }\n\n return config\n }\n"],"names":["BETTER_EDITOR_SETTINGS_BANNER_FIELD","betterEditorSettingsGlobal","normalizeEntities","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","defaultBlocksField","collectionMap","collections","globalMap","globals","toggleClientProps","adminPortalSelector","storageNamespace","showBanner","showSettingsBanner","settingsGlobal","filter","f","existingGlobals","hasSettingsGlobal","g","size","map","collection","get","undefined","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'\nimport { normalizeEntities } from './internal/entities'\nimport { en } from './i18n/en'\nimport { de } from './i18n/de'\nimport { mergeTranslations } from './i18n/merge'\n\nexport type { BetterEditorConfig }\nexport type { BetterEditorSettings, HoverToolbarPosition } from './state/useBetterEditorSettings'\nexport type { SidebarPosition } from './internal/constants'\nexport type { BetterEditorTranslations } from './i18n/types'\nexport { BETTER_EDITOR_SETTINGS_SLUG, betterEditorSettingsGlobal } 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 hideToggleLabel?: boolean\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 defaultBlocksField = pluginOptions?.blocksField || DEFAULT_BLOCKS_FIELD\n const collectionMap = normalizeEntities(pluginOptions?.collections, defaultBlocksField)\n const globalMap = normalizeEntities(pluginOptions?.globals, defaultBlocksField)\n const toggleClientProps = (blocksField: string): ToggleClientProps => ({\n blocksField,\n adminPortalSelector: pluginOptions?.adminPortalSelector,\n storageNamespace: pluginOptions?.storageNamespace,\n hideToggleLabel: pluginOptions?.hideToggleLabel,\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 const existingTranslations = config.i18n?.translations ?? {}\n config.i18n = {\n ...config.i18n,\n translations: {\n ...existingTranslations,\n en: {\n ...(existingTranslations.en as object ?? {}),\n betterEditor: mergeTranslations(en, existingTranslations.en),\n },\n de: {\n ...(existingTranslations.de as object ?? {}),\n betterEditor: mergeTranslations(de, existingTranslations.de),\n },\n },\n }\n\n if (collectionMap.size === 0 && globalMap.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 (collectionMap.size > 0 && config.collections) {\n config.collections = config.collections.map((collection) => {\n const blocksField = collectionMap.get(collection.slug)\n if (blocksField === undefined) return collection\n if (isDev && !hasBlocksField(collection.fields, blocksField)) {\n warnMissingBlocksField('collection', collection.slug, blocksField)\n }\n return withToggleInjected(collection, 'edit', toggleClientProps(blocksField))\n })\n }\n\n if (globalMap.size > 0) {\n config.globals = (config.globals ?? []).map((global) => {\n const blocksField = globalMap.get(global.slug)\n if (blocksField === undefined) return global\n if (isDev && !hasBlocksField(global.fields, blocksField)) {\n warnMissingBlocksField('global', global.slug, blocksField)\n }\n return withToggleInjected(global, 'elements', toggleClientProps(blocksField))\n })\n }\n\n return config\n }\n"],"names":["BETTER_EDITOR_SETTINGS_BANNER_FIELD","betterEditorSettingsGlobal","normalizeEntities","en","de","mergeTranslations","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","defaultBlocksField","collectionMap","collections","globalMap","globals","toggleClientProps","adminPortalSelector","storageNamespace","hideToggleLabel","showBanner","showSettingsBanner","settingsGlobal","filter","f","existingGlobals","hasSettingsGlobal","g","existingTranslations","i18n","translations","size","map","collection","get","undefined","global"],"mappings":"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"}
|
|
@@ -1,8 +1,16 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
2
|
import type { BlockActionMessage } from './protocol';
|
|
3
3
|
type Action = BlockActionMessage['action'];
|
|
4
|
+
export type HoverToolbarLabels = {
|
|
5
|
+
moveUp: string;
|
|
6
|
+
moveDown: string;
|
|
7
|
+
duplicate: string;
|
|
8
|
+
addBelow: string;
|
|
9
|
+
delete: string;
|
|
10
|
+
};
|
|
4
11
|
export type HoverToolbarProps = {
|
|
5
12
|
onAction: (action: Action) => void;
|
|
13
|
+
labels: HoverToolbarLabels;
|
|
6
14
|
};
|
|
7
15
|
export declare const HoverToolbar: React.FC<HoverToolbarProps>;
|
|
8
16
|
export {};
|
|
@@ -2,35 +2,36 @@
|
|
|
2
2
|
import { jsx as _jsx, Fragment as _Fragment } from "react/jsx-runtime";
|
|
3
3
|
import React from 'react';
|
|
4
4
|
import { ChevronDown, ChevronUp, CopyIcon, PlusIcon, TrashIcon } from '../admin/icons';
|
|
5
|
-
const
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
5
|
+
export const HoverToolbar = ({ onAction, labels })=>{
|
|
6
|
+
const buttons = [
|
|
7
|
+
{
|
|
8
|
+
action: 'move-up',
|
|
9
|
+
Icon: ChevronUp,
|
|
10
|
+
label: labels.moveUp
|
|
11
|
+
},
|
|
12
|
+
{
|
|
13
|
+
action: 'move-down',
|
|
14
|
+
Icon: ChevronDown,
|
|
15
|
+
label: labels.moveDown
|
|
16
|
+
},
|
|
17
|
+
{
|
|
18
|
+
action: 'duplicate',
|
|
19
|
+
Icon: CopyIcon,
|
|
20
|
+
label: labels.duplicate
|
|
21
|
+
},
|
|
22
|
+
{
|
|
23
|
+
action: 'add',
|
|
24
|
+
Icon: PlusIcon,
|
|
25
|
+
label: labels.addBelow
|
|
26
|
+
},
|
|
27
|
+
{
|
|
28
|
+
action: 'delete',
|
|
29
|
+
Icon: TrashIcon,
|
|
30
|
+
label: labels.delete
|
|
31
|
+
}
|
|
32
|
+
];
|
|
33
|
+
return /*#__PURE__*/ _jsx(_Fragment, {
|
|
34
|
+
children: buttons.map(({ action, Icon, label })=>/*#__PURE__*/ _jsx("button", {
|
|
34
35
|
type: "button",
|
|
35
36
|
"aria-label": label,
|
|
36
37
|
title: label,
|
|
@@ -44,5 +45,6 @@ export const HoverToolbar = ({ onAction })=>/*#__PURE__*/ _jsx(_Fragment, {
|
|
|
44
45
|
})
|
|
45
46
|
}, action))
|
|
46
47
|
});
|
|
48
|
+
};
|
|
47
49
|
|
|
48
50
|
//# sourceMappingURL=HoverToolbar.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/preview/HoverToolbar.tsx"],"sourcesContent":["'use client'\n\nimport React from 'react'\nimport { ChevronDown, ChevronUp, CopyIcon, PlusIcon, TrashIcon } from '../admin/icons'\nimport type { BlockActionMessage } from './protocol'\n\ntype Action = BlockActionMessage['action']\n\
|
|
1
|
+
{"version":3,"sources":["../../src/preview/HoverToolbar.tsx"],"sourcesContent":["'use client'\n\nimport React from 'react'\nimport { ChevronDown, ChevronUp, CopyIcon, PlusIcon, TrashIcon } from '../admin/icons'\nimport type { BlockActionMessage } from './protocol'\n\ntype Action = BlockActionMessage['action']\n\nexport type HoverToolbarLabels = {\n moveUp: string\n moveDown: string\n duplicate: string\n addBelow: string\n delete: string\n}\n\nexport type HoverToolbarProps = {\n onAction: (action: Action) => void\n labels: HoverToolbarLabels\n}\n\nexport const HoverToolbar: React.FC<HoverToolbarProps> = ({ onAction, labels }) => {\n const buttons: ReadonlyArray<{ action: Action; Icon: React.ComponentType<{ size?: number }>; label: string }> = [\n { action: 'move-up', Icon: ChevronUp, label: labels.moveUp },\n { action: 'move-down', Icon: ChevronDown, label: labels.moveDown },\n { action: 'duplicate', Icon: CopyIcon, label: labels.duplicate },\n { action: 'add', Icon: PlusIcon, label: labels.addBelow },\n { action: 'delete', Icon: TrashIcon, label: labels.delete },\n ]\n\n return (\n <>\n {buttons.map(({ action, Icon, label }) => (\n <button\n key={action}\n type=\"button\"\n aria-label={label}\n title={label}\n data-action={action}\n onClick={(e) => {\n e.stopPropagation()\n onAction(action)\n }}\n >\n <Icon size={14} />\n </button>\n ))}\n </>\n )\n}\n"],"names":["React","ChevronDown","ChevronUp","CopyIcon","PlusIcon","TrashIcon","HoverToolbar","onAction","labels","buttons","action","Icon","label","moveUp","moveDown","duplicate","addBelow","delete","map","button","type","aria-label","title","data-action","onClick","e","stopPropagation","size"],"mappings":"AAAA;;AAEA,OAAOA,WAAW,QAAO;AACzB,SAASC,WAAW,EAAEC,SAAS,EAAEC,QAAQ,EAAEC,QAAQ,EAAEC,SAAS,QAAQ,iBAAgB;AAkBtF,OAAO,MAAMC,eAA4C,CAAC,EAAEC,QAAQ,EAAEC,MAAM,EAAE;IAC5E,MAAMC,UAA0G;QAC9G;YAAEC,QAAQ;YAAWC,MAAMT;YAAWU,OAAOJ,OAAOK,MAAM;QAAC;QAC3D;YAAEH,QAAQ;YAAaC,MAAMV;YAAaW,OAAOJ,OAAOM,QAAQ;QAAC;QACjE;YAAEJ,QAAQ;YAAaC,MAAMR;YAAUS,OAAOJ,OAAOO,SAAS;QAAC;QAC/D;YAAEL,QAAQ;YAAOC,MAAMP;YAAUQ,OAAOJ,OAAOQ,QAAQ;QAAC;QACxD;YAAEN,QAAQ;YAAUC,MAAMN;YAAWO,OAAOJ,OAAOS,MAAM;QAAC;KAC3D;IAED,qBACE;kBACGR,QAAQS,GAAG,CAAC,CAAC,EAAER,MAAM,EAAEC,IAAI,EAAEC,KAAK,EAAE,iBACnC,KAACO;gBAECC,MAAK;gBACLC,cAAYT;gBACZU,OAAOV;gBACPW,eAAab;gBACbc,SAAS,CAACC;oBACRA,EAAEC,eAAe;oBACjBnB,SAASG;gBACX;0BAEA,cAAA,KAACC;oBAAKgB,MAAM;;eAVPjB;;AAef,EAAC"}
|
|
@@ -1,10 +1,15 @@
|
|
|
1
1
|
import type { HoverToolbarPosition } from '../internal/constants';
|
|
2
2
|
import type { BlockActionMessage } from './protocol';
|
|
3
|
+
import { type HoverToolbarLabels } from './HoverToolbar';
|
|
4
|
+
import type { BetterEditorTranslations } from '../i18n/types';
|
|
3
5
|
export type HoverToolbarOptions = {
|
|
4
6
|
position: HoverToolbarPosition;
|
|
5
7
|
outlineWidth: number;
|
|
6
8
|
onAction: (id: string, action: BlockActionMessage['action']) => void;
|
|
9
|
+
labels: HoverToolbarLabels;
|
|
7
10
|
};
|
|
11
|
+
export type { HoverToolbarLabels };
|
|
12
|
+
export declare const toHoverToolbarLabels: (actions: BetterEditorTranslations["blocks"]["actions"]) => HoverToolbarLabels;
|
|
8
13
|
export declare class HoverToolbarController {
|
|
9
14
|
private readonly doc;
|
|
10
15
|
private opts;
|
|
@@ -21,6 +26,7 @@ export declare class HoverToolbarController {
|
|
|
21
26
|
constructor(doc: Document, opts: HoverToolbarOptions);
|
|
22
27
|
private scheduleReselect;
|
|
23
28
|
update(opts: HoverToolbarOptions): void;
|
|
29
|
+
private renderToolbar;
|
|
24
30
|
select(id: string): void;
|
|
25
31
|
deselect(): void;
|
|
26
32
|
destroy(): void;
|
|
@@ -4,6 +4,16 @@ import { ACTIVE_CLASS, BLOCK_ID_ATTR, BLOCK_ID_SELECTOR } from '../internal/dom'
|
|
|
4
4
|
import { TOOLBAR_ID } from './hover-css';
|
|
5
5
|
import { HoverToolbar } from './HoverToolbar';
|
|
6
6
|
import { calculateToolbarPosition } from './toolbar-position';
|
|
7
|
+
// The toolbar only needs the five short action labels, not the longer `*Label`
|
|
8
|
+
// aria variants in `blocks.actions` — pick them in one place so the two preview
|
|
9
|
+
// hooks that build a controller stay in sync.
|
|
10
|
+
export const toHoverToolbarLabels = (actions)=>({
|
|
11
|
+
moveUp: actions.moveUp,
|
|
12
|
+
moveDown: actions.moveDown,
|
|
13
|
+
duplicate: actions.duplicate,
|
|
14
|
+
addBelow: actions.addBelow,
|
|
15
|
+
delete: actions.delete
|
|
16
|
+
});
|
|
7
17
|
const FALLBACK_TB_WIDTH = 120;
|
|
8
18
|
const FALLBACK_TB_HEIGHT = 32;
|
|
9
19
|
export class HoverToolbarController {
|
|
@@ -28,11 +38,7 @@ export class HoverToolbarController {
|
|
|
28
38
|
doc.body.appendChild(toolbar);
|
|
29
39
|
this.toolbar = toolbar;
|
|
30
40
|
this.root = createRoot(toolbar);
|
|
31
|
-
this.
|
|
32
|
-
onAction: (action)=>{
|
|
33
|
-
if (this.currentBlockId) this.opts.onAction(this.currentBlockId, action);
|
|
34
|
-
}
|
|
35
|
-
}));
|
|
41
|
+
this.renderToolbar();
|
|
36
42
|
this.onScroll = ()=>this.scheduleReposition();
|
|
37
43
|
doc.defaultView?.addEventListener('scroll', this.onScroll, true);
|
|
38
44
|
this.observer = new MutationObserver(()=>{
|
|
@@ -57,8 +63,17 @@ export class HoverToolbarController {
|
|
|
57
63
|
}
|
|
58
64
|
update(opts) {
|
|
59
65
|
this.opts = opts;
|
|
66
|
+
this.renderToolbar();
|
|
60
67
|
if (this.currentBlockEl) this.scheduleReposition();
|
|
61
68
|
}
|
|
69
|
+
renderToolbar() {
|
|
70
|
+
this.root.render(React.createElement(HoverToolbar, {
|
|
71
|
+
labels: this.opts.labels,
|
|
72
|
+
onAction: (action)=>{
|
|
73
|
+
if (this.currentBlockId) this.opts.onAction(this.currentBlockId, action);
|
|
74
|
+
}
|
|
75
|
+
}));
|
|
76
|
+
}
|
|
62
77
|
select(id) {
|
|
63
78
|
if (this.destroyed) return;
|
|
64
79
|
const escaped = typeof CSS !== 'undefined' && CSS.escape ? CSS.escape(id) : id.replace(/["\\]/g, '\\$&');
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/preview/HoverToolbarController.ts"],"sourcesContent":["import React from 'react'\nimport { createRoot, type Root } from 'react-dom/client'\nimport type { HoverToolbarPosition } from '../internal/constants'\nimport { ACTIVE_CLASS, BLOCK_ID_ATTR, BLOCK_ID_SELECTOR } from '../internal/dom'\nimport type { BlockActionMessage } from './protocol'\nimport { TOOLBAR_ID } from './hover-css'\nimport { HoverToolbar } from './HoverToolbar'\nimport { calculateToolbarPosition } from './toolbar-position'\n\nexport type HoverToolbarOptions = {\n position: HoverToolbarPosition\n outlineWidth: number\n onAction: (id: string, action: BlockActionMessage['action']) => void\n}\n\nconst FALLBACK_TB_WIDTH = 120\nconst FALLBACK_TB_HEIGHT = 32\n\nexport class HoverToolbarController {\n private readonly doc: Document\n private opts: HoverToolbarOptions\n private readonly toolbar: HTMLDivElement\n private readonly root: Root\n private destroyed = false\n private currentBlockId: string | null = null\n private currentBlockEl: HTMLElement | null = null\n private activeChain: HTMLElement[] = []\n private positionRaf = 0\n private observerRaf = 0\n private readonly onScroll: () => void\n private readonly observer: MutationObserver\n\n constructor(doc: Document, opts: HoverToolbarOptions) {\n this.doc = doc\n this.opts = opts\n\n doc.getElementById(TOOLBAR_ID)?.remove()\n const toolbar = doc.createElement('div')\n toolbar.id = TOOLBAR_ID\n doc.body.appendChild(toolbar)\n this.toolbar = toolbar\n\n this.root = createRoot(toolbar)\n this.root.render(\n React.createElement(HoverToolbar, {\n onAction: (action) => {\n if (this.currentBlockId) this.opts.onAction(this.currentBlockId, action)\n },\n }),\n )\n\n this.onScroll = () => this.scheduleReposition()\n doc.defaultView?.addEventListener('scroll', this.onScroll, true)\n\n this.observer = new MutationObserver(() => {\n if (this.destroyed || !this.currentBlockId) return\n // Coalesce mutation bursts into a single re-select on the next paint.\n // Re-using positionRaf avoids the previous two-tier RAF pyramid.\n this.scheduleReselect()\n })\n this.observer.observe(doc.body, { childList: true, subtree: true })\n }\n\n private scheduleReselect(): void {\n const view = this.doc.defaultView\n if (!view || this.observerRaf) return\n this.observerRaf = view.requestAnimationFrame(() => {\n this.observerRaf = 0\n if (this.destroyed || !this.currentBlockId) return\n this.select(this.currentBlockId)\n })\n }\n\n update(opts: HoverToolbarOptions): void {\n this.opts = opts\n if (this.currentBlockEl) this.scheduleReposition()\n }\n\n select(id: string): void {\n if (this.destroyed) return\n const escaped = typeof CSS !== 'undefined' && CSS.escape ? CSS.escape(id) : id.replace(/[\"\\\\]/g, '\\\\$&')\n const el = this.doc.querySelector<HTMLElement>(`[${BLOCK_ID_ATTR}=\"${escaped}\"]`)\n if (!el) {\n // Block not in DOM (yet) — keep id but hide the toolbar; a later\n // select(id) call after the iframe re-render will resolve it.\n this.currentBlockId = id\n this.currentBlockEl = null\n this.clearActive()\n this.toolbar.classList.remove('is-visible')\n return\n }\n this.currentBlockId = id\n this.currentBlockEl = el\n this.markActiveChain(el)\n const isNested = this.activeChain.length > 1\n this.toolbar.dataset.nested = isNested ? '1' : '0'\n this.toolbar.classList.add('is-visible')\n this.scheduleReposition()\n }\n\n deselect(): void {\n if (this.destroyed) return\n if (!this.currentBlockId) return\n this.clearActive()\n this.currentBlockId = null\n this.currentBlockEl = null\n this.toolbar.classList.remove('is-visible')\n }\n\n destroy(): void {\n if (this.destroyed) return\n this.destroyed = true\n this.observer.disconnect()\n const view = this.doc.defaultView\n view?.removeEventListener('scroll', this.onScroll, true)\n if (this.positionRaf) view?.cancelAnimationFrame(this.positionRaf)\n if (this.observerRaf) view?.cancelAnimationFrame(this.observerRaf)\n this.positionRaf = 0\n this.observerRaf = 0\n this.clearActive()\n this.currentBlockId = null\n this.currentBlockEl = null\n // Defer unmount — React 19 throws if it lands synchronously mid-render of\n // another tree; StrictMode also double-invokes us, so only remove the\n // toolbar node if we still own it.\n const { root, toolbar } = this\n queueMicrotask(() => {\n try { root.unmount() } catch { /* already unmounted */ }\n if (toolbar.isConnected) toolbar.remove()\n })\n }\n\n private scheduleReposition(): void {\n const view = this.doc.defaultView\n if (!view) {\n this.positionToolbar()\n return\n }\n if (this.positionRaf) view.cancelAnimationFrame(this.positionRaf)\n this.positionRaf = view.requestAnimationFrame(() => {\n this.positionRaf = 0\n this.positionToolbar()\n })\n }\n\n private positionToolbar(): void {\n const el = this.currentBlockEl\n if (!el || !el.isConnected) return\n const view = this.doc.defaultView\n if (!view) return\n const { top, left } = calculateToolbarPosition(\n el.getBoundingClientRect(),\n {\n width: this.toolbar.offsetWidth || FALLBACK_TB_WIDTH,\n height: this.toolbar.offsetHeight || FALLBACK_TB_HEIGHT,\n },\n { scrollX: view.scrollX, scrollY: view.scrollY },\n this.opts.position,\n this.opts.outlineWidth,\n )\n const { style } = this.toolbar\n style.top = `${top}px`\n style.left = `${left}px`\n style.right = 'auto'\n }\n\n // Only clears the chain we marked, avoiding a full-document scan.\n private clearActive(): void {\n for (const node of this.activeChain) node.classList.remove(ACTIVE_CLASS)\n this.activeChain = []\n }\n\n private markActiveChain(el: HTMLElement): void {\n this.clearActive()\n const chain: HTMLElement[] = []\n for (\n let cur: HTMLElement | null = el;\n cur;\n cur = cur.parentElement?.closest<HTMLElement>(BLOCK_ID_SELECTOR) ?? null\n ) {\n cur.classList.add(ACTIVE_CLASS)\n chain.push(cur)\n }\n this.activeChain = chain\n }\n}\n"],"names":["React","createRoot","ACTIVE_CLASS","BLOCK_ID_ATTR","BLOCK_ID_SELECTOR","TOOLBAR_ID","HoverToolbar","calculateToolbarPosition","FALLBACK_TB_WIDTH","FALLBACK_TB_HEIGHT","HoverToolbarController","doc","opts","toolbar","root","destroyed","currentBlockId","currentBlockEl","activeChain","positionRaf","observerRaf","onScroll","observer","constructor","getElementById","remove","createElement","id","body","appendChild","render","onAction","action","scheduleReposition","defaultView","addEventListener","MutationObserver","scheduleReselect","observe","childList","subtree","view","requestAnimationFrame","select","update","escaped","CSS","escape","replace","el","querySelector","clearActive","classList","markActiveChain","isNested","length","dataset","nested","add","deselect","destroy","disconnect","removeEventListener","cancelAnimationFrame","queueMicrotask","unmount","isConnected","positionToolbar","top","left","getBoundingClientRect","width","offsetWidth","height","offsetHeight","scrollX","scrollY","position","outlineWidth","style","right","node","chain","cur","parentElement","closest","push"],"mappings":"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"}
|
|
1
|
+
{"version":3,"sources":["../../src/preview/HoverToolbarController.ts"],"sourcesContent":["import React from 'react'\nimport { createRoot, type Root } from 'react-dom/client'\nimport type { HoverToolbarPosition } from '../internal/constants'\nimport { ACTIVE_CLASS, BLOCK_ID_ATTR, BLOCK_ID_SELECTOR } from '../internal/dom'\nimport type { BlockActionMessage } from './protocol'\nimport { TOOLBAR_ID } from './hover-css'\nimport { HoverToolbar, type HoverToolbarLabels } from './HoverToolbar'\nimport { calculateToolbarPosition } from './toolbar-position'\nimport type { BetterEditorTranslations } from '../i18n/types'\n\nexport type HoverToolbarOptions = {\n position: HoverToolbarPosition\n outlineWidth: number\n onAction: (id: string, action: BlockActionMessage['action']) => void\n labels: HoverToolbarLabels\n}\n\nexport type { HoverToolbarLabels }\n\n// The toolbar only needs the five short action labels, not the longer `*Label`\n// aria variants in `blocks.actions` — pick them in one place so the two preview\n// hooks that build a controller stay in sync.\nexport const toHoverToolbarLabels = (\n actions: BetterEditorTranslations['blocks']['actions'],\n): HoverToolbarLabels => ({\n moveUp: actions.moveUp,\n moveDown: actions.moveDown,\n duplicate: actions.duplicate,\n addBelow: actions.addBelow,\n delete: actions.delete,\n})\n\nconst FALLBACK_TB_WIDTH = 120\nconst FALLBACK_TB_HEIGHT = 32\n\nexport class HoverToolbarController {\n private readonly doc: Document\n private opts: HoverToolbarOptions\n private readonly toolbar: HTMLDivElement\n private readonly root: Root\n private destroyed = false\n private currentBlockId: string | null = null\n private currentBlockEl: HTMLElement | null = null\n private activeChain: HTMLElement[] = []\n private positionRaf = 0\n private observerRaf = 0\n private readonly onScroll: () => void\n private readonly observer: MutationObserver\n\n constructor(doc: Document, opts: HoverToolbarOptions) {\n this.doc = doc\n this.opts = opts\n\n doc.getElementById(TOOLBAR_ID)?.remove()\n const toolbar = doc.createElement('div')\n toolbar.id = TOOLBAR_ID\n doc.body.appendChild(toolbar)\n this.toolbar = toolbar\n\n this.root = createRoot(toolbar)\n this.renderToolbar()\n\n this.onScroll = () => this.scheduleReposition()\n doc.defaultView?.addEventListener('scroll', this.onScroll, true)\n\n this.observer = new MutationObserver(() => {\n if (this.destroyed || !this.currentBlockId) return\n // Coalesce mutation bursts into a single re-select on the next paint.\n // Re-using positionRaf avoids the previous two-tier RAF pyramid.\n this.scheduleReselect()\n })\n this.observer.observe(doc.body, { childList: true, subtree: true })\n }\n\n private scheduleReselect(): void {\n const view = this.doc.defaultView\n if (!view || this.observerRaf) return\n this.observerRaf = view.requestAnimationFrame(() => {\n this.observerRaf = 0\n if (this.destroyed || !this.currentBlockId) return\n this.select(this.currentBlockId)\n })\n }\n\n update(opts: HoverToolbarOptions): void {\n this.opts = opts\n this.renderToolbar()\n if (this.currentBlockEl) this.scheduleReposition()\n }\n\n private renderToolbar(): void {\n this.root.render(\n React.createElement(HoverToolbar, {\n labels: this.opts.labels,\n onAction: (action) => {\n if (this.currentBlockId) this.opts.onAction(this.currentBlockId, action)\n },\n }),\n )\n }\n\n select(id: string): void {\n if (this.destroyed) return\n const escaped = typeof CSS !== 'undefined' && CSS.escape ? CSS.escape(id) : id.replace(/[\"\\\\]/g, '\\\\$&')\n const el = this.doc.querySelector<HTMLElement>(`[${BLOCK_ID_ATTR}=\"${escaped}\"]`)\n if (!el) {\n // Block not in DOM (yet) — keep id but hide the toolbar; a later\n // select(id) call after the iframe re-render will resolve it.\n this.currentBlockId = id\n this.currentBlockEl = null\n this.clearActive()\n this.toolbar.classList.remove('is-visible')\n return\n }\n this.currentBlockId = id\n this.currentBlockEl = el\n this.markActiveChain(el)\n const isNested = this.activeChain.length > 1\n this.toolbar.dataset.nested = isNested ? '1' : '0'\n this.toolbar.classList.add('is-visible')\n this.scheduleReposition()\n }\n\n deselect(): void {\n if (this.destroyed) return\n if (!this.currentBlockId) return\n this.clearActive()\n this.currentBlockId = null\n this.currentBlockEl = null\n this.toolbar.classList.remove('is-visible')\n }\n\n destroy(): void {\n if (this.destroyed) return\n this.destroyed = true\n this.observer.disconnect()\n const view = this.doc.defaultView\n view?.removeEventListener('scroll', this.onScroll, true)\n if (this.positionRaf) view?.cancelAnimationFrame(this.positionRaf)\n if (this.observerRaf) view?.cancelAnimationFrame(this.observerRaf)\n this.positionRaf = 0\n this.observerRaf = 0\n this.clearActive()\n this.currentBlockId = null\n this.currentBlockEl = null\n // Defer unmount — React 19 throws if it lands synchronously mid-render of\n // another tree; StrictMode also double-invokes us, so only remove the\n // toolbar node if we still own it.\n const { root, toolbar } = this\n queueMicrotask(() => {\n try { root.unmount() } catch { /* already unmounted */ }\n if (toolbar.isConnected) toolbar.remove()\n })\n }\n\n private scheduleReposition(): void {\n const view = this.doc.defaultView\n if (!view) {\n this.positionToolbar()\n return\n }\n if (this.positionRaf) view.cancelAnimationFrame(this.positionRaf)\n this.positionRaf = view.requestAnimationFrame(() => {\n this.positionRaf = 0\n this.positionToolbar()\n })\n }\n\n private positionToolbar(): void {\n const el = this.currentBlockEl\n if (!el || !el.isConnected) return\n const view = this.doc.defaultView\n if (!view) return\n const { top, left } = calculateToolbarPosition(\n el.getBoundingClientRect(),\n {\n width: this.toolbar.offsetWidth || FALLBACK_TB_WIDTH,\n height: this.toolbar.offsetHeight || FALLBACK_TB_HEIGHT,\n },\n { scrollX: view.scrollX, scrollY: view.scrollY },\n this.opts.position,\n this.opts.outlineWidth,\n )\n const { style } = this.toolbar\n style.top = `${top}px`\n style.left = `${left}px`\n style.right = 'auto'\n }\n\n // Only clears the chain we marked, avoiding a full-document scan.\n private clearActive(): void {\n for (const node of this.activeChain) node.classList.remove(ACTIVE_CLASS)\n this.activeChain = []\n }\n\n private markActiveChain(el: HTMLElement): void {\n this.clearActive()\n const chain: HTMLElement[] = []\n for (\n let cur: HTMLElement | null = el;\n cur;\n cur = cur.parentElement?.closest<HTMLElement>(BLOCK_ID_SELECTOR) ?? null\n ) {\n cur.classList.add(ACTIVE_CLASS)\n chain.push(cur)\n }\n this.activeChain = chain\n }\n}\n"],"names":["React","createRoot","ACTIVE_CLASS","BLOCK_ID_ATTR","BLOCK_ID_SELECTOR","TOOLBAR_ID","HoverToolbar","calculateToolbarPosition","toHoverToolbarLabels","actions","moveUp","moveDown","duplicate","addBelow","delete","FALLBACK_TB_WIDTH","FALLBACK_TB_HEIGHT","HoverToolbarController","doc","opts","toolbar","root","destroyed","currentBlockId","currentBlockEl","activeChain","positionRaf","observerRaf","onScroll","observer","constructor","getElementById","remove","createElement","id","body","appendChild","renderToolbar","scheduleReposition","defaultView","addEventListener","MutationObserver","scheduleReselect","observe","childList","subtree","view","requestAnimationFrame","select","update","render","labels","onAction","action","escaped","CSS","escape","replace","el","querySelector","clearActive","classList","markActiveChain","isNested","length","dataset","nested","add","deselect","destroy","disconnect","removeEventListener","cancelAnimationFrame","queueMicrotask","unmount","isConnected","positionToolbar","top","left","getBoundingClientRect","width","offsetWidth","height","offsetHeight","scrollX","scrollY","position","outlineWidth","style","right","node","chain","cur","parentElement","closest","push"],"mappings":"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"}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
export declare const HOVER_STYLE_ID = "better-editor-hover-style";
|
|
2
|
+
export declare const HOVER_VARS_STYLE_ID = "better-editor-hover-vars";
|
|
2
3
|
export declare const TOOLBAR_ID = "better-editor-block-toolbar";
|
|
3
4
|
export declare const INTERACT_BODY_ATTR = "data-bee-interact";
|
|
4
5
|
export declare const HOVER_CSS = "\n body:not([data-bee-interact]) [data-better-editor-id] { cursor: pointer; }\n body:not([data-bee-interact]) [data-better-editor-id]:hover,\n body:not([data-bee-interact]) [data-better-editor-id].better-editor-active {\n outline: var(--bee-outline-width) solid var(--bee-top);\n outline-offset: calc(-1 * var(--bee-outline-width) - 1px);\n box-shadow: inset 0 0 0 100vmax color-mix(in srgb, var(--bee-top) 10%, transparent);\n }\n body:not([data-bee-interact]) [data-better-editor-id] [data-better-editor-id]:hover,\n body:not([data-bee-interact]) [data-better-editor-id] [data-better-editor-id].better-editor-active {\n outline-color: var(--bee-nested);\n box-shadow: inset 0 0 0 100vmax color-mix(in srgb, var(--bee-nested) 10%, transparent);\n }\n body[data-bee-interact] #better-editor-block-toolbar { display: none; }\n\n #better-editor-block-toolbar {\n position: absolute;\n z-index: var(--better-editor-z-toolbar, 2147483647);\n display: none;\n gap: 2px;\n padding: 3px;\n border-radius: 4px;\n background: var(--bee-top);\n color: #fff;\n box-shadow: 0 4px 12px rgba(0, 0, 0, 0.18);\n font-family: system-ui, sans-serif;\n }\n #better-editor-block-toolbar[data-nested=\"1\"] { background: var(--bee-nested); }\n #better-editor-block-toolbar.is-visible { display: inline-flex; }\n #better-editor-block-toolbar button {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n width: 26px;\n height: 26px;\n padding: 0;\n border: 0;\n border-radius: 3px;\n background: transparent;\n color: inherit;\n cursor: pointer;\n }\n #better-editor-block-toolbar button:hover { background: rgba(255, 255, 255, 0.18); }\n #better-editor-block-toolbar button[data-action=\"delete\"]:hover { background: rgba(0, 0, 0, 0.25); }\n";
|
|
@@ -1,9 +1,7 @@
|
|
|
1
1
|
import { ACTIVE_CLASS, BLOCK_ID_ATTR } from '../internal/dom';
|
|
2
2
|
export const HOVER_STYLE_ID = 'better-editor-hover-style';
|
|
3
|
+
export const HOVER_VARS_STYLE_ID = 'better-editor-hover-vars';
|
|
3
4
|
export const TOOLBAR_ID = 'better-editor-block-toolbar';
|
|
4
|
-
// Set on doc.body to disable hover affordances + click-to-focus while
|
|
5
|
-
// the user wants to interact with the consumer page (forms, accordions,
|
|
6
|
-
// links). All hover/active rules below are gated on its absence.
|
|
7
5
|
export const INTERACT_BODY_ATTR = 'data-bee-interact';
|
|
8
6
|
const VAR_TOP = '--bee-top';
|
|
9
7
|
const VAR_NESTED = '--bee-nested';
|
|
@@ -66,29 +64,27 @@ const warnInvalid = (kind, value)=>{
|
|
|
66
64
|
console.warn(`[better-editor] ignoring invalid ${kind}:`, value);
|
|
67
65
|
}
|
|
68
66
|
};
|
|
67
|
+
const buildVarsRule = (vars)=>{
|
|
68
|
+
const decls = [];
|
|
69
|
+
if (isValidColor(vars.topColor)) decls.push(`${VAR_TOP}: ${vars.topColor.trim()};`);
|
|
70
|
+
else warnInvalid('topColor', vars.topColor);
|
|
71
|
+
if (isValidColor(vars.nestedColor)) decls.push(`${VAR_NESTED}: ${vars.nestedColor.trim()};`);
|
|
72
|
+
else warnInvalid('nestedColor', vars.nestedColor);
|
|
73
|
+
if (isValidOutline(vars.outlineWidth)) decls.push(`${VAR_OUTLINE_WIDTH}: ${vars.outlineWidth}px;`);
|
|
74
|
+
else warnInvalid('outlineWidth', vars.outlineWidth);
|
|
75
|
+
return `:root { ${decls.join(' ')} }`;
|
|
76
|
+
};
|
|
69
77
|
export const setHoverVars = (doc, vars)=>{
|
|
70
|
-
|
|
71
|
-
if (
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
}
|
|
76
|
-
if (isValidColor(vars.nestedColor)) {
|
|
77
|
-
root.style.setProperty(VAR_NESTED, vars.nestedColor);
|
|
78
|
-
} else {
|
|
79
|
-
warnInvalid('nestedColor', vars.nestedColor);
|
|
80
|
-
}
|
|
81
|
-
if (isValidOutline(vars.outlineWidth)) {
|
|
82
|
-
root.style.setProperty(VAR_OUTLINE_WIDTH, `${vars.outlineWidth}px`);
|
|
83
|
-
} else {
|
|
84
|
-
warnInvalid('outlineWidth', vars.outlineWidth);
|
|
78
|
+
let el = doc.getElementById(HOVER_VARS_STYLE_ID);
|
|
79
|
+
if (!el) {
|
|
80
|
+
el = doc.createElement('style');
|
|
81
|
+
el.id = HOVER_VARS_STYLE_ID;
|
|
82
|
+
doc.head.appendChild(el);
|
|
85
83
|
}
|
|
84
|
+
el.textContent = buildVarsRule(vars);
|
|
86
85
|
};
|
|
87
86
|
export const clearHoverVars = (doc)=>{
|
|
88
|
-
|
|
89
|
-
root.style.removeProperty(VAR_TOP);
|
|
90
|
-
root.style.removeProperty(VAR_NESTED);
|
|
91
|
-
root.style.removeProperty(VAR_OUTLINE_WIDTH);
|
|
87
|
+
doc.getElementById(HOVER_VARS_STYLE_ID)?.remove();
|
|
92
88
|
};
|
|
93
89
|
|
|
94
90
|
//# sourceMappingURL=hover-css.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/preview/hover-css.ts"],"sourcesContent":["import { ACTIVE_CLASS, BLOCK_ID_ATTR } from '../internal/dom'\n\nexport const HOVER_STYLE_ID = 'better-editor-hover-style'\nexport const
|
|
1
|
+
{"version":3,"sources":["../../src/preview/hover-css.ts"],"sourcesContent":["import { ACTIVE_CLASS, BLOCK_ID_ATTR } from '../internal/dom'\n\nexport const HOVER_STYLE_ID = 'better-editor-hover-style'\nexport const HOVER_VARS_STYLE_ID = 'better-editor-hover-vars'\nexport const TOOLBAR_ID = 'better-editor-block-toolbar'\nexport const INTERACT_BODY_ATTR = 'data-bee-interact'\n\nconst VAR_TOP = '--bee-top'\nconst VAR_NESTED = '--bee-nested'\nconst VAR_OUTLINE_WIDTH = '--bee-outline-width'\n\nexport const HOVER_CSS = `\n body:not([${INTERACT_BODY_ATTR}]) [${BLOCK_ID_ATTR}] { cursor: pointer; }\n body:not([${INTERACT_BODY_ATTR}]) [${BLOCK_ID_ATTR}]:hover,\n body:not([${INTERACT_BODY_ATTR}]) [${BLOCK_ID_ATTR}].${ACTIVE_CLASS} {\n outline: var(${VAR_OUTLINE_WIDTH}) solid var(${VAR_TOP});\n outline-offset: calc(-1 * var(${VAR_OUTLINE_WIDTH}) - 1px);\n box-shadow: inset 0 0 0 100vmax color-mix(in srgb, var(${VAR_TOP}) 10%, transparent);\n }\n body:not([${INTERACT_BODY_ATTR}]) [${BLOCK_ID_ATTR}] [${BLOCK_ID_ATTR}]:hover,\n body:not([${INTERACT_BODY_ATTR}]) [${BLOCK_ID_ATTR}] [${BLOCK_ID_ATTR}].${ACTIVE_CLASS} {\n outline-color: var(${VAR_NESTED});\n box-shadow: inset 0 0 0 100vmax color-mix(in srgb, var(${VAR_NESTED}) 10%, transparent);\n }\n body[${INTERACT_BODY_ATTR}] #${TOOLBAR_ID} { display: none; }\n\n #${TOOLBAR_ID} {\n position: absolute;\n z-index: var(--better-editor-z-toolbar, 2147483647);\n display: none;\n gap: 2px;\n padding: 3px;\n border-radius: 4px;\n background: var(${VAR_TOP});\n color: #fff;\n box-shadow: 0 4px 12px rgba(0, 0, 0, 0.18);\n font-family: system-ui, sans-serif;\n }\n #${TOOLBAR_ID}[data-nested=\"1\"] { background: var(${VAR_NESTED}); }\n #${TOOLBAR_ID}.is-visible { display: inline-flex; }\n #${TOOLBAR_ID} button {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n width: 26px;\n height: 26px;\n padding: 0;\n border: 0;\n border-radius: 3px;\n background: transparent;\n color: inherit;\n cursor: pointer;\n }\n #${TOOLBAR_ID} button:hover { background: rgba(255, 255, 255, 0.18); }\n #${TOOLBAR_ID} button[data-action=\"delete\"]:hover { background: rgba(0, 0, 0, 0.25); }\n`\n\nexport type HoverVars = {\n topColor: string\n nestedColor: string\n outlineWidth: number\n}\n\n// Restrictive on purpose: these values are written verbatim into a CSS\n// custom property on the preview iframe, so anything that survives the\n// regex still lands inside `outline:` / `background-color:`. The regex\n// rejects newlines and unmatched parens so an injected value can't escape\n// into a separate declaration.\nconst COLOR_RE = /^(?:#[0-9a-fA-F]{3,8}|rgba?\\([^()\\n\\r]*\\))$/i\n\nconst isValidColor = (v: unknown): v is string =>\n typeof v === 'string' && COLOR_RE.test(v.trim())\n\nconst isValidOutline = (v: unknown): v is number =>\n typeof v === 'number' && Number.isFinite(v) && v >= 0 && v <= 50\n\nconst warnInvalid = (kind: string, value: unknown): void => {\n if (process.env.NODE_ENV !== 'production') {\n console.warn(`[better-editor] ignoring invalid ${kind}:`, value)\n }\n}\n\nconst buildVarsRule = (vars: HoverVars): string => {\n const decls: string[] = []\n if (isValidColor(vars.topColor)) decls.push(`${VAR_TOP}: ${vars.topColor.trim()};`)\n else warnInvalid('topColor', vars.topColor)\n if (isValidColor(vars.nestedColor)) decls.push(`${VAR_NESTED}: ${vars.nestedColor.trim()};`)\n else warnInvalid('nestedColor', vars.nestedColor)\n if (isValidOutline(vars.outlineWidth)) decls.push(`${VAR_OUTLINE_WIDTH}: ${vars.outlineWidth}px;`)\n else warnInvalid('outlineWidth', vars.outlineWidth)\n return `:root { ${decls.join(' ')} }`\n}\n\nexport const setHoverVars = (doc: Document, vars: HoverVars): void => {\n let el = doc.getElementById(HOVER_VARS_STYLE_ID) as HTMLStyleElement | null\n if (!el) {\n el = doc.createElement('style')\n el.id = HOVER_VARS_STYLE_ID\n doc.head.appendChild(el)\n }\n el.textContent = buildVarsRule(vars)\n}\n\nexport const clearHoverVars = (doc: Document): void => {\n doc.getElementById(HOVER_VARS_STYLE_ID)?.remove()\n}\n"],"names":["ACTIVE_CLASS","BLOCK_ID_ATTR","HOVER_STYLE_ID","HOVER_VARS_STYLE_ID","TOOLBAR_ID","INTERACT_BODY_ATTR","VAR_TOP","VAR_NESTED","VAR_OUTLINE_WIDTH","HOVER_CSS","COLOR_RE","isValidColor","v","test","trim","isValidOutline","Number","isFinite","warnInvalid","kind","value","process","env","NODE_ENV","console","warn","buildVarsRule","vars","decls","topColor","push","nestedColor","outlineWidth","join","setHoverVars","doc","el","getElementById","createElement","id","head","appendChild","textContent","clearHoverVars","remove"],"mappings":"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"}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/* Blocks tab — header (kicker / heading /
|
|
1
|
+
/* Blocks tab — header (kicker / heading / Deselect),
|
|
2
2
|
action toolbar, dividers, Block Name input, empty-state CTA. */
|
|
3
3
|
|
|
4
4
|
.better-editor-tab__header {
|
|
@@ -24,12 +24,6 @@
|
|
|
24
24
|
line-height: 1.2;
|
|
25
25
|
}
|
|
26
26
|
|
|
27
|
-
.better-editor-tab__path {
|
|
28
|
-
font-family: var(--font-mono, ui-monospace, monospace);
|
|
29
|
-
font-size: 11px;
|
|
30
|
-
color: var(--theme-elevation-500);
|
|
31
|
-
}
|
|
32
|
-
|
|
33
27
|
.better-editor-tab__clear {
|
|
34
28
|
flex-shrink: 0;
|
|
35
29
|
padding: 6px 10px;
|
package/dist/styles/overlay.css
CHANGED
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
inset: 0;
|
|
7
7
|
z-index: var(--better-editor-z-overlay, 50);
|
|
8
8
|
background: var(--theme-bg, #fff);
|
|
9
|
+
border-top: 1px solid var(--theme-elevation-100);
|
|
9
10
|
}
|
|
10
11
|
|
|
11
12
|
/* While fullscreen the overlay covers the whole screen via the
|
|
@@ -31,6 +32,7 @@
|
|
|
31
32
|
.better-editor__resize-handle {
|
|
32
33
|
cursor: col-resize;
|
|
33
34
|
background: transparent;
|
|
35
|
+
border-left: 1px solid var(--theme-elevation-100);
|
|
34
36
|
}
|
|
35
37
|
|
|
36
38
|
.better-editor__resize-handle:hover,
|
package/dist/styles/sidebar.css
CHANGED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/* Editor toggle button — visible label + open-state emphasis layered on top
|
|
2
|
+
of Payload's own .preview-btn. */
|
|
3
|
+
|
|
4
|
+
.better-editor-toggle {
|
|
5
|
+
width: auto;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
.better-editor-toggle[aria-pressed='true'] {
|
|
9
|
+
border-color: var(--theme-elevation-300);
|
|
10
|
+
background-color: var(--theme-elevation-100);
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
.better-editor-toggle__label {
|
|
14
|
+
display: inline-block;
|
|
15
|
+
margin-right: 0.5em;
|
|
16
|
+
color: var(--theme-elevation-700);
|
|
17
|
+
}
|
package/dist/types.d.ts
CHANGED
|
@@ -46,4 +46,10 @@ export type BetterEditorConfig = {
|
|
|
46
46
|
* hide it for end users. Defaults to `true`.
|
|
47
47
|
*/
|
|
48
48
|
showSettingsBanner?: boolean;
|
|
49
|
+
/**
|
|
50
|
+
* Hide the "Open/Close Better Editor" text next to the toggle button's icon,
|
|
51
|
+
* leaving an icon-only button. The accessible label (`aria-label`/`title`)
|
|
52
|
+
* is kept either way. Defaults to `false` (label shown).
|
|
53
|
+
*/
|
|
54
|
+
hideToggleLabel?: boolean;
|
|
49
55
|
};
|
package/dist/types.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/types.ts"],"sourcesContent":["import type { CollectionSlug, GlobalSlug } from 'payload'\n\n/** Per-entity overrides for a collection/global. */\nexport type BetterEditorEntityOptions = {\n /** Blocks-field name for this entity; falls back to the top-level `blocksField`. */\n blocksField?: string\n}\n\nexport type BetterEditorConfig = {\n /** Skip plugin installation entirely (e.g. behind a feature flag). */\n disabled?: boolean\n /**\n * Collections that get the \"Open Better Editor\" toggle. Either a list of\n * slugs, or a slug → options record for per-collection settings (e.g. a\n * different `blocksField`). Each collection needs `admin.preview` configured\n * for the preview to render and the toggle to appear.\n */\n collections?: string[] | Partial<Record<CollectionSlug, BetterEditorEntityOptions>>\n /**\n * Globals that get the \"Open Better Editor\" toggle — a list of slugs or a\n * slug → options record. Each global needs `admin.preview` configured for the\n * toggle to appear.\n */\n globals?: string[] | Partial<Record<GlobalSlug, BetterEditorEntityOptions>>\n /**\n * Default name of the `blocks` field the sidebar targets. Top-level only;\n * nested paths (e.g. `content.layout`) are not supported. Per-collection\n * overrides via the `collections`/`globals` record take precedence.\n * Defaults to `'layout'`.\n */\n blocksField?: string\n /**\n * CSS selector for the Payload admin element the overlay portals into.\n * Override only if the default selector breaks against a future Payload\n * version. The plugin falls back to `<main>` and finally `<body>`.\n */\n adminPortalSelector?: string\n /**\n * Prefix for all `localStorage` keys the editor writes (sidebar width,\n * responsive viewport width, …). Set this if multiple Better Editor\n * instances share the same origin and would otherwise collide.\n * Defaults to `'better-editor'`.\n */\n storageNamespace?: string\n /**\n * Show the plugin info banner (version, GitHub links, \"Report a bug\")\n * at the top of the `BetterEditorSettings` global. Set to `false` to\n * hide it for end users. Defaults to `true`.\n */\n showSettingsBanner?: boolean\n}\n"],"names":[],"mappings":"AAQA,
|
|
1
|
+
{"version":3,"sources":["../src/types.ts"],"sourcesContent":["import type { CollectionSlug, GlobalSlug } from 'payload'\n\n/** Per-entity overrides for a collection/global. */\nexport type BetterEditorEntityOptions = {\n /** Blocks-field name for this entity; falls back to the top-level `blocksField`. */\n blocksField?: string\n}\n\nexport type BetterEditorConfig = {\n /** Skip plugin installation entirely (e.g. behind a feature flag). */\n disabled?: boolean\n /**\n * Collections that get the \"Open Better Editor\" toggle. Either a list of\n * slugs, or a slug → options record for per-collection settings (e.g. a\n * different `blocksField`). Each collection needs `admin.preview` configured\n * for the preview to render and the toggle to appear.\n */\n collections?: string[] | Partial<Record<CollectionSlug, BetterEditorEntityOptions>>\n /**\n * Globals that get the \"Open Better Editor\" toggle — a list of slugs or a\n * slug → options record. Each global needs `admin.preview` configured for the\n * toggle to appear.\n */\n globals?: string[] | Partial<Record<GlobalSlug, BetterEditorEntityOptions>>\n /**\n * Default name of the `blocks` field the sidebar targets. Top-level only;\n * nested paths (e.g. `content.layout`) are not supported. Per-collection\n * overrides via the `collections`/`globals` record take precedence.\n * Defaults to `'layout'`.\n */\n blocksField?: string\n /**\n * CSS selector for the Payload admin element the overlay portals into.\n * Override only if the default selector breaks against a future Payload\n * version. The plugin falls back to `<main>` and finally `<body>`.\n */\n adminPortalSelector?: string\n /**\n * Prefix for all `localStorage` keys the editor writes (sidebar width,\n * responsive viewport width, …). Set this if multiple Better Editor\n * instances share the same origin and would otherwise collide.\n * Defaults to `'better-editor'`.\n */\n storageNamespace?: string\n /**\n * Show the plugin info banner (version, GitHub links, \"Report a bug\")\n * at the top of the `BetterEditorSettings` global. Set to `false` to\n * hide it for end users. Defaults to `true`.\n */\n showSettingsBanner?: boolean\n /**\n * Hide the \"Open/Close Better Editor\" text next to the toggle button's icon,\n * leaving an icon-only button. The accessible label (`aria-label`/`title`)\n * is kept either way. Defaults to `false` (label shown).\n */\n hideToggleLabel?: boolean\n}\n"],"names":[],"mappings":"AAQA,WAgDC"}
|
package/dist/version.d.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export declare const VERSION = "1.
|
|
1
|
+
export declare const VERSION = "1.3.0";
|
package/dist/version.js
CHANGED
package/dist/version.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/version.ts"],"sourcesContent":["// Package version, re-exported as VERSION; read by the settings banner without\n// pulling the server entry into the client bundle.\nexport const VERSION = '1.
|
|
1
|
+
{"version":3,"sources":["../src/version.ts"],"sourcesContent":["// Package version, re-exported as VERSION; read by the settings banner without\n// pulling the server entry into the client bundle.\nexport const VERSION = '1.3.0'\n"],"names":["VERSION"],"mappings":"AAAA,+EAA+E;AAC/E,mDAAmD;AACnD,OAAO,MAAMA,UAAU,QAAO"}
|