smartrte-react 1.0.0-beta.8 → 1.1.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/CHANGELOG.md +50 -0
- package/README.md +239 -745
- package/dist/components/BlockquoteBorderPopover.d.ts +30 -0
- package/dist/components/BlockquoteBorderPopover.js +115 -0
- package/dist/components/CanonicalAuthorityEditor.js +289 -41
- package/dist/components/ColorPickerPopover.js +8 -7
- package/dist/components/MediaOverlay.d.ts +3 -1
- package/dist/components/MediaOverlay.js +2 -2
- package/dist/components/TableBorderPopover.js +8 -7
- package/dist/components/TableResizeHandles.js +22 -0
- package/dist/components/ToolbarPrimitives.d.ts +20 -4
- package/dist/components/ToolbarPrimitives.js +29 -21
- package/dist/components/fixedPositioning.d.ts +108 -0
- package/dist/components/fixedPositioning.js +139 -0
- package/dist/theme.d.ts +1 -1
- package/dist/theme.js +88 -0
- package/dist/toolbarTools.d.ts +6 -0
- package/dist/toolbarTools.js +3 -2
- package/package.json +13 -12
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { forwardRef, useEffect, useImperativeHandle, useMemo, useRef, useState } from "react";
|
|
3
|
-
import { atomDeclarations, continueListNumbering, createList, createNodeId, deleteAtom, executeMarkTool, indentBlockCommand, indentList, inlineToolDeclarations, isTextNode, insertAtom, insertTableColumnCommand, insertTableCommand, insertTableRowCommand, mergeTableCellsCommand, moveListItems, moveTableColumnCommand, moveTableRowCommand, setTableCellAttributesCommand, setTableColumnWidthCommand, setTableRowHeightCommand, parseCanonicalListHtml, parseCanonicalListMarkdown, removeTableColumnCommand, removeTableCommand, removeTableRowCommand, resizeAtom, runAtomUpload, restartListNumbering, serializeCanonicalListHtml, serializeCanonicalListMarkdown, setListChecked, setListPreset, setListStyle, setTableHeaderCommand, setBlockAttributes, setBlockTypeCommand, moveBlockCommand, outdentBlockCommand, splitTableCellCommand, unwrapBlocks, wrapBlocks, unwrapList, updateAtom, outdentList, commentRangeFromSelection, rebaseCommentThreadsThroughTransaction, foundationSchema, resolvePos, suggestDeleteCommand, suggestInsertOperation, acceptSuggestionCommand, rejectSuggestionCommand, structuralSuggestionFromNode, acceptStructuralSuggestionCommand, rebaseStructuralSuggestionsThroughTransaction, } from "smartrte-core/foundation";
|
|
3
|
+
import { atomDeclarations, continueListNumbering, createList, createNodeId, deleteAtom, executeMarkTool, indentBlockCommand, indentList, inlineToolDeclarations, isTextNode, insertAtom, insertTableColumnCommand, insertTableCommand, insertTableRowCommand, mergeTableCellsCommand, moveListItems, moveTableColumnCommand, moveTableRowCommand, setTableCellAttributesCommand, setTableColumnWidthCommand, setTableRowHeightCommand, parseCanonicalListHtml, parseCanonicalListMarkdown, removeTableColumnCommand, removeTableCommand, removeTableRowCommand, reportMarkApplication, resizeAtom, runAtomUpload, restartListNumbering, serializeCanonicalListHtml, serializeCanonicalListMarkdown, setListChecked, setListPreset, setListStyle, setTableHeaderCommand, setBlockAttributes, setBlockTypeCommand, moveBlockCommand, outdentBlockCommand, splitTableCellCommand, unwrapBlocks, wrapBlocks, unwrapList, updateAtom, outdentList, commentRangeFromSelection, rebaseCommentThreadsThroughTransaction, foundationSchema, resolvePos, suggestDeleteCommand, suggestInsertOperation, acceptSuggestionCommand, rejectSuggestionCommand, structuralSuggestionFromNode, acceptStructuralSuggestionCommand, rebaseStructuralSuggestionsThroughTransaction, } from "smartrte-core/foundation";
|
|
4
4
|
import { FOUNDATION_SMART_LIST_PRESETS as SMART_LIST_PRESETS } from "smartrte-core/foundation";
|
|
5
5
|
import { ensureStyleSheet } from "../theme.js";
|
|
6
6
|
import { VersionHistoryPanel } from "./VersionHistoryPanel.js";
|
|
@@ -17,6 +17,7 @@ import { TableSizePickerPopover } from "./TableSizePickerPopover.js";
|
|
|
17
17
|
import { FormulaLibraryPopover } from "./FormulaLibraryPopover.js";
|
|
18
18
|
import { SpecialCharacterPopover } from "./SpecialCharacterPopover.js";
|
|
19
19
|
import { TableBorderPopover, BORDER_WIDTH_PRESETS } from "./TableBorderPopover.js";
|
|
20
|
+
import { BlockquoteBorderPopover } from "./BlockquoteBorderPopover.js";
|
|
20
21
|
import { TableResizeHandles } from "./TableResizeHandles.js";
|
|
21
22
|
import { MediaOverlay } from "./MediaOverlay.js";
|
|
22
23
|
import { MediaDetailsPopover } from "./MediaDetailsPopover.js";
|
|
@@ -129,10 +130,23 @@ export const CanonicalAuthorityEditor = forwardRef(function CanonicalAuthorityEd
|
|
|
129
130
|
const [linkPopover, setLinkPopover] = useState(null);
|
|
130
131
|
const [colorPopover, setColorPopover] = useState(null);
|
|
131
132
|
const [recentColors, setRecentColors] = useState({ text: [], background: [], border: [] });
|
|
133
|
+
/**
|
|
134
|
+
* "Let both bullet and number list tool icon work as toggle and on click
|
|
135
|
+
* text should [adopt] respective preset list" - the plain Bulleted
|
|
136
|
+
* list/Numbered list buttons always applied a generic disc/decimal
|
|
137
|
+
* marker, ignoring whatever glyph preset (e.g. "❖ ➢ ■") the user had
|
|
138
|
+
* already picked from the "List preset" dropdown elsewhere in the same
|
|
139
|
+
* session. Session-only React state, matching recentColors' own
|
|
140
|
+
* documented "no localStorage" convention above - not persisted across
|
|
141
|
+
* reloads.
|
|
142
|
+
*/
|
|
143
|
+
const [lastListPreset, setLastListPreset] = useState({ bullet: null, ordered: null });
|
|
132
144
|
const [tableSizePopover, setTableSizePopover] = useState(null);
|
|
133
145
|
const [tableBorderPopover, setTableBorderPopover] = useState(null);
|
|
146
|
+
const [blockquoteBorderPopover, setBlockquoteBorderPopover] = useState(null);
|
|
134
147
|
const [mediaDetailsPopover, setMediaDetailsPopover] = useState(null);
|
|
135
148
|
const borderPreviewCheckpointRef = useRef(null);
|
|
149
|
+
const blockquoteBorderPreviewCheckpointRef = useRef(null);
|
|
136
150
|
const [formulaLibraryPopover, setFormulaLibraryPopover] = useState(null);
|
|
137
151
|
const [specialCharPopover, setSpecialCharPopover] = useState(null);
|
|
138
152
|
const [recentSpecialChars, setRecentSpecialChars] = useState([]);
|
|
@@ -272,6 +286,35 @@ export const CanonicalAuthorityEditor = forwardRef(function CanonicalAuthorityEd
|
|
|
272
286
|
};
|
|
273
287
|
const blockScope = () => runtime.editor.resolveScope({ want: "block-range" });
|
|
274
288
|
const blockContext = () => ({ schema: runtime.editor.schema, positions: runtime.editor.positions });
|
|
289
|
+
/**
|
|
290
|
+
* `SelectionDescription` (resolveScope({want:"describe"})) tracks mark
|
|
291
|
+
* coverage (markCoverage below) but has no equivalent for block
|
|
292
|
+
* *attributes* - `blockTypes` is the closest thing, and it's just node
|
|
293
|
+
* types, not attrs. This mirrors foundation/block/commands.ts's own
|
|
294
|
+
* (core-internal, not exported) blockScopes/selectedBlockIds traversal -
|
|
295
|
+
* a "mixed" scope is a caret/selection spanning more than one real
|
|
296
|
+
* block-range part (e.g. a paragraph plus a list item), each of which
|
|
297
|
+
* carries its own blockIds.
|
|
298
|
+
*/
|
|
299
|
+
const selectedLineHeightBlockIds = (scope) => {
|
|
300
|
+
const collect = (candidate) => candidate.kind === "block-range" ? candidate.blockIds
|
|
301
|
+
: candidate.kind === "mixed" ? candidate.parts.flatMap(collect)
|
|
302
|
+
: [];
|
|
303
|
+
return [...new Set(collect(scope))];
|
|
304
|
+
};
|
|
305
|
+
/** `undefined` = no block selected/no override anywhere, a number = every selected block shares that exact value, `"mixed"` = selected blocks disagree - same three-state shape as markCoverage's boolean|"mixed"|undefined. */
|
|
306
|
+
const currentLineHeight = () => {
|
|
307
|
+
const ids = selectedLineHeightBlockIds(blockScope());
|
|
308
|
+
if (!ids.length)
|
|
309
|
+
return undefined;
|
|
310
|
+
const values = ids.map((id) => {
|
|
311
|
+
const value = findNode(runtime.editor.document, id)?.attrs?.lineHeight;
|
|
312
|
+
return typeof value === "number" ? value : undefined;
|
|
313
|
+
});
|
|
314
|
+
const unique = [...new Set(values)];
|
|
315
|
+
return unique.length === 1 ? unique[0] : "mixed";
|
|
316
|
+
};
|
|
317
|
+
const setLineHeight = (value) => transactBlock(setBlockAttributes(runtime.editor.document, blockScope(), { attrs: { lineHeight: value } }, blockContext()));
|
|
275
318
|
const listScope = () => runtime.editor.resolveScope({ want: "list-selection" });
|
|
276
319
|
const tableScope = () => runtime.editor.resolveScope({ want: "table-grid" });
|
|
277
320
|
const atomScope = () => runtime.editor.resolveScope({ want: "atomic-node" });
|
|
@@ -347,15 +390,16 @@ export const CanonicalAuthorityEditor = forwardRef(function CanonicalAuthorityEd
|
|
|
347
390
|
? runtime.surface.renderer?.mapping.nodeToDom(currentAtomScope.nodeId)
|
|
348
391
|
: undefined;
|
|
349
392
|
const selectedAtomNode = atomSelected ? findNode(runtime.editor.document, currentAtomScope.nodeId) : null;
|
|
350
|
-
// A divider (<hr>) is atomic/selectable (so ordinary caret/
|
|
351
|
-
// Delete behavior around it works, and the toolbar's "Delete
|
|
352
|
-
// atom" button applies to it), but
|
|
353
|
-
// all - MediaOverlay and the media-specific Edit/Resize
|
|
354
|
-
// every atom is a real media item, and unconditionally
|
|
355
|
-
//
|
|
356
|
-
// sensible edit or resize target. Media-only UI is gated
|
|
357
|
-
// instead of plain atomSelected; delete stays available for any
|
|
358
|
-
|
|
393
|
+
// A divider (<hr>) or page break is atomic/selectable (so ordinary caret/
|
|
394
|
+
// Backspace/Delete behavior around it works, and the toolbar's "Delete
|
|
395
|
+
// selected atom" button applies to it), but neither has any src/alt/
|
|
396
|
+
// width/height at all - MediaOverlay and the media-specific Edit/Resize
|
|
397
|
+
// actions assume every atom is a real media item, and unconditionally
|
|
398
|
+
// showing them for one of these surfaced nonsensical empty/undefined
|
|
399
|
+
// values with no sensible edit or resize target. Media-only UI is gated
|
|
400
|
+
// on this instead of plain atomSelected; delete stays available for any
|
|
401
|
+
// atom.
|
|
402
|
+
const mediaAtomSelected = atomSelected && selectedAtomNode?.type !== "divider" && selectedAtomNode?.type !== "page_break";
|
|
359
403
|
// Formula (like divider above) has no width/height concept - KaTeX sizes
|
|
360
404
|
// its own rendering from the source/font-size, not stored dimensions, so
|
|
361
405
|
// "Enlarge/Shrink selected media" and MediaOverlay's resize handle had no
|
|
@@ -366,11 +410,18 @@ export const CanonicalAuthorityEditor = forwardRef(function CanonicalAuthorityEd
|
|
|
366
410
|
// node.type.includes("formula") for its own correct "Formula source"
|
|
367
411
|
// prompt.
|
|
368
412
|
const resizableAtomSelected = mediaAtomSelected && selectedAtomNode?.type !== "formula" && selectedAtomNode?.type !== "block_formula";
|
|
413
|
+
// A divider/page break has no editable field and no width/height, but it
|
|
414
|
+
// is still a real, selectable, deletable atom - right-clicking one should
|
|
415
|
+
// surface the same kind of Delete-only action menu formula already gets
|
|
416
|
+
// (docs/bugs/horizontal-line-not-visibly-selectable.md), not the silence
|
|
417
|
+
// `mediaAtomSelected` alone produces (that flag exists to hide fields
|
|
418
|
+
// that don't apply, not to hide a delete affordance entirely).
|
|
419
|
+
const actionOnlyAtomSelected = atomSelected && (selectedAtomNode?.type === "divider" || selectedAtomNode?.type === "page_break");
|
|
369
420
|
const selectedAtomId = atomSelected && "nodeId" in currentAtomScope ? currentAtomScope.nodeId : null;
|
|
370
421
|
useEffect(() => {
|
|
371
|
-
if (mediaContextMenu && (!mediaAtomSelected || mediaContextMenu.nodeId !== selectedAtomId))
|
|
422
|
+
if (mediaContextMenu && (!(mediaAtomSelected || actionOnlyAtomSelected) || mediaContextMenu.nodeId !== selectedAtomId))
|
|
372
423
|
setMediaContextMenu(null);
|
|
373
|
-
}, [mediaContextMenu, mediaAtomSelected, selectedAtomId]);
|
|
424
|
+
}, [mediaContextMenu, mediaAtomSelected, actionOnlyAtomSelected, selectedAtomId]);
|
|
374
425
|
useEffect(() => {
|
|
375
426
|
if (mediaResizeTarget && (!mediaAtomSelected || mediaResizeTarget.nodeId !== selectedAtomId))
|
|
376
427
|
setMediaResizeTarget(null);
|
|
@@ -453,9 +504,36 @@ export const CanonicalAuthorityEditor = forwardRef(function CanonicalAuthorityEd
|
|
|
453
504
|
return "paragraph";
|
|
454
505
|
};
|
|
455
506
|
const currentBlockType = blockTypeAt(runtime.editor.selection.head);
|
|
507
|
+
/**
|
|
508
|
+
* Nearest enclosing blockquote ancestor of `pos` (defaulting to the
|
|
509
|
+
* current selection head), or undefined outside one. For a nested
|
|
510
|
+
* blockquote this resolves to the innermost one - shared by the toggle
|
|
511
|
+
* button's active state, the toggle command itself, the right-click
|
|
512
|
+
* gate, and the blockquote styling context-menu items below, so all
|
|
513
|
+
* four can never drift apart on "which blockquote is this."
|
|
514
|
+
*/
|
|
515
|
+
const findBlockquoteAncestor = (pos = runtime.editor.selection.head) => [...runtime.editor.resolve({ pos }).ancestors].reverse().find((node) => node.type === "blockquote");
|
|
516
|
+
const currentBlockquoteActive = Boolean(findBlockquoteAncestor());
|
|
517
|
+
/** A synthetic block-range scope targeting exactly one blockquote node's own id - the shape setBlockAttributes/wrapBlocks/unwrapBlocks all expect. */
|
|
518
|
+
const blockquoteScopeFor = (blockId, pos) => ({
|
|
519
|
+
kind: "block-range",
|
|
520
|
+
blockIds: [blockId],
|
|
521
|
+
promotedFromPartial: false,
|
|
522
|
+
commonParentId: null,
|
|
523
|
+
range: { from: pos, to: pos },
|
|
524
|
+
isolatingAncestorId: null,
|
|
525
|
+
clamped: false,
|
|
526
|
+
});
|
|
456
527
|
const currentListPreset = currentListParts.length === 1 && typeof rootList?.attrs?.preset === "string"
|
|
457
528
|
? rootList.attrs.preset
|
|
458
529
|
: "";
|
|
530
|
+
// The Bulleted/Numbered list buttons' own split-control style pickers -
|
|
531
|
+
// scoped to one kind each, so unlike the shared "List preset" select above
|
|
532
|
+
// (which lists both kinds together) each only shows/reflects presets of
|
|
533
|
+
// its own kind, and reads as unset (native placeholder) when the active
|
|
534
|
+
// preset (if any) belongs to the other kind.
|
|
535
|
+
const currentBulletPreset = SMART_LIST_PRESETS.find((preset) => preset.id === currentListPreset)?.kind === "bullet" ? currentListPreset : "";
|
|
536
|
+
const currentOrderedPreset = SMART_LIST_PRESETS.find((preset) => preset.id === currentListPreset)?.kind === "ordered" ? currentListPreset : "";
|
|
459
537
|
const toggleCheckedItems = () => {
|
|
460
538
|
if (currentListScope.kind !== "list-selection" || currentList?.attrs?.checkable !== true)
|
|
461
539
|
return;
|
|
@@ -503,17 +581,30 @@ export const CanonicalAuthorityEditor = forwardRef(function CanonicalAuthorityEd
|
|
|
503
581
|
const toggleList = (style, checkable = false) => {
|
|
504
582
|
const selectedList = listScope();
|
|
505
583
|
const context = blockContext();
|
|
584
|
+
// A checklist's marker is a checkbox, not a bullet/number glyph - never
|
|
585
|
+
// substitute a remembered preset for it. Otherwise, reapply whichever
|
|
586
|
+
// preset the user last picked from the "List preset" dropdown for this
|
|
587
|
+
// same kind (bullet/ordered), instead of always resetting to the
|
|
588
|
+
// generic disc/decimal marker - see lastListPreset's own doc comment.
|
|
589
|
+
// setListStyle's own params type has no `preset` field (it's a
|
|
590
|
+
// deliberately distinct command from setListPreset - see setListPreset
|
|
591
|
+
// itself clearing `style` and vice versa), so this is a real branch,
|
|
592
|
+
// not just a differently-shaped params object.
|
|
593
|
+
const rememberedPreset = checkable ? null : lastListPreset[style === "decimal" ? "ordered" : "bullet"];
|
|
506
594
|
if (selectedList.kind === "list-selection") {
|
|
507
595
|
const rootId = outermostListId(selectedList.listId);
|
|
508
596
|
const list = findNode(runtime.editor.document, rootId);
|
|
509
597
|
const sameStyle = listActiveKind(list) === (style === "decimal" ? "ordered" : "bullet") && Boolean(list?.attrs?.checkable) === checkable;
|
|
598
|
+
const target = { ...selectedList, listId: rootId };
|
|
510
599
|
const operations = sameStyle
|
|
511
600
|
// Toggling an already-active style off is deliberately scoped to just
|
|
512
601
|
// the current item (selectedList), not the whole list.
|
|
513
602
|
? unwrapList(runtime.editor.document, selectedList, { splitListIds: ids(4) }, context)
|
|
514
603
|
// Applying a genuinely different style/type is a whole-list decision
|
|
515
604
|
// regardless of how deep the cursor is nested.
|
|
516
|
-
:
|
|
605
|
+
: rememberedPreset
|
|
606
|
+
? setListPreset(runtime.editor.document, target, { preset: rememberedPreset }, context)
|
|
607
|
+
: setListStyle(runtime.editor.document, target, { style, checkable }, context);
|
|
517
608
|
runtime.executeOperations(operations, { preserveSelectionById: true });
|
|
518
609
|
return;
|
|
519
610
|
}
|
|
@@ -523,9 +614,45 @@ export const CanonicalAuthorityEditor = forwardRef(function CanonicalAuthorityEd
|
|
|
523
614
|
// per block, on top of one list per originally-flat contiguous group —
|
|
524
615
|
// over-provision generously rather than compute the exact worst case.
|
|
525
616
|
runtime.executeOperations(createList(runtime.editor.document, selectedBlocks, {
|
|
526
|
-
listIds: ids(Math.max(1, count * 2)), itemIds: ids(Math.max(1, count)),
|
|
617
|
+
listIds: ids(Math.max(1, count * 2)), itemIds: ids(Math.max(1, count)), checkable,
|
|
618
|
+
...(rememberedPreset ? { preset: rememberedPreset } : { style }),
|
|
527
619
|
}, context), { preserveSelectionById: true });
|
|
528
620
|
};
|
|
621
|
+
/**
|
|
622
|
+
* The Bulleted/Numbered list buttons' own split-control style picker
|
|
623
|
+
* (a native `<select>`, not a custom-positioned dropdown - see the
|
|
624
|
+
* `.srte-split-control` styling) - lets the user choose a specific preset
|
|
625
|
+
* directly from the button itself, instead of only ever getting the
|
|
626
|
+
* plain/remembered default from a click and having to visit the separate
|
|
627
|
+
* "More list tools" menu to pick a named style. Applies to an existing
|
|
628
|
+
* list (whole-list, like the shared "List preset" select) or creates a
|
|
629
|
+
* brand-new list with that preset when nothing is selected yet - unlike
|
|
630
|
+
* that shared select, which only ever edits an already-existing list.
|
|
631
|
+
* Scope is resolved fresh here (not from the outer currentListScope/
|
|
632
|
+
* currentListParts consts) to match toggleList's own convention of
|
|
633
|
+
* re-resolving at click time rather than trusting a render-time snapshot.
|
|
634
|
+
*/
|
|
635
|
+
const applyListPreset = (preset) => {
|
|
636
|
+
const pickedKind = SMART_LIST_PRESETS.find((candidate) => candidate.id === preset)?.kind;
|
|
637
|
+
const selectedList = listScope();
|
|
638
|
+
const context = blockContext();
|
|
639
|
+
if (selectedList.kind === "list-selection") {
|
|
640
|
+
const parts = listSelectionParts(selectedList);
|
|
641
|
+
if (parts.length !== 1)
|
|
642
|
+
return;
|
|
643
|
+
const rootId = outermostListId(parts[0].listId);
|
|
644
|
+
runtime.executeOperations(setListPreset(runtime.editor.document, { ...parts[0], listId: rootId }, { preset }, context), { preserveSelectionById: true });
|
|
645
|
+
}
|
|
646
|
+
else {
|
|
647
|
+
const selectedBlocks = blockScope();
|
|
648
|
+
const count = selectedBlocks.kind === "block-range" ? selectedBlocks.blockIds.length : 1;
|
|
649
|
+
runtime.executeOperations(createList(runtime.editor.document, selectedBlocks, {
|
|
650
|
+
listIds: ids(Math.max(1, count * 2)), itemIds: ids(Math.max(1, count)), checkable: false, preset,
|
|
651
|
+
}, context), { preserveSelectionById: true });
|
|
652
|
+
}
|
|
653
|
+
if (pickedKind)
|
|
654
|
+
setLastListPreset((previous) => ({ ...previous, [pickedKind]: preset }));
|
|
655
|
+
};
|
|
529
656
|
const runList = (action) => {
|
|
530
657
|
const scope = listScope();
|
|
531
658
|
const context = blockContext();
|
|
@@ -596,6 +723,16 @@ export const CanonicalAuthorityEditor = forwardRef(function CanonicalAuthorityEd
|
|
|
596
723
|
const value = cell?.attrs?.[attr];
|
|
597
724
|
return typeof value === "string" ? value : undefined;
|
|
598
725
|
};
|
|
726
|
+
const currentBlockquoteColor = (blockId, attr) => {
|
|
727
|
+
const value = findNode(runtime.editor.document, blockId)?.attrs?.[attr];
|
|
728
|
+
return typeof value === "string" ? value : undefined;
|
|
729
|
+
};
|
|
730
|
+
/** Blockquote's *effective* left border (only one side exists, unlike table_cell's 4 - no `sides` toggle concept needed at all). */
|
|
731
|
+
const currentBlockquoteBorderDraft = (blockId) => {
|
|
732
|
+
const value = findNode(runtime.editor.document, blockId)?.attrs?.borderLeft;
|
|
733
|
+
const parsed = parseBorderShorthand(value);
|
|
734
|
+
return { style: parsed?.style ?? "solid", widthPx: parsed?.widthPx ?? BORDER_WIDTH_PRESETS[0].px, hex: parsed?.hex ?? "#000000" };
|
|
735
|
+
};
|
|
599
736
|
const CELL_BORDER_SIDE_KEYS = { top: "borderTop", right: "borderRight", bottom: "borderBottom", left: "borderLeft" };
|
|
600
737
|
/** Reads the cell's *effective* per-side border (a side's own override, falling back to the legacy uniform `borders` value) to seed the Border options popover. */
|
|
601
738
|
const currentCellBorderDraft = (scope) => {
|
|
@@ -645,6 +782,39 @@ export const CanonicalAuthorityEditor = forwardRef(function CanonicalAuthorityEd
|
|
|
645
782
|
setTableBorderPopover(null);
|
|
646
783
|
runtime.focus();
|
|
647
784
|
};
|
|
785
|
+
/** Same shape as applyCellBorderDraft, but for the one side a blockquote has - the whole draft always applies to borderLeft, no per-side toggle to reduce down first. */
|
|
786
|
+
const applyBlockquoteBorderDraft = (blockId, draft, options) => {
|
|
787
|
+
const scope = blockquoteScopeFor(blockId, runtime.editor.selection.head);
|
|
788
|
+
runtime.executeOperations(setBlockAttributes(runtime.editor.document, scope, { attrs: { borderLeft: composeBorderShorthand(draft.widthPx, draft.style, draft.hex) } }, blockContext()), { preserveSelectionById: true, ...options });
|
|
789
|
+
};
|
|
790
|
+
const previewBlockquoteBorder = (draft) => {
|
|
791
|
+
if (!blockquoteBorderPopover)
|
|
792
|
+
return;
|
|
793
|
+
if (!blockquoteBorderPreviewCheckpointRef.current)
|
|
794
|
+
blockquoteBorderPreviewCheckpointRef.current = runtime.createCheckpoint();
|
|
795
|
+
runtime.restoreCheckpoint(blockquoteBorderPreviewCheckpointRef.current);
|
|
796
|
+
applyBlockquoteBorderDraft(blockquoteBorderPopover.blockId, draft, { addToHistory: false });
|
|
797
|
+
};
|
|
798
|
+
const applyBlockquoteBorderCommit = (draft) => {
|
|
799
|
+
if (!blockquoteBorderPopover)
|
|
800
|
+
return;
|
|
801
|
+
if (blockquoteBorderPreviewCheckpointRef.current) {
|
|
802
|
+
runtime.restoreCheckpoint(blockquoteBorderPreviewCheckpointRef.current);
|
|
803
|
+
blockquoteBorderPreviewCheckpointRef.current = null;
|
|
804
|
+
}
|
|
805
|
+
applyBlockquoteBorderDraft(blockquoteBorderPopover.blockId, draft, { addToHistory: true });
|
|
806
|
+
recordRecentColor("border", draft.hex);
|
|
807
|
+
setBlockquoteBorderPopover(null);
|
|
808
|
+
runtime.focus();
|
|
809
|
+
};
|
|
810
|
+
const cancelBlockquoteBorderPopover = () => {
|
|
811
|
+
if (blockquoteBorderPreviewCheckpointRef.current) {
|
|
812
|
+
runtime.restoreCheckpoint(blockquoteBorderPreviewCheckpointRef.current);
|
|
813
|
+
blockquoteBorderPreviewCheckpointRef.current = null;
|
|
814
|
+
}
|
|
815
|
+
setBlockquoteBorderPopover(null);
|
|
816
|
+
runtime.focus();
|
|
817
|
+
};
|
|
648
818
|
const recordRecentColor = (bucket, hex) => {
|
|
649
819
|
setRecentColors((current) => ({
|
|
650
820
|
...current,
|
|
@@ -658,12 +828,17 @@ export const CanonicalAuthorityEditor = forwardRef(function CanonicalAuthorityEd
|
|
|
658
828
|
if (colorPopover.target.kind === "mark") {
|
|
659
829
|
applyMarkAttrs(colorPopover.target.markId, { value: hex }, options);
|
|
660
830
|
}
|
|
661
|
-
else {
|
|
831
|
+
else if (colorPopover.target.kind === "cell") {
|
|
662
832
|
// Uses the scope captured when the popover opened, not a fresh
|
|
663
833
|
// tableScope() re-resolved against the live selection - see the
|
|
664
834
|
// colorPopover state's own doc comment for why.
|
|
665
835
|
runtime.executeOperations(setTableCellAttributesCommand(runtime.editor.document, colorPopover.target.scope, { attrs: { [colorPopover.target.attr]: hex } }, blockContext()), { preserveSelectionById: true, ...options });
|
|
666
836
|
}
|
|
837
|
+
else {
|
|
838
|
+
// Same captured-id-at-click-time discipline as the cell case above.
|
|
839
|
+
const scope = blockquoteScopeFor(colorPopover.target.blockId, runtime.editor.selection.head);
|
|
840
|
+
runtime.executeOperations(setBlockAttributes(runtime.editor.document, scope, { attrs: { [colorPopover.target.attr]: hex } }, blockContext()), { preserveSelectionById: true, ...options });
|
|
841
|
+
}
|
|
667
842
|
};
|
|
668
843
|
/**
|
|
669
844
|
* Live preview while dragging the in-page saturation/hue picker (or typing
|
|
@@ -722,18 +897,10 @@ export const CanonicalAuthorityEditor = forwardRef(function CanonicalAuthorityEd
|
|
|
722
897
|
const toggleBlockquote = () => {
|
|
723
898
|
const scope = blockScope();
|
|
724
899
|
const resolved = runtime.editor.resolve({ pos: runtime.editor.selection.head });
|
|
725
|
-
const ancestor =
|
|
900
|
+
const ancestor = findBlockquoteAncestor();
|
|
726
901
|
const context = blockContext();
|
|
727
902
|
if (ancestor) {
|
|
728
|
-
const quoteScope =
|
|
729
|
-
kind: "block-range",
|
|
730
|
-
blockIds: [ancestor.id],
|
|
731
|
-
promotedFromPartial: false,
|
|
732
|
-
commonParentId: null,
|
|
733
|
-
range: { from: resolved.pos, to: resolved.pos },
|
|
734
|
-
isolatingAncestorId: null,
|
|
735
|
-
clamped: false,
|
|
736
|
-
};
|
|
903
|
+
const quoteScope = blockquoteScopeFor(ancestor.id, resolved.pos);
|
|
737
904
|
runtime.executeOperations(unwrapBlocks(runtime.editor.document, quoteScope, { type: "blockquote" }, context), { preserveSelectionById: true });
|
|
738
905
|
return;
|
|
739
906
|
}
|
|
@@ -895,6 +1062,29 @@ export const CanonicalAuthorityEditor = forwardRef(function CanonicalAuthorityEd
|
|
|
895
1062
|
onSelect: () => setTableBorderPopover({ x: openX, y: openY, scope: tableGridScope, initial: currentCellBorderDraft(tableGridScope) }),
|
|
896
1063
|
});
|
|
897
1064
|
}
|
|
1065
|
+
else {
|
|
1066
|
+
// Table cell checked first (above) - a right-click inside a table
|
|
1067
|
+
// nested inside a blockquote shows cell options, not these.
|
|
1068
|
+
const blockquoteAncestor = findBlockquoteAncestor();
|
|
1069
|
+
if (blockquoteAncestor) {
|
|
1070
|
+
const openX = contextMenu?.x ?? 0;
|
|
1071
|
+
const openY = contextMenu?.y ?? 0;
|
|
1072
|
+
const blockId = blockquoteAncestor.id;
|
|
1073
|
+
items.push({
|
|
1074
|
+
id: "blockquote.contextMenu.backgroundColor",
|
|
1075
|
+
label: "Blockquote background colour",
|
|
1076
|
+
onSelect: () => setColorPopover({ x: openX, y: openY, target: { kind: "blockquote", attr: "backgroundColor", blockId }, initialValue: currentBlockquoteColor(blockId, "backgroundColor") }),
|
|
1077
|
+
}, {
|
|
1078
|
+
id: "blockquote.contextMenu.textColor",
|
|
1079
|
+
label: "Blockquote text colour",
|
|
1080
|
+
onSelect: () => setColorPopover({ x: openX, y: openY, target: { kind: "blockquote", attr: "textColor", blockId }, initialValue: currentBlockquoteColor(blockId, "textColor") }),
|
|
1081
|
+
}, {
|
|
1082
|
+
id: "blockquote.contextMenu.borderOptions",
|
|
1083
|
+
label: "Blockquote border options",
|
|
1084
|
+
onSelect: () => setBlockquoteBorderPopover({ x: openX, y: openY, blockId, initial: currentBlockquoteBorderDraft(blockId) }),
|
|
1085
|
+
});
|
|
1086
|
+
}
|
|
1087
|
+
}
|
|
898
1088
|
return items;
|
|
899
1089
|
};
|
|
900
1090
|
/**
|
|
@@ -1397,6 +1587,26 @@ export const CanonicalAuthorityEditor = forwardRef(function CanonicalAuthorityEd
|
|
|
1397
1587
|
return false;
|
|
1398
1588
|
return entry.coverage === "all" ? true : "mixed";
|
|
1399
1589
|
};
|
|
1590
|
+
/**
|
|
1591
|
+
* Whether clicking this mark tool right now would do anything at all - a
|
|
1592
|
+
* node type can declare `marks: ""` (code_block, every atom) to disallow
|
|
1593
|
+
* every mark, and applyMarkCommand already silently skips owners that
|
|
1594
|
+
* disallow the mark rather than erroring - previously that made these
|
|
1595
|
+
* buttons stay fully clickable no-ops with zero feedback inside a code
|
|
1596
|
+
* block or with an image selected. Reuses reportMarkApplication (already
|
|
1597
|
+
* built for exactly this question, previously only exercised by tests)
|
|
1598
|
+
* rather than re-deriving the schema check: "allowed somewhere" is
|
|
1599
|
+
* ownerCount > ownerIdsSkipped.length, i.e. at least one touched owner
|
|
1600
|
+
* does NOT skip it. Deliberately "somewhere", not "everywhere" - a
|
|
1601
|
+
* selection spanning both a normal paragraph and a code block still
|
|
1602
|
+
* partially applies to the paragraph half today, and disabling here
|
|
1603
|
+
* would take that capability away for no reason.
|
|
1604
|
+
*/
|
|
1605
|
+
const markToolAllowed = (id) => {
|
|
1606
|
+
const scope = runtime.editor.resolveScope({ want: "inline-range" });
|
|
1607
|
+
const report = reportMarkApplication(runtime.editor.document, scope, markTool(id).markType, blockContext());
|
|
1608
|
+
return report.ownerCount > report.ownerIdsSkipped.length;
|
|
1609
|
+
};
|
|
1400
1610
|
const toggleMark = (id) => { executeMarkTool(runtime.editor, markTool(id), "toggle"); runtime.focus(); };
|
|
1401
1611
|
const openLinkPopover = (event) => {
|
|
1402
1612
|
const description = runtime.editor.resolveScope({ want: "describe" });
|
|
@@ -1427,14 +1637,39 @@ export const CanonicalAuthorityEditor = forwardRef(function CanonicalAuthorityEd
|
|
|
1427
1637
|
// twice; below 1440px (including mobile) this copy is what's reachable,
|
|
1428
1638
|
// same as before. "Code" isn't promoted - see the docs/bugs/ writeup on
|
|
1429
1639
|
// why it stayed the dropdown's sole remaining item at wide widths.
|
|
1430
|
-
const textStylesMenuItems = _jsxs(_Fragment, { children: [t.code && _jsx(ToolbarMenuItem, { icon: "code", label: "Code", pressed: markCoverage("inlineCode"), disabled: readOnly, onClick: () => toggleMark("inlineCode") }), t.superscript && _jsx(ToolbarMenuItem, { icon: "superscript", label: "Superscript", pressed: markCoverage("superscript"), disabled: readOnly, onClick: () => toggleMark("superscript"), widePromote: true }), t.subscript && _jsx(ToolbarMenuItem, { icon: "subscript", label: "Subscript", pressed: markCoverage("subscript"), disabled: readOnly, onClick: () => toggleMark("subscript"), widePromote: true }), t.textColor && _jsx(ToolbarMenuItem, { icon: "textColor", label: "Text colour", disabled: readOnly, widePromote: true, onClick: (event) => {
|
|
1640
|
+
const textStylesMenuItems = _jsxs(_Fragment, { children: [t.code && _jsx(ToolbarMenuItem, { icon: "code", label: "Code", pressed: markCoverage("inlineCode"), disabled: readOnly || !markToolAllowed("inlineCode"), onClick: () => toggleMark("inlineCode") }), t.superscript && _jsx(ToolbarMenuItem, { icon: "superscript", label: "Superscript", pressed: markCoverage("superscript"), disabled: readOnly || !markToolAllowed("superscript"), onClick: () => toggleMark("superscript"), widePromote: true }), t.subscript && _jsx(ToolbarMenuItem, { icon: "subscript", label: "Subscript", pressed: markCoverage("subscript"), disabled: readOnly || !markToolAllowed("subscript"), onClick: () => toggleMark("subscript"), widePromote: true }), t.textColor && _jsx(ToolbarMenuItem, { icon: "textColor", label: "Text colour", disabled: readOnly || !markToolAllowed("textColor"), widePromote: true, onClick: (event) => {
|
|
1431
1641
|
const rect = event.currentTarget.getBoundingClientRect();
|
|
1432
1642
|
setColorPopover({ x: rect.left, y: rect.bottom + 4, target: { kind: "mark", markId: "textColor" }, initialValue: currentMarkColor("textColor") });
|
|
1433
|
-
} }), t.backgroundColor && _jsx(ToolbarMenuItem, { icon: "backgroundColor", label: "Background colour", disabled: readOnly, widePromote: true, onClick: (event) => {
|
|
1643
|
+
} }), t.backgroundColor && _jsx(ToolbarMenuItem, { icon: "backgroundColor", label: "Background colour", disabled: readOnly || !markToolAllowed("backgroundColor"), widePromote: true, onClick: (event) => {
|
|
1434
1644
|
const rect = event.currentTarget.getBoundingClientRect();
|
|
1435
1645
|
setColorPopover({ x: rect.left, y: rect.bottom + 4, target: { kind: "mark", markId: "backgroundColor" }, initialValue: currentMarkColor("backgroundColor") });
|
|
1436
|
-
} }), t.fontSize && _jsx(ToolbarMenuItem, { icon: "fontSize", label: "Font size", disabled: readOnly, onClick: () => applyAttributedMark("fontSize"), widePromote: true }), t.fontFamily && _jsx(ToolbarMenuItem, { icon: "fontFamily", label: "Font family", disabled: readOnly, onClick: () => applyAttributedMark("fontFamily"), widePromote: true })] });
|
|
1646
|
+
} }), t.fontSize && _jsx(ToolbarMenuItem, { icon: "fontSize", label: "Font size", disabled: readOnly || !markToolAllowed("fontSize"), onClick: () => applyAttributedMark("fontSize"), widePromote: true }), t.fontFamily && _jsx(ToolbarMenuItem, { icon: "fontFamily", label: "Font family", disabled: readOnly || !markToolAllowed("fontFamily"), onClick: () => applyAttributedMark("fontFamily"), widePromote: true })] });
|
|
1437
1647
|
const showMoreTextStyles = t.code || t.superscript || t.subscript || t.textColor || t.backgroundColor || t.fontSize || t.fontFamily;
|
|
1648
|
+
/**
|
|
1649
|
+
* Line spacing - matches Google Docs' own reference shape: a short preset
|
|
1650
|
+
* list plus a custom/exact value entry, with a checkmark on whichever
|
|
1651
|
+
* preset (or "Default") currently applies. "Default" clears the
|
|
1652
|
+
* attribute entirely (setLineHeight(undefined)) rather than writing an
|
|
1653
|
+
* explicit "1" - the block then renders at the browser/font's own
|
|
1654
|
+
* natural line-height, matching how no override ever forces a value for
|
|
1655
|
+
* align/indent either. A mixed-value selection (blocks disagreeing)
|
|
1656
|
+
* shows no checkmark on any entry, matching every comparison below
|
|
1657
|
+
* evaluating false against the "mixed" sentinel with no special-casing
|
|
1658
|
+
* needed.
|
|
1659
|
+
*/
|
|
1660
|
+
const LINE_HEIGHT_PRESETS = [1, 1.15, 1.5, 2, 2.5];
|
|
1661
|
+
const lineHeightMenuItems = _jsxs(_Fragment, { children: [_jsx(ToolbarMenuItem, { label: "Default", pressed: currentLineHeight() === undefined, disabled: readOnly, onClick: () => setLineHeight(undefined) }), LINE_HEIGHT_PRESETS.map((preset) => _jsx(ToolbarMenuItem, { label: String(preset), pressed: currentLineHeight() === preset, disabled: readOnly, onClick: () => setLineHeight(preset) }, preset)), _jsx("div", { className: "srte-menu-separator" }), _jsxs("div", { className: "srte-line-height-custom", style: { padding: "4px 8px", display: "flex", alignItems: "center", gap: 6 }, children: [_jsx("span", { "aria-hidden": "true", style: { fontSize: 12, whiteSpace: "nowrap" }, children: "Custom:" }), _jsx("input", { "aria-label": "Custom line spacing", type: "number", min: 0.1, max: 10, step: 0.05, disabled: readOnly, defaultValue: (() => { const current = currentLineHeight(); return typeof current === "number" && !LINE_HEIGHT_PRESETS.includes(current) ? current : ""; })(), placeholder: "e.g. 1.75", style: { width: "100%", minWidth: 0 }, onKeyDown: (event) => {
|
|
1662
|
+
if (event.key !== "Enter")
|
|
1663
|
+
return;
|
|
1664
|
+
const value = Number(event.currentTarget.value);
|
|
1665
|
+
if (Number.isFinite(value) && value >= 0.1 && value <= 10)
|
|
1666
|
+
setLineHeight(value);
|
|
1667
|
+
event.currentTarget.closest("details")?.removeAttribute("open");
|
|
1668
|
+
}, onBlur: (event) => {
|
|
1669
|
+
const value = Number(event.currentTarget.value);
|
|
1670
|
+
if (event.currentTarget.value && Number.isFinite(value) && value >= 0.1 && value <= 10)
|
|
1671
|
+
setLineHeight(value);
|
|
1672
|
+
} }, String((() => { const current = currentLineHeight(); return typeof current === "number" && !LINE_HEIGHT_PRESETS.includes(current) ? current : "none"; })()))] })] });
|
|
1438
1673
|
const paragraphToolsMenuItems = _jsxs(_Fragment, { children: [_jsx(ToolbarMenuItem, { icon: "moveUp", label: "Move block up", disabled: readOnly, onClick: () => runBlock("up") }), _jsx(ToolbarMenuItem, { icon: "moveDown", label: "Move block down", disabled: readOnly, onClick: () => runBlock("down") }), _jsx(ToolbarMenuItem, { icon: "indent", label: "Indent block", disabled: readOnly, onClick: () => runBlock("indent") }), _jsx(ToolbarMenuItem, { icon: "outdent", label: "Outdent block", disabled: readOnly, onClick: () => runBlock("outdent") })] });
|
|
1439
1674
|
/**
|
|
1440
1675
|
* Named multi-level marker presets (decimal/lower-alpha/upper-alpha/
|
|
@@ -1455,6 +1690,9 @@ export const CanonicalAuthorityEditor = forwardRef(function CanonicalAuthorityEd
|
|
|
1455
1690
|
// the cursor is nested — see outermostListId.
|
|
1456
1691
|
const rootId = outermostListId(currentListParts[0].listId);
|
|
1457
1692
|
runtime.executeOperations(setListPreset(runtime.editor.document, { ...currentListParts[0], listId: rootId }, { preset }, blockContext()), { preserveSelectionById: true });
|
|
1693
|
+
const pickedKind = SMART_LIST_PRESETS.find((candidate) => candidate.id === preset)?.kind;
|
|
1694
|
+
if (pickedKind)
|
|
1695
|
+
setLastListPreset((previous) => ({ ...previous, [pickedKind]: preset }));
|
|
1458
1696
|
}, style: { width: "100%" }, children: [_jsx("option", { value: "", children: "List preset" }), SMART_LIST_PRESETS.map((preset) => _jsx("option", { value: preset.id, children: preset.kind === "bullet" ? `Bullet · ${preset.label}` : `Number · ${preset.label}` }, preset.id))] }) });
|
|
1459
1697
|
const listToolsMenuItems = _jsxs(_Fragment, { children: [_jsx(ToolbarMenuItem, { icon: "checkSquare", label: "Check selected items", pressed: currentListScope.kind === "list-selection" && currentListScope.items.every((item) => findNode(runtime.editor.document, item.itemId)?.attrs?.checked === true), disabled: readOnly || currentList?.attrs?.checkable !== true, onClick: toggleCheckedItems }), _jsx(ToolbarMenuItem, { icon: "indent", label: "Indent list item", disabled: readOnly || !canIndent, onClick: () => runList("indent") }), _jsx(ToolbarMenuItem, { icon: "outdent", label: "Outdent list item", disabled: readOnly || currentListParts.length === 0, onClick: () => runList("outdent") }), _jsx(ToolbarMenuItem, { icon: "moveUp", label: "Move item up", disabled: readOnly || !canMoveUp, onClick: () => runList("up") }), _jsx(ToolbarMenuItem, { icon: "moveDown", label: "Move item down", disabled: readOnly || !canMoveDown, onClick: () => runList("down") }), _jsx(ToolbarMenuItem, { icon: "restart", label: "Restart numbering", disabled: readOnly || !orderedList, onClick: restartNumbering }), _jsx(ToolbarMenuItem, { icon: "continueNumbering", label: "Continue numbering", disabled: readOnly || !orderedList, onClick: () => runtime.executeOperations(continueListNumbering(runtime.editor.document, currentListScope, {}, blockContext()), { preserveSelectionById: true }) })] });
|
|
1460
1698
|
// Remove link/Insert formula/Special characters also get a standalone
|
|
@@ -1467,7 +1705,7 @@ export const CanonicalAuthorityEditor = forwardRef(function CanonicalAuthorityEd
|
|
|
1467
1705
|
} }), t.specialCharacters && _jsx(ToolbarMenuItem, { icon: "specialChar", label: "Special characters", disabled: readOnly, widePromote: true, onClick: (event) => {
|
|
1468
1706
|
const rect = event.currentTarget.getBoundingClientRect();
|
|
1469
1707
|
setSpecialCharPopover({ x: rect.left, y: rect.bottom + 4 });
|
|
1470
|
-
} }), _jsx(ToolbarMenuItem, { icon: "edit", label: "Edit selected media", disabled: readOnly || !mediaAtomSelected, onClick: () => editSelectedAtom() }), _jsx(ToolbarMenuItem, { icon: "zoomIn", label: "Enlarge selected media", disabled: readOnly || !resizableAtomSelected, onClick: () => editSelectedAtom(20) }), _jsx(ToolbarMenuItem, { icon: "zoomOut", label: "Shrink selected media", disabled: readOnly || !resizableAtomSelected, onClick: () => editSelectedAtom(-20) }), _jsx(ToolbarMenuItem, { icon: "delete", label: "Delete selected media", disabled: readOnly || !atomSelected, onClick: () => runtime.executeOperations(deleteAtom(runtime.editor.document, atomScope(), {}, blockContext())) })] });
|
|
1708
|
+
} }), t.horizontalLine && _jsx(ToolbarMenuItem, { icon: "divider", label: "Horizontal line", disabled: readOnly, widePromote: true, onClick: () => { insertBlockAtom("divider", {}, createNodeId()); runtime.focus(); } }), t.pageBreak && _jsx(ToolbarMenuItem, { icon: "pageBreak", label: "Page break", disabled: readOnly, widePromote: true, onClick: () => { insertBlockAtom("page_break", {}, createNodeId()); runtime.focus(); } }), _jsx(ToolbarMenuItem, { icon: "edit", label: "Edit selected media", disabled: readOnly || !mediaAtomSelected, onClick: () => editSelectedAtom() }), _jsx(ToolbarMenuItem, { icon: "zoomIn", label: "Enlarge selected media", disabled: readOnly || !resizableAtomSelected, onClick: () => editSelectedAtom(20) }), _jsx(ToolbarMenuItem, { icon: "zoomOut", label: "Shrink selected media", disabled: readOnly || !resizableAtomSelected, onClick: () => editSelectedAtom(-20) }), _jsx(ToolbarMenuItem, { icon: "delete", label: "Delete selected media", disabled: readOnly || !atomSelected, onClick: () => runtime.executeOperations(deleteAtom(runtime.editor.document, atomScope(), {}, blockContext())) })] });
|
|
1471
1709
|
// "Remove row"/"Remove column"/"Remove table" renamed to "Delete ..." -
|
|
1472
1710
|
// matches the right-click table context menu's existing wording
|
|
1473
1711
|
// (table/plugin.ts's table.contextMenu.removeRow/removeColumn/removeTable
|
|
@@ -1481,7 +1719,7 @@ export const CanonicalAuthorityEditor = forwardRef(function CanonicalAuthorityEd
|
|
|
1481
1719
|
const rect = event.currentTarget.getBoundingClientRect();
|
|
1482
1720
|
setTableBorderPopover({ x: rect.left, y: rect.bottom + 4, scope: scope, initial: currentCellBorderDraft(scope) });
|
|
1483
1721
|
} })] });
|
|
1484
|
-
const saveCopyMenuItems = _jsxs(_Fragment, { children: [t.saveAsHtml && _jsx(ToolbarMenuItem, { label: "Save as HTML", onClick: () => runExport("html") }), t.saveAsMarkdown && _jsx(ToolbarMenuItem, { label: "Save as Markdown", onClick: () => runExport("markdown") }), t.saveAsWord && _jsx(ToolbarMenuItem, { label: "Save as Word document", onClick: () => void runDocxExport() }), t.saveAsPdf && _jsx(ToolbarMenuItem, { label: "Save as PDF", onClick: runPdfExport }), t.saveAsSmartRte && _jsx(ToolbarMenuItem, { icon: "json", label: "Save as Smart RTE file", onClick: () => runExport("native") })] });
|
|
1722
|
+
const saveCopyMenuItems = _jsxs(_Fragment, { children: [t.saveAsHtml && _jsx(ToolbarMenuItem, { label: "Save as HTML", onClick: () => runExport("html") }), t.saveAsMarkdown && _jsx(ToolbarMenuItem, { label: "Save as Markdown", onClick: () => runExport("markdown") }), t.saveAsWord && _jsx(ToolbarMenuItem, { label: "Save as Word document", onClick: () => void runDocxExport() }), t.saveAsPdf && _jsx(ToolbarMenuItem, { label: "Print / Save as PDF", onClick: runPdfExport }), t.saveAsSmartRte && _jsx(ToolbarMenuItem, { icon: "json", label: "Save as Smart RTE file", onClick: () => runExport("native") })] });
|
|
1485
1723
|
const showSaveCopy = t.saveAsHtml || t.saveAsMarkdown || t.saveAsWord || t.saveAsPdf || t.saveAsSmartRte;
|
|
1486
1724
|
// "Review" splits into two independently-toggleable tools (comments,
|
|
1487
1725
|
// suggestions) - not an arbitrary split, it maps directly onto the two
|
|
@@ -1492,22 +1730,24 @@ export const CanonicalAuthorityEditor = forwardRef(function CanonicalAuthorityEd
|
|
|
1492
1730
|
const suggestionsMenuItems = _jsxs(_Fragment, { children: [_jsx(ToolbarMenuItem, { icon: "suggest", label: "Suggest deletion", disabled: !canSuggestDelete, onClick: suggestDelete }), _jsx(ToolbarMenuItem, { icon: "suggest", label: "Suggest insertion", disabled: readOnly || !suggestionProvider, onClick: startSuggestInsert }), _jsx(ToolbarMenuItem, { icon: "suggest", label: "Suggest removing this", disabled: readOnly || !suggestionProvider, onClick: suggestBlockRemoval }), _jsx(ToolbarMenuItem, { icon: "suggestions", label: "Suggestions", pressed: suggestionPanelOpen, disabled: !suggestionProvider, onClick: () => setSuggestionPanelOpen((open) => !open) }), _jsx(ToolbarMenuItem, { icon: "showEdits", label: "Show edits", pressed: trackChangesEnabled, title: "Tracks ordinary typing and edits within a single paragraph as suggestions. Merging paragraphs together - typing over a selection that spans multiple paragraphs, or pressing Backspace/Delete at a paragraph boundary - still applies directly and can't be reviewed as a suggestion.", disabled: readOnly || !suggestionProvider, onClick: () => setTrackChangesEnabled((enabled) => !enabled) })] });
|
|
1493
1731
|
const reviewMenuItems = _jsxs(_Fragment, { children: [showCommentsTool && commentsMenuItems, showCommentsTool && showSuggestionsTool && _jsx("div", { className: "srte-menu-separator" }), showSuggestionsTool && suggestionsMenuItems] });
|
|
1494
1732
|
const showReviewDropdown = showCommentsTool || showSuggestionsTool;
|
|
1495
|
-
return _jsxs("section", { className: `srte-root srte-editor srte-canonical-authority${className ? ` ${className}` : ""}`, "data-smart-authority": "canonical", children: [_jsxs("div", { className: "srte-toolbar", role: "toolbar", "aria-label": "Formatting toolbar", children: [_jsxs(ToolbarGroup, { children: [t.bold && _jsx(ToolbarButton, { icon: "bold", label: "Bold", pressed: markCoverage("bold"), disabled: readOnly, onClick: () => toggleMark("bold") }), t.italic && _jsx(ToolbarButton, { icon: "italic", label: "Italic", pressed: markCoverage("italic"), disabled: readOnly, onClick: () => toggleMark("italic") }), t.underline && _jsx(ToolbarButton, { icon: "underline", label: "Underline", pressed: markCoverage("underline"), disabled: readOnly, onClick: () => toggleMark("underline") }), t.strikethrough && _jsx(ToolbarButton, { icon: "strikethrough", label: "Strikethrough", pressed: markCoverage("strikethrough"), disabled: readOnly, onClick: () => toggleMark("strikethrough") }), t.superscript && _jsx(ToolbarButton, { icon: "superscript", label: "Superscript", pressed: markCoverage("superscript"), disabled: readOnly, onClick: () => toggleMark("superscript"), widePromote: true }), t.subscript && _jsx(ToolbarButton, { icon: "subscript", label: "Subscript", pressed: markCoverage("subscript"), disabled: readOnly, onClick: () => toggleMark("subscript"), widePromote: true }), t.textColor && _jsx(ToolbarButton, { icon: "textColor", label: "Text colour", disabled: readOnly, widePromote: true, onClick: (event) => {
|
|
1733
|
+
return _jsxs("section", { className: `srte-root srte-editor srte-canonical-authority${className ? ` ${className}` : ""}`, "data-smart-authority": "canonical", children: [_jsxs("div", { className: "srte-toolbar", role: "toolbar", "aria-label": "Formatting toolbar", children: [_jsxs(ToolbarGroup, { children: [t.bold && _jsx(ToolbarButton, { icon: "bold", label: "Bold", pressed: markCoverage("bold"), disabled: readOnly || !markToolAllowed("bold"), onClick: () => toggleMark("bold") }), t.italic && _jsx(ToolbarButton, { icon: "italic", label: "Italic", pressed: markCoverage("italic"), disabled: readOnly || !markToolAllowed("italic"), onClick: () => toggleMark("italic") }), t.underline && _jsx(ToolbarButton, { icon: "underline", label: "Underline", pressed: markCoverage("underline"), disabled: readOnly || !markToolAllowed("underline"), onClick: () => toggleMark("underline") }), t.strikethrough && _jsx(ToolbarButton, { icon: "strikethrough", label: "Strikethrough", pressed: markCoverage("strikethrough"), disabled: readOnly || !markToolAllowed("strikethrough"), onClick: () => toggleMark("strikethrough") }), t.superscript && _jsx(ToolbarButton, { icon: "superscript", label: "Superscript", pressed: markCoverage("superscript"), disabled: readOnly || !markToolAllowed("superscript"), onClick: () => toggleMark("superscript"), widePromote: true }), t.subscript && _jsx(ToolbarButton, { icon: "subscript", label: "Subscript", pressed: markCoverage("subscript"), disabled: readOnly || !markToolAllowed("subscript"), onClick: () => toggleMark("subscript"), widePromote: true }), t.textColor && _jsx(ToolbarButton, { icon: "textColor", label: "Text colour", disabled: readOnly || !markToolAllowed("textColor"), widePromote: true, onClick: (event) => {
|
|
1496
1734
|
const rect = event.currentTarget.getBoundingClientRect();
|
|
1497
1735
|
setColorPopover({ x: rect.left, y: rect.bottom + 4, target: { kind: "mark", markId: "textColor" }, initialValue: currentMarkColor("textColor") });
|
|
1498
|
-
} }), t.backgroundColor && _jsx(ToolbarButton, { icon: "backgroundColor", label: "Background colour", disabled: readOnly, widePromote: true, onClick: (event) => {
|
|
1736
|
+
} }), t.backgroundColor && _jsx(ToolbarButton, { icon: "backgroundColor", label: "Background colour", disabled: readOnly || !markToolAllowed("backgroundColor"), widePromote: true, onClick: (event) => {
|
|
1499
1737
|
const rect = event.currentTarget.getBoundingClientRect();
|
|
1500
1738
|
setColorPopover({ x: rect.left, y: rect.bottom + 4, target: { kind: "mark", markId: "backgroundColor" }, initialValue: currentMarkColor("backgroundColor") });
|
|
1501
|
-
} }), t.fontSize && _jsx(ToolbarButton, { icon: "fontSize", label: "Font size", disabled: readOnly, onClick: () => applyAttributedMark("fontSize"), widePromote: true }), t.fontFamily && _jsx(ToolbarButton, { icon: "fontFamily", label: "Font family", disabled: readOnly, onClick: () => applyAttributedMark("fontFamily"), widePromote: true }), showMoreTextStyles && _jsx(ToolbarDropdown, { icon: "textColor", label: "More text styles", priority: 2, children: textStylesMenuItems })] }), _jsxs(ToolbarGroup, { children: [t.blockType && _jsxs("select", { "aria-label": "Block type", title: "Block type", value: currentBlockType, disabled: readOnly, onChange: (event) => transactBlock(setBlockTypeCommand(runtime.editor.document, blockScope(), {
|
|
1739
|
+
} }), t.fontSize && _jsx(ToolbarButton, { icon: "fontSize", label: "Font size", disabled: readOnly || !markToolAllowed("fontSize"), onClick: () => applyAttributedMark("fontSize"), widePromote: true }), t.fontFamily && _jsx(ToolbarButton, { icon: "fontFamily", label: "Font family", disabled: readOnly || !markToolAllowed("fontFamily"), onClick: () => applyAttributedMark("fontFamily"), widePromote: true }), showMoreTextStyles && _jsx(ToolbarDropdown, { icon: "textColor", label: "More text styles", priority: 2, children: textStylesMenuItems })] }), _jsxs(ToolbarGroup, { children: [t.blockType && _jsxs("select", { className: "srte-block-type-select", "aria-label": "Block type", title: "Block type", value: currentBlockType, disabled: readOnly, onChange: (event) => transactBlock(setBlockTypeCommand(runtime.editor.document, blockScope(), {
|
|
1502
1740
|
type: event.target.value === "paragraph" ? "paragraph" : event.target.value === "code_block" ? "code_block" : "heading",
|
|
1503
1741
|
attrs: event.target.value.startsWith("heading-") ? { level: Number(event.target.value.slice(8)) } : {},
|
|
1504
|
-
}, blockContext())), children: [_jsx("option", { value: "paragraph", children: "Paragraph" }), Array.from({ length: 6 }, (_, index) => _jsxs("option", { value: `heading-${index + 1}`, children: ["Heading ", index + 1] }, index + 1)), _jsx("option", { value: "code_block", children: "Code block" })] }), t.alignLeft && _jsx(ToolbarButton, { icon: "alignLeft", label: "Align left", ariaLabel: "Align left", iconOnly: true, disabled: readOnly, onClick: () => transactBlock(setBlockAttributes(runtime.editor.document, blockScope(), { attrs: { align: "left" } }, blockContext())) }), t.alignCenter && _jsx(ToolbarButton, { icon: "alignCenter", label: "Align center", ariaLabel: "Align center", iconOnly: true, disabled: readOnly, onClick: () => transactBlock(setBlockAttributes(runtime.editor.document, blockScope(), { attrs: { align: "center" } }, blockContext())) }), t.alignRight && _jsx(ToolbarButton, { icon: "alignRight", label: "Align right", ariaLabel: "Align right", iconOnly: true, disabled: readOnly, onClick: () => transactBlock(setBlockAttributes(runtime.editor.document, blockScope(), { attrs: { align: "right" } }, blockContext())) }), t.alignJustify && _jsx(ToolbarButton, { icon: "alignJustify", label: "Justify", ariaLabel: "Align justify", iconOnly: true, disabled: readOnly, onClick: () => transactBlock(setBlockAttributes(runtime.editor.document, blockScope(), { attrs: { align: "justify" } }, blockContext())) }), t.
|
|
1742
|
+
}, blockContext())), children: [_jsx("option", { value: "paragraph", children: "Paragraph" }), Array.from({ length: 6 }, (_, index) => _jsxs("option", { value: `heading-${index + 1}`, children: ["Heading ", index + 1] }, index + 1)), _jsx("option", { value: "code_block", children: "Code block" })] }), t.alignLeft && _jsx(ToolbarButton, { icon: "alignLeft", label: "Align left", ariaLabel: "Align left", iconOnly: true, disabled: readOnly, onClick: () => transactBlock(setBlockAttributes(runtime.editor.document, blockScope(), { attrs: { align: "left" } }, blockContext())) }), t.alignCenter && _jsx(ToolbarButton, { icon: "alignCenter", label: "Align center", ariaLabel: "Align center", iconOnly: true, disabled: readOnly, onClick: () => transactBlock(setBlockAttributes(runtime.editor.document, blockScope(), { attrs: { align: "center" } }, blockContext())) }), t.alignRight && _jsx(ToolbarButton, { icon: "alignRight", label: "Align right", ariaLabel: "Align right", iconOnly: true, disabled: readOnly, onClick: () => transactBlock(setBlockAttributes(runtime.editor.document, blockScope(), { attrs: { align: "right" } }, blockContext())) }), t.alignJustify && _jsx(ToolbarButton, { icon: "alignJustify", label: "Justify", ariaLabel: "Align justify", iconOnly: true, disabled: readOnly, onClick: () => transactBlock(setBlockAttributes(runtime.editor.document, blockScope(), { attrs: { align: "justify" } }, blockContext())) }), t.lineHeight && _jsx(ToolbarDropdown, { icon: "lineHeight", label: "Line spacing", priority: 2, children: lineHeightMenuItems }), t.quote && _jsx(ToolbarButton, { icon: "quote", label: "Quote", ariaLabel: "Blockquote", pressed: currentBlockquoteActive, disabled: readOnly, onClick: toggleBlockquote }), _jsx(ToolbarDropdown, { icon: "moveUp", label: "More paragraph tools", priority: 2, children: paragraphToolsMenuItems })] }), _jsxs(ToolbarGroup, { children: [t.bulletedList && (t.listPreset ? _jsxs("span", { className: "srte-split-control", children: [_jsx(ToolbarButton, { icon: "bulletedList", label: "Bulleted list", ariaLabel: "Bulleted list", pressed: listStyleActive("disc"), disabled: readOnly, narrowIconOnly: true, onClick: () => toggleList("disc") }), _jsxs("select", { "aria-label": "Bulleted list style", title: "Bulleted list style", disabled: readOnly || (currentListScope.kind === "list-selection" && currentListParts.length !== 1), value: currentBulletPreset, onChange: (event) => { const preset = event.target.value; if (preset)
|
|
1743
|
+
applyListPreset(preset); }, children: [_jsx("option", { value: "", children: "Bulleted list style" }), SMART_LIST_PRESETS.filter((preset) => preset.kind === "bullet").map((preset) => _jsx("option", { value: preset.id, children: preset.label }, preset.id))] })] }) : _jsx(ToolbarButton, { icon: "bulletedList", label: "Bulleted list", ariaLabel: "Bulleted list", pressed: listStyleActive("disc"), disabled: readOnly, narrowIconOnly: true, onClick: () => toggleList("disc") })), t.numberedList && (t.listPreset ? _jsxs("span", { className: "srte-split-control", children: [_jsx(ToolbarButton, { icon: "numberedList", label: "Numbered list", ariaLabel: "Numbered list", pressed: listStyleActive("decimal"), disabled: readOnly, narrowIconOnly: true, onClick: () => toggleList("decimal") }), _jsxs("select", { "aria-label": "Numbered list style", title: "Numbered list style", disabled: readOnly || (currentListScope.kind === "list-selection" && currentListParts.length !== 1), value: currentOrderedPreset, onChange: (event) => { const preset = event.target.value; if (preset)
|
|
1744
|
+
applyListPreset(preset); }, children: [_jsx("option", { value: "", children: "Numbered list style" }), SMART_LIST_PRESETS.filter((preset) => preset.kind === "ordered").map((preset) => _jsx("option", { value: preset.id, children: preset.label }, preset.id))] })] }) : _jsx(ToolbarButton, { icon: "numberedList", label: "Numbered list", ariaLabel: "Numbered list", pressed: listStyleActive("decimal"), disabled: readOnly, narrowIconOnly: true, onClick: () => toggleList("decimal") })), t.checklist && _jsx(ToolbarButton, { icon: "checklist", label: "Checklist", ariaLabel: "Checklist", pressed: listStyleActive("disc", true), disabled: readOnly, narrowIconOnly: true, onClick: () => toggleList("disc", true) }), _jsxs(ToolbarDropdown, { icon: "restart", label: "More list tools", priority: 2, children: [listPresetSelect, t.listPreset && _jsx("div", { className: "srte-menu-separator" }), listToolsMenuItems] })] }), _jsxs(ToolbarGroup, { children: [t.link && _jsx(ToolbarButton, { icon: "link", label: "Link", ariaLabel: "Insert or edit link", disabled: readOnly || !markToolAllowed("link"), onClick: openLinkPopover }), showImageTool && _jsx(ToolbarButton, { icon: "image", label: "Image", ariaLabel: "Insert image", disabled: readOnly, onClick: () => setMediaKind("image") }), t.removeLink && _jsx(ToolbarButton, { icon: "unlink", label: "Remove link", disabled: readOnly, onClick: removeLink, widePromote: true }), t.insertFormula && _jsx(ToolbarButton, { icon: "formula", label: "Insert formula", disabled: readOnly, widePromote: true, onClick: (event) => {
|
|
1505
1745
|
const rect = event.currentTarget.getBoundingClientRect();
|
|
1506
1746
|
setFormulaLibraryPopover({ x: rect.left, y: rect.bottom + 4 });
|
|
1507
1747
|
} }), t.specialCharacters && _jsx(ToolbarButton, { icon: "specialChar", label: "Special characters", disabled: readOnly, widePromote: true, onClick: (event) => {
|
|
1508
1748
|
const rect = event.currentTarget.getBoundingClientRect();
|
|
1509
1749
|
setSpecialCharPopover({ x: rect.left, y: rect.bottom + 4 });
|
|
1510
|
-
} }), _jsx(ToolbarDropdown, { icon: "video", label: "More to insert", priority: 2, children: insertMoreMenuItems })] }), showInsertTable && _jsxs(ToolbarGroup, { children: [_jsx(ToolbarButton, { icon: "table", label: "Insert table", ariaLabel: "Insert table", disabled: readOnly, onClick: (event) => {
|
|
1750
|
+
} }), t.horizontalLine && _jsx(ToolbarButton, { icon: "divider", label: "Horizontal line", disabled: readOnly, widePromote: true, onClick: () => { insertBlockAtom("divider", {}, createNodeId()); runtime.focus(); } }), t.pageBreak && _jsx(ToolbarButton, { icon: "pageBreak", label: "Page break", disabled: readOnly, widePromote: true, onClick: () => { insertBlockAtom("page_break", {}, createNodeId()); runtime.focus(); } }), _jsx(ToolbarDropdown, { icon: "video", label: "More to insert", priority: 2, children: insertMoreMenuItems })] }), showInsertTable && _jsxs(ToolbarGroup, { children: [_jsx(ToolbarButton, { icon: "table", label: "Insert table", ariaLabel: "Insert table", disabled: readOnly, onClick: (event) => {
|
|
1511
1751
|
const rect = event.currentTarget.getBoundingClientRect();
|
|
1512
1752
|
setTableSizePopover({ x: rect.left, y: rect.bottom + 4 });
|
|
1513
1753
|
} }), _jsx(ToolbarDropdown, { icon: "mergeCells", label: "Table tools", priority: 2, children: tableToolsMenuItems })] }), _jsx("input", { ref: importRef, type: "file", accept: ".html,.htm,.md,.markdown,.docx,.pdf,text/html,text/markdown,application/vnd.openxmlformats-officedocument.wordprocessingml.document,application/pdf", hidden: true, onChange: (event) => {
|
|
@@ -1515,20 +1755,22 @@ export const CanonicalAuthorityEditor = forwardRef(function CanonicalAuthorityEd
|
|
|
1515
1755
|
if (file)
|
|
1516
1756
|
void runImport(file);
|
|
1517
1757
|
event.currentTarget.value = "";
|
|
1518
|
-
} }), _jsxs(ToolbarGroup, { children: [t.import && _jsx(ToolbarButton, { icon: "import", label: "Import", ariaLabel: "Import document", disabled: readOnly, onClick: () => importRef.current?.click() }), showSaveCopy && _jsx(ToolbarDropdown, { icon: "saveCopy", label: "Save a copy", priority: 2, children: saveCopyMenuItems }), showVersionHistoryTool && _jsx(ToolbarButton, { icon: "history", label: "Version history", ariaLabel: "Version history", disabled: readOnly, onClick: () => setVersionHistoryOpen(true) }), showReviewDropdown && _jsx(ToolbarDropdown, { icon: "comments", label: "Review", priority: 2, children: reviewMenuItems })] }), _jsxs(ToolbarGroup, { children: [t.undo && _jsx(ToolbarButton, { icon: "undo", label: "Undo", ariaLabel: "Undo", disabled: readOnly, onClick: () => { runtime.editor.undo(); runtime.focus(); } }), t.redo && _jsx(ToolbarButton, { icon: "redo", label: "Redo", ariaLabel: "Redo", disabled: readOnly, onClick: () => { runtime.editor.redo(); runtime.focus(); } })] }), _jsxs(MobileMoreMenu, { children: [textStylesMenuItems, _jsx("div", { className: "srte-menu-separator" }), paragraphToolsMenuItems, _jsx("div", { className: "srte-menu-separator" }), listPresetSelect, listToolsMenuItems, _jsx("div", { className: "srte-menu-separator" }), insertMoreMenuItems, showInsertTable && _jsxs(_Fragment, { children: [_jsx("div", { className: "srte-menu-separator" }), tableToolsMenuItems] }), showSaveCopy && _jsxs(_Fragment, { children: [_jsx("div", { className: "srte-menu-separator" }), saveCopyMenuItems] }), showReviewDropdown && _jsxs(_Fragment, { children: [_jsx("div", { className: "srte-menu-separator" }), reviewMenuItems] })] })] }), mediaKind === "image" && mediaManager && mediaProvider && _jsx(MediaManager, { open: true, onClose: () => setMediaKind(null), adapter: mediaManagerAdapter, onSelect: selectFromMediaManager }), mediaKind && mediaProvider && !(mediaKind === "image" && mediaManager) && _jsx(MediaPicker, { kind: mediaKind, onPick: (file) => void insertMediaFile(mediaKind, file), onCancel: () => setMediaKind(null) }), showVersionHistoryTool && _jsx(VersionHistoryPanel, { open: versionHistoryOpen, onClose: () => setVersionHistoryOpen(false), runtime: runtime, versionProvider: versionProvider }), showCommentsTool && rootRef.current && runtime.surface.renderer?.mapping && _jsx(CommentMarkers, { positions: runtime.editor.positions, mapping: runtime.surface.renderer.mapping, containerElement: rootRef.current, threads: threads, activeThreadId: activeThreadId, onSelectThread: selectThread }), showCommentsTool && _jsx(CommentThreadPanel, { open: commentPanelOpen, onClose: () => { setCommentPanelOpen(false); setPendingCommentRange(null); }, threads: threads, activeThreadId: activeThreadId, onSelectThread: selectThread, pendingRange: pendingCommentRange, onCreateThread: createThread, onReply: replyToThread, onResolve: setThreadResolved, onDelete: deleteThread, busyThreadId: busyThreadId }), showSuggestionsTool && rootRef.current && runtime.surface.renderer?.mapping && _jsx(StructuralSuggestionMarkers, { positions: runtime.editor.positions, mapping: runtime.surface.renderer.mapping, containerElement: rootRef.current, suggestions: structuralSuggestions, activeSuggestionId: activeSuggestionId, onSelectSuggestion: selectSuggestion }), showSuggestionsTool && _jsx(SuggestionPanel, { open: suggestionPanelOpen, onClose: () => { setSuggestionPanelOpen(false); setPendingSuggestInsertAt(null); }, document: runtime.editor.document, structuralSuggestions: structuralSuggestions, activeId: activeSuggestionId, onSelect: selectSuggestion, pendingInsert: Boolean(pendingSuggestInsertAt), onSubmitInsert: submitSuggestInsert, onAcceptInline: acceptInlineSuggestion, onRejectInline: rejectInlineSuggestion, onAcceptStructural: acceptStructural, onRejectStructural: rejectStructural, busyId: busySuggestionId }), linkPopover && _jsx(LinkEditorPopover, { x: linkPopover.x, y: linkPopover.y, initialHref: linkPopover.href, initialOpenInNewTab: linkPopover.openInNewTab, showTextInput: linkPopover.collapsed && !linkPopover.editingExisting, showRemove: linkPopover.editingExisting, autoFocus: !linkPopover.autoTriggered, onApply: applyLink, onRemove: removeLink, onCancel: () => {
|
|
1758
|
+
} }), _jsxs(ToolbarGroup, { children: [t.import && _jsx(ToolbarButton, { icon: "import", label: "Import", ariaLabel: "Import document", disabled: readOnly, onClick: () => importRef.current?.click() }), showSaveCopy && _jsx(ToolbarDropdown, { icon: "saveCopy", label: "Save a copy", priority: 2, children: saveCopyMenuItems }), showVersionHistoryTool && _jsx(ToolbarButton, { icon: "history", label: "Version history", ariaLabel: "Version history", disabled: readOnly, onClick: () => setVersionHistoryOpen(true) }), showReviewDropdown && _jsx(ToolbarDropdown, { icon: "comments", label: "Review", priority: 2, children: reviewMenuItems })] }), _jsxs(ToolbarGroup, { children: [t.undo && _jsx(ToolbarButton, { icon: "undo", label: "Undo", ariaLabel: "Undo", disabled: readOnly, onClick: () => { runtime.editor.undo(); runtime.focus(); } }), t.redo && _jsx(ToolbarButton, { icon: "redo", label: "Redo", ariaLabel: "Redo", disabled: readOnly, onClick: () => { runtime.editor.redo(); runtime.focus(); } })] }), _jsxs(MobileMoreMenu, { children: [textStylesMenuItems, _jsx("div", { className: "srte-menu-separator" }), paragraphToolsMenuItems, t.lineHeight && _jsx("div", { className: "srte-menu-separator" }), t.lineHeight && lineHeightMenuItems, _jsx("div", { className: "srte-menu-separator" }), listPresetSelect, listToolsMenuItems, _jsx("div", { className: "srte-menu-separator" }), insertMoreMenuItems, showInsertTable && _jsxs(_Fragment, { children: [_jsx("div", { className: "srte-menu-separator" }), tableToolsMenuItems] }), showSaveCopy && _jsxs(_Fragment, { children: [_jsx("div", { className: "srte-menu-separator" }), saveCopyMenuItems] }), showReviewDropdown && _jsxs(_Fragment, { children: [_jsx("div", { className: "srte-menu-separator" }), reviewMenuItems] })] })] }), mediaKind === "image" && mediaManager && mediaProvider && _jsx(MediaManager, { open: true, onClose: () => setMediaKind(null), adapter: mediaManagerAdapter, onSelect: selectFromMediaManager }), mediaKind && mediaProvider && !(mediaKind === "image" && mediaManager) && _jsx(MediaPicker, { kind: mediaKind, onPick: (file) => void insertMediaFile(mediaKind, file), onCancel: () => setMediaKind(null) }), showVersionHistoryTool && _jsx(VersionHistoryPanel, { open: versionHistoryOpen, onClose: () => setVersionHistoryOpen(false), runtime: runtime, versionProvider: versionProvider }), showCommentsTool && rootRef.current && runtime.surface.renderer?.mapping && _jsx(CommentMarkers, { positions: runtime.editor.positions, mapping: runtime.surface.renderer.mapping, containerElement: rootRef.current, threads: threads, activeThreadId: activeThreadId, onSelectThread: selectThread }), showCommentsTool && _jsx(CommentThreadPanel, { open: commentPanelOpen, onClose: () => { setCommentPanelOpen(false); setPendingCommentRange(null); }, threads: threads, activeThreadId: activeThreadId, onSelectThread: selectThread, pendingRange: pendingCommentRange, onCreateThread: createThread, onReply: replyToThread, onResolve: setThreadResolved, onDelete: deleteThread, busyThreadId: busyThreadId }), showSuggestionsTool && rootRef.current && runtime.surface.renderer?.mapping && _jsx(StructuralSuggestionMarkers, { positions: runtime.editor.positions, mapping: runtime.surface.renderer.mapping, containerElement: rootRef.current, suggestions: structuralSuggestions, activeSuggestionId: activeSuggestionId, onSelectSuggestion: selectSuggestion }), showSuggestionsTool && _jsx(SuggestionPanel, { open: suggestionPanelOpen, onClose: () => { setSuggestionPanelOpen(false); setPendingSuggestInsertAt(null); }, document: runtime.editor.document, structuralSuggestions: structuralSuggestions, activeId: activeSuggestionId, onSelect: selectSuggestion, pendingInsert: Boolean(pendingSuggestInsertAt), onSubmitInsert: submitSuggestInsert, onAcceptInline: acceptInlineSuggestion, onRejectInline: rejectInlineSuggestion, onAcceptStructural: acceptStructural, onRejectStructural: rejectStructural, busyId: busySuggestionId }), linkPopover && _jsx(LinkEditorPopover, { x: linkPopover.x, y: linkPopover.y, initialHref: linkPopover.href, initialOpenInNewTab: linkPopover.openInNewTab, showTextInput: linkPopover.collapsed && !linkPopover.editingExisting, showRemove: linkPopover.editingExisting, autoFocus: !linkPopover.autoTriggered, onApply: applyLink, onRemove: removeLink, onCancel: () => {
|
|
1519
1759
|
setLinkOverlayDismissedAt(runtime.editor.selection.head);
|
|
1520
1760
|
setLinkPopover(null);
|
|
1521
1761
|
runtime.focus();
|
|
1522
|
-
} }), !readOnly && mediaResizeTarget && mediaResizeTarget.nodeId === selectedAtomId && mediaAtomSelected && selectedAtomElement && _jsx(MediaOverlay, { atomElement: selectedAtomElement, alt: typeof selectedAtomNode?.attrs?.alt === "string" ? selectedAtomNode.attrs.alt : "", width: typeof selectedAtomNode?.attrs?.width === "number" ? selectedAtomNode.attrs.width : undefined, height: typeof selectedAtomNode?.attrs?.height === "number" ? selectedAtomNode.attrs.height : undefined, src: typeof selectedAtomNode?.attrs?.src === "string" ? selectedAtomNode.attrs.src : undefined, showMenu: false, resizable: resizableAtomSelected, onEdit: () => undefined, onResize: () => undefined, onResizeTo: resizeSelectedAtomTo, onDelete: () => undefined, onDismiss: () => setMediaResizeTarget(null) }), !readOnly && mediaContextMenu && mediaAtomSelected && selectedAtomElement && _jsx(MediaOverlay, { atomElement: selectedAtomElement, alt: typeof selectedAtomNode?.attrs?.alt === "string" ? selectedAtomNode.attrs.alt : "", width: typeof selectedAtomNode?.attrs?.width === "number" ? selectedAtomNode.attrs.width : undefined, height: typeof selectedAtomNode?.attrs?.height === "number" ? selectedAtomNode.attrs.height : undefined, src: typeof selectedAtomNode?.attrs?.src === "string" ? selectedAtomNode.attrs.src : undefined, x: mediaContextMenu.x, y: mediaContextMenu.y, resizable: resizableAtomSelected, onEdit: () => { setMediaContextMenu(null); editSelectedAtom(); }, onResize: (by) => { setMediaContextMenu(null); editSelectedAtom(by); }, onResizeTo: resizeSelectedAtomTo, onDelete: () => { setMediaContextMenu(null); deleteSelectedAtom(); }, onDismiss: () => { setMediaContextMenu(null); runtime.focus(); } }), colorPopover && _jsx(ColorPickerPopover, { x: colorPopover.x, y: colorPopover.y, label: colorPopover.target.kind === "mark"
|
|
1762
|
+
} }), !readOnly && mediaResizeTarget && mediaResizeTarget.nodeId === selectedAtomId && mediaAtomSelected && selectedAtomElement && _jsx(MediaOverlay, { atomElement: selectedAtomElement, alt: typeof selectedAtomNode?.attrs?.alt === "string" ? selectedAtomNode.attrs.alt : "", width: typeof selectedAtomNode?.attrs?.width === "number" ? selectedAtomNode.attrs.width : undefined, height: typeof selectedAtomNode?.attrs?.height === "number" ? selectedAtomNode.attrs.height : undefined, src: typeof selectedAtomNode?.attrs?.src === "string" ? selectedAtomNode.attrs.src : undefined, showMenu: false, resizable: resizableAtomSelected, onEdit: () => undefined, onResize: () => undefined, onResizeTo: resizeSelectedAtomTo, onDelete: () => undefined, onDismiss: () => setMediaResizeTarget(null) }), !readOnly && mediaContextMenu && (mediaAtomSelected || actionOnlyAtomSelected) && selectedAtomElement && _jsx(MediaOverlay, { atomElement: selectedAtomElement, alt: typeof selectedAtomNode?.attrs?.alt === "string" ? selectedAtomNode.attrs.alt : "", width: typeof selectedAtomNode?.attrs?.width === "number" ? selectedAtomNode.attrs.width : undefined, height: typeof selectedAtomNode?.attrs?.height === "number" ? selectedAtomNode.attrs.height : undefined, src: typeof selectedAtomNode?.attrs?.src === "string" ? selectedAtomNode.attrs.src : undefined, x: mediaContextMenu.x, y: mediaContextMenu.y, resizable: resizableAtomSelected, editable: mediaAtomSelected, onEdit: () => { setMediaContextMenu(null); editSelectedAtom(); }, onResize: (by) => { setMediaContextMenu(null); editSelectedAtom(by); }, onResizeTo: resizeSelectedAtomTo, onDelete: () => { setMediaContextMenu(null); deleteSelectedAtom(); }, onDismiss: () => { setMediaContextMenu(null); runtime.focus(); } }), colorPopover && _jsx(ColorPickerPopover, { x: colorPopover.x, y: colorPopover.y, label: colorPopover.target.kind === "mark"
|
|
1523
1763
|
? (colorPopover.target.markId === "textColor" ? "Text colour" : "Background colour")
|
|
1524
|
-
: colorPopover.target.
|
|
1764
|
+
: colorPopover.target.kind === "blockquote"
|
|
1765
|
+
? (colorPopover.target.attr === "textColor" ? "Blockquote text colour" : "Blockquote background colour")
|
|
1766
|
+
: colorPopover.target.attr === "textColor" ? "Cell text colour" : "Cell background colour", ...(colorPopover.initialValue ? { initialValue: colorPopover.initialValue } : {}), recentColors: recentColors[colorBucketFor(colorPopover.target)], onPreview: previewColor, onApply: applyColor, onCancel: cancelColorPopover }), tableSizePopover && _jsx(TableSizePickerPopover, { x: tableSizePopover.x, y: tableSizePopover.y, onInsert: (rows, columns) => {
|
|
1525
1767
|
setTableSizePopover(null);
|
|
1526
1768
|
insertTable(rows, columns);
|
|
1527
1769
|
runtime.focus();
|
|
1528
1770
|
}, onCancel: () => {
|
|
1529
1771
|
setTableSizePopover(null);
|
|
1530
1772
|
runtime.focus();
|
|
1531
|
-
} }), tableBorderPopover && _jsx(TableBorderPopover, { x: tableBorderPopover.x, y: tableBorderPopover.y, initial: tableBorderPopover.initial, recentColors: recentColors.border, onPreview: previewCellBorder, onApply: applyCellBorderCommit, onCancel: cancelTableBorderPopover }), mediaDetailsPopover && _jsx(MediaDetailsPopover, { x: mediaDetailsPopover.x, y: mediaDetailsPopover.y, initial: mediaDetailsPopover.initial, onApply: applyMediaDetails, onCancel: () => { setMediaDetailsPopover(null); runtime.focus(); } }), formulaLibraryPopover && _jsx(FormulaLibraryPopover, { x: formulaLibraryPopover.x, y: formulaLibraryPopover.y, onInsert: insertFormulaFromLibrary, onCancel: () => { setFormulaLibraryPopover(null); runtime.focus(); } }), specialCharPopover && _jsx(SpecialCharacterPopover, { x: specialCharPopover.x, y: specialCharPopover.y, recentCharacters: recentSpecialChars, onInsert: insertSpecialCharacter, onCancel: () => { setSpecialCharPopover(null); runtime.focus(); } }), !readOnly && selectedTableElement && _jsx(TableResizeHandles, { tableElement: selectedTableElement, onResizeColumn: resizeTableColumn, onResizeRow: resizeTableRow }), contextMenu && _jsx(ContextMenu, { x: contextMenu.x, y: contextMenu.y, items: resolveContextMenuItems(), onDismiss: () => { setContextMenu(null); runtime.focus(); } }), _jsx("div", { ref: rootRef, className: "srte-editor", "data-placeholder": placeholder, contentEditable: !readOnly, suppressContentEditableWarning: true, role: "textbox", "aria-label": "Smart RTE editing surface", "aria-multiline": "true", onMouseDown: (event) => {
|
|
1773
|
+
} }), tableBorderPopover && _jsx(TableBorderPopover, { x: tableBorderPopover.x, y: tableBorderPopover.y, initial: tableBorderPopover.initial, recentColors: recentColors.border, onPreview: previewCellBorder, onApply: applyCellBorderCommit, onCancel: cancelTableBorderPopover }), blockquoteBorderPopover && _jsx(BlockquoteBorderPopover, { x: blockquoteBorderPopover.x, y: blockquoteBorderPopover.y, initial: blockquoteBorderPopover.initial, recentColors: recentColors.border, onPreview: previewBlockquoteBorder, onApply: applyBlockquoteBorderCommit, onCancel: cancelBlockquoteBorderPopover }), mediaDetailsPopover && _jsx(MediaDetailsPopover, { x: mediaDetailsPopover.x, y: mediaDetailsPopover.y, initial: mediaDetailsPopover.initial, onApply: applyMediaDetails, onCancel: () => { setMediaDetailsPopover(null); runtime.focus(); } }), formulaLibraryPopover && _jsx(FormulaLibraryPopover, { x: formulaLibraryPopover.x, y: formulaLibraryPopover.y, onInsert: insertFormulaFromLibrary, onCancel: () => { setFormulaLibraryPopover(null); runtime.focus(); } }), specialCharPopover && _jsx(SpecialCharacterPopover, { x: specialCharPopover.x, y: specialCharPopover.y, recentCharacters: recentSpecialChars, onInsert: insertSpecialCharacter, onCancel: () => { setSpecialCharPopover(null); runtime.focus(); } }), !readOnly && selectedTableElement && _jsx(TableResizeHandles, { tableElement: selectedTableElement, onResizeColumn: resizeTableColumn, onResizeRow: resizeTableRow }), contextMenu && _jsx(ContextMenu, { x: contextMenu.x, y: contextMenu.y, items: resolveContextMenuItems(), onDismiss: () => { setContextMenu(null); runtime.focus(); } }), _jsx("div", { ref: rootRef, className: "srte-editor", "data-placeholder": placeholder, contentEditable: !readOnly, suppressContentEditableWarning: true, role: "textbox", "aria-label": "Smart RTE editing surface", "aria-multiline": "true", onMouseDown: (event) => {
|
|
1532
1774
|
// A Ctrl/Cmd+click on a link is meant to open it (onClick below),
|
|
1533
1775
|
// not move the caret into it - but native mousedown's own default
|
|
1534
1776
|
// action *is* the caret-repositioning, and mousedown fires (and
|
|
@@ -1591,7 +1833,7 @@ export const CanonicalAuthorityEditor = forwardRef(function CanonicalAuthorityEd
|
|
|
1591
1833
|
runtime.editor.setSelection({ type: "node", anchor: range.from, head: range.to }, { source: "api" });
|
|
1592
1834
|
runtime.surface.renderer?.render(runtime.editor.document, runtime.editor.selection);
|
|
1593
1835
|
}
|
|
1594
|
-
if (mapped.node.type !== "
|
|
1836
|
+
if (mapped.node.type !== "unknown") {
|
|
1595
1837
|
setContextMenu(null);
|
|
1596
1838
|
const isImage = mapped.node.type === "image" || mapped.node.type === "block_image";
|
|
1597
1839
|
if (isImage) {
|
|
@@ -1635,10 +1877,16 @@ export const CanonicalAuthorityEditor = forwardRef(function CanonicalAuthorityEd
|
|
|
1635
1877
|
}
|
|
1636
1878
|
}
|
|
1637
1879
|
// A right-click that is not handled by the media branch above and
|
|
1638
|
-
// does not resolve to a table cell opens no
|
|
1880
|
+
// does not resolve to a table cell or a blockquote opens no
|
|
1881
|
+
// generic menu. Table cell checked first: a right-click inside a
|
|
1882
|
+
// table nested inside a blockquote should still show cell
|
|
1883
|
+
// options, matching "closest/innermost container wins."
|
|
1639
1884
|
const rightClickTableScope = runtime.editor.resolveScope({ want: "table-grid" });
|
|
1640
1885
|
if ("kind" in rightClickTableScope && rightClickTableScope.kind === "table-grid") {
|
|
1641
1886
|
setContextMenu({ x: event.clientX, y: event.clientY });
|
|
1642
1887
|
}
|
|
1888
|
+
else if (findBlockquoteAncestor()) {
|
|
1889
|
+
setContextMenu({ x: event.clientX, y: event.clientY });
|
|
1890
|
+
}
|
|
1643
1891
|
}, style: { minHeight, maxHeight, overflow: "auto" } })] });
|
|
1644
1892
|
});
|