@multiplatform.one/rich-text 6.3.0 → 6.4.1
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/dist/cjs/RichEditor.cjs +5 -0
- package/dist/cjs/RichEditor.native.js +22 -0
- package/dist/cjs/RichEditor.native.js.map +1 -1
- package/dist/cjs/RichTextEditor/index.cjs +2 -0
- package/dist/cjs/RichTextEditor/index.native.js +2 -0
- package/dist/cjs/RichTextEditor/index.native.js.map +1 -1
- package/dist/cjs/editor/SlashCommands.cjs +8 -1
- package/dist/cjs/editor/SlashCommands.native.js +14 -1
- package/dist/cjs/editor/SlashCommands.native.js.map +1 -1
- package/dist/cjs/editor/TentapEditor.native.js +15 -0
- package/dist/cjs/editor/TentapEditor.native.js.map +1 -1
- package/dist/cjs/editor/TiptapEditor.cjs +26 -230
- package/dist/cjs/editor/TiptapEditor.native.js +34 -280
- package/dist/cjs/editor/TiptapEditor.native.js.map +1 -1
- package/dist/cjs/editor/capabilities.cjs +37 -0
- package/dist/cjs/editor/capabilities.native.js +41 -0
- package/dist/cjs/editor/capabilities.native.js.map +1 -0
- package/dist/cjs/editor/extensions.cjs +275 -0
- package/dist/cjs/editor/extensions.native.js +328 -0
- package/dist/cjs/editor/extensions.native.js.map +1 -0
- package/dist/cjs/index.cjs +5 -2
- package/dist/cjs/index.native.js +4 -1
- package/dist/cjs/index.native.js.map +1 -1
- package/dist/cjs/toolbar/BubbleToolbar.cjs +9 -6
- package/dist/cjs/toolbar/BubbleToolbar.native.js +12 -7
- package/dist/cjs/toolbar/BubbleToolbar.native.js.map +1 -1
- package/dist/cjs/toolbar/FixedToolbar.cjs +6 -3
- package/dist/cjs/toolbar/MoreDropdown.cjs +17 -9
- package/dist/cjs/toolbar/MoreDropdown.native.js +21 -9
- package/dist/cjs/toolbar/MoreDropdown.native.js.map +1 -1
- package/dist/cjs/toolbar/TableBubble.cjs +15 -2
- package/dist/cjs/toolbar/TableBubble.native.js +17 -2
- package/dist/cjs/toolbar/TableBubble.native.js.map +1 -1
- package/dist/cjs/types.native.js.map +1 -1
- package/dist/esm/RichEditor.mjs +5 -0
- package/dist/esm/RichEditor.mjs.map +1 -1
- package/dist/esm/RichEditor.native.js +22 -0
- package/dist/esm/RichEditor.native.js.map +1 -1
- package/dist/esm/RichTextEditor/index.mjs +2 -0
- package/dist/esm/RichTextEditor/index.mjs.map +1 -1
- package/dist/esm/RichTextEditor/index.native.js +2 -0
- package/dist/esm/RichTextEditor/index.native.js.map +1 -1
- package/dist/esm/editor/SlashCommands.mjs +8 -1
- package/dist/esm/editor/SlashCommands.mjs.map +1 -1
- package/dist/esm/editor/SlashCommands.native.js +14 -1
- package/dist/esm/editor/SlashCommands.native.js.map +1 -1
- package/dist/esm/editor/TentapEditor.native.js +15 -0
- package/dist/esm/editor/TentapEditor.native.js.map +1 -1
- package/dist/esm/editor/TiptapEditor.mjs +28 -221
- package/dist/esm/editor/TiptapEditor.mjs.map +1 -1
- package/dist/esm/editor/TiptapEditor.native.js +36 -271
- package/dist/esm/editor/TiptapEditor.native.js.map +1 -1
- package/dist/esm/editor/capabilities.mjs +10 -0
- package/dist/esm/editor/capabilities.mjs.map +1 -0
- package/dist/esm/editor/capabilities.native.js +11 -0
- package/dist/esm/editor/capabilities.native.js.map +1 -0
- package/dist/esm/editor/extensions.mjs +236 -0
- package/dist/esm/editor/extensions.mjs.map +1 -0
- package/dist/esm/editor/extensions.native.js +286 -0
- package/dist/esm/editor/extensions.native.js.map +1 -0
- package/dist/esm/index.mjs +2 -1
- package/dist/esm/index.mjs.map +1 -1
- package/dist/esm/index.native.js +2 -1
- package/dist/esm/index.native.js.map +1 -1
- package/dist/esm/toolbar/BubbleToolbar.mjs +9 -6
- package/dist/esm/toolbar/BubbleToolbar.mjs.map +1 -1
- package/dist/esm/toolbar/BubbleToolbar.native.js +12 -7
- package/dist/esm/toolbar/BubbleToolbar.native.js.map +1 -1
- package/dist/esm/toolbar/FixedToolbar.mjs +6 -3
- package/dist/esm/toolbar/FixedToolbar.mjs.map +1 -1
- package/dist/esm/toolbar/MoreDropdown.mjs +17 -9
- package/dist/esm/toolbar/MoreDropdown.mjs.map +1 -1
- package/dist/esm/toolbar/MoreDropdown.native.js +21 -9
- package/dist/esm/toolbar/MoreDropdown.native.js.map +1 -1
- package/dist/esm/toolbar/TableBubble.mjs +15 -2
- package/dist/esm/toolbar/TableBubble.mjs.map +1 -1
- package/dist/esm/toolbar/TableBubble.native.js +17 -2
- package/dist/esm/toolbar/TableBubble.native.js.map +1 -1
- package/dist/esm/types.mjs.map +1 -1
- package/dist/esm/types.native.js.map +1 -1
- package/dist/jsx/RichEditor.mjs +5 -0
- package/dist/jsx/RichEditor.mjs.map +1 -1
- package/dist/jsx/RichEditor.native.js +22 -0
- package/dist/jsx/RichEditor.native.js.map +1 -1
- package/dist/jsx/RichTextEditor/index.mjs +2 -0
- package/dist/jsx/RichTextEditor/index.mjs.map +1 -1
- package/dist/jsx/RichTextEditor/index.native.js +2 -0
- package/dist/jsx/RichTextEditor/index.native.js.map +1 -1
- package/dist/jsx/editor/SlashCommands.mjs +8 -1
- package/dist/jsx/editor/SlashCommands.mjs.map +1 -1
- package/dist/jsx/editor/SlashCommands.native.js +14 -1
- package/dist/jsx/editor/SlashCommands.native.js.map +1 -1
- package/dist/jsx/editor/TentapEditor.native.js +15 -0
- package/dist/jsx/editor/TentapEditor.native.js.map +1 -1
- package/dist/jsx/editor/TiptapEditor.mjs +28 -221
- package/dist/jsx/editor/TiptapEditor.mjs.map +1 -1
- package/dist/jsx/editor/TiptapEditor.native.js +34 -280
- package/dist/jsx/editor/TiptapEditor.native.js.map +1 -1
- package/dist/jsx/editor/capabilities.mjs +10 -0
- package/dist/jsx/editor/capabilities.mjs.map +1 -0
- package/dist/jsx/editor/capabilities.native.js +41 -0
- package/dist/jsx/editor/capabilities.native.js.map +1 -0
- package/dist/jsx/editor/extensions.mjs +236 -0
- package/dist/jsx/editor/extensions.mjs.map +1 -0
- package/dist/jsx/editor/extensions.native.js +328 -0
- package/dist/jsx/editor/extensions.native.js.map +1 -0
- package/dist/jsx/index.js +2 -1
- package/dist/jsx/index.js.map +1 -1
- package/dist/jsx/index.mjs +2 -1
- package/dist/jsx/index.mjs.map +1 -1
- package/dist/jsx/index.native.js +4 -1
- package/dist/jsx/index.native.js.map +1 -1
- package/dist/jsx/toolbar/BubbleToolbar.mjs +9 -6
- package/dist/jsx/toolbar/BubbleToolbar.mjs.map +1 -1
- package/dist/jsx/toolbar/BubbleToolbar.native.js +12 -7
- package/dist/jsx/toolbar/BubbleToolbar.native.js.map +1 -1
- package/dist/jsx/toolbar/FixedToolbar.mjs +6 -3
- package/dist/jsx/toolbar/FixedToolbar.mjs.map +1 -1
- package/dist/jsx/toolbar/MoreDropdown.mjs +17 -9
- package/dist/jsx/toolbar/MoreDropdown.mjs.map +1 -1
- package/dist/jsx/toolbar/MoreDropdown.native.js +21 -9
- package/dist/jsx/toolbar/MoreDropdown.native.js.map +1 -1
- package/dist/jsx/toolbar/TableBubble.mjs +15 -2
- package/dist/jsx/toolbar/TableBubble.mjs.map +1 -1
- package/dist/jsx/toolbar/TableBubble.native.js +17 -2
- package/dist/jsx/toolbar/TableBubble.native.js.map +1 -1
- package/dist/jsx/types.mjs.map +1 -1
- package/dist/jsx/types.native.js.map +1 -1
- package/package.json +8 -8
- package/src/RichEditor.native.tsx +20 -0
- package/src/RichEditor.stories.tsx +32 -0
- package/src/RichEditor.tsx +5 -0
- package/src/RichTextEditor/index.tsx +14 -1
- package/src/editor/SlashCommands.tsx +10 -0
- package/src/editor/TentapEditor.native.tsx +13 -0
- package/src/editor/TiptapEditor.tsx +51 -279
- package/src/editor/capabilities.spec.ts +190 -0
- package/src/editor/capabilities.ts +76 -0
- package/src/editor/extensions.ts +315 -0
- package/src/index.ts +3 -0
- package/src/toolbar/BubbleToolbar.tsx +40 -28
- package/src/toolbar/FixedToolbar.tsx +20 -12
- package/src/toolbar/MoreDropdown.tsx +30 -6
- package/src/toolbar/TableBubble.tsx +65 -12
- package/src/types.ts +28 -1
- package/types/RichEditor.d.ts.map +1 -1
- package/types/RichEditor.native.d.ts.map +1 -1
- package/types/RichTextEditor/index.d.ts +7 -1
- package/types/RichTextEditor/index.d.ts.map +1 -1
- package/types/editor/SlashCommands.d.ts.map +1 -1
- package/types/editor/TentapEditor.native.d.ts.map +1 -1
- package/types/editor/TiptapEditor.d.ts.map +1 -1
- package/types/editor/capabilities.d.ts +29 -0
- package/types/editor/capabilities.d.ts.map +1 -0
- package/types/editor/extensions.d.ts +41 -0
- package/types/editor/extensions.d.ts.map +1 -0
- package/types/index.d.ts +2 -1
- package/types/index.d.ts.map +1 -1
- package/types/toolbar/BubbleToolbar.d.ts +4 -2
- package/types/toolbar/BubbleToolbar.d.ts.map +1 -1
- package/types/toolbar/FixedToolbar.d.ts +4 -2
- package/types/toolbar/FixedToolbar.d.ts.map +1 -1
- package/types/toolbar/MoreDropdown.d.ts +4 -2
- package/types/toolbar/MoreDropdown.d.ts.map +1 -1
- package/types/toolbar/TableBubble.d.ts +4 -1
- package/types/toolbar/TableBubble.d.ts.map +1 -1
- package/types/types.d.ts +23 -1
- package/types/types.d.ts.map +1 -1
|
@@ -0,0 +1,315 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Extension factory for the web Tiptap editor.
|
|
3
|
+
*
|
|
4
|
+
* One canonical list (LC-16) consumed by TiptapEditor AND by the
|
|
5
|
+
* capability specs, so what ships is exactly what is verified. With
|
|
6
|
+
* `capabilities: "markdown"` the non-markdown extensions are NOT
|
|
7
|
+
* registered — their commands do not exist on the editor (LC-52
|
|
8
|
+
* MARKDOWN-EXPRESSIBLE-EDITING), and StarterKit's underline sub-extension
|
|
9
|
+
* is disabled.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import { Extension, type Extensions } from "@tiptap/core";
|
|
13
|
+
import StarterKit from "@tiptap/starter-kit";
|
|
14
|
+
import Placeholder from "@tiptap/extension-placeholder";
|
|
15
|
+
import TextAlign from "@tiptap/extension-text-align";
|
|
16
|
+
import { Color } from "@tiptap/extension-color";
|
|
17
|
+
import { TextStyle } from "@tiptap/extension-text-style";
|
|
18
|
+
import { Highlight } from "@tiptap/extension-highlight";
|
|
19
|
+
import { Subscript } from "@tiptap/extension-subscript";
|
|
20
|
+
import { Superscript } from "@tiptap/extension-superscript";
|
|
21
|
+
import TaskList from "@tiptap/extension-task-list";
|
|
22
|
+
import TaskItem from "@tiptap/extension-task-item";
|
|
23
|
+
import Image from "@tiptap/extension-image";
|
|
24
|
+
import Typography from "@tiptap/extension-typography";
|
|
25
|
+
import CharacterCount from "@tiptap/extension-character-count";
|
|
26
|
+
import { Details, DetailsContent, DetailsSummary } from "@tiptap/extension-details";
|
|
27
|
+
import { Table } from "@tiptap/extension-table";
|
|
28
|
+
import { TableRow } from "@tiptap/extension-table-row";
|
|
29
|
+
import { TableHeader } from "@tiptap/extension-table-header";
|
|
30
|
+
import { TableCell } from "@tiptap/extension-table-cell";
|
|
31
|
+
import { common, createLowlight } from "lowlight";
|
|
32
|
+
import type { EditorCapabilities } from "../types";
|
|
33
|
+
import { MARKDOWN_EXCLUDED_SLASH_IDS } from "./capabilities";
|
|
34
|
+
import { SlashCommands } from "./SlashCommands";
|
|
35
|
+
import { MarkdownPaste } from "./MarkdownPaste";
|
|
36
|
+
import { BlockDragHandle } from "./BlockDragHandle";
|
|
37
|
+
import { MediaUpload } from "./media";
|
|
38
|
+
import { EmojiSuggestion } from "./emoji";
|
|
39
|
+
import { InlineMath } from "./mathematics";
|
|
40
|
+
import { CodeBlockWithPreview, PreviewChromeStore } from "./codeBlockPreview";
|
|
41
|
+
import { SuggestionChromeStore } from "./suggestionPopup";
|
|
42
|
+
import { t } from "../shared/t";
|
|
43
|
+
|
|
44
|
+
const lowlight = createLowlight(common);
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Tab / Shift-Tab indentation inside code blocks.
|
|
48
|
+
* Inserts or removes 2 spaces at the cursor (or start of each selected line).
|
|
49
|
+
*/
|
|
50
|
+
export const CodeBlockTabIndent = Extension.create({
|
|
51
|
+
name: "codeBlockTabIndent",
|
|
52
|
+
addKeyboardShortcuts() {
|
|
53
|
+
return {
|
|
54
|
+
Tab: ({ editor }) => {
|
|
55
|
+
if (!editor.isActive("codeBlock")) return false;
|
|
56
|
+
const { state, dispatch } = editor.view;
|
|
57
|
+
const { from, to } = state.selection;
|
|
58
|
+
|
|
59
|
+
if (from === to) {
|
|
60
|
+
dispatch(state.tr.insertText(" ", from, to));
|
|
61
|
+
return true;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const doc = state.doc;
|
|
65
|
+
const lines: { start: number }[] = [];
|
|
66
|
+
doc.nodesBetween(from, to, (node, pos) => {
|
|
67
|
+
if (node.isTextblock) {
|
|
68
|
+
lines.push({ start: pos + 1 });
|
|
69
|
+
}
|
|
70
|
+
});
|
|
71
|
+
if (lines.length === 0) {
|
|
72
|
+
dispatch(state.tr.insertText(" ", from, to));
|
|
73
|
+
return true;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
let tr = state.tr;
|
|
77
|
+
let offset = 0;
|
|
78
|
+
for (const line of lines) {
|
|
79
|
+
const lineStart = line.start + offset;
|
|
80
|
+
tr = tr.insertText(" ", lineStart);
|
|
81
|
+
offset += 2;
|
|
82
|
+
}
|
|
83
|
+
dispatch(tr);
|
|
84
|
+
return true;
|
|
85
|
+
},
|
|
86
|
+
"Shift-Tab": ({ editor }) => {
|
|
87
|
+
if (!editor.isActive("codeBlock")) return false;
|
|
88
|
+
const { state, dispatch } = editor.view;
|
|
89
|
+
const { from, to, $from } = state.selection;
|
|
90
|
+
|
|
91
|
+
if (from === to) {
|
|
92
|
+
const lineStart = from - $from.parentOffset;
|
|
93
|
+
const text = state.doc.textBetween(lineStart, from);
|
|
94
|
+
const leadingSpaces = text.match(/^ {1,2}/)?.[0];
|
|
95
|
+
if (leadingSpaces) {
|
|
96
|
+
dispatch(state.tr.delete(lineStart, lineStart + leadingSpaces.length));
|
|
97
|
+
}
|
|
98
|
+
return true;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
const doc = state.doc;
|
|
102
|
+
let tr = state.tr;
|
|
103
|
+
let offset = 0;
|
|
104
|
+
doc.nodesBetween(from, to, (node, pos) => {
|
|
105
|
+
if (node.isTextblock) {
|
|
106
|
+
const lineStart = pos + 1 + offset;
|
|
107
|
+
const text = tr.doc.textBetween(
|
|
108
|
+
lineStart,
|
|
109
|
+
Math.min(lineStart + node.content.size, tr.doc.content.size),
|
|
110
|
+
);
|
|
111
|
+
const leadingSpaces = text.match(/^ {1,2}/)?.[0];
|
|
112
|
+
if (leadingSpaces) {
|
|
113
|
+
tr = tr.delete(lineStart, lineStart + leadingSpaces.length);
|
|
114
|
+
offset -= leadingSpaces.length;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
});
|
|
118
|
+
dispatch(tr);
|
|
119
|
+
return true;
|
|
120
|
+
},
|
|
121
|
+
};
|
|
122
|
+
},
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Override Shift-Enter and Mod-Enter to create new paragraphs instead of
|
|
127
|
+
* hard breaks (<br>). Mirrors GitLab's approach: HardBreak node stays
|
|
128
|
+
* registered (so <br> in pasted HTML is preserved) but its keyboard
|
|
129
|
+
* shortcuts are replaced with the default enter (split paragraph) behavior.
|
|
130
|
+
*/
|
|
131
|
+
export const ParagraphEnterKeys = Extension.create({
|
|
132
|
+
name: "paragraphEnterKeys",
|
|
133
|
+
priority: 1000,
|
|
134
|
+
addKeyboardShortcuts() {
|
|
135
|
+
return {
|
|
136
|
+
"Shift-Enter": ({ editor }) =>
|
|
137
|
+
editor.commands.first([
|
|
138
|
+
() => editor.commands.newlineInCode(),
|
|
139
|
+
() => editor.commands.splitListItem("listItem"),
|
|
140
|
+
() => editor.commands.createParagraphNear(),
|
|
141
|
+
() => editor.commands.liftEmptyBlock(),
|
|
142
|
+
() => editor.commands.splitBlock(),
|
|
143
|
+
]),
|
|
144
|
+
"Mod-Enter": ({ editor }) =>
|
|
145
|
+
editor.commands.first([
|
|
146
|
+
() => editor.commands.newlineInCode(),
|
|
147
|
+
() => editor.commands.createParagraphNear(),
|
|
148
|
+
() => editor.commands.liftEmptyBlock(),
|
|
149
|
+
() => editor.commands.splitBlock(),
|
|
150
|
+
]),
|
|
151
|
+
};
|
|
152
|
+
},
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* Tab/Shift-Tab indent/dedent and Alt+Arrow move for list items.
|
|
157
|
+
* Tab sinks (indents), Shift-Tab lifts (dedents).
|
|
158
|
+
* Alt+Up/Down moves the list item up or down among siblings.
|
|
159
|
+
*/
|
|
160
|
+
export const ListItemKeys = Extension.create({
|
|
161
|
+
name: "listItemKeys",
|
|
162
|
+
addKeyboardShortcuts() {
|
|
163
|
+
return {
|
|
164
|
+
Tab: ({ editor }) => {
|
|
165
|
+
if (editor.isActive("codeBlock")) return false;
|
|
166
|
+
if (
|
|
167
|
+
editor.isActive("bulletList") ||
|
|
168
|
+
editor.isActive("orderedList") ||
|
|
169
|
+
editor.isActive("taskList")
|
|
170
|
+
) {
|
|
171
|
+
return (
|
|
172
|
+
editor.commands.sinkListItem("listItem") || editor.commands.sinkListItem("taskItem")
|
|
173
|
+
);
|
|
174
|
+
}
|
|
175
|
+
return false;
|
|
176
|
+
},
|
|
177
|
+
"Shift-Tab": ({ editor }) => {
|
|
178
|
+
if (editor.isActive("codeBlock")) return false;
|
|
179
|
+
if (
|
|
180
|
+
editor.isActive("bulletList") ||
|
|
181
|
+
editor.isActive("orderedList") ||
|
|
182
|
+
editor.isActive("taskList")
|
|
183
|
+
) {
|
|
184
|
+
return (
|
|
185
|
+
editor.commands.liftListItem("listItem") || editor.commands.liftListItem("taskItem")
|
|
186
|
+
);
|
|
187
|
+
}
|
|
188
|
+
return false;
|
|
189
|
+
},
|
|
190
|
+
};
|
|
191
|
+
},
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
// ===========================================================================
|
|
195
|
+
// Factory
|
|
196
|
+
// ===========================================================================
|
|
197
|
+
|
|
198
|
+
export interface BuildEditorExtensionsOptions {
|
|
199
|
+
/** Capability preset (default "full") */
|
|
200
|
+
capabilities?: EditorCapabilities;
|
|
201
|
+
/** Placeholder text (defaults to the translated "Start writing...") */
|
|
202
|
+
placeholder?: string;
|
|
203
|
+
/** Pre-built mention extension (ignored in markdown capabilities) */
|
|
204
|
+
mentionExtension?: Extensions[number] | null;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
/** Build the production extension list for the given capability surface */
|
|
208
|
+
export function buildEditorExtensions({
|
|
209
|
+
capabilities = "full",
|
|
210
|
+
placeholder,
|
|
211
|
+
mentionExtension = null,
|
|
212
|
+
}: BuildEditorExtensionsOptions = {}): Extensions {
|
|
213
|
+
const markdown = capabilities === "markdown";
|
|
214
|
+
|
|
215
|
+
return [
|
|
216
|
+
StarterKit.configure({
|
|
217
|
+
heading: {
|
|
218
|
+
levels: [1, 2, 3, 4, 5, 6],
|
|
219
|
+
HTMLAttributes: { dir: "auto" },
|
|
220
|
+
},
|
|
221
|
+
paragraph: {
|
|
222
|
+
HTMLAttributes: { dir: "auto" },
|
|
223
|
+
},
|
|
224
|
+
link: {
|
|
225
|
+
openOnClick: false,
|
|
226
|
+
HTMLAttributes: {
|
|
227
|
+
rel: "noopener noreferrer",
|
|
228
|
+
target: "_blank",
|
|
229
|
+
},
|
|
230
|
+
},
|
|
231
|
+
bulletList: {
|
|
232
|
+
keepMarks: true,
|
|
233
|
+
},
|
|
234
|
+
orderedList: {
|
|
235
|
+
keepMarks: true,
|
|
236
|
+
},
|
|
237
|
+
codeBlock: false,
|
|
238
|
+
hardBreak: false,
|
|
239
|
+
// Markdown has no underline syntax — the mark must not exist
|
|
240
|
+
...(markdown && { underline: false }),
|
|
241
|
+
}),
|
|
242
|
+
CodeBlockWithPreview.configure({
|
|
243
|
+
lowlight,
|
|
244
|
+
exitOnTripleEnter: true,
|
|
245
|
+
exitOnArrowDown: false,
|
|
246
|
+
}),
|
|
247
|
+
ParagraphEnterKeys,
|
|
248
|
+
CodeBlockTabIndent,
|
|
249
|
+
ListItemKeys,
|
|
250
|
+
// superscriptTwo/Three would mangle typed TeX (`$E=mc^2$` became
|
|
251
|
+
// `E=mc²` before the math input rule ran) — off, rest stays.
|
|
252
|
+
Typography.configure({
|
|
253
|
+
superscriptTwo: false,
|
|
254
|
+
superscriptThree: false,
|
|
255
|
+
}),
|
|
256
|
+
CharacterCount,
|
|
257
|
+
SlashCommands.configure({
|
|
258
|
+
excludeIds: markdown ? [...MARKDOWN_EXCLUDED_SLASH_IDS] : [],
|
|
259
|
+
}),
|
|
260
|
+
MarkdownPaste,
|
|
261
|
+
MediaUpload,
|
|
262
|
+
SuggestionChromeStore,
|
|
263
|
+
PreviewChromeStore,
|
|
264
|
+
// Emoji suggestions insert plain unicode characters (just text), so
|
|
265
|
+
// they stay available in markdown capabilities.
|
|
266
|
+
EmojiSuggestion,
|
|
267
|
+
...(markdown
|
|
268
|
+
? []
|
|
269
|
+
: [
|
|
270
|
+
InlineMath,
|
|
271
|
+
// Collapsible sections (GitLab <details> parity). persist: true
|
|
272
|
+
// is the extension's supported mode: its node view re-syncs the
|
|
273
|
+
// open state from node.attrs on every update, so ephemeral
|
|
274
|
+
// (persist: false) toggles get reverted by the next transaction.
|
|
275
|
+
// The open state therefore lives in the doc and serializes as
|
|
276
|
+
// `<details open>` — valid GitLab markdown.
|
|
277
|
+
Details.configure({ persist: true }),
|
|
278
|
+
DetailsSummary,
|
|
279
|
+
DetailsContent,
|
|
280
|
+
]),
|
|
281
|
+
// Mention chips have no markdown representation — full surface only
|
|
282
|
+
...(mentionExtension && !markdown ? [mentionExtension] : []),
|
|
283
|
+
BlockDragHandle,
|
|
284
|
+
Table.configure({ resizable: true }),
|
|
285
|
+
TableRow,
|
|
286
|
+
TableHeader,
|
|
287
|
+
TableCell,
|
|
288
|
+
Placeholder.configure({
|
|
289
|
+
placeholder: placeholder ?? t("Start writing..."),
|
|
290
|
+
}),
|
|
291
|
+
...(markdown
|
|
292
|
+
? []
|
|
293
|
+
: [
|
|
294
|
+
TextAlign.configure({
|
|
295
|
+
types: ["heading", "paragraph"],
|
|
296
|
+
defaultAlignment: "left",
|
|
297
|
+
}),
|
|
298
|
+
TextStyle,
|
|
299
|
+
Color,
|
|
300
|
+
Highlight.configure({
|
|
301
|
+
multicolor: true,
|
|
302
|
+
}),
|
|
303
|
+
Subscript,
|
|
304
|
+
Superscript,
|
|
305
|
+
]),
|
|
306
|
+
TaskList,
|
|
307
|
+
TaskItem.configure({
|
|
308
|
+
nested: true,
|
|
309
|
+
}),
|
|
310
|
+
Image.configure({
|
|
311
|
+
inline: true,
|
|
312
|
+
allowBase64: true,
|
|
313
|
+
}),
|
|
314
|
+
];
|
|
315
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -13,7 +13,10 @@ export type { RichTextEditorProps } from "./RichTextEditor";
|
|
|
13
13
|
export type {
|
|
14
14
|
RichEditorRef,
|
|
15
15
|
RichEditorProps,
|
|
16
|
+
EditorCapabilities,
|
|
16
17
|
EditorCommand,
|
|
17
18
|
EditorState,
|
|
18
19
|
RichTextMentionOptions,
|
|
19
20
|
} from "./types";
|
|
21
|
+
|
|
22
|
+
export { MARKDOWN_COMMANDS, isCommandInCapabilities } from "./editor/capabilities";
|
|
@@ -14,8 +14,9 @@ import type { Editor } from "@tiptap/react";
|
|
|
14
14
|
import { BubbleMenu } from "@tiptap/react/menus";
|
|
15
15
|
import { useResolvedKnobs } from "@multiplatform.one/theme";
|
|
16
16
|
import { XStack } from "tamagui";
|
|
17
|
-
import type { EditorState, RichEditorRef } from "../types";
|
|
17
|
+
import type { EditorCapabilities, EditorState, RichEditorRef } from "../types";
|
|
18
18
|
import { defaultEditorState } from "../types";
|
|
19
|
+
import { isCommandInCapabilities } from "../editor/capabilities";
|
|
19
20
|
import { ToolbarButton } from "./ToolbarButton";
|
|
20
21
|
import {
|
|
21
22
|
BoldIcon,
|
|
@@ -42,6 +43,8 @@ export interface BubbleToolbarProps {
|
|
|
42
43
|
editorRef: React.RefObject<RichEditorRef | null>;
|
|
43
44
|
/** Current editor state */
|
|
44
45
|
state?: EditorState;
|
|
46
|
+
/** Capability surface — markdown hides non-markdown controls (default "full") */
|
|
47
|
+
capabilities?: EditorCapabilities;
|
|
45
48
|
}
|
|
46
49
|
|
|
47
50
|
// ===========================================================================
|
|
@@ -52,8 +55,11 @@ export function BubbleToolbar({
|
|
|
52
55
|
editor,
|
|
53
56
|
editorRef,
|
|
54
57
|
state = defaultEditorState,
|
|
58
|
+
capabilities = "full",
|
|
55
59
|
}: BubbleToolbarProps) {
|
|
56
60
|
const { knobProps } = useResolvedKnobs();
|
|
61
|
+
const has = (command: Parameters<typeof isCommandInCapabilities>[0]) =>
|
|
62
|
+
isCommandInCapabilities(command, capabilities);
|
|
57
63
|
|
|
58
64
|
if (!editor) return null;
|
|
59
65
|
|
|
@@ -97,14 +103,16 @@ export function BubbleToolbar({
|
|
|
97
103
|
<ItalicIcon>I</ItalicIcon>
|
|
98
104
|
</ToolbarButton>
|
|
99
105
|
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
106
|
+
{has("underline") && (
|
|
107
|
+
<ToolbarButton
|
|
108
|
+
active={state.isUnderline}
|
|
109
|
+
onPress={() => editorRef.current?.executeCommand("underline")}
|
|
110
|
+
aria-label={t("Underline")}
|
|
111
|
+
tooltip={SHORTCUTS.underline}
|
|
112
|
+
>
|
|
113
|
+
<UnderlineIcon>U</UnderlineIcon>
|
|
114
|
+
</ToolbarButton>
|
|
115
|
+
)}
|
|
108
116
|
|
|
109
117
|
<ToolbarButton
|
|
110
118
|
active={state.isStrike}
|
|
@@ -124,27 +132,31 @@ export function BubbleToolbar({
|
|
|
124
132
|
<InlineCodeIcon />
|
|
125
133
|
</ToolbarButton>
|
|
126
134
|
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
+
{has("subscript") && (
|
|
136
|
+
<ToolbarButton
|
|
137
|
+
active={state.isSubscript}
|
|
138
|
+
onPress={() => editorRef.current?.executeCommand("subscript")}
|
|
139
|
+
aria-label={t("Subscript")}
|
|
140
|
+
tooltip={SHORTCUTS.subscript}
|
|
141
|
+
>
|
|
142
|
+
<SubscriptIcon />
|
|
143
|
+
</ToolbarButton>
|
|
144
|
+
)}
|
|
135
145
|
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
146
|
+
{has("superscript") && (
|
|
147
|
+
<ToolbarButton
|
|
148
|
+
active={state.isSuperscript}
|
|
149
|
+
onPress={() => editorRef.current?.executeCommand("superscript")}
|
|
150
|
+
aria-label={t("Superscript")}
|
|
151
|
+
tooltip={SHORTCUTS.superscript}
|
|
152
|
+
>
|
|
153
|
+
<SuperscriptIcon />
|
|
154
|
+
</ToolbarButton>
|
|
155
|
+
)}
|
|
144
156
|
|
|
145
|
-
{/* Color pickers */}
|
|
146
|
-
<TextColorPicker editorRef={editorRef} state={state} />
|
|
147
|
-
<HighlightColorPicker editorRef={editorRef} state={state} />
|
|
157
|
+
{/* Color pickers — no markdown syntax (LC-52) */}
|
|
158
|
+
{has("textColor") && <TextColorPicker editorRef={editorRef} state={state} />}
|
|
159
|
+
{has("highlight") && <HighlightColorPicker editorRef={editorRef} state={state} />}
|
|
148
160
|
|
|
149
161
|
{/* Link */}
|
|
150
162
|
<LinkDialog editorRef={editorRef} state={state} />
|
|
@@ -10,8 +10,9 @@
|
|
|
10
10
|
|
|
11
11
|
import { useCallback, useEffect, useRef } from "react";
|
|
12
12
|
import { XStack } from "tamagui";
|
|
13
|
-
import type { EditorState, RichEditorRef, ToolbarSection } from "../types";
|
|
13
|
+
import type { EditorCapabilities, EditorState, RichEditorRef, ToolbarSection } from "../types";
|
|
14
14
|
import { defaultEditorState } from "../types";
|
|
15
|
+
import { isCommandInCapabilities } from "../editor/capabilities";
|
|
15
16
|
import { ToolbarFrame } from "../styled";
|
|
16
17
|
import { ToolbarButton } from "./ToolbarButton";
|
|
17
18
|
import { ToolbarGroup } from "./ToolbarGroup";
|
|
@@ -53,6 +54,8 @@ export interface FixedToolbarProps {
|
|
|
53
54
|
state?: EditorState;
|
|
54
55
|
/** Toolbar sections to show (shows all when omitted) */
|
|
55
56
|
sections?: ToolbarSection[];
|
|
57
|
+
/** Capability surface — markdown hides non-markdown controls (default "full") */
|
|
58
|
+
capabilities?: EditorCapabilities;
|
|
56
59
|
/** Whether the toolbar has an error state */
|
|
57
60
|
hasError?: boolean;
|
|
58
61
|
/** Whether the editor is in fullscreen mode */
|
|
@@ -84,6 +87,7 @@ export function FixedToolbar({
|
|
|
84
87
|
editorRef,
|
|
85
88
|
state = defaultEditorState,
|
|
86
89
|
sections,
|
|
90
|
+
capabilities = "full",
|
|
87
91
|
hasError,
|
|
88
92
|
fullscreen,
|
|
89
93
|
onFullscreenToggle,
|
|
@@ -191,14 +195,16 @@ export function FixedToolbar({
|
|
|
191
195
|
>
|
|
192
196
|
<ItalicIcon>I</ItalicIcon>
|
|
193
197
|
</ToolbarButton>
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
198
|
+
{isCommandInCapabilities("underline", capabilities) && (
|
|
199
|
+
<ToolbarButton
|
|
200
|
+
active={state.isUnderline}
|
|
201
|
+
onPress={() => executeCommand("underline")}
|
|
202
|
+
aria-label={t("Underline")}
|
|
203
|
+
tooltip={SHORTCUTS.underline}
|
|
204
|
+
>
|
|
205
|
+
<UnderlineIcon>U</UnderlineIcon>
|
|
206
|
+
</ToolbarButton>
|
|
207
|
+
)}
|
|
202
208
|
<ToolbarButton
|
|
203
209
|
active={state.isStrike}
|
|
204
210
|
onPress={() => executeCommand("strike")}
|
|
@@ -209,8 +215,10 @@ export function FixedToolbar({
|
|
|
209
215
|
</ToolbarButton>
|
|
210
216
|
</ToolbarGroup>
|
|
211
217
|
|
|
212
|
-
{/* Color */}
|
|
213
|
-
<ToolbarGroup
|
|
218
|
+
{/* Color — no markdown syntax for color/highlight (LC-52) */}
|
|
219
|
+
<ToolbarGroup
|
|
220
|
+
visible={visible("color") && isCommandInCapabilities("textColor", capabilities)}
|
|
221
|
+
>
|
|
214
222
|
<TextColorPicker editorRef={editorRef} state={state} />
|
|
215
223
|
<HighlightColorPicker editorRef={editorRef} state={state} />
|
|
216
224
|
</ToolbarGroup>
|
|
@@ -268,7 +276,7 @@ export function FixedToolbar({
|
|
|
268
276
|
}
|
|
269
277
|
showSeparator={false}
|
|
270
278
|
>
|
|
271
|
-
<MoreDropdown editorRef={editorRef} state={state} />
|
|
279
|
+
<MoreDropdown editorRef={editorRef} state={state} capabilities={capabilities} />
|
|
272
280
|
</ToolbarGroup>
|
|
273
281
|
</XStack>
|
|
274
282
|
</div>
|
|
@@ -7,7 +7,8 @@ import type React from "react";
|
|
|
7
7
|
import { DotsThreeIcon } from "@phosphor-icons/react";
|
|
8
8
|
import { useRef, useState } from "react";
|
|
9
9
|
import { DropdownMenu } from "@multiplatform.one/components";
|
|
10
|
-
import type { EditorCommand, EditorState, RichEditorRef } from "../types";
|
|
10
|
+
import type { EditorCapabilities, EditorCommand, EditorState, RichEditorRef } from "../types";
|
|
11
|
+
import { isCommandInCapabilities } from "../editor/capabilities";
|
|
11
12
|
import { ToolbarButton } from "./ToolbarButton";
|
|
12
13
|
import {
|
|
13
14
|
SubscriptIcon,
|
|
@@ -25,6 +26,8 @@ import { t } from "../shared/t";
|
|
|
25
26
|
export interface MoreDropdownProps {
|
|
26
27
|
editorRef: React.RefObject<RichEditorRef | null>;
|
|
27
28
|
state: EditorState;
|
|
29
|
+
/** Capability surface — markdown drops sub/superscript and alignment */
|
|
30
|
+
capabilities?: EditorCapabilities;
|
|
28
31
|
disabled?: boolean;
|
|
29
32
|
}
|
|
30
33
|
|
|
@@ -130,7 +133,12 @@ function ItemSection({
|
|
|
130
133
|
);
|
|
131
134
|
}
|
|
132
135
|
|
|
133
|
-
export function MoreDropdown({
|
|
136
|
+
export function MoreDropdown({
|
|
137
|
+
editorRef,
|
|
138
|
+
state,
|
|
139
|
+
capabilities = "full",
|
|
140
|
+
disabled,
|
|
141
|
+
}: MoreDropdownProps) {
|
|
134
142
|
const [open, setOpen] = useState(false);
|
|
135
143
|
const selectedRef = useRef(false);
|
|
136
144
|
|
|
@@ -142,6 +150,14 @@ export function MoreDropdown({ editorRef, state, disabled }: MoreDropdownProps)
|
|
|
142
150
|
}
|
|
143
151
|
};
|
|
144
152
|
|
|
153
|
+
// Sections whose commands markdown cannot express disappear whole (LC-52)
|
|
154
|
+
const inlineItems = INLINE_ITEMS.filter((item) =>
|
|
155
|
+
isCommandInCapabilities(item.command, capabilities),
|
|
156
|
+
);
|
|
157
|
+
const alignItems = ALIGN_ITEMS.filter((item) =>
|
|
158
|
+
isCommandInCapabilities(item.command, capabilities),
|
|
159
|
+
);
|
|
160
|
+
|
|
145
161
|
return (
|
|
146
162
|
<DropdownMenu open={open} onOpenChange={setOpen} placement="bottom-end">
|
|
147
163
|
<DropdownMenu.Trigger asChild>
|
|
@@ -167,10 +183,18 @@ export function MoreDropdown({ editorRef, state, disabled }: MoreDropdownProps)
|
|
|
167
183
|
}
|
|
168
184
|
}}
|
|
169
185
|
>
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
186
|
+
{inlineItems.length > 0 && (
|
|
187
|
+
<>
|
|
188
|
+
<ItemSection items={inlineItems} state={state} onSelect={handleSelect} />
|
|
189
|
+
<DropdownMenu.Separator />
|
|
190
|
+
</>
|
|
191
|
+
)}
|
|
192
|
+
{alignItems.length > 0 && (
|
|
193
|
+
<>
|
|
194
|
+
<ItemSection items={alignItems} state={state} onSelect={handleSelect} />
|
|
195
|
+
<DropdownMenu.Separator />
|
|
196
|
+
</>
|
|
197
|
+
)}
|
|
174
198
|
<ItemSection items={BLOCK_ITEMS} state={state} onSelect={handleSelect} />
|
|
175
199
|
</DropdownMenu.Content>
|
|
176
200
|
</DropdownMenu>
|