@cgi-learning-hub/ui 1.14.0 → 1.15.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/dist/index.cjs.js +133 -30668
- package/dist/index.d.ts +3 -3
- package/dist/index.es.js +2 -30529
- package/dist/src-jE7nLe9k.js +29565 -0
- package/dist/tiptap/index.cjs.js +2871 -0
- package/dist/tiptap/index.d.ts +46 -0
- package/dist/tiptap/index.es.js +2843 -0
- package/dist/tiptap/styles.css +621 -0
- package/dist/ui.css +87 -1
- package/package.json +32 -10
|
@@ -0,0 +1,2871 @@
|
|
|
1
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
|
+
const require_src = require("../src-BXHOgzdu.cjs");
|
|
3
|
+
let react = require("react");
|
|
4
|
+
let react_jsx_runtime = require("react/jsx-runtime");
|
|
5
|
+
let _mui_material_styles = require("@mui/material/styles");
|
|
6
|
+
let _mui_material_utils = require("@mui/material/utils");
|
|
7
|
+
let _tiptap_pm_state = require("@tiptap/pm/state");
|
|
8
|
+
let _tiptap_react = require("@tiptap/react");
|
|
9
|
+
require("@tiptap/pm/tables");
|
|
10
|
+
let _mui_material_ToggleButton = require("@mui/material/ToggleButton");
|
|
11
|
+
_mui_material_ToggleButton = require_src.__toESM(_mui_material_ToggleButton, 1);
|
|
12
|
+
let _mui_material_Tooltip = require("@mui/material/Tooltip");
|
|
13
|
+
_mui_material_Tooltip = require_src.__toESM(_mui_material_Tooltip, 1);
|
|
14
|
+
let _mui_material_Badge = require("@mui/material/Badge");
|
|
15
|
+
_mui_material_Badge = require_src.__toESM(_mui_material_Badge, 1);
|
|
16
|
+
let _mui_material_SvgIcon = require("@mui/material/SvgIcon");
|
|
17
|
+
let _mui_material_MenuItem = require("@mui/material/MenuItem");
|
|
18
|
+
_mui_material_MenuItem = require_src.__toESM(_mui_material_MenuItem, 1);
|
|
19
|
+
let _mui_material_Typography = require("@mui/material/Typography");
|
|
20
|
+
_mui_material_Typography = require_src.__toESM(_mui_material_Typography, 1);
|
|
21
|
+
let _mui_material_Menu = require("@mui/material/Menu");
|
|
22
|
+
_mui_material_Menu = require_src.__toESM(_mui_material_Menu, 1);
|
|
23
|
+
let _mui_material_IconButton = require("@mui/material/IconButton");
|
|
24
|
+
_mui_material_IconButton = require_src.__toESM(_mui_material_IconButton, 1);
|
|
25
|
+
let _mui_material_Stack = require("@mui/material/Stack");
|
|
26
|
+
_mui_material_Stack = require_src.__toESM(_mui_material_Stack, 1);
|
|
27
|
+
let _mui_material_InputBase = require("@mui/material/InputBase");
|
|
28
|
+
_mui_material_InputBase = require_src.__toESM(_mui_material_InputBase, 1);
|
|
29
|
+
let _mui_material_Divider = require("@mui/material/Divider");
|
|
30
|
+
_mui_material_Divider = require_src.__toESM(_mui_material_Divider, 1);
|
|
31
|
+
let _mui_material_Popover = require("@mui/material/Popover");
|
|
32
|
+
_mui_material_Popover = require_src.__toESM(_mui_material_Popover, 1);
|
|
33
|
+
//#region src/tiptap/components/icons/text-snippet-icon.tsx
|
|
34
|
+
var TextSnippetIcon = (0, _mui_material_utils.createSvgIcon)(/* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", { d: "M14.17,5L19,9.83V19H5V5L14.17,5L14.17,5 M14.17,3H5C3.9,3,3,3.9,3,5v14c0,1.1,0.9,2,2,2h14c1.1,0,2-0.9,2-2V9.83 c0-0.53-0.21-1.04-0.59-1.41l-4.83-4.83C15.21,3.21,14.7,3,14.17,3L14.17,3z M7,15h10v2H7V15z M7,11h10v2H7V11z M7,7h7v2H7V7z" }), "TextSnippet");
|
|
35
|
+
//#endregion
|
|
36
|
+
//#region src/tiptap/hooks/use-tiptap-editor.ts
|
|
37
|
+
function getActivePageEditor(editor) {
|
|
38
|
+
const pages = editor.storage.pages;
|
|
39
|
+
if (!pages || !("activeEditor" in pages)) return null;
|
|
40
|
+
return pages.activeEditor ?? null;
|
|
41
|
+
}
|
|
42
|
+
function useTiptapEditor(providedEditor) {
|
|
43
|
+
const { editor: coreEditor } = (0, _tiptap_react.useCurrentEditor)();
|
|
44
|
+
const mainEditor = providedEditor ?? coreEditor;
|
|
45
|
+
const [storageEditor, setStorageEditor] = (0, react.useState)(null);
|
|
46
|
+
(0, react.useEffect)(() => {
|
|
47
|
+
if (!mainEditor) {
|
|
48
|
+
setStorageEditor(null);
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
const updateHandler = () => setStorageEditor(getActivePageEditor(mainEditor));
|
|
52
|
+
updateHandler();
|
|
53
|
+
mainEditor.on("update", updateHandler);
|
|
54
|
+
mainEditor.on("selectionUpdate", updateHandler);
|
|
55
|
+
return () => {
|
|
56
|
+
mainEditor.off("update", updateHandler);
|
|
57
|
+
mainEditor.off("selectionUpdate", updateHandler);
|
|
58
|
+
};
|
|
59
|
+
}, [mainEditor]);
|
|
60
|
+
(0, react.useEffect)(() => {
|
|
61
|
+
if (!storageEditor) return;
|
|
62
|
+
const handleDestroy = () => setStorageEditor(null);
|
|
63
|
+
storageEditor.on("destroy", handleDestroy);
|
|
64
|
+
return () => {
|
|
65
|
+
storageEditor.off("destroy", handleDestroy);
|
|
66
|
+
};
|
|
67
|
+
}, [storageEditor]);
|
|
68
|
+
return (0, _tiptap_react.useEditorState)({
|
|
69
|
+
editor: storageEditor ?? mainEditor,
|
|
70
|
+
selector(context) {
|
|
71
|
+
if (!context.editor) return {
|
|
72
|
+
editor: null,
|
|
73
|
+
editorState: void 0,
|
|
74
|
+
canCommand: void 0
|
|
75
|
+
};
|
|
76
|
+
return {
|
|
77
|
+
editor: context.editor,
|
|
78
|
+
editorState: context.editor.state,
|
|
79
|
+
canCommand: context.editor.can
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
}) ?? { editor: null };
|
|
83
|
+
}
|
|
84
|
+
//#endregion
|
|
85
|
+
//#region src/tiptap/lib/tiptap-utils.ts
|
|
86
|
+
var MAC_SYMBOLS = {
|
|
87
|
+
mod: "⌘",
|
|
88
|
+
command: "⌘",
|
|
89
|
+
meta: "⌘",
|
|
90
|
+
ctrl: "⌃",
|
|
91
|
+
control: "⌃",
|
|
92
|
+
alt: "⌥",
|
|
93
|
+
option: "⌥",
|
|
94
|
+
shift: "⇧",
|
|
95
|
+
backspace: "Del",
|
|
96
|
+
delete: "⌦",
|
|
97
|
+
enter: "⏎",
|
|
98
|
+
escape: "⎋",
|
|
99
|
+
capslock: "⇪"
|
|
100
|
+
};
|
|
101
|
+
function cn(...inputs) {
|
|
102
|
+
return require_src.clsx(inputs);
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* Determines if the current platform is macOS
|
|
106
|
+
* @returns boolean indicating if the current platform is Mac
|
|
107
|
+
*/
|
|
108
|
+
function isMac() {
|
|
109
|
+
return typeof navigator !== "undefined" && navigator.platform.toLowerCase().includes("mac");
|
|
110
|
+
}
|
|
111
|
+
/**
|
|
112
|
+
* Formats a shortcut key based on the platform (Mac or non-Mac)
|
|
113
|
+
* @param key - The key to format (e.g., "ctrl", "alt", "shift")
|
|
114
|
+
* @param isMac - Boolean indicating if the platform is Mac
|
|
115
|
+
* @param capitalize - Whether to capitalize the key (default: true)
|
|
116
|
+
* @returns Formatted shortcut key symbol
|
|
117
|
+
*/
|
|
118
|
+
var formatShortcutKey = (key, isMac, capitalize = true) => {
|
|
119
|
+
if (isMac) return MAC_SYMBOLS[key.toLowerCase()] || (capitalize ? key.toUpperCase() : key);
|
|
120
|
+
return capitalize ? key.charAt(0).toUpperCase() + key.slice(1) : key;
|
|
121
|
+
};
|
|
122
|
+
/**
|
|
123
|
+
* Parses a shortcut key string into an array of formatted key symbols
|
|
124
|
+
* @param shortcutKeys - The string of shortcut keys (e.g., "ctrl-alt-shift")
|
|
125
|
+
* @param delimiter - The delimiter used to split the keys (default: "-")
|
|
126
|
+
* @param capitalize - Whether to capitalize the keys (default: true)
|
|
127
|
+
* @returns Array of formatted shortcut key symbols
|
|
128
|
+
*/
|
|
129
|
+
var parseShortcutKeys = (props) => {
|
|
130
|
+
const { shortcutKeys, delimiter = "+", capitalize = true } = props;
|
|
131
|
+
if (!shortcutKeys) return [];
|
|
132
|
+
return shortcutKeys.split(delimiter).map((key) => key.trim()).map((key) => formatShortcutKey(key, isMac(), capitalize));
|
|
133
|
+
};
|
|
134
|
+
/**
|
|
135
|
+
* Checks if a mark exists in the editor schema
|
|
136
|
+
* @param markName - The name of the mark to check
|
|
137
|
+
* @param editor - The editor instance
|
|
138
|
+
* @returns boolean indicating if the mark exists in the schema
|
|
139
|
+
*/
|
|
140
|
+
var isMarkInSchema = (markName, editor) => {
|
|
141
|
+
if (!editor?.schema) return false;
|
|
142
|
+
return editor.schema.spec.marks.get(markName) !== void 0;
|
|
143
|
+
};
|
|
144
|
+
/**
|
|
145
|
+
* Checks if a node exists in the editor schema
|
|
146
|
+
* @param nodeName - The name of the node to check
|
|
147
|
+
* @param editor - The editor instance
|
|
148
|
+
* @returns boolean indicating if the node exists in the schema
|
|
149
|
+
*/
|
|
150
|
+
var isNodeInSchema = (nodeName, editor) => {
|
|
151
|
+
if (!editor?.schema) return false;
|
|
152
|
+
return editor.schema.spec.nodes.get(nodeName) !== void 0;
|
|
153
|
+
};
|
|
154
|
+
/**
|
|
155
|
+
* Checks if a value is a valid number (not null, undefined, or NaN)
|
|
156
|
+
* @param value - The value to check
|
|
157
|
+
* @returns boolean indicating if the value is a valid number
|
|
158
|
+
*/
|
|
159
|
+
function isValidPosition(pos) {
|
|
160
|
+
return typeof pos === "number" && pos >= 0;
|
|
161
|
+
}
|
|
162
|
+
/**
|
|
163
|
+
* Checks if one or more extensions are registered in the Tiptap editor.
|
|
164
|
+
* @param editor - The Tiptap editor instance
|
|
165
|
+
* @param extensionNames - A single extension name or an array of names to check
|
|
166
|
+
* @returns True if at least one of the extensions is available, false otherwise
|
|
167
|
+
*/
|
|
168
|
+
var warnedMissingExtensions = /* @__PURE__ */ new Set();
|
|
169
|
+
function isExtensionAvailable(editor, extensionNames) {
|
|
170
|
+
if (!editor || editor.isDestroyed) return false;
|
|
171
|
+
const names = Array.isArray(extensionNames) ? extensionNames : [extensionNames];
|
|
172
|
+
const extensions = editor.extensionManager?.extensions;
|
|
173
|
+
if (!extensions) return false;
|
|
174
|
+
const found = names.some((name) => extensions.some((ext) => ext.name === name));
|
|
175
|
+
if (!found) {
|
|
176
|
+
const key = names.join(", ");
|
|
177
|
+
if (!warnedMissingExtensions.has(key)) {
|
|
178
|
+
warnedMissingExtensions.add(key);
|
|
179
|
+
console.warn(`None of the extensions [${key}] were found in the editor schema. Ensure they are included in the editor configuration.`);
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
return found;
|
|
183
|
+
}
|
|
184
|
+
/**
|
|
185
|
+
* Finds a node at the specified position with error handling
|
|
186
|
+
* @param editor The Tiptap editor instance
|
|
187
|
+
* @param position The position in the document to find the node
|
|
188
|
+
* @returns The node at the specified position, or null if not found
|
|
189
|
+
*/
|
|
190
|
+
function findNodeAtPosition(editor, position) {
|
|
191
|
+
try {
|
|
192
|
+
const node = editor.state.doc.nodeAt(position);
|
|
193
|
+
if (!node) {
|
|
194
|
+
console.warn(`No node found at position ${position}`);
|
|
195
|
+
return null;
|
|
196
|
+
}
|
|
197
|
+
return node;
|
|
198
|
+
} catch (error) {
|
|
199
|
+
console.error(`Error getting node at position ${position}:`, error);
|
|
200
|
+
return null;
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
/**
|
|
204
|
+
* Finds the position and instance of a node in the document
|
|
205
|
+
* @param props Object containing editor, node (optional), and nodePos (optional)
|
|
206
|
+
* @param props.editor The Tiptap editor instance
|
|
207
|
+
* @param props.node The node to find (optional if nodePos is provided)
|
|
208
|
+
* @param props.nodePos The position of the node to find (optional if node is provided)
|
|
209
|
+
* @returns An object with the position and node, or null if not found
|
|
210
|
+
*/
|
|
211
|
+
function findNodePosition(props) {
|
|
212
|
+
const { editor, node, nodePos } = props;
|
|
213
|
+
if (!editor || !editor.state?.doc) return null;
|
|
214
|
+
const hasValidNode = node !== void 0 && node !== null;
|
|
215
|
+
const hasValidPos = isValidPosition(nodePos);
|
|
216
|
+
if (!hasValidNode && !hasValidPos) return null;
|
|
217
|
+
if (hasValidNode) {
|
|
218
|
+
let foundPos = -1;
|
|
219
|
+
let foundNode = null;
|
|
220
|
+
editor.state.doc.descendants((currentNode, pos) => {
|
|
221
|
+
if (currentNode === node) {
|
|
222
|
+
foundPos = pos;
|
|
223
|
+
foundNode = currentNode;
|
|
224
|
+
return false;
|
|
225
|
+
}
|
|
226
|
+
return true;
|
|
227
|
+
});
|
|
228
|
+
if (foundPos !== -1 && foundNode !== null) return {
|
|
229
|
+
pos: foundPos,
|
|
230
|
+
node: foundNode
|
|
231
|
+
};
|
|
232
|
+
}
|
|
233
|
+
if (hasValidPos) {
|
|
234
|
+
const nodeAtPos = findNodeAtPosition(editor, nodePos);
|
|
235
|
+
if (nodeAtPos) return {
|
|
236
|
+
pos: nodePos,
|
|
237
|
+
node: nodeAtPos
|
|
238
|
+
};
|
|
239
|
+
}
|
|
240
|
+
return null;
|
|
241
|
+
}
|
|
242
|
+
/**
|
|
243
|
+
* Determines whether the current selection contains a node whose type matches
|
|
244
|
+
* any of the provided node type names.
|
|
245
|
+
* @param editor Tiptap editor instance
|
|
246
|
+
* @param nodeTypeNames List of node type names to match against
|
|
247
|
+
* @param checkAncestorNodes Whether to check ancestor node types up the depth chain
|
|
248
|
+
*/
|
|
249
|
+
function isNodeTypeSelected(editor, nodeTypeNames = [], checkAncestorNodes = false) {
|
|
250
|
+
if (!editor || !editor.state.selection) return false;
|
|
251
|
+
const { selection } = editor.state;
|
|
252
|
+
if (selection.empty) return false;
|
|
253
|
+
if (selection instanceof _tiptap_pm_state.NodeSelection) {
|
|
254
|
+
const selectedNode = selection.node;
|
|
255
|
+
return selectedNode ? nodeTypeNames.includes(selectedNode.type.name) : false;
|
|
256
|
+
}
|
|
257
|
+
if (checkAncestorNodes) {
|
|
258
|
+
const { $from } = selection;
|
|
259
|
+
for (let depth = $from.depth; depth > 0; depth--) {
|
|
260
|
+
const ancestorNode = $from.node(depth);
|
|
261
|
+
if (nodeTypeNames.includes(ancestorNode.type.name)) return true;
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
return false;
|
|
265
|
+
}
|
|
266
|
+
/**
|
|
267
|
+
* Check whether the current selection is fully within nodes
|
|
268
|
+
* whose type names are in the provided `types` list.
|
|
269
|
+
*
|
|
270
|
+
* - NodeSelection → checks the selected node.
|
|
271
|
+
* - Text/AllSelection → ensures all textblocks within [from, to) are allowed.
|
|
272
|
+
*/
|
|
273
|
+
function selectionWithinConvertibleTypes(editor, types = []) {
|
|
274
|
+
if (!editor || types.length === 0) return false;
|
|
275
|
+
const { state } = editor;
|
|
276
|
+
const { selection } = state;
|
|
277
|
+
const allowed = new Set(types);
|
|
278
|
+
if (selection instanceof _tiptap_pm_state.NodeSelection) {
|
|
279
|
+
const nodeType = selection.node?.type?.name;
|
|
280
|
+
return !!nodeType && allowed.has(nodeType);
|
|
281
|
+
}
|
|
282
|
+
if (selection instanceof _tiptap_pm_state.TextSelection || selection instanceof _tiptap_pm_state.AllSelection) {
|
|
283
|
+
let valid = true;
|
|
284
|
+
state.doc.nodesBetween(selection.from, selection.to, (node) => {
|
|
285
|
+
if (node.isTextblock && !allowed.has(node.type.name)) {
|
|
286
|
+
valid = false;
|
|
287
|
+
return false;
|
|
288
|
+
}
|
|
289
|
+
return valid;
|
|
290
|
+
});
|
|
291
|
+
return valid;
|
|
292
|
+
}
|
|
293
|
+
return false;
|
|
294
|
+
}
|
|
295
|
+
var ATTR_WHITESPACE = /[\u0000-\u0020\u00A0\u1680\u180E\u2000-\u2029\u205F\u3000]/g;
|
|
296
|
+
function isAllowedUri(uri, protocols) {
|
|
297
|
+
const allowedProtocols = [
|
|
298
|
+
"http",
|
|
299
|
+
"https",
|
|
300
|
+
"ftp",
|
|
301
|
+
"ftps",
|
|
302
|
+
"mailto",
|
|
303
|
+
"tel",
|
|
304
|
+
"callto",
|
|
305
|
+
"sms",
|
|
306
|
+
"cid",
|
|
307
|
+
"xmpp"
|
|
308
|
+
];
|
|
309
|
+
if (protocols) protocols.forEach((protocol) => {
|
|
310
|
+
const nextProtocol = typeof protocol === "string" ? protocol : protocol.scheme;
|
|
311
|
+
if (nextProtocol) allowedProtocols.push(nextProtocol);
|
|
312
|
+
});
|
|
313
|
+
return !uri || uri.replace(ATTR_WHITESPACE, "").match(new RegExp(`^(?:(?:${allowedProtocols.join("|")}):|[^a-z]|[a-z0-9+.\-]+(?:[^a-z+.\-:]|$))`, "i"));
|
|
314
|
+
}
|
|
315
|
+
function sanitizeUrl(inputUrl, baseUrl, protocols) {
|
|
316
|
+
try {
|
|
317
|
+
const url = new URL(inputUrl, baseUrl);
|
|
318
|
+
if (isAllowedUri(url.href, protocols)) return url.href;
|
|
319
|
+
} catch {}
|
|
320
|
+
return "#";
|
|
321
|
+
}
|
|
322
|
+
function getSelectedBlockNodes(editor) {
|
|
323
|
+
const { doc } = editor.state;
|
|
324
|
+
const { from, to } = editor.state.selection;
|
|
325
|
+
const blocks = [];
|
|
326
|
+
const seen = /* @__PURE__ */ new Set();
|
|
327
|
+
doc.nodesBetween(from, to, (node, pos) => {
|
|
328
|
+
if (!node.isBlock) return;
|
|
329
|
+
if (!seen.has(pos)) {
|
|
330
|
+
seen.add(pos);
|
|
331
|
+
blocks.push(node);
|
|
332
|
+
}
|
|
333
|
+
return false;
|
|
334
|
+
});
|
|
335
|
+
return blocks;
|
|
336
|
+
}
|
|
337
|
+
//#endregion
|
|
338
|
+
//#region src/tiptap/components/ui/blockquote-button/use-blockquote.ts
|
|
339
|
+
var BLOCKQUOTE_SHORTCUT_KEY = "mod+shift+b";
|
|
340
|
+
/**
|
|
341
|
+
* Checks if blockquote can be toggled in the current editor state
|
|
342
|
+
*/
|
|
343
|
+
function canToggleBlockquote(editor, turnInto = true) {
|
|
344
|
+
if (!editor || !editor.isEditable) return false;
|
|
345
|
+
if (!isNodeInSchema("blockquote", editor) || isNodeTypeSelected(editor, ["image"])) return false;
|
|
346
|
+
if (!turnInto) return editor.can().toggleWrap("blockquote");
|
|
347
|
+
if (!selectionWithinConvertibleTypes(editor, [
|
|
348
|
+
"paragraph",
|
|
349
|
+
"heading",
|
|
350
|
+
"bulletList",
|
|
351
|
+
"orderedList",
|
|
352
|
+
"taskList",
|
|
353
|
+
"blockquote",
|
|
354
|
+
"codeBlock"
|
|
355
|
+
])) return false;
|
|
356
|
+
return editor.can().toggleWrap("blockquote") || editor.can().clearNodes();
|
|
357
|
+
}
|
|
358
|
+
/**
|
|
359
|
+
* Toggles blockquote formatting for a specific node or the current selection
|
|
360
|
+
*/
|
|
361
|
+
function toggleBlockquote(editor) {
|
|
362
|
+
if (!editor || !editor.isEditable) return false;
|
|
363
|
+
if (!canToggleBlockquote(editor)) return false;
|
|
364
|
+
try {
|
|
365
|
+
const view = editor.view;
|
|
366
|
+
let state = view.state;
|
|
367
|
+
let tr = state.tr;
|
|
368
|
+
const blocks = getSelectedBlockNodes(editor);
|
|
369
|
+
const isPossibleToTurnInto = selectionWithinConvertibleTypes(editor, [
|
|
370
|
+
"paragraph",
|
|
371
|
+
"heading",
|
|
372
|
+
"bulletList",
|
|
373
|
+
"orderedList",
|
|
374
|
+
"taskList",
|
|
375
|
+
"blockquote",
|
|
376
|
+
"codeBlock"
|
|
377
|
+
]) && blocks.length === 1;
|
|
378
|
+
if ((state.selection.empty || state.selection instanceof _tiptap_pm_state.TextSelection) && isPossibleToTurnInto) {
|
|
379
|
+
const pos = findNodePosition({
|
|
380
|
+
editor,
|
|
381
|
+
node: state.selection.$anchor.node(1)
|
|
382
|
+
})?.pos;
|
|
383
|
+
if (!isValidPosition(pos)) return false;
|
|
384
|
+
tr = tr.setSelection(_tiptap_pm_state.NodeSelection.create(state.doc, pos));
|
|
385
|
+
view.dispatch(tr);
|
|
386
|
+
state = view.state;
|
|
387
|
+
}
|
|
388
|
+
const selection = state.selection;
|
|
389
|
+
let chain = editor.chain().focus();
|
|
390
|
+
if (selection instanceof _tiptap_pm_state.NodeSelection) {
|
|
391
|
+
const firstChild = selection.node.firstChild?.firstChild;
|
|
392
|
+
const lastChild = selection.node.lastChild?.lastChild;
|
|
393
|
+
const from = firstChild ? selection.from + firstChild.nodeSize : selection.from + 1;
|
|
394
|
+
const to = lastChild ? selection.to - lastChild.nodeSize : selection.to - 1;
|
|
395
|
+
const resolvedFrom = state.doc.resolve(from);
|
|
396
|
+
const resolvedTo = state.doc.resolve(to);
|
|
397
|
+
chain = chain.setTextSelection(_tiptap_pm_state.TextSelection.between(resolvedFrom, resolvedTo)).clearNodes();
|
|
398
|
+
}
|
|
399
|
+
(editor.isActive("blockquote") ? chain.lift("blockquote") : chain.wrapIn("blockquote")).run();
|
|
400
|
+
editor.chain().focus().selectTextblockEnd().run();
|
|
401
|
+
return true;
|
|
402
|
+
} catch {
|
|
403
|
+
return false;
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
/**
|
|
407
|
+
* Determines if the blockquote button should be shown
|
|
408
|
+
*/
|
|
409
|
+
function shouldShowButton$5(props) {
|
|
410
|
+
const { editor, hideWhenUnavailable } = props;
|
|
411
|
+
if (!editor) return false;
|
|
412
|
+
if (!hideWhenUnavailable) return true;
|
|
413
|
+
if (!editor.isEditable) return false;
|
|
414
|
+
if (!isNodeInSchema("blockquote", editor)) return false;
|
|
415
|
+
if (!editor.isActive("code")) return canToggleBlockquote(editor);
|
|
416
|
+
return true;
|
|
417
|
+
}
|
|
418
|
+
/**
|
|
419
|
+
* Custom hook that provides blockquote functionality for Tiptap editor
|
|
420
|
+
*
|
|
421
|
+
* @example
|
|
422
|
+
* ```tsx
|
|
423
|
+
* // Simple usage - no params needed
|
|
424
|
+
* function MySimpleBlockquoteButton() {
|
|
425
|
+
* const { isVisible, handleToggle, isActive } = useBlockquote()
|
|
426
|
+
*
|
|
427
|
+
* if (!isVisible) return null
|
|
428
|
+
*
|
|
429
|
+
* return <button onClick={handleToggle}>Blockquote</button>
|
|
430
|
+
* }
|
|
431
|
+
*
|
|
432
|
+
* // Advanced usage with configuration
|
|
433
|
+
* function MyAdvancedBlockquoteButton() {
|
|
434
|
+
* const { isVisible, handleToggle, label, isActive } = useBlockquote({
|
|
435
|
+
* editor: myEditor,
|
|
436
|
+
* hideWhenUnavailable: true,
|
|
437
|
+
* onToggled: () => console.log('Blockquote toggled!')
|
|
438
|
+
* })
|
|
439
|
+
*
|
|
440
|
+
* if (!isVisible) return null
|
|
441
|
+
*
|
|
442
|
+
* return (
|
|
443
|
+
* <MyButton
|
|
444
|
+
* onClick={handleToggle}
|
|
445
|
+
* aria-label={label}
|
|
446
|
+
* aria-pressed={isActive}
|
|
447
|
+
* >
|
|
448
|
+
* Toggle Blockquote
|
|
449
|
+
* </MyButton>
|
|
450
|
+
* )
|
|
451
|
+
* }
|
|
452
|
+
* ```
|
|
453
|
+
*/
|
|
454
|
+
function useBlockquote(config) {
|
|
455
|
+
const { editor: providedEditor, hideWhenUnavailable = false, onToggled } = config || {};
|
|
456
|
+
const { editor } = useTiptapEditor(providedEditor);
|
|
457
|
+
const [isVisible, setIsVisible] = (0, react.useState)(true);
|
|
458
|
+
const canToggle = canToggleBlockquote(editor);
|
|
459
|
+
const isActive = editor?.isActive("blockquote") || false;
|
|
460
|
+
(0, react.useEffect)(() => {
|
|
461
|
+
if (!editor) return;
|
|
462
|
+
const handleSelectionUpdate = () => {
|
|
463
|
+
setIsVisible(shouldShowButton$5({
|
|
464
|
+
editor,
|
|
465
|
+
hideWhenUnavailable
|
|
466
|
+
}));
|
|
467
|
+
};
|
|
468
|
+
handleSelectionUpdate();
|
|
469
|
+
editor.on("selectionUpdate", handleSelectionUpdate);
|
|
470
|
+
return () => {
|
|
471
|
+
editor.off("selectionUpdate", handleSelectionUpdate);
|
|
472
|
+
};
|
|
473
|
+
}, [editor, hideWhenUnavailable]);
|
|
474
|
+
return {
|
|
475
|
+
isVisible,
|
|
476
|
+
isActive,
|
|
477
|
+
handleToggle: (0, react.useCallback)(() => {
|
|
478
|
+
if (!editor) return false;
|
|
479
|
+
const success = toggleBlockquote(editor);
|
|
480
|
+
if (success) onToggled?.();
|
|
481
|
+
return success;
|
|
482
|
+
}, [editor, onToggled]),
|
|
483
|
+
canToggle,
|
|
484
|
+
label: "Citation en bloc",
|
|
485
|
+
shortcutKeys: BLOCKQUOTE_SHORTCUT_KEY,
|
|
486
|
+
Icon: TextSnippetIcon
|
|
487
|
+
};
|
|
488
|
+
}
|
|
489
|
+
//#endregion
|
|
490
|
+
//#region src/tiptap/components/ui-primitive/button/button.tsx
|
|
491
|
+
var ShortcutDisplay = ({ shortcuts }) => {
|
|
492
|
+
if (shortcuts.length === 0) return null;
|
|
493
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", { children: shortcuts.map((key, index) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react.Fragment, { children: [index > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("kbd", { children: "+" }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("kbd", { children: key })] }, index)) });
|
|
494
|
+
};
|
|
495
|
+
var Button = (0, react.forwardRef)(({ className, children, tooltip, showTooltip = true, shortcutKeys, ...props }, ref) => {
|
|
496
|
+
const shortcuts = (0, react.useMemo)(() => parseShortcutKeys({ shortcutKeys }), [shortcutKeys]);
|
|
497
|
+
if (!tooltip || !showTooltip) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_mui_material_ToggleButton.default, {
|
|
498
|
+
size: "small",
|
|
499
|
+
color: "primary",
|
|
500
|
+
ref,
|
|
501
|
+
sx: {
|
|
502
|
+
border: "none",
|
|
503
|
+
"&:disabled": { border: "none" }
|
|
504
|
+
},
|
|
505
|
+
...props,
|
|
506
|
+
children
|
|
507
|
+
});
|
|
508
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_mui_material_Tooltip.default, {
|
|
509
|
+
enterDelay: 200,
|
|
510
|
+
title: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [tooltip, /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ShortcutDisplay, { shortcuts })] }),
|
|
511
|
+
slotProps: { popper: { modifiers: [{
|
|
512
|
+
name: "offset",
|
|
513
|
+
options: { offset: [0, -8] }
|
|
514
|
+
}] } },
|
|
515
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_mui_material_ToggleButton.default, {
|
|
516
|
+
size: "small",
|
|
517
|
+
color: "primary",
|
|
518
|
+
ref,
|
|
519
|
+
sx: {
|
|
520
|
+
border: "none",
|
|
521
|
+
"&:disabled": { border: "none" }
|
|
522
|
+
},
|
|
523
|
+
...props,
|
|
524
|
+
children
|
|
525
|
+
})
|
|
526
|
+
});
|
|
527
|
+
});
|
|
528
|
+
Button.displayName = "Button";
|
|
529
|
+
//#endregion
|
|
530
|
+
//#region src/tiptap/components/ui/blockquote-button/blockquote-button.tsx
|
|
531
|
+
function BlockquoteShortcutBadge({ shortcutKeys = BLOCKQUOTE_SHORTCUT_KEY }) {
|
|
532
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_mui_material_Badge.default, { children: parseShortcutKeys({ shortcutKeys }) });
|
|
533
|
+
}
|
|
534
|
+
/**
|
|
535
|
+
* Button component for toggling blockquote in a Tiptap editor.
|
|
536
|
+
*
|
|
537
|
+
* For custom button implementations, use the `useBlockquote` hook instead.
|
|
538
|
+
*/
|
|
539
|
+
var BlockquoteButton = (0, react.forwardRef)(({ editor: providedEditor, text, hideWhenUnavailable = false, onToggled, showShortcut = false, onClick, children, ...buttonProps }, ref) => {
|
|
540
|
+
const { editor } = useTiptapEditor(providedEditor);
|
|
541
|
+
const { isVisible, canToggle, isActive, handleToggle, label, shortcutKeys, Icon } = useBlockquote({
|
|
542
|
+
editor,
|
|
543
|
+
hideWhenUnavailable,
|
|
544
|
+
onToggled
|
|
545
|
+
});
|
|
546
|
+
const handleClick = (0, react.useCallback)((event) => {
|
|
547
|
+
onClick?.(event, !isActive);
|
|
548
|
+
if (event.defaultPrevented) return;
|
|
549
|
+
handleToggle();
|
|
550
|
+
}, [handleToggle, onClick]);
|
|
551
|
+
if (!isVisible) return null;
|
|
552
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Button, {
|
|
553
|
+
value: "blockquote",
|
|
554
|
+
type: "button",
|
|
555
|
+
selected: isActive,
|
|
556
|
+
role: "button",
|
|
557
|
+
tabIndex: -1,
|
|
558
|
+
disabled: !canToggle,
|
|
559
|
+
"data-disabled": !canToggle,
|
|
560
|
+
"aria-label": label,
|
|
561
|
+
tooltip: label,
|
|
562
|
+
onClick: handleClick,
|
|
563
|
+
...buttonProps,
|
|
564
|
+
ref,
|
|
565
|
+
children: children ?? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
|
|
566
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Icon, { fontSize: "small" }),
|
|
567
|
+
text && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
568
|
+
className: "tiptap-button-text",
|
|
569
|
+
children: text
|
|
570
|
+
}),
|
|
571
|
+
showShortcut && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(BlockquoteShortcutBadge, { shortcutKeys })
|
|
572
|
+
] })
|
|
573
|
+
});
|
|
574
|
+
});
|
|
575
|
+
BlockquoteButton.displayName = "BlockquoteButton";
|
|
576
|
+
//#endregion
|
|
577
|
+
//#region src/tiptap/components/icons/integration-instructions-icon.tsx
|
|
578
|
+
var IntegrationInstructionIcon = (0, _mui_material_utils.createSvgIcon)(/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("g", { children: [
|
|
579
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("rect", {
|
|
580
|
+
fill: "none",
|
|
581
|
+
height: "24",
|
|
582
|
+
width: "24"
|
|
583
|
+
}),
|
|
584
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("circle", {
|
|
585
|
+
cx: "12",
|
|
586
|
+
cy: "3.5",
|
|
587
|
+
fill: "none",
|
|
588
|
+
r: ".75"
|
|
589
|
+
}),
|
|
590
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("circle", {
|
|
591
|
+
cx: "12",
|
|
592
|
+
cy: "3.5",
|
|
593
|
+
fill: "none",
|
|
594
|
+
r: ".75"
|
|
595
|
+
}),
|
|
596
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("polygon", {
|
|
597
|
+
fill: "none",
|
|
598
|
+
points: "5,15 5,16 5,19 19,19 19,16 19,15 19,5 5,5"
|
|
599
|
+
}),
|
|
600
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("g", { children: [
|
|
601
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("polygon", { points: "11,14.17 8.83,12 11,9.83 9.59,8.41 6,12 9.59,15.59" }),
|
|
602
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("polygon", { points: "14.41,15.59 18,12 14.41,8.41 13,9.83 15.17,12 13,14.17" }),
|
|
603
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", { d: "M19,3h-4.18C14.4,1.84,13.3,1,12,1S9.6,1.84,9.18,3H5C4.86,3,4.73,3.01,4.6,3.04C4.21,3.12,3.86,3.32,3.59,3.59 c-0.18,0.18-0.33,0.4-0.43,0.64C3.06,4.46,3,4.72,3,5v10v1v3c0,0.27,0.06,0.54,0.16,0.78c0.1,0.24,0.25,0.45,0.43,0.64 c0.27,0.27,0.62,0.47,1.01,0.55C4.73,20.99,4.86,21,5,21h14c1.1,0,2-0.9,2-2v-3v-1V5C21,3.9,20.1,3,19,3z M12,2.75 c0.41,0,0.75,0.34,0.75,0.75S12.41,4.25,12,4.25s-0.75-0.34-0.75-0.75S11.59,2.75,12,2.75z M19,15v1v3H5v-3v-1V5h14V15z" })
|
|
604
|
+
] })
|
|
605
|
+
] }), "IntegrationInstruction");
|
|
606
|
+
//#endregion
|
|
607
|
+
//#region src/tiptap/components/ui/code-block-button/use-code-block.ts
|
|
608
|
+
var CODE_BLOCK_SHORTCUT_KEY = "mod+alt+c";
|
|
609
|
+
/**
|
|
610
|
+
* Checks if code block can be toggled in the current editor state
|
|
611
|
+
*/
|
|
612
|
+
function canToggle$1(editor, turnInto = true) {
|
|
613
|
+
if (!editor || !editor.isEditable) return false;
|
|
614
|
+
if (!isNodeInSchema("codeBlock", editor) || isNodeTypeSelected(editor, ["image"])) return false;
|
|
615
|
+
if (!turnInto) return editor.can().toggleNode("codeBlock", "paragraph");
|
|
616
|
+
if (!selectionWithinConvertibleTypes(editor, [
|
|
617
|
+
"paragraph",
|
|
618
|
+
"heading",
|
|
619
|
+
"bulletList",
|
|
620
|
+
"orderedList",
|
|
621
|
+
"taskList",
|
|
622
|
+
"blockquote",
|
|
623
|
+
"codeBlock"
|
|
624
|
+
])) return false;
|
|
625
|
+
return editor.can().toggleNode("codeBlock", "paragraph") || editor.can().clearNodes();
|
|
626
|
+
}
|
|
627
|
+
/**
|
|
628
|
+
* Toggles code block in the editor
|
|
629
|
+
*/
|
|
630
|
+
function toggleCodeBlock(editor) {
|
|
631
|
+
if (!editor || !editor.isEditable) return false;
|
|
632
|
+
if (!canToggle$1(editor)) return false;
|
|
633
|
+
try {
|
|
634
|
+
const view = editor.view;
|
|
635
|
+
let state = view.state;
|
|
636
|
+
let tr = state.tr;
|
|
637
|
+
const blocks = getSelectedBlockNodes(editor);
|
|
638
|
+
const isPossibleToTurnInto = selectionWithinConvertibleTypes(editor, [
|
|
639
|
+
"paragraph",
|
|
640
|
+
"heading",
|
|
641
|
+
"bulletList",
|
|
642
|
+
"orderedList",
|
|
643
|
+
"taskList",
|
|
644
|
+
"blockquote",
|
|
645
|
+
"codeBlock"
|
|
646
|
+
]) && blocks.length === 1;
|
|
647
|
+
if ((state.selection.empty || state.selection instanceof _tiptap_pm_state.TextSelection) && isPossibleToTurnInto) {
|
|
648
|
+
const pos = findNodePosition({
|
|
649
|
+
editor,
|
|
650
|
+
node: state.selection.$anchor.node(1)
|
|
651
|
+
})?.pos;
|
|
652
|
+
if (!isValidPosition(pos)) return false;
|
|
653
|
+
tr = tr.setSelection(_tiptap_pm_state.NodeSelection.create(state.doc, pos));
|
|
654
|
+
view.dispatch(tr);
|
|
655
|
+
state = view.state;
|
|
656
|
+
}
|
|
657
|
+
const selection = state.selection;
|
|
658
|
+
let chain = editor.chain().focus();
|
|
659
|
+
if (selection instanceof _tiptap_pm_state.NodeSelection) {
|
|
660
|
+
const firstChild = selection.node.firstChild?.firstChild;
|
|
661
|
+
const lastChild = selection.node.lastChild?.lastChild;
|
|
662
|
+
const from = firstChild ? selection.from + firstChild.nodeSize : selection.from + 1;
|
|
663
|
+
const to = lastChild ? selection.to - lastChild.nodeSize : selection.to - 1;
|
|
664
|
+
const resolvedFrom = state.doc.resolve(from);
|
|
665
|
+
const resolvedTo = state.doc.resolve(to);
|
|
666
|
+
chain = chain.setTextSelection(_tiptap_pm_state.TextSelection.between(resolvedFrom, resolvedTo)).clearNodes();
|
|
667
|
+
}
|
|
668
|
+
(editor.isActive("codeBlock") ? chain.setNode("paragraph") : chain.toggleNode("codeBlock", "paragraph")).run();
|
|
669
|
+
editor.chain().focus().selectTextblockEnd().run();
|
|
670
|
+
return true;
|
|
671
|
+
} catch {
|
|
672
|
+
return false;
|
|
673
|
+
}
|
|
674
|
+
}
|
|
675
|
+
/**
|
|
676
|
+
* Determines if the code block button should be shown
|
|
677
|
+
*/
|
|
678
|
+
function shouldShowButton$4(props) {
|
|
679
|
+
const { editor, hideWhenUnavailable } = props;
|
|
680
|
+
if (!editor) return false;
|
|
681
|
+
if (!hideWhenUnavailable) return true;
|
|
682
|
+
if (!editor.isEditable) return false;
|
|
683
|
+
if (!isNodeInSchema("codeBlock", editor)) return false;
|
|
684
|
+
if (!editor.isActive("code")) return canToggle$1(editor);
|
|
685
|
+
return true;
|
|
686
|
+
}
|
|
687
|
+
/**
|
|
688
|
+
* Custom hook that provides code block functionality for Tiptap editor
|
|
689
|
+
*
|
|
690
|
+
* @example
|
|
691
|
+
* ```tsx
|
|
692
|
+
* // Simple usage - no params needed
|
|
693
|
+
* function MySimpleCodeBlockButton() {
|
|
694
|
+
* const { isVisible, isActive, handleToggle } = useCodeBlock()
|
|
695
|
+
*
|
|
696
|
+
* if (!isVisible) return null
|
|
697
|
+
*
|
|
698
|
+
* return (
|
|
699
|
+
* <button
|
|
700
|
+
* onClick={handleToggle}
|
|
701
|
+
* aria-pressed={isActive}
|
|
702
|
+
* >
|
|
703
|
+
* Code Block
|
|
704
|
+
* </button>
|
|
705
|
+
* )
|
|
706
|
+
* }
|
|
707
|
+
*
|
|
708
|
+
* // Advanced usage with configuration
|
|
709
|
+
* function MyAdvancedCodeBlockButton() {
|
|
710
|
+
* const { isVisible, isActive, handleToggle, label } = useCodeBlock({
|
|
711
|
+
* editor: myEditor,
|
|
712
|
+
* hideWhenUnavailable: true,
|
|
713
|
+
* onToggled: (isActive) => console.log('Code block toggled:', isActive)
|
|
714
|
+
* })
|
|
715
|
+
*
|
|
716
|
+
* if (!isVisible) return null
|
|
717
|
+
*
|
|
718
|
+
* return (
|
|
719
|
+
* <MyButton
|
|
720
|
+
* onClick={handleToggle}
|
|
721
|
+
* aria-label={label}
|
|
722
|
+
* aria-pressed={isActive}
|
|
723
|
+
* >
|
|
724
|
+
* Toggle Code Block
|
|
725
|
+
* </MyButton>
|
|
726
|
+
* )
|
|
727
|
+
* }
|
|
728
|
+
* ```
|
|
729
|
+
*/
|
|
730
|
+
function useCodeBlock(config) {
|
|
731
|
+
const { editor: providedEditor, hideWhenUnavailable = false, onToggled } = config || {};
|
|
732
|
+
const { editor } = useTiptapEditor(providedEditor);
|
|
733
|
+
const [isVisible, setIsVisible] = (0, react.useState)(true);
|
|
734
|
+
const canToggleState = canToggle$1(editor);
|
|
735
|
+
const isActive = editor?.isActive("codeBlock") || false;
|
|
736
|
+
(0, react.useEffect)(() => {
|
|
737
|
+
if (!editor) return;
|
|
738
|
+
const handleSelectionUpdate = () => {
|
|
739
|
+
setIsVisible(shouldShowButton$4({
|
|
740
|
+
editor,
|
|
741
|
+
hideWhenUnavailable
|
|
742
|
+
}));
|
|
743
|
+
};
|
|
744
|
+
handleSelectionUpdate();
|
|
745
|
+
editor.on("selectionUpdate", handleSelectionUpdate);
|
|
746
|
+
return () => {
|
|
747
|
+
editor.off("selectionUpdate", handleSelectionUpdate);
|
|
748
|
+
};
|
|
749
|
+
}, [editor, hideWhenUnavailable]);
|
|
750
|
+
return {
|
|
751
|
+
isVisible,
|
|
752
|
+
isActive,
|
|
753
|
+
handleToggle: (0, react.useCallback)(() => {
|
|
754
|
+
if (!editor) return false;
|
|
755
|
+
const success = toggleCodeBlock(editor);
|
|
756
|
+
if (success) onToggled?.();
|
|
757
|
+
return success;
|
|
758
|
+
}, [editor, onToggled]),
|
|
759
|
+
canToggle: canToggleState,
|
|
760
|
+
label: "Bloc de code",
|
|
761
|
+
shortcutKeys: CODE_BLOCK_SHORTCUT_KEY,
|
|
762
|
+
Icon: IntegrationInstructionIcon
|
|
763
|
+
};
|
|
764
|
+
}
|
|
765
|
+
//#endregion
|
|
766
|
+
//#region src/tiptap/components/ui/code-block-button/code-block-button.tsx
|
|
767
|
+
function CodeBlockShortcutBadge({ shortcutKeys = CODE_BLOCK_SHORTCUT_KEY }) {
|
|
768
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_mui_material_Badge.default, { children: parseShortcutKeys({ shortcutKeys }) });
|
|
769
|
+
}
|
|
770
|
+
/**
|
|
771
|
+
* Button component for toggling code block in a Tiptap editor.
|
|
772
|
+
*
|
|
773
|
+
* For custom button implementations, use the `useCodeBlock` hook instead.
|
|
774
|
+
*/
|
|
775
|
+
var CodeBlockButton = (0, react.forwardRef)(({ editor: providedEditor, text, hideWhenUnavailable = false, onToggled, showShortcut = false, onClick, children, ...buttonProps }, ref) => {
|
|
776
|
+
const { editor } = useTiptapEditor(providedEditor);
|
|
777
|
+
const { isVisible, canToggle, isActive, handleToggle, label, shortcutKeys, Icon } = useCodeBlock({
|
|
778
|
+
editor,
|
|
779
|
+
hideWhenUnavailable,
|
|
780
|
+
onToggled
|
|
781
|
+
});
|
|
782
|
+
const handleClick = (0, react.useCallback)((event) => {
|
|
783
|
+
onClick?.(event, !isActive);
|
|
784
|
+
if (event.defaultPrevented) return;
|
|
785
|
+
handleToggle();
|
|
786
|
+
}, [handleToggle, onClick]);
|
|
787
|
+
if (!isVisible) return null;
|
|
788
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Button, {
|
|
789
|
+
value: "codeBlock",
|
|
790
|
+
type: "button",
|
|
791
|
+
selected: isActive,
|
|
792
|
+
role: "button",
|
|
793
|
+
disabled: !canToggle,
|
|
794
|
+
"data-disabled": !canToggle,
|
|
795
|
+
tabIndex: -1,
|
|
796
|
+
"aria-label": label,
|
|
797
|
+
tooltip: label,
|
|
798
|
+
onClick: handleClick,
|
|
799
|
+
...buttonProps,
|
|
800
|
+
ref,
|
|
801
|
+
children: children ?? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
|
|
802
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Icon, { fontSize: "small" }),
|
|
803
|
+
text && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
804
|
+
className: "tiptap-button-text",
|
|
805
|
+
children: text
|
|
806
|
+
}),
|
|
807
|
+
showShortcut && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(CodeBlockShortcutBadge, { shortcutKeys })
|
|
808
|
+
] })
|
|
809
|
+
});
|
|
810
|
+
});
|
|
811
|
+
CodeBlockButton.displayName = "CodeBlockButton";
|
|
812
|
+
//#endregion
|
|
813
|
+
//#region ../../node_modules/.pnpm/@mui+icons-material@9.4.0_@mui+material@9.4.0_@emotion+react@11.14.0_@types+react@19.2._02cb1dd638d2ccdea7d16c50ea4ea174/node_modules/@mui/icons-material/ExpandMoreRounded.mjs
|
|
814
|
+
var ExpandMoreRounded_default = (0, _mui_material_SvgIcon.createSvgIcon)(/*#__PURE__*/ (0, react_jsx_runtime.jsx)("path", { d: "M15.88 9.29 12 13.17 8.12 9.29a.996.996 0 0 0-1.41 0c-.39.39-.39 1.02 0 1.41l4.59 4.59c.39.39 1.02.39 1.41 0l4.59-4.59c.39-.39.39-1.02 0-1.41-.39-.38-1.03-.39-1.42 0" }), "ExpandMoreRounded");
|
|
815
|
+
//#endregion
|
|
816
|
+
//#region src/tiptap/components/icons/heading-five-icon.tsx
|
|
817
|
+
var HeadingFiveIcon = (0, _mui_material_utils.createSvgIcon)(/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("g", { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", {
|
|
818
|
+
d: "M5 6C5 5.44772 4.55228 5 4 5C3.44772 5 3 5.44772 3 6V18C3 18.5523 3.44772 19 4 19C4.55228 19 5 18.5523 5 18V13H11V18C11 18.5523 11.4477 19 12 19C12.5523 19 13 18.5523 13 18V6C13 5.44772 12.5523 5 12 5C11.4477 5 11 5.44772 11 6V11H5V6Z",
|
|
819
|
+
fill: "currentColor"
|
|
820
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", {
|
|
821
|
+
d: "M16 10C16 9.44772 16.4477 9 17 9H21C21.5523 9 22 9.44772 22 10C22 10.5523 21.5523 11 21 11H18V12H18.3C20.2754 12 22 13.4739 22 15.5C22 17.5261 20.2754 19 18.3 19C17.6457 19 17.0925 18.8643 16.5528 18.5944C16.0588 18.3474 15.8586 17.7468 16.1055 17.2528C16.3525 16.7588 16.9532 16.5586 17.4472 16.8056C17.7074 16.9357 17.9542 17 18.3 17C19.3246 17 20 16.2739 20 15.5C20 14.7261 19.3246 14 18.3 14H17C16.4477 14 16 13.5523 16 13L16 12.9928V10Z",
|
|
822
|
+
fill: "currentColor"
|
|
823
|
+
})] }), "HeadingFiveIcon");
|
|
824
|
+
//#endregion
|
|
825
|
+
//#region src/tiptap/components/icons/heading-four-icon.tsx
|
|
826
|
+
var HeadingFourIcon = (0, _mui_material_utils.createSvgIcon)(/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("g", { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", {
|
|
827
|
+
d: "M4 5C4.55228 5 5 5.44772 5 6V11H11V6C11 5.44772 11.4477 5 12 5C12.5523 5 13 5.44772 13 6V18C13 18.5523 12.5523 19 12 19C11.4477 19 11 18.5523 11 18V13H5V18C5 18.5523 4.55228 19 4 19C3.44772 19 3 18.5523 3 18V6C3 5.44772 3.44772 5 4 5Z",
|
|
828
|
+
fill: "currentColor"
|
|
829
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", {
|
|
830
|
+
d: "M17 9C17.5523 9 18 9.44772 18 10V13H20V10C20 9.44772 20.4477 9 21 9C21.5523 9 22 9.44772 22 10V18C22 18.5523 21.5523 19 21 19C20.4477 19 20 18.5523 20 18V15H17C16.4477 15 16 14.5523 16 14V10C16 9.44772 16.4477 9 17 9Z",
|
|
831
|
+
fill: "currentColor"
|
|
832
|
+
})] }), "HeadingFour");
|
|
833
|
+
//#endregion
|
|
834
|
+
//#region src/tiptap/components/icons/heading-one-icon.tsx
|
|
835
|
+
var HeadingOneIcon = (0, _mui_material_utils.createSvgIcon)(/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("g", { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", {
|
|
836
|
+
d: "M5 6C5 5.44772 4.55228 5 4 5C3.44772 5 3 5.44772 3 6V18C3 18.5523 3.44772 19 4 19C4.55228 19 5 18.5523 5 18V13H11V18C11 18.5523 11.4477 19 12 19C12.5523 19 13 18.5523 13 18V6C13 5.44772 12.5523 5 12 5C11.4477 5 11 5.44772 11 6V11H5V6Z",
|
|
837
|
+
fill: "currentColor"
|
|
838
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", {
|
|
839
|
+
d: "M21.0001 10C21.0001 9.63121 20.7971 9.29235 20.472 9.11833C20.1468 8.94431 19.7523 8.96338 19.4454 9.16795L16.4454 11.168C15.9859 11.4743 15.8617 12.0952 16.1681 12.5547C16.4744 13.0142 17.0953 13.1384 17.5548 12.8321L19.0001 11.8685V18C19.0001 18.5523 19.4478 19 20.0001 19C20.5524 19 21.0001 18.5523 21.0001 18V10Z",
|
|
840
|
+
fill: "currentColor"
|
|
841
|
+
})] }), "HeadingOne");
|
|
842
|
+
//#endregion
|
|
843
|
+
//#region src/tiptap/components/icons/heading-six-icon.tsx
|
|
844
|
+
var HeadingSixIcon = (0, _mui_material_utils.createSvgIcon)(/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("g", { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", {
|
|
845
|
+
d: "M5 6C5 5.44772 4.55228 5 4 5C3.44772 5 3 5.44772 3 6V18C3 18.5523 3.44772 19 4 19C4.55228 19 5 18.5523 5 18V13H11V18C11 18.5523 11.4477 19 12 19C12.5523 19 13 18.5523 13 18V6C13 5.44772 12.5523 5 12 5C11.4477 5 11 5.44772 11 6V11H5V6Z",
|
|
846
|
+
fill: "currentColor"
|
|
847
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", {
|
|
848
|
+
fillRule: "evenodd",
|
|
849
|
+
clipRule: "evenodd",
|
|
850
|
+
d: "M20.7071 9.29289C21.0976 9.68342 21.0976 10.3166 20.7071 10.7071C19.8392 11.575 19.2179 12.2949 18.7889 13.0073C18.8587 13.0025 18.929 13 19 13C20.6569 13 22 14.3431 22 16C22 17.6569 20.6569 19 19 19C17.3431 19 16 17.6569 16 16C16 14.6007 16.2837 13.4368 16.8676 12.3419C17.4384 11.2717 18.2728 10.3129 19.2929 9.29289C19.6834 8.90237 20.3166 8.90237 20.7071 9.29289ZM19 17C18.4477 17 18 16.5523 18 16C18 15.4477 18.4477 15 19 15C19.5523 15 20 15.4477 20 16C20 16.5523 19.5523 17 19 17Z",
|
|
851
|
+
fill: "currentColor"
|
|
852
|
+
})] }), "HeadingSix");
|
|
853
|
+
//#endregion
|
|
854
|
+
//#region src/tiptap/components/icons/heading-three-icon.tsx
|
|
855
|
+
var HeadingThreeIcon = (0, _mui_material_utils.createSvgIcon)(/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("g", { children: [
|
|
856
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", {
|
|
857
|
+
d: "M4 5C4.55228 5 5 5.44772 5 6V11H11V6C11 5.44772 11.4477 5 12 5C12.5523 5 13 5.44772 13 6V18C13 18.5523 12.5523 19 12 19C11.4477 19 11 18.5523 11 18V13H5V18C5 18.5523 4.55228 19 4 19C3.44772 19 3 18.5523 3 18V6C3 5.44772 3.44772 5 4 5Z",
|
|
858
|
+
fill: "currentColor"
|
|
859
|
+
}),
|
|
860
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", {
|
|
861
|
+
fillRule: "evenodd",
|
|
862
|
+
clipRule: "evenodd",
|
|
863
|
+
d: "M19.4608 11.2169C19.1135 11.0531 18.5876 11.0204 18.0069 11.3619C17.5309 11.642 16.918 11.4831 16.638 11.007C16.358 10.531 16.5169 9.91809 16.9929 9.63807C18.1123 8.97962 19.3364 8.94691 20.314 9.40808C21.2839 9.86558 21.9999 10.818 21.9999 12C21.9999 12.7957 21.6838 13.5587 21.1212 14.1213C20.5586 14.6839 19.7956 15 18.9999 15C18.4476 15 17.9999 14.5523 17.9999 14C17.9999 13.4477 18.4476 13 18.9999 13C19.2651 13 19.5195 12.8947 19.707 12.7071C19.8946 12.5196 19.9999 12.2652 19.9999 12C19.9999 11.6821 19.8159 11.3844 19.4608 11.2169Z",
|
|
864
|
+
fill: "currentColor"
|
|
865
|
+
}),
|
|
866
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", {
|
|
867
|
+
fillRule: "evenodd",
|
|
868
|
+
clipRule: "evenodd",
|
|
869
|
+
d: "M18.0001 14C18.0001 13.4477 18.4478 13 19.0001 13C19.7957 13 20.5588 13.3161 21.1214 13.8787C21.684 14.4413 22.0001 15.2043 22.0001 16C22.0001 17.2853 21.2767 18.3971 20.1604 18.8994C19.0257 19.41 17.642 19.2315 16.4001 18.3C15.9582 17.9686 15.8687 17.3418 16.2001 16.9C16.5314 16.4582 17.1582 16.3686 17.6001 16.7C18.3581 17.2685 18.9744 17.24 19.3397 17.0756C19.7234 16.9029 20.0001 16.5147 20.0001 16C20.0001 15.7348 19.8947 15.4804 19.7072 15.2929C19.5196 15.1054 19.2653 15 19.0001 15C18.4478 15 18.0001 14.5523 18.0001 14Z",
|
|
870
|
+
fill: "currentColor"
|
|
871
|
+
})
|
|
872
|
+
] }), "HeadingThree");
|
|
873
|
+
//#endregion
|
|
874
|
+
//#region src/tiptap/components/ui/heading-menu-item/use-heading.ts
|
|
875
|
+
var headingIcons = {
|
|
876
|
+
1: HeadingOneIcon,
|
|
877
|
+
2: (0, _mui_material_utils.createSvgIcon)(/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("g", { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", {
|
|
878
|
+
d: "M5 6C5 5.44772 4.55228 5 4 5C3.44772 5 3 5.44772 3 6V18C3 18.5523 3.44772 19 4 19C4.55228 19 5 18.5523 5 18V13H11V18C11 18.5523 11.4477 19 12 19C12.5523 19 13 18.5523 13 18V6C13 5.44772 12.5523 5 12 5C11.4477 5 11 5.44772 11 6V11H5V6Z",
|
|
879
|
+
fill: "currentColor"
|
|
880
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", {
|
|
881
|
+
d: "M22.0001 12C22.0001 10.7611 21.1663 9.79297 20.0663 9.42632C18.9547 9.05578 17.6171 9.28724 16.4001 10.2C15.9582 10.5314 15.8687 11.1582 16.2001 11.6C16.5314 12.0418 17.1582 12.1314 17.6001 11.8C18.383 11.2128 19.0455 11.1942 19.4338 11.3237C19.8339 11.457 20.0001 11.7389 20.0001 12C20.0001 12.4839 19.8554 12.7379 19.6537 12.9481C19.4275 13.1837 19.1378 13.363 18.7055 13.6307C18.6313 13.6767 18.553 13.7252 18.4701 13.777C17.9572 14.0975 17.3128 14.5261 16.8163 15.2087C16.3007 15.9177 16.0001 16.8183 16.0001 18C16.0001 18.5523 16.4478 19 17.0001 19H21.0001C21.5523 19 22.0001 18.5523 22.0001 18C22.0001 17.4477 21.5523 17 21.0001 17H18.131C18.21 16.742 18.3176 16.5448 18.4338 16.385C18.6873 16.0364 19.0429 15.7775 19.5301 15.473C19.5898 15.4357 19.6536 15.3966 19.7205 15.3556C20.139 15.0992 20.6783 14.7687 21.0964 14.3332C21.6447 13.7621 22.0001 13.0161 22.0001 12Z",
|
|
882
|
+
fill: "currentColor"
|
|
883
|
+
})] }), "HeadingTwo"),
|
|
884
|
+
3: HeadingThreeIcon,
|
|
885
|
+
4: HeadingFourIcon,
|
|
886
|
+
5: HeadingFiveIcon,
|
|
887
|
+
6: HeadingSixIcon
|
|
888
|
+
};
|
|
889
|
+
var HEADING_SHORTCUT_KEYS = {
|
|
890
|
+
1: "ctrl+alt+1",
|
|
891
|
+
2: "ctrl+alt+2",
|
|
892
|
+
3: "ctrl+alt+3",
|
|
893
|
+
4: "ctrl+alt+4",
|
|
894
|
+
5: "ctrl+alt+5",
|
|
895
|
+
6: "ctrl+alt+6"
|
|
896
|
+
};
|
|
897
|
+
/**
|
|
898
|
+
* Checks if heading can be toggled in the current editor state
|
|
899
|
+
*/
|
|
900
|
+
function canToggle(editor, level, turnInto = true) {
|
|
901
|
+
if (!editor || !editor.isEditable) return false;
|
|
902
|
+
if (!isNodeInSchema("heading", editor) || isNodeTypeSelected(editor, ["image"])) return false;
|
|
903
|
+
if (!turnInto) return level ? editor.can().setNode("heading", { level }) : editor.can().setNode("heading");
|
|
904
|
+
if (!selectionWithinConvertibleTypes(editor, [
|
|
905
|
+
"paragraph",
|
|
906
|
+
"heading",
|
|
907
|
+
"bulletList",
|
|
908
|
+
"orderedList",
|
|
909
|
+
"taskList",
|
|
910
|
+
"blockquote",
|
|
911
|
+
"codeBlock"
|
|
912
|
+
])) return false;
|
|
913
|
+
return level ? editor.can().setNode("heading", { level }) || editor.can().clearNodes() : editor.can().setNode("heading") || editor.can().clearNodes();
|
|
914
|
+
}
|
|
915
|
+
/**
|
|
916
|
+
* Checks if heading is currently active
|
|
917
|
+
*/
|
|
918
|
+
function isHeadingActive(editor, level) {
|
|
919
|
+
if (!editor || !editor.isEditable) return false;
|
|
920
|
+
if (Array.isArray(level)) return level.some((l) => editor.isActive("heading", { level: l }));
|
|
921
|
+
return level ? editor.isActive("heading", { level }) : editor.isActive("heading");
|
|
922
|
+
}
|
|
923
|
+
/**
|
|
924
|
+
* Toggles heading in the editor
|
|
925
|
+
*/
|
|
926
|
+
function toggleHeading(editor, level) {
|
|
927
|
+
if (!editor || !editor.isEditable) return false;
|
|
928
|
+
const levels = Array.isArray(level) ? level : [level];
|
|
929
|
+
const toggleLevel = levels.find((l) => canToggle(editor, l));
|
|
930
|
+
if (!toggleLevel) return false;
|
|
931
|
+
try {
|
|
932
|
+
const view = editor.view;
|
|
933
|
+
let state = view.state;
|
|
934
|
+
let tr = state.tr;
|
|
935
|
+
const blocks = getSelectedBlockNodes(editor);
|
|
936
|
+
const isPossibleToTurnInto = selectionWithinConvertibleTypes(editor, [
|
|
937
|
+
"paragraph",
|
|
938
|
+
"heading",
|
|
939
|
+
"bulletList",
|
|
940
|
+
"orderedList",
|
|
941
|
+
"taskList",
|
|
942
|
+
"blockquote",
|
|
943
|
+
"codeBlock"
|
|
944
|
+
]) && blocks.length === 1;
|
|
945
|
+
if ((state.selection.empty || state.selection instanceof _tiptap_pm_state.TextSelection) && isPossibleToTurnInto) {
|
|
946
|
+
const pos = findNodePosition({
|
|
947
|
+
editor,
|
|
948
|
+
node: state.selection.$anchor.node(1)
|
|
949
|
+
})?.pos;
|
|
950
|
+
if (!isValidPosition(pos)) return false;
|
|
951
|
+
tr = tr.setSelection(_tiptap_pm_state.NodeSelection.create(state.doc, pos));
|
|
952
|
+
view.dispatch(tr);
|
|
953
|
+
state = view.state;
|
|
954
|
+
}
|
|
955
|
+
const selection = state.selection;
|
|
956
|
+
let chain = editor.chain().focus();
|
|
957
|
+
if (selection instanceof _tiptap_pm_state.NodeSelection) {
|
|
958
|
+
const firstChild = selection.node.firstChild?.firstChild;
|
|
959
|
+
const lastChild = selection.node.lastChild?.lastChild;
|
|
960
|
+
const from = firstChild ? selection.from + firstChild.nodeSize : selection.from + 1;
|
|
961
|
+
const to = lastChild ? selection.to - lastChild.nodeSize : selection.to - 1;
|
|
962
|
+
const resolvedFrom = state.doc.resolve(from);
|
|
963
|
+
const resolvedTo = state.doc.resolve(to);
|
|
964
|
+
chain = chain.setTextSelection(_tiptap_pm_state.TextSelection.between(resolvedFrom, resolvedTo)).clearNodes();
|
|
965
|
+
}
|
|
966
|
+
(levels.some((l) => editor.isActive("heading", { level: l })) ? chain.setNode("paragraph") : chain.setNode("heading", { level: toggleLevel })).run();
|
|
967
|
+
editor.chain().focus().selectTextblockEnd().run();
|
|
968
|
+
return true;
|
|
969
|
+
} catch {
|
|
970
|
+
return false;
|
|
971
|
+
}
|
|
972
|
+
}
|
|
973
|
+
/**
|
|
974
|
+
* Determines if the heading menu item should be shown
|
|
975
|
+
*/
|
|
976
|
+
function shouldShowItem(props) {
|
|
977
|
+
const { editor, level, hideWhenUnavailable } = props;
|
|
978
|
+
if (!editor) return false;
|
|
979
|
+
if (!hideWhenUnavailable) return true;
|
|
980
|
+
if (!editor.isEditable) return false;
|
|
981
|
+
if (!isNodeInSchema("heading", editor)) return false;
|
|
982
|
+
if (!editor.isActive("code")) {
|
|
983
|
+
if (Array.isArray(level)) return level.some((l) => canToggle(editor, l));
|
|
984
|
+
return canToggle(editor, level);
|
|
985
|
+
}
|
|
986
|
+
return true;
|
|
987
|
+
}
|
|
988
|
+
/**
|
|
989
|
+
* Custom hook that provides heading functionality for Tiptap editor
|
|
990
|
+
*
|
|
991
|
+
* @example
|
|
992
|
+
* ```tsx
|
|
993
|
+
* // Simple usage
|
|
994
|
+
* function MySimpleHeadingItem() {
|
|
995
|
+
* const { isVisible, isActive, handleToggle, Icon } = useHeading({ level: 1 })
|
|
996
|
+
*
|
|
997
|
+
* if (!isVisible) return null
|
|
998
|
+
*
|
|
999
|
+
* return (
|
|
1000
|
+
* <button
|
|
1001
|
+
* onClick={handleToggle}
|
|
1002
|
+
* aria-pressed={isActive}
|
|
1003
|
+
* >
|
|
1004
|
+
* <Icon />
|
|
1005
|
+
* Heading 1
|
|
1006
|
+
* </button>
|
|
1007
|
+
* )
|
|
1008
|
+
* }
|
|
1009
|
+
*
|
|
1010
|
+
* // Advanced usage with configuration
|
|
1011
|
+
* function MyAdvancedHeadingItem() {
|
|
1012
|
+
* const { isVisible, isActive, handleToggle, label, Icon } = useHeading({
|
|
1013
|
+
* level: 2,
|
|
1014
|
+
* editor: myEditor,
|
|
1015
|
+
* hideWhenUnavailable: true,
|
|
1016
|
+
* onToggled: (isActive) => console.log('Heading toggled:', isActive)
|
|
1017
|
+
* })
|
|
1018
|
+
*
|
|
1019
|
+
* if (!isVisible) return null
|
|
1020
|
+
*
|
|
1021
|
+
* return (
|
|
1022
|
+
* <MyButton
|
|
1023
|
+
* onClick={handleToggle}
|
|
1024
|
+
* aria-label={label}
|
|
1025
|
+
* aria-pressed={isActive}
|
|
1026
|
+
* >
|
|
1027
|
+
* <Icon />
|
|
1028
|
+
* Toggle Heading 2
|
|
1029
|
+
* </MyButton>
|
|
1030
|
+
* )
|
|
1031
|
+
* }
|
|
1032
|
+
* ```
|
|
1033
|
+
*/
|
|
1034
|
+
function useHeading(config) {
|
|
1035
|
+
const { editor: providedEditor, level, hideWhenUnavailable = false, onToggled } = config;
|
|
1036
|
+
const { editor } = useTiptapEditor(providedEditor);
|
|
1037
|
+
const [isVisible, setIsVisible] = (0, react.useState)(true);
|
|
1038
|
+
const canToggleState = canToggle(editor, level);
|
|
1039
|
+
const isActive = isHeadingActive(editor, level);
|
|
1040
|
+
(0, react.useEffect)(() => {
|
|
1041
|
+
if (!editor) return;
|
|
1042
|
+
const handleSelectionUpdate = () => {
|
|
1043
|
+
setIsVisible(shouldShowItem({
|
|
1044
|
+
editor,
|
|
1045
|
+
level,
|
|
1046
|
+
hideWhenUnavailable
|
|
1047
|
+
}));
|
|
1048
|
+
};
|
|
1049
|
+
handleSelectionUpdate();
|
|
1050
|
+
editor.on("selectionUpdate", handleSelectionUpdate);
|
|
1051
|
+
return () => {
|
|
1052
|
+
editor.off("selectionUpdate", handleSelectionUpdate);
|
|
1053
|
+
};
|
|
1054
|
+
}, [
|
|
1055
|
+
editor,
|
|
1056
|
+
level,
|
|
1057
|
+
hideWhenUnavailable
|
|
1058
|
+
]);
|
|
1059
|
+
return {
|
|
1060
|
+
isVisible,
|
|
1061
|
+
isActive,
|
|
1062
|
+
handleToggle: (0, react.useCallback)(() => {
|
|
1063
|
+
if (!editor) return false;
|
|
1064
|
+
const success = toggleHeading(editor, level);
|
|
1065
|
+
if (success) onToggled?.();
|
|
1066
|
+
return success;
|
|
1067
|
+
}, [
|
|
1068
|
+
editor,
|
|
1069
|
+
level,
|
|
1070
|
+
onToggled
|
|
1071
|
+
]),
|
|
1072
|
+
canToggle: canToggleState,
|
|
1073
|
+
label: `Titre ${level}`,
|
|
1074
|
+
shortcutKeys: HEADING_SHORTCUT_KEYS[level],
|
|
1075
|
+
Icon: headingIcons[level]
|
|
1076
|
+
};
|
|
1077
|
+
}
|
|
1078
|
+
//#endregion
|
|
1079
|
+
//#region src/tiptap/components/ui/heading-menu-item/heading-menu-item.tsx
|
|
1080
|
+
function HeadingShortcutBadge({ level, shortcutKeys = HEADING_SHORTCUT_KEYS[level] }) {
|
|
1081
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_mui_material_Badge.default, { children: parseShortcutKeys({ shortcutKeys }) });
|
|
1082
|
+
}
|
|
1083
|
+
/**
|
|
1084
|
+
* Menu item component for toggling heading in a Tiptap editor.
|
|
1085
|
+
*
|
|
1086
|
+
* For custom menu item implementations, use the `useHeading` hook instead.
|
|
1087
|
+
*/
|
|
1088
|
+
var HeadingMenuItem = (0, react.forwardRef)(({ editor: providedEditor, level, text, hideWhenUnavailable = false, onToggled, showShortcut = false, onClick, children, ...menuItemProps }, ref) => {
|
|
1089
|
+
const { editor } = useTiptapEditor(providedEditor);
|
|
1090
|
+
const { isVisible, canToggle, isActive, handleToggle, label, Icon, shortcutKeys } = useHeading({
|
|
1091
|
+
editor,
|
|
1092
|
+
level,
|
|
1093
|
+
hideWhenUnavailable,
|
|
1094
|
+
onToggled
|
|
1095
|
+
});
|
|
1096
|
+
const handleClick = (0, react.useCallback)((event) => {
|
|
1097
|
+
onClick?.(event);
|
|
1098
|
+
if (event.defaultPrevented) return;
|
|
1099
|
+
handleToggle();
|
|
1100
|
+
}, [handleToggle, onClick]);
|
|
1101
|
+
if (!isVisible) return null;
|
|
1102
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_mui_material_MenuItem.default, {
|
|
1103
|
+
component: "li",
|
|
1104
|
+
role: "button",
|
|
1105
|
+
tabIndex: -1,
|
|
1106
|
+
disabled: !canToggle,
|
|
1107
|
+
"data-disabled": !canToggle,
|
|
1108
|
+
"aria-label": label,
|
|
1109
|
+
"aria-pressed": isActive,
|
|
1110
|
+
tooltip: label,
|
|
1111
|
+
selected: isActive,
|
|
1112
|
+
onClick: handleClick,
|
|
1113
|
+
...menuItemProps,
|
|
1114
|
+
ref,
|
|
1115
|
+
children: children ?? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
|
|
1116
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Icon, { fontSize: "small" }),
|
|
1117
|
+
text && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_mui_material_Typography.default, {
|
|
1118
|
+
component: "span",
|
|
1119
|
+
variant: "body2",
|
|
1120
|
+
sx: { ml: 1 },
|
|
1121
|
+
children: label
|
|
1122
|
+
}),
|
|
1123
|
+
showShortcut && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(HeadingShortcutBadge, {
|
|
1124
|
+
level,
|
|
1125
|
+
shortcutKeys
|
|
1126
|
+
})
|
|
1127
|
+
] })
|
|
1128
|
+
});
|
|
1129
|
+
});
|
|
1130
|
+
HeadingMenuItem.displayName = "HeadingMenuItem";
|
|
1131
|
+
//#endregion
|
|
1132
|
+
//#region src/tiptap/components/icons/heading-icon.tsx
|
|
1133
|
+
var HeadingIcon = (0, _mui_material_utils.createSvgIcon)(/* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", {
|
|
1134
|
+
d: "M6 3C6.55228 3 7 3.44772 7 4V11H17V4C17 3.44772 17.4477 3 18 3C18.5523 3 19 3.44772 19 4V20C19 20.5523 18.5523 21 18 21C17.4477 21 17 20.5523 17 20V13H7V20C7 20.5523 6.55228 21 6 21C5.44772 21 5 20.5523 5 20V4C5 3.44772 5.44772 3 6 3Z",
|
|
1135
|
+
fill: "currentColor"
|
|
1136
|
+
}), "Heading");
|
|
1137
|
+
//#endregion
|
|
1138
|
+
//#region src/tiptap/components/ui/heading-menu/use-heading-menu.ts
|
|
1139
|
+
/**
|
|
1140
|
+
* Gets the currently active heading level from the available levels
|
|
1141
|
+
*/
|
|
1142
|
+
function getActiveHeadingLevel(editor, levels = [
|
|
1143
|
+
1,
|
|
1144
|
+
2,
|
|
1145
|
+
3,
|
|
1146
|
+
4,
|
|
1147
|
+
5,
|
|
1148
|
+
6
|
|
1149
|
+
]) {
|
|
1150
|
+
if (!editor || !editor.isEditable) return void 0;
|
|
1151
|
+
return levels.find((level) => isHeadingActive(editor, level));
|
|
1152
|
+
}
|
|
1153
|
+
/**
|
|
1154
|
+
* Custom hook that provides heading menu functionality for Tiptap editor
|
|
1155
|
+
*
|
|
1156
|
+
* @example
|
|
1157
|
+
* ```tsx
|
|
1158
|
+
* // Simple usage
|
|
1159
|
+
* function MyHeading() {
|
|
1160
|
+
* const {
|
|
1161
|
+
* isVisible,
|
|
1162
|
+
* activeLevel,
|
|
1163
|
+
* isAnyHeadingActive,
|
|
1164
|
+
* canToggle,
|
|
1165
|
+
* levels,
|
|
1166
|
+
* } = useHeadingMenu()
|
|
1167
|
+
*
|
|
1168
|
+
* if (!isVisible) return null
|
|
1169
|
+
*
|
|
1170
|
+
* return (
|
|
1171
|
+
* <Menu>
|
|
1172
|
+
* // content
|
|
1173
|
+
* </Menu>
|
|
1174
|
+
* )
|
|
1175
|
+
* }
|
|
1176
|
+
*
|
|
1177
|
+
* // Advanced usage with configuration
|
|
1178
|
+
* function MyAdvancedHeading() {
|
|
1179
|
+
* const {
|
|
1180
|
+
* isVisible,
|
|
1181
|
+
* activeLevel,
|
|
1182
|
+
* } = useHeadingMenu({
|
|
1183
|
+
* editor: myEditor,
|
|
1184
|
+
* levels: [1, 2, 3],
|
|
1185
|
+
* hideWhenUnavailable: true,
|
|
1186
|
+
* })
|
|
1187
|
+
*
|
|
1188
|
+
* // component implementation
|
|
1189
|
+
* }
|
|
1190
|
+
* ```
|
|
1191
|
+
*/
|
|
1192
|
+
function useHeadingMenu(config) {
|
|
1193
|
+
const { editor: providedEditor, levels = [
|
|
1194
|
+
1,
|
|
1195
|
+
2,
|
|
1196
|
+
3,
|
|
1197
|
+
4,
|
|
1198
|
+
5,
|
|
1199
|
+
6
|
|
1200
|
+
], hideWhenUnavailable = false } = config || {};
|
|
1201
|
+
const { editor } = useTiptapEditor(providedEditor);
|
|
1202
|
+
const [isVisible, setIsVisible] = (0, react.useState)(true);
|
|
1203
|
+
const activeLevel = getActiveHeadingLevel(editor, levels);
|
|
1204
|
+
const isActive = isHeadingActive(editor);
|
|
1205
|
+
const canToggleState = canToggle(editor);
|
|
1206
|
+
(0, react.useEffect)(() => {
|
|
1207
|
+
if (!editor) return;
|
|
1208
|
+
const handleSelectionUpdate = () => {
|
|
1209
|
+
setIsVisible(shouldShowItem({
|
|
1210
|
+
editor,
|
|
1211
|
+
hideWhenUnavailable,
|
|
1212
|
+
level: levels
|
|
1213
|
+
}));
|
|
1214
|
+
};
|
|
1215
|
+
handleSelectionUpdate();
|
|
1216
|
+
editor.on("selectionUpdate", handleSelectionUpdate);
|
|
1217
|
+
return () => {
|
|
1218
|
+
editor.off("selectionUpdate", handleSelectionUpdate);
|
|
1219
|
+
};
|
|
1220
|
+
}, [
|
|
1221
|
+
editor,
|
|
1222
|
+
hideWhenUnavailable,
|
|
1223
|
+
levels
|
|
1224
|
+
]);
|
|
1225
|
+
return {
|
|
1226
|
+
isVisible,
|
|
1227
|
+
activeLevel,
|
|
1228
|
+
isActive,
|
|
1229
|
+
canToggle: canToggleState,
|
|
1230
|
+
levels,
|
|
1231
|
+
label: "Titre",
|
|
1232
|
+
Icon: activeLevel ? headingIcons[activeLevel] : HeadingIcon
|
|
1233
|
+
};
|
|
1234
|
+
}
|
|
1235
|
+
//#endregion
|
|
1236
|
+
//#region src/tiptap/components/ui/heading-menu/heading-menu.tsx
|
|
1237
|
+
/**
|
|
1238
|
+
* Menu component for selecting heading levels in a Tiptap editor.
|
|
1239
|
+
*
|
|
1240
|
+
* For custom menu implementations, use the `useHeadingMenu` hook instead.
|
|
1241
|
+
*/
|
|
1242
|
+
var HeadingMenu = (0, react.forwardRef)(({ editor: providedEditor, levels = [
|
|
1243
|
+
1,
|
|
1244
|
+
2,
|
|
1245
|
+
3,
|
|
1246
|
+
4,
|
|
1247
|
+
5,
|
|
1248
|
+
6
|
|
1249
|
+
], hideWhenUnavailable = false, onOpenChange, children, modal = true, ...buttonProps }, ref) => {
|
|
1250
|
+
const { editor } = useTiptapEditor(providedEditor);
|
|
1251
|
+
const [anchorEl, setAnchorEl] = (0, react.useState)(null);
|
|
1252
|
+
const open = Boolean(anchorEl);
|
|
1253
|
+
const { isVisible, isActive, canToggle, Icon, label } = useHeadingMenu({
|
|
1254
|
+
editor,
|
|
1255
|
+
levels,
|
|
1256
|
+
hideWhenUnavailable
|
|
1257
|
+
});
|
|
1258
|
+
const handleClick = (event) => {
|
|
1259
|
+
setAnchorEl(event.currentTarget);
|
|
1260
|
+
};
|
|
1261
|
+
const handleClose = (0, react.useCallback)(() => {
|
|
1262
|
+
if (!editor || !canToggle) return;
|
|
1263
|
+
setAnchorEl(null);
|
|
1264
|
+
onOpenChange?.(false);
|
|
1265
|
+
}, [
|
|
1266
|
+
canToggle,
|
|
1267
|
+
editor,
|
|
1268
|
+
onOpenChange
|
|
1269
|
+
]);
|
|
1270
|
+
if (!isVisible) return null;
|
|
1271
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Button, {
|
|
1272
|
+
id: "heading-button",
|
|
1273
|
+
value: "heading",
|
|
1274
|
+
type: "button",
|
|
1275
|
+
selected: isActive || open,
|
|
1276
|
+
role: "button",
|
|
1277
|
+
tabIndex: -1,
|
|
1278
|
+
disabled: !canToggle,
|
|
1279
|
+
"data-disabled": !canToggle,
|
|
1280
|
+
"aria-haspopup": "listbox",
|
|
1281
|
+
"aria-controls": "heading-menu",
|
|
1282
|
+
"aria-label": "Format text as heading",
|
|
1283
|
+
"aria-expanded": open,
|
|
1284
|
+
tooltip: label,
|
|
1285
|
+
onClick: handleClick,
|
|
1286
|
+
...buttonProps,
|
|
1287
|
+
ref,
|
|
1288
|
+
children: children ? children : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Icon, { fontSize: "small" }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ExpandMoreRounded_default, { sx: { fontSize: "0.75rem" } })] })
|
|
1289
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_mui_material_Menu.default, {
|
|
1290
|
+
id: "heading-menu",
|
|
1291
|
+
anchorEl,
|
|
1292
|
+
open,
|
|
1293
|
+
onClose: handleClose,
|
|
1294
|
+
slotProps: { list: {
|
|
1295
|
+
"aria-labelledby": "heading-button",
|
|
1296
|
+
role: "listbox"
|
|
1297
|
+
} },
|
|
1298
|
+
children: levels.map((level) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(HeadingMenuItem, {
|
|
1299
|
+
editor,
|
|
1300
|
+
level,
|
|
1301
|
+
text: `Heading ${level}`,
|
|
1302
|
+
onClick: handleClose
|
|
1303
|
+
}, `heading-${level}`))
|
|
1304
|
+
})] });
|
|
1305
|
+
});
|
|
1306
|
+
HeadingMenu.displayName = "HeadingMenu";
|
|
1307
|
+
//#endregion
|
|
1308
|
+
//#region ../../node_modules/.pnpm/@mui+icons-material@9.4.0_@mui+material@9.4.0_@emotion+react@11.14.0_@types+react@19.2._02cb1dd638d2ccdea7d16c50ea4ea174/node_modules/@mui/icons-material/KeyboardReturnRounded.mjs
|
|
1309
|
+
var KeyboardReturnRounded_default = (0, _mui_material_SvgIcon.createSvgIcon)(/*#__PURE__*/ (0, react_jsx_runtime.jsx)("path", { d: "M19 8v3H5.83l2.88-2.88c.39-.39.39-1.02 0-1.41a.996.996 0 0 0-1.41 0L2.71 11.3c-.39.39-.39 1.02 0 1.41L7.3 17.3c.39.39 1.02.39 1.41 0s.39-1.02 0-1.41L5.83 13H20c.55 0 1-.45 1-1V8c0-.55-.45-1-1-1s-1 .45-1 1" }), "KeyboardReturnRounded");
|
|
1310
|
+
//#endregion
|
|
1311
|
+
//#region ../../node_modules/.pnpm/@mui+icons-material@9.4.0_@mui+material@9.4.0_@emotion+react@11.14.0_@types+react@19.2._02cb1dd638d2ccdea7d16c50ea4ea174/node_modules/@mui/icons-material/LinkRounded.mjs
|
|
1312
|
+
var LinkRounded_default = (0, _mui_material_SvgIcon.createSvgIcon)(/*#__PURE__*/ (0, react_jsx_runtime.jsx)("path", { d: "M17 7h-3c-.55 0-1 .45-1 1s.45 1 1 1h3c1.65 0 3 1.35 3 3s-1.35 3-3 3h-3c-.55 0-1 .45-1 1s.45 1 1 1h3c2.76 0 5-2.24 5-5s-2.24-5-5-5m-9 5c0 .55.45 1 1 1h6c.55 0 1-.45 1-1s-.45-1-1-1H9c-.55 0-1 .45-1 1m2 3H7c-1.65 0-3-1.35-3-3s1.35-3 3-3h3c.55 0 1-.45 1-1s-.45-1-1-1H7c-2.76 0-5 2.24-5 5s2.24 5 5 5h3c.55 0 1-.45 1-1s-.45-1-1-1" }), "LinkRounded");
|
|
1313
|
+
//#endregion
|
|
1314
|
+
//#region ../../node_modules/.pnpm/@mui+icons-material@9.4.0_@mui+material@9.4.0_@emotion+react@11.14.0_@types+react@19.2._02cb1dd638d2ccdea7d16c50ea4ea174/node_modules/@mui/icons-material/OpenInNewRounded.mjs
|
|
1315
|
+
var OpenInNewRounded_default = (0, _mui_material_SvgIcon.createSvgIcon)(/*#__PURE__*/ (0, react_jsx_runtime.jsx)("path", { d: "M18 19H6c-.55 0-1-.45-1-1V6c0-.55.45-1 1-1h5c.55 0 1-.45 1-1s-.45-1-1-1H5c-1.11 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2v-6c0-.55-.45-1-1-1s-1 .45-1 1v5c0 .55-.45 1-1 1M14 4c0 .55.45 1 1 1h2.59l-9.13 9.13c-.39.39-.39 1.02 0 1.41s1.02.39 1.41 0L19 6.41V9c0 .55.45 1 1 1s1-.45 1-1V4c0-.55-.45-1-1-1h-5c-.55 0-1 .45-1 1" }), "OpenInNewRounded");
|
|
1316
|
+
//#endregion
|
|
1317
|
+
//#region src/tiptap/components/ui/link-popover/use-link-popover.ts
|
|
1318
|
+
/**
|
|
1319
|
+
* Checks if a link can be set in the current editor state
|
|
1320
|
+
*/
|
|
1321
|
+
function canSetLink(editor) {
|
|
1322
|
+
if (!editor || !editor.isEditable) return false;
|
|
1323
|
+
if (isNodeTypeSelected(editor, ["image"], true)) return false;
|
|
1324
|
+
try {
|
|
1325
|
+
return editor.can().setMark("link");
|
|
1326
|
+
} catch {
|
|
1327
|
+
return false;
|
|
1328
|
+
}
|
|
1329
|
+
}
|
|
1330
|
+
/**
|
|
1331
|
+
* Checks if a link is currently active in the editor
|
|
1332
|
+
*/
|
|
1333
|
+
function isLinkActive(editor) {
|
|
1334
|
+
if (!editor || !editor.isEditable) return false;
|
|
1335
|
+
return editor.isActive("link");
|
|
1336
|
+
}
|
|
1337
|
+
/**
|
|
1338
|
+
* Determines if the link button should be shown
|
|
1339
|
+
*/
|
|
1340
|
+
function shouldShowLinkButton(props) {
|
|
1341
|
+
const { editor, hideWhenUnavailable } = props;
|
|
1342
|
+
if (!editor || !editor.isEditable) return false;
|
|
1343
|
+
const linkInSchema = isMarkInSchema("link", editor);
|
|
1344
|
+
if (!hideWhenUnavailable) return true;
|
|
1345
|
+
if (!linkInSchema) return false;
|
|
1346
|
+
if (!editor.isActive("code")) return canSetLink(editor);
|
|
1347
|
+
return true;
|
|
1348
|
+
}
|
|
1349
|
+
/**
|
|
1350
|
+
* Custom hook for handling link operations in a Tiptap editor
|
|
1351
|
+
*/
|
|
1352
|
+
function useLinkHandler(props) {
|
|
1353
|
+
const { editor, onSetLink } = props;
|
|
1354
|
+
const [url, setUrl] = (0, react.useState)(null);
|
|
1355
|
+
(0, react.useEffect)(() => {
|
|
1356
|
+
if (!editor) return;
|
|
1357
|
+
const { href } = editor.getAttributes("link");
|
|
1358
|
+
if (isLinkActive(editor) && url === null) setUrl(href || "");
|
|
1359
|
+
}, [editor, url]);
|
|
1360
|
+
(0, react.useEffect)(() => {
|
|
1361
|
+
if (!editor) return;
|
|
1362
|
+
const updateLinkState = () => {
|
|
1363
|
+
const { href } = editor.getAttributes("link");
|
|
1364
|
+
setUrl(href || "");
|
|
1365
|
+
};
|
|
1366
|
+
editor.on("selectionUpdate", updateLinkState);
|
|
1367
|
+
return () => {
|
|
1368
|
+
editor.off("selectionUpdate", updateLinkState);
|
|
1369
|
+
};
|
|
1370
|
+
}, [editor]);
|
|
1371
|
+
const setLink = (0, react.useCallback)(() => {
|
|
1372
|
+
if (!url || !editor) return;
|
|
1373
|
+
const { selection } = editor.state;
|
|
1374
|
+
const isEmpty = selection.empty;
|
|
1375
|
+
let chain = editor.chain().focus();
|
|
1376
|
+
chain = chain.extendMarkRange("link").setLink({ href: url });
|
|
1377
|
+
if (isEmpty) chain = chain.insertContent({
|
|
1378
|
+
type: "text",
|
|
1379
|
+
text: url
|
|
1380
|
+
});
|
|
1381
|
+
chain.run();
|
|
1382
|
+
setUrl(null);
|
|
1383
|
+
onSetLink?.();
|
|
1384
|
+
}, [
|
|
1385
|
+
editor,
|
|
1386
|
+
onSetLink,
|
|
1387
|
+
url
|
|
1388
|
+
]);
|
|
1389
|
+
const removeLink = (0, react.useCallback)(() => {
|
|
1390
|
+
if (!editor) return;
|
|
1391
|
+
editor.chain().focus().extendMarkRange("link").unsetLink().setMeta("preventAutolink", true).run();
|
|
1392
|
+
setUrl("");
|
|
1393
|
+
}, [editor]);
|
|
1394
|
+
const openLink = (0, react.useCallback)((target = "_blank", features = "noopener,noreferrer") => {
|
|
1395
|
+
if (!url) return;
|
|
1396
|
+
const safeUrl = sanitizeUrl(url, window.location.href);
|
|
1397
|
+
if (safeUrl !== "#") window.open(safeUrl, target, features);
|
|
1398
|
+
}, [url]);
|
|
1399
|
+
return {
|
|
1400
|
+
url: url || "",
|
|
1401
|
+
setUrl,
|
|
1402
|
+
setLink,
|
|
1403
|
+
removeLink,
|
|
1404
|
+
openLink
|
|
1405
|
+
};
|
|
1406
|
+
}
|
|
1407
|
+
/**
|
|
1408
|
+
* Custom hook for link popover state management
|
|
1409
|
+
*/
|
|
1410
|
+
function useLinkState(props) {
|
|
1411
|
+
const { editor, hideWhenUnavailable = false } = props;
|
|
1412
|
+
const canSet = canSetLink(editor);
|
|
1413
|
+
const isActive = isLinkActive(editor);
|
|
1414
|
+
const [isVisible, setIsVisible] = (0, react.useState)(true);
|
|
1415
|
+
(0, react.useEffect)(() => {
|
|
1416
|
+
if (!editor) return;
|
|
1417
|
+
const handleSelectionUpdate = () => {
|
|
1418
|
+
setIsVisible(shouldShowLinkButton({
|
|
1419
|
+
editor,
|
|
1420
|
+
hideWhenUnavailable
|
|
1421
|
+
}));
|
|
1422
|
+
};
|
|
1423
|
+
handleSelectionUpdate();
|
|
1424
|
+
editor.on("selectionUpdate", handleSelectionUpdate);
|
|
1425
|
+
return () => {
|
|
1426
|
+
editor.off("selectionUpdate", handleSelectionUpdate);
|
|
1427
|
+
};
|
|
1428
|
+
}, [editor, hideWhenUnavailable]);
|
|
1429
|
+
return {
|
|
1430
|
+
isVisible,
|
|
1431
|
+
canSet,
|
|
1432
|
+
isActive
|
|
1433
|
+
};
|
|
1434
|
+
}
|
|
1435
|
+
/**
|
|
1436
|
+
* Main hook that provides link popover functionality for Tiptap editor
|
|
1437
|
+
*
|
|
1438
|
+
* @example
|
|
1439
|
+
* ```tsx
|
|
1440
|
+
* // Simple usage
|
|
1441
|
+
* function MyLinkButton() {
|
|
1442
|
+
* const { isVisible, canSet, isActive, Icon, label } = useLinkPopover()
|
|
1443
|
+
*
|
|
1444
|
+
* if (!isVisible) return null
|
|
1445
|
+
*
|
|
1446
|
+
* return <button disabled={!canSet}>Link</button>
|
|
1447
|
+
* }
|
|
1448
|
+
*
|
|
1449
|
+
* // Advanced usage with configuration
|
|
1450
|
+
* function MyAdvancedLinkButton() {
|
|
1451
|
+
* const { isVisible, canSet, isActive, Icon, label } = useLinkPopover({
|
|
1452
|
+
* editor: myEditor,
|
|
1453
|
+
* hideWhenUnavailable: true,
|
|
1454
|
+
* onSetLink: () => console.log('Link set!')
|
|
1455
|
+
* })
|
|
1456
|
+
*
|
|
1457
|
+
* if (!isVisible) return null
|
|
1458
|
+
*
|
|
1459
|
+
* return (
|
|
1460
|
+
* <MyButton
|
|
1461
|
+
* disabled={!canSet}
|
|
1462
|
+
* aria-label={label}
|
|
1463
|
+
* aria-pressed={isActive}
|
|
1464
|
+
* >
|
|
1465
|
+
* <Icon />
|
|
1466
|
+
* {label}
|
|
1467
|
+
* </MyButton>
|
|
1468
|
+
* )
|
|
1469
|
+
* }
|
|
1470
|
+
* ```
|
|
1471
|
+
*/
|
|
1472
|
+
function useLinkPopover(config) {
|
|
1473
|
+
const { editor: providedEditor, hideWhenUnavailable = false, onSetLink } = config || {};
|
|
1474
|
+
const { editor } = useTiptapEditor(providedEditor);
|
|
1475
|
+
const { isVisible, canSet, isActive } = useLinkState({
|
|
1476
|
+
editor,
|
|
1477
|
+
hideWhenUnavailable
|
|
1478
|
+
});
|
|
1479
|
+
return {
|
|
1480
|
+
isVisible,
|
|
1481
|
+
canSet,
|
|
1482
|
+
isActive,
|
|
1483
|
+
label: "Lien",
|
|
1484
|
+
Icon: LinkRounded_default,
|
|
1485
|
+
...useLinkHandler({
|
|
1486
|
+
editor,
|
|
1487
|
+
onSetLink
|
|
1488
|
+
})
|
|
1489
|
+
};
|
|
1490
|
+
}
|
|
1491
|
+
//#endregion
|
|
1492
|
+
//#region src/tiptap/components/ui/link-popover/link-popover.tsx
|
|
1493
|
+
/**
|
|
1494
|
+
* Link button component for triggering the link popover
|
|
1495
|
+
*/
|
|
1496
|
+
var LinkButton = (0, react.forwardRef)(({ className, children, ...props }, ref) => {
|
|
1497
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Button, {
|
|
1498
|
+
type: "button",
|
|
1499
|
+
className,
|
|
1500
|
+
role: "button",
|
|
1501
|
+
tabIndex: -1,
|
|
1502
|
+
ref,
|
|
1503
|
+
...props,
|
|
1504
|
+
children: children || /* @__PURE__ */ (0, react_jsx_runtime.jsx)(LinkRounded_default, { fontSize: "small" })
|
|
1505
|
+
});
|
|
1506
|
+
});
|
|
1507
|
+
LinkButton.displayName = "LinkButton";
|
|
1508
|
+
var StyledIconButton = (0, _mui_material_styles.styled)(_mui_material_IconButton.default)(({ theme }) => ({
|
|
1509
|
+
borderRadius: theme.shape.borderRadius,
|
|
1510
|
+
"& .MuiTouchRipple-root .MuiTouchRipple-child": { borderRadius: theme.shape.borderRadius }
|
|
1511
|
+
}));
|
|
1512
|
+
/**
|
|
1513
|
+
* Main content component for the link popover
|
|
1514
|
+
*/
|
|
1515
|
+
var LinkMain = ({ url, setUrl, setLink, removeLink, openLink, isActive }) => {
|
|
1516
|
+
const handleKeyDown = (event) => {
|
|
1517
|
+
if (event.key === "Enter") {
|
|
1518
|
+
event.preventDefault();
|
|
1519
|
+
setLink();
|
|
1520
|
+
}
|
|
1521
|
+
};
|
|
1522
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(_mui_material_Stack.default, {
|
|
1523
|
+
direction: "row",
|
|
1524
|
+
spacing: .5,
|
|
1525
|
+
sx: {
|
|
1526
|
+
alignItems: "center",
|
|
1527
|
+
p: 1
|
|
1528
|
+
},
|
|
1529
|
+
children: [
|
|
1530
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_mui_material_InputBase.default, {
|
|
1531
|
+
type: "url",
|
|
1532
|
+
placeholder: "Coller un lien...",
|
|
1533
|
+
value: url,
|
|
1534
|
+
onChange: (e) => setUrl(e.target.value),
|
|
1535
|
+
onKeyDown: handleKeyDown,
|
|
1536
|
+
autoFocus: true,
|
|
1537
|
+
autoComplete: "off",
|
|
1538
|
+
autoCorrect: "off",
|
|
1539
|
+
autoCapitalize: "off",
|
|
1540
|
+
sx: {
|
|
1541
|
+
padding: ".25rem .625rem",
|
|
1542
|
+
fontSize: "0.875rem",
|
|
1543
|
+
minWidth: "12rem"
|
|
1544
|
+
}
|
|
1545
|
+
}),
|
|
1546
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(StyledIconButton, {
|
|
1547
|
+
size: "small",
|
|
1548
|
+
type: "button",
|
|
1549
|
+
onClick: setLink,
|
|
1550
|
+
title: "Appliquer le lien",
|
|
1551
|
+
disabled: !url && !isActive,
|
|
1552
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(KeyboardReturnRounded_default, { fontSize: "small" })
|
|
1553
|
+
}),
|
|
1554
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_mui_material_Divider.default, {
|
|
1555
|
+
orientation: "vertical",
|
|
1556
|
+
flexItem: true,
|
|
1557
|
+
sx: { my: .5 }
|
|
1558
|
+
}),
|
|
1559
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(StyledIconButton, {
|
|
1560
|
+
size: "small",
|
|
1561
|
+
type: "button",
|
|
1562
|
+
onClick: openLink,
|
|
1563
|
+
title: "Ouvrir dans une nouvelle fenêtre",
|
|
1564
|
+
disabled: !url && !isActive,
|
|
1565
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(OpenInNewRounded_default, { fontSize: "small" })
|
|
1566
|
+
}),
|
|
1567
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(StyledIconButton, {
|
|
1568
|
+
size: "small",
|
|
1569
|
+
type: "button",
|
|
1570
|
+
onClick: removeLink,
|
|
1571
|
+
title: "Supprimer le lien",
|
|
1572
|
+
disabled: !url && !isActive,
|
|
1573
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(require_src.DeleteRounded_default, { fontSize: "small" })
|
|
1574
|
+
})
|
|
1575
|
+
]
|
|
1576
|
+
});
|
|
1577
|
+
};
|
|
1578
|
+
/**
|
|
1579
|
+
* Link popover component for Tiptap editors.
|
|
1580
|
+
*
|
|
1581
|
+
* For custom popover implementations, use the `useLinkPopover` hook instead.
|
|
1582
|
+
*/
|
|
1583
|
+
var LinkPopover = (0, react.forwardRef)(({ editor: providedEditor, hideWhenUnavailable = false, onSetLink, onOpenChange, autoOpenOnLinkActive = true, onClick, children, ...buttonProps }, ref) => {
|
|
1584
|
+
const { editor } = useTiptapEditor(providedEditor);
|
|
1585
|
+
const [anchorEl, setAnchorEl] = (0, react.useState)(null);
|
|
1586
|
+
const internalRef = (0, react.useRef)(null);
|
|
1587
|
+
const justSubmittedRef = (0, react.useRef)(false);
|
|
1588
|
+
const { isVisible, canSet, isActive, url, setUrl, setLink, removeLink, openLink, label, Icon } = useLinkPopover({
|
|
1589
|
+
editor,
|
|
1590
|
+
hideWhenUnavailable,
|
|
1591
|
+
onSetLink
|
|
1592
|
+
});
|
|
1593
|
+
const setRefs = (0, react.useCallback)((node) => {
|
|
1594
|
+
internalRef.current = node;
|
|
1595
|
+
if (typeof ref === "function") ref(node);
|
|
1596
|
+
else if (ref) ref.current = node;
|
|
1597
|
+
}, [ref]);
|
|
1598
|
+
const handleClose = (0, react.useCallback)(() => {
|
|
1599
|
+
setAnchorEl(null);
|
|
1600
|
+
onOpenChange?.(false);
|
|
1601
|
+
}, [onOpenChange]);
|
|
1602
|
+
const handleSetLink = (0, react.useCallback)(() => {
|
|
1603
|
+
justSubmittedRef.current = true;
|
|
1604
|
+
setLink();
|
|
1605
|
+
setAnchorEl(null);
|
|
1606
|
+
queueMicrotask(() => {
|
|
1607
|
+
justSubmittedRef.current = false;
|
|
1608
|
+
});
|
|
1609
|
+
}, [setLink]);
|
|
1610
|
+
const handleClick = (0, react.useCallback)((event) => {
|
|
1611
|
+
onClick?.(event, !isActive);
|
|
1612
|
+
if (event.defaultPrevented) return;
|
|
1613
|
+
setAnchorEl(event.currentTarget);
|
|
1614
|
+
}, [onClick, anchorEl]);
|
|
1615
|
+
(0, react.useEffect)(() => {
|
|
1616
|
+
if (autoOpenOnLinkActive && isActive && !justSubmittedRef.current) setAnchorEl(internalRef.current);
|
|
1617
|
+
}, [autoOpenOnLinkActive, isActive]);
|
|
1618
|
+
if (!isVisible) return null;
|
|
1619
|
+
const open = Boolean(anchorEl);
|
|
1620
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(LinkButton, {
|
|
1621
|
+
value: "link",
|
|
1622
|
+
disabled: !canSet,
|
|
1623
|
+
selected: isActive || open,
|
|
1624
|
+
"data-disabled": !canSet,
|
|
1625
|
+
"aria-label": label,
|
|
1626
|
+
tooltip: label,
|
|
1627
|
+
onClick: handleClick,
|
|
1628
|
+
...buttonProps,
|
|
1629
|
+
ref: setRefs,
|
|
1630
|
+
children: children ?? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Icon, { fontSize: "small" })
|
|
1631
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_mui_material_Popover.default, {
|
|
1632
|
+
open,
|
|
1633
|
+
anchorEl,
|
|
1634
|
+
onClose: handleClose,
|
|
1635
|
+
anchorOrigin: {
|
|
1636
|
+
vertical: "bottom",
|
|
1637
|
+
horizontal: "center"
|
|
1638
|
+
},
|
|
1639
|
+
transformOrigin: {
|
|
1640
|
+
vertical: -4,
|
|
1641
|
+
horizontal: "center"
|
|
1642
|
+
},
|
|
1643
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(LinkMain, {
|
|
1644
|
+
url,
|
|
1645
|
+
setUrl,
|
|
1646
|
+
setLink: handleSetLink,
|
|
1647
|
+
removeLink,
|
|
1648
|
+
openLink,
|
|
1649
|
+
isActive
|
|
1650
|
+
})
|
|
1651
|
+
})] });
|
|
1652
|
+
});
|
|
1653
|
+
LinkPopover.displayName = "LinkPopover";
|
|
1654
|
+
//#endregion
|
|
1655
|
+
//#region src/tiptap/components/ui/list-button/use-list.ts
|
|
1656
|
+
var listIcons = {
|
|
1657
|
+
bulletList: (0, _mui_material_SvgIcon.createSvgIcon)(/*#__PURE__*/ (0, react_jsx_runtime.jsx)("path", { d: "M4 10.5c-.83 0-1.5.67-1.5 1.5s.67 1.5 1.5 1.5 1.5-.67 1.5-1.5-.67-1.5-1.5-1.5m0-6c-.83 0-1.5.67-1.5 1.5S3.17 7.5 4 7.5 5.5 6.83 5.5 6 4.83 4.5 4 4.5m0 12c-.83 0-1.5.68-1.5 1.5s.68 1.5 1.5 1.5 1.5-.68 1.5-1.5-.67-1.5-1.5-1.5M8 19h12c.55 0 1-.45 1-1s-.45-1-1-1H8c-.55 0-1 .45-1 1s.45 1 1 1m0-6h12c.55 0 1-.45 1-1s-.45-1-1-1H8c-.55 0-1 .45-1 1s.45 1 1 1M7 6c0 .55.45 1 1 1h12c.55 0 1-.45 1-1s-.45-1-1-1H8c-.55 0-1 .45-1 1" }), "FormatListBulletedRounded"),
|
|
1658
|
+
orderedList: (0, _mui_material_SvgIcon.createSvgIcon)(/*#__PURE__*/ (0, react_jsx_runtime.jsx)("path", { d: "M8 7h12c.55 0 1-.45 1-1s-.45-1-1-1H8c-.55 0-1 .45-1 1s.45 1 1 1m12 10H8c-.55 0-1 .45-1 1s.45 1 1 1h12c.55 0 1-.45 1-1s-.45-1-1-1m0-6H8c-.55 0-1 .45-1 1s.45 1 1 1h12c.55 0 1-.45 1-1s-.45-1-1-1M4.5 16h-2c-.28 0-.5.22-.5.5s.22.5.5.5H4v.5h-.5c-.28 0-.5.22-.5.5s.22.5.5.5H4v.5H2.5c-.28 0-.5.22-.5.5s.22.5.5.5h2c.28 0 .5-.22.5-.5v-3c0-.28-.22-.5-.5-.5m-2-11H3v2.5c0 .28.22.5.5.5s.5-.22.5-.5v-3c0-.28-.22-.5-.5-.5h-1c-.28 0-.5.22-.5.5s.22.5.5.5m2 5h-2c-.28 0-.5.22-.5.5s.22.5.5.5h1.3l-1.68 1.96c-.08.09-.12.21-.12.32v.22c0 .28.22.5.5.5h2c.28 0 .5-.22.5-.5s-.22-.5-.5-.5H3.2l1.68-1.96c.08-.09.12-.21.12-.32v-.22c0-.28-.22-.5-.5-.5" }), "FormatListNumberedRounded"),
|
|
1659
|
+
taskList: (0, _mui_material_utils.createSvgIcon)(/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("g", { children: [
|
|
1660
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", {
|
|
1661
|
+
fillRule: "evenodd",
|
|
1662
|
+
clipRule: "evenodd",
|
|
1663
|
+
d: "M2 6C2 4.89543 2.89543 4 4 4H8C9.10457 4 10 4.89543 10 6V10C10 11.1046 9.10457 12 8 12H4C2.89543 12 2 11.1046 2 10V6ZM8 6H4V10H8V6Z",
|
|
1664
|
+
fill: "currentColor"
|
|
1665
|
+
}),
|
|
1666
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", {
|
|
1667
|
+
fillRule: "evenodd",
|
|
1668
|
+
clipRule: "evenodd",
|
|
1669
|
+
d: "M9.70711 14.2929C10.0976 14.6834 10.0976 15.3166 9.70711 15.7071L5.70711 19.7071C5.31658 20.0976 4.68342 20.0976 4.29289 19.7071L2.29289 17.7071C1.90237 17.3166 1.90237 16.6834 2.29289 16.2929C2.68342 15.9024 3.31658 15.9024 3.70711 16.2929L5 17.5858L8.29289 14.2929C8.68342 13.9024 9.31658 13.9024 9.70711 14.2929Z",
|
|
1670
|
+
fill: "currentColor"
|
|
1671
|
+
}),
|
|
1672
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", {
|
|
1673
|
+
fillRule: "evenodd",
|
|
1674
|
+
clipRule: "evenodd",
|
|
1675
|
+
d: "M12 6C12 5.44772 12.4477 5 13 5H21C21.5523 5 22 5.44772 22 6C22 6.55228 21.5523 7 21 7H13C12.4477 7 12 6.55228 12 6Z",
|
|
1676
|
+
fill: "currentColor"
|
|
1677
|
+
}),
|
|
1678
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", {
|
|
1679
|
+
fillRule: "evenodd",
|
|
1680
|
+
clipRule: "evenodd",
|
|
1681
|
+
d: "M12 12C12 11.4477 12.4477 11 13 11H21C21.5523 11 22 11.4477 22 12C22 12.5523 21.5523 13 21 13H13C12.4477 13 12 12.5523 12 12Z",
|
|
1682
|
+
fill: "currentColor"
|
|
1683
|
+
}),
|
|
1684
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", {
|
|
1685
|
+
fillRule: "evenodd",
|
|
1686
|
+
clipRule: "evenodd",
|
|
1687
|
+
d: "M12 18C12 17.4477 12.4477 17 13 17H21C21.5523 17 22 17.4477 22 18C22 18.5523 21.5523 19 21 19H13C12.4477 19 12 18.5523 12 18Z",
|
|
1688
|
+
fill: "currentColor"
|
|
1689
|
+
})
|
|
1690
|
+
] }), "ListTodo")
|
|
1691
|
+
};
|
|
1692
|
+
var listLabels = {
|
|
1693
|
+
bulletList: "Liste à puces",
|
|
1694
|
+
orderedList: "Liste numérotée",
|
|
1695
|
+
taskList: "Liste de tâches"
|
|
1696
|
+
};
|
|
1697
|
+
var LIST_SHORTCUT_KEYS = {
|
|
1698
|
+
bulletList: "mod+shift+8",
|
|
1699
|
+
orderedList: "mod+shift+7",
|
|
1700
|
+
taskList: "mod+shift+9"
|
|
1701
|
+
};
|
|
1702
|
+
/**
|
|
1703
|
+
* Checks if a list can be toggled in the current editor state
|
|
1704
|
+
*/
|
|
1705
|
+
function canToggleList(editor, type, turnInto = true) {
|
|
1706
|
+
if (!editor || !editor.isEditable) return false;
|
|
1707
|
+
if (!isNodeInSchema(type, editor) || isNodeTypeSelected(editor, ["image"])) return false;
|
|
1708
|
+
if (!turnInto) switch (type) {
|
|
1709
|
+
case "bulletList": return editor.can().toggleBulletList();
|
|
1710
|
+
case "orderedList": return editor.can().toggleOrderedList();
|
|
1711
|
+
case "taskList": return editor.can().toggleList("taskList", "taskItem");
|
|
1712
|
+
default: return false;
|
|
1713
|
+
}
|
|
1714
|
+
if (!selectionWithinConvertibleTypes(editor, [
|
|
1715
|
+
"paragraph",
|
|
1716
|
+
"heading",
|
|
1717
|
+
"bulletList",
|
|
1718
|
+
"orderedList",
|
|
1719
|
+
"taskList",
|
|
1720
|
+
"blockquote",
|
|
1721
|
+
"codeBlock"
|
|
1722
|
+
])) return false;
|
|
1723
|
+
switch (type) {
|
|
1724
|
+
case "bulletList": return editor.can().toggleBulletList() || editor.can().clearNodes();
|
|
1725
|
+
case "orderedList": return editor.can().toggleOrderedList() || editor.can().clearNodes();
|
|
1726
|
+
case "taskList": return editor.can().toggleList("taskList", "taskItem") || editor.can().clearNodes();
|
|
1727
|
+
default: return false;
|
|
1728
|
+
}
|
|
1729
|
+
}
|
|
1730
|
+
/**
|
|
1731
|
+
* Checks if list is currently active
|
|
1732
|
+
*/
|
|
1733
|
+
function isListActive(editor, type) {
|
|
1734
|
+
if (!editor || !editor.isEditable) return false;
|
|
1735
|
+
switch (type) {
|
|
1736
|
+
case "bulletList": return editor.isActive("bulletList");
|
|
1737
|
+
case "orderedList": return editor.isActive("orderedList");
|
|
1738
|
+
case "taskList": return editor.isActive("taskList");
|
|
1739
|
+
default: return false;
|
|
1740
|
+
}
|
|
1741
|
+
}
|
|
1742
|
+
/**
|
|
1743
|
+
* Toggles list in the editor
|
|
1744
|
+
*/
|
|
1745
|
+
function toggleList(editor, type) {
|
|
1746
|
+
if (!editor || !editor.isEditable) return false;
|
|
1747
|
+
if (!canToggleList(editor, type)) return false;
|
|
1748
|
+
try {
|
|
1749
|
+
const view = editor.view;
|
|
1750
|
+
let state = view.state;
|
|
1751
|
+
let tr = state.tr;
|
|
1752
|
+
const blocks = getSelectedBlockNodes(editor);
|
|
1753
|
+
const isPossibleToTurnInto = selectionWithinConvertibleTypes(editor, [
|
|
1754
|
+
"paragraph",
|
|
1755
|
+
"heading",
|
|
1756
|
+
"bulletList",
|
|
1757
|
+
"orderedList",
|
|
1758
|
+
"taskList",
|
|
1759
|
+
"blockquote",
|
|
1760
|
+
"codeBlock"
|
|
1761
|
+
]) && blocks.length === 1;
|
|
1762
|
+
if ((state.selection.empty || state.selection instanceof _tiptap_pm_state.TextSelection) && isPossibleToTurnInto) {
|
|
1763
|
+
const pos = findNodePosition({
|
|
1764
|
+
editor,
|
|
1765
|
+
node: state.selection.$anchor.node(1)
|
|
1766
|
+
})?.pos;
|
|
1767
|
+
if (!isValidPosition(pos)) return false;
|
|
1768
|
+
tr = tr.setSelection(_tiptap_pm_state.NodeSelection.create(state.doc, pos));
|
|
1769
|
+
view.dispatch(tr);
|
|
1770
|
+
state = view.state;
|
|
1771
|
+
}
|
|
1772
|
+
const selection = state.selection;
|
|
1773
|
+
let chain = editor.chain().focus();
|
|
1774
|
+
if (selection instanceof _tiptap_pm_state.NodeSelection) {
|
|
1775
|
+
const firstChild = selection.node.firstChild?.firstChild;
|
|
1776
|
+
const lastChild = selection.node.lastChild?.lastChild;
|
|
1777
|
+
const from = firstChild ? selection.from + firstChild.nodeSize : selection.from + 1;
|
|
1778
|
+
const to = lastChild ? selection.to - lastChild.nodeSize : selection.to - 1;
|
|
1779
|
+
const resolvedFrom = state.doc.resolve(from);
|
|
1780
|
+
const resolvedTo = state.doc.resolve(to);
|
|
1781
|
+
chain = chain.setTextSelection(_tiptap_pm_state.TextSelection.between(resolvedFrom, resolvedTo)).clearNodes();
|
|
1782
|
+
}
|
|
1783
|
+
if (editor.isActive(type)) chain.liftListItem("listItem").lift("bulletList").lift("orderedList").run();
|
|
1784
|
+
else {
|
|
1785
|
+
const toggle = {
|
|
1786
|
+
bulletList: () => chain.toggleBulletList(),
|
|
1787
|
+
orderedList: () => chain.toggleOrderedList(),
|
|
1788
|
+
taskList: () => chain.toggleList("taskList", "taskItem")
|
|
1789
|
+
}[type];
|
|
1790
|
+
if (!toggle) return false;
|
|
1791
|
+
toggle().run();
|
|
1792
|
+
}
|
|
1793
|
+
editor.chain().focus().selectTextblockEnd().run();
|
|
1794
|
+
return true;
|
|
1795
|
+
} catch {
|
|
1796
|
+
return false;
|
|
1797
|
+
}
|
|
1798
|
+
}
|
|
1799
|
+
/**
|
|
1800
|
+
* Determines if the list button should be shown
|
|
1801
|
+
*/
|
|
1802
|
+
function shouldShowButton$3(props) {
|
|
1803
|
+
const { editor, type, hideWhenUnavailable } = props;
|
|
1804
|
+
if (!editor) return false;
|
|
1805
|
+
if (!hideWhenUnavailable) return true;
|
|
1806
|
+
if (!editor.isEditable) return false;
|
|
1807
|
+
if (!isNodeInSchema(type, editor)) return false;
|
|
1808
|
+
if (!editor.isActive("code")) return canToggleList(editor, type);
|
|
1809
|
+
return true;
|
|
1810
|
+
}
|
|
1811
|
+
/**
|
|
1812
|
+
* Custom hook that provides list functionality for Tiptap editor
|
|
1813
|
+
*
|
|
1814
|
+
* @example
|
|
1815
|
+
* ```tsx
|
|
1816
|
+
* // Simple usage
|
|
1817
|
+
* function MySimpleListButton() {
|
|
1818
|
+
* const { isVisible, handleToggle, isActive } = useList({ type: "bulletList" })
|
|
1819
|
+
*
|
|
1820
|
+
* if (!isVisible) return null
|
|
1821
|
+
*
|
|
1822
|
+
* return <button onClick={handleToggle}>Bullet List</button>
|
|
1823
|
+
* }
|
|
1824
|
+
*
|
|
1825
|
+
* // Advanced usage with configuration
|
|
1826
|
+
* function MyAdvancedListButton() {
|
|
1827
|
+
* const { isVisible, handleToggle, label, isActive } = useList({
|
|
1828
|
+
* type: "orderedList",
|
|
1829
|
+
* editor: myEditor,
|
|
1830
|
+
* hideWhenUnavailable: true,
|
|
1831
|
+
* onToggled: () => console.log('List toggled!')
|
|
1832
|
+
* })
|
|
1833
|
+
*
|
|
1834
|
+
* if (!isVisible) return null
|
|
1835
|
+
*
|
|
1836
|
+
* return (
|
|
1837
|
+
* <MyButton
|
|
1838
|
+
* onClick={handleToggle}
|
|
1839
|
+
* aria-label={label}
|
|
1840
|
+
* aria-pressed={isActive}
|
|
1841
|
+
* >
|
|
1842
|
+
* Toggle List
|
|
1843
|
+
* </MyButton>
|
|
1844
|
+
* )
|
|
1845
|
+
* }
|
|
1846
|
+
* ```
|
|
1847
|
+
*/
|
|
1848
|
+
function useList(config) {
|
|
1849
|
+
const { editor: providedEditor, type, hideWhenUnavailable = false, onToggled } = config;
|
|
1850
|
+
const { editor } = useTiptapEditor(providedEditor);
|
|
1851
|
+
const [isVisible, setIsVisible] = (0, react.useState)(true);
|
|
1852
|
+
const canToggle = canToggleList(editor, type);
|
|
1853
|
+
const isActive = isListActive(editor, type);
|
|
1854
|
+
(0, react.useEffect)(() => {
|
|
1855
|
+
if (!editor) return;
|
|
1856
|
+
const handleSelectionUpdate = () => {
|
|
1857
|
+
setIsVisible(shouldShowButton$3({
|
|
1858
|
+
editor,
|
|
1859
|
+
type,
|
|
1860
|
+
hideWhenUnavailable
|
|
1861
|
+
}));
|
|
1862
|
+
};
|
|
1863
|
+
handleSelectionUpdate();
|
|
1864
|
+
editor.on("selectionUpdate", handleSelectionUpdate);
|
|
1865
|
+
return () => {
|
|
1866
|
+
editor.off("selectionUpdate", handleSelectionUpdate);
|
|
1867
|
+
};
|
|
1868
|
+
}, [
|
|
1869
|
+
editor,
|
|
1870
|
+
type,
|
|
1871
|
+
hideWhenUnavailable
|
|
1872
|
+
]);
|
|
1873
|
+
return {
|
|
1874
|
+
isVisible,
|
|
1875
|
+
isActive,
|
|
1876
|
+
handleToggle: (0, react.useCallback)(() => {
|
|
1877
|
+
if (!editor) return false;
|
|
1878
|
+
const success = toggleList(editor, type);
|
|
1879
|
+
if (success) onToggled?.();
|
|
1880
|
+
return success;
|
|
1881
|
+
}, [
|
|
1882
|
+
editor,
|
|
1883
|
+
type,
|
|
1884
|
+
onToggled
|
|
1885
|
+
]),
|
|
1886
|
+
canToggle,
|
|
1887
|
+
label: listLabels[type],
|
|
1888
|
+
shortcutKeys: LIST_SHORTCUT_KEYS[type],
|
|
1889
|
+
Icon: listIcons[type]
|
|
1890
|
+
};
|
|
1891
|
+
}
|
|
1892
|
+
//#endregion
|
|
1893
|
+
//#region src/tiptap/components/ui/list-button/list-button.tsx
|
|
1894
|
+
function ListShortcutBadge({ type, shortcutKeys = LIST_SHORTCUT_KEYS[type] }) {
|
|
1895
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_mui_material_Badge.default, { children: parseShortcutKeys({ shortcutKeys }) });
|
|
1896
|
+
}
|
|
1897
|
+
/**
|
|
1898
|
+
* Button component for toggling lists in a Tiptap editor.
|
|
1899
|
+
*
|
|
1900
|
+
* For custom button implementations, use the `useList` hook instead.
|
|
1901
|
+
*/
|
|
1902
|
+
var ListButton = (0, react.forwardRef)(({ editor: providedEditor, type, text, hideWhenUnavailable = false, onToggled, showShortcut = false, onClick, children, ...buttonProps }, ref) => {
|
|
1903
|
+
const { editor } = useTiptapEditor(providedEditor);
|
|
1904
|
+
const { isVisible, canToggle, isActive, handleToggle, label, shortcutKeys, Icon } = useList({
|
|
1905
|
+
editor,
|
|
1906
|
+
type,
|
|
1907
|
+
hideWhenUnavailable,
|
|
1908
|
+
onToggled
|
|
1909
|
+
});
|
|
1910
|
+
const handleClick = (0, react.useCallback)((event) => {
|
|
1911
|
+
onClick?.(event, !isActive);
|
|
1912
|
+
if (event.defaultPrevented) return;
|
|
1913
|
+
handleToggle();
|
|
1914
|
+
}, [handleToggle, onClick]);
|
|
1915
|
+
if (!isVisible) return null;
|
|
1916
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Button, {
|
|
1917
|
+
value: type,
|
|
1918
|
+
type: "button",
|
|
1919
|
+
selected: isActive,
|
|
1920
|
+
role: "button",
|
|
1921
|
+
tabIndex: -1,
|
|
1922
|
+
disabled: !canToggle,
|
|
1923
|
+
"data-disabled": !canToggle,
|
|
1924
|
+
"aria-label": label,
|
|
1925
|
+
tooltip: label,
|
|
1926
|
+
onClick: handleClick,
|
|
1927
|
+
...buttonProps,
|
|
1928
|
+
ref,
|
|
1929
|
+
children: children ?? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
|
|
1930
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Icon, { fontSize: "small" }),
|
|
1931
|
+
text && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1932
|
+
className: "tiptap-button-text",
|
|
1933
|
+
children: text
|
|
1934
|
+
}),
|
|
1935
|
+
showShortcut && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ListShortcutBadge, {
|
|
1936
|
+
type,
|
|
1937
|
+
shortcutKeys
|
|
1938
|
+
})
|
|
1939
|
+
] })
|
|
1940
|
+
});
|
|
1941
|
+
});
|
|
1942
|
+
ListButton.displayName = "ListButton";
|
|
1943
|
+
//#endregion
|
|
1944
|
+
//#region ../../node_modules/.pnpm/@mui+icons-material@9.4.0_@mui+material@9.4.0_@emotion+react@11.14.0_@types+react@19.2._02cb1dd638d2ccdea7d16c50ea4ea174/node_modules/@mui/icons-material/CodeRounded.mjs
|
|
1945
|
+
var CodeRounded_default = (0, _mui_material_SvgIcon.createSvgIcon)(/*#__PURE__*/ (0, react_jsx_runtime.jsx)("path", { d: "M8.7 15.9 4.8 12l3.9-3.9c.39-.39.39-1.01 0-1.4a.984.984 0 0 0-1.4 0l-4.59 4.59c-.39.39-.39 1.02 0 1.41l4.59 4.6c.39.39 1.01.39 1.4 0s.39-1.01 0-1.4m6.6 0 3.9-3.9-3.9-3.9a.984.984 0 0 1 0-1.4c.39-.39 1.01-.39 1.4 0l4.59 4.59c.39.39.39 1.02 0 1.41l-4.59 4.6c-.39.39-1.01.39-1.4 0a.984.984 0 0 1 0-1.4" }), "CodeRounded");
|
|
1946
|
+
//#endregion
|
|
1947
|
+
//#region ../../node_modules/.pnpm/@mui+icons-material@9.4.0_@mui+material@9.4.0_@emotion+react@11.14.0_@types+react@19.2._02cb1dd638d2ccdea7d16c50ea4ea174/node_modules/@mui/icons-material/FormatBoldRounded.mjs
|
|
1948
|
+
var FormatBoldRounded_default = (0, _mui_material_SvgIcon.createSvgIcon)(/*#__PURE__*/ (0, react_jsx_runtime.jsx)("path", { d: "M15.6 10.79c.97-.67 1.65-1.77 1.65-2.79 0-2.26-1.75-4-4-4H8c-.55 0-1 .45-1 1v12c0 .55.45 1 1 1h5.78c2.07 0 3.96-1.69 3.97-3.77.01-1.53-.85-2.84-2.15-3.44M10 6.5h3c.83 0 1.5.67 1.5 1.5s-.67 1.5-1.5 1.5h-3zm3.5 9H10v-3h3.5c.83 0 1.5.67 1.5 1.5s-.67 1.5-1.5 1.5" }), "FormatBoldRounded");
|
|
1949
|
+
//#endregion
|
|
1950
|
+
//#region ../../node_modules/.pnpm/@mui+icons-material@9.4.0_@mui+material@9.4.0_@emotion+react@11.14.0_@types+react@19.2._02cb1dd638d2ccdea7d16c50ea4ea174/node_modules/@mui/icons-material/FormatItalicRounded.mjs
|
|
1951
|
+
var FormatItalicRounded_default = (0, _mui_material_SvgIcon.createSvgIcon)(/*#__PURE__*/ (0, react_jsx_runtime.jsx)("path", { d: "M10 5.5c0 .83.67 1.5 1.5 1.5h.71l-3.42 8H7.5c-.83 0-1.5.67-1.5 1.5S6.67 18 7.5 18h5c.83 0 1.5-.67 1.5-1.5s-.67-1.5-1.5-1.5h-.71l3.42-8h1.29c.83 0 1.5-.67 1.5-1.5S17.33 4 16.5 4h-5c-.83 0-1.5.67-1.5 1.5" }), "FormatItalicRounded");
|
|
1952
|
+
//#endregion
|
|
1953
|
+
//#region ../../node_modules/.pnpm/@mui+icons-material@9.4.0_@mui+material@9.4.0_@emotion+react@11.14.0_@types+react@19.2._02cb1dd638d2ccdea7d16c50ea4ea174/node_modules/@mui/icons-material/FormatUnderlinedRounded.mjs
|
|
1954
|
+
var FormatUnderlinedRounded_default = (0, _mui_material_SvgIcon.createSvgIcon)(/*#__PURE__*/ (0, react_jsx_runtime.jsx)("path", { d: "M12.79 16.95c3.03-.39 5.21-3.11 5.21-6.16V4.25C18 3.56 17.44 3 16.75 3s-1.25.56-1.25 1.25v6.65c0 1.67-1.13 3.19-2.77 3.52-2.25.47-4.23-1.25-4.23-3.42V4.25C8.5 3.56 7.94 3 7.25 3S6 3.56 6 4.25V11c0 3.57 3.13 6.42 6.79 5.95M5 20c0 .55.45 1 1 1h12c.55 0 1-.45 1-1s-.45-1-1-1H6c-.55 0-1 .45-1 1" }), "FormatUnderlinedRounded");
|
|
1955
|
+
//#endregion
|
|
1956
|
+
//#region ../../node_modules/.pnpm/@mui+icons-material@9.4.0_@mui+material@9.4.0_@emotion+react@11.14.0_@types+react@19.2._02cb1dd638d2ccdea7d16c50ea4ea174/node_modules/@mui/icons-material/StrikethroughSRounded.mjs
|
|
1957
|
+
var StrikethroughSRounded_default = (0, _mui_material_SvgIcon.createSvgIcon)(/*#__PURE__*/ (0, react_jsx_runtime.jsx)("path", { d: "M14.59 7.52c0-.31-.05-.59-.15-.85-.09-.27-.24-.49-.44-.68s-.45-.33-.75-.44c-.3-.1-.66-.16-1.06-.16-.39 0-.74.04-1.03.13s-.53.21-.72.36c-.19.16-.34.34-.44.55q-.15.315-.15.66c0 .48.25.88.74 1.21.38.25.77.48 1.41.7H7.39c-.05-.08-.11-.17-.15-.25-.26-.48-.39-1.03-.39-1.67 0-.61.13-1.16.4-1.67q.39-.75 1.11-1.29c.48-.35 1.05-.63 1.7-.83.66-.19 1.39-.29 2.18-.29.81 0 1.54.11 2.21.34.66.22 1.23.54 1.69.94.47.4.83.88 1.08 1.43s.38 1.15.38 1.81zM20 10H4c-.55 0-1 .45-1 1s.45 1 1 1h8.62c.18.07.4.14.55.2q.555.255.87.51c.315.255.35.36.43.57.07.2.11.43.11.69 0 .23-.05.45-.14.66-.09.2-.23.38-.42.53s-.42.26-.71.35c-.29.08-.63.13-1.01.13-.43 0-.83-.04-1.18-.13s-.66-.23-.91-.42-.45-.44-.59-.75-.25-.76-.25-1.21H6.4c0 .55.08 1.13.24 1.58s.37.85.65 1.21c.28.35.6.66.98.92.37.26.78.48 1.22.65q.66.255 1.38.39c.48.08.96.13 1.44.13.8 0 1.53-.09 2.18-.28s1.21-.45 1.67-.79.82-.77 1.07-1.27.38-1.07.38-1.71c0-.6-.1-1.14-.31-1.61-.05-.11-.11-.23-.17-.33H20c.55 0 1-.45 1-1V11c0-.55-.45-1-1-1" }), "StrikethroughSRounded");
|
|
1958
|
+
//#endregion
|
|
1959
|
+
//#region ../../node_modules/.pnpm/@mui+icons-material@9.4.0_@mui+material@9.4.0_@emotion+react@11.14.0_@types+react@19.2._02cb1dd638d2ccdea7d16c50ea4ea174/node_modules/@mui/icons-material/SubscriptRounded.mjs
|
|
1960
|
+
var SubscriptRounded_default = (0, _mui_material_SvgIcon.createSvgIcon)(/*#__PURE__*/ (0, react_jsx_runtime.jsx)("path", { d: "M10.52 10.73 7.3 5.72C6.82 4.97 7.35 4 8.23 4c.39 0 .74.2.95.53l2.76 4.46h.12l2.74-4.45c.21-.34.57-.54.96-.54.88 0 1.42.98.94 1.72l-3.23 5 3.55 5.55c.48.75-.06 1.73-.94 1.73-.38 0-.74-.2-.95-.52l-3.07-4.89h-.12l-3.07 4.89c-.2.32-.56.52-.95.52-.88 0-1.42-.97-.94-1.72zM23 19.5c0-.28-.22-.5-.5-.5H20v-1h2c.55 0 1-.45 1-1v-1c0-.55-.45-1-1-1h-2.5c-.28 0-.5.22-.5.5s.22.5.5.5H22v1h-2c-.55 0-1 .45-1 1v1c0 .55.45 1 1 1h2.5c.28 0 .5-.22.5-.5" }), "SubscriptRounded");
|
|
1961
|
+
//#endregion
|
|
1962
|
+
//#region src/tiptap/components/ui/mark-button/use-mark.ts
|
|
1963
|
+
var markIcons = {
|
|
1964
|
+
bold: FormatBoldRounded_default,
|
|
1965
|
+
italic: FormatItalicRounded_default,
|
|
1966
|
+
underline: FormatUnderlinedRounded_default,
|
|
1967
|
+
strike: StrikethroughSRounded_default,
|
|
1968
|
+
code: CodeRounded_default,
|
|
1969
|
+
superscript: (0, _mui_material_SvgIcon.createSvgIcon)(/*#__PURE__*/ (0, react_jsx_runtime.jsx)("path", { d: "M10.51 12.73 7.3 7.72C6.82 6.97 7.35 6 8.23 6c.39 0 .74.2.95.53l2.76 4.46h.12l2.74-4.45c.2-.34.56-.54.95-.54.88 0 1.42.98.94 1.72l-3.23 5 3.55 5.55c.49.75-.05 1.73-.93 1.73-.38 0-.74-.2-.95-.52l-3.07-4.89h-.12l-3.07 4.89c-.21.32-.56.52-.95.52-.88 0-1.42-.97-.94-1.72zM23 8.5c0-.28-.22-.5-.5-.5H20V7h2c.55 0 1-.45 1-1V5c0-.55-.45-1-1-1h-2.5c-.28 0-.5.22-.5.5s.22.5.5.5H22v1h-2c-.55 0-1 .45-1 1v1c0 .55.45 1 1 1h2.5c.28 0 .5-.22.5-.5" }), "SuperscriptRounded"),
|
|
1970
|
+
subscript: SubscriptRounded_default
|
|
1971
|
+
};
|
|
1972
|
+
var MARK_SHORTCUT_KEYS = {
|
|
1973
|
+
bold: "mod+b",
|
|
1974
|
+
italic: "mod+i",
|
|
1975
|
+
underline: "mod+u",
|
|
1976
|
+
strike: "mod+shift+s",
|
|
1977
|
+
code: "mod+e",
|
|
1978
|
+
superscript: "mod+.",
|
|
1979
|
+
subscript: "mod+,"
|
|
1980
|
+
};
|
|
1981
|
+
/**
|
|
1982
|
+
* Checks if a mark can be toggled in the current editor state
|
|
1983
|
+
*/
|
|
1984
|
+
function canToggleMark(editor, type) {
|
|
1985
|
+
if (!editor || !editor.isEditable) return false;
|
|
1986
|
+
if (!isMarkInSchema(type, editor) || isNodeTypeSelected(editor, ["image"])) return false;
|
|
1987
|
+
return editor.can().toggleMark(type);
|
|
1988
|
+
}
|
|
1989
|
+
/**
|
|
1990
|
+
* Checks if a mark is currently active
|
|
1991
|
+
*/
|
|
1992
|
+
function isMarkActive(editor, type) {
|
|
1993
|
+
if (!editor || !editor.isEditable) return false;
|
|
1994
|
+
return editor.isActive(type);
|
|
1995
|
+
}
|
|
1996
|
+
/**
|
|
1997
|
+
* Toggles a mark in the editor
|
|
1998
|
+
*/
|
|
1999
|
+
function toggleMark(editor, type) {
|
|
2000
|
+
if (!editor || !editor.isEditable) return false;
|
|
2001
|
+
if (!canToggleMark(editor, type)) return false;
|
|
2002
|
+
return editor.chain().focus().toggleMark(type).run();
|
|
2003
|
+
}
|
|
2004
|
+
/**
|
|
2005
|
+
* Determines if the mark button should be shown
|
|
2006
|
+
*/
|
|
2007
|
+
function shouldShowButton$2(props) {
|
|
2008
|
+
const { editor, type, hideWhenUnavailable } = props;
|
|
2009
|
+
if (!editor) return false;
|
|
2010
|
+
if (!hideWhenUnavailable) return true;
|
|
2011
|
+
if (!editor.isEditable) return false;
|
|
2012
|
+
if (!isMarkInSchema(type, editor)) return false;
|
|
2013
|
+
if (!editor.isActive("code")) return canToggleMark(editor, type);
|
|
2014
|
+
return true;
|
|
2015
|
+
}
|
|
2016
|
+
/**
|
|
2017
|
+
* Gets the formatted mark name
|
|
2018
|
+
*/
|
|
2019
|
+
function getFormattedMarkName(type) {
|
|
2020
|
+
switch (type) {
|
|
2021
|
+
case "bold": return "Gras";
|
|
2022
|
+
case "italic": return "Italique";
|
|
2023
|
+
case "strike": return "Barré";
|
|
2024
|
+
case "code": return "Code";
|
|
2025
|
+
case "underline": return "Souligné";
|
|
2026
|
+
case "superscript": return "Exposant";
|
|
2027
|
+
case "subscript": return "Indice";
|
|
2028
|
+
}
|
|
2029
|
+
}
|
|
2030
|
+
/**
|
|
2031
|
+
* Custom hook that provides mark functionality for Tiptap editor
|
|
2032
|
+
*
|
|
2033
|
+
* @example
|
|
2034
|
+
* ```tsx
|
|
2035
|
+
* // Simple usage
|
|
2036
|
+
* function MySimpleBoldButton() {
|
|
2037
|
+
* const { isVisible, handleMark } = useMark({ type: "bold" })
|
|
2038
|
+
*
|
|
2039
|
+
* if (!isVisible) return null
|
|
2040
|
+
*
|
|
2041
|
+
* return <button onClick={handleMark}>Bold</button>
|
|
2042
|
+
* }
|
|
2043
|
+
*
|
|
2044
|
+
* // Advanced usage with configuration
|
|
2045
|
+
* function MyAdvancedItalicButton() {
|
|
2046
|
+
* const { isVisible, handleMark, label, isActive } = useMark({
|
|
2047
|
+
* editor: myEditor,
|
|
2048
|
+
* type: "italic",
|
|
2049
|
+
* hideWhenUnavailable: true,
|
|
2050
|
+
* onToggled: () => console.log('Mark toggled!')
|
|
2051
|
+
* })
|
|
2052
|
+
*
|
|
2053
|
+
* if (!isVisible) return null
|
|
2054
|
+
*
|
|
2055
|
+
* return (
|
|
2056
|
+
* <MyButton
|
|
2057
|
+
* onClick={handleMark}
|
|
2058
|
+
* aria-pressed={isActive}
|
|
2059
|
+
* aria-label={label}
|
|
2060
|
+
* >
|
|
2061
|
+
* Italic
|
|
2062
|
+
* </MyButton>
|
|
2063
|
+
* )
|
|
2064
|
+
* }
|
|
2065
|
+
* ```
|
|
2066
|
+
*/
|
|
2067
|
+
function useMark(config) {
|
|
2068
|
+
const { editor: providedEditor, type, hideWhenUnavailable = false, onToggled } = config;
|
|
2069
|
+
const { editor } = useTiptapEditor(providedEditor);
|
|
2070
|
+
const [isVisible, setIsVisible] = (0, react.useState)(true);
|
|
2071
|
+
const canToggle = canToggleMark(editor, type);
|
|
2072
|
+
const isActive = isMarkActive(editor, type);
|
|
2073
|
+
(0, react.useEffect)(() => {
|
|
2074
|
+
if (!editor) return;
|
|
2075
|
+
const handleSelectionUpdate = () => {
|
|
2076
|
+
setIsVisible(shouldShowButton$2({
|
|
2077
|
+
editor,
|
|
2078
|
+
type,
|
|
2079
|
+
hideWhenUnavailable
|
|
2080
|
+
}));
|
|
2081
|
+
};
|
|
2082
|
+
handleSelectionUpdate();
|
|
2083
|
+
editor.on("selectionUpdate", handleSelectionUpdate);
|
|
2084
|
+
return () => {
|
|
2085
|
+
editor.off("selectionUpdate", handleSelectionUpdate);
|
|
2086
|
+
};
|
|
2087
|
+
}, [
|
|
2088
|
+
editor,
|
|
2089
|
+
type,
|
|
2090
|
+
hideWhenUnavailable
|
|
2091
|
+
]);
|
|
2092
|
+
return {
|
|
2093
|
+
isVisible,
|
|
2094
|
+
isActive,
|
|
2095
|
+
handleMark: (0, react.useCallback)(() => {
|
|
2096
|
+
if (!editor) return false;
|
|
2097
|
+
const success = toggleMark(editor, type);
|
|
2098
|
+
if (success) onToggled?.();
|
|
2099
|
+
return success;
|
|
2100
|
+
}, [
|
|
2101
|
+
editor,
|
|
2102
|
+
type,
|
|
2103
|
+
onToggled
|
|
2104
|
+
]),
|
|
2105
|
+
canToggle,
|
|
2106
|
+
label: getFormattedMarkName(type),
|
|
2107
|
+
shortcutKeys: MARK_SHORTCUT_KEYS[type],
|
|
2108
|
+
Icon: markIcons[type]
|
|
2109
|
+
};
|
|
2110
|
+
}
|
|
2111
|
+
//#endregion
|
|
2112
|
+
//#region src/tiptap/components/ui/mark-button/mark-button.tsx
|
|
2113
|
+
function MarkShortcutBadge({ type, shortcutKeys = MARK_SHORTCUT_KEYS[type] }) {
|
|
2114
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_mui_material_Badge.default, { children: parseShortcutKeys({ shortcutKeys }) });
|
|
2115
|
+
}
|
|
2116
|
+
/**
|
|
2117
|
+
* Button component for toggling marks in a Tiptap editor.
|
|
2118
|
+
*
|
|
2119
|
+
* For custom button implementations, use the `useMark` hook instead.
|
|
2120
|
+
*/
|
|
2121
|
+
var MarkButton = (0, react.forwardRef)(({ editor: providedEditor, type, text, hideWhenUnavailable = false, onToggled, showShortcut = false, onClick, children, ...buttonProps }, ref) => {
|
|
2122
|
+
const { editor } = useTiptapEditor(providedEditor);
|
|
2123
|
+
const { isVisible, handleMark, label, canToggle, isActive, Icon, shortcutKeys } = useMark({
|
|
2124
|
+
editor,
|
|
2125
|
+
type,
|
|
2126
|
+
hideWhenUnavailable,
|
|
2127
|
+
onToggled
|
|
2128
|
+
});
|
|
2129
|
+
const handleClick = (0, react.useCallback)((event) => {
|
|
2130
|
+
onClick?.(event, !isActive);
|
|
2131
|
+
if (event.defaultPrevented) return;
|
|
2132
|
+
handleMark();
|
|
2133
|
+
}, [handleMark, onClick]);
|
|
2134
|
+
if (!isVisible) return null;
|
|
2135
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Button, {
|
|
2136
|
+
value: type,
|
|
2137
|
+
type: "button",
|
|
2138
|
+
disabled: !canToggle,
|
|
2139
|
+
selected: isActive,
|
|
2140
|
+
"data-disabled": !canToggle,
|
|
2141
|
+
role: "button",
|
|
2142
|
+
tabIndex: -1,
|
|
2143
|
+
"aria-label": label,
|
|
2144
|
+
tooltip: label,
|
|
2145
|
+
onClick: handleClick,
|
|
2146
|
+
...buttonProps,
|
|
2147
|
+
ref,
|
|
2148
|
+
children: children ?? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
|
|
2149
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Icon, { fontSize: "small" }),
|
|
2150
|
+
text && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
2151
|
+
className: "tiptap-button-text",
|
|
2152
|
+
children: text
|
|
2153
|
+
}),
|
|
2154
|
+
showShortcut && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(MarkShortcutBadge, {
|
|
2155
|
+
type,
|
|
2156
|
+
shortcutKeys
|
|
2157
|
+
})
|
|
2158
|
+
] })
|
|
2159
|
+
});
|
|
2160
|
+
});
|
|
2161
|
+
MarkButton.displayName = "MarkButton";
|
|
2162
|
+
//#endregion
|
|
2163
|
+
//#region ../../node_modules/.pnpm/@mui+icons-material@9.4.0_@mui+material@9.4.0_@emotion+react@11.14.0_@types+react@19.2._02cb1dd638d2ccdea7d16c50ea4ea174/node_modules/@mui/icons-material/FormatAlignCenterRounded.mjs
|
|
2164
|
+
var FormatAlignCenterRounded_default = (0, _mui_material_SvgIcon.createSvgIcon)(/*#__PURE__*/ (0, react_jsx_runtime.jsx)("path", { d: "M7 16c0 .55.45 1 1 1h8c.55 0 1-.45 1-1s-.45-1-1-1H8c-.55 0-1 .45-1 1m-3 5h16c.55 0 1-.45 1-1s-.45-1-1-1H4c-.55 0-1 .45-1 1s.45 1 1 1m0-8h16c.55 0 1-.45 1-1s-.45-1-1-1H4c-.55 0-1 .45-1 1s.45 1 1 1m3-5c0 .55.45 1 1 1h8c.55 0 1-.45 1-1s-.45-1-1-1H8c-.55 0-1 .45-1 1M3 4c0 .55.45 1 1 1h16c.55 0 1-.45 1-1s-.45-1-1-1H4c-.55 0-1 .45-1 1" }), "FormatAlignCenterRounded");
|
|
2165
|
+
//#endregion
|
|
2166
|
+
//#region ../../node_modules/.pnpm/@mui+icons-material@9.4.0_@mui+material@9.4.0_@emotion+react@11.14.0_@types+react@19.2._02cb1dd638d2ccdea7d16c50ea4ea174/node_modules/@mui/icons-material/FormatAlignJustifyRounded.mjs
|
|
2167
|
+
var FormatAlignJustifyRounded_default = (0, _mui_material_SvgIcon.createSvgIcon)(/*#__PURE__*/ (0, react_jsx_runtime.jsx)("path", { d: "M4 21h16c.55 0 1-.45 1-1s-.45-1-1-1H4c-.55 0-1 .45-1 1s.45 1 1 1m0-4h16c.55 0 1-.45 1-1s-.45-1-1-1H4c-.55 0-1 .45-1 1s.45 1 1 1m0-4h16c.55 0 1-.45 1-1s-.45-1-1-1H4c-.55 0-1 .45-1 1s.45 1 1 1m0-4h16c.55 0 1-.45 1-1s-.45-1-1-1H4c-.55 0-1 .45-1 1s.45 1 1 1M3 4c0 .55.45 1 1 1h16c.55 0 1-.45 1-1s-.45-1-1-1H4c-.55 0-1 .45-1 1" }), "FormatAlignJustifyRounded");
|
|
2168
|
+
//#endregion
|
|
2169
|
+
//#region ../../node_modules/.pnpm/@mui+icons-material@9.4.0_@mui+material@9.4.0_@emotion+react@11.14.0_@types+react@19.2._02cb1dd638d2ccdea7d16c50ea4ea174/node_modules/@mui/icons-material/FormatAlignLeftRounded.mjs
|
|
2170
|
+
var FormatAlignLeftRounded_default = (0, _mui_material_SvgIcon.createSvgIcon)(/*#__PURE__*/ (0, react_jsx_runtime.jsx)("path", { d: "M14 15H4c-.55 0-1 .45-1 1s.45 1 1 1h10c.55 0 1-.45 1-1s-.45-1-1-1m0-8H4c-.55 0-1 .45-1 1s.45 1 1 1h10c.55 0 1-.45 1-1s-.45-1-1-1M4 13h16c.55 0 1-.45 1-1s-.45-1-1-1H4c-.55 0-1 .45-1 1s.45 1 1 1m0 8h16c.55 0 1-.45 1-1s-.45-1-1-1H4c-.55 0-1 .45-1 1s.45 1 1 1M3 4c0 .55.45 1 1 1h16c.55 0 1-.45 1-1s-.45-1-1-1H4c-.55 0-1 .45-1 1" }), "FormatAlignLeftRounded");
|
|
2171
|
+
//#endregion
|
|
2172
|
+
//#region ../../node_modules/.pnpm/@mui+icons-material@9.4.0_@mui+material@9.4.0_@emotion+react@11.14.0_@types+react@19.2._02cb1dd638d2ccdea7d16c50ea4ea174/node_modules/@mui/icons-material/FormatAlignRightRounded.mjs
|
|
2173
|
+
var FormatAlignRightRounded_default = (0, _mui_material_SvgIcon.createSvgIcon)(/*#__PURE__*/ (0, react_jsx_runtime.jsx)("path", { d: "M4 21h16c.55 0 1-.45 1-1s-.45-1-1-1H4c-.55 0-1 .45-1 1s.45 1 1 1m6-4h10c.55 0 1-.45 1-1s-.45-1-1-1H10c-.55 0-1 .45-1 1s.45 1 1 1m-6-4h16c.55 0 1-.45 1-1s-.45-1-1-1H4c-.55 0-1 .45-1 1s.45 1 1 1m6-4h10c.55 0 1-.45 1-1s-.45-1-1-1H10c-.55 0-1 .45-1 1s.45 1 1 1M3 4c0 .55.45 1 1 1h16c.55 0 1-.45 1-1s-.45-1-1-1H4c-.55 0-1 .45-1 1" }), "FormatAlignRightRounded");
|
|
2174
|
+
//#endregion
|
|
2175
|
+
//#region src/tiptap/components/ui/text-align-button/use-text-align.ts
|
|
2176
|
+
var TEXT_ALIGN_SHORTCUT_KEYS = {
|
|
2177
|
+
left: "mod+shift+l",
|
|
2178
|
+
center: "mod+shift+e",
|
|
2179
|
+
right: "mod+shift+r",
|
|
2180
|
+
justify: "mod+shift+j"
|
|
2181
|
+
};
|
|
2182
|
+
var textAlignIcons = {
|
|
2183
|
+
left: FormatAlignLeftRounded_default,
|
|
2184
|
+
center: FormatAlignCenterRounded_default,
|
|
2185
|
+
right: FormatAlignRightRounded_default,
|
|
2186
|
+
justify: FormatAlignJustifyRounded_default
|
|
2187
|
+
};
|
|
2188
|
+
var textAlignLabels = {
|
|
2189
|
+
left: "Aligner à gauche",
|
|
2190
|
+
center: "Aligner au centre",
|
|
2191
|
+
right: "Aligner à droite",
|
|
2192
|
+
justify: "Justifier"
|
|
2193
|
+
};
|
|
2194
|
+
/**
|
|
2195
|
+
* Checks if text alignment can be performed in the current editor state
|
|
2196
|
+
*/
|
|
2197
|
+
function canSetTextAlign(editor, align) {
|
|
2198
|
+
if (!editor || editor.isDestroyed || !editor.isEditable) return false;
|
|
2199
|
+
if (!isExtensionAvailable(editor, "textAlign") || isNodeTypeSelected(editor, ["image", "horizontalRule"])) return false;
|
|
2200
|
+
try {
|
|
2201
|
+
return editor.can().setTextAlign(align);
|
|
2202
|
+
} catch {
|
|
2203
|
+
return false;
|
|
2204
|
+
}
|
|
2205
|
+
}
|
|
2206
|
+
function hasSetTextAlign(commands) {
|
|
2207
|
+
return "setTextAlign" in commands;
|
|
2208
|
+
}
|
|
2209
|
+
/**
|
|
2210
|
+
* Checks if the text alignment is currently active
|
|
2211
|
+
*/
|
|
2212
|
+
function isTextAlignActive(editor, align) {
|
|
2213
|
+
if (!editor || !editor.isEditable) return false;
|
|
2214
|
+
return editor.isActive({ textAlign: align });
|
|
2215
|
+
}
|
|
2216
|
+
/**
|
|
2217
|
+
* Sets text alignment in the editor
|
|
2218
|
+
*/
|
|
2219
|
+
function setTextAlign(editor, align) {
|
|
2220
|
+
if (!editor || editor.isDestroyed || !editor.isEditable) return false;
|
|
2221
|
+
if (!canSetTextAlign(editor, align)) return false;
|
|
2222
|
+
try {
|
|
2223
|
+
const chain = editor.chain().focus();
|
|
2224
|
+
if (hasSetTextAlign(chain)) return chain.setTextAlign(align).run();
|
|
2225
|
+
} catch {
|
|
2226
|
+
return false;
|
|
2227
|
+
}
|
|
2228
|
+
return false;
|
|
2229
|
+
}
|
|
2230
|
+
/**
|
|
2231
|
+
* Determines if the text align button should be shown
|
|
2232
|
+
*/
|
|
2233
|
+
function shouldShowButton$1(props) {
|
|
2234
|
+
const { editor, hideWhenUnavailable, align } = props;
|
|
2235
|
+
if (!editor) return false;
|
|
2236
|
+
if (!hideWhenUnavailable) return true;
|
|
2237
|
+
if (!editor.isEditable) return false;
|
|
2238
|
+
if (!isExtensionAvailable(editor, "textAlign")) return false;
|
|
2239
|
+
if (!editor.isActive("code")) return canSetTextAlign(editor, align);
|
|
2240
|
+
return true;
|
|
2241
|
+
}
|
|
2242
|
+
/**
|
|
2243
|
+
* Custom hook that provides text align functionality for Tiptap editor
|
|
2244
|
+
*
|
|
2245
|
+
* @example
|
|
2246
|
+
* ```tsx
|
|
2247
|
+
* // Simple usage
|
|
2248
|
+
* function MySimpleAlignButton() {
|
|
2249
|
+
* const { isVisible, handleTextAlign } = useTextAlign({ align: "center" })
|
|
2250
|
+
*
|
|
2251
|
+
* if (!isVisible) return null
|
|
2252
|
+
*
|
|
2253
|
+
* return <button onClick={handleTextAlign}>Align Center</button>
|
|
2254
|
+
* }
|
|
2255
|
+
*
|
|
2256
|
+
* // Advanced usage with configuration
|
|
2257
|
+
* function MyAdvancedAlignButton() {
|
|
2258
|
+
* const { isVisible, handleTextAlign, label, isActive } = useTextAlign({
|
|
2259
|
+
* editor: myEditor,
|
|
2260
|
+
* align: "right",
|
|
2261
|
+
* hideWhenUnavailable: true,
|
|
2262
|
+
* onAligned: () => console.log('Text aligned!')
|
|
2263
|
+
* })
|
|
2264
|
+
*
|
|
2265
|
+
* if (!isVisible) return null
|
|
2266
|
+
*
|
|
2267
|
+
* return (
|
|
2268
|
+
* <MyButton
|
|
2269
|
+
* onClick={handleTextAlign}
|
|
2270
|
+
* aria-pressed={isActive}
|
|
2271
|
+
* aria-label={label}
|
|
2272
|
+
* >
|
|
2273
|
+
* Align Right
|
|
2274
|
+
* </MyButton>
|
|
2275
|
+
* )
|
|
2276
|
+
* }
|
|
2277
|
+
* ```
|
|
2278
|
+
*/
|
|
2279
|
+
function useTextAlign(config) {
|
|
2280
|
+
const { editor: providedEditor, align, hideWhenUnavailable = false, onAligned } = config;
|
|
2281
|
+
const { editor } = useTiptapEditor(providedEditor);
|
|
2282
|
+
const [isVisible, setIsVisible] = (0, react.useState)(true);
|
|
2283
|
+
const canAlign = canSetTextAlign(editor, align);
|
|
2284
|
+
const isActive = isTextAlignActive(editor, align);
|
|
2285
|
+
(0, react.useEffect)(() => {
|
|
2286
|
+
if (!editor) return;
|
|
2287
|
+
const handleSelectionUpdate = () => {
|
|
2288
|
+
setIsVisible(shouldShowButton$1({
|
|
2289
|
+
editor,
|
|
2290
|
+
align,
|
|
2291
|
+
hideWhenUnavailable
|
|
2292
|
+
}));
|
|
2293
|
+
};
|
|
2294
|
+
handleSelectionUpdate();
|
|
2295
|
+
editor.on("selectionUpdate", handleSelectionUpdate);
|
|
2296
|
+
return () => {
|
|
2297
|
+
editor.off("selectionUpdate", handleSelectionUpdate);
|
|
2298
|
+
};
|
|
2299
|
+
}, [
|
|
2300
|
+
editor,
|
|
2301
|
+
hideWhenUnavailable,
|
|
2302
|
+
align
|
|
2303
|
+
]);
|
|
2304
|
+
return {
|
|
2305
|
+
isVisible,
|
|
2306
|
+
isActive,
|
|
2307
|
+
handleTextAlign: (0, react.useCallback)(() => {
|
|
2308
|
+
if (!editor) return false;
|
|
2309
|
+
const success = setTextAlign(editor, align);
|
|
2310
|
+
if (success) onAligned?.();
|
|
2311
|
+
return success;
|
|
2312
|
+
}, [
|
|
2313
|
+
editor,
|
|
2314
|
+
align,
|
|
2315
|
+
onAligned
|
|
2316
|
+
]),
|
|
2317
|
+
canAlign,
|
|
2318
|
+
label: textAlignLabels[align],
|
|
2319
|
+
shortcutKeys: TEXT_ALIGN_SHORTCUT_KEYS[align],
|
|
2320
|
+
Icon: textAlignIcons[align]
|
|
2321
|
+
};
|
|
2322
|
+
}
|
|
2323
|
+
//#endregion
|
|
2324
|
+
//#region src/tiptap/components/ui/text-align-button/text-align-button.tsx
|
|
2325
|
+
function TextAlignShortcutBadge({ align, shortcutKeys = TEXT_ALIGN_SHORTCUT_KEYS[align] }) {
|
|
2326
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_mui_material_Badge.default, { children: parseShortcutKeys({ shortcutKeys }) });
|
|
2327
|
+
}
|
|
2328
|
+
/**
|
|
2329
|
+
* Button component for setting text alignment in a Tiptap editor.
|
|
2330
|
+
*
|
|
2331
|
+
* For custom button implementations, use the `useTextAlign` hook instead.
|
|
2332
|
+
*/
|
|
2333
|
+
var TextAlignButton = (0, react.forwardRef)(({ editor: providedEditor, align, text, hideWhenUnavailable = false, onAligned, showShortcut = false, onClick, icon: CustomIcon, children, ...buttonProps }, ref) => {
|
|
2334
|
+
const { editor } = useTiptapEditor(providedEditor);
|
|
2335
|
+
const { isVisible, handleTextAlign, label, canAlign, isActive, Icon, shortcutKeys } = useTextAlign({
|
|
2336
|
+
editor,
|
|
2337
|
+
align,
|
|
2338
|
+
hideWhenUnavailable,
|
|
2339
|
+
onAligned
|
|
2340
|
+
});
|
|
2341
|
+
const handleClick = (0, react.useCallback)((event) => {
|
|
2342
|
+
onClick?.(event, !isActive);
|
|
2343
|
+
if (event.defaultPrevented) return;
|
|
2344
|
+
handleTextAlign();
|
|
2345
|
+
}, [handleTextAlign, onClick]);
|
|
2346
|
+
if (!isVisible) return null;
|
|
2347
|
+
const RenderIcon = CustomIcon ?? Icon;
|
|
2348
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Button, {
|
|
2349
|
+
value: align,
|
|
2350
|
+
type: "button",
|
|
2351
|
+
disabled: !canAlign,
|
|
2352
|
+
selected: isActive,
|
|
2353
|
+
"data-disabled": !canAlign,
|
|
2354
|
+
role: "button",
|
|
2355
|
+
tabIndex: -1,
|
|
2356
|
+
"aria-label": label,
|
|
2357
|
+
tooltip: label,
|
|
2358
|
+
onClick: handleClick,
|
|
2359
|
+
...buttonProps,
|
|
2360
|
+
ref,
|
|
2361
|
+
children: children ?? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
|
|
2362
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(RenderIcon, { fontSize: "small" }),
|
|
2363
|
+
text && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
2364
|
+
className: "tiptap-button-text",
|
|
2365
|
+
children: text
|
|
2366
|
+
}),
|
|
2367
|
+
showShortcut && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(TextAlignShortcutBadge, {
|
|
2368
|
+
align,
|
|
2369
|
+
shortcutKeys
|
|
2370
|
+
})
|
|
2371
|
+
] })
|
|
2372
|
+
});
|
|
2373
|
+
});
|
|
2374
|
+
TextAlignButton.displayName = "TextAlignButton";
|
|
2375
|
+
//#endregion
|
|
2376
|
+
//#region ../../node_modules/.pnpm/@mui+icons-material@9.4.0_@mui+material@9.4.0_@emotion+react@11.14.0_@types+react@19.2._02cb1dd638d2ccdea7d16c50ea4ea174/node_modules/@mui/icons-material/RedoRounded.mjs
|
|
2377
|
+
var RedoRounded_default = (0, _mui_material_SvgIcon.createSvgIcon)(/*#__PURE__*/ (0, react_jsx_runtime.jsx)("path", { d: "M18.4 10.6C16.55 8.99 14.15 8 11.5 8c-4.16 0-7.74 2.42-9.44 5.93-.32.67.04 1.47.75 1.71.59.2 1.23-.08 1.5-.64 1.3-2.66 4.03-4.5 7.19-4.5 1.95 0 3.73.72 5.12 1.88l-1.91 1.91c-.63.63-.19 1.71.7 1.71H21c.55 0 1-.45 1-1V9.41c0-.89-1.08-1.34-1.71-.71z" }), "RedoRounded");
|
|
2378
|
+
//#endregion
|
|
2379
|
+
//#region ../../node_modules/.pnpm/@mui+icons-material@9.4.0_@mui+material@9.4.0_@emotion+react@11.14.0_@types+react@19.2._02cb1dd638d2ccdea7d16c50ea4ea174/node_modules/@mui/icons-material/UndoRounded.mjs
|
|
2380
|
+
var UndoRounded_default = (0, _mui_material_SvgIcon.createSvgIcon)(/*#__PURE__*/ (0, react_jsx_runtime.jsx)("path", { d: "M12.5 8c-2.65 0-5.05.99-6.9 2.6L3.71 8.71C3.08 8.08 2 8.52 2 9.41V15c0 .55.45 1 1 1h5.59c.89 0 1.34-1.08.71-1.71l-1.91-1.91c1.39-1.16 3.16-1.88 5.12-1.88 3.16 0 5.89 1.84 7.19 4.5.27.56.91.84 1.5.64.71-.23 1.07-1.04.75-1.72C20.23 10.42 16.65 8 12.5 8" }), "UndoRounded");
|
|
2381
|
+
//#endregion
|
|
2382
|
+
//#region src/tiptap/components/ui/undo-redo-button/use-undo-redo.ts
|
|
2383
|
+
var UNDO_REDO_SHORTCUT_KEYS = {
|
|
2384
|
+
undo: "mod+z",
|
|
2385
|
+
redo: "mod+shift+z"
|
|
2386
|
+
};
|
|
2387
|
+
var historyActionLabels = {
|
|
2388
|
+
undo: "Annuler",
|
|
2389
|
+
redo: "Rétablir"
|
|
2390
|
+
};
|
|
2391
|
+
var historyIcons = {
|
|
2392
|
+
undo: UndoRounded_default,
|
|
2393
|
+
redo: RedoRounded_default
|
|
2394
|
+
};
|
|
2395
|
+
/**
|
|
2396
|
+
* Checks if a history action can be executed
|
|
2397
|
+
*/
|
|
2398
|
+
function canExecuteUndoRedoAction(editor, action) {
|
|
2399
|
+
if (!editor || editor.isDestroyed || !editor.isEditable) return false;
|
|
2400
|
+
try {
|
|
2401
|
+
if (isNodeTypeSelected(editor, ["image"])) return false;
|
|
2402
|
+
const commands = editor.can();
|
|
2403
|
+
const command = action === "undo" ? commands.undo : commands.redo;
|
|
2404
|
+
return typeof command === "function" ? command() : false;
|
|
2405
|
+
} catch {
|
|
2406
|
+
return false;
|
|
2407
|
+
}
|
|
2408
|
+
}
|
|
2409
|
+
/**
|
|
2410
|
+
* Executes a history action on the editor
|
|
2411
|
+
*/
|
|
2412
|
+
function executeUndoRedoAction(editor, action) {
|
|
2413
|
+
if (!editor || editor.isDestroyed || !editor.isEditable) return false;
|
|
2414
|
+
if (!canExecuteUndoRedoAction(editor, action)) return false;
|
|
2415
|
+
try {
|
|
2416
|
+
const chain = editor.chain().focus();
|
|
2417
|
+
return action === "undo" ? chain.undo().run() : chain.redo().run();
|
|
2418
|
+
} catch {
|
|
2419
|
+
return false;
|
|
2420
|
+
}
|
|
2421
|
+
}
|
|
2422
|
+
/**
|
|
2423
|
+
* Determines if the history button should be shown
|
|
2424
|
+
*/
|
|
2425
|
+
function shouldShowButton(props) {
|
|
2426
|
+
const { editor, hideWhenUnavailable, action } = props;
|
|
2427
|
+
if (!editor) return false;
|
|
2428
|
+
if (!hideWhenUnavailable) return true;
|
|
2429
|
+
if (!editor.isEditable) return false;
|
|
2430
|
+
if (!editor.isActive("code")) return canExecuteUndoRedoAction(editor, action);
|
|
2431
|
+
return true;
|
|
2432
|
+
}
|
|
2433
|
+
/**
|
|
2434
|
+
* Custom hook that provides history functionality for Tiptap editor
|
|
2435
|
+
*
|
|
2436
|
+
* @example
|
|
2437
|
+
* ```tsx
|
|
2438
|
+
* // Simple usage
|
|
2439
|
+
* function MySimpleUndoButton() {
|
|
2440
|
+
* const { isVisible, handleAction } = useHistory({ action: "undo" })
|
|
2441
|
+
*
|
|
2442
|
+
* if (!isVisible) return null
|
|
2443
|
+
*
|
|
2444
|
+
* return <button onClick={handleAction}>Undo</button>
|
|
2445
|
+
* }
|
|
2446
|
+
*
|
|
2447
|
+
* // Advanced usage with configuration
|
|
2448
|
+
* function MyAdvancedRedoButton() {
|
|
2449
|
+
* const { isVisible, handleAction, label } = useHistory({
|
|
2450
|
+
* editor: myEditor,
|
|
2451
|
+
* action: "redo",
|
|
2452
|
+
* hideWhenUnavailable: true,
|
|
2453
|
+
* onExecuted: () => console.log('Action executed!')
|
|
2454
|
+
* })
|
|
2455
|
+
*
|
|
2456
|
+
* if (!isVisible) return null
|
|
2457
|
+
*
|
|
2458
|
+
* return (
|
|
2459
|
+
* <MyButton
|
|
2460
|
+
* onClick={handleAction}
|
|
2461
|
+
* aria-label={label}
|
|
2462
|
+
* >
|
|
2463
|
+
* Redo
|
|
2464
|
+
* </MyButton>
|
|
2465
|
+
* )
|
|
2466
|
+
* }
|
|
2467
|
+
* ```
|
|
2468
|
+
*/
|
|
2469
|
+
function useUndoRedo(config) {
|
|
2470
|
+
const { editor: providedEditor, action, hideWhenUnavailable = false, onExecuted } = config;
|
|
2471
|
+
const { editor } = useTiptapEditor(providedEditor);
|
|
2472
|
+
const [isVisible, setIsVisible] = (0, react.useState)(true);
|
|
2473
|
+
const canExecute = canExecuteUndoRedoAction(editor, action);
|
|
2474
|
+
(0, react.useEffect)(() => {
|
|
2475
|
+
if (!editor) return;
|
|
2476
|
+
const handleUpdate = () => {
|
|
2477
|
+
setIsVisible(shouldShowButton({
|
|
2478
|
+
editor,
|
|
2479
|
+
hideWhenUnavailable,
|
|
2480
|
+
action
|
|
2481
|
+
}));
|
|
2482
|
+
};
|
|
2483
|
+
handleUpdate();
|
|
2484
|
+
editor.on("transaction", handleUpdate);
|
|
2485
|
+
return () => {
|
|
2486
|
+
editor.off("transaction", handleUpdate);
|
|
2487
|
+
};
|
|
2488
|
+
}, [
|
|
2489
|
+
editor,
|
|
2490
|
+
hideWhenUnavailable,
|
|
2491
|
+
action
|
|
2492
|
+
]);
|
|
2493
|
+
return {
|
|
2494
|
+
isVisible,
|
|
2495
|
+
handleAction: (0, react.useCallback)(() => {
|
|
2496
|
+
if (!editor) return false;
|
|
2497
|
+
const success = executeUndoRedoAction(editor, action);
|
|
2498
|
+
if (success) onExecuted?.();
|
|
2499
|
+
return success;
|
|
2500
|
+
}, [
|
|
2501
|
+
editor,
|
|
2502
|
+
action,
|
|
2503
|
+
onExecuted
|
|
2504
|
+
]),
|
|
2505
|
+
canExecute,
|
|
2506
|
+
label: historyActionLabels[action],
|
|
2507
|
+
shortcutKeys: UNDO_REDO_SHORTCUT_KEYS[action],
|
|
2508
|
+
Icon: historyIcons[action]
|
|
2509
|
+
};
|
|
2510
|
+
}
|
|
2511
|
+
//#endregion
|
|
2512
|
+
//#region src/tiptap/components/ui/undo-redo-button/undo-redo-button.tsx
|
|
2513
|
+
function HistoryShortcutBadge({ action, shortcutKeys = UNDO_REDO_SHORTCUT_KEYS[action] }) {
|
|
2514
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_mui_material_Badge.default, { children: parseShortcutKeys({ shortcutKeys }) });
|
|
2515
|
+
}
|
|
2516
|
+
/**
|
|
2517
|
+
* Button component for triggering undo/redo actions in a Tiptap editor.
|
|
2518
|
+
*
|
|
2519
|
+
* For custom button implementations, use the `useHistory` hook instead.
|
|
2520
|
+
*/
|
|
2521
|
+
var UndoRedoButton = (0, react.forwardRef)(({ editor: providedEditor, action, text, hideWhenUnavailable = false, onExecuted, showShortcut = false, onClick, children, ...buttonProps }, ref) => {
|
|
2522
|
+
const { editor } = useTiptapEditor(providedEditor);
|
|
2523
|
+
const { isVisible, handleAction, label, canExecute, Icon, shortcutKeys } = useUndoRedo({
|
|
2524
|
+
editor,
|
|
2525
|
+
action,
|
|
2526
|
+
hideWhenUnavailable,
|
|
2527
|
+
onExecuted
|
|
2528
|
+
});
|
|
2529
|
+
const handleClick = (0, react.useCallback)((event) => {
|
|
2530
|
+
onClick?.(event, true);
|
|
2531
|
+
if (event.defaultPrevented) return;
|
|
2532
|
+
handleAction();
|
|
2533
|
+
}, [handleAction, onClick]);
|
|
2534
|
+
if (!isVisible) return null;
|
|
2535
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Button, {
|
|
2536
|
+
value: action,
|
|
2537
|
+
type: "button",
|
|
2538
|
+
disabled: !canExecute,
|
|
2539
|
+
"data-disabled": !canExecute,
|
|
2540
|
+
role: "button",
|
|
2541
|
+
tabIndex: -1,
|
|
2542
|
+
"aria-label": label,
|
|
2543
|
+
tooltip: label,
|
|
2544
|
+
onClick: handleClick,
|
|
2545
|
+
...buttonProps,
|
|
2546
|
+
ref,
|
|
2547
|
+
children: children ?? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
|
|
2548
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Icon, { fontSize: "small" }),
|
|
2549
|
+
text && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
2550
|
+
className: "tiptap-button-text",
|
|
2551
|
+
children: text
|
|
2552
|
+
}),
|
|
2553
|
+
showShortcut && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(HistoryShortcutBadge, {
|
|
2554
|
+
action,
|
|
2555
|
+
shortcutKeys
|
|
2556
|
+
})
|
|
2557
|
+
] })
|
|
2558
|
+
});
|
|
2559
|
+
});
|
|
2560
|
+
UndoRedoButton.displayName = "UndoRedoButton";
|
|
2561
|
+
//#endregion
|
|
2562
|
+
//#region src/tiptap/hooks/use-composed-ref.ts
|
|
2563
|
+
var updateRef = (ref, value) => {
|
|
2564
|
+
if (typeof ref === "function") ref(value);
|
|
2565
|
+
else if (ref && typeof ref === "object" && "current" in ref) {
|
|
2566
|
+
const mutableRef = ref;
|
|
2567
|
+
mutableRef.current = value;
|
|
2568
|
+
}
|
|
2569
|
+
};
|
|
2570
|
+
var useComposedRef = (libRef, userRef) => {
|
|
2571
|
+
const prevUserRef = (0, react.useRef)(null);
|
|
2572
|
+
return (0, react.useCallback)((instance) => {
|
|
2573
|
+
if (libRef && "current" in libRef) {
|
|
2574
|
+
const mutableLibRef = libRef;
|
|
2575
|
+
mutableLibRef.current = instance;
|
|
2576
|
+
}
|
|
2577
|
+
if (prevUserRef.current) updateRef(prevUserRef.current, null);
|
|
2578
|
+
prevUserRef.current = userRef;
|
|
2579
|
+
if (userRef) updateRef(userRef, instance);
|
|
2580
|
+
}, [libRef, userRef]);
|
|
2581
|
+
};
|
|
2582
|
+
//#endregion
|
|
2583
|
+
//#region src/tiptap/hooks/use-menu-navigation.ts
|
|
2584
|
+
/**
|
|
2585
|
+
* Hook that implements keyboard navigation for dropdown menus and command palettes.
|
|
2586
|
+
*
|
|
2587
|
+
* Handles arrow keys, tab, home/end, enter for selection, and escape to close.
|
|
2588
|
+
* Works with both Tiptap editors and regular DOM elements.
|
|
2589
|
+
*
|
|
2590
|
+
* @param options - Configuration options for the menu navigation
|
|
2591
|
+
* @returns Object containing the selected index and a setter function
|
|
2592
|
+
*/
|
|
2593
|
+
function useMenuNavigation({ editor, containerRef, query, items, onSelect, onClose, orientation = "vertical", autoSelectFirstItem = true, loopOnTab = true }) {
|
|
2594
|
+
const [selectedIndex, setSelectedIndex] = (0, react.useState)(autoSelectFirstItem ? 0 : -1);
|
|
2595
|
+
(0, react.useEffect)(() => {
|
|
2596
|
+
const handleKeyboardNavigation = (event) => {
|
|
2597
|
+
if (!items.length) return false;
|
|
2598
|
+
const moveNext = () => setSelectedIndex((currentIndex) => {
|
|
2599
|
+
if (currentIndex === -1) return 0;
|
|
2600
|
+
return (currentIndex + 1) % items.length;
|
|
2601
|
+
});
|
|
2602
|
+
const movePrev = () => setSelectedIndex((currentIndex) => {
|
|
2603
|
+
if (currentIndex === -1) return items.length - 1;
|
|
2604
|
+
return (currentIndex - 1 + items.length) % items.length;
|
|
2605
|
+
});
|
|
2606
|
+
switch (event.key) {
|
|
2607
|
+
case "ArrowUp":
|
|
2608
|
+
if (orientation === "horizontal") return false;
|
|
2609
|
+
event.preventDefault();
|
|
2610
|
+
movePrev();
|
|
2611
|
+
return true;
|
|
2612
|
+
case "ArrowDown":
|
|
2613
|
+
if (orientation === "horizontal") return false;
|
|
2614
|
+
event.preventDefault();
|
|
2615
|
+
moveNext();
|
|
2616
|
+
return true;
|
|
2617
|
+
case "ArrowLeft":
|
|
2618
|
+
if (orientation === "vertical") return false;
|
|
2619
|
+
event.preventDefault();
|
|
2620
|
+
movePrev();
|
|
2621
|
+
return true;
|
|
2622
|
+
case "ArrowRight":
|
|
2623
|
+
if (orientation === "vertical") return false;
|
|
2624
|
+
event.preventDefault();
|
|
2625
|
+
moveNext();
|
|
2626
|
+
return true;
|
|
2627
|
+
case "Tab":
|
|
2628
|
+
if (!loopOnTab) return false;
|
|
2629
|
+
event.preventDefault();
|
|
2630
|
+
if (event.shiftKey) movePrev();
|
|
2631
|
+
else moveNext();
|
|
2632
|
+
return true;
|
|
2633
|
+
case "Home":
|
|
2634
|
+
event.preventDefault();
|
|
2635
|
+
setSelectedIndex(0);
|
|
2636
|
+
return true;
|
|
2637
|
+
case "End":
|
|
2638
|
+
event.preventDefault();
|
|
2639
|
+
setSelectedIndex(items.length - 1);
|
|
2640
|
+
return true;
|
|
2641
|
+
case "Enter":
|
|
2642
|
+
if (event.isComposing) return false;
|
|
2643
|
+
event.preventDefault();
|
|
2644
|
+
if (selectedIndex !== -1 && items[selectedIndex]) onSelect?.(items[selectedIndex]);
|
|
2645
|
+
return true;
|
|
2646
|
+
case "Escape":
|
|
2647
|
+
if (!onClose) return false;
|
|
2648
|
+
event.preventDefault();
|
|
2649
|
+
onClose?.();
|
|
2650
|
+
return true;
|
|
2651
|
+
default: return false;
|
|
2652
|
+
}
|
|
2653
|
+
};
|
|
2654
|
+
let targetElement = null;
|
|
2655
|
+
if (editor) targetElement = editor.view.dom;
|
|
2656
|
+
else if (containerRef?.current) targetElement = containerRef.current;
|
|
2657
|
+
if (targetElement) {
|
|
2658
|
+
targetElement.addEventListener("keydown", handleKeyboardNavigation, true);
|
|
2659
|
+
return () => {
|
|
2660
|
+
targetElement?.removeEventListener("keydown", handleKeyboardNavigation, true);
|
|
2661
|
+
};
|
|
2662
|
+
}
|
|
2663
|
+
}, [
|
|
2664
|
+
editor,
|
|
2665
|
+
containerRef,
|
|
2666
|
+
items,
|
|
2667
|
+
selectedIndex,
|
|
2668
|
+
onSelect,
|
|
2669
|
+
onClose,
|
|
2670
|
+
orientation,
|
|
2671
|
+
loopOnTab
|
|
2672
|
+
]);
|
|
2673
|
+
(0, react.useEffect)(() => {
|
|
2674
|
+
if (query) setSelectedIndex(autoSelectFirstItem ? 0 : -1);
|
|
2675
|
+
}, [query, autoSelectFirstItem]);
|
|
2676
|
+
return {
|
|
2677
|
+
selectedIndex: items.length ? selectedIndex : void 0,
|
|
2678
|
+
setSelectedIndex
|
|
2679
|
+
};
|
|
2680
|
+
}
|
|
2681
|
+
//#endregion
|
|
2682
|
+
//#region src/tiptap/components/ui-primitive/toolbar/toolbar.tsx
|
|
2683
|
+
var useToolbarNavigation = (toolbarRef) => {
|
|
2684
|
+
const [items, setItems] = (0, react.useState)([]);
|
|
2685
|
+
const initialContainerTabIndexRef = (0, react.useRef)(void 0);
|
|
2686
|
+
const collectItems = (0, react.useCallback)(() => {
|
|
2687
|
+
if (!toolbarRef.current) return [];
|
|
2688
|
+
return Array.from(toolbarRef.current.querySelectorAll("button:not([disabled]), [role=\"button\"]:not([disabled]), [tabindex=\"0\"]:not([disabled])"));
|
|
2689
|
+
}, [toolbarRef]);
|
|
2690
|
+
(0, react.useEffect)(() => {
|
|
2691
|
+
const toolbar = toolbarRef.current;
|
|
2692
|
+
if (!toolbar) return;
|
|
2693
|
+
if (initialContainerTabIndexRef.current === void 0) initialContainerTabIndexRef.current = toolbar.hasAttribute("tabindex") && toolbar.tabIndex >= 0 ? toolbar.tabIndex : null;
|
|
2694
|
+
const updateItems = () => setItems(collectItems());
|
|
2695
|
+
updateItems();
|
|
2696
|
+
const observer = new MutationObserver(updateItems);
|
|
2697
|
+
observer.observe(toolbar, {
|
|
2698
|
+
childList: true,
|
|
2699
|
+
subtree: true,
|
|
2700
|
+
attributes: true,
|
|
2701
|
+
attributeFilter: ["disabled"]
|
|
2702
|
+
});
|
|
2703
|
+
return () => observer.disconnect();
|
|
2704
|
+
}, [collectItems, toolbarRef]);
|
|
2705
|
+
const { selectedIndex, setSelectedIndex } = useMenuNavigation({
|
|
2706
|
+
containerRef: toolbarRef,
|
|
2707
|
+
items,
|
|
2708
|
+
orientation: "horizontal",
|
|
2709
|
+
onSelect: (el) => el.click(),
|
|
2710
|
+
autoSelectFirstItem: false,
|
|
2711
|
+
loopOnTab: false
|
|
2712
|
+
});
|
|
2713
|
+
(0, react.useEffect)(() => {
|
|
2714
|
+
const toolbar = toolbarRef.current;
|
|
2715
|
+
if (!toolbar) return;
|
|
2716
|
+
const handleFocus = (e) => {
|
|
2717
|
+
const target = e.target;
|
|
2718
|
+
if (toolbar.contains(target)) {
|
|
2719
|
+
target.setAttribute("data-focus-visible", "true");
|
|
2720
|
+
const itemIndex = items.indexOf(target);
|
|
2721
|
+
if (itemIndex >= 0) setSelectedIndex(itemIndex);
|
|
2722
|
+
}
|
|
2723
|
+
};
|
|
2724
|
+
const handleBlur = (e) => {
|
|
2725
|
+
const target = e.target;
|
|
2726
|
+
if (toolbar.contains(target)) target.removeAttribute("data-focus-visible");
|
|
2727
|
+
const nextTarget = e.relatedTarget;
|
|
2728
|
+
const NodeConstructor = toolbar.ownerDocument.defaultView?.Node;
|
|
2729
|
+
if ((!nextTarget || !NodeConstructor || !(nextTarget instanceof NodeConstructor) || !toolbar.contains(nextTarget)) && initialContainerTabIndexRef.current != null) queueMicrotask(() => {
|
|
2730
|
+
if (toolbar.isConnected && !toolbar.contains(toolbar.ownerDocument.activeElement)) {
|
|
2731
|
+
toolbar.tabIndex = initialContainerTabIndexRef.current;
|
|
2732
|
+
setSelectedIndex(-1);
|
|
2733
|
+
}
|
|
2734
|
+
});
|
|
2735
|
+
};
|
|
2736
|
+
toolbar.addEventListener("focus", handleFocus, true);
|
|
2737
|
+
toolbar.addEventListener("blur", handleBlur, true);
|
|
2738
|
+
return () => {
|
|
2739
|
+
toolbar.removeEventListener("focus", handleFocus, true);
|
|
2740
|
+
toolbar.removeEventListener("blur", handleBlur, true);
|
|
2741
|
+
};
|
|
2742
|
+
}, [
|
|
2743
|
+
items,
|
|
2744
|
+
setSelectedIndex,
|
|
2745
|
+
toolbarRef
|
|
2746
|
+
]);
|
|
2747
|
+
(0, react.useEffect)(() => {
|
|
2748
|
+
if (!items.length) return;
|
|
2749
|
+
const toolbar = toolbarRef.current;
|
|
2750
|
+
if (!toolbar) return;
|
|
2751
|
+
const activeEl = toolbar.ownerDocument.activeElement;
|
|
2752
|
+
if (activeEl && !toolbar.contains(activeEl)) return;
|
|
2753
|
+
const initialContainerTabIndex = initialContainerTabIndexRef.current;
|
|
2754
|
+
const currentIndex = selectedIndex ?? -1;
|
|
2755
|
+
if (initialContainerTabIndex != null) {
|
|
2756
|
+
items.forEach((item) => {
|
|
2757
|
+
item.tabIndex = -1;
|
|
2758
|
+
});
|
|
2759
|
+
if (currentIndex >= 0 && items[currentIndex]) {
|
|
2760
|
+
toolbar.tabIndex = -1;
|
|
2761
|
+
items[currentIndex].focus();
|
|
2762
|
+
} else toolbar.tabIndex = initialContainerTabIndex;
|
|
2763
|
+
return;
|
|
2764
|
+
}
|
|
2765
|
+
const existingTabStop = items.findIndex((item) => item.tabIndex === 0);
|
|
2766
|
+
const activeIndex = currentIndex >= 0 && items[currentIndex] ? currentIndex : existingTabStop >= 0 ? existingTabStop : 0;
|
|
2767
|
+
items.forEach((item, index) => {
|
|
2768
|
+
item.tabIndex = index === activeIndex ? 0 : -1;
|
|
2769
|
+
});
|
|
2770
|
+
if (currentIndex >= 0) items[activeIndex]?.focus();
|
|
2771
|
+
}, [
|
|
2772
|
+
selectedIndex,
|
|
2773
|
+
items,
|
|
2774
|
+
toolbarRef
|
|
2775
|
+
]);
|
|
2776
|
+
};
|
|
2777
|
+
var Toolbar = (0, react.forwardRef)(({ children, className, variant = "fixed", ...props }, ref) => {
|
|
2778
|
+
const toolbarRef = (0, react.useRef)(null);
|
|
2779
|
+
const composedRef = useComposedRef(toolbarRef, ref);
|
|
2780
|
+
useToolbarNavigation(toolbarRef);
|
|
2781
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
2782
|
+
ref: composedRef,
|
|
2783
|
+
role: "toolbar",
|
|
2784
|
+
"aria-label": "toolbar",
|
|
2785
|
+
"data-variant": variant,
|
|
2786
|
+
className: cn("tiptap-toolbar", className),
|
|
2787
|
+
...props,
|
|
2788
|
+
children
|
|
2789
|
+
});
|
|
2790
|
+
});
|
|
2791
|
+
Toolbar.displayName = "Toolbar";
|
|
2792
|
+
var ToolbarGroup = (0, react.forwardRef)(({ children, className, ...props }, ref) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
2793
|
+
ref,
|
|
2794
|
+
role: "group",
|
|
2795
|
+
className: cn("tiptap-toolbar-group", className),
|
|
2796
|
+
...props,
|
|
2797
|
+
children
|
|
2798
|
+
}));
|
|
2799
|
+
ToolbarGroup.displayName = "ToolbarGroup";
|
|
2800
|
+
var ToolbarSeparator = () => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_mui_material_Divider.default, {
|
|
2801
|
+
orientation: "vertical",
|
|
2802
|
+
flexItem: true,
|
|
2803
|
+
sx: { my: 1 }
|
|
2804
|
+
});
|
|
2805
|
+
ToolbarSeparator.displayName = "ToolbarSeparator";
|
|
2806
|
+
//#endregion
|
|
2807
|
+
//#region src/tiptap/components/ui-primitive/formatting-controls/formatting-controls.tsx
|
|
2808
|
+
var FormattingControls = ({ editor }) => {
|
|
2809
|
+
const hasSuperscriptSubscript = editor.extensionManager.extensions.map((e) => e.name).some((name) => name === "subscript" || name === "superscript");
|
|
2810
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
|
|
2811
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)(ToolbarGroup, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(UndoRedoButton, { action: "undo" }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(UndoRedoButton, { action: "redo" })] }),
|
|
2812
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(ToolbarSeparator, {}),
|
|
2813
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)(ToolbarGroup, { children: [
|
|
2814
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(HeadingMenu, {
|
|
2815
|
+
modal: false,
|
|
2816
|
+
levels: [
|
|
2817
|
+
1,
|
|
2818
|
+
2,
|
|
2819
|
+
3,
|
|
2820
|
+
4
|
|
2821
|
+
]
|
|
2822
|
+
}),
|
|
2823
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(ListButton, { type: "bulletList" }),
|
|
2824
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(ListButton, { type: "orderedList" }),
|
|
2825
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(BlockquoteButton, {}),
|
|
2826
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(CodeBlockButton, {})
|
|
2827
|
+
] }),
|
|
2828
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(ToolbarSeparator, {}),
|
|
2829
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)(ToolbarGroup, { children: [
|
|
2830
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(MarkButton, { type: "bold" }),
|
|
2831
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(MarkButton, { type: "italic" }),
|
|
2832
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(MarkButton, { type: "strike" }),
|
|
2833
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(MarkButton, { type: "code" }),
|
|
2834
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(MarkButton, { type: "underline" }),
|
|
2835
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(LinkPopover, {})
|
|
2836
|
+
] }),
|
|
2837
|
+
hasSuperscriptSubscript ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(ToolbarSeparator, {}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(ToolbarGroup, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(MarkButton, { type: "superscript" }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(MarkButton, { type: "subscript" })] })] }) : null,
|
|
2838
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(ToolbarSeparator, {}),
|
|
2839
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)(ToolbarGroup, { children: [
|
|
2840
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(TextAlignButton, { align: "left" }),
|
|
2841
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(TextAlignButton, { align: "center" }),
|
|
2842
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(TextAlignButton, { align: "right" }),
|
|
2843
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(TextAlignButton, { align: "justify" })
|
|
2844
|
+
] })
|
|
2845
|
+
] });
|
|
2846
|
+
};
|
|
2847
|
+
FormattingControls.displayName = "FormattingControls";
|
|
2848
|
+
//#endregion
|
|
2849
|
+
//#region src/tiptap/components/ui-primitive/spacer/spacer.tsx
|
|
2850
|
+
function Spacer({ orientation = "horizontal", size, style = {}, ...props }) {
|
|
2851
|
+
const computedStyle = {
|
|
2852
|
+
...style,
|
|
2853
|
+
...orientation === "horizontal" && !size && { flex: 1 },
|
|
2854
|
+
...size && {
|
|
2855
|
+
width: orientation === "vertical" ? "1px" : size,
|
|
2856
|
+
height: orientation === "horizontal" ? "1px" : size
|
|
2857
|
+
}
|
|
2858
|
+
};
|
|
2859
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
2860
|
+
...props,
|
|
2861
|
+
style: computedStyle
|
|
2862
|
+
});
|
|
2863
|
+
}
|
|
2864
|
+
//#endregion
|
|
2865
|
+
exports.FormattingControls = FormattingControls;
|
|
2866
|
+
exports.Spacer = Spacer;
|
|
2867
|
+
exports.Toolbar = Toolbar;
|
|
2868
|
+
exports.ToolbarGroup = ToolbarGroup;
|
|
2869
|
+
exports.ToolbarSeparator = ToolbarSeparator;
|
|
2870
|
+
|
|
2871
|
+
//# sourceMappingURL=index.cjs.js.map
|