@prosekit/extensions 0.11.6 → 0.12.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.
Files changed (98) hide show
  1. package/dist/{drop-indicator-E7nCfdnR.js → drop-indicator-BMvWUDXz.js} +2 -2
  2. package/dist/{drop-indicator-E7nCfdnR.js.map → drop-indicator-BMvWUDXz.js.map} +1 -1
  3. package/dist/{enter-rule-RdhEA900.js → enter-rule-D-p4ykfv.js} +3 -4
  4. package/dist/enter-rule-D-p4ykfv.js.map +1 -0
  5. package/dist/{file-DVUhe5KJ.js → file-DKoIIa7q.js} +3 -5
  6. package/dist/{file-DVUhe5KJ.js.map → file-DKoIIa7q.js.map} +1 -1
  7. package/dist/{index-DY6lIIYV.d.ts → index-DdjnBeho.d.ts} +2 -2
  8. package/dist/index-DdjnBeho.d.ts.map +1 -0
  9. package/dist/{input-rule-B17tpW4m.js → input-rule-COGr_GBb.js} +5 -8
  10. package/dist/input-rule-COGr_GBb.js.map +1 -0
  11. package/dist/{mark-rule-CGmswjQ_.js → mark-rule-v2E7B4C0.js} +2 -2
  12. package/dist/{mark-rule-CGmswjQ_.js.map → mark-rule-v2E7B4C0.js.map} +1 -1
  13. package/dist/{paste-rule-BIztzELg.js → paste-rule-qSz46pqD.js} +3 -4
  14. package/dist/{paste-rule-BIztzELg.js.map → paste-rule-qSz46pqD.js.map} +1 -1
  15. package/dist/prosekit-extensions-autocomplete.d.ts.map +1 -1
  16. package/dist/prosekit-extensions-autocomplete.js +1 -2
  17. package/dist/prosekit-extensions-autocomplete.js.map +1 -1
  18. package/dist/prosekit-extensions-blockquote.d.ts.map +1 -1
  19. package/dist/prosekit-extensions-blockquote.js +1 -1
  20. package/dist/prosekit-extensions-bold.d.ts.map +1 -1
  21. package/dist/prosekit-extensions-bold.js +1 -1
  22. package/dist/prosekit-extensions-code-block.d.ts +1 -1
  23. package/dist/prosekit-extensions-code-block.d.ts.map +1 -1
  24. package/dist/prosekit-extensions-code-block.js +4 -5
  25. package/dist/prosekit-extensions-code-block.js.map +1 -1
  26. package/dist/prosekit-extensions-code.d.ts.map +1 -1
  27. package/dist/prosekit-extensions-code.js +1 -1
  28. package/dist/prosekit-extensions-commit.d.ts.map +1 -1
  29. package/dist/prosekit-extensions-commit.js +2 -4
  30. package/dist/prosekit-extensions-commit.js.map +1 -1
  31. package/dist/prosekit-extensions-doc.d.ts.map +1 -1
  32. package/dist/prosekit-extensions-drop-cursor.d.ts.map +1 -1
  33. package/dist/prosekit-extensions-drop-indicator.d.ts.map +1 -1
  34. package/dist/prosekit-extensions-drop-indicator.js +1 -1
  35. package/dist/prosekit-extensions-enter-rule.d.ts.map +1 -1
  36. package/dist/prosekit-extensions-enter-rule.js +1 -1
  37. package/dist/prosekit-extensions-file.d.ts +1 -1
  38. package/dist/prosekit-extensions-file.js +1 -1
  39. package/dist/prosekit-extensions-hard-break.d.ts.map +1 -1
  40. package/dist/prosekit-extensions-heading.d.ts.map +1 -1
  41. package/dist/prosekit-extensions-heading.js +1 -1
  42. package/dist/prosekit-extensions-horizontal-rule.d.ts.map +1 -1
  43. package/dist/prosekit-extensions-horizontal-rule.js +1 -1
  44. package/dist/prosekit-extensions-image.d.ts +86 -30
  45. package/dist/prosekit-extensions-image.d.ts.map +1 -1
  46. package/dist/prosekit-extensions-image.js +93 -52
  47. package/dist/prosekit-extensions-image.js.map +1 -1
  48. package/dist/prosekit-extensions-input-rule.d.ts.map +1 -1
  49. package/dist/prosekit-extensions-input-rule.js +1 -1
  50. package/dist/prosekit-extensions-italic.d.ts.map +1 -1
  51. package/dist/prosekit-extensions-italic.js +1 -1
  52. package/dist/prosekit-extensions-link.d.ts.map +1 -1
  53. package/dist/prosekit-extensions-link.js +4 -4
  54. package/dist/prosekit-extensions-list.d.ts.map +1 -1
  55. package/dist/prosekit-extensions-list.js +37 -10
  56. package/dist/prosekit-extensions-list.js.map +1 -1
  57. package/dist/prosekit-extensions-loro.d.ts.map +1 -1
  58. package/dist/prosekit-extensions-mark-rule.d.ts.map +1 -1
  59. package/dist/prosekit-extensions-mark-rule.js +1 -1
  60. package/dist/prosekit-extensions-mention.d.ts.map +1 -1
  61. package/dist/prosekit-extensions-paragraph.d.ts.map +1 -1
  62. package/dist/prosekit-extensions-paste-rule.d.ts.map +1 -1
  63. package/dist/prosekit-extensions-paste-rule.js +1 -1
  64. package/dist/prosekit-extensions-placeholder.d.ts.map +1 -1
  65. package/dist/prosekit-extensions-placeholder.js +3 -4
  66. package/dist/prosekit-extensions-placeholder.js.map +1 -1
  67. package/dist/prosekit-extensions-search.d.ts.map +1 -1
  68. package/dist/prosekit-extensions-search.js +1 -2
  69. package/dist/prosekit-extensions-search.js.map +1 -1
  70. package/dist/prosekit-extensions-strike.d.ts.map +1 -1
  71. package/dist/prosekit-extensions-strike.js +1 -1
  72. package/dist/prosekit-extensions-table.d.ts.map +1 -1
  73. package/dist/prosekit-extensions-table.js +2 -2
  74. package/dist/prosekit-extensions-text-align.d.ts.map +1 -1
  75. package/dist/prosekit-extensions-text.d.ts.map +1 -1
  76. package/dist/prosekit-extensions-underline.d.ts.map +1 -1
  77. package/dist/prosekit-extensions-yjs.d.ts.map +1 -1
  78. package/dist/{shiki-highlighter-chunk-Cwu1Jr9o.d.ts → shiki-highlighter-chunk-DcY3Ud8v.d.ts} +2 -2
  79. package/dist/shiki-highlighter-chunk-DcY3Ud8v.d.ts.map +1 -0
  80. package/dist/shiki-highlighter-chunk.d.ts +1 -1
  81. package/dist/{table-BNwuK7xg.js → table-BLjD91VB.js} +11 -20
  82. package/dist/{table-BNwuK7xg.js.map → table-BLjD91VB.js.map} +1 -1
  83. package/package.json +14 -14
  84. package/src/file/file-upload.ts +2 -10
  85. package/src/image/image-commands/insert-image.ts +14 -0
  86. package/src/image/image-commands/upload-image.ts +137 -0
  87. package/src/image/image-commands.ts +7 -12
  88. package/src/image/image-upload-handler.ts +10 -70
  89. package/src/image/index.ts +8 -3
  90. package/src/link/index.spec.ts +1 -1
  91. package/src/list/list-serializer.ts +57 -3
  92. package/src/list/list.spec.ts +129 -0
  93. package/src/testing/keyboard.ts +1 -1
  94. package/src/testing/markdown.ts +3 -0
  95. package/dist/enter-rule-RdhEA900.js.map +0 -1
  96. package/dist/index-DY6lIIYV.d.ts.map +0 -1
  97. package/dist/input-rule-B17tpW4m.js.map +0 -1
  98. package/dist/shiki-highlighter-chunk-Cwu1Jr9o.d.ts.map +0 -1
