@prosekit/extensions 0.14.2 → 0.16.0-beta.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/commit/style.css +1 -3
- package/dist/{drop-indicator-DJq8pF92.js → drop-indicator.js} +2 -4
- package/dist/drop-indicator.js.map +1 -0
- package/dist/{file-upload-I9m1EJAM.js → file.js} +2 -6
- package/dist/file.js.map +1 -0
- package/dist/gap-cursor/style.css +5 -8
- package/dist/{file-upload-dr3IXUty.d.ts → index.d.ts} +1 -1
- package/dist/index.d.ts.map +1 -0
- package/dist/list/style.css +79 -110
- package/dist/loro/style.css +18 -21
- package/dist/{mark-rule-Bqdm49Eq.js → mark-rule.js} +2 -5
- package/dist/mark-rule.js.map +1 -0
- package/dist/page/style.css +43 -0
- package/dist/{mark-paste-rule--F1QPUcU.js → paste-rule.js} +2 -6
- package/dist/paste-rule.js.map +1 -0
- package/dist/placeholder/style.css +4 -7
- package/dist/prosekit-extensions-autocomplete.js +1 -5
- package/dist/prosekit-extensions-autocomplete.js.map +1 -1
- package/dist/prosekit-extensions-background-color.js +1 -4
- package/dist/prosekit-extensions-background-color.js.map +1 -1
- package/dist/prosekit-extensions-blockquote.js +1 -6
- package/dist/prosekit-extensions-blockquote.js.map +1 -1
- package/dist/prosekit-extensions-bold.js +1 -6
- package/dist/prosekit-extensions-bold.js.map +1 -1
- package/dist/prosekit-extensions-code-block.d.ts +4 -2
- package/dist/prosekit-extensions-code-block.d.ts.map +1 -1
- package/dist/prosekit-extensions-code-block.js +1 -10
- package/dist/prosekit-extensions-code-block.js.map +1 -1
- package/dist/prosekit-extensions-code.js +1 -6
- package/dist/prosekit-extensions-code.js.map +1 -1
- package/dist/prosekit-extensions-commit.js +1 -2
- package/dist/prosekit-extensions-commit.js.map +1 -1
- package/dist/prosekit-extensions-doc.js +1 -2
- package/dist/prosekit-extensions-doc.js.map +1 -1
- package/dist/prosekit-extensions-drop-cursor.js +1 -2
- package/dist/prosekit-extensions-drop-cursor.js.map +1 -1
- package/dist/prosekit-extensions-drop-indicator.js +2 -3
- package/dist/prosekit-extensions-enter-rule.js +1 -2
- package/dist/prosekit-extensions-enter-rule.js.map +1 -1
- package/dist/prosekit-extensions-file.d.ts +2 -2
- package/dist/prosekit-extensions-file.js +2 -3
- package/dist/prosekit-extensions-gap-cursor.js +1 -2
- package/dist/prosekit-extensions-gap-cursor.js.map +1 -1
- package/dist/prosekit-extensions-hard-break.js +1 -5
- package/dist/prosekit-extensions-hard-break.js.map +1 -1
- package/dist/prosekit-extensions-heading.js +1 -6
- package/dist/prosekit-extensions-heading.js.map +1 -1
- package/dist/prosekit-extensions-horizontal-rule.d.ts.map +1 -1
- package/dist/prosekit-extensions-horizontal-rule.js +3 -6
- package/dist/prosekit-extensions-horizontal-rule.js.map +1 -1
- package/dist/prosekit-extensions-image.d.ts +1 -2
- package/dist/prosekit-extensions-image.d.ts.map +1 -1
- package/dist/prosekit-extensions-image.js +2 -8
- package/dist/prosekit-extensions-image.js.map +1 -1
- package/dist/prosekit-extensions-input-rule.js +1 -2
- package/dist/prosekit-extensions-input-rule.js.map +1 -1
- package/dist/prosekit-extensions-italic.js +1 -6
- package/dist/prosekit-extensions-italic.js.map +1 -1
- package/dist/prosekit-extensions-link.js +3 -6
- package/dist/prosekit-extensions-link.js.map +1 -1
- package/dist/prosekit-extensions-list.js +2 -10
- package/dist/prosekit-extensions-list.js.map +1 -1
- package/dist/prosekit-extensions-loro.d.ts.map +1 -1
- package/dist/prosekit-extensions-loro.js +3 -9
- package/dist/prosekit-extensions-loro.js.map +1 -1
- package/dist/prosekit-extensions-mark-rule.js +2 -3
- package/dist/prosekit-extensions-math.js +1 -5
- package/dist/prosekit-extensions-math.js.map +1 -1
- package/dist/prosekit-extensions-mention.js +1 -2
- package/dist/prosekit-extensions-mention.js.map +1 -1
- package/dist/prosekit-extensions-mod-click-prevention.js +1 -2
- package/dist/prosekit-extensions-mod-click-prevention.js.map +1 -1
- package/dist/prosekit-extensions-page.d.ts +114 -0
- package/dist/prosekit-extensions-page.d.ts.map +1 -0
- package/dist/prosekit-extensions-page.js +324 -0
- package/dist/prosekit-extensions-page.js.map +1 -0
- package/dist/prosekit-extensions-paragraph.d.ts.map +1 -1
- package/dist/prosekit-extensions-paragraph.js +3 -7
- package/dist/prosekit-extensions-paragraph.js.map +1 -1
- package/dist/prosekit-extensions-paste-rule.js +2 -3
- package/dist/prosekit-extensions-placeholder.js +2 -3
- package/dist/prosekit-extensions-placeholder.js.map +1 -1
- package/dist/prosekit-extensions-readonly.js +1 -2
- package/dist/prosekit-extensions-readonly.js.map +1 -1
- package/dist/prosekit-extensions-search.js +1 -2
- package/dist/prosekit-extensions-search.js.map +1 -1
- package/dist/prosekit-extensions-strike.js +1 -2
- package/dist/prosekit-extensions-strike.js.map +1 -1
- package/dist/prosekit-extensions-table.js +2 -3
- package/dist/prosekit-extensions-text-align.js +1 -2
- package/dist/prosekit-extensions-text-align.js.map +1 -1
- package/dist/prosekit-extensions-text-color.js +1 -4
- package/dist/prosekit-extensions-text-color.js.map +1 -1
- package/dist/prosekit-extensions-text.js +1 -2
- package/dist/prosekit-extensions-text.js.map +1 -1
- package/dist/prosekit-extensions-underline.js +1 -2
- package/dist/prosekit-extensions-underline.js.map +1 -1
- package/dist/prosekit-extensions-virtual-selection.js +1 -2
- package/dist/prosekit-extensions-virtual-selection.js.map +1 -1
- package/dist/prosekit-extensions-yjs.d.ts.map +1 -1
- package/dist/prosekit-extensions-yjs.js +3 -9
- package/dist/prosekit-extensions-yjs.js.map +1 -1
- package/dist/prosekit-extensions.js +1 -1
- package/dist/search/style.css +4 -8
- package/dist/shiki-highlighter-chunk.js +1 -2
- package/dist/shiki-highlighter-chunk.js.map +1 -1
- package/dist/table/style.css +16 -16
- package/dist/{table-B81i9oH9.js → table.js} +3 -15
- package/dist/table.js.map +1 -0
- package/dist/virtual-selection/style.css +1 -4
- package/dist/yjs/style.css +14 -20
- package/package.json +27 -14
- package/src/horizontal-rule/horizontal-rule-commands.ts +2 -1
- package/src/loro/loro.ts +3 -2
- package/src/page/index.ts +5 -0
- package/src/page/page-break-commands.spec.ts +61 -0
- package/src/page/page-break-commands.ts +41 -0
- package/src/page/page-break-keymap.ts +17 -0
- package/src/page/page-break-spec.ts +33 -0
- package/src/page/page-break.ts +23 -0
- package/src/page/page-element.ts +246 -0
- package/src/page/page-rendering.ts +164 -0
- package/src/page/style.css +43 -0
- package/src/paragraph/paragraph.ts +3 -2
- package/src/yjs/yjs.ts +3 -2
- package/dist/commit/style.css.map +0 -1
- package/dist/commit/style.js +0 -1
- package/dist/drop-indicator-DJq8pF92.js.map +0 -1
- package/dist/file-upload-I9m1EJAM.js.map +0 -1
- package/dist/file-upload-dr3IXUty.d.ts.map +0 -1
- package/dist/gap-cursor/style.css.map +0 -1
- package/dist/gap-cursor/style.js +0 -1
- package/dist/list/style.css.map +0 -1
- package/dist/list/style.js +0 -1
- package/dist/loro/style.css.map +0 -1
- package/dist/loro/style.js +0 -1
- package/dist/mark-paste-rule--F1QPUcU.js.map +0 -1
- package/dist/mark-rule-Bqdm49Eq.js.map +0 -1
- package/dist/placeholder/style.css.map +0 -1
- package/dist/placeholder/style.js +0 -1
- package/dist/search/style.css.map +0 -1
- package/dist/search/style.js +0 -1
- package/dist/shiki-highlighter-chunk.d.ts +0 -19
- package/dist/shiki-highlighter-chunk.d.ts.map +0 -1
- package/dist/table/style.css.map +0 -1
- package/dist/table/style.js +0 -1
- package/dist/table-B81i9oH9.js.map +0 -1
- package/dist/virtual-selection/style.css.map +0 -1
- package/dist/virtual-selection/style.js +0 -1
- package/dist/yjs/style.css.map +0 -1
- package/dist/yjs/style.js +0 -1
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"table.js","names":[],"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-cell.ts","../src/table/table-commands/select-table-column.ts","../src/table/table-commands/select-table-row.ts","../src/table/table-commands/select-table.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 { getNodeType, insertNode } 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, findCellPos, findCellRange, findTable } from 'prosemirror-tables'\n\nexport { findCellPos, findCellRange, findTable }\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 { CellSelection } from 'prosemirror-tables'\n\nimport { findCellPos } from '../table-utils.ts'\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.ts'\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.ts'\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 type { Command } from '@prosekit/pm/state'\nimport { CellSelection, TableMap } from 'prosemirror-tables'\n\nimport { findTable } from '../table-utils.ts'\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 { defineCommands, type Extension } 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.ts'\nimport { exitTable } from './table-commands/exit-table.ts'\nimport { insertTable, type InsertTableOptions } from './table-commands/insert-table.ts'\nimport { moveTableColumn, type MoveTableColumnOptions } from './table-commands/move-table-column.ts'\nimport { moveTableRow, type MoveTableRowOptions } from './table-commands/move-table-row.ts'\nimport { selectTableCell, type SelectTableCellOptions } from './table-commands/select-table-cell.ts'\nimport { selectTableColumn, type SelectTableColumnOptions } from './table-commands/select-table-column.ts'\nimport { selectTableRow, type SelectTableRowOptions } from './table-commands/select-table-row.ts'\nimport { selectTable, type SelectTableOptions } from './table-commands/select-table.ts'\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/index.ts'\nimport { defineDropIndicator } from '../drop-indicator/index.ts'\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 { definePlugin, type PlainExtension } from '@prosekit/core'\nimport { columnResizing, tableEditing } from 'prosemirror-tables'\n\n/**\n * @public\n */\nexport function defineTablePlugins(): PlainExtension {\n return definePlugin([tableEditing(), columnResizing()])\n}\n","import { defineNodeSpec, type Extension } from '@prosekit/core'\nimport type { AttributeSpec, Attrs } 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 { union, type Union } from '@prosekit/core'\n\nimport { defineTableCommands, type TableCommandsExtension } from './table-commands.ts'\nimport { defineTableDropIndicator } from './table-drop-indicator.ts'\nimport { defineTablePlugins } from './table-plugins.ts'\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.ts'\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/dist/yjs/style.css
CHANGED
|
@@ -1,34 +1,28 @@
|
|
|
1
1
|
.ProseMirror .ProseMirror-yjs-cursor {
|
|
2
|
-
position: absolute;
|
|
3
|
-
height: 1em;
|
|
4
|
-
border-left: black;
|
|
5
|
-
border-left-width: 2px;
|
|
6
|
-
border-left-style: solid;
|
|
7
|
-
border-color: orange;
|
|
8
2
|
word-break: normal;
|
|
9
3
|
pointer-events: none;
|
|
4
|
+
border-color: orange;
|
|
5
|
+
border-left-style: solid;
|
|
6
|
+
border-left-width: 2px;
|
|
7
|
+
height: 1em;
|
|
8
|
+
position: absolute;
|
|
10
9
|
}
|
|
11
10
|
|
|
12
11
|
.ProseMirror .ProseMirror-yjs-cursor > div {
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
12
|
+
color: #fff;
|
|
13
|
+
user-select: none;
|
|
14
|
+
background-color: #fa8100;
|
|
16
15
|
padding-left: 2px;
|
|
17
|
-
|
|
18
|
-
|
|
16
|
+
padding-right: 2px;
|
|
17
|
+
font-family: serif;
|
|
18
|
+
font-size: 13px;
|
|
19
19
|
font-style: normal;
|
|
20
20
|
font-weight: normal;
|
|
21
|
-
font-size: 13px;
|
|
22
21
|
line-height: normal;
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
-moz-user-select: none;
|
|
26
|
-
-ms-user-select: none;
|
|
27
|
-
user-select: none;
|
|
22
|
+
position: relative;
|
|
23
|
+
top: -1.05em;
|
|
28
24
|
}
|
|
25
|
+
|
|
29
26
|
.ProseMirror > .ProseMirror-yjs-cursor:first-child {
|
|
30
27
|
margin-top: 16px;
|
|
31
28
|
}
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
/*# sourceMappingURL=style.css.map*/
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@prosekit/extensions",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "0.
|
|
4
|
+
"version": "0.16.0-beta.0",
|
|
5
5
|
"private": false,
|
|
6
6
|
"description": "A collection of common extensions for ProseKit",
|
|
7
7
|
"author": {
|
|
@@ -146,6 +146,13 @@
|
|
|
146
146
|
"types": "./dist/prosekit-extensions-mod-click-prevention.d.ts",
|
|
147
147
|
"default": "./dist/prosekit-extensions-mod-click-prevention.js"
|
|
148
148
|
},
|
|
149
|
+
"./page": {
|
|
150
|
+
"types": "./dist/prosekit-extensions-page.d.ts",
|
|
151
|
+
"default": "./dist/prosekit-extensions-page.js"
|
|
152
|
+
},
|
|
153
|
+
"./page/style.css": {
|
|
154
|
+
"default": "./dist/page/style.css"
|
|
155
|
+
},
|
|
149
156
|
"./paragraph": {
|
|
150
157
|
"types": "./dist/prosekit-extensions-paragraph.d.ts",
|
|
151
158
|
"default": "./dist/prosekit-extensions-paragraph.js"
|
|
@@ -219,19 +226,20 @@
|
|
|
219
226
|
"src"
|
|
220
227
|
],
|
|
221
228
|
"dependencies": {
|
|
222
|
-
"@ocavue/utils": "^1.
|
|
229
|
+
"@ocavue/utils": "^1.6.0",
|
|
223
230
|
"prosemirror-changeset": "^2.4.0",
|
|
224
231
|
"prosemirror-drop-indicator": "^0.1.3",
|
|
225
232
|
"prosemirror-dropcursor": "^1.8.2",
|
|
226
233
|
"prosemirror-enter-rules": "^0.1.5",
|
|
227
234
|
"prosemirror-flat-list": "^0.5.8",
|
|
228
|
-
"prosemirror-gapcursor": "^1.4.
|
|
229
|
-
"prosemirror-highlight": "^0.15.
|
|
235
|
+
"prosemirror-gapcursor": "^1.4.1",
|
|
236
|
+
"prosemirror-highlight": "^0.15.1",
|
|
230
237
|
"prosemirror-math": "^0.2.2",
|
|
231
238
|
"prosemirror-search": "^1.1.0",
|
|
232
239
|
"prosemirror-tables": "^1.8.5",
|
|
233
|
-
"
|
|
234
|
-
"
|
|
240
|
+
"server-dom-shim": "^1.1.0",
|
|
241
|
+
"shiki": "^3.22.0 || ^4.0.0",
|
|
242
|
+
"@prosekit/core": "^0.12.0-beta.0",
|
|
235
243
|
"@prosekit/pm": "^0.1.15"
|
|
236
244
|
},
|
|
237
245
|
"peerDependencies": {
|
|
@@ -257,8 +265,8 @@
|
|
|
257
265
|
"devDependencies": {
|
|
258
266
|
"diffable-html-snapshot": "^0.2.0",
|
|
259
267
|
"just-pick": "^4.2.0",
|
|
260
|
-
"katex": "^0.16.
|
|
261
|
-
"loro-crdt": "^1.10.
|
|
268
|
+
"katex": "^0.16.40",
|
|
269
|
+
"loro-crdt": "^1.10.8",
|
|
262
270
|
"loro-prosemirror": "^0.4.3",
|
|
263
271
|
"rehype-parse": "^9.0.1",
|
|
264
272
|
"rehype-remark": "^10.0.1",
|
|
@@ -266,17 +274,17 @@
|
|
|
266
274
|
"remark-html": "^16.0.1",
|
|
267
275
|
"remark-parse": "^11.0.0",
|
|
268
276
|
"remark-stringify": "^11.0.0",
|
|
269
|
-
"tsdown": "^0.
|
|
270
|
-
"type-fest": "^5.
|
|
277
|
+
"tsdown": "^0.21.4",
|
|
278
|
+
"type-fest": "^5.5.0",
|
|
271
279
|
"typescript": "~5.9.3",
|
|
272
280
|
"unified": "^11.0.5",
|
|
273
|
-
"vitest": "^4.1.
|
|
281
|
+
"vitest": "^4.1.1",
|
|
274
282
|
"vitest-browser-commands": "^0.2.0",
|
|
275
283
|
"y-prosemirror": "^1.3.7",
|
|
276
|
-
"yjs": "^13.6.
|
|
284
|
+
"yjs": "^13.6.30",
|
|
285
|
+
"@prosekit/config-ts": "0.0.0",
|
|
277
286
|
"@prosekit/config-tsdown": "0.0.0",
|
|
278
|
-
"@prosekit/config-vitest": "0.0.0"
|
|
279
|
-
"@prosekit/config-ts": "0.0.0"
|
|
287
|
+
"@prosekit/config-vitest": "0.0.0"
|
|
280
288
|
},
|
|
281
289
|
"publishConfig": {
|
|
282
290
|
"dev": {}
|
|
@@ -314,6 +322,8 @@
|
|
|
314
322
|
"prosekit-extensions-math": "./src/math/index.ts",
|
|
315
323
|
"prosekit-extensions-mention": "./src/mention/index.ts",
|
|
316
324
|
"prosekit-extensions-mod-click-prevention": "./src/mod-click-prevention/index.ts",
|
|
325
|
+
"prosekit-extensions-page": "./src/page/index.ts",
|
|
326
|
+
"page/style": "./src/page/style.css",
|
|
317
327
|
"prosekit-extensions-paragraph": "./src/paragraph/index.ts",
|
|
318
328
|
"prosekit-extensions-paste-rule": "./src/paste-rule/index.ts",
|
|
319
329
|
"prosekit-extensions-placeholder": "./src/placeholder/index.ts",
|
|
@@ -422,6 +432,9 @@
|
|
|
422
432
|
"mod-click-prevention": [
|
|
423
433
|
"./dist/prosekit-extensions-mod-click-prevention.d.ts"
|
|
424
434
|
],
|
|
435
|
+
"page": [
|
|
436
|
+
"./dist/prosekit-extensions-page.d.ts"
|
|
437
|
+
],
|
|
425
438
|
"paragraph": [
|
|
426
439
|
"./dist/prosekit-extensions-paragraph.d.ts"
|
|
427
440
|
],
|
|
@@ -15,7 +15,8 @@ const insertHorizontalRuleCommand: Command = (state, dispatch): boolean => {
|
|
|
15
15
|
const type = getNodeType(schema, 'horizontalRule')
|
|
16
16
|
const node = type.createChecked()
|
|
17
17
|
const pos = tr.selection.anchor
|
|
18
|
-
|
|
18
|
+
const slice = new Slice(Fragment.from(node), 0, 0)
|
|
19
|
+
tr.replaceRange(pos, pos, slice).scrollIntoView()
|
|
19
20
|
dispatch(tr)
|
|
20
21
|
return true
|
|
21
22
|
}
|
package/src/loro/loro.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import { Priority
|
|
1
|
+
import type { Priority } from '@prosekit/core'
|
|
2
|
+
import { union, withPriority, type PlainExtension, type Union } from '@prosekit/core'
|
|
2
3
|
import type {
|
|
3
4
|
CursorAwareness,
|
|
4
5
|
CursorEphemeralStore,
|
|
@@ -65,6 +66,6 @@ export function defineLoro(options: LoroOptions): LoroExtension {
|
|
|
65
66
|
defineLoroUndoPlugin({ ...undo, doc }),
|
|
66
67
|
defineLoroSyncPlugin({ ...sync, doc }),
|
|
67
68
|
]),
|
|
68
|
-
Priority.high,
|
|
69
|
+
3 satisfies typeof Priority.high,
|
|
69
70
|
)
|
|
70
71
|
}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export { definePageBreakCommands, insertPageBreak, type PageBreakCommandsExtension } from './page-break-commands.ts'
|
|
2
|
+
export { definePageBreakKeymap, type PageBreakKeymapExtension } from './page-break-keymap.ts'
|
|
3
|
+
export { definePageBreakSpec, type PageBreakSpecExtension } from './page-break-spec.ts'
|
|
4
|
+
export { definePageBreak, type PageBreakExtension } from './page-break.ts'
|
|
5
|
+
export { definePageRendering, type PageRenderingExtension, type PageRenderingOptions } from './page-rendering.ts'
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { union } from '@prosekit/core'
|
|
2
|
+
import { describe, expect, it } from 'vitest'
|
|
3
|
+
|
|
4
|
+
import { defineDoc } from '../doc/index.ts'
|
|
5
|
+
import { defineHardBreak } from '../hard-break/index.ts'
|
|
6
|
+
import { defineHorizontalRule } from '../horizontal-rule/index.ts'
|
|
7
|
+
import { defineParagraph } from '../paragraph/index.ts'
|
|
8
|
+
import { setupTestFromExtension } from '../testing/index.ts'
|
|
9
|
+
import { defineText } from '../text/index.ts'
|
|
10
|
+
|
|
11
|
+
import { definePageBreak } from './page-break.ts'
|
|
12
|
+
|
|
13
|
+
function setup() {
|
|
14
|
+
const extension = union(
|
|
15
|
+
defineDoc(),
|
|
16
|
+
defineText(),
|
|
17
|
+
defineParagraph(),
|
|
18
|
+
defineHorizontalRule(),
|
|
19
|
+
defineHardBreak(),
|
|
20
|
+
definePageBreak(),
|
|
21
|
+
)
|
|
22
|
+
return setupTestFromExtension(extension)
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
describe('insertPageBreak', () => {
|
|
26
|
+
it('should insert a page break in an empty paragraph', () => {
|
|
27
|
+
const { editor, n } = setup()
|
|
28
|
+
editor.set(n.doc(n.paragraph('<a>')))
|
|
29
|
+
editor.commands.insertPageBreak()
|
|
30
|
+
expect(editor.view.state.doc.toJSON()).toEqual(
|
|
31
|
+
n.doc(n.pageBreak()).toJSON(),
|
|
32
|
+
)
|
|
33
|
+
})
|
|
34
|
+
|
|
35
|
+
it('should insert a page break after text', () => {
|
|
36
|
+
const { editor, n } = setup()
|
|
37
|
+
editor.set(n.doc(n.paragraph('hello<a>')))
|
|
38
|
+
editor.commands.insertPageBreak()
|
|
39
|
+
expect(editor.view.state.doc.toJSON()).toEqual(
|
|
40
|
+
n.doc(n.paragraph('hello'), n.pageBreak()).toJSON(),
|
|
41
|
+
)
|
|
42
|
+
})
|
|
43
|
+
|
|
44
|
+
it('should insert a page break before text', () => {
|
|
45
|
+
const { editor, n } = setup()
|
|
46
|
+
editor.set(n.doc(n.paragraph('<a>hello')))
|
|
47
|
+
editor.commands.insertPageBreak()
|
|
48
|
+
expect(editor.view.state.doc.toJSON()).toEqual(
|
|
49
|
+
n.doc(n.pageBreak(), n.paragraph('hello')).toJSON(),
|
|
50
|
+
)
|
|
51
|
+
})
|
|
52
|
+
|
|
53
|
+
it('should insert a page break between text', () => {
|
|
54
|
+
const { editor, n } = setup()
|
|
55
|
+
editor.set(n.doc(n.paragraph('hel<a>lo')))
|
|
56
|
+
editor.commands.insertPageBreak()
|
|
57
|
+
expect(editor.view.state.doc.toJSON()).toEqual(
|
|
58
|
+
n.doc(n.paragraph('hel'), n.pageBreak(), n.paragraph('lo')).toJSON(),
|
|
59
|
+
)
|
|
60
|
+
})
|
|
61
|
+
})
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { defineCommands, getNodeType, type Extension } from '@prosekit/core'
|
|
2
|
+
import { Fragment, Slice } from '@prosekit/pm/model'
|
|
3
|
+
import type { Command } from '@prosekit/pm/state'
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* @internal
|
|
7
|
+
*/
|
|
8
|
+
export type PageBreakCommandsExtension = Extension<{
|
|
9
|
+
Commands: {
|
|
10
|
+
insertPageBreak: []
|
|
11
|
+
}
|
|
12
|
+
}>
|
|
13
|
+
|
|
14
|
+
const insertPageBreakCommand: Command = (state, dispatch): boolean => {
|
|
15
|
+
if (!dispatch) return true
|
|
16
|
+
|
|
17
|
+
const { schema, tr } = state
|
|
18
|
+
const type = getNodeType(schema, 'pageBreak')
|
|
19
|
+
const node = type.createChecked()
|
|
20
|
+
const pos = tr.selection.anchor
|
|
21
|
+
const slice = new Slice(Fragment.from(node), 0, 0)
|
|
22
|
+
tr.replaceRange(pos, pos, slice).scrollIntoView()
|
|
23
|
+
dispatch(tr)
|
|
24
|
+
return true
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* @internal
|
|
29
|
+
*/
|
|
30
|
+
export function insertPageBreak(): Command {
|
|
31
|
+
return insertPageBreakCommand
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* @internal
|
|
36
|
+
*/
|
|
37
|
+
export function definePageBreakCommands(): PageBreakCommandsExtension {
|
|
38
|
+
return defineCommands({
|
|
39
|
+
insertPageBreak: insertPageBreak,
|
|
40
|
+
})
|
|
41
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { defineKeymap, type PlainExtension } from '@prosekit/core'
|
|
2
|
+
|
|
3
|
+
import { insertPageBreak } from './page-break-commands.ts'
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* @internal
|
|
7
|
+
*/
|
|
8
|
+
export type PageBreakKeymapExtension = PlainExtension
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* @internal
|
|
12
|
+
*/
|
|
13
|
+
export function definePageBreakKeymap(): PageBreakKeymapExtension {
|
|
14
|
+
return defineKeymap({
|
|
15
|
+
'Mod-Enter': insertPageBreak(),
|
|
16
|
+
})
|
|
17
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { defineNodeSpec, type Extension } from '@prosekit/core'
|
|
2
|
+
import type { Attrs } from '@prosekit/pm/model'
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* @internal
|
|
6
|
+
*/
|
|
7
|
+
export type PageBreakSpecExtension = Extension<{
|
|
8
|
+
Nodes: {
|
|
9
|
+
pageBreak: Attrs
|
|
10
|
+
}
|
|
11
|
+
}>
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* @internal
|
|
15
|
+
*/
|
|
16
|
+
export function definePageBreakSpec(): PageBreakSpecExtension {
|
|
17
|
+
return defineNodeSpec({
|
|
18
|
+
name: 'pageBreak',
|
|
19
|
+
group: 'block',
|
|
20
|
+
selectable: true,
|
|
21
|
+
parseDOM: [{ tag: 'div.prosekit-page-break' }],
|
|
22
|
+
toDOM() {
|
|
23
|
+
return ['div', { class: 'prosekit-horizontal-rule prosekit-page-break' }, ['hr']]
|
|
24
|
+
},
|
|
25
|
+
pageBreak: true,
|
|
26
|
+
})
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
declare module '@prosekit/pm/model' {
|
|
30
|
+
interface NodeSpec {
|
|
31
|
+
pageBreak?: boolean | undefined
|
|
32
|
+
}
|
|
33
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { union, type Union } from '@prosekit/core'
|
|
2
|
+
|
|
3
|
+
import { definePageBreakCommands, type PageBreakCommandsExtension } from './page-break-commands.ts'
|
|
4
|
+
import { definePageBreakKeymap, type PageBreakKeymapExtension } from './page-break-keymap.ts'
|
|
5
|
+
import { definePageBreakSpec, type PageBreakSpecExtension } from './page-break-spec.ts'
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* @internal
|
|
9
|
+
*/
|
|
10
|
+
export type PageBreakExtension = Union<
|
|
11
|
+
[PageBreakSpecExtension, PageBreakCommandsExtension, PageBreakKeymapExtension]
|
|
12
|
+
>
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* @public
|
|
16
|
+
*/
|
|
17
|
+
export function definePageBreak(): PageBreakExtension {
|
|
18
|
+
return union(
|
|
19
|
+
definePageBreakSpec(),
|
|
20
|
+
definePageBreakCommands(),
|
|
21
|
+
definePageBreakKeymap(),
|
|
22
|
+
)
|
|
23
|
+
}
|
|
@@ -0,0 +1,246 @@
|
|
|
1
|
+
import { once } from '@ocavue/utils'
|
|
2
|
+
import { customElements, HTMLElement } from 'server-dom-shim'
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* @internal
|
|
6
|
+
*/
|
|
7
|
+
export const PAGE_CHUNK_TAG_NAME = 'pm-page-chunk'
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* @internal
|
|
11
|
+
*/
|
|
12
|
+
export const registerPageChunkElement: VoidFunction = /* @__PURE__ */ once(() => {
|
|
13
|
+
if (typeof window === 'undefined' || customElements.get(PAGE_CHUNK_TAG_NAME)) return
|
|
14
|
+
customElements.define(PAGE_CHUNK_TAG_NAME, PageChunkElement)
|
|
15
|
+
})
|
|
16
|
+
|
|
17
|
+
class PageChunkElement extends HTMLElement {
|
|
18
|
+
static observedAttributes = [
|
|
19
|
+
'data-group',
|
|
20
|
+
'data-break',
|
|
21
|
+
'data-h',
|
|
22
|
+
'data-mt',
|
|
23
|
+
'data-mb',
|
|
24
|
+
|
|
25
|
+
// Only the first chunk of the whole document has this attribute.
|
|
26
|
+
'data-size',
|
|
27
|
+
]
|
|
28
|
+
|
|
29
|
+
// Data attributes set by external code
|
|
30
|
+
#group: string = ''
|
|
31
|
+
#forceNextBreak: boolean = false
|
|
32
|
+
#pageHeight: number = 0
|
|
33
|
+
#pageMarginTop: number = 0
|
|
34
|
+
#pageMarginBottom: number = 0
|
|
35
|
+
#size: number | undefined = undefined
|
|
36
|
+
|
|
37
|
+
// Internal states
|
|
38
|
+
#updateRequested: boolean = false
|
|
39
|
+
#contentBoxHeight: number = 0
|
|
40
|
+
|
|
41
|
+
// Rendering states
|
|
42
|
+
#isHead: boolean = false
|
|
43
|
+
#isTail: boolean = false
|
|
44
|
+
#paddingTop: number = 0
|
|
45
|
+
#paddingBottom: number = 0
|
|
46
|
+
|
|
47
|
+
// Pending rendering states
|
|
48
|
+
#isHeadPending: boolean = false
|
|
49
|
+
#isTailPending: boolean = false
|
|
50
|
+
#paddingTopPending: number = 0
|
|
51
|
+
#paddingBottomPending: number = 0
|
|
52
|
+
|
|
53
|
+
connectedCallback() {
|
|
54
|
+
this.#parseDataAttributes()
|
|
55
|
+
|
|
56
|
+
if (this.#isLeader()) {
|
|
57
|
+
this.#isHeadPending = true
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
this.#render()
|
|
61
|
+
|
|
62
|
+
// Get the initial content box height when the resize observer is not started yet. Notice that
|
|
63
|
+
// `this.clientHeight` is an integer while the content box height can be a float, so this is not
|
|
64
|
+
// accurate but should be good enough for the first render.
|
|
65
|
+
this.#contentBoxHeight = this.clientHeight - this.#paddingTop - this.#paddingBottom
|
|
66
|
+
|
|
67
|
+
observeElement(this)
|
|
68
|
+
|
|
69
|
+
this.#requestUpdate()
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
disconnectedCallback() {
|
|
73
|
+
unobserveElement(this)
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
attributeChangedCallback(_: string, oldValue: string | null, newValue: string | null) {
|
|
77
|
+
if (oldValue === newValue) return
|
|
78
|
+
this.#parseDataAttributes()
|
|
79
|
+
this.#requestUpdate()
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
#parseDataAttributes() {
|
|
83
|
+
this.#group = this.getAttribute('data-group') || ''
|
|
84
|
+
this.#forceNextBreak = this.hasAttribute('data-break')
|
|
85
|
+
this.#pageHeight = this.#parseFloatAttribute('data-h')
|
|
86
|
+
this.#pageMarginTop = this.#parseFloatAttribute('data-mt')
|
|
87
|
+
this.#pageMarginBottom = this.#parseFloatAttribute('data-mb')
|
|
88
|
+
|
|
89
|
+
const sizeAttr = this.getAttribute('data-size')
|
|
90
|
+
this.#size = sizeAttr ? Number.parseInt(sizeAttr, 10) : undefined
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
#parseFloatAttribute(name: string): number {
|
|
94
|
+
const value = this.getAttribute(name)
|
|
95
|
+
return value != null ? Number.parseFloat(value) : 0
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
#isLeader() {
|
|
99
|
+
return this.#size != null
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
#render() {
|
|
103
|
+
if (this.#paddingTop !== this.#paddingTopPending || this.#paddingBottom !== this.#paddingBottomPending) {
|
|
104
|
+
Object.assign(this.style, {
|
|
105
|
+
paddingTop: `${this.#paddingTop = this.#paddingTopPending}px`,
|
|
106
|
+
paddingBottom: `${this.#paddingBottom = this.#paddingBottomPending}px`,
|
|
107
|
+
})
|
|
108
|
+
}
|
|
109
|
+
if (this.#isHead !== this.#isHeadPending) {
|
|
110
|
+
this.toggleAttribute('data-page-head', this.#isHead = this.#isHeadPending)
|
|
111
|
+
}
|
|
112
|
+
if (this.#isTail !== this.#isTailPending) {
|
|
113
|
+
this.toggleAttribute('data-page-tail', this.#isTail = this.#isTailPending)
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
setHeight(height: number) {
|
|
118
|
+
// Avoid potential float number precision issues
|
|
119
|
+
if (Math.abs(this.#contentBoxHeight - height) < 0.1) {
|
|
120
|
+
return
|
|
121
|
+
}
|
|
122
|
+
this.#contentBoxHeight = height
|
|
123
|
+
this.#requestUpdate()
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Schedules a batched page layout recalculation.
|
|
128
|
+
*
|
|
129
|
+
* Any chunk can call this method, but the actual layout work (#updateAll)
|
|
130
|
+
* always runs on the leader chunk, because it needs to iterate over every
|
|
131
|
+
* chunk in order to compute page breaks.
|
|
132
|
+
*
|
|
133
|
+
* Two nested microtasks are used to batch updates:
|
|
134
|
+
*
|
|
135
|
+
* Microtask 1 – Delegation: non-leader chunks forward the request to the
|
|
136
|
+
* leader chunk, so multiple chunks changing in the same tick only trigger
|
|
137
|
+
* one layout pass.
|
|
138
|
+
*
|
|
139
|
+
* Microtask 2 – Execution: the leader chunk defers #updateAll to a second
|
|
140
|
+
* microtask so that any other attribute / resize changes that were queued
|
|
141
|
+
* in the same tick (and forwarded during microtask 1) are already reflected
|
|
142
|
+
* before the layout is recalculated.
|
|
143
|
+
*
|
|
144
|
+
* The #updateRequested flag acts as a deduplication guard so that rapid
|
|
145
|
+
* successive calls (e.g. multiple attributes changing at once) result in at
|
|
146
|
+
* most one scheduled pass per chunk.
|
|
147
|
+
*/
|
|
148
|
+
#requestUpdate() {
|
|
149
|
+
if (this.#updateRequested) {
|
|
150
|
+
return
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
this.#updateRequested = true
|
|
154
|
+
queueMicrotask(() => {
|
|
155
|
+
if (!this.#isLeader()) {
|
|
156
|
+
this.#updateRequested = false
|
|
157
|
+
const leader = findLeaderChunk(this, this.#group)
|
|
158
|
+
if (!leader) return
|
|
159
|
+
leader.#requestUpdate()
|
|
160
|
+
return
|
|
161
|
+
}
|
|
162
|
+
queueMicrotask(() => {
|
|
163
|
+
this.#updateRequested = false
|
|
164
|
+
this.#updateAll()
|
|
165
|
+
})
|
|
166
|
+
})
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
#updateAll() {
|
|
170
|
+
if (!this.isConnected) {
|
|
171
|
+
return
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
const elements = findAllChunks(this, this.#group)
|
|
175
|
+
const count = elements.length
|
|
176
|
+
if (count === 0) return
|
|
177
|
+
|
|
178
|
+
const pageHeight = this.#pageHeight
|
|
179
|
+
const pageMarginTop = this.#pageMarginTop
|
|
180
|
+
const maxContentHeight = pageHeight - pageMarginTop - this.#pageMarginBottom
|
|
181
|
+
|
|
182
|
+
let currentContentHeight = 0
|
|
183
|
+
let forceNextBreak = false
|
|
184
|
+
|
|
185
|
+
for (let i = 0; i < count; i++) {
|
|
186
|
+
const element = elements[i]
|
|
187
|
+
const h = element.#contentBoxHeight
|
|
188
|
+
const isHead = forceNextBreak || i === 0 || (currentContentHeight + h > maxContentHeight)
|
|
189
|
+
|
|
190
|
+
forceNextBreak = element.#forceNextBreak
|
|
191
|
+
|
|
192
|
+
if (isHead && i > 0) {
|
|
193
|
+
const prev = elements[i - 1]
|
|
194
|
+
prev.#paddingBottomPending = Math.max(0, pageHeight - pageMarginTop - currentContentHeight)
|
|
195
|
+
prev.#isTailPending = true
|
|
196
|
+
currentContentHeight = h
|
|
197
|
+
} else {
|
|
198
|
+
currentContentHeight += h
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
element.#paddingTopPending = isHead ? pageMarginTop : 0
|
|
202
|
+
element.#paddingBottomPending = 0
|
|
203
|
+
element.#isTailPending = false
|
|
204
|
+
element.#isHeadPending = isHead
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
const last = elements[count - 1]
|
|
208
|
+
last.#paddingBottomPending = Math.max(0, pageHeight - pageMarginTop - currentContentHeight)
|
|
209
|
+
last.#isTailPending = true
|
|
210
|
+
|
|
211
|
+
for (const element of elements) {
|
|
212
|
+
element.#render()
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
function handleResize(entries: ResizeObserverEntry[]) {
|
|
218
|
+
for (const entry of entries) {
|
|
219
|
+
const contentBoxHeight = entry.contentBoxSize?.[0]?.blockSize ?? entry.contentRect.height
|
|
220
|
+
const element = entry.target as PageChunkElement
|
|
221
|
+
element.setHeight(contentBoxHeight)
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
const getResizeObserver = /* @__PURE__ */ once(() => {
|
|
226
|
+
return new ResizeObserver(handleResize)
|
|
227
|
+
})
|
|
228
|
+
|
|
229
|
+
function observeElement(element: PageChunkElement) {
|
|
230
|
+
getResizeObserver().observe(element)
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
function unobserveElement(element: PageChunkElement) {
|
|
234
|
+
getResizeObserver().unobserve(element)
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
function findLeaderChunk(element: HTMLElement, group: string): PageChunkElement | null | undefined {
|
|
238
|
+
const root = element.closest('.ProseMirror')
|
|
239
|
+
return root?.querySelector<PageChunkElement>(`${PAGE_CHUNK_TAG_NAME}[data-group="${group}"][data-size]`)
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
function findAllChunks(element: HTMLElement, group: string): PageChunkElement[] {
|
|
243
|
+
const root = element.closest('.ProseMirror')
|
|
244
|
+
const elements = root?.querySelectorAll<PageChunkElement>(`${PAGE_CHUNK_TAG_NAME}[data-group="${group}"]`)
|
|
245
|
+
return Array.from(elements || [])
|
|
246
|
+
}
|