rimelight-components 2.0.47 → 2.0.49

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.
Files changed (59) hide show
  1. package/dist/module.json +1 -1
  2. package/dist/module.mjs +4 -2
  3. package/dist/runtime/components/app/ScrollToTop.d.vue.ts +4 -1
  4. package/dist/runtime/components/app/ScrollToTop.vue.d.ts +4 -1
  5. package/dist/runtime/components/blocks/Block.d.vue.ts +4 -17
  6. package/dist/runtime/components/blocks/Block.vue +25 -46
  7. package/dist/runtime/components/blocks/Block.vue.d.ts +4 -17
  8. package/dist/runtime/components/blocks/BlockEditRenderer.vue +55 -0
  9. package/dist/runtime/components/blocks/BlockEditor.d.vue.ts +23 -0
  10. package/dist/runtime/components/blocks/BlockEditor.vue +127 -0
  11. package/dist/runtime/components/blocks/BlockEditor.vue.d.ts +23 -0
  12. package/dist/runtime/components/{renderers/BlockRenderer.vue → blocks/BlockViewRenderer.vue} +21 -18
  13. package/dist/runtime/components/blocks/editor/CalloutBlockEditor.vue +1 -1
  14. package/dist/runtime/components/blocks/editor/CardBlockEditor.vue +1 -1
  15. package/dist/runtime/components/blocks/editor/ParagraphBlockEditor.d.vue.ts +5 -2
  16. package/dist/runtime/components/blocks/editor/ParagraphBlockEditor.vue +53 -5
  17. package/dist/runtime/components/blocks/editor/ParagraphBlockEditor.vue.d.ts +5 -2
  18. package/dist/runtime/components/blocks/editor/SectionBlockEditor.d.vue.ts +5 -2
  19. package/dist/runtime/components/blocks/editor/SectionBlockEditor.vue +101 -45
  20. package/dist/runtime/components/blocks/editor/SectionBlockEditor.vue.d.ts +5 -2
  21. package/dist/runtime/components/blocks/editor/TestBlockEditor.d.vue.ts +7 -0
  22. package/dist/runtime/components/blocks/editor/TestBlockEditor.vue +44 -0
  23. package/dist/runtime/components/blocks/editor/TestBlockEditor.vue.d.ts +7 -0
  24. package/dist/runtime/components/blocks/renderer/CalloutBlockRenderer.vue +1 -1
  25. package/dist/runtime/components/blocks/renderer/CardBlockRenderer.vue +1 -1
  26. package/dist/runtime/components/blocks/renderer/ParagraphBlockRenderer.vue +1 -3
  27. package/dist/runtime/components/blocks/renderer/SectionBlockRenderer.vue +2 -1
  28. package/dist/runtime/components/blocks/renderer/TestBlockRenderer.d.vue.ts +4 -0
  29. package/dist/runtime/components/blocks/renderer/TestBlockRenderer.vue +9 -0
  30. package/dist/runtime/components/blocks/renderer/TestBlockRenderer.vue.d.ts +4 -0
  31. package/dist/runtime/components/cards/TeamCard.d.vue.ts +2 -2
  32. package/dist/runtime/components/cards/TeamCard.vue.d.ts +2 -2
  33. package/dist/runtime/components/content/Callout.d.vue.ts +2 -2
  34. package/dist/runtime/components/content/Callout.vue.d.ts +2 -2
  35. package/dist/runtime/components/content/Section.d.vue.ts +5 -5
  36. package/dist/runtime/components/content/Section.vue +13 -8
  37. package/dist/runtime/components/content/Section.vue.d.ts +5 -5
  38. package/dist/runtime/components/content/Test.d.vue.ts +16 -0
  39. package/dist/runtime/components/content/Test.vue +13 -0
  40. package/dist/runtime/components/content/Test.vue.d.ts +16 -0
  41. package/dist/runtime/composables/useBlockEditor.d.ts +27233 -0
  42. package/dist/runtime/composables/useBlockEditor.js +163 -0
  43. package/dist/runtime/types/blocks.d.ts +9 -2
  44. package/dist/runtime/utils/richTextHelpers.d.ts +16 -0
  45. package/dist/runtime/utils/richTextHelpers.js +17 -0
  46. package/package.json +19 -21
  47. package/dist/runtime/components/renderers/BlockEditor.vue +0 -63
  48. package/dist/runtime/composables/useBlockContentEditor.d.ts +0 -32
  49. package/dist/runtime/composables/useBlockContentEditor.js +0 -63
  50. /package/dist/runtime/components/{renderers/BlockEditor.d.vue.ts → blocks/BlockEditRenderer.d.vue.ts} +0 -0
  51. /package/dist/runtime/components/{renderers/BlockEditor.vue.d.ts → blocks/BlockEditRenderer.vue.d.ts} +0 -0
  52. /package/dist/runtime/components/{renderers/BlockRenderer.d.vue.ts → blocks/BlockViewRenderer.d.vue.ts} +0 -0
  53. /package/dist/runtime/components/{renderers/BlockRenderer.vue.d.ts → blocks/BlockViewRenderer.vue.d.ts} +0 -0
  54. /package/dist/runtime/components/{renderers → blocks}/TOC.d.vue.ts +0 -0
  55. /package/dist/runtime/components/{renderers → blocks}/TOC.vue +0 -0
  56. /package/dist/runtime/components/{renderers → blocks}/TOC.vue.d.ts +0 -0
  57. /package/dist/runtime/components/{renderers → blocks}/TextRenderer.d.vue.ts +0 -0
  58. /package/dist/runtime/components/{renderers → blocks}/TextRenderer.vue +0 -0
  59. /package/dist/runtime/components/{renderers → blocks}/TextRenderer.vue.d.ts +0 -0
