@trafica/editor 1.0.15 → 1.0.17
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.mts +21 -13
- package/dist/index.d.ts +21 -13
- package/dist/index.js +343 -43
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +343 -44
- package/dist/index.mjs.map +1 -1
- package/dist/styles/editor.css +86 -0
- package/package.json +1 -1
package/dist/index.d.mts
CHANGED
|
@@ -806,25 +806,33 @@ declare function execCommand(engine: EditorEngineInterface, command: string, ...
|
|
|
806
806
|
declare function registerCommand(name: string, fn: CommandFn): void;
|
|
807
807
|
|
|
808
808
|
/**
|
|
809
|
-
* PastePlugin
|
|
809
|
+
* PastePlugin / ClipboardPlugin
|
|
810
810
|
*
|
|
811
|
-
*
|
|
812
|
-
* converts it to our document model, and inserts it at the current cursor.
|
|
811
|
+
* CKEditor-style clipboard pipeline:
|
|
813
812
|
*
|
|
814
|
-
*
|
|
815
|
-
*
|
|
816
|
-
* - Normalize formatting (convert <b> → mark, etc.)
|
|
817
|
-
* - Reject unknown elements (fall back to plain text)
|
|
813
|
+
* Copy/Cut — intercept event, serialize selection from model to clean HTML,
|
|
814
|
+
* write text/html + text/plain to clipboard. Cut also deletes selection.
|
|
818
815
|
*
|
|
819
|
-
*
|
|
820
|
-
*
|
|
821
|
-
*
|
|
816
|
+
* Paste — normalize HTML from external sources (Word, Google Docs, web),
|
|
817
|
+
* deserialize to model, merge at cursor with block-split/merge logic.
|
|
818
|
+
*
|
|
819
|
+
* Pipeline stages:
|
|
820
|
+
* 1. normalizeHTML — strip MSO/GDocs junk, preserve useful inline styles
|
|
821
|
+
* 2. deserialize — HTML → model (BlockNode[])
|
|
822
|
+
* 3. mergeAtCursor — split current block, insert pasted blocks, merge edges
|
|
823
|
+
* 4. set cursor — place after last pasted character
|
|
824
|
+
*
|
|
825
|
+
* Never uses document.execCommand. Uses Clipboard API, Selection API, DOM APIs only.
|
|
822
826
|
*/
|
|
823
827
|
|
|
824
828
|
declare function createPastePlugin(): EditorPlugin;
|
|
825
829
|
/**
|
|
826
|
-
* Attach paste
|
|
827
|
-
*
|
|
830
|
+
* Attach copy, cut, and paste handlers to the editor container.
|
|
831
|
+
* Returns a cleanup function that removes all listeners.
|
|
832
|
+
*/
|
|
833
|
+
declare function attachClipboardHandlers(container: HTMLElement, engine: EditorEngineInterface): () => void;
|
|
834
|
+
/**
|
|
835
|
+
* Attach only the paste handler (kept for backward compat).
|
|
828
836
|
*/
|
|
829
837
|
declare function attachPasteHandler(container: HTMLElement, engine: EditorEngineInterface): () => void;
|
|
830
838
|
|
|
@@ -971,4 +979,4 @@ declare const DEFAULT_TOOLBAR: EditorToolbarItem[];
|
|
|
971
979
|
declare const MINIMAL_TOOLBAR: EditorToolbarItem[];
|
|
972
980
|
declare const BASIC_TOOLBAR: EditorToolbarItem[];
|
|
973
981
|
|
|
974
|
-
export { type AddMarkStep, type AlignmentType, BASIC_TOOLBAR, type BlockNode, type BlockNodeType, type CellPosition, type Command, DEFAULT_TOOLBAR, type DeleteNodeStep, type DeleteRangeStep, type DeleteTextStep, type Document, Editor, type EditorAPI, EditorCore, type EditorCoreProps, EditorEngine, type EditorEngineInterface, type EditorNode, type EditorPlugin, type EditorProps, type EditorSelection, type EditorState, type EditorToolbarConfig, type EditorToolbarItem, type InlineNodeType, type InsertNodeStep, type InsertTextStep, type JoinBlocksStep, MINIMAL_TOOLBAR, type Mark, type MarkType, type NodeAttrs, type NodePosition, type NodeType, type RemoveMarkStep, type ReplaceDocStep, type SearchMatch, type SearchOptions, type Serializer, type SetMarkStep, type SetNodeAttrsStep, type SetNodeTypeStep, type SetSelectionStep, type SplitBlockStep, type StateListener, type Step, type StepType, type TableDimensions, TablePlugin, type TextNode, type ThemeConfig, type ThemeMode, type ThemeTokens, Toolbar, type ToolbarItem, type Transaction, addColumnLeft, addColumnRight, addMarkToNode, addRowAbove, addRowBelow, applyMarkToRange, applySearchHighlights, applyTransaction, attachPasteHandler, captureSelection, clearSearchHighlights, collectText, comparePaths, comparePositions, createEmptyDocument, createHistoryManager, createHistoryPlugin, createParagraph, createPastePlugin, createTableCell, createTableNode, createTransaction, deleteColumn, deleteImageAtPath, deleteRange, deleteRow, deleteTable, deleteTableColumn, deleteTableRow, deleteTextAtPath, execCommand, findCellPosition, findContentBlockPath, findMatches, findTablePath, getActiveAlignment, getActiveBlockType, getActiveFontFamily, getActiveFontSize, getActiveHighlightColor, getActiveLinkHref, getActiveLinkRange, getActiveMarks, getActiveTextColor, getCellFirstPosition, getCellLastPosition, getDocumentLength, getDocumentMarkAttrValues, getNodeAtPath, getTableDimensions, getTextNodesBetween, htmlSerializer, insertImage, insertLink, insertTable, insertTableColumnAfter, insertTableColumnBefore, insertTableRowAfter, insertTableRowBefore, insertText, insertTextAtPath, isBlockNode, isContainerBlock, isSelectionInContainer, isTextNode, joinBlocks, jsonSerializer, makeCollapsedSelection, makePosition, markdownSerializer, marksEqual, mergeAdjacentTextNodesInDoc, mergeCells, mergeTableCells, normalizeRange, redo, registerCommand, removeMarkFromNode, removeMarkFromRange, renderDocument, replaceAllMatches, replaceMatch, restoreSelection, scrollMatchIntoView, setAlignment, setBlockType, setCodeBlockLanguage, setColumnWidth, setFontFamily, setFontSize, setHighlightColor, setImageAttr, setMarkOnRange, setTextColor, splitBlock, splitCell, splitTableCell, toggleCheckItemAt, toggleHeaderRow, toggleMark, undo, updateColumnWidth, useEditorEngine, useEditorState, walkDocument };
|
|
982
|
+
export { type AddMarkStep, type AlignmentType, BASIC_TOOLBAR, type BlockNode, type BlockNodeType, type CellPosition, type Command, DEFAULT_TOOLBAR, type DeleteNodeStep, type DeleteRangeStep, type DeleteTextStep, type Document, Editor, type EditorAPI, EditorCore, type EditorCoreProps, EditorEngine, type EditorEngineInterface, type EditorNode, type EditorPlugin, type EditorProps, type EditorSelection, type EditorState, type EditorToolbarConfig, type EditorToolbarItem, type InlineNodeType, type InsertNodeStep, type InsertTextStep, type JoinBlocksStep, MINIMAL_TOOLBAR, type Mark, type MarkType, type NodeAttrs, type NodePosition, type NodeType, type RemoveMarkStep, type ReplaceDocStep, type SearchMatch, type SearchOptions, type Serializer, type SetMarkStep, type SetNodeAttrsStep, type SetNodeTypeStep, type SetSelectionStep, type SplitBlockStep, type StateListener, type Step, type StepType, type TableDimensions, TablePlugin, type TextNode, type ThemeConfig, type ThemeMode, type ThemeTokens, Toolbar, type ToolbarItem, type Transaction, addColumnLeft, addColumnRight, addMarkToNode, addRowAbove, addRowBelow, applyMarkToRange, applySearchHighlights, applyTransaction, attachClipboardHandlers, attachPasteHandler, captureSelection, clearSearchHighlights, collectText, comparePaths, comparePositions, createEmptyDocument, createHistoryManager, createHistoryPlugin, createParagraph, createPastePlugin, createTableCell, createTableNode, createTransaction, deleteColumn, deleteImageAtPath, deleteRange, deleteRow, deleteTable, deleteTableColumn, deleteTableRow, deleteTextAtPath, execCommand, findCellPosition, findContentBlockPath, findMatches, findTablePath, getActiveAlignment, getActiveBlockType, getActiveFontFamily, getActiveFontSize, getActiveHighlightColor, getActiveLinkHref, getActiveLinkRange, getActiveMarks, getActiveTextColor, getCellFirstPosition, getCellLastPosition, getDocumentLength, getDocumentMarkAttrValues, getNodeAtPath, getTableDimensions, getTextNodesBetween, htmlSerializer, insertImage, insertLink, insertTable, insertTableColumnAfter, insertTableColumnBefore, insertTableRowAfter, insertTableRowBefore, insertText, insertTextAtPath, isBlockNode, isContainerBlock, isSelectionInContainer, isTextNode, joinBlocks, jsonSerializer, makeCollapsedSelection, makePosition, markdownSerializer, marksEqual, mergeAdjacentTextNodesInDoc, mergeCells, mergeTableCells, normalizeRange, redo, registerCommand, removeMarkFromNode, removeMarkFromRange, renderDocument, replaceAllMatches, replaceMatch, restoreSelection, scrollMatchIntoView, setAlignment, setBlockType, setCodeBlockLanguage, setColumnWidth, setFontFamily, setFontSize, setHighlightColor, setImageAttr, setMarkOnRange, setTextColor, splitBlock, splitCell, splitTableCell, toggleCheckItemAt, toggleHeaderRow, toggleMark, undo, updateColumnWidth, useEditorEngine, useEditorState, walkDocument };
|
package/dist/index.d.ts
CHANGED
|
@@ -806,25 +806,33 @@ declare function execCommand(engine: EditorEngineInterface, command: string, ...
|
|
|
806
806
|
declare function registerCommand(name: string, fn: CommandFn): void;
|
|
807
807
|
|
|
808
808
|
/**
|
|
809
|
-
* PastePlugin
|
|
809
|
+
* PastePlugin / ClipboardPlugin
|
|
810
810
|
*
|
|
811
|
-
*
|
|
812
|
-
* converts it to our document model, and inserts it at the current cursor.
|
|
811
|
+
* CKEditor-style clipboard pipeline:
|
|
813
812
|
*
|
|
814
|
-
*
|
|
815
|
-
*
|
|
816
|
-
* - Normalize formatting (convert <b> → mark, etc.)
|
|
817
|
-
* - Reject unknown elements (fall back to plain text)
|
|
813
|
+
* Copy/Cut — intercept event, serialize selection from model to clean HTML,
|
|
814
|
+
* write text/html + text/plain to clipboard. Cut also deletes selection.
|
|
818
815
|
*
|
|
819
|
-
*
|
|
820
|
-
*
|
|
821
|
-
*
|
|
816
|
+
* Paste — normalize HTML from external sources (Word, Google Docs, web),
|
|
817
|
+
* deserialize to model, merge at cursor with block-split/merge logic.
|
|
818
|
+
*
|
|
819
|
+
* Pipeline stages:
|
|
820
|
+
* 1. normalizeHTML — strip MSO/GDocs junk, preserve useful inline styles
|
|
821
|
+
* 2. deserialize — HTML → model (BlockNode[])
|
|
822
|
+
* 3. mergeAtCursor — split current block, insert pasted blocks, merge edges
|
|
823
|
+
* 4. set cursor — place after last pasted character
|
|
824
|
+
*
|
|
825
|
+
* Never uses document.execCommand. Uses Clipboard API, Selection API, DOM APIs only.
|
|
822
826
|
*/
|
|
823
827
|
|
|
824
828
|
declare function createPastePlugin(): EditorPlugin;
|
|
825
829
|
/**
|
|
826
|
-
* Attach paste
|
|
827
|
-
*
|
|
830
|
+
* Attach copy, cut, and paste handlers to the editor container.
|
|
831
|
+
* Returns a cleanup function that removes all listeners.
|
|
832
|
+
*/
|
|
833
|
+
declare function attachClipboardHandlers(container: HTMLElement, engine: EditorEngineInterface): () => void;
|
|
834
|
+
/**
|
|
835
|
+
* Attach only the paste handler (kept for backward compat).
|
|
828
836
|
*/
|
|
829
837
|
declare function attachPasteHandler(container: HTMLElement, engine: EditorEngineInterface): () => void;
|
|
830
838
|
|
|
@@ -971,4 +979,4 @@ declare const DEFAULT_TOOLBAR: EditorToolbarItem[];
|
|
|
971
979
|
declare const MINIMAL_TOOLBAR: EditorToolbarItem[];
|
|
972
980
|
declare const BASIC_TOOLBAR: EditorToolbarItem[];
|
|
973
981
|
|
|
974
|
-
export { type AddMarkStep, type AlignmentType, BASIC_TOOLBAR, type BlockNode, type BlockNodeType, type CellPosition, type Command, DEFAULT_TOOLBAR, type DeleteNodeStep, type DeleteRangeStep, type DeleteTextStep, type Document, Editor, type EditorAPI, EditorCore, type EditorCoreProps, EditorEngine, type EditorEngineInterface, type EditorNode, type EditorPlugin, type EditorProps, type EditorSelection, type EditorState, type EditorToolbarConfig, type EditorToolbarItem, type InlineNodeType, type InsertNodeStep, type InsertTextStep, type JoinBlocksStep, MINIMAL_TOOLBAR, type Mark, type MarkType, type NodeAttrs, type NodePosition, type NodeType, type RemoveMarkStep, type ReplaceDocStep, type SearchMatch, type SearchOptions, type Serializer, type SetMarkStep, type SetNodeAttrsStep, type SetNodeTypeStep, type SetSelectionStep, type SplitBlockStep, type StateListener, type Step, type StepType, type TableDimensions, TablePlugin, type TextNode, type ThemeConfig, type ThemeMode, type ThemeTokens, Toolbar, type ToolbarItem, type Transaction, addColumnLeft, addColumnRight, addMarkToNode, addRowAbove, addRowBelow, applyMarkToRange, applySearchHighlights, applyTransaction, attachPasteHandler, captureSelection, clearSearchHighlights, collectText, comparePaths, comparePositions, createEmptyDocument, createHistoryManager, createHistoryPlugin, createParagraph, createPastePlugin, createTableCell, createTableNode, createTransaction, deleteColumn, deleteImageAtPath, deleteRange, deleteRow, deleteTable, deleteTableColumn, deleteTableRow, deleteTextAtPath, execCommand, findCellPosition, findContentBlockPath, findMatches, findTablePath, getActiveAlignment, getActiveBlockType, getActiveFontFamily, getActiveFontSize, getActiveHighlightColor, getActiveLinkHref, getActiveLinkRange, getActiveMarks, getActiveTextColor, getCellFirstPosition, getCellLastPosition, getDocumentLength, getDocumentMarkAttrValues, getNodeAtPath, getTableDimensions, getTextNodesBetween, htmlSerializer, insertImage, insertLink, insertTable, insertTableColumnAfter, insertTableColumnBefore, insertTableRowAfter, insertTableRowBefore, insertText, insertTextAtPath, isBlockNode, isContainerBlock, isSelectionInContainer, isTextNode, joinBlocks, jsonSerializer, makeCollapsedSelection, makePosition, markdownSerializer, marksEqual, mergeAdjacentTextNodesInDoc, mergeCells, mergeTableCells, normalizeRange, redo, registerCommand, removeMarkFromNode, removeMarkFromRange, renderDocument, replaceAllMatches, replaceMatch, restoreSelection, scrollMatchIntoView, setAlignment, setBlockType, setCodeBlockLanguage, setColumnWidth, setFontFamily, setFontSize, setHighlightColor, setImageAttr, setMarkOnRange, setTextColor, splitBlock, splitCell, splitTableCell, toggleCheckItemAt, toggleHeaderRow, toggleMark, undo, updateColumnWidth, useEditorEngine, useEditorState, walkDocument };
|
|
982
|
+
export { type AddMarkStep, type AlignmentType, BASIC_TOOLBAR, type BlockNode, type BlockNodeType, type CellPosition, type Command, DEFAULT_TOOLBAR, type DeleteNodeStep, type DeleteRangeStep, type DeleteTextStep, type Document, Editor, type EditorAPI, EditorCore, type EditorCoreProps, EditorEngine, type EditorEngineInterface, type EditorNode, type EditorPlugin, type EditorProps, type EditorSelection, type EditorState, type EditorToolbarConfig, type EditorToolbarItem, type InlineNodeType, type InsertNodeStep, type InsertTextStep, type JoinBlocksStep, MINIMAL_TOOLBAR, type Mark, type MarkType, type NodeAttrs, type NodePosition, type NodeType, type RemoveMarkStep, type ReplaceDocStep, type SearchMatch, type SearchOptions, type Serializer, type SetMarkStep, type SetNodeAttrsStep, type SetNodeTypeStep, type SetSelectionStep, type SplitBlockStep, type StateListener, type Step, type StepType, type TableDimensions, TablePlugin, type TextNode, type ThemeConfig, type ThemeMode, type ThemeTokens, Toolbar, type ToolbarItem, type Transaction, addColumnLeft, addColumnRight, addMarkToNode, addRowAbove, addRowBelow, applyMarkToRange, applySearchHighlights, applyTransaction, attachClipboardHandlers, attachPasteHandler, captureSelection, clearSearchHighlights, collectText, comparePaths, comparePositions, createEmptyDocument, createHistoryManager, createHistoryPlugin, createParagraph, createPastePlugin, createTableCell, createTableNode, createTransaction, deleteColumn, deleteImageAtPath, deleteRange, deleteRow, deleteTable, deleteTableColumn, deleteTableRow, deleteTextAtPath, execCommand, findCellPosition, findContentBlockPath, findMatches, findTablePath, getActiveAlignment, getActiveBlockType, getActiveFontFamily, getActiveFontSize, getActiveHighlightColor, getActiveLinkHref, getActiveLinkRange, getActiveMarks, getActiveTextColor, getCellFirstPosition, getCellLastPosition, getDocumentLength, getDocumentMarkAttrValues, getNodeAtPath, getTableDimensions, getTextNodesBetween, htmlSerializer, insertImage, insertLink, insertTable, insertTableColumnAfter, insertTableColumnBefore, insertTableRowAfter, insertTableRowBefore, insertText, insertTextAtPath, isBlockNode, isContainerBlock, isSelectionInContainer, isTextNode, joinBlocks, jsonSerializer, makeCollapsedSelection, makePosition, markdownSerializer, marksEqual, mergeAdjacentTextNodesInDoc, mergeCells, mergeTableCells, normalizeRange, redo, registerCommand, removeMarkFromNode, removeMarkFromRange, renderDocument, replaceAllMatches, replaceMatch, restoreSelection, scrollMatchIntoView, setAlignment, setBlockType, setCodeBlockLanguage, setColumnWidth, setFontFamily, setFontSize, setHighlightColor, setImageAttr, setMarkOnRange, setTextColor, splitBlock, splitCell, splitTableCell, toggleCheckItemAt, toggleHeaderRow, toggleMark, undo, updateColumnWidth, useEditorEngine, useEditorState, walkDocument };
|
package/dist/index.js
CHANGED
|
@@ -1655,10 +1655,14 @@ function serializeBlock(node, idCounts = /* @__PURE__ */ new Map()) {
|
|
|
1655
1655
|
case "list_item":
|
|
1656
1656
|
return `<li>${serializeChildren(node.children)}</li>`;
|
|
1657
1657
|
case "check_list":
|
|
1658
|
-
return `<ul data-type="checklist">${serializeChildren(node.children)}</ul>`;
|
|
1658
|
+
return `<ul class="todo-list" data-type="checklist">${serializeChildren(node.children)}</ul>`;
|
|
1659
1659
|
case "check_list_item": {
|
|
1660
|
-
const checked = ((_g = node.attrs) == null ? void 0 : _g.checked)
|
|
1661
|
-
|
|
1660
|
+
const checked = !!((_g = node.attrs) == null ? void 0 : _g.checked);
|
|
1661
|
+
const dataChecked = checked ? ' data-checked="true"' : "";
|
|
1662
|
+
const itemClass = `todo-list__item${checked ? " todo-list__item_checked" : ""}`;
|
|
1663
|
+
const checkedAttr = checked ? ' checked="checked"' : "";
|
|
1664
|
+
const innerContent = serializeChildren(node.children);
|
|
1665
|
+
return `<li class="${itemClass}"${dataChecked}><label class="todo-list__label"><input type="checkbox" disabled="disabled"${checkedAttr}><span class="todo-list__label__description">${innerContent}</span></label></li>`;
|
|
1662
1666
|
}
|
|
1663
1667
|
case "code_block": {
|
|
1664
1668
|
const lang = ((_h = node.attrs) == null ? void 0 : _h.language) ? ` class="language-${node.attrs.language}"` : "";
|
|
@@ -1931,6 +1935,7 @@ function parseInlineChildren(el) {
|
|
|
1931
1935
|
results.push({ type: "text", text: "\n", marks: [...marks] });
|
|
1932
1936
|
return;
|
|
1933
1937
|
}
|
|
1938
|
+
if (tag === "input") return;
|
|
1934
1939
|
const newMarks = [...marks, ...getMarksForTag(tag, elem)];
|
|
1935
1940
|
for (const child of Array.from(elem.childNodes)) {
|
|
1936
1941
|
walk(child, newMarks);
|
|
@@ -1980,10 +1985,16 @@ function getMarksForTag(tag, el) {
|
|
|
1980
1985
|
|
|
1981
1986
|
// src/editor/plugins/PastePlugin.ts
|
|
1982
1987
|
function createPastePlugin() {
|
|
1983
|
-
return {
|
|
1984
|
-
|
|
1985
|
-
|
|
1986
|
-
|
|
1988
|
+
return { name: "clipboard" };
|
|
1989
|
+
}
|
|
1990
|
+
function attachClipboardHandlers(container, engine) {
|
|
1991
|
+
const removeCopy = attachCopyOrCutHandler(container, engine, false);
|
|
1992
|
+
const removeCut = attachCopyOrCutHandler(container, engine, true);
|
|
1993
|
+
const removePaste = attachPasteHandler(container, engine);
|
|
1994
|
+
return () => {
|
|
1995
|
+
removeCopy();
|
|
1996
|
+
removeCut();
|
|
1997
|
+
removePaste();
|
|
1987
1998
|
};
|
|
1988
1999
|
}
|
|
1989
2000
|
function attachPasteHandler(container, engine) {
|
|
@@ -1999,11 +2010,9 @@ function attachPasteHandler(container, engine) {
|
|
|
1999
2010
|
if (file) {
|
|
2000
2011
|
const blobUrl = URL.createObjectURL(file);
|
|
2001
2012
|
const state2 = engine.getState();
|
|
2002
|
-
|
|
2013
|
+
const sel2 = state2.selection;
|
|
2014
|
+
const insertIdx = sel2 ? sel2.anchor.path[0] + 1 : state2.doc.children.length;
|
|
2003
2015
|
const tr2 = createTransaction();
|
|
2004
|
-
const sel = state2.selection;
|
|
2005
|
-
const blockPath = sel ? [sel.anchor.path[0]] : [state2.doc.children.length - 1];
|
|
2006
|
-
const insertIdx = blockPath[0] + 1;
|
|
2007
2016
|
tr2.steps.push({
|
|
2008
2017
|
type: "insert_node",
|
|
2009
2018
|
parentPath: [],
|
|
@@ -2016,56 +2025,346 @@ function attachPasteHandler(container, engine) {
|
|
|
2016
2025
|
}
|
|
2017
2026
|
const html = clipboardData.getData("text/html");
|
|
2018
2027
|
const plainText = clipboardData.getData("text/plain");
|
|
2019
|
-
let
|
|
2028
|
+
let pastedDoc;
|
|
2020
2029
|
if (html) {
|
|
2021
|
-
const
|
|
2022
|
-
|
|
2030
|
+
const normalized = normalizeHTML(html);
|
|
2031
|
+
pastedDoc = htmlSerializer.deserialize(normalized);
|
|
2023
2032
|
} else if (plainText) {
|
|
2024
|
-
|
|
2025
|
-
if (isURL(trimmed)) {
|
|
2026
|
-
const href = /^https?:\/\//i.test(trimmed) ? trimmed : `https://${trimmed}`;
|
|
2027
|
-
const escaped = escapeHTML2(trimmed);
|
|
2028
|
-
newDoc = htmlSerializer.deserialize(
|
|
2029
|
-
`<p><a href="${escapeAttr2(href)}">${escaped}</a></p>`
|
|
2030
|
-
);
|
|
2031
|
-
} else {
|
|
2032
|
-
newDoc = htmlSerializer.deserialize(`<p>${escapeHTML2(plainText)}</p>`);
|
|
2033
|
-
}
|
|
2033
|
+
pastedDoc = parsePlainText(plainText);
|
|
2034
2034
|
} else {
|
|
2035
2035
|
return;
|
|
2036
2036
|
}
|
|
2037
|
+
const pastedBlocks = pastedDoc.children.filter(
|
|
2038
|
+
(b) => !(b.type === "paragraph" && b.children.length === 0)
|
|
2039
|
+
);
|
|
2040
|
+
if (pastedBlocks.length === 0) return;
|
|
2037
2041
|
const state = engine.getState();
|
|
2038
|
-
|
|
2039
|
-
|
|
2040
|
-
|
|
2041
|
-
|
|
2042
|
+
let sel = state.selection;
|
|
2043
|
+
if (sel && !sel.isCollapsed) {
|
|
2044
|
+
const { from, to } = normalizeRange(sel.anchor, sel.focus);
|
|
2045
|
+
const delTr = createTransaction();
|
|
2046
|
+
delTr.steps.push(tr_deleteRange(from, to));
|
|
2047
|
+
engine.dispatch(delTr);
|
|
2048
|
+
sel = engine.getState().selection;
|
|
2049
|
+
}
|
|
2050
|
+
const currentState = engine.getState();
|
|
2051
|
+
const { mergedDoc, cursorPos } = mergeAtCursor(
|
|
2052
|
+
currentState,
|
|
2053
|
+
{ children: pastedBlocks },
|
|
2054
|
+
sel
|
|
2055
|
+
);
|
|
2042
2056
|
const tr = createTransaction();
|
|
2043
|
-
tr.steps.push(tr_replaceDoc(
|
|
2057
|
+
tr.steps.push(tr_replaceDoc(mergedDoc));
|
|
2058
|
+
if (cursorPos) {
|
|
2059
|
+
tr.steps.push(tr_setSelection({ anchor: cursorPos, focus: cursorPos, isCollapsed: true }));
|
|
2060
|
+
}
|
|
2044
2061
|
engine.dispatch(tr);
|
|
2045
2062
|
};
|
|
2046
2063
|
container.addEventListener("paste", handler);
|
|
2047
2064
|
return () => container.removeEventListener("paste", handler);
|
|
2048
2065
|
}
|
|
2049
|
-
function
|
|
2066
|
+
function attachCopyOrCutHandler(container, engine, isCut) {
|
|
2067
|
+
const handler = (e) => {
|
|
2068
|
+
const state = engine.getState();
|
|
2069
|
+
const sel = state.selection;
|
|
2070
|
+
if (!sel || sel.isCollapsed) return;
|
|
2071
|
+
e.preventDefault();
|
|
2072
|
+
const selectedDoc = extractSelection(state);
|
|
2073
|
+
const selectedHTML = htmlSerializer.serialize(selectedDoc);
|
|
2074
|
+
const selectedText = docToPlainText(selectedDoc);
|
|
2075
|
+
try {
|
|
2076
|
+
e.clipboardData.setData("text/html", selectedHTML);
|
|
2077
|
+
e.clipboardData.setData("text/plain", selectedText);
|
|
2078
|
+
} catch (e2) {
|
|
2079
|
+
}
|
|
2080
|
+
if (isCut) {
|
|
2081
|
+
const { from, to } = normalizeRange(sel.anchor, sel.focus);
|
|
2082
|
+
const tr = createTransaction();
|
|
2083
|
+
tr.steps.push(tr_deleteRange(from, to));
|
|
2084
|
+
engine.dispatch(tr);
|
|
2085
|
+
}
|
|
2086
|
+
};
|
|
2087
|
+
const event = isCut ? "cut" : "copy";
|
|
2088
|
+
container.addEventListener(event, handler);
|
|
2089
|
+
return () => container.removeEventListener(event, handler);
|
|
2090
|
+
}
|
|
2091
|
+
function extractSelection(state) {
|
|
2092
|
+
var _a, _b;
|
|
2093
|
+
const sel = state.selection;
|
|
2094
|
+
if (!sel || sel.isCollapsed) return { type: "doc", children: [createParagraph()] };
|
|
2095
|
+
const { from, to } = normalizeRange(sel.anchor, sel.focus);
|
|
2096
|
+
const blocks = state.doc.children;
|
|
2097
|
+
const fromBlockIdx = (_a = from.path[0]) != null ? _a : 0;
|
|
2098
|
+
const toBlockIdx = (_b = to.path[0]) != null ? _b : 0;
|
|
2099
|
+
if (fromBlockIdx === toBlockIdx) {
|
|
2100
|
+
const sliced = sliceBlock(blocks[fromBlockIdx], from.path.slice(1), from.offset, to.path.slice(1), to.offset);
|
|
2101
|
+
return { type: "doc", children: sliced.length > 0 ? sliced : [createParagraph()] };
|
|
2102
|
+
}
|
|
2103
|
+
const firstBlock = sliceBlockFrom(blocks[fromBlockIdx], from.path.slice(1), from.offset);
|
|
2104
|
+
const middleBlocks = blocks.slice(fromBlockIdx + 1, toBlockIdx);
|
|
2105
|
+
const lastBlock = sliceBlockTo(blocks[toBlockIdx], to.path.slice(1), to.offset);
|
|
2106
|
+
return { type: "doc", children: [firstBlock, ...middleBlocks, lastBlock] };
|
|
2107
|
+
}
|
|
2108
|
+
function sliceBlock(block, fromRelPath, fromOffset, toRelPath, toOffset) {
|
|
2109
|
+
var _a, _b;
|
|
2110
|
+
const fromIdx = (_a = fromRelPath[0]) != null ? _a : 0;
|
|
2111
|
+
const toIdx = (_b = toRelPath[0]) != null ? _b : Math.max(0, block.children.length - 1);
|
|
2112
|
+
if (!block.children.every((c) => isTextNode(c))) {
|
|
2113
|
+
return [block];
|
|
2114
|
+
}
|
|
2115
|
+
const children = [];
|
|
2116
|
+
for (let i = fromIdx; i <= toIdx && i < block.children.length; i++) {
|
|
2117
|
+
const t = block.children[i];
|
|
2118
|
+
const s = i === fromIdx ? fromOffset : 0;
|
|
2119
|
+
const end = i === toIdx ? toOffset : t.text.length;
|
|
2120
|
+
const slice = t.text.slice(s, end);
|
|
2121
|
+
if (slice) children.push({ type: "text", text: slice, marks: t.marks });
|
|
2122
|
+
}
|
|
2123
|
+
return children.length > 0 ? [{ ...block, children }] : [];
|
|
2124
|
+
}
|
|
2125
|
+
function sliceBlockFrom(block, relPath, offset) {
|
|
2126
|
+
var _a;
|
|
2127
|
+
const fromIdx = (_a = relPath[0]) != null ? _a : 0;
|
|
2128
|
+
if (!block.children.every((c) => isTextNode(c))) return block;
|
|
2129
|
+
const children = [];
|
|
2130
|
+
for (let i = fromIdx; i < block.children.length; i++) {
|
|
2131
|
+
const t = block.children[i];
|
|
2132
|
+
const s = i === fromIdx ? offset : 0;
|
|
2133
|
+
const slice = t.text.slice(s);
|
|
2134
|
+
if (slice) children.push({ type: "text", text: slice, marks: t.marks });
|
|
2135
|
+
}
|
|
2136
|
+
return { ...block, children };
|
|
2137
|
+
}
|
|
2138
|
+
function sliceBlockTo(block, relPath, offset) {
|
|
2139
|
+
var _a;
|
|
2140
|
+
const toIdx = (_a = relPath[0]) != null ? _a : Math.max(0, block.children.length - 1);
|
|
2141
|
+
if (!block.children.every((c) => isTextNode(c))) return block;
|
|
2142
|
+
const children = [];
|
|
2143
|
+
for (let i = 0; i <= toIdx && i < block.children.length; i++) {
|
|
2144
|
+
const t = block.children[i];
|
|
2145
|
+
const end = i === toIdx ? offset : t.text.length;
|
|
2146
|
+
const slice = t.text.slice(0, end);
|
|
2147
|
+
if (slice) children.push({ type: "text", text: slice, marks: t.marks });
|
|
2148
|
+
}
|
|
2149
|
+
return { ...block, children };
|
|
2150
|
+
}
|
|
2151
|
+
function mergeAtCursor(state, pastedDoc, sel) {
|
|
2152
|
+
var _a, _b;
|
|
2153
|
+
const pasted = pastedDoc.children;
|
|
2154
|
+
if (pasted.length === 0) return { mergedDoc: state.doc, cursorPos: (_a = sel == null ? void 0 : sel.anchor) != null ? _a : null };
|
|
2155
|
+
const existingBlocks = state.doc.children;
|
|
2156
|
+
if (!sel) {
|
|
2157
|
+
const mergedDoc = { ...state.doc, children: [...existingBlocks, ...pasted] };
|
|
2158
|
+
const lastIdx = mergedDoc.children.length - 1;
|
|
2159
|
+
const cursorPos = endOfBlock(mergedDoc.children[lastIdx], lastIdx);
|
|
2160
|
+
return { mergedDoc, cursorPos };
|
|
2161
|
+
}
|
|
2162
|
+
const blockIdx = (_b = sel.anchor.path[0]) != null ? _b : 0;
|
|
2163
|
+
const relPath = sel.anchor.path.slice(1);
|
|
2164
|
+
const charOffset = sel.anchor.offset;
|
|
2165
|
+
const currentBlock = existingBlocks[blockIdx];
|
|
2166
|
+
if (!currentBlock) {
|
|
2167
|
+
const mergedDoc = { ...state.doc, children: [...existingBlocks, ...pasted] };
|
|
2168
|
+
const lastIdx = mergedDoc.children.length - 1;
|
|
2169
|
+
return { mergedDoc, cursorPos: endOfBlock(mergedDoc.children[lastIdx], lastIdx) };
|
|
2170
|
+
}
|
|
2171
|
+
const [beforeChildren, afterChildren] = splitBlockChildren(currentBlock, relPath, charOffset);
|
|
2172
|
+
let newBlocks;
|
|
2173
|
+
let cursorBlockOffset;
|
|
2174
|
+
let cursorInBlock;
|
|
2175
|
+
if (pasted.length === 1) {
|
|
2176
|
+
const pastedChildren = pasted[0].children;
|
|
2177
|
+
const mergedChildren2 = [...beforeChildren, ...pastedChildren, ...afterChildren];
|
|
2178
|
+
const cleanChildren = mergedChildren2.length > 0 ? mergedChildren2 : [{ type: "text", text: "", marks: [] }];
|
|
2179
|
+
newBlocks = [{ ...currentBlock, children: cleanChildren }];
|
|
2180
|
+
cursorBlockOffset = 0;
|
|
2181
|
+
const cursorChildIdx = beforeChildren.length + pastedChildren.length;
|
|
2182
|
+
const nodeBeforeCursor = cleanChildren[cursorChildIdx - 1];
|
|
2183
|
+
if (nodeBeforeCursor && isTextNode(nodeBeforeCursor)) {
|
|
2184
|
+
cursorInBlock = {
|
|
2185
|
+
path: [blockIdx, cursorChildIdx - 1],
|
|
2186
|
+
offset: nodeBeforeCursor.text.length
|
|
2187
|
+
};
|
|
2188
|
+
} else {
|
|
2189
|
+
cursorInBlock = { path: [blockIdx], offset: 0 };
|
|
2190
|
+
}
|
|
2191
|
+
} else {
|
|
2192
|
+
const firstPasted = pasted[0];
|
|
2193
|
+
const lastPasted = pasted[pasted.length - 1];
|
|
2194
|
+
const middleBlocks = pasted.slice(1, -1);
|
|
2195
|
+
const firstMerged = {
|
|
2196
|
+
...currentBlock,
|
|
2197
|
+
children: [...beforeChildren, ...firstPasted.children]
|
|
2198
|
+
};
|
|
2199
|
+
const lastMerged = {
|
|
2200
|
+
type: lastPasted.type,
|
|
2201
|
+
attrs: lastPasted.attrs,
|
|
2202
|
+
children: [...lastPasted.children, ...afterChildren]
|
|
2203
|
+
};
|
|
2204
|
+
newBlocks = [firstMerged, ...middleBlocks, lastMerged];
|
|
2205
|
+
cursorBlockOffset = newBlocks.length - 1;
|
|
2206
|
+
const lpChildren = lastPasted.children;
|
|
2207
|
+
const absoluteLastBlockIdx = blockIdx + cursorBlockOffset;
|
|
2208
|
+
if (lpChildren.length > 0) {
|
|
2209
|
+
const lastNode = lpChildren[lpChildren.length - 1];
|
|
2210
|
+
cursorInBlock = {
|
|
2211
|
+
path: [absoluteLastBlockIdx, lpChildren.length - 1],
|
|
2212
|
+
offset: isTextNode(lastNode) ? lastNode.text.length : 0
|
|
2213
|
+
};
|
|
2214
|
+
} else {
|
|
2215
|
+
cursorInBlock = { path: [absoluteLastBlockIdx], offset: 0 };
|
|
2216
|
+
}
|
|
2217
|
+
}
|
|
2218
|
+
const mergedChildren = [
|
|
2219
|
+
...existingBlocks.slice(0, blockIdx),
|
|
2220
|
+
...newBlocks,
|
|
2221
|
+
...existingBlocks.slice(blockIdx + 1)
|
|
2222
|
+
];
|
|
2223
|
+
return {
|
|
2224
|
+
mergedDoc: { ...state.doc, children: mergedChildren },
|
|
2225
|
+
cursorPos: cursorInBlock
|
|
2226
|
+
};
|
|
2227
|
+
}
|
|
2228
|
+
function splitBlockChildren(block, relPath, offset) {
|
|
2229
|
+
const children = block.children;
|
|
2230
|
+
if (children.length === 0) return [[], []];
|
|
2231
|
+
if (relPath.length === 0) return [[], [...children]];
|
|
2232
|
+
const textIdx = relPath[0];
|
|
2233
|
+
if (textIdx >= children.length) return [[...children], []];
|
|
2234
|
+
const node = children[textIdx];
|
|
2235
|
+
if (!isTextNode(node)) {
|
|
2236
|
+
return [[...children.slice(0, textIdx)], [...children.slice(textIdx)]];
|
|
2237
|
+
}
|
|
2238
|
+
const textNode = node;
|
|
2239
|
+
const before = [
|
|
2240
|
+
...children.slice(0, textIdx),
|
|
2241
|
+
...offset > 0 ? [{ type: "text", text: textNode.text.slice(0, offset), marks: textNode.marks }] : []
|
|
2242
|
+
];
|
|
2243
|
+
const after = [
|
|
2244
|
+
...offset < textNode.text.length ? [{ type: "text", text: textNode.text.slice(offset), marks: textNode.marks }] : [],
|
|
2245
|
+
...children.slice(textIdx + 1)
|
|
2246
|
+
];
|
|
2247
|
+
return [before, after];
|
|
2248
|
+
}
|
|
2249
|
+
function endOfBlock(block, blockIdx) {
|
|
2250
|
+
const children = block.children;
|
|
2251
|
+
for (let i = children.length - 1; i >= 0; i--) {
|
|
2252
|
+
const n = children[i];
|
|
2253
|
+
if (isTextNode(n)) {
|
|
2254
|
+
return { path: [blockIdx, i], offset: n.text.length };
|
|
2255
|
+
}
|
|
2256
|
+
}
|
|
2257
|
+
return { path: [blockIdx], offset: 0 };
|
|
2258
|
+
}
|
|
2259
|
+
function parsePlainText(text) {
|
|
2260
|
+
const normalized = text.replace(/\r\n/g, "\n").replace(/\r/g, "\n").trim();
|
|
2261
|
+
if (isURL(normalized)) {
|
|
2262
|
+
const href = /^https?:\/\//i.test(normalized) ? normalized : `https://${normalized}`;
|
|
2263
|
+
return htmlSerializer.deserialize(
|
|
2264
|
+
`<p><a href="${escapeAttr2(href)}">${escapeHTML2(normalized)}</a></p>`
|
|
2265
|
+
);
|
|
2266
|
+
}
|
|
2267
|
+
const paragraphs = normalized.split(/\n{2,}/);
|
|
2268
|
+
if (paragraphs.length === 1) {
|
|
2269
|
+
const lines = normalized.split("\n");
|
|
2270
|
+
if (lines.length === 1) {
|
|
2271
|
+
return htmlSerializer.deserialize(`<p>${escapeHTML2(normalized)}</p>`);
|
|
2272
|
+
}
|
|
2273
|
+
const html2 = lines.map((l) => escapeHTML2(l)).join("<br>");
|
|
2274
|
+
return htmlSerializer.deserialize(`<p>${html2}</p>`);
|
|
2275
|
+
}
|
|
2276
|
+
const html = paragraphs.map((p) => `<p>${p.split("\n").map((l) => escapeHTML2(l)).join("<br>")}</p>`).join("");
|
|
2277
|
+
return htmlSerializer.deserialize(html);
|
|
2278
|
+
}
|
|
2279
|
+
function normalizeHTML(html) {
|
|
2280
|
+
let out = html.replace(/<!--[\s\S]*?-->/g, "");
|
|
2281
|
+
out = out.replace(/<!\[if[^\]]*\]>[\s\S]*?<!\[endif\]>/gi, "");
|
|
2282
|
+
out = out.replace(/<\/?(?:o|w|m|v|st\d?|x):[^>]*>/gi, "");
|
|
2050
2283
|
const parser = new DOMParser();
|
|
2051
|
-
const doc = parser.parseFromString(
|
|
2052
|
-
|
|
2053
|
-
"script, style, iframe, object, embed, form,
|
|
2054
|
-
);
|
|
2055
|
-
|
|
2056
|
-
|
|
2057
|
-
const
|
|
2058
|
-
|
|
2059
|
-
|
|
2060
|
-
|
|
2061
|
-
|
|
2062
|
-
|
|
2284
|
+
const doc = parser.parseFromString(out, "text/html");
|
|
2285
|
+
doc.querySelectorAll(
|
|
2286
|
+
"script, style, iframe, object, embed, form, button, select, textarea, meta, link"
|
|
2287
|
+
).forEach((el) => el.remove());
|
|
2288
|
+
doc.querySelectorAll("input").forEach((el) => {
|
|
2289
|
+
var _a;
|
|
2290
|
+
const isCheckbox = ((_a = el.getAttribute("type")) == null ? void 0 : _a.toLowerCase()) === "checkbox";
|
|
2291
|
+
const inTodoList = !!el.closest('ul[data-type="checklist"], ul.todo-list, li.todo-list__item, li[data-checked]');
|
|
2292
|
+
if (!isCheckbox || !inTodoList) el.remove();
|
|
2293
|
+
});
|
|
2294
|
+
doc.querySelectorAll("b").forEach((el) => {
|
|
2295
|
+
const fw = el.style.fontWeight;
|
|
2296
|
+
if (fw === "normal" || fw === "400" || fw === "inherit" || fw === "") {
|
|
2297
|
+
const parent = el.parentNode;
|
|
2298
|
+
if (!parent) return;
|
|
2299
|
+
while (el.firstChild) parent.insertBefore(el.firstChild, el);
|
|
2300
|
+
el.remove();
|
|
2301
|
+
}
|
|
2302
|
+
});
|
|
2303
|
+
doc.querySelectorAll("*").forEach((node) => {
|
|
2304
|
+
var _a, _b, _c, _d, _e, _f, _g, _h, _i;
|
|
2305
|
+
const el = node;
|
|
2306
|
+
const tag = el.tagName.toLowerCase();
|
|
2307
|
+
const fontSize = (_b = (_a = el.style) == null ? void 0 : _a.fontSize) != null ? _b : "";
|
|
2308
|
+
const fontFamily = (_d = (_c = el.style) == null ? void 0 : _c.fontFamily) != null ? _d : "";
|
|
2309
|
+
const color = (_f = (_e = el.style) == null ? void 0 : _e.color) != null ? _f : "";
|
|
2310
|
+
const bgColor = (_h = (_g = el.style) == null ? void 0 : _g.backgroundColor) != null ? _h : "";
|
|
2063
2311
|
el.removeAttribute("style");
|
|
2312
|
+
const safeStyle = [];
|
|
2313
|
+
if (fontSize) safeStyle.push(`font-size:${fontSize}`);
|
|
2314
|
+
if (fontFamily) safeStyle.push(`font-family:${fontFamily}`);
|
|
2315
|
+
if (color) safeStyle.push(`color:${color}`);
|
|
2316
|
+
if (bgColor && !["transparent", "rgba(0, 0, 0, 0)", "white", "rgb(255, 255, 255)", "#ffffff", "#fff"].includes(
|
|
2317
|
+
bgColor.toLowerCase().replace(/\s/g, "")
|
|
2318
|
+
)) {
|
|
2319
|
+
safeStyle.push(`background-color:${bgColor}`);
|
|
2320
|
+
}
|
|
2321
|
+
if (safeStyle.length > 0) el.setAttribute("style", safeStyle.join(";"));
|
|
2064
2322
|
el.removeAttribute("class");
|
|
2065
2323
|
el.removeAttribute("id");
|
|
2324
|
+
Array.from(el.attributes).forEach((attr) => {
|
|
2325
|
+
if (attr.name.startsWith("on")) el.removeAttribute(attr.name);
|
|
2326
|
+
});
|
|
2327
|
+
Array.from(el.attributes).forEach((attr) => {
|
|
2328
|
+
if (attr.name.startsWith("data-") && !["data-align", "data-type", "data-checked"].includes(attr.name)) {
|
|
2329
|
+
el.removeAttribute(attr.name);
|
|
2330
|
+
}
|
|
2331
|
+
});
|
|
2332
|
+
if (tag === "a") {
|
|
2333
|
+
const href = (_i = el.getAttribute("href")) != null ? _i : "";
|
|
2334
|
+
const isUnsafe = /^(javascript:|vbscript:|data:)/i.test(href.trim());
|
|
2335
|
+
Array.from(el.attributes).forEach((attr) => {
|
|
2336
|
+
if (!["href", "target", "rel", "style"].includes(attr.name)) el.removeAttribute(attr.name);
|
|
2337
|
+
});
|
|
2338
|
+
if (isUnsafe) el.removeAttribute("href");
|
|
2339
|
+
}
|
|
2340
|
+
if (tag === "img") {
|
|
2341
|
+
Array.from(el.attributes).forEach((attr) => {
|
|
2342
|
+
if (!["src", "alt", "width", "height", "style"].includes(attr.name)) el.removeAttribute(attr.name);
|
|
2343
|
+
});
|
|
2344
|
+
}
|
|
2345
|
+
if (tag === "td" || tag === "th") {
|
|
2346
|
+
Array.from(el.attributes).forEach((attr) => {
|
|
2347
|
+
if (!["colspan", "rowspan", "style"].includes(attr.name)) el.removeAttribute(attr.name);
|
|
2348
|
+
});
|
|
2349
|
+
}
|
|
2350
|
+
if (tag === "code") {
|
|
2351
|
+
Array.from(el.attributes).forEach((attr) => {
|
|
2352
|
+
if (!["class", "style"].includes(attr.name)) el.removeAttribute(attr.name);
|
|
2353
|
+
});
|
|
2354
|
+
}
|
|
2066
2355
|
});
|
|
2067
2356
|
return doc.body.innerHTML;
|
|
2068
2357
|
}
|
|
2358
|
+
function docToPlainText(doc) {
|
|
2359
|
+
return doc.children.map(blockToPlainText).join("\n\n");
|
|
2360
|
+
}
|
|
2361
|
+
function blockToPlainText(block) {
|
|
2362
|
+
if (block.children.length === 0) return "";
|
|
2363
|
+
return block.children.map((c) => {
|
|
2364
|
+
if (isTextNode(c)) return c.text;
|
|
2365
|
+
return blockToPlainText(c);
|
|
2366
|
+
}).join("");
|
|
2367
|
+
}
|
|
2069
2368
|
function escapeHTML2(str) {
|
|
2070
2369
|
return str.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">");
|
|
2071
2370
|
}
|
|
@@ -7585,7 +7884,7 @@ function EditorCore({
|
|
|
7585
7884
|
react.useEffect(() => {
|
|
7586
7885
|
const container = containerRef.current;
|
|
7587
7886
|
if (!container || readOnly) return;
|
|
7588
|
-
return
|
|
7887
|
+
return attachClipboardHandlers(container, engine);
|
|
7589
7888
|
}, [engine, readOnly]);
|
|
7590
7889
|
react.useEffect(() => {
|
|
7591
7890
|
const container = containerRef.current;
|
|
@@ -8735,6 +9034,7 @@ exports.addRowBelow = addRowBelow;
|
|
|
8735
9034
|
exports.applyMarkToRange = applyMarkToRange;
|
|
8736
9035
|
exports.applySearchHighlights = applySearchHighlights;
|
|
8737
9036
|
exports.applyTransaction = applyTransaction;
|
|
9037
|
+
exports.attachClipboardHandlers = attachClipboardHandlers;
|
|
8738
9038
|
exports.attachPasteHandler = attachPasteHandler;
|
|
8739
9039
|
exports.captureSelection = captureSelection;
|
|
8740
9040
|
exports.clearSearchHighlights = clearSearchHighlights;
|