@riducms/plugin-richtext 0.2.2 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +7 -1
- package/package.json +7 -5
- package/src/block/rich-text-block-card.scss +139 -0
- package/src/block/rich-text-block-card.svelte +42 -21
- package/src/block/rich-text-block-clipboard.ts +1 -1
- package/src/block/rich-text-block-node.ts +1 -1
- package/src/block/rich-text-blocks-plugin.svelte +5 -2
- package/src/document-validation.ts +7 -5
- package/src/field/reference-node-placement.ts +49 -0
- package/src/field/rich-text-blocks.ts +1 -5
- package/src/field/rich-text-config.ts +1 -4
- package/src/field/rich-text-document.ts +1 -4
- package/src/field/rich-text-editor.svelte +105 -57
- package/src/field/rich-text-footer.svelte +7 -12
- package/src/field/rich-text-markdown.ts +62 -0
- package/src/link/link-url.ts +30 -0
- package/src/link/rich-text-link-plugin.svelte +528 -0
- package/src/link/rich-text-link.scss +144 -0
- package/src/link/safe-link-transform.ts +20 -0
- package/src/link-url-policy.ts +14 -0
- package/src/menu/rich-text-block-toolbar.svelte +403 -70
- package/src/menu/rich-text-commands.ts +10 -1
- package/src/menu/rich-text-menu.scss +92 -0
- package/src/menu/rich-text-menu.svelte +62 -69
- package/src/menu/rich-text-option-filter.ts +30 -0
- package/src/menu/rich-text-options.ts +81 -84
- package/src/menu/rich-text-slash-menu.svelte +15 -11
- package/src/messages.ts +72 -27
- package/src/relationship/rich-text-relationship-node.scss +98 -0
- package/src/relationship/rich-text-relationship-node.svelte +38 -32
- package/src/relationship/rich-text-relationship-plugin.svelte +24 -40
- package/src/render/rich-text.svelte +10 -5
- package/src/render.ts +3 -8
- package/src/styles/integration.scss +384 -0
- package/src/toolbar/PAYLOAD-LICENSE.md +22 -0
- package/src/toolbar/rich-text-floating-toolbar.svelte +331 -169
- package/src/toolbar/rich-text-toolbar.scss +80 -0
- package/src/toolbar/toolbar-icon.svelte +200 -30
- package/src/upload/rich-text-upload-node.scss +308 -0
- package/src/upload/rich-text-upload-node.svelte +96 -125
- package/src/upload/rich-text-upload-node.ts +1 -0
- package/src/upload/rich-text-upload-plugin.svelte +10 -33
- package/src/styles/integration.css +0 -83
package/README.md
CHANGED
|
@@ -15,7 +15,7 @@ Add `richtext.New()` to `Config.Plugins` and author fields with `richtext.Field`
|
|
|
15
15
|
registry imports `richTextAdminPlugin`; do not register it again. Generation rejects incompatible Go
|
|
16
16
|
and npm pairing metadata before the editor loads.
|
|
17
17
|
|
|
18
|
-
Run `
|
|
18
|
+
Run `bun run dev`, verify the editor, then create and review the adapter migration before deployment:
|
|
19
19
|
|
|
20
20
|
```sh
|
|
21
21
|
ridu migrate create --name add-rich-text
|
|
@@ -33,3 +33,9 @@ Portable types are available from `@riducms/plugin-richtext/document`, HTML rend
|
|
|
33
33
|
`@riducms/plugin-richtext/render`, and optional typed Svelte rendering from
|
|
34
34
|
`@riducms/plugin-richtext/svelte`. These entries do not load the admin editor. See the rich-text
|
|
35
35
|
guide for strict payloads, localization, typed renderers and experimental-content migration.
|
|
36
|
+
|
|
37
|
+
The supported authoring UI uses semantic SCSS, compact grouped insertion menus, six heading levels,
|
|
38
|
+
Markdown shortcuts, interactive checklists, custom-URL link drawers and intrinsic-ratio media cards.
|
|
39
|
+
See [customizing admin CSS](https://riducms.com/docs/admin/customizing-css/) for styling and the
|
|
40
|
+
[rich-text guide](https://riducms.com/docs/rich-text/) for supported features and limits.
|
|
41
|
+
No utility stylesheet is needed for this plugin's UI.
|
package/package.json
CHANGED
|
@@ -9,12 +9,13 @@
|
|
|
9
9
|
"@lexical/history": "0.49.0",
|
|
10
10
|
"@lexical/link": "0.49.0",
|
|
11
11
|
"@lexical/list": "0.49.0",
|
|
12
|
+
"@lexical/markdown": "0.49.0",
|
|
12
13
|
"@lexical/rich-text": "0.49.0",
|
|
13
14
|
"@lexical/selection": "0.49.0",
|
|
14
15
|
"@lexical/utils": "0.49.0",
|
|
15
|
-
"@riducms/plugin": "0.
|
|
16
|
-
"@riducms/protocol": "0.
|
|
17
|
-
"@riducms/ui": "0.
|
|
16
|
+
"@riducms/plugin": "0.3.0",
|
|
17
|
+
"@riducms/protocol": "0.3.0",
|
|
18
|
+
"@riducms/ui": "0.3.0",
|
|
18
19
|
"@types/prismjs": "^1.26.5",
|
|
19
20
|
"lexical": "0.49.0",
|
|
20
21
|
"prismjs": "^1.30.0",
|
|
@@ -22,7 +23,7 @@
|
|
|
22
23
|
},
|
|
23
24
|
"description": "Svelte and Lexical admin integration for Ridu's official rich-text plugin.",
|
|
24
25
|
"devDependencies": {
|
|
25
|
-
"@riducms/build": "0.
|
|
26
|
+
"@riducms/build": "0.3.0"
|
|
26
27
|
},
|
|
27
28
|
"exports": {
|
|
28
29
|
".": "./src/index.ts",
|
|
@@ -38,6 +39,7 @@
|
|
|
38
39
|
"imports": {
|
|
39
40
|
"#richtext/document": "./src/document.ts",
|
|
40
41
|
"#richtext/document-validation": "./src/document-validation.ts",
|
|
42
|
+
"#richtext/link-url-policy": "./src/link-url-policy.ts",
|
|
41
43
|
"#richtext/render": "./src/render.ts",
|
|
42
44
|
"#richtext/rich-text.svelte": "./src/render/rich-text.svelte",
|
|
43
45
|
"#richtext/svelte": "./src/render/index.ts"
|
|
@@ -59,5 +61,5 @@
|
|
|
59
61
|
"test": "bun test"
|
|
60
62
|
},
|
|
61
63
|
"type": "module",
|
|
62
|
-
"version": "0.
|
|
64
|
+
"version": "0.3.0"
|
|
63
65
|
}
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
@use "@riducms/ui/styles.scss" as ui;
|
|
2
|
+
|
|
3
|
+
@layer ridu.components {
|
|
4
|
+
.ridu-richtext-block-card {
|
|
5
|
+
position: relative;
|
|
6
|
+
z-index: 1;
|
|
7
|
+
margin-block: 10px;
|
|
8
|
+
padding: 15px;
|
|
9
|
+
border: 1px solid var(--control-border);
|
|
10
|
+
border-radius: var(--control-radius);
|
|
11
|
+
background: var(--control-surface);
|
|
12
|
+
box-shadow: var(--control-shadow);
|
|
13
|
+
color: var(--foreground);
|
|
14
|
+
font-family: var(--font-sans);
|
|
15
|
+
line-height: var(--line-height-body);
|
|
16
|
+
transition:
|
|
17
|
+
border-color 100ms,
|
|
18
|
+
box-shadow 100ms;
|
|
19
|
+
|
|
20
|
+
&:hover {
|
|
21
|
+
border-color: var(--control-border-hover);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
&.is-selected {
|
|
25
|
+
border-color: var(--foreground-muted);
|
|
26
|
+
box-shadow:
|
|
27
|
+
var(--control-shadow),
|
|
28
|
+
0 0 0 1px var(--foreground-muted);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
&.has-issues {
|
|
32
|
+
border-color: var(--control-invalid-border);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
&.has-name-field {
|
|
36
|
+
white-space: normal;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
.ridu-richtext-block-card__name {
|
|
41
|
+
margin-block-end: 10px;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
.ridu-richtext-block-card__header {
|
|
45
|
+
display: flex;
|
|
46
|
+
align-items: center;
|
|
47
|
+
justify-content: space-between;
|
|
48
|
+
gap: 12px;
|
|
49
|
+
min-width: 0;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
.ridu-richtext-block-card__select {
|
|
53
|
+
min-width: 96px;
|
|
54
|
+
flex: 1;
|
|
55
|
+
border: 0;
|
|
56
|
+
border-radius: var(--control-radius);
|
|
57
|
+
padding: 2px;
|
|
58
|
+
background: transparent;
|
|
59
|
+
color: inherit;
|
|
60
|
+
font: inherit;
|
|
61
|
+
text-align: start;
|
|
62
|
+
cursor: pointer;
|
|
63
|
+
|
|
64
|
+
&:focus-visible {
|
|
65
|
+
@include ui.focus-outline;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
.ridu-richtext-block-card__label,
|
|
70
|
+
.ridu-richtext-block-card__summary {
|
|
71
|
+
display: block;
|
|
72
|
+
overflow: hidden;
|
|
73
|
+
text-overflow: ellipsis;
|
|
74
|
+
white-space: nowrap;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
.ridu-richtext-block-card__label {
|
|
78
|
+
color: var(--foreground-muted);
|
|
79
|
+
font-size: 12px;
|
|
80
|
+
font-weight: 500;
|
|
81
|
+
line-height: 16px;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
.ridu-richtext-block-card__summary {
|
|
85
|
+
margin-block-start: 4px;
|
|
86
|
+
color: var(--foreground);
|
|
87
|
+
font-size: var(--font-size-body);
|
|
88
|
+
line-height: var(--line-height-body);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
.ridu-richtext-block-card__actions {
|
|
92
|
+
display: flex;
|
|
93
|
+
align-items: center;
|
|
94
|
+
flex-shrink: 0;
|
|
95
|
+
gap: 2px;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
.ridu-richtext-block-card__action {
|
|
99
|
+
padding-inline: 8px;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
.ridu-richtext-block-card__status {
|
|
103
|
+
margin: 10px 0 0;
|
|
104
|
+
color: var(--destructive);
|
|
105
|
+
font-size: var(--font-size-body);
|
|
106
|
+
line-height: var(--line-height-body);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
.ridu-richtext-block-message {
|
|
110
|
+
margin: 8px 0;
|
|
111
|
+
color: var(--destructive);
|
|
112
|
+
font-size: var(--font-size-body);
|
|
113
|
+
line-height: var(--line-height-body);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
.ridu-richtext-block-card__recovery {
|
|
117
|
+
display: flex;
|
|
118
|
+
align-items: flex-start;
|
|
119
|
+
justify-content: space-between;
|
|
120
|
+
gap: 12px;
|
|
121
|
+
margin-block-start: 10px;
|
|
122
|
+
|
|
123
|
+
.ridu-richtext-block-card__status {
|
|
124
|
+
margin: 0;
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
@media (max-width: ui.$small) {
|
|
129
|
+
.ridu-richtext-block-card__header,
|
|
130
|
+
.ridu-richtext-block-card__recovery {
|
|
131
|
+
align-items: stretch;
|
|
132
|
+
flex-direction: column;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
.ridu-richtext-block-card__actions {
|
|
136
|
+
flex-wrap: wrap;
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
}
|
|
@@ -20,6 +20,7 @@
|
|
|
20
20
|
MOVE_BLOCK_COMMAND,
|
|
21
21
|
UPDATE_BLOCK_NAME_COMMAND,
|
|
22
22
|
} from "@plugin-richtext/menu/rich-text-commands";
|
|
23
|
+
import "@plugin-richtext/block/rich-text-block-card.scss";
|
|
23
24
|
|
|
24
25
|
let {
|
|
25
26
|
nodeKey,
|
|
@@ -133,10 +134,10 @@
|
|
|
133
134
|
|
|
134
135
|
<article
|
|
135
136
|
class={[
|
|
136
|
-
"ridu-richtext-embedded-card
|
|
137
|
-
nameField !== undefined && "
|
|
138
|
-
selected() && "
|
|
139
|
-
issues.length > 0 && "
|
|
137
|
+
"ridu-richtext-embedded-card ridu-richtext-block-card",
|
|
138
|
+
nameField !== undefined && "has-name-field",
|
|
139
|
+
selected() && "is-selected",
|
|
140
|
+
issues.length > 0 && "has-issues",
|
|
140
141
|
]}
|
|
141
142
|
data-block-key={identity}
|
|
142
143
|
data-block-type={blockType}
|
|
@@ -146,7 +147,7 @@
|
|
|
146
147
|
>
|
|
147
148
|
{#if nameField !== undefined && authoring?.schemaHeader !== undefined}
|
|
148
149
|
<div
|
|
149
|
-
class="
|
|
150
|
+
class="ridu-richtext-block-card__name"
|
|
150
151
|
onfocuscapture={nameHistory.reset.bind(nameHistory)}
|
|
151
152
|
onblurcapture={nameHistory.reset.bind(nameHistory)}
|
|
152
153
|
onkeydowncapture={nameKeydown}
|
|
@@ -159,11 +160,11 @@
|
|
|
159
160
|
})}
|
|
160
161
|
</div>
|
|
161
162
|
{/if}
|
|
162
|
-
<div class="
|
|
163
|
+
<div class="ridu-richtext-block-card__header">
|
|
163
164
|
<button
|
|
164
165
|
type="button"
|
|
165
166
|
data-block-select
|
|
166
|
-
class="
|
|
167
|
+
class="ridu-richtext-block-card__select"
|
|
167
168
|
aria-label={i18n.t("plugin.richtext:block.select", {
|
|
168
169
|
label: type?.labels.singular ?? blockType,
|
|
169
170
|
})}
|
|
@@ -171,36 +172,56 @@
|
|
|
171
172
|
onclickcapture={select}
|
|
172
173
|
onkeydowncapture={keydown}
|
|
173
174
|
>
|
|
174
|
-
<span class="
|
|
175
|
+
<span class="ridu-richtext-block-card__label">
|
|
175
176
|
{type?.labels.singular ?? blockType}
|
|
176
177
|
</span>
|
|
177
|
-
{#if nameField === undefined}
|
|
178
|
+
{#if nameField === undefined}
|
|
179
|
+
<span class="ridu-richtext-block-card__summary">
|
|
178
180
|
{summary}
|
|
179
|
-
</span>
|
|
181
|
+
</span>
|
|
182
|
+
{/if}
|
|
180
183
|
</button>
|
|
181
184
|
{#if editable()}
|
|
182
|
-
<div
|
|
183
|
-
|
|
185
|
+
<div
|
|
186
|
+
class="ridu-richtext-block-card__actions"
|
|
187
|
+
role="group"
|
|
188
|
+
aria-label={i18n.t("plugin.richtext:editor.blockActions")}
|
|
189
|
+
>
|
|
190
|
+
<Button
|
|
191
|
+
class="ridu-richtext-block-card__action"
|
|
192
|
+
size="sm"
|
|
193
|
+
variant="ghost"
|
|
194
|
+
onclick={edit}
|
|
195
|
+
disabled={recovery}
|
|
196
|
+
>
|
|
184
197
|
{i18n.t("plugin.richtext:block.edit")}
|
|
185
198
|
</Button>
|
|
186
|
-
<Button
|
|
199
|
+
<Button
|
|
200
|
+
class="ridu-richtext-block-card__action"
|
|
201
|
+
size="sm"
|
|
202
|
+
variant="ghost"
|
|
203
|
+
onclick={duplicate}
|
|
204
|
+
disabled={recovery}
|
|
205
|
+
>
|
|
187
206
|
{i18n.t("plugin.richtext:block.duplicate")}
|
|
188
207
|
</Button>
|
|
189
|
-
<Button size="sm" variant="ghost" onclick={remove}>
|
|
208
|
+
<Button class="ridu-richtext-block-card__action" size="sm" variant="ghost" onclick={remove}>
|
|
190
209
|
{i18n.t("plugin.richtext:block.remove")}
|
|
191
210
|
</Button>
|
|
192
211
|
</div>
|
|
193
212
|
{/if}
|
|
194
213
|
</div>
|
|
195
214
|
{#if recovery}
|
|
196
|
-
<
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
215
|
+
<div class="ridu-richtext-block-card__recovery">
|
|
216
|
+
<p role="alert" class="ridu-richtext-block-card__status">
|
|
217
|
+
{i18n.t("plugin.richtext:block.recovery")}
|
|
218
|
+
</p>
|
|
219
|
+
<Button size="sm" variant="outline" onclick={exportBlock}>
|
|
220
|
+
{i18n.t("plugin.richtext:block.export")}
|
|
221
|
+
</Button>
|
|
222
|
+
</div>
|
|
202
223
|
{:else if issues.length > 0}
|
|
203
|
-
<p class="
|
|
224
|
+
<p class="ridu-richtext-block-card__status">
|
|
204
225
|
{i18n.t("plugin.richtext:block.issues", { count: issues.length })}
|
|
205
226
|
</p>
|
|
206
227
|
{/if}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
+
import { isRecord } from "@riducms/protocol";
|
|
1
2
|
import type { FieldAuthoringHost } from "@riducms/plugin";
|
|
2
3
|
import type { SchemaBlockType } from "@riducms/protocol";
|
|
3
4
|
import type { SerializedLexicalNode } from "lexical";
|
|
4
|
-
import { isRecord } from "@plugin-richtext/field/rich-text-blocks";
|
|
5
5
|
import { documentRecoveryIssue } from "@plugin-richtext/document-validation";
|
|
6
6
|
|
|
7
7
|
/** Validates first, then copies only declared payloads through the generic schema host. */
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { isRecord } from "@riducms/protocol";
|
|
1
2
|
import type { Decorator } from "@hvniel/lexical-svelte";
|
|
2
3
|
import {
|
|
3
4
|
$applyNodeReplacement,
|
|
@@ -9,7 +10,6 @@ import {
|
|
|
9
10
|
type SerializedLexicalNode,
|
|
10
11
|
} from "lexical";
|
|
11
12
|
import RichTextBlockCard from "@plugin-richtext/block/rich-text-block-card.svelte";
|
|
12
|
-
import { isRecord } from "@plugin-richtext/field/rich-text-blocks";
|
|
13
13
|
|
|
14
14
|
/** Retains the received envelope verbatim so historical content can be exported safely. */
|
|
15
15
|
export type SerializedBlockNode = SerializedLexicalNode & Record<string, unknown>;
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
|
+
import { isRecord } from "@riducms/protocol";
|
|
2
3
|
import { getAdminI18n, type EmbeddedSchemaDraft, type FieldAuthoringHost } from "@riducms/plugin";
|
|
3
4
|
import type { SchemaField } from "@riducms/protocol";
|
|
4
5
|
import { useLexicalComposerContext } from "@hvniel/lexical-svelte";
|
|
@@ -9,7 +10,6 @@
|
|
|
9
10
|
$insertGeneratedNodes,
|
|
10
11
|
} from "@lexical/clipboard";
|
|
11
12
|
import { copyBlockClipboardNodes } from "@plugin-richtext/block/rich-text-block-clipboard";
|
|
12
|
-
import { isRecord } from "@plugin-richtext/field/rich-text-blocks";
|
|
13
13
|
import { $insertNodeToNearestRoot, mergeRegister } from "@lexical/utils";
|
|
14
14
|
import {
|
|
15
15
|
$addUpdateTag,
|
|
@@ -46,6 +46,7 @@
|
|
|
46
46
|
MOVE_BLOCK_COMMAND,
|
|
47
47
|
UPDATE_BLOCK_NAME_COMMAND,
|
|
48
48
|
} from "@plugin-richtext/menu/rich-text-commands";
|
|
49
|
+
import "@plugin-richtext/block/rich-text-block-card.scss";
|
|
49
50
|
|
|
50
51
|
let { authoring, field }: { authoring: FieldAuthoringHost | undefined; field: SchemaField } =
|
|
51
52
|
$props();
|
|
@@ -319,7 +320,9 @@
|
|
|
319
320
|
});
|
|
320
321
|
</script>
|
|
321
322
|
|
|
322
|
-
{#if message !== ""}
|
|
323
|
+
{#if message !== ""}
|
|
324
|
+
<p role="alert" class="ridu-richtext-block-message">{message}</p>
|
|
325
|
+
{/if}
|
|
323
326
|
{#if session !== undefined && authoring?.schemaDraftEditor !== undefined}
|
|
324
327
|
{@render authoring.schemaDraftEditor({
|
|
325
328
|
draft: session.draft,
|
|
@@ -1,4 +1,6 @@
|
|
|
1
|
+
import { isRecord } from "@riducms/protocol";
|
|
1
2
|
import type { RichTextDocument } from "#richtext/document";
|
|
3
|
+
import { renderableRichTextURL } from "#richtext/link-url-policy";
|
|
2
4
|
const base = ["type", "version"];
|
|
3
5
|
const element = [...base, "children", "direction", "format", "indent"];
|
|
4
6
|
const container = [...element, "textFormat", "textStyle"];
|
|
@@ -21,10 +23,6 @@ const properties: Readonly<Record<string, readonly string[]>> = {
|
|
|
21
23
|
const integers = new Set(["detail", "textFormat", "indent", "start", "value"]);
|
|
22
24
|
const nullableStrings = new Set(["target", "rel", "title", "direction"]);
|
|
23
25
|
|
|
24
|
-
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
25
|
-
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
26
|
-
}
|
|
27
|
-
|
|
28
26
|
/** Return the first unsupported envelope path without interpreting schema-owned payloads.
|
|
29
27
|
* Both editor admission and portable rendering use this lossless JSON boundary.
|
|
30
28
|
*/
|
|
@@ -76,7 +74,11 @@ export function documentRecoveryIssue(
|
|
|
76
74
|
!["", "left", "start", "center", "right", "end", "justify"].includes(String(node.format))
|
|
77
75
|
)
|
|
78
76
|
return `${path}.format`;
|
|
79
|
-
if (
|
|
77
|
+
if (
|
|
78
|
+
type === "link" &&
|
|
79
|
+
(typeof node.url !== "string" || renderableRichTextURL(node.url) === undefined)
|
|
80
|
+
)
|
|
81
|
+
return `${path}.url`;
|
|
80
82
|
if (type === "heading" && !/^h[1-6]$/.test(String(node.tag))) return `${path}.tag`;
|
|
81
83
|
if (type === "list" && !["bullet", "number", "check"].includes(String(node.listType)))
|
|
82
84
|
return `${path}.listType`;
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { $insertNodeToNearestRoot } from "@lexical/utils";
|
|
2
|
+
import {
|
|
3
|
+
$createParagraphNode,
|
|
4
|
+
$getPreviousSelection,
|
|
5
|
+
$getRoot,
|
|
6
|
+
$getSelection,
|
|
7
|
+
$isElementNode,
|
|
8
|
+
$isParagraphNode,
|
|
9
|
+
$isRangeSelection,
|
|
10
|
+
type LexicalNode,
|
|
11
|
+
} from "lexical";
|
|
12
|
+
|
|
13
|
+
/** Inserts a reference block and a usable trailing selection within a Lexical update scope. */
|
|
14
|
+
export function insertReferenceNode(node: LexicalNode) {
|
|
15
|
+
const selection = $getSelection() ?? $getPreviousSelection();
|
|
16
|
+
if (!$isRangeSelection(selection)) $getRoot().selectEnd();
|
|
17
|
+
const focusNode = $isRangeSelection(selection) ? selection.focus.getNode() : undefined;
|
|
18
|
+
|
|
19
|
+
$insertNodeToNearestRoot(node);
|
|
20
|
+
const next = node.getNextSibling();
|
|
21
|
+
if ($isParagraphNode(focusNode) && focusNode.getChildrenSize() === 0 && focusNode !== next) {
|
|
22
|
+
focusNode.remove();
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const trailing = node.getNextSibling();
|
|
26
|
+
if ($isParagraphNode(trailing) && trailing.getChildrenSize() === 0) {
|
|
27
|
+
trailing.select();
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const paragraph = $createParagraphNode();
|
|
32
|
+
node.insertAfter(paragraph);
|
|
33
|
+
paragraph.select();
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/** Removes a reference block and selects a valid neighbour within a Lexical update scope. */
|
|
37
|
+
export function removeReferenceNode(node: LexicalNode) {
|
|
38
|
+
const next = node.getNextSibling();
|
|
39
|
+
const previous = node.getPreviousSibling();
|
|
40
|
+
node.remove();
|
|
41
|
+
|
|
42
|
+
if ($isElementNode(next)) next.selectStart();
|
|
43
|
+
else if ($isElementNode(previous)) previous.selectEnd();
|
|
44
|
+
else {
|
|
45
|
+
const paragraph = $createParagraphNode();
|
|
46
|
+
$getRoot().append(paragraph);
|
|
47
|
+
paragraph.select();
|
|
48
|
+
}
|
|
49
|
+
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { resolveBlockTypes } from "@riducms/protocol";
|
|
1
|
+
import { isRecord, resolveBlockTypes } from "@riducms/protocol";
|
|
2
2
|
import type { SchemaBlockType, SchemaField } from "@riducms/protocol";
|
|
3
3
|
|
|
4
4
|
/** The allowlist comes from resolved executable schemas, never plugin settings. */
|
|
@@ -12,10 +12,6 @@ export function richTextBlockTypes(field: SchemaField): readonly SchemaBlockType
|
|
|
12
12
|
);
|
|
13
13
|
}
|
|
14
14
|
|
|
15
|
-
export function isRecord(value: unknown): value is Record<string, unknown> {
|
|
16
|
-
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
17
|
-
}
|
|
18
|
-
|
|
19
15
|
export function blockSummary(fields: Record<string, unknown>, type?: SchemaBlockType): string {
|
|
20
16
|
// Named cards render their guarded header instead. Never mirror an editorial name
|
|
21
17
|
// into plugin-owned text, because the configured field may be unreadable.
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { isRecord } from "@riducms/protocol";
|
|
1
2
|
export type RichTextFeature =
|
|
2
3
|
"links" | "lists" | "code" | "horizontal-rule" | "uploads" | "relationships" | "blocks";
|
|
3
4
|
|
|
@@ -44,7 +45,3 @@ export function hasRichTextFeature(config: RichTextConfig, feature: RichTextFeat
|
|
|
44
45
|
function isRichTextFeature(value: unknown): value is RichTextFeature {
|
|
45
46
|
return typeof value === "string" && features.has(value as RichTextFeature);
|
|
46
47
|
}
|
|
47
|
-
|
|
48
|
-
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
49
|
-
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
50
|
-
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { isRecord } from "@riducms/protocol";
|
|
1
2
|
import { documentRecoveryIssue } from "@plugin-richtext/document-validation";
|
|
2
3
|
import type { RichTextConfig } from "@plugin-richtext/field/rich-text-config";
|
|
3
4
|
|
|
@@ -38,7 +39,3 @@ function withElementDefaults(node: Record<string, unknown>): Record<string, unkn
|
|
|
38
39
|
indent: node.indent ?? 0,
|
|
39
40
|
};
|
|
40
41
|
}
|
|
41
|
-
|
|
42
|
-
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
43
|
-
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
44
|
-
}
|