@@ -1 +1 @@
1
- {"version":3,"file":"table-BNwuK7xg.js","names":["exitTable: Command","onDrag: DragEventHandler","match: boolean"],"sources":["../src/table/table-commands/exit-table.ts","../src/table/table-commands/insert-table.ts","../src/table/table-utils.ts","../src/table/table-commands/select-table.ts","../src/table/table-commands/select-table-cell.ts","../src/table/table-commands/select-table-column.ts","../src/table/table-commands/select-table-row.ts","../src/table/table-commands.ts","../src/table/table-drop-indicator.ts","../src/table/table-plugins.ts","../src/table/table-spec.ts","../src/table/table.ts"],"sourcesContent":["import { defaultBlockAt } from '@prosekit/core'\nimport { TextSelection } from '@prosekit/pm/state'\nimport type { Command } from '@prosekit/pm/state'\nimport type { TableRole } from 'prosemirror-tables'\n\n/**\n * When the selection is in a table node, create a default block after the table\n * table, and move the cursor there.\n *\n * @public\n */\nexport const exitTable: Command = (state, dispatch) => {\n const { $head, $anchor } = state.selection\n\n if (!$head.sameParent($anchor)) {\n return false\n }\n\n let tableStart = -1\n let tableDepth = -1\n for (let depth = $head.depth; depth >= 0; depth--) {\n const node = $head.node(depth)\n if ((node.type.spec.tableRole as TableRole) === 'table') {\n tableStart = $head.before(depth)\n tableDepth = depth\n }\n }\n\n if (tableStart < 0 || tableDepth <= 0) {\n return false\n }\n\n const above = $head.node(tableDepth - 1)\n const after = $head.indexAfter(tableDepth - 1)\n const type = defaultBlockAt(above.contentMatchAt(after))\n const node = type?.createAndFill()\n\n if (!type || !node || !above.canReplaceWith(after, after, type)) {\n return false\n }\n\n if (dispatch) {\n const pos = $head.after(tableDepth)\n const tr = state.tr.replaceWith(pos, pos, node)\n tr.setSelection(TextSelection.near(tr.doc.resolve(pos), 1))\n dispatch(tr.scrollIntoView())\n }\n return true\n}\n","import {\n getNodeType,\n insertNode,\n} from '@prosekit/core'\nimport type { Schema } from '@prosekit/pm/model'\nimport type { Command } from '@prosekit/pm/state'\n\nfunction createEmptyTable(\n schema: Schema,\n row: number,\n col: number,\n header: boolean,\n) {\n const tableType = getNodeType(schema, 'table')\n const tableRowType = getNodeType(schema, 'tableRow')\n const tableCellType = getNodeType(schema, 'tableCell')\n const tableHeaderCellType = getNodeType(schema, 'tableHeaderCell')\n\n if (header) {\n const headerCell = tableHeaderCellType.createAndFill()!\n const headerCells = repeat(headerCell, col)\n const headerRow = tableRowType.createAndFill(null, headerCells)!\n\n const bodyCell = tableCellType.createAndFill()!\n const bodyCells = repeat(bodyCell, col)\n const bodyRow = tableRowType.createAndFill(null, bodyCells)!\n const bodyRows = repeat(bodyRow, row - 1)\n\n return tableType.createAndFill(null, [headerRow, ...bodyRows])!\n } else {\n const bodyCell = tableCellType.createAndFill()!\n const bodyCells = repeat(bodyCell, col)\n const bodyRow = tableRowType.createAndFill(null, bodyCells)!\n const bodyRows = repeat(bodyRow, row)\n\n return tableType.createAndFill(null, bodyRows)!\n }\n}\n\nfunction repeat<T>(node: T, length: number): T[] {\n return Array<T>(length).fill(node)\n}\n\n/**\n * @public\n */\nexport interface InsertTableOptions {\n /**\n * The number of rows in the table.\n */\n row: number\n\n /**\n * The number of columns in the table.\n */\n col: number\n\n /**\n * Whether the table has a header row.\n *\n * @default false\n */\n header?: boolean\n}\n\n/**\n * Insert a table node with the given number of rows and columns, and optionally\n * a header row.\n *\n * @param options\n *\n * @public\n */\nexport function insertTable(options: InsertTableOptions): Command {\n return (state, dispatch, view) => {\n const { row, col, header = false } = options\n const table = createEmptyTable(state.schema, row, col, header)\n return insertNode({ node: table })(state, dispatch, view)\n }\n}\n","import { CellSelection } from 'prosemirror-tables'\n\nexport {\n findCellPos,\n findCellRange,\n findTable,\n} from 'prosemirror-tables'\n\n/**\n * Checks if the given object is a `CellSelection` instance.\n *\n * @public\n */\nexport function isCellSelection(value: unknown): value is CellSelection {\n return value instanceof CellSelection\n}\n","import type { Command } from '@prosekit/pm/state'\nimport {\n CellSelection,\n TableMap,\n} from 'prosemirror-tables'\n\nimport { findTable } from '../table-utils'\n\n/**\n * @public\n */\nexport interface SelectTableOptions {\n /**\n * A hit position of the table to select from. By default, the selection\n * anchor will be used.\n */\n pos?: number\n}\n\n/**\n * @public\n */\nexport function selectTable(options?: SelectTableOptions): Command {\n return (state, dispatch) => {\n const $pos = options?.pos\n ? state.doc.resolve(options.pos)\n : state.selection.$anchor\n const table = findTable($pos)\n if (!table) {\n return false\n }\n const map = TableMap.get(table.node)\n if (map.map.length === 0) {\n return false\n }\n if (dispatch) {\n let tr = state.tr\n const firstCellPosInTable = map.map[0]\n const lastCellPosInTable = map.map[map.map.length - 1]\n const firstCellPos = table.pos + firstCellPosInTable + 1\n const lastCellPos = table.pos + lastCellPosInTable + 1\n const $firstCellPos = tr.doc.resolve(firstCellPos)\n const $lastCellPos = tr.doc.resolve(lastCellPos)\n const selection = new CellSelection($firstCellPos, $lastCellPos)\n tr = tr.setSelection(selection)\n dispatch?.(tr)\n }\n return true\n }\n}\n","import type { Command } from '@prosekit/pm/state'\nimport { CellSelection } from 'prosemirror-tables'\n\nimport { findCellPos } from '../table-utils'\n\n/**\n * @public\n */\nexport interface SelectTableCellOptions {\n /**\n * A hit position of the table cell to select from. By default, the selection\n * anchor will be used.\n */\n pos?: number\n}\n\n/**\n * @public\n */\nexport function selectTableCell(options?: SelectTableCellOptions): Command {\n return (state, dispatch) => {\n const $cellPos = findCellPos(\n state.doc,\n options?.pos ?? state.selection.anchor,\n )\n if (!$cellPos) {\n return false\n }\n if (dispatch) {\n const selection = new CellSelection($cellPos)\n dispatch(state.tr.setSelection(selection))\n }\n return true\n }\n}\n","import type { Command } from '@prosekit/pm/state'\nimport { CellSelection } from 'prosemirror-tables'\n\nimport { findCellRange } from '../table-utils'\n\n/**\n * @public\n */\nexport interface SelectTableColumnOptions {\n /**\n * A hit position of the table cell to select from. By default, the selection\n * anchor will be used.\n */\n anchor?: number\n\n /**\n * A hit position of the table cell to select to. By default, the selection\n * head will be used.\n */\n head?: number\n}\n\n/**\n * @public\n */\nexport function selectTableColumn(options?: SelectTableColumnOptions): Command {\n return (state, dispatch) => {\n const range = findCellRange(state.selection, options?.anchor, options?.head)\n if (!range) {\n return false\n }\n if (dispatch) {\n const [$anchorCell, $headCell] = range\n const selection = CellSelection.colSelection($anchorCell, $headCell)\n dispatch(state.tr.setSelection(selection))\n }\n return true\n }\n}\n","import type { Command } from '@prosekit/pm/state'\nimport { CellSelection } from 'prosemirror-tables'\n\nimport { findCellRange } from '../table-utils'\n\n/**\n * @public\n */\nexport interface SelectTableRowOptions {\n /**\n * A hit position of the table cell to select from. By default, the selection\n * anchor will be used.\n */\n anchor?: number\n\n /**\n * A hit position of the table cell to select to. By default, the selection\n * head will be used.\n */\n head?: number\n}\n\n/**\n * @public\n */\nexport function selectTableRow(options?: SelectTableRowOptions): Command {\n return (state, dispatch) => {\n const range = findCellRange(state.selection, options?.anchor, options?.head)\n if (!range) {\n return false\n }\n if (dispatch) {\n const [$anchorCell, $headCell] = range\n const selection = CellSelection.rowSelection($anchorCell, $headCell)\n dispatch(state.tr.setSelection(selection))\n }\n return true\n }\n}\n","import {\n defineCommands,\n type Extension,\n} from '@prosekit/core'\nimport {\n addColumnAfter,\n addColumnBefore,\n addRowAfter,\n addRowBefore,\n deleteColumn,\n deleteRow,\n deleteTable,\n mergeCells,\n splitCell,\n} from 'prosemirror-tables'\n\nimport { deleteCellSelection } from './table-commands/delete-cell-selection'\nimport { exitTable } from './table-commands/exit-table'\nimport {\n insertTable,\n type InsertTableOptions,\n} from './table-commands/insert-table'\nimport {\n moveTableColumn,\n type MoveTableColumnOptions,\n} from './table-commands/move-table-column'\nimport {\n moveTableRow,\n type MoveTableRowOptions,\n} from './table-commands/move-table-row'\nimport {\n selectTable,\n type SelectTableOptions,\n} from './table-commands/select-table'\nimport {\n selectTableCell,\n type SelectTableCellOptions,\n} from './table-commands/select-table-cell'\nimport {\n selectTableColumn,\n type SelectTableColumnOptions,\n} from './table-commands/select-table-column'\nimport {\n selectTableRow,\n type SelectTableRowOptions,\n} from './table-commands/select-table-row'\n\n/**\n * @internal\n */\nexport type TableCommandsExtension = Extension<{\n Commands: {\n insertTable: [options: InsertTableOptions]\n exitTable: []\n\n selectTable: [options?: SelectTableOptions]\n selectTableCell: [options?: SelectTableCellOptions]\n selectTableColumn: [options?: SelectTableColumnOptions]\n selectTableRow: [options?: SelectTableRowOptions]\n\n addTableColumnBefore: []\n addTableColumnAfter: []\n addTableRowAbove: []\n addTableRowBelow: []\n\n deleteTable: []\n deleteTableColumn: []\n deleteTableRow: []\n deleteCellSelection: []\n\n mergeTableCells: []\n splitTableCell: []\n\n moveTableRow: [options: MoveTableRowOptions]\n moveTableColumn: [options: MoveTableColumnOptions]\n }\n}>\n\n/**\n * Adds commands for working with `table` nodes.\n *\n * @public\n */\nexport function defineTableCommands(): TableCommandsExtension {\n return defineCommands({\n insertTable,\n exitTable: () => exitTable,\n\n selectTable,\n selectTableCell,\n selectTableColumn,\n selectTableRow,\n\n addTableColumnBefore: () => addColumnBefore,\n addTableColumnAfter: () => addColumnAfter,\n addTableRowAbove: () => addRowBefore,\n addTableRowBelow: () => addRowAfter,\n\n deleteTable: () => deleteTable,\n deleteTableColumn: () => deleteColumn,\n deleteTableRow: () => deleteRow,\n deleteCellSelection: () => deleteCellSelection,\n\n mergeTableCells: () => mergeCells,\n splitTableCell: () => splitCell,\n\n moveTableRow,\n moveTableColumn,\n })\n}\n","import type { PlainExtension } from '@prosekit/core'\n\nimport type { DragEventHandler } from '../drop-indicator'\nimport { defineDropIndicator } from '../drop-indicator'\n\n/**\n * Hides the drop indicator when dragging a table column or row by using the\n * table handle.\n *\n * @internal\n */\nexport function defineTableDropIndicator(): PlainExtension {\n return defineDropIndicator({\n onDrag,\n })\n}\n\nconst matchMap = new WeakMap<DataTransfer, boolean>()\n\nconst onDrag: DragEventHandler = ({ event }): boolean => {\n const dataTransfer = event.dataTransfer\n if (!dataTransfer) return true\n\n let match: boolean\n\n if (matchMap.has(dataTransfer)) {\n match = matchMap.get(dataTransfer)!\n } else {\n // On Safari, accessing `dataTransfer.types` is more than 10x slower than in\n // Chrome. This becomes a bottleneck when `onDrag` is called frequently, so\n // we cache the result in a WeakMap.\n const types = dataTransfer.types\n match = types.includes('application/x-prosekit-table-handle-drag')\n matchMap.set(dataTransfer, match)\n }\n\n // Don't show the drop indicator when the drag event has\n // \"application/x-prosekit-table-handle-drag\" type.\n return !match\n}\n","import {\n definePlugin,\n type PlainExtension,\n} from '@prosekit/core'\nimport {\n columnResizing,\n tableEditing,\n} from 'prosemirror-tables'\n\n/**\n * @public\n */\nexport function defineTablePlugins(): PlainExtension {\n return definePlugin([tableEditing(), columnResizing()])\n}\n","import {\n defineNodeSpec,\n type Extension,\n} from '@prosekit/core'\nimport type {\n AttributeSpec,\n Attrs,\n} from '@prosekit/pm/model'\nimport { tableNodes } from 'prosemirror-tables'\n\nconst cellContent = 'block+'\n\n/**\n * @public\n */\nexport interface CellAttrs {\n colspan?: number\n rowspan?: number\n colwidth?: number[] | null\n}\n\nconst cellAttrs = {\n colspan: { default: 1 },\n rowspan: { default: 1 },\n colwidth: { default: null },\n} satisfies Record<string, AttributeSpec>\n\n/**\n * @internal\n */\nexport type TableSpecExtension = Extension<{\n Nodes: {\n table: Attrs\n }\n}>\n\nconst specs = tableNodes({\n tableGroup: 'block',\n cellContent,\n cellAttributes: {},\n})\n\n/**\n * @internal\n */\nexport function defineTableSpec(): TableSpecExtension {\n return defineNodeSpec({\n ...specs['table'],\n content: 'tableRow+',\n name: 'table',\n })\n}\n\n/**\n * @internal\n */\nexport type TableRowSpecExtension = Extension<{\n Nodes: {\n tableRow: Attrs\n }\n}>\n\n/**\n * @internal\n */\nexport function defineTableRowSpec(): TableRowSpecExtension {\n return defineNodeSpec({\n ...specs['table_row'],\n content: '(tableCell | tableHeaderCell)*',\n name: 'tableRow',\n })\n}\n\n/**\n * @internal\n */\nexport type TableCellSpecExtension = Extension<{\n Nodes: {\n tableCell: CellAttrs\n }\n}>\n\n/**\n * @internal\n */\nexport function defineTableCellSpec(): TableCellSpecExtension {\n return defineNodeSpec({\n ...specs['table_cell'],\n name: 'tableCell',\n attrs: cellAttrs,\n })\n}\n\n/**\n * @internal\n */\nexport type TableHeaderCellSpecExtension = Extension<{\n Nodes: {\n tableHeaderCell: CellAttrs\n }\n}>\n\nexport function defineTableHeaderCellSpec(): TableHeaderCellSpecExtension {\n return defineNodeSpec({\n ...specs['table_header'],\n name: 'tableHeaderCell',\n attrs: cellAttrs,\n })\n}\n","import {\n union,\n type Union,\n} from '@prosekit/core'\n\nimport {\n defineTableCommands,\n type TableCommandsExtension,\n} from './table-commands'\nimport { defineTableDropIndicator } from './table-drop-indicator'\nimport { defineTablePlugins } from './table-plugins'\nimport {\n defineTableCellSpec,\n defineTableHeaderCellSpec,\n defineTableRowSpec,\n defineTableSpec,\n type TableCellSpecExtension,\n type TableHeaderCellSpecExtension,\n type TableRowSpecExtension,\n type TableSpecExtension,\n} from './table-spec'\n\n/**\n * @internal\n */\nexport type TableExtension = Union<\n [\n TableSpecExtension,\n TableRowSpecExtension,\n TableCellSpecExtension,\n TableHeaderCellSpecExtension,\n TableCommandsExtension,\n ]\n>\n\n/**\n * @public\n */\nexport function defineTable(): TableExtension {\n return union(\n defineTableSpec(),\n defineTableRowSpec(),\n defineTableCellSpec(),\n defineTableHeaderCellSpec(),\n defineTablePlugins(),\n defineTableCommands(),\n defineTableDropIndicator(),\n )\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"table-BLjD91VB.js","names":["exitTable: Command","onDrag: DragEventHandler","match: boolean"],"sources":["../src/table/table-commands/exit-table.ts","../src/table/table-commands/insert-table.ts","../src/table/table-utils.ts","../src/table/table-commands/select-table.ts","../src/table/table-commands/select-table-cell.ts","../src/table/table-commands/select-table-column.ts","../src/table/table-commands/select-table-row.ts","../src/table/table-commands.ts","../src/table/table-drop-indicator.ts","../src/table/table-plugins.ts","../src/table/table-spec.ts","../src/table/table.ts"],"sourcesContent":["import { defaultBlockAt } from '@prosekit/core'\nimport { TextSelection } from '@prosekit/pm/state'\nimport type { Command } from '@prosekit/pm/state'\nimport type { TableRole } from 'prosemirror-tables'\n\n/**\n * When the selection is in a table node, create a default block after the table\n * table, and move the cursor there.\n *\n * @public\n */\nexport const exitTable: Command = (state, dispatch) => {\n const { $head, $anchor } = state.selection\n\n if (!$head.sameParent($anchor)) {\n return false\n }\n\n let tableStart = -1\n let tableDepth = -1\n for (let depth = $head.depth; depth >= 0; depth--) {\n const node = $head.node(depth)\n if ((node.type.spec.tableRole as TableRole) === 'table') {\n tableStart = $head.before(depth)\n tableDepth = depth\n }\n }\n\n if (tableStart < 0 || tableDepth <= 0) {\n return false\n }\n\n const above = $head.node(tableDepth - 1)\n const after = $head.indexAfter(tableDepth - 1)\n const type = defaultBlockAt(above.contentMatchAt(after))\n const node = type?.createAndFill()\n\n if (!type || !node || !above.canReplaceWith(after, after, type)) {\n return false\n }\n\n if (dispatch) {\n const pos = $head.after(tableDepth)\n const tr = state.tr.replaceWith(pos, pos, node)\n tr.setSelection(TextSelection.near(tr.doc.resolve(pos), 1))\n dispatch(tr.scrollIntoView())\n }\n return true\n}\n","import {\n getNodeType,\n insertNode,\n} from '@prosekit/core'\nimport type { Schema } from '@prosekit/pm/model'\nimport type { Command } from '@prosekit/pm/state'\n\nfunction createEmptyTable(\n schema: Schema,\n row: number,\n col: number,\n header: boolean,\n) {\n const tableType = getNodeType(schema, 'table')\n const tableRowType = getNodeType(schema, 'tableRow')\n const tableCellType = getNodeType(schema, 'tableCell')\n const tableHeaderCellType = getNodeType(schema, 'tableHeaderCell')\n\n if (header) {\n const headerCell = tableHeaderCellType.createAndFill()!\n const headerCells = repeat(headerCell, col)\n const headerRow = tableRowType.createAndFill(null, headerCells)!\n\n const bodyCell = tableCellType.createAndFill()!\n const bodyCells = repeat(bodyCell, col)\n const bodyRow = tableRowType.createAndFill(null, bodyCells)!\n const bodyRows = repeat(bodyRow, row - 1)\n\n return tableType.createAndFill(null, [headerRow, ...bodyRows])!\n } else {\n const bodyCell = tableCellType.createAndFill()!\n const bodyCells = repeat(bodyCell, col)\n const bodyRow = tableRowType.createAndFill(null, bodyCells)!\n const bodyRows = repeat(bodyRow, row)\n\n return tableType.createAndFill(null, bodyRows)!\n }\n}\n\nfunction repeat<T>(node: T, length: number): T[] {\n return Array<T>(length).fill(node)\n}\n\n/**\n * @public\n */\nexport interface InsertTableOptions {\n /**\n * The number of rows in the table.\n */\n row: number\n\n /**\n * The number of columns in the table.\n */\n col: number\n\n /**\n * Whether the table has a header row.\n *\n * @default false\n */\n header?: boolean\n}\n\n/**\n * Insert a table node with the given number of rows and columns, and optionally\n * a header row.\n *\n * @param options\n *\n * @public\n */\nexport function insertTable(options: InsertTableOptions): Command {\n return (state, dispatch, view) => {\n const { row, col, header = false } = options\n const table = createEmptyTable(state.schema, row, col, header)\n return insertNode({ node: table })(state, dispatch, view)\n }\n}\n","import { CellSelection } from 'prosemirror-tables'\n\nexport {\n findCellPos,\n findCellRange,\n findTable,\n} from 'prosemirror-tables'\n\n/**\n * Checks if the given object is a `CellSelection` instance.\n *\n * @public\n */\nexport function isCellSelection(value: unknown): value is CellSelection {\n return value instanceof CellSelection\n}\n","import type { Command } from '@prosekit/pm/state'\nimport {\n CellSelection,\n TableMap,\n} from 'prosemirror-tables'\n\nimport { findTable } from '../table-utils'\n\n/**\n * @public\n */\nexport interface SelectTableOptions {\n /**\n * A hit position of the table to select from. By default, the selection\n * anchor will be used.\n */\n pos?: number\n}\n\n/**\n * @public\n */\nexport function selectTable(options?: SelectTableOptions): Command {\n return (state, dispatch) => {\n const $pos = options?.pos\n ? state.doc.resolve(options.pos)\n : state.selection.$anchor\n const table = findTable($pos)\n if (!table) {\n return false\n }\n const map = TableMap.get(table.node)\n if (map.map.length === 0) {\n return false\n }\n if (dispatch) {\n let tr = state.tr\n const firstCellPosInTable = map.map[0]\n const lastCellPosInTable = map.map[map.map.length - 1]\n const firstCellPos = table.pos + firstCellPosInTable + 1\n const lastCellPos = table.pos + lastCellPosInTable + 1\n const $firstCellPos = tr.doc.resolve(firstCellPos)\n const $lastCellPos = tr.doc.resolve(lastCellPos)\n const selection = new CellSelection($firstCellPos, $lastCellPos)\n tr = tr.setSelection(selection)\n dispatch?.(tr)\n }\n return true\n }\n}\n","import type { Command } from '@prosekit/pm/state'\nimport { CellSelection } from 'prosemirror-tables'\n\nimport { findCellPos } from '../table-utils'\n\n/**\n * @public\n */\nexport interface SelectTableCellOptions {\n /**\n * A hit position of the table cell to select from. By default, the selection\n * anchor will be used.\n */\n pos?: number\n}\n\n/**\n * @public\n */\nexport function selectTableCell(options?: SelectTableCellOptions): Command {\n return (state, dispatch) => {\n const $cellPos = findCellPos(\n state.doc,\n options?.pos ?? state.selection.anchor,\n )\n if (!$cellPos) {\n return false\n }\n if (dispatch) {\n const selection = new CellSelection($cellPos)\n dispatch(state.tr.setSelection(selection))\n }\n return true\n }\n}\n","import type { Command } from '@prosekit/pm/state'\nimport { CellSelection } from 'prosemirror-tables'\n\nimport { findCellRange } from '../table-utils'\n\n/**\n * @public\n */\nexport interface SelectTableColumnOptions {\n /**\n * A hit position of the table cell to select from. By default, the selection\n * anchor will be used.\n */\n anchor?: number\n\n /**\n * A hit position of the table cell to select to. By default, the selection\n * head will be used.\n */\n head?: number\n}\n\n/**\n * @public\n */\nexport function selectTableColumn(options?: SelectTableColumnOptions): Command {\n return (state, dispatch) => {\n const range = findCellRange(state.selection, options?.anchor, options?.head)\n if (!range) {\n return false\n }\n if (dispatch) {\n const [$anchorCell, $headCell] = range\n const selection = CellSelection.colSelection($anchorCell, $headCell)\n dispatch(state.tr.setSelection(selection))\n }\n return true\n }\n}\n","import type { Command } from '@prosekit/pm/state'\nimport { CellSelection } from 'prosemirror-tables'\n\nimport { findCellRange } from '../table-utils'\n\n/**\n * @public\n */\nexport interface SelectTableRowOptions {\n /**\n * A hit position of the table cell to select from. By default, the selection\n * anchor will be used.\n */\n anchor?: number\n\n /**\n * A hit position of the table cell to select to. By default, the selection\n * head will be used.\n */\n head?: number\n}\n\n/**\n * @public\n */\nexport function selectTableRow(options?: SelectTableRowOptions): Command {\n return (state, dispatch) => {\n const range = findCellRange(state.selection, options?.anchor, options?.head)\n if (!range) {\n return false\n }\n if (dispatch) {\n const [$anchorCell, $headCell] = range\n const selection = CellSelection.rowSelection($anchorCell, $headCell)\n dispatch(state.tr.setSelection(selection))\n }\n return true\n }\n}\n","import {\n defineCommands,\n type Extension,\n} from '@prosekit/core'\nimport {\n addColumnAfter,\n addColumnBefore,\n addRowAfter,\n addRowBefore,\n deleteColumn,\n deleteRow,\n deleteTable,\n mergeCells,\n splitCell,\n} from 'prosemirror-tables'\n\nimport { deleteCellSelection } from './table-commands/delete-cell-selection'\nimport { exitTable } from './table-commands/exit-table'\nimport {\n insertTable,\n type InsertTableOptions,\n} from './table-commands/insert-table'\nimport {\n moveTableColumn,\n type MoveTableColumnOptions,\n} from './table-commands/move-table-column'\nimport {\n moveTableRow,\n type MoveTableRowOptions,\n} from './table-commands/move-table-row'\nimport {\n selectTable,\n type SelectTableOptions,\n} from './table-commands/select-table'\nimport {\n selectTableCell,\n type SelectTableCellOptions,\n} from './table-commands/select-table-cell'\nimport {\n selectTableColumn,\n type SelectTableColumnOptions,\n} from './table-commands/select-table-column'\nimport {\n selectTableRow,\n type SelectTableRowOptions,\n} from './table-commands/select-table-row'\n\n/**\n * @internal\n */\nexport type TableCommandsExtension = Extension<{\n Commands: {\n insertTable: [options: InsertTableOptions]\n exitTable: []\n\n selectTable: [options?: SelectTableOptions]\n selectTableCell: [options?: SelectTableCellOptions]\n selectTableColumn: [options?: SelectTableColumnOptions]\n selectTableRow: [options?: SelectTableRowOptions]\n\n addTableColumnBefore: []\n addTableColumnAfter: []\n addTableRowAbove: []\n addTableRowBelow: []\n\n deleteTable: []\n deleteTableColumn: []\n deleteTableRow: []\n deleteCellSelection: []\n\n mergeTableCells: []\n splitTableCell: []\n\n moveTableRow: [options: MoveTableRowOptions]\n moveTableColumn: [options: MoveTableColumnOptions]\n }\n}>\n\n/**\n * Adds commands for working with `table` nodes.\n *\n * @public\n */\nexport function defineTableCommands(): TableCommandsExtension {\n return defineCommands({\n insertTable,\n exitTable: () => exitTable,\n\n selectTable,\n selectTableCell,\n selectTableColumn,\n selectTableRow,\n\n addTableColumnBefore: () => addColumnBefore,\n addTableColumnAfter: () => addColumnAfter,\n addTableRowAbove: () => addRowBefore,\n addTableRowBelow: () => addRowAfter,\n\n deleteTable: () => deleteTable,\n deleteTableColumn: () => deleteColumn,\n deleteTableRow: () => deleteRow,\n deleteCellSelection: () => deleteCellSelection,\n\n mergeTableCells: () => mergeCells,\n splitTableCell: () => splitCell,\n\n moveTableRow,\n moveTableColumn,\n })\n}\n","import type { PlainExtension } from '@prosekit/core'\n\nimport type { DragEventHandler } from '../drop-indicator'\nimport { defineDropIndicator } from '../drop-indicator'\n\n/**\n * Hides the drop indicator when dragging a table column or row by using the\n * table handle.\n *\n * @internal\n */\nexport function defineTableDropIndicator(): PlainExtension {\n return defineDropIndicator({\n onDrag,\n })\n}\n\nconst matchMap = new WeakMap<DataTransfer, boolean>()\n\nconst onDrag: DragEventHandler = ({ event }): boolean => {\n const dataTransfer = event.dataTransfer\n if (!dataTransfer) return true\n\n let match: boolean\n\n if (matchMap.has(dataTransfer)) {\n match = matchMap.get(dataTransfer)!\n } else {\n // On Safari, accessing `dataTransfer.types` is more than 10x slower than in\n // Chrome. This becomes a bottleneck when `onDrag` is called frequently, so\n // we cache the result in a WeakMap.\n const types = dataTransfer.types\n match = types.includes('application/x-prosekit-table-handle-drag')\n matchMap.set(dataTransfer, match)\n }\n\n // Don't show the drop indicator when the drag event has\n // \"application/x-prosekit-table-handle-drag\" type.\n return !match\n}\n","import {\n definePlugin,\n type PlainExtension,\n} from '@prosekit/core'\nimport {\n columnResizing,\n tableEditing,\n} from 'prosemirror-tables'\n\n/**\n * @public\n */\nexport function defineTablePlugins(): PlainExtension {\n return definePlugin([tableEditing(), columnResizing()])\n}\n","import {\n defineNodeSpec,\n type Extension,\n} from '@prosekit/core'\nimport type {\n AttributeSpec,\n Attrs,\n} from '@prosekit/pm/model'\nimport { tableNodes } from 'prosemirror-tables'\n\nconst cellContent = 'block+'\n\n/**\n * @public\n */\nexport interface CellAttrs {\n colspan?: number\n rowspan?: number\n colwidth?: number[] | null\n}\n\nconst cellAttrs = {\n colspan: { default: 1 },\n rowspan: { default: 1 },\n colwidth: { default: null },\n} satisfies Record<string, AttributeSpec>\n\n/**\n * @internal\n */\nexport type TableSpecExtension = Extension<{\n Nodes: {\n table: Attrs\n }\n}>\n\nconst specs = tableNodes({\n tableGroup: 'block',\n cellContent,\n cellAttributes: {},\n})\n\n/**\n * @internal\n */\nexport function defineTableSpec(): TableSpecExtension {\n return defineNodeSpec({\n ...specs['table'],\n content: 'tableRow+',\n name: 'table',\n })\n}\n\n/**\n * @internal\n */\nexport type TableRowSpecExtension = Extension<{\n Nodes: {\n tableRow: Attrs\n }\n}>\n\n/**\n * @internal\n */\nexport function defineTableRowSpec(): TableRowSpecExtension {\n return defineNodeSpec({\n ...specs['table_row'],\n content: '(tableCell | tableHeaderCell)*',\n name: 'tableRow',\n })\n}\n\n/**\n * @internal\n */\nexport type TableCellSpecExtension = Extension<{\n Nodes: {\n tableCell: CellAttrs\n }\n}>\n\n/**\n * @internal\n */\nexport function defineTableCellSpec(): TableCellSpecExtension {\n return defineNodeSpec({\n ...specs['table_cell'],\n name: 'tableCell',\n attrs: cellAttrs,\n })\n}\n\n/**\n * @internal\n */\nexport type TableHeaderCellSpecExtension = Extension<{\n Nodes: {\n tableHeaderCell: CellAttrs\n }\n}>\n\nexport function defineTableHeaderCellSpec(): TableHeaderCellSpecExtension {\n return defineNodeSpec({\n ...specs['table_header'],\n name: 'tableHeaderCell',\n attrs: cellAttrs,\n })\n}\n","import {\n union,\n type Union,\n} from '@prosekit/core'\n\nimport {\n defineTableCommands,\n type TableCommandsExtension,\n} from './table-commands'\nimport { defineTableDropIndicator } from './table-drop-indicator'\nimport { defineTablePlugins } from './table-plugins'\nimport {\n defineTableCellSpec,\n defineTableHeaderCellSpec,\n defineTableRowSpec,\n defineTableSpec,\n type TableCellSpecExtension,\n type TableHeaderCellSpecExtension,\n type TableRowSpecExtension,\n type TableSpecExtension,\n} from './table-spec'\n\n/**\n * @internal\n */\nexport type TableExtension = Union<\n [\n TableSpecExtension,\n TableRowSpecExtension,\n TableCellSpecExtension,\n TableHeaderCellSpecExtension,\n TableCommandsExtension,\n ]\n>\n\n/**\n * @public\n */\nexport function defineTable(): TableExtension {\n return union(\n defineTableSpec(),\n defineTableRowSpec(),\n defineTableCellSpec(),\n defineTableHeaderCellSpec(),\n defineTablePlugins(),\n defineTableCommands(),\n defineTableDropIndicator(),\n )\n}\n"],"mappings":"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"}
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@prosekit/extensions",
3
3
  "type": "module",
4
- "version": "0.11.6",
4
+ "version": "0.12.0",
5
5
  "private": false,
6
6
  "description": "A collection of common extensions for ProseKit",
7
7
  "author": {
@@ -207,18 +207,18 @@
207
207
  "src"
208
208
  ],
209
209
  "dependencies": {
210
- "@ocavue/utils": "^0.7.1",
210
+ "@ocavue/utils": "^0.8.1",
211
211
  "prosemirror-changeset": "^2.3.1",
212
+ "prosemirror-drop-indicator": "^0.1.2",
212
213
  "prosemirror-dropcursor": "^1.8.2",
213
- "prosemirror-flat-list": "^0.5.5",
214
- "prosemirror-gapcursor": "^1.3.2",
214
+ "prosemirror-flat-list": "^0.5.8",
215
+ "prosemirror-gapcursor": "^1.4.0",
215
216
  "prosemirror-highlight": "^0.13.0",
216
217
  "prosemirror-search": "^1.1.0",
217
218
  "prosemirror-tables": "^1.8.1",
218
- "shiki": "^3.13.0",
219
+ "shiki": "^3.14.0",
219
220
  "@prosekit/core": "^0.8.4",
220
- "@prosekit/pm": "^0.1.12",
221
- "prosemirror-drop-indicator": "^0.1.0"
221
+ "@prosekit/pm": "^0.1.13"
222
222
  },
223
223
  "peerDependencies": {
224
224
  "loro-crdt": ">= 0.16.7",
@@ -242,21 +242,21 @@
242
242
  },
243
243
  "devDependencies": {
244
244
  "@types/diffable-html": "^5.0.2",
245
- "@vitest/browser": "^3.2.4",
246
245
  "diffable-html": "^6.0.1",
247
246
  "just-pick": "^4.2.0",
248
- "loro-crdt": "^1.8.0",
249
- "loro-prosemirror": "^0.3.1",
247
+ "loro-crdt": "^1.8.8",
248
+ "loro-prosemirror": "^0.3.6",
250
249
  "rehype-parse": "^9.0.1",
251
250
  "rehype-remark": "^10.0.1",
251
+ "remark-gfm": "^4.0.1",
252
252
  "remark-html": "^16.0.1",
253
253
  "remark-parse": "^11.0.0",
254
254
  "remark-stringify": "^11.0.0",
255
- "tsdown": "^0.15.4",
256
- "type-fest": "^5.0.1",
257
- "typescript": "~5.9.2",
255
+ "tsdown": "^0.15.12",
256
+ "type-fest": "^5.1.0",
257
+ "typescript": "~5.9.3",
258
258
  "unified": "^11.0.5",
259
- "vitest": "^3.2.4",
259
+ "vitest": "^4.0.5",
260
260
  "y-prosemirror": "^1.3.7",
261
261
  "y-protocols": "^1.0.6",
262
262
  "yjs": "^13.6.27",
@@ -75,16 +75,8 @@ export class UploadTask<Result> {
75
75
  const maybePromise = uploader({
76
76
  file,
77
77
  onProgress: (progress) => {
78
- if (typeof progress.total !== 'number') {
79
- throw new TypeError('total must be a number')
80
- }
81
- if (typeof progress.loaded !== 'number') {
82
- throw new TypeError('loaded must be a number')
83
- }
84
- if (progress.loaded > 0) {
85
- for (const subscriber of this.subscribers) {
86
- subscriber(progress)
87
- }
78
+ for (const subscriber of this.subscribers) {
79
+ subscriber(progress)
88
80
  }
89
81
  },
90
82
  })
@@ -0,0 +1,14 @@
1
+ import { insertNode } from '@prosekit/core'
2
+ import type { Command } from '@prosekit/pm/state'
3
+
4
+ import type { ImageAttrs } from '../image-spec'
5
+
6
+ /**
7
+ * Returns a command that inserts an image node with the given attributes at the
8
+ * current selection position.
9
+ *
10
+ * @public
11
+ */
12
+ export function insertImage(attrs?: ImageAttrs): Command {
13
+ return insertNode({ type: 'image', attrs })
14
+ }
@@ -0,0 +1,137 @@
1
+ import {
2
+ insertNode,
3
+ ProseKitError,
4
+ } from '@prosekit/core'
5
+ import type { Command } from '@prosekit/pm/state'
6
+ import type { EditorView } from '@prosekit/pm/view'
7
+
8
+ import {
9
+ UploadTask,
10
+ type Uploader,
11
+ } from '../../file'
12
+ import type { ImageAttrs } from '../image-spec'
13
+
14
+ /**
15
+ * Options for {@link uploadImage}.
16
+ *
17
+ * @public
18
+ */
19
+ export interface UploadImageOptions {
20
+ /**
21
+ * The uploader used to upload the file. It should return a promise that
22
+ * resolves to the URL of the uploaded image.
23
+ */
24
+ uploader: Uploader<string>
25
+ /**
26
+ * The file that will be uploaded.
27
+ */
28
+ file: File
29
+ /**
30
+ * The position where the image should be inserted. If not provided, the
31
+ * image is inserted at the current selection.
32
+ */
33
+ pos?: number
34
+ /**
35
+ * A handler to be called when an error occurs during the upload.
36
+ */
37
+ onError?: ImageUploadErrorHandler
38
+ }
39
+
40
+ /**
41
+ * Options for the {@link ImageUploadErrorHandler} callback.
42
+ *
43
+ * @public
44
+ */
45
+ export interface ImageUploadErrorHandlerOptions {
46
+ /**
47
+ * The file that was uploaded.
48
+ */
49
+ file: File
50
+ /**
51
+ * The error that occurred during the upload.
52
+ */
53
+ error: unknown
54
+ /**
55
+ * The upload task that was used to upload the file.
56
+ */
57
+ uploadTask: UploadTask<string>
58
+ }
59
+
60
+ /**
61
+ * A handler to be called when an error occurs during the upload.
62
+ *
63
+ * @public
64
+ */
65
+ export type ImageUploadErrorHandler = (options: ImageUploadErrorHandlerOptions) => void
66
+
67
+ /**
68
+ * Returns a command that uploads an image file and inserts an image node with a
69
+ * temporary URL which is replaced once the upload completes.
70
+ *
71
+ * @param options
72
+ *
73
+ * @public
74
+ */
75
+ export function uploadImage({ uploader, file, pos, onError }: UploadImageOptions): Command {
76
+ return (state, dispatch, view) => {
77
+ const uploadTask = new UploadTask({ file, uploader })
78
+ const objectURL = uploadTask.objectURL
79
+ const attrs: ImageAttrs = { src: objectURL }
80
+
81
+ uploadTask.finished
82
+ .then((resultURL) => {
83
+ if (view && view.isDestroyed) {
84
+ return
85
+ } else if (typeof resultURL !== 'string') {
86
+ const error = new ProseKitError(
87
+ `Unexpected upload result. Expected a string but got ${typeof resultURL}`,
88
+ )
89
+ onError?.({ file, error, uploadTask })
90
+ } else if (!view) {
91
+ const error = new ProseKitError(
92
+ 'View must be available to replace the image URL',
93
+ )
94
+ onError?.({ file, error, uploadTask })
95
+ } else {
96
+ replaceImageURL(view, objectURL, resultURL)
97
+ UploadTask.delete(objectURL)
98
+ }
99
+ })
100
+ .catch((error) => {
101
+ onError?.({ file, error, uploadTask })
102
+ })
103
+
104
+ return insertNode({ type: 'image', attrs, pos })(state, dispatch, view)
105
+ }
106
+ }
107
+
108
+ /**
109
+ * Replaces the temporary image URL with the final uploaded URL.
110
+ *
111
+ * @internal
112
+ */
113
+ export function replaceImageURL(
114
+ view: EditorView,
115
+ oldURL: string,
116
+ newURL: string,
117
+ ): void {
118
+ const positions: number[] = []
119
+ view.state.doc.descendants((node, pos) => {
120
+ if (node.type.name === 'image') {
121
+ const attrs = node.attrs as ImageAttrs
122
+ if (attrs.src === oldURL) {
123
+ positions.push(pos)
124
+ }
125
+ }
126
+ })
127
+
128
+ if (positions.length === 0) {
129
+ return
130
+ }
131
+
132
+ const tr = view.state.tr
133
+ for (const pos of positions) {
134
+ tr.setNodeAttribute(pos, 'src', newURL)
135
+ }
136
+ view.dispatch(tr)
137
+ }
@@ -1,10 +1,13 @@
1
1
  import {
2
2
  defineCommands,
3
- insertNode,
4
3
  type Extension,
5
4
  } from '@prosekit/core'
6
- import type { Command } from '@prosekit/pm/state'
7
5
 
6
+ import { insertImage } from './image-commands/insert-image'
7
+ import {
8
+ uploadImage,
9
+ type UploadImageOptions,
10
+ } from './image-commands/upload-image'
8
11
  import type { ImageAttrs } from './image-spec'
9
12
 
10
13
  /**
@@ -13,24 +16,16 @@ import type { ImageAttrs } from './image-spec'
13
16
  export type ImageCommandsExtension = Extension<{
14
17
  Commands: {
15
18
  insertImage: [attrs?: ImageAttrs]
19
+ uploadImage: [options: UploadImageOptions]
16
20
  }
17
21
  }>
18
22
 
19
- /**
20
- * Returns a command that inserts an image node with the given attributes at the
21
- * current selection position.
22
- *
23
- * @public
24
- */
25
- export function insertImage(attrs?: ImageAttrs): Command {
26
- return insertNode({ type: 'image', attrs })
27
- }
28
-
29
23
  /**
30
24
  * @internal
31
25
  */
32
26
  export function defineImageCommands(): ImageCommandsExtension {
33
27
  return defineCommands({
34
28
  insertImage,
29
+ uploadImage,
35
30
  })
36
31
  }
@@ -1,15 +1,11 @@
1
1
  import {
2
- insertNode,
3
- ProseKitError,
4
2
  union,
5
3
  type PlainExtension,
6
4
  } from '@prosekit/core'
7
- import type { EditorView } from '@prosekit/pm/view'
8
5
 
9
6
  import {
10
7
  defineFileDropHandler,
11
8
  defineFilePasteHandler,
12
- UploadTask,
13
9
  type FileDropHandler,
14
10
  type FileDropHandlerOptions,
15
11
  type FilePasteHandler,
@@ -17,7 +13,10 @@ import {
17
13
  type Uploader,
18
14
  } from '../file'
19
15
 
20
- import type { ImageAttrs } from './image-spec'
16
+ import {
17
+ uploadImage,
18
+ type ImageUploadErrorHandler,
19
+ } from './image-commands/upload-image'
21
20
 
22
21
  /**
23
22
  * A predicate to determine if the pasted file should be uploaded and inserted as an image.
@@ -32,29 +31,6 @@ export type ImageCanDropPredicate = (options: FileDropHandlerOptions) => boolean
32
31
  /**
33
32
  * A handler to be called when an error occurs during the upload.
34
33
  */
35
- export type ImageUploadErrorHandler = (options: ImageUploadErrorHandlerOptions) => void
36
-
37
- /**
38
- * Options for the {@link ImageUploadErrorHandler} callback.
39
- */
40
- export interface ImageUploadErrorHandlerOptions {
41
- /**
42
- * The file that was uploaded.
43
- */
44
- file: File
45
- /**
46
- * The error that occurred during the upload.
47
- */
48
- error: unknown
49
- /**
50
- * The upload task that was used to upload the file.
51
- */
52
- uploadTask: UploadTask<string>
53
- }
54
-
55
- /**
56
- * Options for {@link defineImageUploadHandler}.
57
- */
58
34
  export interface ImageUploadHandlerOptions {
59
35
  /**
60
36
  * The uploader used to upload the file. It should return a promise that
@@ -99,35 +75,18 @@ export function defineImageUploadHandler({
99
75
  canDrop = defaultCanUpload,
100
76
  onError = defaultOnError,
101
77
  }: ImageUploadHandlerOptions): PlainExtension {
102
- const handleInsert = (view: EditorView, file: File, pos?: number): boolean => {
103
- const uploadTask = new UploadTask({ file, uploader })
104
- const objectURL = uploadTask.objectURL
105
- const attrs: ImageAttrs = { src: objectURL }
106
- uploadTask.finished.then((resultURL) => {
107
- if (view.isDestroyed) {
108
- return
109
- } else if (typeof resultURL !== 'string') {
110
- const error = new ProseKitError(`Unexpected upload result. Expected a string but got ${typeof resultURL}`)
111
- onError({ file, error, uploadTask })
112
- } else {
113
- replaceImageURL(view, objectURL, resultURL)
114
- UploadTask.delete(objectURL)
115
- }
116
- }).catch((error) => {
117
- onError({ file, error, uploadTask })
118
- })
119
- const command = insertNode({ type: 'image', attrs, pos })
120
- return command(view.state, view.dispatch, view)
121
- }
122
-
123
78
  const handlePaste: FilePasteHandler = (options) => {
124
79
  if (!canPaste(options)) return false
125
- return handleInsert(options.view, options.file)
80
+ const { view, file } = options
81
+ const command = uploadImage({ uploader, file, onError })
82
+ return command(view.state, view.dispatch, view)
126
83
  }
127
84
 
128
85
  const handleDrop: FileDropHandler = (options) => {
129
86
  if (!canDrop(options)) return false
130
- return handleInsert(options.view, options.file, options.pos)
87
+ const { view, file, pos } = options
88
+ const command = uploadImage({ uploader, file, onError, pos })
89
+ return command(view.state, view.dispatch, view)
131
90
  }
132
91
 
133
92
  return union(
@@ -135,22 +94,3 @@ export function defineImageUploadHandler({
135
94
  defineFileDropHandler(handleDrop),
136
95
  )
137
96
  }
138
-
139
- function replaceImageURL(view: EditorView, oldURL: string, newURL: string) {
140
- const positions: number[] = []
141
- view.state.doc.descendants((node, pos) => {
142
- if (node.type.name === 'image') {
143
- const attrs = node.attrs as ImageAttrs
144
- if (attrs.src === oldURL) {
145
- positions.push(pos)
146
- }
147
- }
148
- })
149
- if (positions.length > 0) {
150
- const tr = view.state.tr
151
- for (const pos of positions) {
152
- tr.setNodeAttribute(pos, 'src', newURL)
153
- }
154
- view.dispatch(tr)
155
- }
156
- }
@@ -4,9 +4,16 @@ export {
4
4
  } from './image'
5
5
  export {
6
6
  defineImageCommands,
7
- insertImage,
8
7
  type ImageCommandsExtension,
9
8
  } from './image-commands'
9
+ export { insertImage } from './image-commands/insert-image'
10
+ export {
11
+ replaceImageURL,
12
+ uploadImage,
13
+ type ImageUploadErrorHandler,
14
+ type ImageUploadErrorHandlerOptions,
15
+ type UploadImageOptions,
16
+ } from './image-commands/upload-image'
10
17
  export {
11
18
  defineImageSpec,
12
19
  type ImageAttrs,
@@ -16,7 +23,5 @@ export {
16
23
  defineImageUploadHandler,
17
24
  type ImageCanDropPredicate,
18
25
  type ImageCanPastePredicate,
19
- type ImageUploadErrorHandler,
20
- type ImageUploadErrorHandlerOptions,
21
26
  type ImageUploadHandlerOptions,
22
27
  } from './image-upload-handler'
@@ -1,9 +1,9 @@
1
- import { userEvent } from '@vitest/browser/context'
2
1
  import {
3
2
  describe,
4
3
  expect,
5
4
  it,
6
5
  } from 'vitest'
6
+ import { userEvent } from 'vitest/browser'
7
7
 
8
8
  import { setupTest } from '../testing'
9
9
 
@@ -4,6 +4,7 @@ import {
4
4
  type PlainExtension,
5
5
  } from '@prosekit/core'
6
6
  import {
7
+ findCheckboxInListItem,
7
8
  joinListElements,
8
9
  listToDOM,
9
10
  } from 'prosemirror-flat-list'
@@ -16,13 +17,15 @@ export function defineListSerializer(): PlainExtension {
16
17
  serializeFragmentWrapper: (fn) => {
17
18
  return (...args) => {
18
19
  const dom = fn(...args)
19
- return joinListElements(dom)
20
+ return normalizeElementTree(joinListElements(dom))
20
21
  }
21
22
  },
22
23
  serializeNodeWrapper: (fn) => {
23
24
  return (...args) => {
24
25
  const dom = fn(...args)
25
- return isElementLike(dom) ? joinListElements(dom) : dom
26
+ return isElementLike(dom)
27
+ ? normalizeElementTree(joinListElements(dom))
28
+ : dom
26
29
  }
27
30
  },
28
31
  nodesFromSchemaWrapper: (fn) => {
@@ -30,9 +33,60 @@ export function defineListSerializer(): PlainExtension {
30
33
  const nodes = fn(...args)
31
34
  return {
32
35
  ...nodes,
33
- list: (node) => listToDOM({ node, nativeList: true, getMarkers: () => null }),
36
+ list: (node) => listToDOM({ node, nativeList: true }),
34
37
  }
35
38
  }
36
39
  },
37
40
  })
38
41
  }
42
+
43
+ function normalizeElementTree<T extends Element | DocumentFragment>(
44
+ node: T,
45
+ ): T {
46
+ if (isElementLike(node)) {
47
+ normalizeTaskList(node)
48
+ }
49
+
50
+ for (const child of node.children) {
51
+ normalizeElementTree(child)
52
+ }
53
+
54
+ return node
55
+ }
56
+
57
+ /**
58
+ * Modifies the DOM tree for task lists to ensure that the output HTML can be
59
+ * parsed by rehype-remark.
60
+ */
61
+ function normalizeTaskList(node: Element): void {
62
+ if (
63
+ !node.classList.contains('prosemirror-flat-list')
64
+ || node.getAttribute('data-list-kind') !== 'task'
65
+ || node.children.length !== 2
66
+ ) {
67
+ return
68
+ }
69
+
70
+ const marker = node.children.item(0)
71
+ if (!marker || !marker.classList.contains('list-marker')) {
72
+ return
73
+ }
74
+
75
+ const checkbox = findCheckboxInListItem(marker)
76
+ if (!checkbox) {
77
+ return
78
+ }
79
+
80
+ const content = node.children.item(1)
81
+ if (!content || !content.classList.contains('list-content')) {
82
+ return
83
+ }
84
+
85
+ const textBlock = content.children.item(0)
86
+ if (!textBlock || !['P', 'H1', 'H2', 'H3', 'H4', 'H5', 'H6'].includes(textBlock.tagName)) {
87
+ return
88
+ }
89
+
90
+ node.replaceChildren(...content.children)
91
+ textBlock.prepend(checkbox)
92
+ }