edytor 0.1.0-next.16 → 0.1.0-next.18
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 +14 -13
- package/dist/block/block.svelte.d.ts +2 -0
- package/dist/block/block.utils.d.ts +14 -0
- package/dist/clipboard/createClipboardFragment.js +5 -2
- package/dist/clipboard/htmlFlow.js +8 -0
- package/dist/components/Block.svelte +13 -5
- package/dist/components/Edytor.svelte +3 -8
- package/dist/crdt/document.d.ts +3 -2
- package/dist/crdt/document.js +9 -4
- package/dist/crdt/edytor-doc.d.ts +17 -0
- package/dist/crdt/edytor-doc.js +329 -24
- package/dist/crdt/flow.d.ts +9 -2
- package/dist/crdt/flow.js +25 -2
- package/dist/crdt/index.d.ts +8 -1
- package/dist/crdt/index.js +1 -1
- package/dist/crdt/placement/model.d.ts +25 -5
- package/dist/crdt/placement/model.js +28 -14
- package/dist/crdt/rangeDelete.d.ts +7 -0
- package/dist/crdt/rangeDelete.js +2 -0
- package/dist/crdt/semantics.d.ts +27 -2
- package/dist/crdt/semantics.js +18 -3
- package/dist/crdt/text/runs.d.ts +12 -1
- package/dist/crdt/text/runs.js +93 -4
- package/dist/edytor.svelte.js +5 -7
- package/dist/events/beforeInputDeleteCommands.js +19 -3
- package/dist/events/onBeforeInput.d.ts +1 -1
- package/dist/events/onBeforeInput.js +28 -1
- package/dist/events/onDrop.d.ts +1 -1
- package/dist/events/onFocus.d.ts +11 -0
- package/dist/events/onFocus.js +80 -16
- package/dist/events/onKeyDown.js +25 -0
- package/dist/events/onPaste.js +9 -3
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/kinds.d.ts +23 -0
- package/dist/kinds.js +49 -1
- package/dist/plugins/blockHandles/BlockHandle.svelte +9 -3
- package/dist/plugins/blockHandles/BlockHandleController.svelte.d.ts +110 -6
- package/dist/plugins/blockHandles/BlockHandleController.svelte.js +430 -25
- package/dist/plugins/blockHandles/BlockHandles.svelte +94 -5
- package/dist/plugins/blockHandles/blockHandlesPlugin.d.ts +10 -3
- package/dist/plugins/blockHandles/blockHandlesPlugin.js +38 -6
- package/dist/plugins/blockHandles/dragPreview.d.ts +2 -2
- package/dist/plugins/blockHandles/dragPreview.js +6 -4
- package/dist/plugins/blockMenu/BlockMenu.svelte +9 -1
- package/dist/plugins/blockMenu/BlockMenuController.svelte.d.ts +10 -1
- package/dist/plugins/blockMenu/BlockMenuController.svelte.js +32 -12
- package/dist/plugins/blockMenu/blockMenuPlugin.js +35 -6
- package/dist/plugins/columns/ColumnResize.svelte +87 -0
- package/dist/plugins/columns/ColumnResize.svelte.d.ts +7 -0
- package/dist/plugins/columns/ColumnsPlugin.svelte +237 -0
- package/dist/plugins/columns/ColumnsPlugin.svelte.d.ts +40 -0
- package/dist/plugins/columns/columns.css +52 -0
- package/dist/plugins/columns/gaps.d.ts +33 -0
- package/dist/plugins/columns/gaps.js +41 -0
- package/dist/plugins/columns/resize.svelte.d.ts +109 -0
- package/dist/plugins/columns/resize.svelte.js +253 -0
- package/dist/plugins/columns/stacking.d.ts +7 -0
- package/dist/plugins/columns/stacking.js +7 -0
- package/dist/plugins/icons.js +10 -0
- package/dist/plugins/index.d.ts +1 -0
- package/dist/plugins/index.js +1 -0
- package/dist/plugins.d.ts +21 -4
- package/dist/selection/replaceSelection.d.ts +19 -1
- package/dist/selection/replaceSelection.js +58 -4
- package/dist/selection/selection.svelte.d.ts +20 -4
- package/dist/selection/selection.svelte.js +26 -2
- package/dist/session/attempt.d.ts +15 -0
- package/dist/session/attempt.js +20 -0
- package/dist/session/bindings.js +28 -8
- package/dist/session/commands.d.ts +2 -0
- package/dist/session/commands.js +19 -2
- package/dist/session/composition.svelte.d.ts +17 -2
- package/dist/session/composition.svelte.js +49 -13
- package/dist/session/history.d.ts +8 -4
- package/dist/session/history.js +20 -4
- package/dist/session/moves.d.ts +9 -2
- package/dist/session/moves.js +42 -10
- package/dist/session/navigation.d.ts +7 -0
- package/dist/session/navigation.js +22 -5
- package/dist/surface/observer.svelte.js +3 -0
- package/dist/surface/projector.svelte.d.ts +26 -2
- package/dist/surface/projector.svelte.js +69 -3
- package/dist/themes/notion.css +16 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -7,22 +7,23 @@
|
|
|
7
7
|
[](https://svelte.dev)
|
|
8
8
|
|
|
9
9
|
<p>
|
|
10
|
-
<a href="https://edytor
|
|
10
|
+
<a href="https://edytor.dev/docs">Documentation</a> •
|
|
11
11
|
<a href="#quick-start">Quick start</a> •
|
|
12
|
-
<a href="https://edytor
|
|
12
|
+
<a href="https://edytor.dev/docs/reference/migration">Migrating from 0.0.11</a>
|
|
13
13
|
</p>
|
|
14
14
|
</div>
|
|
15
15
|
|
|
16
16
|
Edytor aims to be for Svelte what Slate.js is for React: a heavily customizable editor with an API to build any kind of collaborative rich text editor.
|
|
17
17
|
|
|
18
|
-
> **Work in progress.** Edytor is a pre-release (`0.1.0-next.
|
|
18
|
+
> **Work in progress.** Edytor is a pre-release (`0.1.0-next.18`, `edytor@next` on npm) and not ready for production; the API changes between releases without a compatibility layer. The untagged `edytor@0.0.11` on npm predates the current API. Issues and PRs are welcome: when you report a bug, include the document's JSON value.
|
|
19
19
|
|
|
20
20
|
## Features
|
|
21
21
|
|
|
22
22
|
- **Notion-style editing out of the box.** `<Edytor />` alone is a rich text editor with images: headings, lists, to-dos, toggles, callouts, quotes, dividers, ten marks, Notion's markdown shortcuts and hotkeys. Add the Notion theme, block handles with a block menu (drag one block, or every block the selection covers), a slash menu, a selection toolbar and code blocks.
|
|
23
|
+
- **Columns, opt-in.** List `columnsPlugin` for Notion's multi-column layouts: drag a block to the edge of another to put them side by side, or pick "2 columns" to "5 columns"; drag the gap to resize. Layouts converge across collaborators and the room ([Columns](https://edytor.dev/docs/plugins/columns)).
|
|
23
24
|
- **Your markup.** Blocks, marks and inline atoms render through your Svelte snippets; the slash menu, toolbar, block menu and handles take a snippet and keep their behavior.
|
|
24
25
|
- **Synced properties.** `block.data`, `atom.data` and `edytor.data` read and write like plain objects (`bind:value={block.data.title}`); each property and each array item syncs on its own, so concurrent edits of different properties, or of different items of one array, merge.
|
|
25
|
-
- **AI suggestions.** `edytor.suggestions.add(position, content)` proposes text, paragraphs, lists, to-dos or images after, before or inside a block, at the end of its text, or in place of the selection; it streams in, shows only on your screen, and becomes one undo step when accepted ([Suggestions](https://edytor
|
|
26
|
+
- **AI suggestions.** `edytor.suggestions.add(position, content)` proposes text, paragraphs, lists, to-dos or images after, before or inside a block, at the end of its text, or in place of the selection; it streams in, shows only on your screen, and becomes one undo step when accepted ([Suggestions](https://edytor.dev/docs/editor/suggestions)).
|
|
26
27
|
- **Plugins that can veto anything.** Every command is prepared before it writes; plugins see the command and each planned step and can refuse or replace it.
|
|
27
28
|
- **Real-time collaboration.** One `EdytorDocument` shared by any number of views, or none (headless). Presence cursors, identity-preserving moves, splits and merges, and undo that only takes back your own edits.
|
|
28
29
|
- **Offline first.** A local IndexedDB copy and cross-tab sync; offline edits survive reloads and reach the server on reconnect.
|
|
@@ -37,7 +38,7 @@ The pre-release is on npm under the `next` tag. Name the tag: a bare `edytor` is
|
|
|
37
38
|
pnpm add edytor@next
|
|
38
39
|
```
|
|
39
40
|
|
|
40
|
-
`svelte@^5` is a peer dependency. Do not install `yjs`: the v14 engine is vendored (`edytor/crdt`). See [installation](https://edytor
|
|
41
|
+
`svelte@^5` is a peer dependency. Do not install `yjs`: the v14 engine is vendored (`edytor/crdt`). See [installation](https://edytor.dev/docs/getting-started).
|
|
41
42
|
|
|
42
43
|
## Quick start
|
|
43
44
|
|
|
@@ -77,7 +78,7 @@ pnpm add edytor@next
|
|
|
77
78
|
</div>
|
|
78
79
|
```
|
|
79
80
|
|
|
80
|
-
`<Edytor>` adds the rich text, image, arrow-move and suggestions plugins after yours, and block handles. To collaborate, name a room and a server running the [`edytor/cloudflare` room](https://edytor
|
|
81
|
+
`<Edytor>` adds the rich text, image, arrow-move and suggestions plugins after yours, and block handles. To collaborate, name a room and a server running the [`edytor/cloudflare` room](https://edytor.dev/docs/server/quick-start):
|
|
81
82
|
|
|
82
83
|
```svelte
|
|
83
84
|
<Edytor
|
|
@@ -90,14 +91,14 @@ pnpm add edytor@next
|
|
|
90
91
|
|
|
91
92
|
## Documentation
|
|
92
93
|
|
|
93
|
-
The [documentation site](https://edytor
|
|
94
|
+
The [documentation site](https://edytor.dev/docs) is the single source for the API and behavior; this README only introduces the package.
|
|
94
95
|
|
|
95
|
-
- [Getting started](https://edytor
|
|
96
|
-
- [Concepts](https://edytor
|
|
97
|
-
- [Editor](https://edytor
|
|
98
|
-
- [Plugins](https://edytor
|
|
99
|
-
- [Collaboration](https://edytor
|
|
100
|
-
- [Reference](https://edytor
|
|
96
|
+
- [Getting started](https://edytor.dev/docs/getting-started): installation, quick start, SvelteKit, entry points and bundle size
|
|
97
|
+
- [Concepts](https://edytor.dev/docs/concepts/document-model): the document model, blocks, void and island roles
|
|
98
|
+
- [Editor](https://edytor.dev/docs/editor/edytor-component): the component, commands, selection, history, clipboard, readonly
|
|
99
|
+
- [Plugins](https://edytor.dev/docs/plugins) and [customization](https://edytor.dev/docs/customization/blocks): bundled plugins, custom blocks and marks, [hotkeys and editing behavior](https://edytor.dev/docs/customization/hotkeys)
|
|
100
|
+
- [Collaboration](https://edytor.dev/docs/collaboration) and [server](https://edytor.dev/docs/server/quick-start): documents, providers, presence, the Durable Object room and its protocol
|
|
101
|
+
- [Reference](https://edytor.dev/docs/reference/document-api): the document API, [troubleshooting](https://edytor.dev/docs/reference/troubleshooting), [migration from 0.0.11](https://edytor.dev/docs/reference/migration), limitations
|
|
101
102
|
|
|
102
103
|
## Contributing
|
|
103
104
|
|
|
@@ -145,10 +145,12 @@ export declare class Block {
|
|
|
145
145
|
}, plan?: import("../crdt/index.js").Prepared | undefined) => void;
|
|
146
146
|
moveBlock: (this: Block, payload: {
|
|
147
147
|
path: number[];
|
|
148
|
+
beside?: import("./block.utils.js").BlockBeside;
|
|
148
149
|
}, plan?: import("../crdt/index.js").Prepared | undefined) => Block | null;
|
|
149
150
|
moveBlocks: (this: Block, payload: {
|
|
150
151
|
blocks: Block[];
|
|
151
152
|
path: number[];
|
|
153
|
+
beside?: import("./block.utils.js").BlockBeside;
|
|
152
154
|
}, plan?: import("../crdt/index.js").Prepared | undefined) => Block[];
|
|
153
155
|
pushContentIntoBlock: (args_0: {
|
|
154
156
|
value: (Text | InlineBlock)[];
|
|
@@ -38,6 +38,11 @@ import type { DataPatch } from '../crdt/data.js';
|
|
|
38
38
|
import type { ResolvedAt } from '../session/suggestions.svelte.js';
|
|
39
39
|
import { type JSONBlock, type JSONInlineBlock, type JSONText } from '../utils/json.js';
|
|
40
40
|
import { InlineBlock } from './inlineBlock.svelte.js';
|
|
41
|
+
/** A move beside `target`, to its `side` (`prepare.placeBeside`, `layout.place-beside`). */
|
|
42
|
+
export type BlockBeside = {
|
|
43
|
+
target: Block;
|
|
44
|
+
side: 'left' | 'right';
|
|
45
|
+
};
|
|
41
46
|
export type BlockOperations = {
|
|
42
47
|
removeInlineBlock: {
|
|
43
48
|
index: number;
|
|
@@ -88,12 +93,15 @@ export type BlockOperations = {
|
|
|
88
93
|
block: JSONInlineBlock;
|
|
89
94
|
text: Text;
|
|
90
95
|
};
|
|
96
|
+
/** `beside`: placed beside a target (`left`/`right` moves); `path` is then the target's. */
|
|
91
97
|
moveBlock: {
|
|
92
98
|
path: number[];
|
|
99
|
+
beside?: BlockBeside;
|
|
93
100
|
};
|
|
94
101
|
moveBlocks: {
|
|
95
102
|
blocks: Block[];
|
|
96
103
|
path: number[];
|
|
104
|
+
beside?: BlockBeside;
|
|
97
105
|
};
|
|
98
106
|
pushContentIntoBlock: {
|
|
99
107
|
value: (Text | InlineBlock)[];
|
|
@@ -128,6 +136,12 @@ export type BlockOperations = {
|
|
|
128
136
|
deleteBlocks: {
|
|
129
137
|
blocks: Block[];
|
|
130
138
|
};
|
|
139
|
+
/** Wrap sibling blocks in a new layout of `kind` and `columns` items, one block per item (`layout.wrap`). */
|
|
140
|
+
wrapBlocks: {
|
|
141
|
+
blocks: Block[];
|
|
142
|
+
kind?: string;
|
|
143
|
+
columns?: number;
|
|
144
|
+
};
|
|
131
145
|
/** A divider at the caret: its steps are the conversion, insertion or split it plans. */
|
|
132
146
|
insertDivider: {};
|
|
133
147
|
};
|
|
@@ -3,6 +3,7 @@ import { Text } from '../text/text.svelte.js';
|
|
|
3
3
|
import { sliceTextValue } from '../block/contentRange.js';
|
|
4
4
|
import { cloneJson } from '../utils/json.js';
|
|
5
5
|
import { rangeCovers, selectedMembers } from '../selection/visibility.js';
|
|
6
|
+
import { liftLayouts } from '../selection/replaceSelection.js';
|
|
6
7
|
const extractContentRange = (block, startText, startOffset, endText, endOffset) => {
|
|
7
8
|
const content = [];
|
|
8
9
|
const startIndex = block.content.indexOf(startText);
|
|
@@ -67,13 +68,15 @@ const extractBlockRange = (edytor) => {
|
|
|
67
68
|
});
|
|
68
69
|
};
|
|
69
70
|
export const createEdytorClipboardFragment = (edytor) => {
|
|
70
|
-
|
|
71
|
+
// A layout the block selection covers whole is copied as the layout (D3).
|
|
72
|
+
const selectedBlocks = selectedMembers(edytor, liftLayouts(edytor.selection.selectedBlocks));
|
|
71
73
|
if (selectedBlocks.length > 0) {
|
|
72
74
|
return {
|
|
73
75
|
version: 1,
|
|
74
76
|
source: 'edytor',
|
|
75
77
|
kind: 'blocks',
|
|
76
|
-
// Exactly the members (`sel.blocks.exact`)
|
|
78
|
+
// Exactly the members (`sel.blocks.exact`), a layout covered whole as itself, which
|
|
79
|
+
// deleting its blocks removes: what a cut copies is what it deletes.
|
|
77
80
|
blocks: nestMembers(selectedBlocks, (block) => block.value),
|
|
78
81
|
whole: true
|
|
79
82
|
};
|
|
@@ -135,6 +135,14 @@ export const flowOfHtml = (kinds, html) => {
|
|
|
135
135
|
}));
|
|
136
136
|
return [{ type: claim.type, data: claim.data, content: [], children }];
|
|
137
137
|
}
|
|
138
|
+
// A kind that shows no text of its own (a list, a layout, a column) takes none: its
|
|
139
|
+
// inline runs and a leading paragraph are lines of its default child, never hidden text.
|
|
140
|
+
if (claim && blocks.get(claim.type)?.rendersContent === false) {
|
|
141
|
+
const children = linesOf(element, kinds.document.defaultChild(claim.type));
|
|
142
|
+
return [
|
|
143
|
+
{ type: claim.type, data: claim.data, content: [], ...(children.length && { children }) }
|
|
144
|
+
];
|
|
145
|
+
}
|
|
138
146
|
const wraps = (element) => [...element.children].some(isBlock);
|
|
139
147
|
// An element that only wraps blocks (a `div`, a `ul`) is not a line of its own.
|
|
140
148
|
if (!claim && wraps(element))
|
|
@@ -44,12 +44,18 @@
|
|
|
44
44
|
/** Tags that take no content: the kind renders the element only. */
|
|
45
45
|
const VOID_TAGS = new Set(['area', 'br', 'col', 'embed', 'hr', 'img', 'input', 'wbr']);
|
|
46
46
|
|
|
47
|
-
/**
|
|
47
|
+
/**
|
|
48
|
+
* The element a kind declares (O45): a tag, or tag and attributes, from the
|
|
49
|
+
* block's data and id (a kind's own view state keyed by block, such as a
|
|
50
|
+
* column's width while a resize drags; reactive state the function reads
|
|
51
|
+
* re-renders the element).
|
|
52
|
+
*/
|
|
48
53
|
const elementOf = (
|
|
49
54
|
declared: BlockDefinition['element'],
|
|
50
|
-
data: Record<string, unknown> | undefined
|
|
55
|
+
data: Record<string, unknown> | undefined,
|
|
56
|
+
id: string
|
|
51
57
|
) => {
|
|
52
|
-
const spec = typeof declared === 'function' ? declared(data ?? {}) : declared;
|
|
58
|
+
const spec = typeof declared === 'function' ? declared(data ?? {}, id) : declared;
|
|
53
59
|
if (spec === undefined) return undefined;
|
|
54
60
|
return typeof spec === 'string' ? { tag: spec, attributes: {} } : { attributes: {}, ...spec };
|
|
55
61
|
};
|
|
@@ -100,9 +106,11 @@
|
|
|
100
106
|
);
|
|
101
107
|
const definition = $derived(cell && edytor.definitionOf(cell.type));
|
|
102
108
|
// The core renders the block element from the definition; the snippet renders inside it (R11).
|
|
103
|
-
const element = $derived(definition && elementOf(definition.element ?? 'div', cell?.data));
|
|
109
|
+
const element = $derived(definition && elementOf(definition.element ?? 'div', cell?.data, id));
|
|
104
110
|
// The element around the block's own text (a heading's `h2`): the core's, so an override keeps it.
|
|
105
|
-
const contentElement = $derived(
|
|
111
|
+
const contentElement = $derived(
|
|
112
|
+
definition && elementOf(definition.contentElement, cell?.data, id)
|
|
113
|
+
);
|
|
106
114
|
/** Registers the block element (O45): one element per block, re-registered when the tag changes. */
|
|
107
115
|
const register = (node: HTMLElement) => block.handle?.attach(node);
|
|
108
116
|
/** The block element's attributes: the kind's, then the core's. */
|
|
@@ -323,18 +323,13 @@
|
|
|
323
323
|
|
|
324
324
|
const nonNativeEditableBlockChromeSelection = (node: HTMLElement) => {
|
|
325
325
|
const { selection } = edytor;
|
|
326
|
-
const pointerdown = (event: PointerEvent) =>
|
|
327
|
-
selection.handleNonNativeEditableBlockChromePointerDown(event);
|
|
328
|
-
node.addEventListener('pointerdown', pointerdown, true);
|
|
329
326
|
// `selectstart` is the only event fired before a drag-selection
|
|
330
327
|
// begins — the guard keeps one from starting on non-editable
|
|
331
|
-
// chrome (markers, void/island chrome, plugin UI).
|
|
328
|
+
// chrome (markers, void/island chrome, plugin UI). A press on that
|
|
329
|
+
// chrome is the press's (`events/onFocus.ts`).
|
|
332
330
|
node.addEventListener('selectstart', selection.onSelectStart);
|
|
333
331
|
return {
|
|
334
|
-
destroy: () =>
|
|
335
|
-
node.removeEventListener('pointerdown', pointerdown, true);
|
|
336
|
-
node.removeEventListener('selectstart', selection.onSelectStart);
|
|
337
|
-
}
|
|
332
|
+
destroy: () => node.removeEventListener('selectstart', selection.onSelectStart)
|
|
338
333
|
};
|
|
339
334
|
};
|
|
340
335
|
</script>
|
package/dist/crdt/document.d.ts
CHANGED
|
@@ -24,7 +24,7 @@ export type DocumentActor = {
|
|
|
24
24
|
* part of this: they are view-side rendering policy.
|
|
25
25
|
*/
|
|
26
26
|
export type DocumentSemanticsConfig = {
|
|
27
|
-
/** Structural role per block type (`{void?, island?, lines?}` — absent flags mean false). */
|
|
27
|
+
/** Structural role per block type (`{void?, island?, lines?, layout?}` — absent flags mean false). */
|
|
28
28
|
roles?: Record<string, BlockRole>;
|
|
29
29
|
/** Whether a kind renders its own content slot (undeclared kinds do). */
|
|
30
30
|
rendersContent?: Record<string, boolean>;
|
|
@@ -343,7 +343,8 @@ export declare class EdytorDocument {
|
|
|
343
343
|
*/
|
|
344
344
|
adoptSemantics: (config: DocumentSemanticsConfig) => void;
|
|
345
345
|
/**
|
|
346
|
-
* DEV: `
|
|
346
|
+
* DEV: `layout` needs a default child (its item kind); `lines` needs an
|
|
347
|
+
* island with a default child (its line kind), and
|
|
347
348
|
* the line kind belongs to it — outside such an island a block of that
|
|
348
349
|
* kind shows as its parent's default child, so a kind other blocks use
|
|
349
350
|
* (the default type, another kind's default child) would be recast.
|
package/dist/crdt/document.js
CHANGED
|
@@ -202,7 +202,8 @@ export const DEFAULT_READINESS_BOUND = 1000;
|
|
|
202
202
|
const normalizeRole = (role) => ({
|
|
203
203
|
void: role?.void === true,
|
|
204
204
|
island: role?.island === true,
|
|
205
|
-
lines: role?.lines === true
|
|
205
|
+
lines: role?.lines === true,
|
|
206
|
+
...(role?.layout === true && { layout: true })
|
|
206
207
|
});
|
|
207
208
|
const anonymousActor = () => ({
|
|
208
209
|
id: `anon-${crypto.randomUUID()}`
|
|
@@ -496,12 +497,14 @@ export class EdytorDocument {
|
|
|
496
497
|
if (DEV)
|
|
497
498
|
this._warnLines();
|
|
498
499
|
// A newly void kind sheds its children at read time (UW-21b); a block
|
|
499
|
-
// promoted out of a newly island kind displays as a default child
|
|
500
|
-
|
|
500
|
+
// promoted out of a newly island kind displays as a default child; a
|
|
501
|
+
// newly layout kind displays only its items (`layout.*`).
|
|
502
|
+
if (Object.values(config.roles ?? {}).some((role) => role?.void || role?.island || role?.layout))
|
|
501
503
|
this.facade.rolesChanged();
|
|
502
504
|
};
|
|
503
505
|
/**
|
|
504
|
-
* DEV: `
|
|
506
|
+
* DEV: `layout` needs a default child (its item kind); `lines` needs an
|
|
507
|
+
* island with a default child (its line kind), and
|
|
505
508
|
* the line kind belongs to it — outside such an island a block of that
|
|
506
509
|
* kind shows as its parent's default child, so a kind other blocks use
|
|
507
510
|
* (the default type, another kind's default child) would be recast.
|
|
@@ -509,6 +512,8 @@ export class EdytorDocument {
|
|
|
509
512
|
_warnLines() {
|
|
510
513
|
const { roles, defaultChild } = this._capability;
|
|
511
514
|
for (const [type, role] of roles) {
|
|
515
|
+
if (role.layout && !defaultChild.has(type))
|
|
516
|
+
console.warn(`[edytor] "${type}" declares \`layout\` without a \`defaultChild\` (its column kind): it is no layout.`);
|
|
512
517
|
if (!role.lines)
|
|
513
518
|
continue;
|
|
514
519
|
const line = defaultChild.get(type);
|
|
@@ -113,6 +113,13 @@ export type BlockRole = {
|
|
|
113
113
|
* `island` and a `defaultChild`; other islands keep their structure.
|
|
114
114
|
*/
|
|
115
115
|
lines?: boolean;
|
|
116
|
+
/**
|
|
117
|
+
* A layout (columns): it displays only its items — its `defaultChild`
|
|
118
|
+
* kind, a container that renders no content — side by side, and only
|
|
119
|
+
* while it shows two or more (`layout.*` in the delete contract). Needs
|
|
120
|
+
* a `defaultChild`.
|
|
121
|
+
*/
|
|
122
|
+
layout?: boolean;
|
|
116
123
|
};
|
|
117
124
|
/** Island-sealing policy for a walk in document order (R5; see `next`). */
|
|
118
125
|
export type OrderPolicy = {
|
|
@@ -436,6 +443,8 @@ export declare const bindEdytorDoc: (Y: EngineApi) => {
|
|
|
436
443
|
keep?: boolean;
|
|
437
444
|
after?: readonly BlockSpec[];
|
|
438
445
|
}) => Prepared;
|
|
446
|
+
placeBeside: (ids: readonly BlockId[], target: BlockId, side: "left" | "right", kind?: string) => Prepared;
|
|
447
|
+
wrapInLayout: (ids: readonly BlockId[], kind?: string, columns?: number) => Prepared;
|
|
439
448
|
splitBlock: (id: BlockId, offset: number, newId: BlockId, tail?: SplitTail) => Prepared;
|
|
440
449
|
mergeBlocks: (fromId: BlockId, intoId: BlockId) => Prepared;
|
|
441
450
|
mergeBackward: (id: BlockId) => Prepared;
|
|
@@ -561,6 +570,8 @@ export declare const bindEdytorDoc: (Y: EngineApi) => {
|
|
|
561
570
|
keep?: boolean;
|
|
562
571
|
after?: readonly BlockSpec[];
|
|
563
572
|
} | undefined) => OpResult;
|
|
573
|
+
placeBeside: (ids: readonly string[], target: string, side: "left" | "right", kind?: string | undefined) => OpResult;
|
|
574
|
+
wrapInLayout: (ids: readonly string[], kind?: string | undefined, columns?: number | undefined) => OpResult;
|
|
564
575
|
splitBlock: (id: string, offset: number, newId: string, tail?: SplitTail | undefined) => OpResult;
|
|
565
576
|
mergeBlocks: (fromId: string, intoId: string) => OpResult;
|
|
566
577
|
mergeBackward: (id: string) => OpResult;
|
|
@@ -654,6 +665,12 @@ export declare const bindEdytorDoc: (Y: EngineApi) => {
|
|
|
654
665
|
isVoid: (id: string) => boolean;
|
|
655
666
|
isIsland: (id: string) => boolean;
|
|
656
667
|
isLines: (id: string) => boolean;
|
|
668
|
+
/** `id` is a layout: its kind's role says `layout` (`layout.*`). */
|
|
669
|
+
isLayout: (id: string) => boolean;
|
|
670
|
+
/** `id` is a layout item: of its layout's item kind, directly in it (a column). */
|
|
671
|
+
isLayoutItem: (id: string) => boolean;
|
|
672
|
+
/** The block a beside placement at `id` stands beside (`placeBeside`'s resolution). */
|
|
673
|
+
besideAt: (id: BlockId, kind?: string) => string;
|
|
657
674
|
islandOf: (id: string) => string | null;
|
|
658
675
|
insideIsland: (id: string) => boolean;
|
|
659
676
|
canPlace: (ids: readonly BlockId[], parent?: BlockId | null) => boolean;
|