eddev 2.0.0-beta.116 → 2.0.0-beta.118

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.
@@ -92,3 +92,7 @@
92
92
  .acf-block-panel .acf-empty-block-fields {
93
93
  display: none;
94
94
  }
95
+
96
+ .simple-appender {
97
+ display: inline-flex !important;
98
+ }
@@ -1,12 +1,25 @@
1
1
  import { ElementType } from "react";
2
2
  import { InlineValueStore } from "./inline-editing.js";
3
3
  export type InlineTextValueStore = InlineValueStore<string>;
4
+ type BuiltinFormats = "core/bold" | "core/code" | "core/italic" | "core/link" | "core/strikethrough" | "core/underline" | "core/subscript" | "core/superscript" | "core/unknown" | "core/non-breaking-space" | "core/footnote";
4
5
  type Props<T extends ElementType> = {
5
6
  /** Specify a tag name or React component */
6
7
  as?: T;
7
8
  /** Prevents this text element from being multi-line */
8
9
  disableLineBreaks?: boolean;
9
- /** Enables the rich text toolbar */
10
+ /**
11
+ * Specify which formatting options are allowed in this text element (bold, italics etc).
12
+ *
13
+ * By default, all options are enabled.
14
+ *
15
+ * Set to an empty array to disable formatting options.
16
+ *
17
+ * You can register new formats in `_editor.tsx` using the `defineEditorConfig` function.
18
+ **/
19
+ allowedFormats?: (BuiltinFormats | (string & {}))[];
20
+ /**
21
+ * @deprecated use `allowedFormats` instead
22
+ */
10
23
  inlineToolbar?: boolean;
11
24
  /** Specify default content to use on the frontend if nothing has been entered */
12
25
  defaultValue?: string;
@@ -6,8 +6,13 @@ export function EditableText({ id, as, appendOnEnter, store, ...props }) {
6
6
  const readOnly = useBlockContext()?.readonly;
7
7
  if (!readOnly) {
8
8
  const [value, setValue] = useValueStore(store ?? id);
9
+ // const defaultFormats = wp.data.useSelect((select) => {
10
+ // const formats = (select(wp.richText.store) as any).getFormatTypes()
11
+ // console.log("U", formats)
12
+ // return formats.map((f: any) => f.name)
13
+ // }, [])
9
14
  const appendBlocks = useBlockAppender();
10
- return (_jsx(wp.blockEditor.RichText, { ...props, placeholder: props.placeholder ?? props.defaultValue, tagName: as, value: value || "", onChange: setValue, inlineToolbar: props.inlineToolbar, disableLineBreaks: props.disableLineBreaks, onKeyDownCapture: (e) => {
15
+ return (_jsx(wp.blockEditor.RichText, { ...props, placeholder: props.placeholder ?? props.defaultValue, tagName: as, value: value || "", onChange: setValue, allowedFormats: props.inlineToolbar === false ? [] : props.allowedFormats, disableLineBreaks: props.disableLineBreaks, "data-allowed-formats": props.allowedFormats?.join(" "), onKeyDownCapture: (e) => {
11
16
  if (e.key === "Enter" && appendOnEnter && appendBlocks) {
12
17
  appendBlocks([
13
18
  wp.blocks.createBlock(typeof appendOnEnter === "string" ? appendOnEnter : "core/paragraph"),
@@ -21,11 +26,12 @@ export function EditableText({ id, as, appendOnEnter, store, ...props }) {
21
26
  let [value] = useValueStore(store ?? id);
22
27
  const handleClickEvent = useRouter((r) => r.handleClickEvent);
23
28
  const otherProps = { ...props };
24
- delete otherProps.inlineToolbar;
25
29
  delete otherProps.disableLineBreaks;
26
30
  delete otherProps.id;
27
31
  otherProps.as = otherProps.asProp ?? undefined;
28
32
  delete otherProps.asProp;
33
+ delete otherProps.allowedFormats;
34
+ delete otherProps.disableLineBreaks;
29
35
  delete otherProps.placeholder;
30
36
  if (value === "" || typeof value !== "string") {
31
37
  if (props.defaultValue) {
@@ -2,7 +2,7 @@ import { FunctionComponent } from "react";
2
2
  import { ContentBlockLayoutProps } from "./ContentBlocks.js";
3
3
  import { BlockTemplate } from "./editor/block-templates.js";
4
4
  type AppenderConfig = {
5
- type: "default" | "button" | CustomBlockAppender;
5
+ type: "default" | "button" | "simple" | CustomBlockAppender;
6
6
  className?: string;
7
7
  };
8
8
  export type CustomBlockAppender = FunctionComponent<{
@@ -26,9 +26,13 @@ type InnerBlocksProps = {
26
26
  * NOTE: This will have no effect on the frontend, since no wrapper div is created on the frontend.
27
27
  **/
28
28
  adminClassName?: string;
29
- /**
30
- *
31
- */
29
+ /** The default blocks to insert when there are no (non-templated) blocks */
30
+ defaultBlocks?: BlockTemplate;
31
+ /** Blocks to ensure are inserted at the top of the page */
32
+ headerTemplate?: BlockTemplate;
33
+ /** Blocks to ensure are inserted at the bottom of the page */
34
+ footerTemplate?: BlockTemplate;
35
+ /** A full-page block template */
32
36
  template?: BlockTemplate;
33
37
  /**
34
38
  * `false` allows all operations
@@ -41,7 +45,7 @@ type InnerBlocksProps = {
41
45
  * @default false
42
46
  *
43
47
  */
44
- templateLock?: "all" | "insert" | "contentOnly" | false;
48
+ templateLock?: "all" | "insert" | "contentOnly" | "none" | false;
45
49
  appender?: AppenderConfig;
46
50
  prioritizedInserterBlocks?: ChildBlockTypeName[];
47
51
  } & ContentBlockLayoutProps;
@@ -1,25 +1,58 @@
1
1
  import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { useEffect, useMemo } from "react";
2
3
  import { ContentBlocks } from "./ContentBlocks.js";
3
- import { transformBlockTemplate } from "./editor/block-templates.js";
4
+ import { applyTemplateBlocks, transformBlockTemplate } from "./editor/block-templates.js";
4
5
  import { blocksByTag } from "./editor/blocks-by-tag.js";
5
6
  import { useBlockContext, useInnerBlocks } from "./inline-editing.js";
6
- const Appender = (props) => {
7
+ import { hash } from "object-code";
8
+ const Appender = ({ config, ...props }) => {
7
9
  const clientId = useBlockContext()?.block[1].clientId;
8
- if (props.type === "button") {
9
- return _jsx(wp.blockEditor.ButtonBlockAppender, { className: props.className, rootClientId: clientId });
10
+ if (config?.type === "button") {
11
+ return _jsx(wp.blockEditor.ButtonBlockAppender, { ...props, rootClientId: clientId, className: config.className });
10
12
  }
11
- else if (typeof props.type === "function") {
12
- const Type = props.type;
13
- return (_jsx(wp.blockEditor.Inserter, { renderToggle: (p) => {
13
+ else if (config?.type === "simple") {
14
+ return (_jsx(wp.blockEditor.ButtonBlockAppender, { ...props, rootClientId: clientId, className: (props.className || "") + " block-editor-inserter__toggle has-icon simple-appender" }));
15
+ }
16
+ else if (typeof config?.type === "function") {
17
+ const Type = config?.type;
18
+ return (_jsx(wp.blockEditor.Inserter, { rootClientId: clientId, renderToggle: (p) => {
14
19
  return _jsx(Type, { ...p });
15
- }, rootClientId: clientId }));
20
+ }, isAppender: true,
21
+ // @ts-ignore
22
+ __experimentalIsQuick: true }));
23
+ // } else if (config?.type === "simple") {
24
+ // return (
25
+ // <wp.blockEditor.Inserter
26
+ // clientId={clientId}
27
+ // rootClientId={clientId}
28
+ // renderToggle={(p: any) => {
29
+ // return (
30
+ // <button
31
+ // className="simple-appender components-button block-editor-inserter__toggle !inline-flex has-icon"
32
+ // onClick={() => p.onToggle()}
33
+ // >
34
+ // <svg
35
+ // xmlns="http://www.w3.org/2000/svg"
36
+ // viewBox="0 0 24 24"
37
+ // width="24"
38
+ // height="24"
39
+ // aria-hidden="true"
40
+ // focusable="false"
41
+ // >
42
+ // <path d="M11 12.5V17.5H12.5V12.5H17.5V11H12.5V6H11V11H6V12.5H11Z"></path>
43
+ // </svg>
44
+ // </button>
45
+ // )
46
+ // }}
47
+ // isAppender
48
+ // // @ts-ignore
49
+ // __experimentalIsQuick
50
+ // />
51
+ // )
16
52
  }
17
53
  else {
18
- return (_jsx(wp.blockEditor.DefaultBlockAppender
19
54
  // @ts-ignore
20
- , {
21
- // @ts-ignore
22
- className: props.className, rootClientId: clientId, lastBlockClientId: clientId }));
55
+ return _jsx(wp.blockEditor.InnerBlocks.DefaultBlockAppender, { ...props });
23
56
  }
24
57
  };
25
58
  export function createAppender(comp) {
@@ -33,29 +66,35 @@ export function createAppender(comp) {
33
66
  export function InnerBlocks(props) {
34
67
  if (env.admin) {
35
68
  const inlineContext = useBlockContext();
69
+ const appender = useMemo(() => {
70
+ return (p) => _jsx(Appender, { config: props.appender, ...p });
71
+ }, [props.appender]);
36
72
  if (!inlineContext?.readonly) {
37
73
  const innerBlocksProps = wp.blockEditor.useInnerBlocksProps({}, {
38
74
  orientation: props.orientation ?? "vertical",
39
75
  allowedBlocks: props.allowedBlocks ? blocksByTag.expand(props.allowedBlocks) : undefined,
40
76
  prioritizedInserterBlocks: props.prioritizedInserterBlocks,
41
- renderAppender: props.appender
42
- ? () => _jsx(Appender, { ...props.appender })
43
- : wp.blockEditor.InnerBlocks.ButtonBlockAppender,
44
- templateLock: props.templateLock ?? false,
77
+ renderAppender: appender,
78
+ templateLock: props.templateLock === "none" ? false : (props.templateLock ?? false),
45
79
  template: props.template ? transformBlockTemplate(props.template) : undefined,
46
80
  });
81
+ /**
82
+ * A little bit experimental
83
+ *
84
+ * Adds support for headerTemplate/defaultBlocks/footerTemplate, which was first introduced in `_editor.tsx` for generate templates.
85
+ */
86
+ const blockId = inlineContext?.block[1].clientId;
87
+ useEffect(() => {
88
+ if (props.defaultBlocks || props.headerTemplate || props.footerTemplate) {
89
+ const newBlocks = applyTemplateBlocks(inlineContext?.innerBlocks ?? [], props);
90
+ wp.data.dispatch(wp.blockEditor.store).replaceInnerBlocks(blockId, newBlocks);
91
+ }
92
+ }, [
93
+ hash(inlineContext?.innerBlocks.map((b) => [b.blockName, b.clientId])),
94
+ hash([props.template, props.defaultBlocks, props.headerTemplate, props.footerTemplate]),
95
+ blockId,
96
+ ]);
47
97
  return (_jsx("div", { ...innerBlocksProps, className: [innerBlocksProps.className, props.adminClassName].filter(Boolean).join(" ") }));
48
- // return (
49
- // <wp.blockEditor.InnerBlocks
50
- // // @ts-ignore
51
- // orientation={props.orientation}
52
- // prioritizedInserterBlocks={props.prioritizedInserterBlocks}
53
- // allowedBlocks={props.allowedBlocks ? blocksByTag.expand(props.allowedBlocks) : undefined}
54
- // renderAppender={props.appender ? () => <Appender {...props.appender!} /> : undefined}
55
- // templateLock={(props.templateLock as any) ?? false}
56
- // template={props.template ? transformBlockTemplate(props.template) : undefined}
57
- // />
58
- // )
59
98
  }
60
99
  }
61
100
  const blocks = useInnerBlocks();
@@ -1,2 +1,5 @@
1
1
  import { ComponentType, ReactNode } from "react";
2
2
  export declare function defineBlock<TName extends keyof BlockProps>(name: TName, component: (props: BlockProps[TName]) => ReactNode): ComponentType<BlockProps[TName]>;
3
+ export declare namespace defineBlock {
4
+ var meta: (name: string, meta: any) => void;
5
+ }
@@ -1,3 +1,10 @@
1
+ import { blockMetaDescriptors } from "./editor/installGutenbergHooks";
2
+ import { resolveAcfBlockName } from "./editor/block-templates";
1
3
  export function defineBlock(name, component) {
2
4
  return component;
3
5
  }
6
+ defineBlock.meta = (name, meta) => {
7
+ if (env.admin) {
8
+ blockMetaDescriptors.set(resolveAcfBlockName(name), meta);
9
+ }
10
+ };
@@ -1,7 +1,7 @@
1
- import { jsx as _jsx } from "react/jsx-runtime";
2
- import { useEffect, useRef, useState } from "react";
1
+ import { Fragment as _Fragment, jsx as _jsx } from "react/jsx-runtime";
2
+ import { useEffect, useState } from "react";
3
3
  export function EditorHighlights(props) {
4
- const ref = useRef(null);
4
+ const [element, setElement] = useState(null);
5
5
  const [controller, setController] = useState(null);
6
6
  // wp.data.select('core/editor').isBlockSelected
7
7
  // const isSelected = wp.data.useSelect(
@@ -9,12 +9,12 @@ export function EditorHighlights(props) {
9
9
  // [props.clientId]
10
10
  // )
11
11
  useEffect(() => {
12
- if (!props.enabled)
12
+ if (!props.enabled || !element)
13
13
  return;
14
- const controller = new HighlightController(ref.current);
14
+ const controller = new HighlightController(element);
15
15
  setController(controller);
16
16
  return () => controller.teardown();
17
- }, [props.enabled]);
17
+ }, [props.enabled, element]);
18
18
  useEffect(() => {
19
19
  if (controller) {
20
20
  controller.isSelected = false;
@@ -22,7 +22,38 @@ export function EditorHighlights(props) {
22
22
  controller.update();
23
23
  }
24
24
  }, [controller, false]);
25
- return (_jsx("div", { ref: ref, style: { display: "contents" }, onPointerEnter: () => controller?.enable(), onPointerLeave: () => controller?.disable(), children: props.children }));
25
+ useEffect(() => {
26
+ const element = document.getElementById(`block-${props.clientId}`);
27
+ if (element) {
28
+ setElement(element);
29
+ }
30
+ else {
31
+ setElement(null);
32
+ }
33
+ }, [props.clientId]);
34
+ useEffect(() => {
35
+ if (controller && element) {
36
+ const onPointerEnter = () => controller.enable();
37
+ const onPointerLeave = () => controller.disable();
38
+ element.addEventListener("pointerenter", onPointerEnter);
39
+ element.addEventListener("pointerleave", onPointerLeave);
40
+ return () => {
41
+ element.removeEventListener("pointerenter", onPointerEnter);
42
+ element.removeEventListener("pointerleave", onPointerLeave);
43
+ };
44
+ }
45
+ }, [element, controller]);
46
+ return _jsx(_Fragment, { children: props.children });
47
+ // return (
48
+ // <div
49
+ // ref={ref}
50
+ // style={{ display: "contents" }}
51
+ // // onPointerEnter={() => controller?.enable()}
52
+ // // onPointerLeave={() => controller?.disable()}
53
+ // >
54
+ // {props.children}
55
+ // </div>
56
+ // )
26
57
  }
27
58
  class HighlightController {
28
59
  root;
@@ -40,7 +71,7 @@ class HighlightController {
40
71
  let editables = Array.from(this.root.querySelectorAll("[contenteditable=true], .editable-slot"));
41
72
  let childBlocks = Array.from(this.root.querySelectorAll(".wp-block"));
42
73
  const childIsSelected = this.root.querySelectorAll(".editor-highlighter-root-selected").length > 0;
43
- this.block = childBlocks[0];
74
+ this.block = this.root;
44
75
  childBlocks = childBlocks.slice(1);
45
76
  editables = childIsSelected
46
77
  ? []
@@ -96,7 +127,10 @@ class HighlightController {
96
127
  el.style.position = "absolute";
97
128
  el.style.pointerEvents = "none";
98
129
  el.style.zIndex = "20";
99
- el.className = "editor-highlight";
130
+ el.classList.add("editor-highlight");
131
+ if (target.getAttribute("data-editor-higlight-class")) {
132
+ el.classList.add(target.getAttribute("data-editor-higlight-class"));
133
+ }
100
134
  this.block.appendChild(el);
101
135
  return el;
102
136
  }
@@ -1,17 +1,23 @@
1
1
  import { jsx as _jsx } from "react/jsx-runtime";
2
- import { createContext, Suspense, useContext } from "react";
2
+ import { createContext, Suspense, useContext, useEffect } from "react";
3
3
  import { blockManifestReader } from "../../internal/read-block-manifest.js";
4
4
  import { APIProvider } from "../../../utils/APIProvider.js";
5
5
  import { ErrorBoundaryEditor } from "./ErrorBoundaryEditor.js";
6
6
  export const BlockContext = createContext(undefined);
7
7
  export function EditableBlock({ payload }) {
8
- const block = useContext(BlockContext);
9
- if (!block)
8
+ if (!env.admin)
9
+ throw new Error("`EditableBlock` can only be used in the admin environment");
10
+ const info = useContext(BlockContext);
11
+ useEffect(() => {
12
+ const block = wp.data.select("core/block-editor").getBlock(info?.props.clientId);
13
+ if (block && block.attributes) {
14
+ // @ts-ignore
15
+ block.attributes.props = payload;
16
+ }
17
+ }, [payload, info?.props.clientId]);
18
+ if (!info)
10
19
  return null;
11
- const getBlock = () => {
12
- return blockManifestReader.value[block.name];
13
- };
14
- const BlockComponent = getBlock();
20
+ const BlockComponent = blockManifestReader.value[info.name];
15
21
  if (!BlockComponent)
16
22
  return _jsx("div", { children: "Unable to load block component" });
17
23
  return (_jsx(ErrorBoundaryEditor, { children: _jsx(APIProvider, { children: _jsx(Suspense, { children: _jsx(BlockComponent, { ...payload }) }) }) }));
@@ -1,3 +1,9 @@
1
1
  export type BlockTemplate = [name: ChildBlockTypeName, props: any, children?: BlockTemplate][];
2
2
  export declare function resolveAcfBlockName(name: string): string;
3
3
  export declare function transformBlockTemplate(template: BlockTemplate): BlockTemplate;
4
+ export declare function applyTemplateBlocks(currentBlocks: any[], config: {
5
+ defaultBlocks?: BlockTemplate;
6
+ headerTemplate?: BlockTemplate;
7
+ footerTemplate?: BlockTemplate;
8
+ }): any[];
9
+ export declare function transformTemplateToBlocks(template: BlockTemplate, locked?: boolean, isFromTemplate?: boolean): any;
@@ -7,3 +7,67 @@ export function transformBlockTemplate(template) {
7
7
  return [resolveAcfBlockName(name), props, children ? transformBlockTemplate(children) : undefined];
8
8
  });
9
9
  }
10
+ export function applyTemplateBlocks(currentBlocks, config) {
11
+ const templateBlocks = currentBlocks.filter((block) => block.attributes.isFromTemplate);
12
+ let header = config.headerTemplate ? syncBlocks(transformTemplateToBlocks(config.headerTemplate)) : [];
13
+ let footer = config.footerTemplate ? syncBlocks(transformTemplateToBlocks(config.footerTemplate)) : [];
14
+ let blocks = currentBlocks.filter((block) => !header.includes(block) && !footer.includes(block));
15
+ blocks.forEach((block) => {
16
+ delete block.attributes.lock;
17
+ delete block.isFromTemplate;
18
+ });
19
+ // const blocksToDelete = currentBlocks.filter((block: any) => {
20
+ // return !header.includes(block) && !footer.includes(block) && !blocks.includes(block)
21
+ // })
22
+ // blocksToDelete.forEach((block: any) => {
23
+ // delete block.attributes.lock
24
+ // delete block.isFromTemplate
25
+ // })
26
+ // blocks = [...blocks, ...blocksToDelete]
27
+ // console.log("blocks", blocks)
28
+ // console.log("blocksToDelete", blocksToDelete)
29
+ if (!blocks.length && config.defaultBlocks) {
30
+ blocks = transformTemplateToBlocks(config.defaultBlocks, false, false);
31
+ }
32
+ let newBlocks = [...header, ...blocks, ...footer];
33
+ function syncBlocks(blocks) {
34
+ return blocks.map((block) => {
35
+ const matched = templateBlocks.find((templateBlock) => templateBlock.name === block.name);
36
+ templateBlocks.splice(templateBlocks.indexOf(matched), 1);
37
+ if (matched) {
38
+ matched.attributes.lock = block.attributes.lock;
39
+ return matched;
40
+ }
41
+ return block;
42
+ });
43
+ }
44
+ return newBlocks;
45
+ }
46
+ export function transformTemplateToBlocks(template, locked = true, isFromTemplate = true) {
47
+ return template.map(([name, props, children]) => {
48
+ const attributes = {
49
+ data: {},
50
+ inline: {},
51
+ isFromTemplate: isFromTemplate,
52
+ lock: undefined,
53
+ };
54
+ if (props.locked === false) {
55
+ attributes.lock = { move: false, remove: false };
56
+ }
57
+ else if (locked || props.locked === true || props.locked === "all") {
58
+ attributes.lock = { move: true, remove: true };
59
+ }
60
+ else {
61
+ attributes.lock = props.lock;
62
+ }
63
+ Object.assign(attributes, props);
64
+ return {
65
+ clientId: "block-" + Math.random().toString(36),
66
+ name: resolveAcfBlockName(name),
67
+ attributes: attributes,
68
+ innerBlocks: children ? transformTemplateToBlocks(children, false, isFromTemplate) : [],
69
+ isValid: true,
70
+ validationIssues: [],
71
+ };
72
+ });
73
+ }
@@ -0,0 +1,9 @@
1
+ import { BlockInstance } from "../inline-editing";
2
+ /**
3
+ * Creates a block instance for the editor. This doesn't add the block to the editor on it's own.
4
+ * @param name The name of the block
5
+ * @param attributes
6
+ * @param innerBlocks
7
+ * @returns
8
+ */
9
+ export declare function createBlock(name: ChildBlockTypeName, attributes?: Record<string, any>, innerBlocks?: BlockInstance[]): BlockInstance;
@@ -0,0 +1,13 @@
1
+ /**
2
+ * Creates a block instance for the editor. This doesn't add the block to the editor on it's own.
3
+ * @param name The name of the block
4
+ * @param attributes
5
+ * @param innerBlocks
6
+ * @returns
7
+ */
8
+ export function createBlock(name, attributes = {}, innerBlocks = []) {
9
+ if (!env.admin) {
10
+ throw new Error("`createBlock` can only be used in the admin environment");
11
+ }
12
+ return wp.blocks.createBlock(name, attributes, innerBlocks);
13
+ }
@@ -44,7 +44,6 @@ export declare const editorConfigStore: {
44
44
  currentBlocksConfig: EditorConfigItem;
45
45
  };
46
46
  export declare function configureEditorBlocks(config: EditorConfigItem): void;
47
- export declare function transformTemplateToBlocks(template: BlockTemplate, locked?: boolean, isFromTemplate?: boolean): any;
48
47
  type PostInfo = {
49
48
  type: PostTypeName;
50
49
  isPattern: false;
@@ -72,9 +71,45 @@ type Matcher = {
72
71
  /** Configuration for the editor, when the post type and template match */
73
72
  config: EditorConfigItem | ((post: PostInfo) => EditorConfigItem);
74
73
  };
74
+ export type BlockTransform = {
75
+ from: ChildBlockTypeName[];
76
+ to: ChildBlockTypeName;
77
+ };
78
+ export type CustomRichTextFormat = {
79
+ /** The ID of the format, eg. `"custom/fancy"` */
80
+ id: string;
81
+ /** The title of the format, shown on the tooltip */
82
+ title: string;
83
+ /**
84
+ * The HTML tag name to use.
85
+ * @default `"span"`
86
+ **/
87
+ tagName?: string;
88
+ /**
89
+ * The class name to apply to the element.
90
+ * @default `undefined`
91
+ **/
92
+ className?: string;
93
+ /**
94
+ * The icon to use in the toolbar.
95
+ */
96
+ icon?: string;
97
+ /**
98
+ * Whether format makes content interactive or not.
99
+ * @default `false`
100
+ */
101
+ interactive?: boolean;
102
+ /**
103
+ * Whether to disable this format by default. If `true`, you must pass the format ID to `allowedFormats` on a `EditableText` component.
104
+ *
105
+ * @default `false`
106
+ */
107
+ disabledByDefault?: boolean;
108
+ };
75
109
  type EditorConfig = {
76
- /** A list of template/post type matchers, and the resulting editor config */
77
- matchers: Matcher[];
110
+ customRichTextFormats?: CustomRichTextFormat[];
111
+ /** A list of template/post type matchers, and resulting editor config that they will apply */
112
+ matchers?: Matcher[];
78
113
  };
79
114
  /**
80
115
  * This call should be placed in blocks/_editor.tsx
@@ -1,5 +1,5 @@
1
1
  import { proxy } from "valtio";
2
- import { resolveAcfBlockName, transformBlockTemplate } from "./block-templates.js";
2
+ import { applyTemplateBlocks, resolveAcfBlockName, transformBlockTemplate, transformTemplateToBlocks, } from "./block-templates.js";
3
3
  export const editorConfigStore = proxy({
4
4
  config: null,
5
5
  currentBlocksConfig: {},
@@ -26,68 +26,8 @@ export function configureEditorBlocks(config) {
26
26
  }
27
27
  }
28
28
  const currentBlocks = wp.data.select("core/block-editor").getBlocks();
29
- const templateBlocks = currentBlocks.filter((block) => block.attributes.isFromTemplate);
30
- let header = config.headerTemplate ? syncBlocks(transformTemplateToBlocks(config.headerTemplate)) : [];
31
- let footer = config.footerTemplate ? syncBlocks(transformTemplateToBlocks(config.footerTemplate)) : [];
32
- let blocks = currentBlocks.filter((block) => !header.includes(block) && !footer.includes(block));
33
- blocks.forEach((block) => {
34
- delete block.attributes.lock;
35
- delete block.isFromTemplate;
36
- });
37
- // const blocksToDelete = currentBlocks.filter((block: any) => {
38
- // return !header.includes(block) && !footer.includes(block) && !blocks.includes(block)
39
- // })
40
- // blocksToDelete.forEach((block: any) => {
41
- // delete block.attributes.lock
42
- // delete block.isFromTemplate
43
- // })
44
- // blocks = [...blocks, ...blocksToDelete]
45
- // console.log("blocks", blocks)
46
- // console.log("blocksToDelete", blocksToDelete)
47
- if (!blocks.length && config.defaultBlocks) {
48
- blocks = transformTemplateToBlocks(config.defaultBlocks, false, false);
49
- }
50
- let newBlocks = [...header, ...blocks, ...footer];
29
+ const newBlocks = applyTemplateBlocks(currentBlocks, config);
51
30
  wp.data.dispatch("core/block-editor").resetBlocks(newBlocks);
52
- function syncBlocks(blocks) {
53
- return blocks.map((block) => {
54
- const matched = templateBlocks.find((templateBlock) => templateBlock.name === block.name);
55
- templateBlocks.splice(templateBlocks.indexOf(matched), 1);
56
- if (matched) {
57
- matched.attributes.lock = block.attributes.lock;
58
- return matched;
59
- }
60
- return block;
61
- });
62
- }
63
- }
64
- export function transformTemplateToBlocks(template, locked = true, isFromTemplate = true) {
65
- return template.map(([name, props, children]) => {
66
- const attributes = {
67
- data: {},
68
- inline: {},
69
- isFromTemplate: isFromTemplate,
70
- lock: undefined,
71
- };
72
- if (props.locked === false) {
73
- attributes.lock = { move: false, remove: false };
74
- }
75
- else if (locked || props.locked === true || props.locked === "all") {
76
- attributes.lock = { move: true, remove: true };
77
- }
78
- else {
79
- attributes.lock = props.lock;
80
- }
81
- Object.assign(attributes, props);
82
- return {
83
- clientId: "block-" + Math.random().toString(36),
84
- name: resolveAcfBlockName(name),
85
- attributes: attributes,
86
- innerBlocks: children ? transformTemplateToBlocks(children, false, isFromTemplate) : [],
87
- isValid: true,
88
- validationIssues: [],
89
- };
90
- });
91
31
  }
92
32
  /**
93
33
  * This call should be placed in blocks/_editor.tsx
@@ -125,7 +65,7 @@ export function updateTemplateConfig() {
125
65
  const editorConfig = editorConfigStore.config;
126
66
  if (!editorConfig)
127
67
  return;
128
- const matched = editorConfig.matchers.find((matcher) => {
68
+ const matched = editorConfig.matchers?.find((matcher) => {
129
69
  return matcher.match(postInfo);
130
70
  });
131
71
  if (matched) {
@@ -1,2 +1,5 @@
1
+ export declare const blockMetaDescriptors: Map<string, BlockMeta> & {
2
+ $$valtioSnapshot: Omit<Map<string, BlockMeta>, "set" | "clear" | "delete">;
3
+ };
1
4
  export declare function whenEditorIsReady(): Promise<void>;
2
5
  export declare function installEDGutenbergHooks(): void;
@@ -1,10 +1,12 @@
1
1
  import { jsx as _jsx } from "react/jsx-runtime";
2
2
  import { useEffect } from "react";
3
- import { useSnapshot } from "valtio";
3
+ import { subscribe, useSnapshot } from "valtio";
4
+ import { proxyMap } from "valtio/utils";
4
5
  import { getBlockMetadata } from "../block-utils.js";
5
6
  import { InlineEditingContextProvider } from "../inline-editing.js";
6
7
  import { EditorHighlights } from "./EditorHighlights.js";
7
8
  import { BlockContext, EditableBlock } from "./EditorSupport.js";
9
+ import { resolveAcfBlockName } from "./block-templates.js";
8
10
  import { blocksByTag } from "./blocks-by-tag.js";
9
11
  import { editorConfigStore, getEditingPostInfo, watchEditorTemplate } from "./editor-config.js";
10
12
  import { rootBlocks } from "./root-blocks.js";
@@ -21,6 +23,7 @@ function listenForHandleResize() {
21
23
  }
22
24
  }, 100);
23
25
  }
26
+ export const blockMetaDescriptors = proxyMap();
24
27
  export function whenEditorIsReady() {
25
28
  return new Promise((resolve) => {
26
29
  let ready = false;
@@ -57,6 +60,45 @@ export function installEDGutenbergHooks() {
57
60
  };
58
61
  }, "withClientIdClassName");
59
62
  wp.hooks.addFilter("editor.BlockListBlock", "ed", withClientIdClassName);
63
+ // Manage custom rich text formats
64
+ setTimeout(() => {
65
+ subscribe(editorConfigStore, () => {
66
+ const formats = editorConfigStore.config?.customRichTextFormats;
67
+ if (formats) {
68
+ const activeFormats = wp.data.select("core/rich-text").getFormatTypes();
69
+ formats.forEach((format) => {
70
+ if (activeFormats.some((f) => f.name === format.id)) {
71
+ wp.richText.unregisterFormatType(format.id);
72
+ }
73
+ const EditComponent = (props) => {
74
+ if (format.disabledByDefault) {
75
+ const explicitlyEnabled = props.contentRef.current
76
+ ?.getAttribute("data-allowed-formats")
77
+ ?.includes(format.id);
78
+ if (!explicitlyEnabled) {
79
+ return null;
80
+ }
81
+ }
82
+ return (_jsx(wp.components.Fill, { name: "BlockFormatControls", children: _jsx(wp.components.ToolbarButton, { icon: format.icon, title: format.title, onClick: () => {
83
+ props.onChange(wp.richText.toggleFormat(props.value, {
84
+ type: format.id,
85
+ }));
86
+ }, isActive: props.isActive }) }));
87
+ };
88
+ wp.richText.registerFormatType(format.id, {
89
+ name: format.id,
90
+ title: format.title,
91
+ tagName: format.tagName ?? "span",
92
+ className: format.className,
93
+ interactive: false,
94
+ edit: EditComponent,
95
+ // @ts-ignore
96
+ disabledByDefault: format.disabledByDefault,
97
+ });
98
+ });
99
+ }
100
+ });
101
+ });
60
102
  listenForHandleResize();
61
103
  // Remove unwanted formatting options
62
104
  // https://developer.wordpress.org/block-editor/how-to-guides/format-api/
@@ -81,7 +123,19 @@ export function installEDGutenbergHooks() {
81
123
  item.attributes.inline = { type: "object" };
82
124
  item.attributes.values = { type: "object" };
83
125
  item.attributes.isFromTemplate = { type: "boolean" };
126
+ item.supports.mode = false;
84
127
  item.supports.customClassName = false;
128
+ // item.transforms = {
129
+ // to: [
130
+ // {
131
+ // type: "block",
132
+ // blocks: ["core/paragraph"],
133
+ // transform: (attributes: any) => {
134
+ // return wp.blocks.createBlock("core/paragraph", { content: JSON.stringify(attributes) })
135
+ // },
136
+ // },
137
+ // ],
138
+ // }
85
139
  item.edit = function (props) {
86
140
  const self = this;
87
141
  useEffect(() => {
@@ -127,6 +181,19 @@ export function installEDGutenbergHooks() {
127
181
  return undefined;
128
182
  return blocksByTag.expand([...(item.parent ?? []), ...(isRootBlock ? ["core/post-content"] : [])]);
129
183
  },
184
+ get transforms() {
185
+ const result = {
186
+ from: item.transforms?.from ?? [],
187
+ to: item.transforms?.to ?? [],
188
+ ungroup: item.transforms?.ungroup,
189
+ };
190
+ const meta = blockMetaDescriptors.get(resolveAcfBlockName(name));
191
+ if (meta?.transforms) {
192
+ result.from = [...(result.from ?? []), ...(meta.transforms.from ?? [])];
193
+ result.to = [...(result.to ?? []), ...(meta.transforms.to ?? [])];
194
+ }
195
+ return result;
196
+ },
130
197
  get ancestor() {
131
198
  if (name === "core/block")
132
199
  return undefined;
@@ -1,9 +1,10 @@
1
+ export * from "./block-utils.js";
1
2
  export * from "./ContentBlocks.js";
2
- export * from "./EditableText.js";
3
- export * from "./InnerBlocks.js";
4
3
  export * from "./defineBlock.js";
4
+ export * from "./EditableText.js";
5
5
  export * from "./editor/controls.js";
6
- export * from "./editor/usePostEditor.js";
7
- export * from "./block-utils.js";
6
+ export * from "./editor/create-block.js";
8
7
  export { defineEditorConfig } from "./editor/editor-config.js";
8
+ export * from "./editor/usePostEditor.js";
9
9
  export { useBlockContext, useInlineEditableValue, useInnerBlocks, useTemplate } from "./inline-editing.js";
10
+ export * from "./InnerBlocks.js";
@@ -1,9 +1,10 @@
1
+ export * from "./block-utils.js";
1
2
  export * from "./ContentBlocks.js";
2
- export * from "./EditableText.js";
3
- export * from "./InnerBlocks.js";
4
3
  export * from "./defineBlock.js";
4
+ export * from "./EditableText.js";
5
5
  export * from "./editor/controls.js";
6
- export * from "./editor/usePostEditor.js";
7
- export * from "./block-utils.js";
6
+ export * from "./editor/create-block.js";
8
7
  export { defineEditorConfig } from "./editor/editor-config.js";
8
+ export * from "./editor/usePostEditor.js";
9
9
  export { useBlockContext, useInlineEditableValue, useInnerBlocks, useTemplate } from "./inline-editing.js";
10
+ export * from "./InnerBlocks.js";
@@ -2,6 +2,14 @@ import { ContentBlock } from "./ContentBlocks.js";
2
2
  import { PropsWithChildren } from "react";
3
3
  type Attributes = Record<string, any>;
4
4
  export type InlineValueStore<T> = [value: T, setValue: (value: T) => void];
5
+ export type BlockInstance = {
6
+ attributes: any;
7
+ clientId: string;
8
+ innerBlocks: BlockInstance[];
9
+ isValid: boolean;
10
+ name: string;
11
+ originalContent?: string | undefined;
12
+ };
5
13
  type InlineEditingContext = {
6
14
  values: Attributes;
7
15
  innerBlocks: ContentBlock[];
@@ -915,8 +915,8 @@ export declare function useTailwindConfig(): {
915
915
  readonly flex?: boolean | undefined;
916
916
  readonly blur?: boolean | undefined;
917
917
  readonly resize?: boolean | undefined;
918
- readonly position?: boolean | undefined;
919
918
  readonly columns?: boolean | undefined;
919
+ readonly position?: boolean | undefined;
920
920
  readonly preflight?: boolean | undefined;
921
921
  readonly container?: boolean | undefined;
922
922
  readonly accessibility?: boolean | undefined;
@@ -1960,8 +1960,8 @@ export declare function useTailwindConfig(): {
1960
1960
  readonly flex?: boolean | undefined;
1961
1961
  readonly blur?: boolean | undefined;
1962
1962
  readonly resize?: boolean | undefined;
1963
- readonly position?: boolean | undefined;
1964
1963
  readonly columns?: boolean | undefined;
1964
+ readonly position?: boolean | undefined;
1965
1965
  readonly preflight?: boolean | undefined;
1966
1966
  readonly container?: boolean | undefined;
1967
1967
  readonly accessibility?: boolean | undefined;
@@ -2980,8 +2980,8 @@ export declare function useTailwindConfig(): {
2980
2980
  readonly flex?: boolean | undefined;
2981
2981
  readonly blur?: boolean | undefined;
2982
2982
  readonly resize?: boolean | undefined;
2983
- readonly position?: boolean | undefined;
2984
2983
  readonly columns?: boolean | undefined;
2984
+ readonly position?: boolean | undefined;
2985
2985
  readonly preflight?: boolean | undefined;
2986
2986
  readonly container?: boolean | undefined;
2987
2987
  readonly accessibility?: boolean | undefined;
@@ -4051,8 +4051,8 @@ export declare function useTailwindConfig(): {
4051
4051
  readonly flex?: boolean | undefined;
4052
4052
  readonly blur?: boolean | undefined;
4053
4053
  readonly resize?: boolean | undefined;
4054
- readonly position?: boolean | undefined;
4055
4054
  readonly columns?: boolean | undefined;
4055
+ readonly position?: boolean | undefined;
4056
4056
  readonly preflight?: boolean | undefined;
4057
4057
  readonly container?: boolean | undefined;
4058
4058
  readonly accessibility?: boolean | undefined;
@@ -5069,8 +5069,8 @@ export declare function useTailwindConfig(): {
5069
5069
  readonly flex?: boolean | undefined;
5070
5070
  readonly blur?: boolean | undefined;
5071
5071
  readonly resize?: boolean | undefined;
5072
- readonly position?: boolean | undefined;
5073
5072
  readonly columns?: boolean | undefined;
5073
+ readonly position?: boolean | undefined;
5074
5074
  readonly preflight?: boolean | undefined;
5075
5075
  readonly container?: boolean | undefined;
5076
5076
  readonly accessibility?: boolean | undefined;
@@ -1 +1 @@
1
- export declare const VERSION = "2.0.0-beta.116";
1
+ export declare const VERSION = "2.0.0-beta.118";
@@ -1 +1 @@
1
- export const VERSION = "2.0.0-beta.116";
1
+ export const VERSION = "2.0.0-beta.118";
@@ -11,7 +11,10 @@ export type Args = {
11
11
  client: boolean;
12
12
  };
13
13
  export declare function envPlugin(args: Args): PluginOption;
14
- export declare function reactPlugin(): PluginOption[];
14
+ type ReactPluginArgs = {
15
+ retainBlockMeta?: boolean;
16
+ };
17
+ export declare function reactPlugin(args?: ReactPluginArgs): PluginOption[];
15
18
  /**
16
19
  * Turns on SSR module proxying,
17
20
  */
@@ -19,3 +22,4 @@ export declare function ssrPlugin(): PluginOption;
19
22
  export declare function corePlugins(args: Args): PluginOption[];
20
23
  export declare function storybookVitePlugins(args: Args): PluginOption[];
21
24
  export declare function getViteConfig(args: Args): InlineConfig;
25
+ export {};
@@ -34,14 +34,35 @@ export function envPlugin(args) {
34
34
  preventAssignment: true,
35
35
  });
36
36
  }
37
- export function reactPlugin() {
37
+ export function reactPlugin(args = {}) {
38
+ let currentFile = null;
38
39
  return react({
39
40
  babel: {
40
41
  overrides: [{ test: /types\./, compact: true }],
41
42
  plugins: [
42
43
  {
43
44
  visitor: {
44
- ExportNamedDeclaration: (path) => {
45
+ Program: {
46
+ enter: (path, state) => {
47
+ const filename = state.file.opts.filename;
48
+ if (filename.match(/.\/blocks\/.+\.tsx/)) {
49
+ currentFile = { type: "block", id: filename.replace(/.*\/blocks\/(.+)\.tsx/, "$1") };
50
+ }
51
+ else if (filename.match(/.\/views\/.+\.tsx/)) {
52
+ currentFile = { type: "view", id: filename.replace(/.*\/views\/(.+)\.tsx/, "$1") };
53
+ }
54
+ else {
55
+ currentFile = null;
56
+ }
57
+ },
58
+ exit: (path, state) => {
59
+ currentFile = null;
60
+ // console.log("Exiting", state.file.opts.filename)
61
+ },
62
+ },
63
+ ExportNamedDeclaration: (path, state) => {
64
+ if (!currentFile)
65
+ return;
45
66
  const decl = path.node.declaration;
46
67
  if (decl?.type !== "VariableDeclaration")
47
68
  return;
@@ -49,11 +70,20 @@ export function reactPlugin() {
49
70
  if (!t.isIdentifier(id))
50
71
  return;
51
72
  if (id.name === "meta") {
52
- path.remove();
73
+ if (args.retainBlockMeta && currentFile?.type === "block") {
74
+ // Adds a defineBlock.meta("name", meta) call, which can be used by the editor
75
+ path.replaceWith(t.callExpression(t.memberExpression(t.identifier("defineBlock"), t.identifier("meta")), [
76
+ t.stringLiteral(currentFile.id),
77
+ decl.declarations[0].init,
78
+ ]));
79
+ }
80
+ else {
81
+ path.remove();
82
+ }
53
83
  }
54
84
  },
55
85
  ExportDefaultDeclaration: (path) => {
56
- if (!path.hub.file.opts.filename.match(/\/(blocks|views)\//))
86
+ if (!currentFile)
57
87
  return;
58
88
  if (path.node.declaration.type === "CallExpression") {
59
89
  const call = path.node.declaration;
@@ -145,7 +175,12 @@ function buildStatusPlugin(console) {
145
175
  };
146
176
  }
147
177
  export function corePlugins(args) {
148
- return [args.serverless && ssrPlugin(), envPlugin(args), tsconfigPaths(), reactPlugin()].flat();
178
+ return [
179
+ args.serverless && ssrPlugin(),
180
+ envPlugin(args),
181
+ tsconfigPaths(),
182
+ reactPlugin({ retainBlockMeta: args.target === "cms" }),
183
+ ].flat();
149
184
  }
150
185
  export function storybookVitePlugins(args) {
151
186
  return [args.serverless && ssrPlugin(), envPlugin(args), tsconfigPaths()].flat();
@@ -176,7 +211,14 @@ export function getViteConfig(args) {
176
211
  define: {
177
212
  "process.env.NODE_ENV": JSON.stringify(args.mode),
178
213
  },
179
- plugins: [tsconfigPaths(), envPlugin(args), reactPlugin(), buildStatusPlugin(console)],
214
+ plugins: [
215
+ tsconfigPaths(),
216
+ envPlugin(args),
217
+ reactPlugin({
218
+ retainBlockMeta: args.target === "cms",
219
+ }),
220
+ buildStatusPlugin(console),
221
+ ],
180
222
  build: {
181
223
  manifest: true,
182
224
  sourcemap: true,
@@ -40,8 +40,8 @@ export declare const BlockMetaSchema: z.ZodObject<{
40
40
  slug: string;
41
41
  tags: string[];
42
42
  flags: Record<string, any>;
43
- category: string;
44
43
  parent: string[];
44
+ category: string;
45
45
  acfName: string;
46
46
  fileName: string;
47
47
  graphqlFieldName: string;
@@ -72,9 +72,9 @@ export declare const BlockMetaSchema: z.ZodObject<{
72
72
  mode?: "both" | "edit" | "preview" | undefined;
73
73
  tags?: string[] | undefined;
74
74
  flags?: Record<string, any> | undefined;
75
+ parent?: string[] | undefined;
75
76
  icon?: string | undefined;
76
77
  category?: string | undefined;
77
- parent?: string[] | undefined;
78
78
  description?: string | undefined;
79
79
  keywords?: string[] | undefined;
80
80
  templates?: string[] | null | undefined;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "eddev",
3
- "version": "2.0.0-beta.116",
3
+ "version": "2.0.0-beta.118",
4
4
  "description": "",
5
5
  "main": "index.js",
6
6
  "type": "module",
@@ -135,7 +135,7 @@
135
135
  },
136
136
  "peerDependencies": {
137
137
  "react": "^18.3.1",
138
- "react-dom": "^108.3.1",
138
+ "react-dom": "^18.3.1",
139
139
  "tailwindcss": "^3.4.4"
140
140
  }
141
141
  }
package/types.meta.d.ts CHANGED
@@ -153,6 +153,14 @@ declare global {
153
153
  * Set to `false` for blocks which should not be added manually, such as deprecated blocks, or blocks which are added automatically using template locks.
154
154
  */
155
155
  inserter?: boolean
156
+
157
+ /**
158
+ * Transforms
159
+ */
160
+ transforms?: {
161
+ from?: AnyBlockTransform[]
162
+ to?: AnyBlockTransform[]
163
+ }
156
164
  }
157
165
  }
158
166
  type BlockIcon =
@@ -316,4 +324,101 @@ type BlockIcon =
316
324
  | "lightbulb"
317
325
  | "smiley"
318
326
 
327
+ type TransformRawSchema = {
328
+ [k in keyof HTMLElementTagNameMap | "#text"]?: {
329
+ attributes?: string[] | undefined
330
+ require?: Array<keyof HTMLElementTagNameMap> | undefined
331
+ classes?: Array<string | RegExp> | undefined
332
+ children?: TransformRawSchema | undefined
333
+ }
334
+ }
335
+
336
+ interface TransformBlock<T extends Record<string, any>> {
337
+ type: "block"
338
+ priority?: number | undefined
339
+ blocks: string[]
340
+ isMatch?(attributes: T, block: string | string[]): boolean
341
+ isMultiBlock?: boolean | undefined
342
+ transform(attributes: T): BlockInstance<Partial<T>>
343
+ }
344
+
345
+ interface TransformEnter<T extends Record<string, any>> {
346
+ type: "enter"
347
+ priority?: number | undefined
348
+ regExp: RegExp
349
+ transform(): BlockInstance<Partial<T>>
350
+ }
351
+
352
+ interface TransformFiles<T extends Record<string, any>> {
353
+ type: "files"
354
+ priority?: number | undefined
355
+ isMatch?(files: FileList): boolean
356
+ transform(files: FileList, onChange?: (id: string, attrs: T) => void): BlockInstance<Partial<T>>
357
+ }
358
+
359
+ interface TransformPrefix<T extends Record<string, any>> {
360
+ type: "prefix"
361
+ priority?: number | undefined
362
+ prefix: string
363
+ transform(content: string): BlockInstance<Partial<T>>
364
+ }
365
+
366
+ interface TransformRaw<T extends Record<string, any>> {
367
+ type: "raw"
368
+ priority?: number | undefined
369
+ /**
370
+ * Comma-separated list of selectors, no spaces.
371
+ *
372
+ * @example 'p,div,h1,.css-class,#id'
373
+ */
374
+ selector?: string | undefined
375
+ schema?: TransformRawSchema | undefined
376
+ isMatch?(node: Node): boolean
377
+ // eslint-disable-next-line @typescript-eslint/no-invalid-void-type
378
+ transform?(node: Node): BlockInstance<Partial<T>> | void
379
+ }
380
+
381
+ interface TransformShortcode<T extends Record<string, any>> {
382
+ type: "shortcode"
383
+ priority?: number | undefined
384
+ tag: string
385
+ transform?(attributes: any, match: ShortcodeMatch): BlockInstance<T>
386
+ attributes?: any // TODO: add stronger types here.
387
+ }
388
+
389
+ type AnyBlockTransform<T extends Record<string, any> = Record<string, any>> =
390
+ | TransformBlock<T>
391
+ | TransformEnter<T>
392
+ | TransformFiles<T>
393
+ | TransformPrefix<T>
394
+ | TransformRaw<T>
395
+ | TransformShortcode<T>
396
+
397
+ type BlockInstance<T extends Record<string, any> = { [k: string]: any }> = {
398
+ /**
399
+ * Attributes for the block.
400
+ */
401
+ readonly attributes: T
402
+ /**
403
+ * Unique ID registered to the block.
404
+ */
405
+ readonly clientId: string
406
+ /**
407
+ * Array of inner blocks, if the block has any.
408
+ */
409
+ readonly innerBlocks: BlockInstance[]
410
+ /**
411
+ * Indicates whether or not the block is valid.
412
+ */
413
+ readonly isValid: boolean
414
+ /**
415
+ * The block's registered name.
416
+ */
417
+ readonly name: string
418
+ /**
419
+ * The parsed HTML content of the block.
420
+ */
421
+ readonly originalContent?: string | undefined
422
+ }
423
+
319
424
  export {}