@pilotiq/tiptap 3.17.0 → 3.19.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 +30 -0
- package/dist/extensions/SlashCommandExtension.js +0 -35
- package/dist/extensions/contentBlocks.d.ts +31 -0
- package/dist/extensions/contentBlocks.js +130 -0
- package/dist/index.d.ts +2 -2
- package/dist/index.js +2 -2
- package/dist/react/FloatingToolbar.js +8 -0
- package/package.json +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,35 @@
|
|
|
1
1
|
# @pilotiq/tiptap
|
|
2
2
|
|
|
3
|
+
## 3.19.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- 75875da: Slash menu: remove the Align left/center/right, Lead, Small, and Clear formatting
|
|
8
|
+
entries. These remain available as toolbar buttons; dropping them from the `/`
|
|
9
|
+
menu keeps it focused on real content blocks (closes #151, #152).
|
|
10
|
+
|
|
11
|
+
### Patch Changes
|
|
12
|
+
|
|
13
|
+
- 4b82d23: Don't show the inline mark toolbar inside the callout (alert) block.
|
|
14
|
+
|
|
15
|
+
The selection-based `FloatingToolbar` (bold / italic / strike / code / link) was appearing when text was selected inside an `alert` block, even though the callout owns its own content + chrome through the in-block gear menu (#155). Its `shouldShow` now bails when either selection endpoint sits inside an `alert` at any ancestor depth.
|
|
16
|
+
|
|
17
|
+
- 4013010: Also hide the inline mark toolbar when the whole callout block is "picked".
|
|
18
|
+
|
|
19
|
+
Follow-up to the previous callout fix (#155): the `FloatingToolbar` still appeared when the entire `alert` block was selected via the drag handle, because that is a `NodeSelection` whose `$from` resolves to _before_ the node — so walking ancestors from `$from` never sees the `alert`. The alert-detection is now an exported `isSelectionInAlert(selection)` predicate that handles both a text/range selection inside the alert AND a whole-block `NodeSelection` on it, pinned by `contentBlockAlertSelection.dom.test.ts` against the real schema (including a real `NodeSelection` on the alert).
|
|
20
|
+
|
|
21
|
+
- d09373b: Fix double-Enter trapping an empty node inside landmark blocks (`keyTakeaways` / `summary` / `intro`).
|
|
22
|
+
|
|
23
|
+
These blocks use `content: 'block+'`, so the default list-exit on double-Enter only _lifted_ the empty list item into a paragraph — a valid `block+` child — which stayed trapped inside the block instead of escaping it (#150). A new `LabeledBlockExitKeymap` (high priority, so it runs before `ListItem`'s `splitListItem`) intercepts the gesture: an empty trailing node (a paragraph, or the empty paragraph of a last list item) inside a landmark block is dropped and the cursor lands in a fresh paragraph _after_ the block, mirroring the FAQ block's Enter-flow. The empty-chain-only case replaces the now-useless block with a paragraph. The logic is exported as `planExitLabeledBlock` and pinned by a `contentBlockExit.dom.test.ts` contract test against the real schema.
|
|
24
|
+
|
|
25
|
+
## 3.18.0
|
|
26
|
+
|
|
27
|
+
### Minor Changes
|
|
28
|
+
|
|
29
|
+
- 442df8a: Export `planWrapBlocks` from the package entry point.
|
|
30
|
+
|
|
31
|
+
`#148` shipped `planWrapBlocks` but left it internal (only `useAiInlineDiff` could reach it), so the Normalizer agent's wrap path had no way to be contract-tested against the real schema. It now sits alongside the other surgical planners (`planInsertBlockBefore` / `planReplaceBlock` / `planDeleteBlock`) in the public API, and a `surgicalOpsWrap.dom.test.ts` contract test pins its editor-side guarantees (content-preserving wrap, exactly one wrapper node, and the one-trailing-empty-paragraph rule when the wrap produces a terminal landmark) against the live `@pilotiq/tiptap` planners + schema — mirroring the FAQ-placement contract.
|
|
32
|
+
|
|
3
33
|
## 3.17.0
|
|
4
34
|
|
|
5
35
|
### Minor Changes
|
|
@@ -231,41 +231,6 @@ export function buildSlashItems(blocks, mergeTags, query, insert) {
|
|
|
231
231
|
insert.onInsertImage();
|
|
232
232
|
},
|
|
233
233
|
}] : []),
|
|
234
|
-
{
|
|
235
|
-
key: 'align-left', label: 'Align left', icon: '⇤', group: 'Align',
|
|
236
|
-
searchKey: 'align left start',
|
|
237
|
-
command: ({ editor, range }) => editor.chain().focus().deleteRange(range).setTextAlign('left').run(),
|
|
238
|
-
},
|
|
239
|
-
{
|
|
240
|
-
key: 'align-center', label: 'Align center', icon: '⇔', group: 'Align',
|
|
241
|
-
searchKey: 'align center middle',
|
|
242
|
-
command: ({ editor, range }) => editor.chain().focus().deleteRange(range).setTextAlign('center').run(),
|
|
243
|
-
},
|
|
244
|
-
{
|
|
245
|
-
key: 'align-right', label: 'Align right', icon: '⇥', group: 'Align',
|
|
246
|
-
searchKey: 'align right end',
|
|
247
|
-
command: ({ editor, range }) => editor.chain().focus().deleteRange(range).setTextAlign('right').run(),
|
|
248
|
-
},
|
|
249
|
-
{
|
|
250
|
-
key: 'clear-format', label: 'Clear formatting', icon: '⌫', group: 'Basic',
|
|
251
|
-
searchKey: 'clear formatting reset',
|
|
252
|
-
command: ({ editor, range }) => editor.chain().focus().deleteRange(range).clearNodes().unsetAllMarks().run(),
|
|
253
|
-
},
|
|
254
|
-
// Inline-mark size variants. Slash-menu form leaves the slash range in
|
|
255
|
-
// place rather than swallowing it, so the user runs the command on the
|
|
256
|
-
// word they were just typing — the alternative ("/lead" deletes the
|
|
257
|
-
// range, then user types more) requires re-positioning the cursor and
|
|
258
|
-
// breaks the "type-toggle-keep-typing" rhythm authors use most.
|
|
259
|
-
{
|
|
260
|
-
key: 'lead', label: 'Lead', icon: 'P+', group: 'Style',
|
|
261
|
-
searchKey: 'lead lede intro paragraph emphasis',
|
|
262
|
-
command: ({ editor, range }) => editor.chain().focus().deleteRange(range).toggleMark('lead').run(),
|
|
263
|
-
},
|
|
264
|
-
{
|
|
265
|
-
key: 'small', label: 'Small', icon: 'P-', group: 'Style',
|
|
266
|
-
searchKey: 'small fine print footnote caption',
|
|
267
|
-
command: ({ editor, range }) => editor.chain().focus().deleteRange(range).toggleMark('small').run(),
|
|
268
|
-
},
|
|
269
234
|
];
|
|
270
235
|
const customs = blocks.map((b) => ({
|
|
271
236
|
key: `block:${b.name}`,
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { Node, Extension } from '@tiptap/core';
|
|
2
|
+
import { type EditorState, type Transaction, type Selection } from '@tiptap/pm/state';
|
|
2
3
|
export { ALERT_VARIANTS, ALERT_VARIANT_LABEL, coerceAlertType, type AlertType } from './alertVariants.js';
|
|
3
4
|
export declare const KeyTakeaways: Node<any, any>;
|
|
4
5
|
export declare const Summary: Node<any, any>;
|
|
@@ -26,10 +27,40 @@ export declare const Alert: Node<any, any>;
|
|
|
26
27
|
export declare const AlertTitle: Node<any, any>;
|
|
27
28
|
/** The Alert body — the editable description (`block+`). */
|
|
28
29
|
export declare const AlertBody: Node<any, any>;
|
|
30
|
+
/**
|
|
31
|
+
* True when `selection` is the callout (`alert`) block or sits inside it.
|
|
32
|
+
* Used by `FloatingToolbar` to suppress the inline mark toolbar in callouts
|
|
33
|
+
* (#155). Two distinct cases:
|
|
34
|
+
* - a text/range selection WITHIN the alert — its endpoints resolve to an
|
|
35
|
+
* `alert` ancestor;
|
|
36
|
+
* - the whole block PICKED via the drag handle — a `NodeSelection` on the
|
|
37
|
+
* alert, whose `$from` resolves to *before* the node, so its ancestors are
|
|
38
|
+
* the doc (not the alert) and the endpoint walk alone would miss it.
|
|
39
|
+
*/
|
|
40
|
+
export declare function isSelectionInAlert(selection: Selection): boolean;
|
|
29
41
|
export declare const ProsCons: Node<any, any>;
|
|
30
42
|
export declare const ProsColumn: Node<any, any>;
|
|
31
43
|
export declare const ConsColumn: Node<any, any>;
|
|
32
44
|
export declare const ContentBlockKeymap: Extension<any, any>;
|
|
45
|
+
/**
|
|
46
|
+
* Plan the "exit a landmark block" Enter gesture. Returns a transaction
|
|
47
|
+
* modifier when the cursor sits in an EMPTY trailing node (a paragraph, or the
|
|
48
|
+
* empty paragraph of a last list item) inside a `block+` landmark block — the
|
|
49
|
+
* modifier removes that empty node and places the cursor in a paragraph after
|
|
50
|
+
* the block. Returns `null` when the gesture doesn't apply, so the keymap
|
|
51
|
+
* yields to the default Enter / list-split behaviour. Exported (alongside the
|
|
52
|
+
* `surgicalOps` planners) so it can be exercised against a real editor in a
|
|
53
|
+
* test without mounting React.
|
|
54
|
+
*/
|
|
55
|
+
export declare function planExitLabeledBlock(state: EditorState): ((tr: Transaction) => void) | null;
|
|
56
|
+
/**
|
|
57
|
+
* Enter-to-exit for landmark blocks. A dedicated keymap at high priority so it
|
|
58
|
+
* runs BEFORE `ListItem`'s Enter (`splitListItem`, default priority 100) — and
|
|
59
|
+
* kept separate from `ContentBlockKeymap` so it doesn't change that keymap's
|
|
60
|
+
* Backspace priority (which would alter delete-block behaviour). Returns
|
|
61
|
+
* `false` when `planExitLabeledBlock` declines, yielding to the default Enter.
|
|
62
|
+
*/
|
|
63
|
+
export declare const LabeledBlockExitKeymap: Extension<any, any>;
|
|
33
64
|
/** All inline content-block extensions — registered in the editor's list. */
|
|
34
65
|
export declare const contentBlockNodes: (Node<any, any> | Extension<any, any>)[];
|
|
35
66
|
//# sourceMappingURL=contentBlocks.d.ts.map
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { Node, Extension, mergeAttributes } from '@tiptap/core';
|
|
2
2
|
import { ReactNodeViewRenderer } from '@tiptap/react';
|
|
3
|
+
import { TextSelection, NodeSelection } from '@tiptap/pm/state';
|
|
3
4
|
import { AlertNodeView } from '../react/AlertNodeView.js';
|
|
4
5
|
import { FaqNodeView } from '../react/FaqNodeView.js';
|
|
5
6
|
import { FaqItemNodeView } from '../react/FaqItemNodeView.js';
|
|
@@ -501,6 +502,29 @@ export const AlertBody = Node.create({
|
|
|
501
502
|
return ['div', mergeAttributes(HTMLAttributes, { 'data-type': 'alertBody', class: 'pilotiq-alert-description' }), 0];
|
|
502
503
|
},
|
|
503
504
|
});
|
|
505
|
+
/** True when `$pos` sits inside an `alert` (callout) block at any ancestor depth. */
|
|
506
|
+
function isInsideAlert($pos) {
|
|
507
|
+
for (let d = $pos.depth; d > 0; d--) {
|
|
508
|
+
if ($pos.node(d).type.name === 'alert')
|
|
509
|
+
return true;
|
|
510
|
+
}
|
|
511
|
+
return false;
|
|
512
|
+
}
|
|
513
|
+
/**
|
|
514
|
+
* True when `selection` is the callout (`alert`) block or sits inside it.
|
|
515
|
+
* Used by `FloatingToolbar` to suppress the inline mark toolbar in callouts
|
|
516
|
+
* (#155). Two distinct cases:
|
|
517
|
+
* - a text/range selection WITHIN the alert — its endpoints resolve to an
|
|
518
|
+
* `alert` ancestor;
|
|
519
|
+
* - the whole block PICKED via the drag handle — a `NodeSelection` on the
|
|
520
|
+
* alert, whose `$from` resolves to *before* the node, so its ancestors are
|
|
521
|
+
* the doc (not the alert) and the endpoint walk alone would miss it.
|
|
522
|
+
*/
|
|
523
|
+
export function isSelectionInAlert(selection) {
|
|
524
|
+
if (selection instanceof NodeSelection && selection.node.type.name === 'alert')
|
|
525
|
+
return true;
|
|
526
|
+
return isInsideAlert(selection.$from) || isInsideAlert(selection.$to);
|
|
527
|
+
}
|
|
504
528
|
// ── Pros & cons — two labelled columns (each a `block+` body, list by default) ──
|
|
505
529
|
export const ProsCons = Node.create({
|
|
506
530
|
name: 'prosCons',
|
|
@@ -591,6 +615,111 @@ export const ContentBlockKeymap = Extension.create({
|
|
|
591
615
|
};
|
|
592
616
|
},
|
|
593
617
|
});
|
|
618
|
+
// ── Keyboard: Enter exits a landmark block from an empty trailing node ──
|
|
619
|
+
//
|
|
620
|
+
// Landmark blocks (`keyTakeaways` / `summary` / `intro`) use `content: 'block+'`.
|
|
621
|
+
// The default double-Enter list-exit only *lifts* the empty list item into a
|
|
622
|
+
// paragraph, which is a valid `block+` child — so it stays trapped inside the
|
|
623
|
+
// block instead of escaping it (#150). This handler intercepts the gesture: an
|
|
624
|
+
// empty trailing node (a paragraph, or an empty last list item) inside a
|
|
625
|
+
// landmark block → drop it and land the cursor in a fresh paragraph AFTER the
|
|
626
|
+
// block, mirroring the FAQ block's Enter-flow.
|
|
627
|
+
const LANDMARK_BLOCKS = new Set(['keyTakeaways', 'summary', 'intro']);
|
|
628
|
+
const LIST_CONTAINERS = new Set(['bulletList', 'orderedList', 'taskList']);
|
|
629
|
+
const LIST_ITEMS = new Set(['listItem', 'taskItem']);
|
|
630
|
+
/**
|
|
631
|
+
* Plan the "exit a landmark block" Enter gesture. Returns a transaction
|
|
632
|
+
* modifier when the cursor sits in an EMPTY trailing node (a paragraph, or the
|
|
633
|
+
* empty paragraph of a last list item) inside a `block+` landmark block — the
|
|
634
|
+
* modifier removes that empty node and places the cursor in a paragraph after
|
|
635
|
+
* the block. Returns `null` when the gesture doesn't apply, so the keymap
|
|
636
|
+
* yields to the default Enter / list-split behaviour. Exported (alongside the
|
|
637
|
+
* `surgicalOps` planners) so it can be exercised against a real editor in a
|
|
638
|
+
* test without mounting React.
|
|
639
|
+
*/
|
|
640
|
+
export function planExitLabeledBlock(state) {
|
|
641
|
+
const { $from, empty } = state.selection;
|
|
642
|
+
if (!empty)
|
|
643
|
+
return null;
|
|
644
|
+
// Cursor must be in an empty textblock (an empty paragraph / list-item body).
|
|
645
|
+
if (!$from.parent.isTextblock || $from.parent.content.size !== 0)
|
|
646
|
+
return null;
|
|
647
|
+
// Nearest landmark-block ancestor.
|
|
648
|
+
let blockDepth = -1;
|
|
649
|
+
for (let d = $from.depth - 1; d > 0; d--) {
|
|
650
|
+
if (LANDMARK_BLOCKS.has($from.node(d).type.name)) {
|
|
651
|
+
blockDepth = d;
|
|
652
|
+
break;
|
|
653
|
+
}
|
|
654
|
+
}
|
|
655
|
+
if (blockDepth === -1)
|
|
656
|
+
return null;
|
|
657
|
+
// The empty textblock must be the LAST descendant at every level down from
|
|
658
|
+
// the block — i.e. genuinely trailing, not an empty node mid-block.
|
|
659
|
+
for (let d = $from.depth; d > blockDepth; d--) {
|
|
660
|
+
if ($from.index(d - 1) !== $from.node(d - 1).childCount - 1)
|
|
661
|
+
return null;
|
|
662
|
+
}
|
|
663
|
+
// Climb past only-child LIST wrappers (the listItem / list holding the empty
|
|
664
|
+
// paragraph) so we drop the whole empty item, not just its inner paragraph —
|
|
665
|
+
// but never past a non-list single-child wrapper (e.g. a blockquote), which
|
|
666
|
+
// we'd leave illegally empty; bail to the default Enter in that case.
|
|
667
|
+
let removeDepth = $from.depth;
|
|
668
|
+
while (removeDepth > blockDepth + 1 &&
|
|
669
|
+
$from.node(removeDepth - 1).childCount === 1 &&
|
|
670
|
+
(LIST_ITEMS.has($from.node(removeDepth - 1).type.name) ||
|
|
671
|
+
LIST_CONTAINERS.has($from.node(removeDepth - 1).type.name))) {
|
|
672
|
+
removeDepth--;
|
|
673
|
+
}
|
|
674
|
+
// Removing this node would strand an empty non-list parent → let the default
|
|
675
|
+
// Enter handle it.
|
|
676
|
+
if (removeDepth > blockDepth + 1 && $from.node(removeDepth - 1).childCount === 1)
|
|
677
|
+
return null;
|
|
678
|
+
const block = $from.node(blockDepth);
|
|
679
|
+
const blockStart = $from.before(blockDepth);
|
|
680
|
+
const blockEnd = blockStart + block.nodeSize;
|
|
681
|
+
const paragraph = state.schema.nodes['paragraph']?.createAndFill();
|
|
682
|
+
if (!paragraph)
|
|
683
|
+
return null;
|
|
684
|
+
// The empty chain is the block's ONLY content → replace the whole (now
|
|
685
|
+
// useless) block with the empty paragraph.
|
|
686
|
+
if (removeDepth === blockDepth + 1 && block.childCount === 1) {
|
|
687
|
+
return (tr) => {
|
|
688
|
+
tr.replaceWith(blockStart, blockEnd, paragraph);
|
|
689
|
+
tr.setSelection(TextSelection.create(tr.doc, blockStart + 1));
|
|
690
|
+
};
|
|
691
|
+
}
|
|
692
|
+
// Otherwise drop the trailing empty node and add a paragraph after the block.
|
|
693
|
+
const removeStart = $from.before(removeDepth);
|
|
694
|
+
const removeEnd = removeStart + $from.node(removeDepth).nodeSize;
|
|
695
|
+
return (tr) => {
|
|
696
|
+
tr.delete(removeStart, removeEnd);
|
|
697
|
+
const after = tr.mapping.map(blockEnd);
|
|
698
|
+
tr.insert(after, paragraph);
|
|
699
|
+
tr.setSelection(TextSelection.create(tr.doc, after + 1));
|
|
700
|
+
};
|
|
701
|
+
}
|
|
702
|
+
/**
|
|
703
|
+
* Enter-to-exit for landmark blocks. A dedicated keymap at high priority so it
|
|
704
|
+
* runs BEFORE `ListItem`'s Enter (`splitListItem`, default priority 100) — and
|
|
705
|
+
* kept separate from `ContentBlockKeymap` so it doesn't change that keymap's
|
|
706
|
+
* Backspace priority (which would alter delete-block behaviour). Returns
|
|
707
|
+
* `false` when `planExitLabeledBlock` declines, yielding to the default Enter.
|
|
708
|
+
*/
|
|
709
|
+
export const LabeledBlockExitKeymap = Extension.create({
|
|
710
|
+
name: 'pilotiqLabeledBlockExitKeymap',
|
|
711
|
+
priority: 1000,
|
|
712
|
+
addKeyboardShortcuts() {
|
|
713
|
+
return {
|
|
714
|
+
Enter: ({ editor }) => {
|
|
715
|
+
const plan = planExitLabeledBlock(editor.state);
|
|
716
|
+
if (!plan)
|
|
717
|
+
return false;
|
|
718
|
+
return editor.commands.command(({ tr }) => { plan(tr); return true; });
|
|
719
|
+
},
|
|
720
|
+
};
|
|
721
|
+
},
|
|
722
|
+
});
|
|
594
723
|
/** All inline content-block extensions — registered in the editor's list. */
|
|
595
724
|
export const contentBlockNodes = [
|
|
596
725
|
KeyTakeaways,
|
|
@@ -607,4 +736,5 @@ export const contentBlockNodes = [
|
|
|
607
736
|
ProsColumn,
|
|
608
737
|
ConsColumn,
|
|
609
738
|
ContentBlockKeymap,
|
|
739
|
+
LabeledBlockExitKeymap,
|
|
610
740
|
];
|
package/dist/index.d.ts
CHANGED
|
@@ -9,7 +9,7 @@ export { TiptapEditor } from './react/TiptapEditor.js';
|
|
|
9
9
|
export { AiSuggestionExtension, aiSuggestionPluginKey, upsertSuggestion, upsertSuggestions, removeSuggestion, remapSuggestions, sortForApproveAll, clampPos, type AiSuggestion, type AiSuggestionExtensionOptions, } from './extensions/AiSuggestionExtension.js';
|
|
10
10
|
export { useAiSuggestionBridge } from './react/useAiSuggestionBridge.js';
|
|
11
11
|
export { AiInlineDiffExtension, aiInlineDiffPluginKey, getAiInlineDiffState, type AiInlineDiffExtensionOptions, } from './extensions/AiInlineDiffExtension.js';
|
|
12
|
-
export { planReplaceBlock, planInsertBlockBefore, planDeleteBlock, planUpdateBlockMark, summarizeBlockStructure, type BlockMarkRange, type TransactionModifier, } from './surgicalOps.js';
|
|
12
|
+
export { planReplaceBlock, planInsertBlockBefore, planDeleteBlock, planWrapBlocks, planUpdateBlockMark, summarizeBlockStructure, type BlockMarkRange, type TransactionModifier, } from './surgicalOps.js';
|
|
13
13
|
export { renderRichTextToHtml, isRichTextValue, type RenderRichTextOptions, type TiptapNode, type TiptapMark, } from './render.js';
|
|
14
|
-
export { contentBlockNodes, Intro, Faq, FaqItem, FaqQuestion, FaqAnswer, Alert, AlertTitle, AlertBody, Summary, KeyTakeaways, ProsCons, ProsColumn, ConsColumn, ContentBlockKeymap, ALERT_VARIANTS, ALERT_VARIANT_LABEL, coerceAlertType, type AlertType, } from './extensions/contentBlocks.js';
|
|
14
|
+
export { contentBlockNodes, Intro, Faq, FaqItem, FaqQuestion, FaqAnswer, Alert, AlertTitle, AlertBody, Summary, KeyTakeaways, ProsCons, ProsColumn, ConsColumn, ContentBlockKeymap, LabeledBlockExitKeymap, planExitLabeledBlock, isSelectionInAlert, ALERT_VARIANTS, ALERT_VARIANT_LABEL, coerceAlertType, type AlertType, } from './extensions/contentBlocks.js';
|
|
15
15
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.js
CHANGED
|
@@ -9,11 +9,11 @@ export { TiptapEditor } from './react/TiptapEditor.js';
|
|
|
9
9
|
export { AiSuggestionExtension, aiSuggestionPluginKey, upsertSuggestion, upsertSuggestions, removeSuggestion, remapSuggestions, sortForApproveAll, clampPos, } from './extensions/AiSuggestionExtension.js';
|
|
10
10
|
export { useAiSuggestionBridge } from './react/useAiSuggestionBridge.js';
|
|
11
11
|
export { AiInlineDiffExtension, aiInlineDiffPluginKey, getAiInlineDiffState, } from './extensions/AiInlineDiffExtension.js';
|
|
12
|
-
export { planReplaceBlock, planInsertBlockBefore, planDeleteBlock, planUpdateBlockMark, summarizeBlockStructure, } from './surgicalOps.js';
|
|
12
|
+
export { planReplaceBlock, planInsertBlockBefore, planDeleteBlock, planWrapBlocks, planUpdateBlockMark, summarizeBlockStructure, } from './surgicalOps.js';
|
|
13
13
|
export { renderRichTextToHtml, isRichTextValue, } from './render.js';
|
|
14
14
|
// Default content-block node specs (Intro / FAQ / Alert / Summary / Key takeaways /
|
|
15
15
|
// Pros & cons). `contentBlockNodes` is the exact array `TiptapEditor` registers,
|
|
16
16
|
// so a consumer can build a headless editor whose schema matches the live
|
|
17
17
|
// editor — e.g. to parse the content-block HTML or drive the surgical-op
|
|
18
18
|
// planners (`planInsertBlockBefore` & co.) in a test, without mounting React.
|
|
19
|
-
export { contentBlockNodes, Intro, Faq, FaqItem, FaqQuestion, FaqAnswer, Alert, AlertTitle, AlertBody, Summary, KeyTakeaways, ProsCons, ProsColumn, ConsColumn, ContentBlockKeymap, ALERT_VARIANTS, ALERT_VARIANT_LABEL, coerceAlertType, } from './extensions/contentBlocks.js';
|
|
19
|
+
export { contentBlockNodes, Intro, Faq, FaqItem, FaqQuestion, FaqAnswer, Alert, AlertTitle, AlertBody, Summary, KeyTakeaways, ProsCons, ProsColumn, ConsColumn, ContentBlockKeymap, LabeledBlockExitKeymap, planExitLabeledBlock, isSelectionInAlert, ALERT_VARIANTS, ALERT_VARIANT_LABEL, coerceAlertType, } from './extensions/contentBlocks.js';
|
|
@@ -2,6 +2,7 @@ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-run
|
|
|
2
2
|
import { useEffect, useState } from 'react';
|
|
3
3
|
import { Tooltip } from '@base-ui/react/tooltip';
|
|
4
4
|
import { Dialog } from '@base-ui/react/dialog';
|
|
5
|
+
import { isSelectionInAlert } from '../extensions/contentBlocks.js';
|
|
5
6
|
/**
|
|
6
7
|
* Selection-based formatting toolbar. Visible whenever the editor has a
|
|
7
8
|
* non-empty range selection inside text content. Inline marks (B/I/S/Code)
|
|
@@ -19,6 +20,13 @@ export function FloatingToolbar({ editor }) {
|
|
|
19
20
|
setPos(null);
|
|
20
21
|
return;
|
|
21
22
|
}
|
|
23
|
+
// The callout/alert block owns its content + chrome (the in-block gear
|
|
24
|
+
// menu); the inline mark toolbar shouldn't appear inside it — or when the
|
|
25
|
+
// whole block is picked via the drag handle (a NodeSelection) (#155).
|
|
26
|
+
if (isSelectionInAlert(editor.state.selection)) {
|
|
27
|
+
setPos(null);
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
22
30
|
// Don't show on full-block selections (e.g. clicking a custom block).
|
|
23
31
|
const slice = editor.state.doc.slice(from, to);
|
|
24
32
|
if (slice.content.childCount === 0) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pilotiq/tiptap",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.19.0",
|
|
4
4
|
"description": "Tiptap rich-text editor adapter for @pilotiq/pilotiq — slash menu, draggable blocks, custom-block API",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -93,7 +93,7 @@
|
|
|
93
93
|
"react-dom": "^19",
|
|
94
94
|
"tiptap-markdown": "^0.9",
|
|
95
95
|
"typescript": "^5",
|
|
96
|
-
"@pilotiq/pilotiq": "^0.
|
|
96
|
+
"@pilotiq/pilotiq": "^0.41.0"
|
|
97
97
|
},
|
|
98
98
|
"author": "Suleiman Shahbari",
|
|
99
99
|
"scripts": {
|