@@ -0,0 +1,163 @@
1
+ import { computed, ref, shallowRef } from "vue";
2
+ import { v7 as uuidv7 } from "uuid";
3
+ function findBlockLocation(blocks, id) {
4
+ for (let i = 0; i < blocks.length; i++) {
5
+ const block = blocks[i];
6
+ if (!block) continue;
7
+ if (block.id === id) {
8
+ return { parentArray: blocks, index: i };
9
+ }
10
+ if ("children" in block.props && Array.isArray(block.props.children)) {
11
+ const result = findBlockLocation(block.props.children, id);
12
+ if (result) return result;
13
+ }
14
+ }
15
+ return null;
16
+ }
17
+ function regenerateIds(block) {
18
+ block.id = uuidv7();
19
+ if ("children" in block.props && Array.isArray(block.props.children)) {
20
+ block.props.children.forEach((child) => regenerateIds(child));
21
+ }
22
+ }
23
+ export function useBlockEditor(initialBlocks, maxHistorySize = 100) {
24
+ const history = shallowRef([]);
25
+ const future = shallowRef([]);
26
+ const committedBlocks = ref(JSON.parse(JSON.stringify(initialBlocks.value)));
27
+ const captureSnapshot = () => {
28
+ const snapshot = JSON.parse(JSON.stringify(initialBlocks.value));
29
+ future.value = [];
30
+ const newHistory = [...history.value, snapshot];
31
+ if (newHistory.length > maxHistorySize) {
32
+ newHistory.shift();
33
+ }
34
+ history.value = newHistory;
35
+ };
36
+ const executeMutation = (mutationFn) => {
37
+ captureSnapshot();
38
+ mutationFn();
39
+ };
40
+ const undo = () => {
41
+ if (history.value.length === 0) return;
42
+ const currentState = JSON.parse(JSON.stringify(initialBlocks.value));
43
+ future.value = [currentState, ...future.value];
44
+ const previousState = history.value[history.value.length - 1];
45
+ if (previousState) {
46
+ initialBlocks.value.splice(0, initialBlocks.value.length, ...previousState);
47
+ const newHistory = [...history.value];
48
+ newHistory.pop();
49
+ history.value = newHistory;
50
+ }
51
+ };
52
+ const redo = () => {
53
+ if (future.value.length === 0) return;
54
+ const currentState = JSON.parse(JSON.stringify(initialBlocks.value));
55
+ let newHistory = [...history.value, currentState];
56
+ if (newHistory.length > maxHistorySize) {
57
+ newHistory.shift();
58
+ }
59
+ history.value = newHistory;
60
+ const nextState = future.value[0];
61
+ if (nextState) {
62
+ initialBlocks.value.splice(0, initialBlocks.value.length, ...nextState);
63
+ const newFuture = [...future.value];
64
+ newFuture.shift();
65
+ future.value = newFuture;
66
+ }
67
+ };
68
+ const canUndo = computed(() => history.value.length > 0);
69
+ const canRedo = computed(() => future.value.length > 0);
70
+ const updateBlockProps = (id, newProps) => {
71
+ executeMutation(() => {
72
+ const loc = findBlockLocation(initialBlocks.value, id);
73
+ if (!loc) return;
74
+ const oldBlock = loc.parentArray[loc.index];
75
+ if (!oldBlock) return;
76
+ const newPropsObject = {
77
+ ...oldBlock.props,
78
+ ...newProps
79
+ };
80
+ const newBlock = {
81
+ id: oldBlock.id,
82
+ // Explicitly carry over the required BaseBlock properties
83
+ type: oldBlock.type,
84
+ // Explicitly carry over the required BaseBlock properties
85
+ props: newPropsObject
86
+ };
87
+ loc.parentArray.splice(loc.index, 1, newBlock);
88
+ });
89
+ };
90
+ const removeBlock = (id) => {
91
+ executeMutation(() => {
92
+ const loc = findBlockLocation(initialBlocks.value, id);
93
+ if (!loc) return;
94
+ loc.parentArray.splice(loc.index, 1);
95
+ });
96
+ };
97
+ const moveBlock = (id, direction) => {
98
+ executeMutation(() => {
99
+ const loc = findBlockLocation(initialBlocks.value, id);
100
+ if (!loc) return;
101
+ const { parentArray, index } = loc;
102
+ const newIndex = index + direction;
103
+ if (newIndex >= 0 && newIndex < parentArray.length) {
104
+ const movedBlock = parentArray.splice(index, 1)[0];
105
+ if (movedBlock) {
106
+ parentArray.splice(newIndex, 0, movedBlock);
107
+ }
108
+ }
109
+ });
110
+ };
111
+ const duplicateBlock = (id) => {
112
+ executeMutation(() => {
113
+ const loc = findBlockLocation(initialBlocks.value, id);
114
+ if (!loc) return;
115
+ const original = loc.parentArray[loc.index];
116
+ const clone = JSON.parse(JSON.stringify(original));
117
+ regenerateIds(clone);
118
+ loc.parentArray.splice(loc.index + 1, 0, clone);
119
+ });
120
+ };
121
+ const insertBlock = (newBlockType, targetId = null, position = "after") => {
122
+ executeMutation(() => {
123
+ const newBlock = {
124
+ id: uuidv7(),
125
+ type: newBlockType,
126
+ props: getDefaultPropsForType(newBlockType)
127
+ };
128
+ if (!targetId) {
129
+ initialBlocks.value.push(newBlock);
130
+ return;
131
+ }
132
+ const loc = findBlockLocation(initialBlocks.value, targetId);
133
+ if (loc) {
134
+ const insertIndex = position === "after" ? loc.index + 1 : loc.index;
135
+ loc.parentArray.splice(insertIndex, 0, newBlock);
136
+ }
137
+ });
138
+ };
139
+ const commitChanges = () => {
140
+ const committedSnapshot = JSON.parse(JSON.stringify(initialBlocks.value));
141
+ committedBlocks.value = committedSnapshot;
142
+ return committedSnapshot;
143
+ };
144
+ return {
145
+ // Mutations
146
+ updateBlockProps,
147
+ removeBlock,
148
+ moveBlock,
149
+ duplicateBlock,
150
+ insertBlock,
151
+ // History
152
+ undo,
153
+ redo,
154
+ canUndo,
155
+ canRedo,
156
+ // State
157
+ committedBlocks,
158
+ commitChanges
159
+ };
160
+ }
161
+ function getDefaultPropsForType(type) {
162
+ return { children: [] };
163
+ }
@@ -6,7 +6,7 @@ export type HeadingLevel = 1 | 2 | 3 | 4 | 5 | 6;
6
6
  * All valid block types the application can render.
