@37signals/lexxy 0.9.27 → 0.9.28
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/lexxy.esm.js +168 -1
- package/package.json +1 -1
package/dist/lexxy.esm.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
export { highlightCode, highlightElement } from './lexxy_helpers.esm.js';
|
|
2
2
|
import DOMPurify from 'dompurify';
|
|
3
3
|
import { getStyleObjectFromCSS, getCSSFromStyleObject, $getSelectionStyleValueForProperty, $ensureForwardRangeSelection, $isAtNodeEnd, $patchStyleText, $setBlocksType, $forEachSelectedTextNode } from '@lexical/selection';
|
|
4
|
-
import { SKIP_DOM_SELECTION_TAG, CAN_UNDO_COMMAND, COMMAND_PRIORITY_LOW, CAN_REDO_COMMAND, $getSelection, $isRangeSelection, DecoratorNode, $createTextNode, defineExtension, $getRoot, $caretFromPoint, $setSelectionFromCaretRange, $getCaretRange, $normalizeCaret, $getChildCaret, $getCaretInDirection, $isParagraphNode, $isLineBreakNode, $createParagraphNode, $isElementNode, $isRootOrShadowRoot, $isRootNode, $createNodeSelection, $isDecoratorNode, $isTextNode, $getSiblingCaret, $rewindSiblingCaret, $splitAtPointCaretNext, $normalizeSelection__EXPERIMENTAL, $isChildCaret, $isTextPointCaret, $isExtendableTextPointCaret, $isSiblingCaret, $getCommonAncestor, $findMatchingParent, TextNode, createCommand, COMMAND_PRIORITY_EDITOR, $getEditor, $getNodeByKey, HISTORY_MERGE_TAG, SKIP_SCROLL_INTO_VIEW_TAG, $cloneWithProperties, $getNearestRootOrShadowRoot, $createRangeSelection, $setSelection, createState, COMMAND_PRIORITY_NORMAL, $getState, $setState, $hasUpdateTag, PASTE_TAG, FORMAT_TEXT_COMMAND, UNDO_COMMAND, REDO_COMMAND, KEY_ARROW_RIGHT_COMMAND, KEY_TAB_COMMAND, INSERT_LINE_BREAK_COMMAND, COMMAND_PRIORITY_HIGH, INSERT_PARAGRAPH_COMMAND, OUTDENT_CONTENT_COMMAND, INDENT_CONTENT_COMMAND, $isNodeSelection, KEY_ARROW_LEFT_COMMAND, KEY_ARROW_UP_COMMAND, KEY_ARROW_DOWN_COMMAND, DELETE_CHARACTER_COMMAND, SELECTION_CHANGE_COMMAND, COMMAND_PRIORITY_CRITICAL, $addUpdateTag, CLICK_COMMAND, isDOMNode, $getNearestNodeFromDOMNode, ElementNode, $splitNode, $getChildCaretAtIndex, $createLineBreakNode, SELECTION_INSERT_CLIPBOARD_NODES_COMMAND, PASTE_COMMAND, $onUpdate, ParagraphNode, RootNode, DRAGSTART_COMMAND, DROP_COMMAND, mergeRegister as mergeRegister$1, $createRangeSelectionFromDom, CLEAR_HISTORY_COMMAND, KEY_ENTER_COMMAND, KEY_SPACE_COMMAND, INPUT_COMMAND, KEY_BACKSPACE_COMMAND, KEY_DOWN_COMMAND } from 'lexical';
|
|
4
|
+
import { SKIP_DOM_SELECTION_TAG, CAN_UNDO_COMMAND, COMMAND_PRIORITY_LOW, CAN_REDO_COMMAND, $getSelection, $isRangeSelection, DecoratorNode, $createTextNode, defineExtension, $getRoot, $caretFromPoint, $setSelectionFromCaretRange, $getCaretRange, $normalizeCaret, $getChildCaret, $getCaretInDirection, $isParagraphNode, $isLineBreakNode, $createParagraphNode, $isElementNode, $isRootOrShadowRoot, $isRootNode, $createNodeSelection, $isDecoratorNode, $isTextNode, $getSiblingCaret, $rewindSiblingCaret, $splitAtPointCaretNext, $normalizeSelection__EXPERIMENTAL, $isChildCaret, $isTextPointCaret, $isExtendableTextPointCaret, $isSiblingCaret, $getCommonAncestor, $findMatchingParent, TextNode, createCommand, COMMAND_PRIORITY_EDITOR, $getEditor, $getNodeByKey, HISTORY_MERGE_TAG, SKIP_SCROLL_INTO_VIEW_TAG, $cloneWithProperties, $getNearestRootOrShadowRoot, $createRangeSelection, $setSelection, createState, COMMAND_PRIORITY_NORMAL, $getState, $setState, $hasUpdateTag, PASTE_TAG, FORMAT_TEXT_COMMAND, UNDO_COMMAND, REDO_COMMAND, KEY_ARROW_RIGHT_COMMAND, KEY_TAB_COMMAND, INSERT_LINE_BREAK_COMMAND, COMMAND_PRIORITY_HIGH, INSERT_PARAGRAPH_COMMAND, OUTDENT_CONTENT_COMMAND, INDENT_CONTENT_COMMAND, $isNodeSelection, KEY_ARROW_LEFT_COMMAND, KEY_ARROW_UP_COMMAND, KEY_ARROW_DOWN_COMMAND, DELETE_CHARACTER_COMMAND, SELECTION_CHANGE_COMMAND, COMMAND_PRIORITY_CRITICAL, $addUpdateTag, CLICK_COMMAND, isDOMNode, $getNearestNodeFromDOMNode, ElementNode, $splitNode, $getChildCaretAtIndex, $createLineBreakNode, SELECTION_INSERT_CLIPBOARD_NODES_COMMAND, PASTE_COMMAND, $onUpdate, ParagraphNode, RootNode, DRAGSTART_COMMAND, DROP_COMMAND, mergeRegister as mergeRegister$1, $createRangeSelectionFromDom, CLEAR_HISTORY_COMMAND, KEY_ENTER_COMMAND, CONTROLLED_TEXT_INSERTION_COMMAND, KEY_SPACE_COMMAND, INPUT_COMMAND, KEY_BACKSPACE_COMMAND, KEY_DOWN_COMMAND } from 'lexical';
|
|
5
5
|
import * as lexical from 'lexical';
|
|
6
6
|
export { lexical as Lexical };
|
|
7
7
|
import { LinkNode, $createAutoLinkNode, $toggleLink, $createLinkNode, $isLinkNode, AutoLinkNode } from '@lexical/link';
|
|
@@ -5938,6 +5938,149 @@ BaseNodeInserter.for = (selection) => {
|
|
|
5938
5938
|
return inserterClass ? new inserterClass(selection) : selection
|
|
5939
5939
|
};
|
|
5940
5940
|
|
|
5941
|
+
// Word and Outlook never emit <ul>/<ol>/<li>. They simulate lists with flat
|
|
5942
|
+
// paragraphs that declare their depth in an inline `mso-list:l0 level2 lfo1`
|
|
5943
|
+
// and carry the bullet or number glyph in a <span style="mso-list:Ignore">.
|
|
5944
|
+
// Nothing downstream understands either, so the items land as plain paragraphs
|
|
5945
|
+
// with the marker baked into the text. Rebuild real lists from the level each
|
|
5946
|
+
// paragraph declares.
|
|
5947
|
+
//
|
|
5948
|
+
// Everything this reads lives in inline attributes, so it stays correct after
|
|
5949
|
+
// the pasted content formatter has dropped Office's style sheet.
|
|
5950
|
+
class OfficeFormatter {
|
|
5951
|
+
constructor(doc) {
|
|
5952
|
+
this.doc = doc;
|
|
5953
|
+
}
|
|
5954
|
+
|
|
5955
|
+
format() {
|
|
5956
|
+
for (const run of this.#listRuns()) {
|
|
5957
|
+
this.#replaceWithLists(run);
|
|
5958
|
+
}
|
|
5959
|
+
}
|
|
5960
|
+
|
|
5961
|
+
// A run is a stretch of Office list paragraphs that are siblings with nothing
|
|
5962
|
+
// but whitespace and Word's conditional comments between them. Anything else
|
|
5963
|
+
// in between (a heading, a plain paragraph) ends one list and starts another.
|
|
5964
|
+
#listRuns() {
|
|
5965
|
+
const runs = [];
|
|
5966
|
+
const claimed = new Set();
|
|
5967
|
+
|
|
5968
|
+
for (const paragraph of this.doc.querySelectorAll("p[style*='mso-list']")) {
|
|
5969
|
+
if (claimed.has(paragraph) || this.#listLevel(paragraph) === null) continue
|
|
5970
|
+
|
|
5971
|
+
const run = [];
|
|
5972
|
+
let node = paragraph;
|
|
5973
|
+
while (node) {
|
|
5974
|
+
run.push(node);
|
|
5975
|
+
claimed.add(node);
|
|
5976
|
+
node = this.#nextListParagraph(node);
|
|
5977
|
+
}
|
|
5978
|
+
runs.push(run);
|
|
5979
|
+
}
|
|
5980
|
+
|
|
5981
|
+
return runs
|
|
5982
|
+
}
|
|
5983
|
+
|
|
5984
|
+
#nextListParagraph(paragraph) {
|
|
5985
|
+
let sibling = paragraph.nextSibling;
|
|
5986
|
+
while (sibling && this.#isIgnorableBetweenListParagraphs(sibling)) {
|
|
5987
|
+
sibling = sibling.nextSibling;
|
|
5988
|
+
}
|
|
5989
|
+
|
|
5990
|
+
if (sibling && sibling.nodeType === Node.ELEMENT_NODE && this.#listLevel(sibling) !== null) {
|
|
5991
|
+
return sibling
|
|
5992
|
+
}
|
|
5993
|
+
|
|
5994
|
+
return null
|
|
5995
|
+
}
|
|
5996
|
+
|
|
5997
|
+
#isIgnorableBetweenListParagraphs(node) {
|
|
5998
|
+
if (node.nodeType === Node.COMMENT_NODE) {
|
|
5999
|
+
return true
|
|
6000
|
+
}
|
|
6001
|
+
return node.nodeType === Node.TEXT_NODE && node.textContent.trim() === ""
|
|
6002
|
+
}
|
|
6003
|
+
|
|
6004
|
+
// Word numbers levels from 1, but a copied selection often starts partway down
|
|
6005
|
+
// a list, so every paragraph declares level3 with no level1 above it. Anchoring
|
|
6006
|
+
// the run at its own shallowest level is what keeps the result from arriving
|
|
6007
|
+
// needlessly deep.
|
|
6008
|
+
#replaceWithLists(paragraphs) {
|
|
6009
|
+
const levels = paragraphs.map((paragraph) => this.#listLevel(paragraph));
|
|
6010
|
+
const baseLevel = Math.min(...levels);
|
|
6011
|
+
const roots = [];
|
|
6012
|
+
const stack = [];
|
|
6013
|
+
|
|
6014
|
+
paragraphs.forEach((paragraph, index) => {
|
|
6015
|
+
const depth = levels[index] - baseLevel + 1;
|
|
6016
|
+
const tagName = this.#listTagName(paragraph);
|
|
6017
|
+
|
|
6018
|
+
while (stack.length > depth) {
|
|
6019
|
+
stack.pop();
|
|
6020
|
+
}
|
|
6021
|
+
if (stack.length === depth && stack[depth - 1].tagName !== tagName.toUpperCase()) {
|
|
6022
|
+
stack.pop();
|
|
6023
|
+
}
|
|
6024
|
+
|
|
6025
|
+
while (stack.length < depth) {
|
|
6026
|
+
const list = this.doc.createElement(tagName);
|
|
6027
|
+
if (stack.length === 0) {
|
|
6028
|
+
roots.push(list);
|
|
6029
|
+
} else {
|
|
6030
|
+
this.#lastItemOf(stack[stack.length - 1]).appendChild(list);
|
|
6031
|
+
}
|
|
6032
|
+
stack.push(list);
|
|
6033
|
+
}
|
|
6034
|
+
|
|
6035
|
+
stack[stack.length - 1].appendChild(this.#createListItem(paragraph));
|
|
6036
|
+
});
|
|
6037
|
+
|
|
6038
|
+
paragraphs[0].replaceWith(...roots);
|
|
6039
|
+
for (const paragraph of paragraphs.slice(1)) {
|
|
6040
|
+
paragraph.remove();
|
|
6041
|
+
}
|
|
6042
|
+
}
|
|
6043
|
+
|
|
6044
|
+
#lastItemOf(list) {
|
|
6045
|
+
if (!list.lastElementChild) {
|
|
6046
|
+
list.appendChild(this.doc.createElement("li"));
|
|
6047
|
+
}
|
|
6048
|
+
return list.lastElementChild
|
|
6049
|
+
}
|
|
6050
|
+
|
|
6051
|
+
// Word writes bullets as the raw glyph of the marker font (Symbol's "·",
|
|
6052
|
+
// Wingdings' "§", Courier New's "o") and numbers as the rendered counter
|
|
6053
|
+
// ("1.", "a)", "iv)"). A bare letter with no delimiter after it is Courier
|
|
6054
|
+
// New's bullet, not a counter, so the delimiter is what tells them apart.
|
|
6055
|
+
#listTagName(paragraph) {
|
|
6056
|
+
const marker = paragraph.querySelector("[style*='mso-list:Ignore']");
|
|
6057
|
+
const text = marker?.textContent || "";
|
|
6058
|
+
|
|
6059
|
+
if (/\d/.test(text) || /^\(?[A-Za-z]+[.)]/.test(text)) {
|
|
6060
|
+
return "ol"
|
|
6061
|
+
}
|
|
6062
|
+
return "ul"
|
|
6063
|
+
}
|
|
6064
|
+
|
|
6065
|
+
#createListItem(paragraph) {
|
|
6066
|
+
for (const marker of paragraph.querySelectorAll("[style*='mso-list:Ignore']")) {
|
|
6067
|
+
marker.remove();
|
|
6068
|
+
}
|
|
6069
|
+
|
|
6070
|
+
const item = this.doc.createElement("li");
|
|
6071
|
+
item.append(...paragraph.childNodes);
|
|
6072
|
+
return item
|
|
6073
|
+
}
|
|
6074
|
+
|
|
6075
|
+
#listLevel(element) {
|
|
6076
|
+
const match = /mso-list:[^;"']*\blevel(\d+)/.exec(element.getAttribute("style") || "");
|
|
6077
|
+
if (match) {
|
|
6078
|
+
return parseInt(match[1], 10)
|
|
6079
|
+
}
|
|
6080
|
+
return null
|
|
6081
|
+
}
|
|
6082
|
+
}
|
|
6083
|
+
|
|
5941
6084
|
class PastedContentFormatter {
|
|
5942
6085
|
constructor(doc) {
|
|
5943
6086
|
this.doc = doc;
|
|
@@ -5947,6 +6090,7 @@ class PastedContentFormatter {
|
|
|
5947
6090
|
this.#stripStyleElements();
|
|
5948
6091
|
this.#unwrapPlaceholderAnchors();
|
|
5949
6092
|
this.#stripTableCellColorStyles();
|
|
6093
|
+
new OfficeFormatter(this.doc).format();
|
|
5950
6094
|
this.#unwrapWrappedListChildren();
|
|
5951
6095
|
this.#nestStrayListChildren();
|
|
5952
6096
|
this.#stripStrayListChildren();
|
|
@@ -6105,6 +6249,20 @@ class Contents {
|
|
|
6105
6249
|
}, { tag });
|
|
6106
6250
|
}
|
|
6107
6251
|
|
|
6252
|
+
// A literal "\n" stays visible in the editor, which Lexical renders with white-space: pre-wrap,
|
|
6253
|
+
// but collapses to a space once serialized to HTML, so it has to become line break nodes.
|
|
6254
|
+
insertTextWithLineBreaks(text) {
|
|
6255
|
+
const normalizedText = text?.replace(/\r\n?/g, "\n");
|
|
6256
|
+
const selection = $getSelection();
|
|
6257
|
+
|
|
6258
|
+
if (normalizedText?.includes("\n") && $isRangeSelection(selection)) {
|
|
6259
|
+
selection.insertRawText(normalizedText);
|
|
6260
|
+
return true
|
|
6261
|
+
} else {
|
|
6262
|
+
return false
|
|
6263
|
+
}
|
|
6264
|
+
}
|
|
6265
|
+
|
|
6108
6266
|
insertAtCursor(...nodes) {
|
|
6109
6267
|
const selection = this.#insertableSelection();
|
|
6110
6268
|
const inserter = BaseNodeInserter.for(selection);
|
|
@@ -9096,6 +9254,7 @@ class LexicalEditorElement extends HTMLElement {
|
|
|
9096
9254
|
#initialize() {
|
|
9097
9255
|
this.#registerComponents();
|
|
9098
9256
|
this.#handleEnter();
|
|
9257
|
+
this.#handleReplacementText();
|
|
9099
9258
|
this.#registerFocusEvents();
|
|
9100
9259
|
this.#registerHistoryEvents();
|
|
9101
9260
|
this.#registerFileAcceptFilter();
|
|
@@ -9359,6 +9518,14 @@ class LexicalEditorElement extends HTMLElement {
|
|
|
9359
9518
|
));
|
|
9360
9519
|
}
|
|
9361
9520
|
|
|
9521
|
+
#handleReplacementText() {
|
|
9522
|
+
this.#listeners.track(this.editor.registerCommand(
|
|
9523
|
+
CONTROLLED_TEXT_INSERTION_COMMAND,
|
|
9524
|
+
(event) => event instanceof InputEvent && this.contents.insertTextWithLineBreaks(event.data),
|
|
9525
|
+
COMMAND_PRIORITY_NORMAL
|
|
9526
|
+
));
|
|
9527
|
+
}
|
|
9528
|
+
|
|
9362
9529
|
#registerFocusEvents() {
|
|
9363
9530
|
this.#listeners.track(
|
|
9364
9531
|
registerEventListener(this, "focusin", this.#handleFocusIn),
|