@slash-editor/core 0.0.7 → 0.0.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.mts CHANGED
@@ -1,7 +1,9 @@
1
1
  import { Editor, Extension, Extensions, Mark, Node, Range } from "@tiptap/core";
2
2
  import { Node as Node$1, NodeType } from "@tiptap/pm/model";
3
3
  import { EditorState, PluginKey } from "@tiptap/pm/state";
4
+ import { DetailsOptions } from "@tiptap/extension-details";
4
5
  import { SuggestionProps } from "@tiptap/suggestion";
6
+ import { BlockquoteOptions } from "@tiptap/extension-blockquote";
5
7
  import { TableKit, TableKitOptions } from "@tiptap/extension-table";
6
8
  import { Doc } from "yjs";
7
9
  //#region src/slash-items.d.ts
@@ -926,7 +928,7 @@ declare function mention(options: Partial<MentionOptions> & Pick<MentionOptions,
926
928
  * a quote or a callout is a plain `paragraph`, so node type alone cannot tell
927
929
  * those cases apart.
928
930
  */
929
- type PlaceholderKey = "paragraph" | "heading1" | "heading2" | "heading3" | "listItem" | "taskItem" | "blockquote" | "callout" | "details";
931
+ type PlaceholderKey = "paragraph" | "heading1" | "heading2" | "heading3" | "listItem" | "taskItem" | "blockquote" | "callout" | "details" | "toggleHeading1" | "toggleHeading2" | "toggleHeading3";
930
932
  interface PlaceholderContext {
931
933
  editor: Editor;
932
934
  /** The empty block holding the caret. */
@@ -1036,6 +1038,40 @@ declare function slashCommand(options?: Partial<SlashCommandOptions>): Extension
1036
1038
  */
1037
1039
  declare function table(options?: Partial<TableKitOptions>): import("@tiptap/core").Extension<TableKitOptions, any>;
1038
1040
  //#endregion
1041
+ //#region src/toggle.d.ts
1042
+ type ToggleOptions = DetailsOptions;
1043
+ /** Heading level a toggle renders its title at; `0` is a plain toggle list. */
1044
+ type ToggleLevel = 0 | 1 | 2 | 3;
1045
+ declare module "@tiptap/core" {
1046
+ interface Commands<ReturnType> {
1047
+ toggle: {
1048
+ /**
1049
+ * Turns the current block into a toggle whose title is that block's
1050
+ * text, or re-levels the toggle already holding the selection.
1051
+ */
1052
+ setToggle: (level?: ToggleLevel) => ReturnType;
1053
+ /** Sets the heading level of the toggle holding the selection. */
1054
+ setToggleLevel: (level: ToggleLevel) => ReturnType;
1055
+ };
1056
+ }
1057
+ }
1058
+ /** `>` + space: the toggle shorthand. */
1059
+ declare const toggleInputRegex: RegExp;
1060
+ /** `#`…`###` + space inside a toggle title: the toggle-heading shorthand. */
1061
+ declare const toggleHeadingInputRegex: RegExp;
1062
+ /**
1063
+ * Tiptap's `Details` with a `level` attribute, the `>` shorthand, and the
1064
+ * toggle-heading shorthands.
1065
+ *
1066
+ * `level` lives on the container rather than the title because the node view
1067
+ * renders the disclosure button as a *sibling* of the summary: with
1068
+ * `data-level` on the wrapper, one selector sizes both, and the marker stays
1069
+ * centred on a title of any size.
1070
+ */
1071
+ declare const Toggle: import("@tiptap/core").Node<DetailsOptions, any>;
1072
+ /** Configures the toggle (details) node. */
1073
+ declare function toggle(options?: Partial<ToggleOptions>): import("@tiptap/core").Node<DetailsOptions, any>;
1074
+ //#endregion
1039
1075
  //#region src/video.d.ts
1040
1076
  interface VideoOptions {
1041
1077
  HTMLAttributes: Record<string, unknown>;
@@ -1212,6 +1248,14 @@ interface BlockKitOptions {
1212
1248
  * @default {}
1213
1249
  */
1214
1250
  comment?: Partial<CommentOptions> | false;
1251
+ /**
1252
+ * Toggle (details) node configuration. Options only — the node itself is
1253
+ * unconditional, like blockquote. This is the seam a UI layer uses to
1254
+ * render its own disclosure marker via `renderToggleButton`.
1255
+ *
1256
+ * @default { persist: true }
1257
+ */
1258
+ toggle?: Partial<ToggleOptions>;
1215
1259
  }
1216
1260
  /**
1217
1261
  * The baseline block schema: document, text, paragraph, headings, lists,
@@ -1255,4 +1299,20 @@ declare const Callout: Node<CalloutOptions, any>;
1255
1299
  /** Configures the callout node. */
1256
1300
  declare function callout(options?: Partial<CalloutOptions>): Node<CalloutOptions, any>;
1257
1301
  //#endregion
1258
- export { type AiActionStatus, AiBlock, type AiBlockOptions, type AiBlockStorage, type AiKitOptions, type AiRequest, type AiSlashAction, BLOCK_ID_REMOTE_META, BlockDrag, type BlockDragOptions, type BlockDragState, type BlockDragStorage, BlockId, type BlockIdOptions, type BlockKitOptions, type BlockLocation, type BlockRect, type BlockTarget, BubbleToolbar, type BubbleToolbarItem, type BubbleToolbarOptions, type BubbleToolbarState, type BubbleToolbarStorage, Callout, type CalloutOptions, type CollaborationOptions, type CollaborationProvider, type CollaborationUser, Column, type ColumnOptions, Columns, type ColumnsOptions, Comment, type CommentMessage, type CommentOptions, type CommentState, type CommentStorage, type CommentThread, type CommentThreadStore, type DropMode, type DropTarget, Embed, type EmbedMode, type EmbedOptions, File$1 as File, type FileOptions, type FileStorage, type HeadingLevel, Image, type ImageOptions, type ImageStorage, LinkEditor, type LinkEditorOptions, type LinkEditorState, type LinkEditorStorage, Mention, type MentionItem, type MentionOptions, type MentionState, type MentionStorage, PendingAiRegistry, PendingUploadRegistry, Placeholder, type PlaceholderContext, type PlaceholderKey, type PlaceholderOptions, type RetryUploadOptions, type RunAiActionOptions, type RunUploadOptions, type SetEmbedOptions, type SetFileFromFile, type SetFileFromSrc, type SetFileOptions, type SetImageFromFile, type SetImageFromSrc, type SetImageOptions, type SetVideoFromFile, type SetVideoFromSrc, type SetVideoOptions, SlashCommand, type SlashCommandOptions, type SlashCommandStorage, type SlashContext, type SlashItem, type SlashMenuState, type StreamAdapter, type StreamContext, TableKit, type TableKitOptions, type UploadAdapter, type UploadContext, type UploadResult, type UploadStatus, Video, type VideoOptions, type VideoStorage, activeThreadIds, aiBlock, blockDrag, blockDragPluginKey, blockId, blockIdPluginKey, bubbleToolbar, callout, canAppendChild, canOpenLinkEditor, collaboration, column, columns, comment, createAiSlashItems, createBlockKit, defaultAiSlashActions, defaultBubbleToolbarItems, defaultPlaceholderText, defaultSlashItems, embed, file, filterBubbleToolbarItems, filterSlashItems, findNodeById, image, linkEditor, mention, mentionPluginKey, placeholder, placeholderKeyFor, placeholderPluginKey, resolveDropTarget, retryUpload, runUpload, slashCommand, slashCommandPluginKey, table, video };
1302
+ //#region src/quote.d.ts
1303
+ type QuoteOptions = BlockquoteOptions;
1304
+ /** `"` + space at the start of a block, Notion style. */
1305
+ declare const quoteInputRegex: RegExp;
1306
+ /**
1307
+ * Blockquote with its markdown shorthand moved from `>` to `"`. The node name
1308
+ * stays `blockquote`: only the input rule changes, so stored documents,
1309
+ * clipboard HTML, and `Mod+Shift+B` are untouched.
1310
+ *
1311
+ * `>` belongs to the toggle (see `toggle.ts`), which is what Notion users
1312
+ * reach for it expecting.
1313
+ */
1314
+ declare const Quote: import("@tiptap/core").Node<BlockquoteOptions, any>;
1315
+ /** Configures the quote (blockquote) node. */
1316
+ declare function quote(options?: Partial<QuoteOptions>): import("@tiptap/core").Node<BlockquoteOptions, any>;
1317
+ //#endregion
1318
+ export { type AiActionStatus, AiBlock, type AiBlockOptions, type AiBlockStorage, type AiKitOptions, type AiRequest, type AiSlashAction, BLOCK_ID_REMOTE_META, BlockDrag, type BlockDragOptions, type BlockDragState, type BlockDragStorage, BlockId, type BlockIdOptions, type BlockKitOptions, type BlockLocation, type BlockRect, type BlockTarget, BubbleToolbar, type BubbleToolbarItem, type BubbleToolbarOptions, type BubbleToolbarState, type BubbleToolbarStorage, Callout, type CalloutOptions, type CollaborationOptions, type CollaborationProvider, type CollaborationUser, Column, type ColumnOptions, Columns, type ColumnsOptions, Comment, type CommentMessage, type CommentOptions, type CommentState, type CommentStorage, type CommentThread, type CommentThreadStore, type DropMode, type DropTarget, Embed, type EmbedMode, type EmbedOptions, File$1 as File, type FileOptions, type FileStorage, type HeadingLevel, Image, type ImageOptions, type ImageStorage, LinkEditor, type LinkEditorOptions, type LinkEditorState, type LinkEditorStorage, Mention, type MentionItem, type MentionOptions, type MentionState, type MentionStorage, PendingAiRegistry, PendingUploadRegistry, Placeholder, type PlaceholderContext, type PlaceholderKey, type PlaceholderOptions, Quote, type QuoteOptions, type RetryUploadOptions, type RunAiActionOptions, type RunUploadOptions, type SetEmbedOptions, type SetFileFromFile, type SetFileFromSrc, type SetFileOptions, type SetImageFromFile, type SetImageFromSrc, type SetImageOptions, type SetVideoFromFile, type SetVideoFromSrc, type SetVideoOptions, SlashCommand, type SlashCommandOptions, type SlashCommandStorage, type SlashContext, type SlashItem, type SlashMenuState, type StreamAdapter, type StreamContext, TableKit, type TableKitOptions, Toggle, type ToggleLevel, type ToggleOptions, type UploadAdapter, type UploadContext, type UploadResult, type UploadStatus, Video, type VideoOptions, type VideoStorage, activeThreadIds, aiBlock, blockDrag, blockDragPluginKey, blockId, blockIdPluginKey, bubbleToolbar, callout, canAppendChild, canOpenLinkEditor, collaboration, column, columns, comment, createAiSlashItems, createBlockKit, defaultAiSlashActions, defaultBubbleToolbarItems, defaultPlaceholderText, defaultSlashItems, embed, file, filterBubbleToolbarItems, filterSlashItems, findNodeById, image, linkEditor, mention, mentionPluginKey, placeholder, placeholderKeyFor, placeholderPluginKey, quote, quoteInputRegex, resolveDropTarget, retryUpload, runUpload, slashCommand, slashCommandPluginKey, table, toggle, toggleHeadingInputRegex, toggleInputRegex, video };
package/dist/index.mjs CHANGED
@@ -1,6 +1,6 @@
1
- import { Extension, Mark, Node, callOrReturn, combineTransactionSteps, findChildrenInRange, getChangedRanges, getExtensionField, isTextSelection, mergeAttributes, posToDOMRect } from "@tiptap/core";
1
+ import { Extension, InputRule, Mark, Node, callOrReturn, combineTransactionSteps, findChildrenInRange, findParentNode, getChangedRanges, getExtensionField, isTextSelection, mergeAttributes, posToDOMRect, wrappingInputRule } from "@tiptap/core";
2
2
  import { Fragment } from "@tiptap/pm/model";
3
- import { Plugin, PluginKey } from "@tiptap/pm/state";
3
+ import { Plugin, PluginKey, TextSelection } from "@tiptap/pm/state";
4
4
  import { Decoration, DecorationSet } from "@tiptap/pm/view";
5
5
  import { StarterKit } from "@tiptap/starter-kit";
6
6
  import { Details, DetailsContent, DetailsSummary } from "@tiptap/extension-details";
@@ -8,6 +8,7 @@ import { TaskItem, TaskList } from "@tiptap/extension-list";
8
8
  import { Collaboration } from "@tiptap/extension-collaboration";
9
9
  import { CollaborationCaret } from "@tiptap/extension-collaboration-caret";
10
10
  import { Suggestion } from "@tiptap/suggestion";
11
+ import { Blockquote } from "@tiptap/extension-blockquote";
11
12
  import { TableKit } from "@tiptap/extension-table";
12
13
  //#region src/upload.ts
13
14
  /**
@@ -1914,7 +1915,10 @@ const defaultPlaceholderText = {
1914
1915
  taskItem: "To-do",
1915
1916
  blockquote: "Quote",
1916
1917
  callout: "Callout",
1917
- details: "Toggle"
1918
+ details: "Toggle",
1919
+ toggleHeading1: "Toggle heading 1",
1920
+ toggleHeading2: "Toggle heading 2",
1921
+ toggleHeading3: "Toggle heading 3"
1918
1922
  };
1919
1923
  const PARENT_KEYS = {
1920
1924
  listItem: "listItem",
@@ -1934,7 +1938,10 @@ function placeholderKeyFor(node, parent) {
1934
1938
  const { level } = node.attrs;
1935
1939
  return level === 1 || level === 2 || level === 3 ? `heading${level}` : null;
1936
1940
  }
1937
- if (name === "detailsSummary") return "details";
1941
+ if (name === "detailsSummary") {
1942
+ const level = parent?.attrs.level;
1943
+ return level === 1 || level === 2 || level === 3 ? `toggleHeading${level}` : "details";
1944
+ }
1938
1945
  if (name !== "paragraph") return null;
1939
1946
  return parent ? PARENT_KEYS[parent.type.name] ?? "paragraph" : "paragraph";
1940
1947
  }
@@ -1990,6 +1997,28 @@ function placeholder(options = {}) {
1990
1997
  return Placeholder.configure(options);
1991
1998
  }
1992
1999
  //#endregion
2000
+ //#region src/quote.ts
2001
+ /** `"` + space at the start of a block, Notion style. */
2002
+ const quoteInputRegex = /^\s*"\s$/;
2003
+ /**
2004
+ * Blockquote with its markdown shorthand moved from `>` to `"`. The node name
2005
+ * stays `blockquote`: only the input rule changes, so stored documents,
2006
+ * clipboard HTML, and `Mod+Shift+B` are untouched.
2007
+ *
2008
+ * `>` belongs to the toggle (see `toggle.ts`), which is what Notion users
2009
+ * reach for it expecting.
2010
+ */
2011
+ const Quote = Blockquote.extend({ addInputRules() {
2012
+ return [wrappingInputRule({
2013
+ find: quoteInputRegex,
2014
+ type: this.type
2015
+ })];
2016
+ } });
2017
+ /** Configures the quote (blockquote) node. */
2018
+ function quote(options = {}) {
2019
+ return Quote.configure(options);
2020
+ }
2021
+ //#endregion
1993
2022
  //#region src/slash-items.ts
1994
2023
  const TITLE_PREFIX = 100;
1995
2024
  const TITLE_WORD_PREFIX = 80;
@@ -2019,6 +2048,7 @@ function filterSlashItems(items, query, editor) {
2019
2048
  })).filter((entry) => entry.score > 0).sort((a, b) => b.score - a.score).map((entry) => entry.item);
2020
2049
  }