7
7
  * This union type is the single source of truth for component names.
8
8
  */
9
- export type BlockType = "SectionBlock" | "ParagraphBlock" | "CalloutBlock" | "ImageBlock" | "QuoteBlock" | "UnorderedListBlock" | "CardBlock" | "CollapsibleCardBlock";
9
+ export type BlockType = "TestBlock" | "SectionBlock" | "ParagraphBlock" | "CalloutBlock" | "ImageBlock" | "QuoteBlock" | "UnorderedListBlock" | "CardBlock" | "CollapsibleCardBlock";
10
10
  /**
11
11
  * Defines the common structure for any content block object.
12
12
  * The 'type' must be one of the registered BlockType values.
@@ -16,6 +16,9 @@ export interface BaseBlock {
16
16
  type: BlockType;
17
17
  props: Record<string, any>;
18
18
  }
19
+ export interface TestBlockProps {
20
+ text: string;
21
+ }
19
22
  export interface SectionBlockProps {
20
23
  level: HeadingLevel;
21
24
  title: string;
@@ -56,6 +59,10 @@ export interface CollapsibleCardBlockProps {
56
59
  name?: string;
57
60
  children: Block[];
58
61
  }
62
+ export interface TestBlock extends BaseBlock {
63
+ type: "TestBlock";
64
+ props: TestBlockProps;
65
+ }
59
66
  export interface SectionBlock extends BaseBlock {
60
67
  type: "SectionBlock";
61
68
  props: SectionBlockProps;
@@ -92,7 +99,7 @@ export interface QuoteContentBlock extends BaseBlock {
92
99
  * The full union type for a single block. This allows for type-checking the
93
100
  * payload based on the block 'type'.
94
101
  */
