quoin-editor 0.2.0 → 0.4.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/README.md +77 -6
- package/dist/index.cjs +39 -21
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +85 -15
- package/dist/index.d.ts +85 -15
- package/dist/index.js +39 -21
- package/dist/index.js.map +1 -1
- package/dist/styles.css +1 -1
- package/package.json +1 -1
package/dist/index.d.cts
CHANGED
|
@@ -42,18 +42,11 @@ type BlockEditorProps<D = Record<string, unknown>> = {
|
|
|
42
42
|
onDone?: () => void;
|
|
43
43
|
};
|
|
44
44
|
|
|
45
|
-
/**
|
|
46
|
-
type
|
|
45
|
+
/** The fields every block definition carries, whatever it can do with its data. */
|
|
46
|
+
type BlockDefinitionCommon<D extends object> = {
|
|
47
47
|
type: string;
|
|
48
48
|
label: string;
|
|
49
49
|
icon: LucideIcon;
|
|
50
|
-
editor: ComponentType<BlockEditorProps<D>>;
|
|
51
|
-
/**
|
|
52
|
-
* What the block shows when it is not being edited. Optional so a host can register an
|
|
53
|
-
* edit-only block type, and overridable so a host can supply rich rendering (a full
|
|
54
|
-
* markdown pipeline, a chart) without Quoin depending on one.
|
|
55
|
-
*/
|
|
56
|
-
preview?: ComponentType<BlockPreviewProps<D>>;
|
|
57
50
|
/**
|
|
58
51
|
* Called when a new block of this type is inserted, to seed its data.
|
|
59
52
|
* `NoInfer` keeps `D` inferred from `editor` and `preview` alone, so the seed is checked
|
|
@@ -69,6 +62,31 @@ type BlockDefinition<D extends object = Record<string, unknown>> = {
|
|
|
69
62
|
*/
|
|
70
63
|
alwaysEditing?: boolean;
|
|
71
64
|
};
|
|
65
|
+
/**
|
|
66
|
+
* Describes one block type: how to label it, how to draw it at rest, how to edit it.
|
|
67
|
+
*
|
|
68
|
+
* Supply `editor`, `preview`, or both, and the canvas does the rest. A definition with no
|
|
69
|
+
* `preview` is edit-only: a read-only canvas has nothing to draw for it but its label.
|
|
70
|
+
* A definition with no `editor` can never be edited in the canvas, which is what a block
|
|
71
|
+
* whose content belongs to a server-side record wants; the card offers no edit affordance
|
|
72
|
+
* for it and a click goes through to whatever the preview itself renders.
|
|
73
|
+
*
|
|
74
|
+
* Omitting both is a type error, because such a block would only ever draw its own label.
|
|
75
|
+
*/
|
|
76
|
+
type BlockDefinition<D extends object = Record<string, unknown>> = (BlockDefinitionCommon<D> & {
|
|
77
|
+
editor: ComponentType<BlockEditorProps<D>>;
|
|
78
|
+
/**
|
|
79
|
+
* What the block shows when it is not being edited. Optional so a host can register an
|
|
80
|
+
* edit-only block type, and overridable so a host can supply rich rendering (a full
|
|
81
|
+
* markdown pipeline, a chart) without Quoin depending on one.
|
|
82
|
+
*/
|
|
83
|
+
preview?: ComponentType<BlockPreviewProps<D>>;
|
|
84
|
+
}) | (BlockDefinitionCommon<D> & {
|
|
85
|
+
/** Omitted: this block is never editable in the canvas. */
|
|
86
|
+
editor?: undefined;
|
|
87
|
+
/** Required here, because it is the only thing that can draw the block. */
|
|
88
|
+
preview: ComponentType<BlockPreviewProps<D>>;
|
|
89
|
+
});
|
|
72
90
|
/** A definition with its data type erased, so definitions of different shapes compose in one array. */
|
|
73
91
|
type AnyBlockDefinition = BlockDefinition<Record<string, unknown>>;
|
|
74
92
|
/**
|
|
@@ -77,8 +95,11 @@ type AnyBlockDefinition = BlockDefinition<Record<string, unknown>>;
|
|
|
77
95
|
* on `editor`, so without it an array of differently-shaped definitions will not typecheck.
|
|
78
96
|
* `initialData` is checked against the editor's data type. That relies on `NoInfer`, so
|
|
79
97
|
* TypeScript 5.4 or later is required.
|
|
98
|
+
*
|
|
99
|
+
* `D` defaults to `Record<string, unknown>`, so a definition whose components take no
|
|
100
|
+
* annotation still gets a usable `data` rather than a bare `object`.
|
|
80
101
|
*/
|
|
81
|
-
declare function defineBlock<D extends object
|
|
102
|
+
declare function defineBlock<D extends object = Record<string, unknown>>(definition: BlockDefinition<D>): AnyBlockDefinition;
|
|
82
103
|
/** The definition for a type, or undefined; the last registration of a type wins, so a host can override a core block. */
|
|
83
104
|
declare function blockByType(definitions: AnyBlockDefinition[], type: string): AnyBlockDefinition | undefined;
|
|
84
105
|
/** The definitions offered in the slash palette, in registration order, one per type, with the last registration of a type winning. */
|
|
@@ -132,6 +153,54 @@ declare function BlockCanvas({ blocks, onChange, onDelete, blockTypes, upload, r
|
|
|
132
153
|
/** The block types Quoin ships with. Spread them alongside your own definitions. */
|
|
133
154
|
declare const coreBlocks: AnyBlockDefinition[];
|
|
134
155
|
|
|
156
|
+
/**
|
|
157
|
+
* The `data` shape each core block keeps its content under. A host that persists blocks
|
|
158
|
+
* itself reads and writes these, so they are part of the public surface rather than an
|
|
159
|
+
* implementation detail. `core-data.test.tsx` holds every one of them against the editor
|
|
160
|
+
* that writes it and the seed it starts from.
|
|
161
|
+
*
|
|
162
|
+
* Every field is declared because something writes it. The optional ones are written
|
|
163
|
+
* later than the block's first save: an image's `caption` when the author types one, its
|
|
164
|
+
* `width` and `height` once the uploaded bytes have been measured, a file's `size` when
|
|
165
|
+
* the upload reports it.
|
|
166
|
+
*/
|
|
167
|
+
/** `MARKDOWN`. The source text, not rendered HTML: Quoin stores and edits markdown. */
|
|
168
|
+
type MarkdownBlockData = {
|
|
169
|
+
markdown: string;
|
|
170
|
+
};
|
|
171
|
+
/** `CALLOUT`. The prose is under `body`, not `text`. */
|
|
172
|
+
type CalloutBlockData = {
|
|
173
|
+
tone: string;
|
|
174
|
+
body: string;
|
|
175
|
+
};
|
|
176
|
+
/** `CODE`. `language` is a free string, written through as the author typed it. */
|
|
177
|
+
type CodeBlockData = {
|
|
178
|
+
language: string;
|
|
179
|
+
code: string;
|
|
180
|
+
};
|
|
181
|
+
/** `IMAGE`. `storageKey` names the uploaded file; the host's `resolveAssetUrl` turns it into a URL. */
|
|
182
|
+
type ImageBlockData = {
|
|
183
|
+
storageKey: string;
|
|
184
|
+
alt: string;
|
|
185
|
+
caption?: string;
|
|
186
|
+
width?: number;
|
|
187
|
+
height?: number;
|
|
188
|
+
};
|
|
189
|
+
/** `FILE`. `storageKey` names the uploaded file; `filename` is what the author picked. */
|
|
190
|
+
type FileBlockData = {
|
|
191
|
+
storageKey: string;
|
|
192
|
+
filename: string;
|
|
193
|
+
size?: number;
|
|
194
|
+
};
|
|
195
|
+
/** Maps each core block's `type` string to its data shape. */
|
|
196
|
+
type CoreBlockData = {
|
|
197
|
+
MARKDOWN: MarkdownBlockData;
|
|
198
|
+
CALLOUT: CalloutBlockData;
|
|
199
|
+
CODE: CodeBlockData;
|
|
200
|
+
IMAGE: ImageBlockData;
|
|
201
|
+
FILE: FileBlockData;
|
|
202
|
+
};
|
|
203
|
+
|
|
135
204
|
/** Props for `UploadField`; the upload function itself comes from the canvas, not from here. */
|
|
136
205
|
type UploadFieldProps = {
|
|
137
206
|
/** Names what the file is for, and is passed to the host's `upload` as its second argument. */
|
|
@@ -199,15 +268,16 @@ declare function useBlockList(initial: QuoinBlock[]): BlockList;
|
|
|
199
268
|
|
|
200
269
|
/** Where a debounced save stands, for a "Saving..." or "Saved" indicator. */
|
|
201
270
|
type AutosaveStatus = 'idle' | 'saving' | 'saved' | 'retrying';
|
|
202
|
-
type SaveResult = {
|
|
203
|
-
ok?: boolean;
|
|
204
|
-
} | void;
|
|
205
271
|
/** What `useAutosave` tracks, how to build the payload, and how to persist it. */
|
|
206
272
|
type UseAutosaveInput<T> = {
|
|
207
273
|
/** The tracked fields. An edit to any of them marks dirty and restarts the debounce. */
|
|
208
274
|
deps: React.DependencyList;
|
|
209
275
|
build: () => T;
|
|
210
|
-
|
|
276
|
+
/**
|
|
277
|
+
* Persists the payload. Return anything: a mutation or a `fetch` that resolves to a body
|
|
278
|
+
* can be passed straight in. A save fails by rejecting, or by resolving to `{ ok: false }`.
|
|
279
|
+
*/
|
|
280
|
+
save: (payload: T) => unknown;
|
|
211
281
|
debounceMs?: number;
|
|
212
282
|
maxBackoffMs?: number;
|
|
213
283
|
};
|
|
@@ -318,4 +388,4 @@ declare function useMarkdownKeymap({ commit, onOpenPalette, }: {
|
|
|
318
388
|
onPaste: (event: React.ClipboardEvent<HTMLTextAreaElement>) => void;
|
|
319
389
|
};
|
|
320
390
|
|
|
321
|
-
export { type AnyBlockDefinition, type AutosaveStatus, BlockCanvas, type BlockCanvasProps, type BlockDefinition, type BlockEditorProps, type BlockList, type BlockPreviewProps, type CaretRect, type Edit, FilePickerButton, type FilePickerButtonProps, type InsertableCommand, type LinePrefix, type QuoinBlock, type QuoinConfig, type QuoinConfigInput, QuoinProvider, TEXT_COMMANDS, type TextCommand, type UndoToken, UploadField, type UploadFieldProps, type UploadFn, type UploadResult, type UseAutosaveInput, type UseAutosaveResult, type WrapKind, applyEdit, blockByType, caretRect, continueList, coreBlocks, defineBlock, formatShortcut, indentList, insertCommand, lineAt, linkPaste, paletteBlocks, shouldOpenPalette, toggleLinePrefix, useAutoGrow, useAutosave, useBlockList, useMarkdownKeymap, useQuoin, wrapSelection };
|
|
391
|
+
export { type AnyBlockDefinition, type AutosaveStatus, BlockCanvas, type BlockCanvasProps, type BlockDefinition, type BlockEditorProps, type BlockList, type BlockPreviewProps, type CalloutBlockData, type CaretRect, type CodeBlockData, type CoreBlockData, type Edit, type FileBlockData, FilePickerButton, type FilePickerButtonProps, type ImageBlockData, type InsertableCommand, type LinePrefix, type MarkdownBlockData, type QuoinBlock, type QuoinConfig, type QuoinConfigInput, QuoinProvider, TEXT_COMMANDS, type TextCommand, type UndoToken, UploadField, type UploadFieldProps, type UploadFn, type UploadResult, type UseAutosaveInput, type UseAutosaveResult, type WrapKind, applyEdit, blockByType, caretRect, continueList, coreBlocks, defineBlock, formatShortcut, indentList, insertCommand, lineAt, linkPaste, paletteBlocks, shouldOpenPalette, toggleLinePrefix, useAutoGrow, useAutosave, useBlockList, useMarkdownKeymap, useQuoin, wrapSelection };
|
package/dist/index.d.ts
CHANGED
|
@@ -42,18 +42,11 @@ type BlockEditorProps<D = Record<string, unknown>> = {
|
|
|
42
42
|
onDone?: () => void;
|
|
43
43
|
};
|
|
44
44
|
|
|
45
|
-
/**
|
|
46
|
-
type
|
|
45
|
+
/** The fields every block definition carries, whatever it can do with its data. */
|
|
46
|
+
type BlockDefinitionCommon<D extends object> = {
|
|
47
47
|
type: string;
|
|
48
48
|
label: string;
|
|
49
49
|
icon: LucideIcon;
|
|
50
|
-
editor: ComponentType<BlockEditorProps<D>>;
|
|
51
|
-
/**
|
|
52
|
-
* What the block shows when it is not being edited. Optional so a host can register an
|
|
53
|
-
* edit-only block type, and overridable so a host can supply rich rendering (a full
|
|
54
|
-
* markdown pipeline, a chart) without Quoin depending on one.
|
|
55
|
-
*/
|
|
56
|
-
preview?: ComponentType<BlockPreviewProps<D>>;
|
|
57
50
|
/**
|
|
58
51
|
* Called when a new block of this type is inserted, to seed its data.
|
|
59
52
|
* `NoInfer` keeps `D` inferred from `editor` and `preview` alone, so the seed is checked
|
|
@@ -69,6 +62,31 @@ type BlockDefinition<D extends object = Record<string, unknown>> = {
|
|
|
69
62
|
*/
|
|
70
63
|
alwaysEditing?: boolean;
|
|
71
64
|
};
|
|
65
|
+
/**
|
|
66
|
+
* Describes one block type: how to label it, how to draw it at rest, how to edit it.
|
|
67
|
+
*
|
|
68
|
+
* Supply `editor`, `preview`, or both, and the canvas does the rest. A definition with no
|
|
69
|
+
* `preview` is edit-only: a read-only canvas has nothing to draw for it but its label.
|
|
70
|
+
* A definition with no `editor` can never be edited in the canvas, which is what a block
|
|
71
|
+
* whose content belongs to a server-side record wants; the card offers no edit affordance
|
|
72
|
+
* for it and a click goes through to whatever the preview itself renders.
|
|
73
|
+
*
|
|
74
|
+
* Omitting both is a type error, because such a block would only ever draw its own label.
|
|
75
|
+
*/
|
|
76
|
+
type BlockDefinition<D extends object = Record<string, unknown>> = (BlockDefinitionCommon<D> & {
|
|
77
|
+
editor: ComponentType<BlockEditorProps<D>>;
|
|
78
|
+
/**
|
|
79
|
+
* What the block shows when it is not being edited. Optional so a host can register an
|
|
80
|
+
* edit-only block type, and overridable so a host can supply rich rendering (a full
|
|
81
|
+
* markdown pipeline, a chart) without Quoin depending on one.
|
|
82
|
+
*/
|
|
83
|
+
preview?: ComponentType<BlockPreviewProps<D>>;
|
|
84
|
+
}) | (BlockDefinitionCommon<D> & {
|
|
85
|
+
/** Omitted: this block is never editable in the canvas. */
|
|
86
|
+
editor?: undefined;
|
|
87
|
+
/** Required here, because it is the only thing that can draw the block. */
|
|
88
|
+
preview: ComponentType<BlockPreviewProps<D>>;
|
|
89
|
+
});
|
|
72
90
|
/** A definition with its data type erased, so definitions of different shapes compose in one array. */
|
|
73
91
|
type AnyBlockDefinition = BlockDefinition<Record<string, unknown>>;
|
|
74
92
|
/**
|
|
@@ -77,8 +95,11 @@ type AnyBlockDefinition = BlockDefinition<Record<string, unknown>>;
|
|
|
77
95
|
* on `editor`, so without it an array of differently-shaped definitions will not typecheck.
|
|
78
96
|
* `initialData` is checked against the editor's data type. That relies on `NoInfer`, so
|
|
79
97
|
* TypeScript 5.4 or later is required.
|
|
98
|
+
*
|
|
99
|
+
* `D` defaults to `Record<string, unknown>`, so a definition whose components take no
|
|
100
|
+
* annotation still gets a usable `data` rather than a bare `object`.
|
|
80
101
|
*/
|
|
81
|
-
declare function defineBlock<D extends object
|
|
102
|
+
declare function defineBlock<D extends object = Record<string, unknown>>(definition: BlockDefinition<D>): AnyBlockDefinition;
|
|
82
103
|
/** The definition for a type, or undefined; the last registration of a type wins, so a host can override a core block. */
|
|
83
104
|
declare function blockByType(definitions: AnyBlockDefinition[], type: string): AnyBlockDefinition | undefined;
|
|
84
105
|
/** The definitions offered in the slash palette, in registration order, one per type, with the last registration of a type winning. */
|
|
@@ -132,6 +153,54 @@ declare function BlockCanvas({ blocks, onChange, onDelete, blockTypes, upload, r
|
|
|
132
153
|
/** The block types Quoin ships with. Spread them alongside your own definitions. */
|
|
133
154
|
declare const coreBlocks: AnyBlockDefinition[];
|
|
134
155
|
|
|
156
|
+
/**
|
|
157
|
+
* The `data` shape each core block keeps its content under. A host that persists blocks
|
|
158
|
+
* itself reads and writes these, so they are part of the public surface rather than an
|
|
159
|
+
* implementation detail. `core-data.test.tsx` holds every one of them against the editor
|
|
160
|
+
* that writes it and the seed it starts from.
|
|
161
|
+
*
|
|
162
|
+
* Every field is declared because something writes it. The optional ones are written
|
|
163
|
+
* later than the block's first save: an image's `caption` when the author types one, its
|
|
164
|
+
* `width` and `height` once the uploaded bytes have been measured, a file's `size` when
|
|
165
|
+
* the upload reports it.
|
|
166
|
+
*/
|
|
167
|
+
/** `MARKDOWN`. The source text, not rendered HTML: Quoin stores and edits markdown. */
|
|
168
|
+
type MarkdownBlockData = {
|
|
169
|
+
markdown: string;
|
|
170
|
+
};
|
|
171
|
+
/** `CALLOUT`. The prose is under `body`, not `text`. */
|
|
172
|
+
type CalloutBlockData = {
|
|
173
|
+
tone: string;
|
|
174
|
+
body: string;
|
|
175
|
+
};
|
|
176
|
+
/** `CODE`. `language` is a free string, written through as the author typed it. */
|
|
177
|
+
type CodeBlockData = {
|
|
178
|
+
language: string;
|
|
179
|
+
code: string;
|
|
180
|
+
};
|
|
181
|
+
/** `IMAGE`. `storageKey` names the uploaded file; the host's `resolveAssetUrl` turns it into a URL. */
|
|
182
|
+
type ImageBlockData = {
|
|
183
|
+
storageKey: string;
|
|
184
|
+
alt: string;
|
|
185
|
+
caption?: string;
|
|
186
|
+
width?: number;
|
|
187
|
+
height?: number;
|
|
188
|
+
};
|
|
189
|
+
/** `FILE`. `storageKey` names the uploaded file; `filename` is what the author picked. */
|
|
190
|
+
type FileBlockData = {
|
|
191
|
+
storageKey: string;
|
|
192
|
+
filename: string;
|
|
193
|
+
size?: number;
|
|
194
|
+
};
|
|
195
|
+
/** Maps each core block's `type` string to its data shape. */
|
|
196
|
+
type CoreBlockData = {
|
|
197
|
+
MARKDOWN: MarkdownBlockData;
|
|
198
|
+
CALLOUT: CalloutBlockData;
|
|
199
|
+
CODE: CodeBlockData;
|
|
200
|
+
IMAGE: ImageBlockData;
|
|
201
|
+
FILE: FileBlockData;
|
|
202
|
+
};
|
|
203
|
+
|
|
135
204
|
/** Props for `UploadField`; the upload function itself comes from the canvas, not from here. */
|
|
136
205
|
type UploadFieldProps = {
|
|
137
206
|
/** Names what the file is for, and is passed to the host's `upload` as its second argument. */
|
|
@@ -199,15 +268,16 @@ declare function useBlockList(initial: QuoinBlock[]): BlockList;
|
|
|
199
268
|
|
|
200
269
|
/** Where a debounced save stands, for a "Saving..." or "Saved" indicator. */
|
|
201
270
|
type AutosaveStatus = 'idle' | 'saving' | 'saved' | 'retrying';
|
|
202
|
-
type SaveResult = {
|
|
203
|
-
ok?: boolean;
|
|
204
|
-
} | void;
|
|
205
271
|
/** What `useAutosave` tracks, how to build the payload, and how to persist it. */
|
|
206
272
|
type UseAutosaveInput<T> = {
|
|
207
273
|
/** The tracked fields. An edit to any of them marks dirty and restarts the debounce. */
|
|
208
274
|
deps: React.DependencyList;
|
|
209
275
|
build: () => T;
|
|
210
|
-
|
|
276
|
+
/**
|
|
277
|
+
* Persists the payload. Return anything: a mutation or a `fetch` that resolves to a body
|
|
278
|
+
* can be passed straight in. A save fails by rejecting, or by resolving to `{ ok: false }`.
|
|
279
|
+
*/
|
|
280
|
+
save: (payload: T) => unknown;
|
|
211
281
|
debounceMs?: number;
|
|
212
282
|
maxBackoffMs?: number;
|
|
213
283
|
};
|
|
@@ -318,4 +388,4 @@ declare function useMarkdownKeymap({ commit, onOpenPalette, }: {
|
|
|
318
388
|
onPaste: (event: React.ClipboardEvent<HTMLTextAreaElement>) => void;
|
|
319
389
|
};
|
|
320
390
|
|
|
321
|
-
export { type AnyBlockDefinition, type AutosaveStatus, BlockCanvas, type BlockCanvasProps, type BlockDefinition, type BlockEditorProps, type BlockList, type BlockPreviewProps, type CaretRect, type Edit, FilePickerButton, type FilePickerButtonProps, type InsertableCommand, type LinePrefix, type QuoinBlock, type QuoinConfig, type QuoinConfigInput, QuoinProvider, TEXT_COMMANDS, type TextCommand, type UndoToken, UploadField, type UploadFieldProps, type UploadFn, type UploadResult, type UseAutosaveInput, type UseAutosaveResult, type WrapKind, applyEdit, blockByType, caretRect, continueList, coreBlocks, defineBlock, formatShortcut, indentList, insertCommand, lineAt, linkPaste, paletteBlocks, shouldOpenPalette, toggleLinePrefix, useAutoGrow, useAutosave, useBlockList, useMarkdownKeymap, useQuoin, wrapSelection };
|
|
391
|
+
export { type AnyBlockDefinition, type AutosaveStatus, BlockCanvas, type BlockCanvasProps, type BlockDefinition, type BlockEditorProps, type BlockList, type BlockPreviewProps, type CalloutBlockData, type CaretRect, type CodeBlockData, type CoreBlockData, type Edit, type FileBlockData, FilePickerButton, type FilePickerButtonProps, type ImageBlockData, type InsertableCommand, type LinePrefix, type MarkdownBlockData, type QuoinBlock, type QuoinConfig, type QuoinConfigInput, QuoinProvider, TEXT_COMMANDS, type TextCommand, type UndoToken, UploadField, type UploadFieldProps, type UploadFn, type UploadResult, type UseAutosaveInput, type UseAutosaveResult, type WrapKind, applyEdit, blockByType, caretRect, continueList, coreBlocks, defineBlock, formatShortcut, indentList, insertCommand, lineAt, linkPaste, paletteBlocks, shouldOpenPalette, toggleLinePrefix, useAutoGrow, useAutosave, useBlockList, useMarkdownKeymap, useQuoin, wrapSelection };
|
package/dist/index.js
CHANGED
|
@@ -52,12 +52,12 @@ function blockByType(definitions, type) {
|
|
|
52
52
|
return void 0;
|
|
53
53
|
}
|
|
54
54
|
function paletteBlocks(definitions) {
|
|
55
|
-
const
|
|
55
|
+
const seen2 = /* @__PURE__ */ new Set();
|
|
56
56
|
const out = [];
|
|
57
57
|
for (let i = definitions.length - 1; i >= 0; i -= 1) {
|
|
58
58
|
const d = definitions[i];
|
|
59
|
-
if (
|
|
60
|
-
|
|
59
|
+
if (seen2.has(d.type)) continue;
|
|
60
|
+
seen2.add(d.type);
|
|
61
61
|
if (d.inSlashPalette === false) continue;
|
|
62
62
|
out.push(d);
|
|
63
63
|
}
|
|
@@ -1478,12 +1478,12 @@ function FileBlockEditor({ data, onChange }) {
|
|
|
1478
1478
|
// src/blocks/previews.tsx
|
|
1479
1479
|
import { jsx as jsx18 } from "react/jsx-runtime";
|
|
1480
1480
|
var HINT = "text-sm text-muted-foreground";
|
|
1481
|
-
var
|
|
1482
|
-
info: "border-
|
|
1483
|
-
tip: "border-
|
|
1484
|
-
warning: "border-
|
|
1485
|
-
success: "border-
|
|
1486
|
-
danger: "border-destructive"
|
|
1481
|
+
var TONE = {
|
|
1482
|
+
info: "border-info bg-info/15",
|
|
1483
|
+
tip: "border-primary bg-primary/15",
|
|
1484
|
+
warning: "border-warning bg-warning/15",
|
|
1485
|
+
success: "border-success bg-success/15",
|
|
1486
|
+
danger: "border-destructive bg-destructive/15"
|
|
1487
1487
|
};
|
|
1488
1488
|
function MarkdownPreview({ data }) {
|
|
1489
1489
|
const markdown = str(data.markdown);
|
|
@@ -1510,8 +1510,8 @@ function CalloutPreview({ data }) {
|
|
|
1510
1510
|
const body = str(data.body);
|
|
1511
1511
|
if (body.trim() === "") return /* @__PURE__ */ jsx18("p", { className: HINT, children: "Empty callout" });
|
|
1512
1512
|
const tone = str(data.tone) || "info";
|
|
1513
|
-
const
|
|
1514
|
-
return /* @__PURE__ */ jsx18("div", { "data-tone": tone, className: `rounded-md border-l-4 ${
|
|
1513
|
+
const paint = TONE[tone] ?? "border-border bg-muted/50";
|
|
1514
|
+
return /* @__PURE__ */ jsx18("div", { "data-tone": tone, className: `rounded-md border-l-4 ${paint} p-3 text-sm`, children: body });
|
|
1515
1515
|
}
|
|
1516
1516
|
|
|
1517
1517
|
// src/blocks/core.ts
|
|
@@ -1825,9 +1825,8 @@ function BlockEditor({
|
|
|
1825
1825
|
onDone
|
|
1826
1826
|
}) {
|
|
1827
1827
|
const { blockTypes } = useQuoin();
|
|
1828
|
-
const
|
|
1829
|
-
if (!
|
|
1830
|
-
const Editor = definition.editor;
|
|
1828
|
+
const Editor = blockByType(blockTypes, type)?.editor;
|
|
1829
|
+
if (!Editor) return null;
|
|
1831
1830
|
return /* @__PURE__ */ jsx21(Editor, { data, onChange, onInsertBlock, onDone });
|
|
1832
1831
|
}
|
|
1833
1832
|
|
|
@@ -1876,6 +1875,15 @@ function sourceOffsetFor(source, probe) {
|
|
|
1876
1875
|
return search(source, probe);
|
|
1877
1876
|
}
|
|
1878
1877
|
|
|
1878
|
+
// src/dev-warn.ts
|
|
1879
|
+
var seen = /* @__PURE__ */ new Set();
|
|
1880
|
+
function devWarn(message) {
|
|
1881
|
+
if (typeof process !== "undefined" && process?.env?.NODE_ENV === "production") return;
|
|
1882
|
+
if (seen.has(message)) return;
|
|
1883
|
+
seen.add(message);
|
|
1884
|
+
console.warn(`Quoin: ${message}`);
|
|
1885
|
+
}
|
|
1886
|
+
|
|
1879
1887
|
// src/canvas/BlockCard.tsx
|
|
1880
1888
|
import { jsx as jsx22, jsxs as jsxs15 } from "react/jsx-runtime";
|
|
1881
1889
|
var INTERACTIVE_SELECTOR = 'a, button, input, textarea, select, [role="button"], [tabindex], iframe';
|
|
@@ -1897,7 +1905,8 @@ function BlockCardImpl({
|
|
|
1897
1905
|
const { blockTypes, onError } = useQuoin();
|
|
1898
1906
|
const definition = blockByType(blockTypes, block.type);
|
|
1899
1907
|
const Preview = definition?.preview;
|
|
1900
|
-
const
|
|
1908
|
+
const editable = Boolean(definition?.editor) && !readOnly;
|
|
1909
|
+
const [editing, setEditing] = React8.useState(editingByDefault && editable);
|
|
1901
1910
|
const editButtonRef = React8.useRef(null);
|
|
1902
1911
|
const editorContainerRef = React8.useRef(null);
|
|
1903
1912
|
const returnFocusOnClose = React8.useRef(false);
|
|
@@ -1977,7 +1986,13 @@ function BlockCardImpl({
|
|
|
1977
1986
|
reportedType.current = block.type;
|
|
1978
1987
|
onError(new Error(`No block type is registered for "${block.type}".`));
|
|
1979
1988
|
}, [definition, block.type, onError]);
|
|
1980
|
-
|
|
1989
|
+
React8.useEffect(() => {
|
|
1990
|
+
if (!readOnly || !definition || definition.preview) return;
|
|
1991
|
+
devWarn(
|
|
1992
|
+
`the block type "${block.type}" has no \`preview\`, so a read-only canvas can draw only its label. Give its definition a \`preview\` to render the block itself.`
|
|
1993
|
+
);
|
|
1994
|
+
}, [readOnly, definition, block.type]);
|
|
1995
|
+
if (definition?.alwaysEditing && editable) {
|
|
1981
1996
|
return /* @__PURE__ */ jsx22(
|
|
1982
1997
|
BlockEditor,
|
|
1983
1998
|
{
|
|
@@ -2017,7 +2032,7 @@ function BlockCardImpl({
|
|
|
2017
2032
|
);
|
|
2018
2033
|
}
|
|
2019
2034
|
const rest = Preview ? /* @__PURE__ */ jsx22(Preview, { data: block.data, blockId: block.id }) : /* @__PURE__ */ jsx22("p", { className: "py-1 text-sm text-muted-foreground", children: definition?.label ?? block.type });
|
|
2020
|
-
if (
|
|
2035
|
+
if (!editable) return /* @__PURE__ */ jsx22("div", { children: rest });
|
|
2021
2036
|
return /* @__PURE__ */ jsxs15("div", { className: "group/card relative", children: [
|
|
2022
2037
|
/* @__PURE__ */ jsx22(
|
|
2023
2038
|
"div",
|
|
@@ -2473,6 +2488,9 @@ function BlockCanvas({
|
|
|
2473
2488
|
|
|
2474
2489
|
// src/hooks/useAutosave.ts
|
|
2475
2490
|
import * as React12 from "react";
|
|
2491
|
+
function reportedFailure(result) {
|
|
2492
|
+
return typeof result === "object" && result !== null && "ok" in result && result.ok === false;
|
|
2493
|
+
}
|
|
2476
2494
|
function useAutosave({
|
|
2477
2495
|
deps,
|
|
2478
2496
|
build,
|
|
@@ -2496,7 +2514,7 @@ function useAutosave({
|
|
|
2496
2514
|
setStatus("saving");
|
|
2497
2515
|
try {
|
|
2498
2516
|
const res = await saveRef.current(buildRef.current());
|
|
2499
|
-
if (res
|
|
2517
|
+
if (reportedFailure(res)) throw new Error("save rejected");
|
|
2500
2518
|
retryRef.current = 0;
|
|
2501
2519
|
dirtyRef.current = false;
|
|
2502
2520
|
setStatus("saved");
|
|
@@ -2512,10 +2530,10 @@ function useAutosave({
|
|
|
2512
2530
|
});
|
|
2513
2531
|
const seenRef = React12.useRef(null);
|
|
2514
2532
|
React12.useEffect(() => {
|
|
2515
|
-
const
|
|
2533
|
+
const seen2 = seenRef.current;
|
|
2516
2534
|
seenRef.current = deps;
|
|
2517
|
-
if (
|
|
2518
|
-
const unchanged =
|
|
2535
|
+
if (seen2 === null) return;
|
|
2536
|
+
const unchanged = seen2.length === deps.length && seen2.every((v, i) => v === deps[i]);
|
|
2519
2537
|
if (!unchanged) dirtyRef.current = true;
|
|
2520
2538
|
}, deps);
|
|
2521
2539
|
React12.useEffect(() => {
|