2021
2050
  const BASIC = "Basic blocks";
2051
+ const ADVANCED = "Advanced blocks";
2022
2052
  const MEDIA = "Media";
2023
2053
  const STRUCTURE = "Structure";
2024
2054
  function hasNode(editor, name) {
@@ -2132,8 +2162,8 @@ const defaultSlashItems = [
2132
2162
  group: BASIC,
2133
2163
  description: "Capture a quotation",
2134
2164
  aliases: ["quote", "citation"],
2135
- keywords: [">"],
2136
- shortcut: ">",
2165
+ keywords: ["\""],
2166
+ shortcut: "\"",
2137
2167
  icon: "quote",
2138
2168
  when: (editor) => hasNode(editor, "blockquote"),
2139
2169
  run: ({ editor, range }) => {
@@ -2168,6 +2198,7 @@ const defaultSlashItems = [
2168
2198
  "dropdown"
2169
2199
  ],
2170
2200
  keywords: ["toggle"],
2201
+ shortcut: ">",
2171
2202
  icon: "chevron-right",
2172
2203
  when: (editor) => hasNode(editor, "details"),
2173
2204
  run: ({ editor, range }) => {
@@ -2206,6 +2237,48 @@ const defaultSlashItems = [
2206
2237
  editor.chain().focus().deleteRange(range).setHorizontalRule().run();
2207
2238
  }
2208
2239
  },
2240
+ {
2241
+ id: "toggle-heading-1",
2242
+ title: "Toggle heading 1",
2243
+ group: ADVANCED,
2244
+ description: "Collapsible large title",
2245
+ aliases: ["th1", "toggleheading1"],
2246
+ keywords: ["collapsible", "details"],
2247
+ shortcut: "# >",
2248
+ icon: "heading-1",
2249
+ when: (editor) => hasNode(editor, "details"),
2250
+ run: ({ editor, range }) => {
2251
+ editor.chain().focus().deleteRange(range).setToggle(1).run();
2252
+ }
2253
+ },
2254
+ {
2255
+ id: "toggle-heading-2",
2256
+ title: "Toggle heading 2",
2257
+ group: ADVANCED,
2258
+ description: "Collapsible medium title",
2259
+ aliases: ["th2", "toggleheading2"],
2260
+ keywords: ["collapsible", "details"],
2261
+ shortcut: "## >",
2262
+ icon: "heading-2",
2263
+ when: (editor) => hasNode(editor, "details"),
2264
+ run: ({ editor, range }) => {
2265
+ editor.chain().focus().deleteRange(range).setToggle(2).run();
2266
+ }
2267
+ },
2268
+ {
2269
+ id: "toggle-heading-3",
2270
+ title: "Toggle heading 3",
2271
+ group: ADVANCED,
2272
+ description: "Collapsible small title",
2273
+ aliases: ["th3", "toggleheading3"],
2274
+ keywords: ["collapsible", "details"],
2275
+ shortcut: "### >",
2276
+ icon: "heading-3",
2277
+ when: (editor) => hasNode(editor, "details"),
2278
+ run: ({ editor, range }) => {
2279
+ editor.chain().focus().deleteRange(range).setToggle(3).run();
2280
+ }
2281
+ },
2209
2282
  {
2210
2283
  id: "image",
2211
2284
  title: "Image",
@@ -2445,6 +2518,182 @@ function table(options = {}) {
2445
2518
  });
2446
2519
  }
2447
2520
  //#endregion
2521
+ //#region src/toggle.ts
2522
+ /** `>` + space: the toggle shorthand. */
2523
+ const toggleInputRegex = /^>\s$/;
2524
+ /** `#`…`###` + space inside a toggle title: the toggle-heading shorthand. */
2525
+ const toggleHeadingInputRegex = /^(#{1,3})\s$/;
2526
+ function asToggleLevel(value) {
2527
+ return value === 1 || value === 2 || value === 3 ? value : 0;
2528
+ }
2529
+ function canReplaceBlockWithToggle($pos, detailsType) {
2530
+ if ($pos.depth === 0 || !$pos.parent.isTextblock) return false;
2531
+ const index = $pos.index($pos.depth - 1);
2532
+ return $pos.node($pos.depth - 1).canReplaceWith(index, index + 1, detailsType);
2533
+ }
2534
+ /**
2535
+ * Replaces the textblock holding `pos` with a toggle: that block's inline
2536
+ * content becomes the title, the body starts as one empty paragraph, and the
2537
+ * caret lands at the end of the title.
2538
+ *
2539
+ * Titles are `text*`, so inline nodes a summary cannot hold (a mention chip)
2540
+ * degrade to their text rather than failing the whole conversion.
2541
+ */
2542
+ function replaceBlockWithToggle(tr, pos, level) {
2543
+ const $pos = tr.doc.resolve(pos);
2544
+ const { schema } = $pos.parent.type;
2545
+ const detailsType = schema.nodes.details;
2546
+ const summaryType = schema.nodes.detailsSummary;
2547
+ const contentType = schema.nodes.detailsContent;
2548
+ const paragraphType = schema.nodes.paragraph;
2549
+ if (!detailsType || !summaryType || !contentType || !paragraphType) return false;
2550
+ if (!canReplaceBlockWithToggle($pos, detailsType)) return false;
2551
+ const inline = $pos.parent.content;
2552
+ const text = inline.textBetween(0, inline.size);
2553
+ const title = summaryType.contentMatch.matchFragment(inline)?.validEnd ? inline : text ? Fragment.from(schema.text(text)) : Fragment.empty;
2554
+ const summary = summaryType.create(null, title);
2555
+ const body = contentType.create(null, paragraphType.create());
2556
+ const from = $pos.before($pos.depth);
2557
+ tr.replaceWith(from, $pos.after($pos.depth), detailsType.create({ level }, [summary, body]));
2558
+ tr.setSelection(TextSelection.create(tr.doc, from + 2 + summary.content.size));
2559
+ return true;
2560
+ }
2561
+ /**
2562
+ * Tiptap's `Details` with a `level` attribute, the `>` shorthand, and the
2563
+ * toggle-heading shorthands.
2564
+ *
2565
+ * `level` lives on the container rather than the title because the node view
2566
+ * renders the disclosure button as a *sibling* of the summary: with
2567
+ * `data-level` on the wrapper, one selector sizes both, and the marker stays
2568
+ * centred on a title of any size.
2569
+ */
2570
+ const Toggle = Details.extend({
2571
+ addAttributes() {
2572
+ return {
2573
+ ...this.parent?.(),
2574
+ level: {
2575
+ default: 0,
2576
+ parseHTML: (element) => asToggleLevel(Number(element.getAttribute("data-level"))),
2577
+ renderHTML: ({ level }) => level ? { "data-level": String(level) } : {}
2578
+ }
2579
+ };
2580
+ },
2581
+ renderHTML({ HTMLAttributes }) {
2582
+ return [
2583
+ "details",
2584
+ mergeAttributes(this.options.HTMLAttributes, HTMLAttributes),
2585
+ 0
2586
+ ];
2587
+ },
2588
+ addNodeView() {
2589
+ const parent = this.parent?.();
2590
+ if (!parent) return null;
2591
+ return (props) => {
2592
+ const view = parent(props);
2593
+ const dom = view.dom;
2594
+ const applyLevel = (node) => {
2595
+ const level = asToggleLevel(node.attrs.level);
2596
+ if (level) dom.setAttribute("data-level", String(level));
2597
+ else dom.removeAttribute("data-level");
2598
+ };
2599
+ applyLevel(props.node);
2600
+ if (this.options.persist) dom.addEventListener("click", (event) => {
2601
+ const { editor, getPos } = props;
2602
+ const target = event.target;
2603
+ if (!editor.isEditable || target?.closest("button")?.parentElement !== dom) return;
2604
+ event.stopPropagation();
2605
+ const pos = getPos();
2606
+ const node = typeof pos === "number" ? editor.state.doc.nodeAt(pos) : null;
2607
+ if (!node || node.type !== this.type) return;
2608
+ const { from, to } = editor.state.selection;
2609
+ editor.chain().command(({ tr }) => {
2610
+ tr.setNodeMarkup(pos, void 0, {
2611
+ ...node.attrs,
2612
+ open: !node.attrs.open
2613
+ });
2614
+ return true;
2615
+ }).setTextSelection({
2616
+ from,
2617
+ to
2618
+ }).focus(void 0, { scrollIntoView: false }).run();
2619
+ }, true);
2620
+ return {
2621
+ ...view,
2622
+ update: (node, decorations, innerDecorations) => {
2623
+ const updated = view.update?.(node, decorations, innerDecorations) ?? true;
2624
+ if (updated) applyLevel(node);
2625
+ return updated;
2626
+ }
2627
+ };
2628
+ };
2629
+ },
2630
+ addCommands() {
2631
+ return {
2632
+ ...this.parent?.(),
2633
+ setToggle: (level = 0) => ({ state, tr, dispatch }) => {
2634
+ const detailsType = state.schema.nodes.details;
2635
+ if (!detailsType) return false;
2636
+ const details = findParentNode((node) => node.type === detailsType)(state.selection);
2637
+ if (details) {
2638
+ if (dispatch) tr.setNodeMarkup(details.pos, void 0, {
2639
+ ...details.node.attrs,
2640
+ level
2641
+ });
2642
+ return true;
2643
+ }
2644
+ if (!canReplaceBlockWithToggle(state.selection.$from, detailsType)) return false;
2645
+ return dispatch ? replaceBlockWithToggle(tr, state.selection.from, level) : true;
2646
+ },
2647
+ setToggleLevel: (level) => ({ state, tr, dispatch }) => {
2648
+ const detailsType = state.schema.nodes.details;
2649
+ const details = detailsType ? findParentNode((node) => node.type === detailsType)(state.selection) : void 0;
2650
+ if (!details) return false;
2651
+ if (dispatch) tr.setNodeMarkup(details.pos, void 0, {
2652
+ ...details.node.attrs,
2653
+ level
2654
+ });
2655
+ return true;
2656
+ }
2657
+ };
2658
+ },
2659
+ addInputRules() {
2660
+ return [
2661
+ ...this.parent?.() ?? [],
2662
+ new InputRule({
2663
+ find: toggleInputRegex,
2664
+ handler: ({ state, range }) => {
2665
+ const { tr } = state;
2666
+ const block = tr.doc.resolve(range.from).parent;
2667
+ const name = block.type.name;
2668
+ if (name !== "paragraph" && name !== "heading") return null;
2669
+ const level = name === "heading" ? asToggleLevel(block.attrs.level) : 0;
2670
+ tr.delete(range.from, range.to);
2671
+ return replaceBlockWithToggle(tr, range.from, level) ? void 0 : null;
2672
+ }
2673
+ }),
2674
+ new InputRule({
2675
+ find: toggleHeadingInputRegex,
2676
+ handler: ({ state, range, match }) => {
2677
+ const { tr } = state;
2678
+ const $from = tr.doc.resolve(range.from);
2679
+ if ($from.parent.type.name !== "detailsSummary") return null;
2680
+ const details = $from.node($from.depth - 1);
2681
+ const level = asToggleLevel(match[1]?.length);
2682
+ tr.delete(range.from, range.to);
2683
+ tr.setNodeMarkup($from.before($from.depth - 1), void 0, {
2684
+ ...details.attrs,
2685
+ level
2686
+ });
2687
+ }
2688
+ })
2689
+ ];
2690
+ }
2691
+ });
2692
+ /** Configures the toggle (details) node. */
2693
+ function toggle(options = {}) {
2694
+ return Toggle.configure(options);
2695
+ }
2696
+ //#endregion
2448
2697
  //#region src/video.ts
2449
2698
  function initialAttrs(id, options) {
2450
2699
  if (options && "file" in options) return {
@@ -2564,7 +2813,7 @@ const DEFAULT_HEADING_LEVELS = [
2564
2813
  * the `data-*` attributes rendered by slash-editor nodes.
2565
2814
  */
2566
2815
  function createBlockKit(options = {}) {
2567
- const { headingLevels = DEFAULT_HEADING_LEVELS, history = true, slash, placeholder: placeholderOptions, blockId: blockIdOptions, drag, bubbleToolbar: bubbleToolbarOptions, image: imageOptions, file: fileOptions, video: videoOptions, embed: embedOptions, table: tableOptions, columns: columnsOptions, linkEditor: linkEditorOptions, mention: mentionOptions, ai: aiOptions, collaboration: collaborationOptions, comment: commentOptions, extend = [] } = options;
2816
+ const { headingLevels = DEFAULT_HEADING_LEVELS, history = true, slash, placeholder: placeholderOptions, blockId: blockIdOptions, drag, bubbleToolbar: bubbleToolbarOptions, image: imageOptions, file: fileOptions, video: videoOptions, embed: embedOptions, table: tableOptions, columns: columnsOptions, linkEditor: linkEditorOptions, mention: mentionOptions, ai: aiOptions, collaboration: collaborationOptions, comment: commentOptions, toggle: toggleOptions, extend = [] } = options;
2568
2817
  const resolvedAi = aiOptions ? {
2569
2818
  actions: defaultAiSlashActions,
2570
2819
  node: true,
@@ -2578,12 +2827,17 @@ function createBlockKit(options = {}) {
2578
2827
  link: {
2579
2828
  openOnClick: false,
2580
2829
  enableClickSelection: true
2581
- }
2830
+ },
2831
+ blockquote: false
2582
2832
  }),
2833
+ quote(),
2583
2834
  TaskList,
2584
2835
  TaskItem.configure({ nested: true }),
2585
2836
  Callout,
2586
- Details.configure({ persist: true }),
2837
+ toggle({
2838
+ persist: true,
2839
+ ...toggleOptions
2840
+ }),
2587
2841
  DetailsSummary,
2588
2842
  DetailsContent,
2589
2843
  ...imageOptions === false ? [] : [image(imageOptions)],
@@ -2609,4 +2863,4 @@ function createBlockKit(options = {}) {
2609
2863
  ];
2610
2864
  }
2611
2865
  //#endregion
2612
- export { AiBlock, BLOCK_ID_REMOTE_META, BlockDrag, BlockId, BubbleToolbar, Callout, Column, Columns, Comment, Embed, File, Image, LinkEditor, Mention, PendingAiRegistry, PendingUploadRegistry, Placeholder, SlashCommand, TableKit, Video, activeThreadIds, aiBlock, blockDrag, blockDragPluginKey, blockId, blockIdPluginKey, bubbleToolbar, callout, canAppendChild, canOpenLinkEditor, collaboration, column, columns, comment, createAiSlashItems, createBlockKit, defaultAiSlashActions, defaultBubbleToolbarItems, defaultPlaceholderText, defaultSlashItems, embed, file, filterBubbleToolbarItems, filterSlashItems, findNodeById, image, linkEditor, mention, mentionPluginKey, placeholder, placeholderKeyFor, placeholderPluginKey, resolveDropTarget, retryUpload, runUpload, slashCommand, slashCommandPluginKey, table, video };
2866
+ export { AiBlock, BLOCK_ID_REMOTE_META, BlockDrag, BlockId, BubbleToolbar, Callout, Column, Columns, Comment, Embed, File, Image, LinkEditor, Mention, PendingAiRegistry, PendingUploadRegistry, Placeholder, Quote, SlashCommand, TableKit, Toggle, Video, activeThreadIds, aiBlock, blockDrag, blockDragPluginKey, blockId, blockIdPluginKey, bubbleToolbar, callout, canAppendChild, canOpenLinkEditor, collaboration, column, columns, comment, createAiSlashItems, createBlockKit, defaultAiSlashActions, defaultBubbleToolbarItems, defaultPlaceholderText, defaultSlashItems, embed, file, filterBubbleToolbarItems, filterSlashItems, findNodeById, image, linkEditor, mention, mentionPluginKey, placeholder, placeholderKeyFor, placeholderPluginKey, quote, quoteInputRegex, resolveDropTarget, retryUpload, runUpload, slashCommand, slashCommandPluginKey, table, toggle, toggleHeadingInputRegex, toggleInputRegex, video };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@slash-editor/core",
3
- "version": "0.0.7",
3
+ "version": "0.0.8",
4
4
  "description": "Headless block-editor core: ProseMirror/Tiptap extensions, schema, commands.",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -24,6 +24,7 @@
24
24
  "dev": "vp pack --watch"
25
25
  },
26
26
  "dependencies": {
27
+ "@tiptap/extension-blockquote": "^3.31.3",
27
28
  "@tiptap/extension-collaboration": "^3.31.3",
28
29
  "@tiptap/extension-collaboration-caret": "^3.31.3",
29
30
  "@tiptap/extension-details": "^3.31.3",