95
- export type Block = SectionBlock | ParagraphContentBlock | CalloutContentBlock | ImageContentBlock | QuoteContentBlock | UnorderedListContentBlock | CardBlockContent | CollapsibleCardContentBlock;
102
+ export type Block = TestBlock | SectionBlock | ParagraphContentBlock | CalloutContentBlock | ImageContentBlock | QuoteContentBlock | UnorderedListContentBlock | CardBlockContent | CollapsibleCardContentBlock;
96
103
  /**
97
104
  * Text Rendering Components
98
105
  */
@@ -0,0 +1,16 @@
1
+ import type { RichTextContent } from "../types/blocks.js";
2
+ /**
3
+ * Helper: Converts RichTextContent array into a plain string for a simple contenteditable area.
4
+ * For complex editors, this would generate full HTML/DOM nodes, preserving links/mentions.
5
+ * For now, we only extract the text content.
6
+ */
7
+ export declare function richTextToHtml(content: RichTextContent): string;
8
+ /**
9
+ * Helper: Converts a plain string from a contenteditable area back into RichTextContent.
10
+ *
11
+ * NOTE: For full rich text support, this function must parse complex HTML (<a>, <b>, <span>)
12
+ * and generate the correct InlineLink, InlineMention, and InlineText objects.
13
+ * Since we are only simulating the text content change, we convert the entire string
14
+ * into a single InlineText element.
15
+ */
16
+ export declare function parseHtmlToRichText(html: string): RichTextContent;
@@ -0,0 +1,17 @@
1
+ import { v7 as uuidv7 } from "uuid";
2
+ export function richTextToHtml(content) {
3
+ return content.map((item) => item.props.content).join("");
4
+ }
5
+ export function parseHtmlToRichText(html) {
6
+ if (html.trim().length === 0) {
7
+ return [];
8
+ }
9
+ const newTextNode = {
10
+ id: uuidv7(),
11
+ type: "text",
12
+ props: {
13
+ content: html.trim()
14
+ }
15
+ };
16
+ return [newTextNode];
17
+ }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "rimelight-components",
3
3
  "description": "A component library by Rimelight Entertainment.",
4
- "version": "2.0.47",
4
+ "version": "2.0.49",
5
5
  "type": "module",
6
6
  "repository": {
7
7
  "type": "git",
@@ -79,34 +79,32 @@
79
79
  },
80
80
  "dependencies": {
81
81
  "@nuxt/image": "^1.11.0",
82
- "@nuxt/kit": "^4.2.0",
83
- "@nuxt/ui": "^4.1.0",
84
- "@nuxtjs/i18n": "^10.1.2",
85
- "@vueuse/core": "^14.0.0",
86
- "@vueuse/nuxt": "^14.0.0",
82
+ "@nuxt/kit": "^4.2.2",
83
+ "@nuxt/ui": "^4.2.1",
84
+ "@nuxtjs/i18n": "^10.2.1",
85
+ "@vueuse/core": "^14.1.0",
86
+ "@vueuse/nuxt": "^14.1.0",
87
87
  "date-fns": "^4.1.0",
88
88
  "defu": "^6.1.4",
89
- "nuxt": "^4.2.0",
90
- "pinia": "^3.0.4",
91
- "tailwind-variants": "^3.1.1",
92
- "vue": "^3.5.22"
89
+ "nuxt": "^4.2.2",
90
+ "tailwind-variants": "^3.2.2",
91
+ "uuid": "^13.0.0",
92
+ "vue": "^3.5.25"
93
93
  },
94
94
  "devDependencies": {
95
- "@nuxt/devtools": "^3.0.1",
95
+ "@nuxt/devtools": "^3.1.1",
96
96
  "@nuxt/module-builder": "^1.0.2",
97
- "@nuxt/schema": "^4.2.0",
98
- "@nuxt/test-utils": "^3.20.1",
99
- "@prettier/plugin-oxc": "^0.0.4",
97
+ "@nuxt/schema": "^4.2.2",
98
+ "@nuxt/test-utils": "^3.21.0",
100
99
  "@types/node": "latest",
101
100
  "changelogen": "^0.6.2",
102
- "oxlint": "^1.25.0",
103
- "prettier": "^3.6.2",
104
- "prettier-plugin-tailwindcss": "^0.7.1",
105
- "release-it": "^19.0.5",
106
- "tailwind-merge": "^3.3.1",
101
+ "oxfmt": "^0.18.0",
102
+ "oxlint": "^1.33.0",
103
+ "release-it": "^19.1.0",
104
+ "tailwind-merge": "^3.4.0",
107
105
  "typescript": "~5.9.3",
108
- "vitest": "^4.0.6",
109
- "vue-tsc": "^3.1.2"
106
+ "vitest": "^4.0.15",
107
+ "vue-tsc": "^3.1.8"
110
108
  },
111
109
  "trustedDependencies": [
112
110
  "@parcel/watcher",
@@ -1,63 +0,0 @@
1
- <script setup>
2
- import { inject, provide } from "vue";
3
- import { getBlockEditorComponent } from "../../utils/blockMapper";
4
- const { blocks } = defineProps({
5
- blocks: { type: Array, required: true }
6
- });
7
- const { removeBlock, moveBlock } = inject("blockEditorMutators", {
8
- removeBlock: (id) => console.warn(`[Renderer] removeBlock not provided. Cannot delete block ${id}.`),
9
- moveBlock: (id, newIndex) => console.warn(`[Renderer] moveBlock not provided. Cannot move block ${id}.`)
10
- });
11
- provide("blockEditorMutators", { removeBlock, moveBlock });
12
- const getComponent = (block) => {
13
- if (!block || !block.type || block.type.length === 0) {
14
- console.error(
15
- "[EditorBlockRenderer] Block object is missing the critical 'type' field."
16
- );
17
- return null;
18
- }
19
- const resolvedComponent = getBlockEditorComponent(block.type);
20
- if (!resolvedComponent) {
21
- console.error(
22
- `[EditorBlockRenderer] Editor component resolution failed for block type: ${block.type}`
23
- );
24
- return null;
25
- }
26
- return resolvedComponent;
27
- };
28
- </script>
29
-
30
- <template>
31
- <div class="flex flex-col gap-lg editor-block-renderer">
32
- <UEmpty
33
- v-if="!blocks || blocks.length === 0"
34
- variant="naked"
35
- icon="lucide:pencil-line"
36
- title="Start adding content blocks."
37
- description="There is no content yet. Use the '+' button below to add your first block."
38
- />
39
- <div v-else class="flex flex-col gap-md ml-10">
40
- <template v-for="block in blocks" :key="block.id">
41
- <RCBlock v-if="getComponent(block)" :id="block.id">
42
- <component
43
- :is="getComponent(block)"
44
- v-bind="block.props"
45
- :is-editing="true"
46
- :key="block.id"
47
- :type="block.type"
48
- class="block-editor-container"
49
- />
50
- </RCBlock>
51
- <template v-else>
52
- <UAlert
53
- color="warning"
54
- variant="subtle"
55
- icon="lucide:octagon-alert"
56
- title="Editor Rendering Error"
57
- :description="`Editor component for type \'${block.type || 'UNKNOWN_OR_MISSING'}\' was not found. Please ensure the block name is correct and the editor component exists in the \'editor\' subdirectory.`"
58
- />
59
- </template>
60
- </template>
61
- </div>
62
- </div>
63
- </template>
@@ -1,32 +0,0 @@
1
- import { type Ref } from "vue";
2
- import { type Block } from "../types/blocks.js";
3
- /**
4
- * Defines the contract for the consuming application's save function.
5
- * This allows the composable to trigger persistence without knowing the API details.
6
- * It's assumed the callback will handle the necessary API calls and error handling.
7
- */
8
- type SaveCallback = (blocks: Block[]) => Promise<void> | void;
9
- export interface UseBlockContentEditorOptions {
10
- /** The initial array of blocks loaded from the database/API. */
11
- initialBlocks: Block[];
12
- /** The function the editor calls to persist changes. It is debounced internally. */
13
- saveCallback: SaveCallback;
14
- /** Optional. The debounce delay in milliseconds (default: 1000ms). */
15
- debounceDelay?: number;
16
- }
17
- /**
18
- * Manages the reactive state, updates, and debounced persistence of a block content array.
19
- * This composable is decoupled from data fetching and specific API endpoints.
20
- *
21
- * @param options Configuration object including initial blocks and a save callback.
22
- */
23
- export declare const useBlockContentEditor: ({ initialBlocks, saveCallback, debounceDelay, }: UseBlockContentEditorOptions) => {
24
- blocks: Ref<Block[], Block[]>;
25
- isSaving: Ref<boolean, boolean>;
26
- lastSavedAt: Ref<Date | null, Date | null>;
27
- hasUnsavedChanges: Ref<boolean, boolean>;
28
- addBlock: (newBlock: Block, index?: number) => void;
29
- removeBlock: (blockId: string) => void;
30
- moveBlock: (blockId: string, newIndex: number) => void;
31
- };
32
- export {};
@@ -1,63 +0,0 @@
1
- import { ref, watch } from "vue";
2
- import { useDebounceFn } from "@vueuse/core";
3
- export const useBlockContentEditor = ({
4
- initialBlocks,
5
- saveCallback,
6
- debounceDelay = 1e3
7
- }) => {
8
- const blocks = ref(JSON.parse(JSON.stringify(initialBlocks)));
9
- const isSaving = ref(false);
10
- const lastSavedAt = ref(null);
11
- const hasUnsavedChanges = ref(false);
12
- const persistChanges = async (currentBlocks) => {
13
- isSaving.value = true;
14
- hasUnsavedChanges.value = false;
15
- try {
16
- await saveCallback(currentBlocks);
17
- lastSavedAt.value = /* @__PURE__ */ new Date();
18
- console.log("Blocks persisted successfully via callback.");
19
- } catch (error) {
20
- console.error("[BlockEditor] Error during persistence callback:", error);
21
- hasUnsavedChanges.value = true;
22
- } finally {
23
- isSaving.value = false;
24
- }
25
- };
26
- const debouncedSave = useDebounceFn(() => {
27
- persistChanges(JSON.parse(JSON.stringify(blocks.value)));
28
- }, debounceDelay);
29
- watch(
30
- blocks,
31
- () => {
32
- hasUnsavedChanges.value = true;
33
- debouncedSave();
34
- },
35
- { deep: true }
36
- // Crucial: Watch nested changes (e.g., text inside a ParagraphBlock)
37
- );
38
- const addBlock = (newBlock, index = blocks.value.length) => {
39
- blocks.value.splice(index, 0, newBlock);
40
- };
41
- const removeBlock = (blockId) => {
42
- blocks.value = blocks.value.filter((block) => block.id !== blockId);
43
- };
44
- const moveBlock = (blockId, newIndex) => {
45
- const oldIndex = blocks.value.findIndex((block) => block.id === blockId);
46
- if (oldIndex === -1) return;
47
- const [blockToMove] = blocks.value.splice(oldIndex, 1);
48
- blocks.value.splice(newIndex, 0, blockToMove);
49
- };
50
- return {
51
- // Core Data
52
- blocks,
53
- // The array the EditorBlockRenderer will use
54
- // Status
55
- isSaving,
56
- lastSavedAt,
57
- hasUnsavedChanges,
58
- // Editor Actions (Mutators)
59
- addBlock,
60
- removeBlock,
61
- moveBlock
62
- };
63
- };