@37signals/lexxy 0.7.5-beta → 0.8.0-beta
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 +2296 -1678
- package/dist/lexxy_helpers.esm.js +10 -1
- package/dist/stylesheets/lexxy-content.css +172 -141
- package/dist/stylesheets/lexxy-editor.css +340 -195
- package/package.json +2 -2
package/dist/lexxy.esm.js
CHANGED
|
@@ -9,8 +9,8 @@ import 'prismjs/components/prism-bash';
|
|
|
9
9
|
import 'prismjs/components/prism-json';
|
|
10
10
|
import 'prismjs/components/prism-diff';
|
|
11
11
|
import DOMPurify from 'dompurify';
|
|
12
|
-
import { getStyleObjectFromCSS, getCSSFromStyleObject, $getSelectionStyleValueForProperty, $patchStyleText } from '@lexical/selection';
|
|
13
|
-
import { SKIP_DOM_SELECTION_TAG, $getSelection, $isRangeSelection, DecoratorNode, $
|
|
12
|
+
import { getStyleObjectFromCSS, getCSSFromStyleObject, $isAtNodeEnd, $getSelectionStyleValueForProperty, $patchStyleText } from '@lexical/selection';
|
|
13
|
+
import { SKIP_DOM_SELECTION_TAG, $getSelection, $isRangeSelection, DecoratorNode, $createNodeSelection, HISTORY_MERGE_TAG, SKIP_SCROLL_INTO_VIEW_TAG, $isTextNode, $createParagraphNode, TextNode, createCommand, createState, defineExtension, COMMAND_PRIORITY_NORMAL, $getState, $setState, $hasUpdateTag, PASTE_TAG, FORMAT_TEXT_COMMAND, $createTextNode, $isRootOrShadowRoot, UNDO_COMMAND, REDO_COMMAND, PASTE_COMMAND, COMMAND_PRIORITY_LOW, KEY_TAB_COMMAND, OUTDENT_CONTENT_COMMAND, INDENT_CONTENT_COMMAND, $getEditor, $getNearestRootOrShadowRoot, $isNodeSelection, $getRoot, $isLineBreakNode, $isElementNode, KEY_ARROW_LEFT_COMMAND, KEY_ARROW_RIGHT_COMMAND, KEY_ARROW_UP_COMMAND, KEY_ARROW_DOWN_COMMAND, DELETE_CHARACTER_COMMAND, SELECTION_CHANGE_COMMAND, CLICK_COMMAND, isDOMNode, $getNearestNodeFromDOMNode, $isDecoratorNode, $setSelection, KEY_ENTER_COMMAND, COMMAND_PRIORITY_HIGH, $isParagraphNode, ElementNode, $splitNode, $getNodeByKey, $createLineBreakNode, ParagraphNode, RootNode, CLEAR_HISTORY_COMMAND, $addUpdateTag, COMMAND_PRIORITY_CRITICAL, KEY_SPACE_COMMAND, KEY_BACKSPACE_COMMAND, KEY_DOWN_COMMAND } from 'lexical';
|
|
14
14
|
import { buildEditorFromExtensions } from '@lexical/extension';
|
|
15
15
|
import { ListNode, INSERT_UNORDERED_LIST_COMMAND, INSERT_ORDERED_LIST_COMMAND, ListItemNode, $getListDepth, $isListItemNode, $isListNode, $createListNode, registerList } from '@lexical/list';
|
|
16
16
|
import { $createAutoLinkNode, $toggleLink, LinkNode, $createLinkNode, AutoLinkNode, $isLinkNode } from '@lexical/link';
|
|
@@ -20,10 +20,10 @@ import { $generateNodesFromDOM, $generateHtmlFromNodes } from '@lexical/html';
|
|
|
20
20
|
import { $isCodeNode, CodeNode, normalizeCodeLang, CodeHighlightNode, registerCodeHighlighting, CODE_LANGUAGE_FRIENDLY_NAME_MAP } from '@lexical/code';
|
|
21
21
|
import { registerMarkdownShortcuts, TRANSFORMERS } from '@lexical/markdown';
|
|
22
22
|
import { createEmptyHistoryState, registerHistory } from '@lexical/history';
|
|
23
|
-
import { createElement, createAttachmentFigure, isPreviewableImage, parseHtml,
|
|
23
|
+
import { createElement, createAttachmentFigure, isPreviewableImage, dispatch, parseHtml, addBlockSpacing, generateDomId } from './lexxy_helpers.esm.js';
|
|
24
24
|
export { highlightCode as highlightAll, highlightCode } from './lexxy_helpers.esm.js';
|
|
25
|
-
import { $getNearestNodeOfType, mergeRegister, $insertFirst, $firstToLastIterator, $descendantsMatching } from '@lexical/utils';
|
|
26
25
|
import { INSERT_TABLE_COMMAND, $getTableCellNodeFromLexicalNode, TableCellNode, TableNode, TableRowNode, registerTablePlugin, registerTableSelectionObserver, setScrollableTablesActive, TableCellHeaderStates, $insertTableRowAtSelection, $insertTableColumnAtSelection, $deleteTableRowAtSelection, $deleteTableColumnAtSelection, $findTableNode, $getTableRowIndexFromTableCellNode, $getTableColumnIndexFromTableCellNode, $findCellNode, $getElementForTableNode } from '@lexical/table';
|
|
26
|
+
import { $getNearestNodeOfType, $wrapNodeInElement, mergeRegister, $descendantsMatching, $insertFirst, $unwrapAndFilterDescendants, $firstToLastIterator } from '@lexical/utils';
|
|
27
27
|
import { marked } from 'marked';
|
|
28
28
|
import { $insertDataTransferForRichText } from '@lexical/clipboard';
|
|
29
29
|
|
|
@@ -106,7 +106,7 @@ var Lexxy = {
|
|
|
106
106
|
}
|
|
107
107
|
};
|
|
108
108
|
|
|
109
|
-
const ALLOWED_HTML_TAGS = [ "a", "b", "blockquote", "br", "code", "em",
|
|
109
|
+
const ALLOWED_HTML_TAGS = [ "a", "b", "blockquote", "br", "code", "div", "em",
|
|
110
110
|
"figcaption", "figure", "h1", "h2", "h3", "h4", "h5", "h6", "hr", "i", "img", "li", "mark", "ol", "p", "pre", "q", "s", "strong", "ul", "table", "tbody", "tr", "th", "td" ];
|
|
111
111
|
|
|
112
112
|
const ALLOWED_HTML_ATTRIBUTES = [ "alt", "caption", "class", "content", "content-type", "contenteditable",
|
|
@@ -243,6 +243,94 @@ function isActiveAndVisible(element) {
|
|
|
243
243
|
return element && !element.disabled && element.checkVisibility()
|
|
244
244
|
}
|
|
245
245
|
|
|
246
|
+
var ToolbarIcons = {
|
|
247
|
+
"bold":
|
|
248
|
+
`<svg viewBox="0 0 18 18" xmlns="http://www.w3.org/2000/svg">
|
|
249
|
+
<path fill-rule="evenodd" clip-rule="evenodd" d="M9.05273 1.88232C10.6866 1.88237 12.0033 2.20353 12.9529 2.89673L13.1272 3.0293C13.974 3.70864 14.4008 4.63245 14.4009 5.76562C14.4008 6.49354 14.2316 7.15281 13.8845 7.73145C13.6683 8.09188 13.3997 8.40162 13.0818 8.66016C13.5902 8.92606 14.0196 9.28599 14.3635 9.74121C14.8586 10.3834 15.0945 11.1743 15.0945 12.0879C15.0944 13.3698 14.5922 14.3931 13.5879 15.1106L13.5857 15.1128C12.5967 15.805 11.196 16.125 9.43799 16.125H3.10547V1.88232L9.05273 1.88232ZM6.36108 13.4084H9.28418C10.224 13.4084 10.8634 13.2491 11.2581 12.9851C11.6259 12.7389 11.8198 12.3768 11.8198 11.8367C11.8197 11.2968 11.6259 10.9351 11.2581 10.689C10.8634 10.425 10.2241 10.2649 9.28418 10.2649H6.36108V13.4084ZM6.36108 7.56812H8.78247C9.5163 7.56809 10.0547 7.45371 10.429 7.25757L10.5791 7.16895C10.9438 6.92178 11.1255 6.57934 11.1255 6.09302C11.1254 5.59017 10.9414 5.25227 10.5835 5.02002L10.5784 5.01636L10.5732 5.01343C10.1994 4.75387 9.61878 4.59818 8.78247 4.59814H6.36108V7.56812Z"/>
|
|
250
|
+
</svg>`,
|
|
251
|
+
|
|
252
|
+
"italic":
|
|
253
|
+
`<svg viewBox="0 0 18 18" xmlns="http://www.w3.org/2000/svg">
|
|
254
|
+
<path d="M14.1379 3.91187L14.1086 4.06421H11.4668L9.49805 13.9431H12.0981L11.7473 15.7852L11.7188 15.9375H4.16675L4.51758 14.0955L4.54614 13.9431H7.18799L9.17505 4.06421H6.55664L6.90747 2.22217L6.93677 2.06982H14.4888L14.1379 3.91187Z"/>
|
|
255
|
+
</svg>`,
|
|
256
|
+
|
|
257
|
+
"strikethrough":
|
|
258
|
+
`<svg viewBox="0 0 18 18" xmlns="http://www.w3.org/2000/svg">
|
|
259
|
+
<path d="M14.3723 11.8015C14.3771 11.8858 14.3811 11.9756 14.3811 12.0681C14.3811 12.811 14.1777 13.4959 13.7725 14.1174L13.7717 14.1189C13.3624 14.7329 12.7463 15.2162 11.9377 15.5742L11.9348 15.5757C11.1214 15.9223 10.1306 16.092 8.96997 16.092C7.9356 16.092 6.93308 15.9348 5.96338 15.6204L5.96045 15.6189C5.00593 15.292 4.24112 14.8699 3.67676 14.3459L3.57568 14.2522L3.63501 14.1277L4.45605 12.397L4.64282 12.5654C5.13492 13.0083 5.76733 13.3759 6.54492 13.6648C7.33475 13.9406 8.14322 14.0786 8.96997 14.0786C10.0731 14.0786 10.8638 13.8932 11.3708 13.5513C11.8757 13.1982 12.1172 12.7464 12.1172 12.1838C12.1172 12.0662 12.1049 11.9556 12.0828 11.8513L12.0344 11.625H14.3621L14.3723 11.8015Z"/>
|
|
260
|
+
<path d="M9.2981 1.91602C10.111 1.91604 10.9109 2.02122 11.6975 2.23096C12.4855 2.44111 13.1683 2.74431 13.7417 3.14429L13.8655 3.23071L13.8083 3.36987L13.1726 4.91235L13.0869 5.1189L12.8987 4.99878C12.3487 4.64881 11.761 4.38633 11.1365 4.21143L11.1328 4.20996C10.585 4.04564 10.0484 3.95419 9.52295 3.93384L9.2981 3.92944C8.22329 3.92944 7.44693 4.12611 6.94043 4.49121C6.44619 4.85665 6.20874 5.31616 6.20874 5.88135L6.21533 6.03296C6.24495 6.37662 6.37751 6.65526 6.61011 6.87964L6.72144 6.97632C6.98746 7.19529 7.30625 7.37584 7.68018 7.51538L8.05151 7.63184C8.45325 7.75061 8.94669 7.87679 9.53247 8.01123L9.53467 8.01196C10.1213 8.15305 10.6426 8.29569 11.0991 8.4375H15C15.5178 8.4375 15.9375 8.85723 15.9375 9.375C15.9375 9.89277 15.5178 10.3125 15 10.3125H3C2.48223 10.3125 2.0625 9.89277 2.0625 9.375C2.0625 8.85723 2.48223 8.4375 3 8.4375H4.93726C4.83783 8.34526 4.74036 8.24896 4.64795 8.146L4.64502 8.14233C4.1721 7.58596 3.94482 6.85113 3.94482 5.95825C3.94483 5.20441 4.14059 4.51965 4.53369 3.90967L4.53516 3.90747C4.94397 3.29427 5.55262 2.81114 6.34863 2.45288C7.15081 2.0919 8.13683 1.91602 9.2981 1.91602Z"/>
|
|
261
|
+
</svg>`,
|
|
262
|
+
|
|
263
|
+
"heading":
|
|
264
|
+
`<svg viewBox="0 0 18 18" xmlns="http://www.w3.org/2000/svg">
|
|
265
|
+
<path d="M11.5 2C12.0523 2 12.5 2.44772 12.5 3V3.5C12.5 4.05228 12.0523 4.5 11.5 4.5H8V15C8 15.5523 7.55228 16 7 16H6.5C5.94772 16 5.5 15.5523 5.5 15V4.5H2C1.44772 4.5 1 4.05228 1 3.5V3C1 2.44772 1.44772 2 2 2H11.5ZM16 7C16.5523 7 17 7.44772 17 8V8.5C17 9.05228 16.5523 9.5 16 9.5H15V15C15 15.5523 14.5523 16 14 16H13.5C12.9477 16 12.5 15.5523 12.5 15V9.5H11.5C10.9477 9.5 10.5 9.05228 10.5 8.5V8C10.5 7.44772 10.9477 7 11.5 7H16Z"/>
|
|
266
|
+
</svg>`,
|
|
267
|
+
|
|
268
|
+
"highlight":
|
|
269
|
+
`<svg viewBox="0 0 18 18" xmlns="http://www.w3.org/2000/svg">
|
|
270
|
+
<path d="M16.4564 14.4272C17.1356 15.5592 16.3204 17.0002 15.0003 17.0004C13.68 17.0004 12.864 15.5593 13.5433 14.4272L15.0003 12.0004L16.4564 14.4272ZM5.1214 1.70746C5.51192 1.31693 6.14494 1.31693 6.53546 1.70746L9.7171 4.8891L13.2532 8.42426C14.2295 9.40056 14.2295 10.9841 13.2532 11.9604L9.7171 15.4955C8.74078 16.4718 7.15822 16.4718 6.18195 15.4955L2.64679 11.9604C1.67048 10.9841 1.67048 9.40057 2.64679 8.42426L6.18195 4.8891C6.30299 4.76805 6.43323 4.66177 6.57062 4.57074L5.1214 3.12152C4.73091 2.73104 4.73099 2.09799 5.1214 1.70746ZM8.30304 6.30316C8.10776 6.10815 7.79119 6.10799 7.59601 6.30316L4.06085 9.83929L3.9964 9.91742C3.88661 10.0838 3.88645 10.3019 3.9964 10.4682L4.02277 10.5004H11.8763C12.0312 10.3043 12.02 10.0205 11.8392 9.83929L8.30304 6.30316Z"/>
|
|
271
|
+
</svg>`,
|
|
272
|
+
|
|
273
|
+
"link":
|
|
274
|
+
`<svg viewBox="0 0 18 18" xmlns="http://www.w3.org/2000/svg">
|
|
275
|
+
<path d="M12.8885 7.23091L13.9479 6.17155C14.5337 5.58576 14.5337 4.63602 13.9479 4.05023C13.3621 3.46444 12.4124 3.46444 11.8266 4.05023L8.29235 7.58446C7.9263 7.95051 7.90312 8.52994 8.2233 8.92271L8.36141 9.07463C8.68158 9.4674 8.65841 10.0468 8.29235 10.4129C7.90183 10.8034 7.26866 10.8034 6.87814 10.4129C5.70657 9.24131 5.70657 7.34182 6.87814 6.17025L10.4124 2.63602C11.7792 1.26918 13.9953 1.26918 15.3621 2.63602C16.729 4.00285 16.729 6.21893 15.3621 7.58576L14.3028 8.64512C13.9122 9.03564 13.2791 9.03564 12.8885 8.64512C12.498 8.2546 12.498 7.62143 12.8885 7.23091Z"/>
|
|
276
|
+
<path d="M5.11038 10.7664L4.04843 11.8284C3.46264 12.4142 3.46264 13.3639 4.04842 13.9497C4.63421 14.5355 5.58396 14.5355 6.16975 13.9497L9.70657 10.4129C10.0726 10.0468 10.0958 9.46741 9.77563 9.07464L9.63752 8.92272C9.31734 8.52995 9.34052 7.95052 9.70657 7.58446C10.0971 7.19394 10.7303 7.19394 11.1208 7.58446C12.2924 8.75604 12.2924 10.6555 11.1208 11.8271L7.58396 15.3639C6.21712 16.7308 4.00105 16.7308 2.63421 15.3639C1.26738 13.9971 1.26738 11.781 2.63421 10.4142L3.69617 9.35223C4.08669 8.96171 4.71986 8.96171 5.11038 9.35223C5.5009 9.74275 5.5009 10.3759 5.11038 10.7664Z"/>
|
|
277
|
+
</svg>`,
|
|
278
|
+
|
|
279
|
+
"quote":
|
|
280
|
+
`<svg viewBox="0 0 18 18" xmlns="http://www.w3.org/2000/svg">
|
|
281
|
+
<path d="M4.96387 4.23438C6.8769 4.23438 8.42767 5.78522 8.42773 7.69824C8.42773 8.32925 8.25769 8.92015 7.96289 9.42969L7.96387 9.43066L5.11816 14.3584C4.77659 14.95 4.02038 15.153 3.42871 14.8115C2.83701 14.4699 2.63397 13.7128 2.97559 13.1211L4.16113 11.0674C2.63532 10.7052 1.5 9.33485 1.5 7.69824C1.50006 5.78524 3.05086 4.2344 4.96387 4.23438ZM13.0361 4.23438C14.9491 4.23449 16.4999 5.7853 16.5 7.69824C16.5 8.32921 16.3299 8.92017 16.0352 9.42969L16.0361 9.43066L13.1904 14.3584C12.8488 14.9501 12.0917 15.1531 11.5 14.8115C10.9085 14.4698 10.7063 13.7127 11.0479 13.1211L12.2324 11.0674C10.7069 10.7049 9.57227 9.33461 9.57227 7.69824C9.57233 5.78522 11.1231 4.23438 13.0361 4.23438Z"/>
|
|
282
|
+
</svg>`,
|
|
283
|
+
|
|
284
|
+
"code":
|
|
285
|
+
`<svg viewBox="0 0 18 18" xmlns="http://www.w3.org/2000/svg">
|
|
286
|
+
<path d="M6.29289 3.79295C6.68342 3.40243 7.31643 3.40243 7.70696 3.79295C8.09748 4.18348 8.09748 4.81649 7.70696 5.20702L3.91399 8.99999L7.70696 12.793C8.09748 13.1835 8.09748 13.8165 7.70696 14.207C7.31643 14.5975 6.68342 14.5975 6.29289 14.207L1.79289 9.70702C1.40237 9.31649 1.40237 8.68348 1.79289 8.29295L6.29289 3.79295Z"/>
|
|
287
|
+
<path d="M11.707 3.79295C11.3164 3.40243 10.6834 3.40243 10.2929 3.79295C9.90237 4.18348 9.90237 4.81649 10.2929 5.20702L14.0859 8.99999L10.2929 12.793C9.90237 13.1835 9.90237 13.8165 10.2929 14.207C10.6834 14.5975 11.3164 14.5975 11.707 14.207L16.207 9.70702C16.5975 9.31649 16.5975 8.68348 16.207 8.29295L11.707 3.79295Z"/>
|
|
288
|
+
</svg>`,
|
|
289
|
+
|
|
290
|
+
"ul":
|
|
291
|
+
`<svg viewBox="0 0 18 18" xmlns="http://www.w3.org/2000/svg">
|
|
292
|
+
<path d="M3 12.5C3.82843 12.5 4.5 13.1716 4.5 14C4.5 14.8284 3.82843 15.5 3 15.5C2.17157 15.5 1.5 14.8284 1.5 14C1.5 13.1716 2.17157 12.5 3 12.5ZM15.5 13C16.0523 13 16.5 13.4477 16.5 14C16.5 14.5523 16.0523 15 15.5 15H7C6.44772 15 6 14.5523 6 14C6 13.4477 6.44772 13 7 13H15.5ZM3 7.5C3.82843 7.5 4.5 8.17157 4.5 9C4.5 9.82843 3.82843 10.5 3 10.5C2.17157 10.5 1.5 9.82843 1.5 9C1.5 8.17157 2.17157 7.5 3 7.5ZM15.5 8C16.0523 8 16.5 8.44772 16.5 9C16.5 9.55228 16.0523 10 15.5 10H7C6.44772 10 6 9.55228 6 9C6 8.44772 6.44772 8 7 8H15.5ZM3 2.5C3.82843 2.5 4.5 3.17157 4.5 4C4.5 4.82843 3.82843 5.5 3 5.5C2.17157 5.5 1.5 4.82843 1.5 4C1.5 3.17157 2.17157 2.5 3 2.5ZM15.5 3C16.0523 3 16.5 3.44772 16.5 4C16.5 4.55228 16.0523 5 15.5 5H7C6.44772 5 6 4.55228 6 4C6 3.44772 6.44772 3 7 3H15.5Z"/>
|
|
293
|
+
</svg>`,
|
|
294
|
+
|
|
295
|
+
"ol":
|
|
296
|
+
`<svg viewBox="0 0 18 18" xmlns="http://www.w3.org/2000/svg">
|
|
297
|
+
<path d="M15.5 13C16.0523 13 16.5 13.4477 16.5 14C16.5 14.5523 16.0523 15 15.5 15H7C6.44772 15 6 14.5523 6 14C6 13.4477 6.44772 13 7 13H15.5ZM15.5 8C16.0523 8 16.5 8.44772 16.5 9C16.5 9.55228 16.0523 10 15.5 10H7C6.44772 10 6 9.55228 6 9C6 8.44772 6.44772 8 7 8H15.5ZM15.5 3C16.0523 3 16.5 3.44772 16.5 4C16.5 4.55228 16.0523 5 15.5 5H7C6.44772 5 6 4.55228 6 4C6 3.44772 6.44772 3 7 3H15.5Z"/>
|
|
298
|
+
<path d="M2.98657 16.0967C2.68042 16.0967 2.41187 16.0465 2.18091 15.9463C1.95174 15.846 1.77002 15.7046 1.63574 15.522C1.50146 15.3376 1.42448 15.1227 1.40479 14.8774L1.4021 14.8452H2.34204L2.34741 14.8748C2.35815 14.9589 2.39038 15.035 2.44409 15.103C2.49959 15.1711 2.5721 15.2248 2.66162 15.2642C2.75293 15.3035 2.86035 15.3232 2.98389 15.3232C3.10563 15.3232 3.21037 15.3027 3.2981 15.2615C3.38761 15.2185 3.45654 15.1603 3.50488 15.0869C3.55322 15.0135 3.57739 14.9294 3.57739 14.8345V14.8291C3.57739 14.6715 3.51921 14.5516 3.40283 14.4692C3.28646 14.3869 3.12085 14.3457 2.90601 14.3457H2.48706V13.677H2.90063C3.02775 13.677 3.13607 13.6582 3.22559 13.6206C3.31689 13.583 3.38672 13.5302 3.43506 13.4622C3.48519 13.3941 3.51025 13.3153 3.51025 13.2258V13.2205C3.51025 13.1256 3.48877 13.0441 3.4458 12.9761C3.40462 12.9062 3.34375 12.8534 3.26318 12.8176C3.18441 12.78 3.08952 12.7612 2.97852 12.7612C2.86572 12.7612 2.76636 12.7809 2.68042 12.8203C2.59627 12.8579 2.52913 12.9125 2.479 12.9841C2.43066 13.054 2.40112 13.1363 2.39038 13.2312L2.3877 13.2581H1.49341L1.49609 13.2205C1.514 12.977 1.58561 12.7666 1.71094 12.5894C1.83805 12.4103 2.00903 12.2725 2.22388 12.1758C2.44051 12.0773 2.69206 12.0281 2.97852 12.0281C3.27393 12.0281 3.52995 12.0728 3.74658 12.1624C3.96322 12.2501 4.13062 12.3727 4.24878 12.5303C4.36694 12.6878 4.42603 12.8722 4.42603 13.0835V13.0889C4.42603 13.2518 4.38932 13.3941 4.31592 13.5159C4.2443 13.6358 4.14762 13.7343 4.02588 13.8113C3.90592 13.8883 3.77254 13.942 3.62573 13.9724V13.9912C3.91756 14.0199 4.14941 14.1121 4.32129 14.2678C4.49316 14.4236 4.5791 14.6295 4.5791 14.8855V14.8909C4.5791 15.1344 4.51375 15.3474 4.38306 15.53C4.25236 15.7109 4.06795 15.8505 3.82983 15.949C3.59172 16.0474 3.31063 16.0967 2.98657 16.0967Z"/>
|
|
299
|
+
<path d="M1.54443 11V10.342L2.76099 9.20874C2.95076 9.03507 3.09757 8.89274 3.20142 8.78174C3.30705 8.66895 3.37956 8.57316 3.41895 8.49438C3.46012 8.41382 3.48071 8.33415 3.48071 8.25537V8.24463C3.48071 8.14795 3.46012 8.0638 3.41895 7.99219C3.37777 7.92057 3.31779 7.86507 3.23901 7.82568C3.16024 7.7863 3.06714 7.7666 2.95972 7.7666C2.84692 7.7666 2.74756 7.78988 2.66162 7.83643C2.57747 7.88298 2.51123 7.94743 2.46289 8.02979C2.41455 8.11035 2.39038 8.20345 2.39038 8.30908V8.33057L1.48804 8.32788V8.31177C1.48804 8.05396 1.5507 7.82837 1.67603 7.63501C1.80314 7.44165 1.97949 7.29126 2.20508 7.18384C2.43245 7.07463 2.69653 7.02002 2.99731 7.02002C3.28556 7.02002 3.53711 7.06836 3.75195 7.16504C3.96859 7.25993 4.13688 7.39331 4.25684 7.56519C4.37858 7.73706 4.43945 7.93758 4.43945 8.16675V8.18018C4.43945 8.3252 4.40902 8.46932 4.34814 8.61255C4.28727 8.75578 4.18701 8.90885 4.04736 9.07178C3.90771 9.23291 3.71883 9.41642 3.48071 9.62231L2.58374 10.4092L2.85498 9.98486V10.4092L2.58374 10.2319H4.49048V11H1.54443Z"/>
|
|
300
|
+
<path d="M2.84155 6V3.01367H2.79053L1.85596 3.64478V2.79614L2.84155 2.12476H3.82715V6H2.84155Z"/>
|
|
301
|
+
</svg>`,
|
|
302
|
+
|
|
303
|
+
"attachment":
|
|
304
|
+
`<svg viewBox="0 0 18 18" xmlns="http://www.w3.org/2000/svg">
|
|
305
|
+
<path d="M13 13.5V6C13 4.067 11.433 2.5 9.5 2.5C7.567 2.5 6 4.067 6 6V13.5C6 14.6046 6.89543 15.5 8 15.5H8.23047C9.20759 15.5 10 14.7076 10 13.7305V7C10 6.72386 9.77614 6.5 9.5 6.5C9.22386 6.5 9 6.72386 9 7V12.5C9 13.0523 8.55228 13.5 8 13.5C7.44772 13.5 7 13.0523 7 12.5V7C7 5.61929 8.11929 4.5 9.5 4.5C10.8807 4.5 12 5.61929 12 7V13.7305C12 15.8122 10.3122 17.5 8.23047 17.5H8C5.79086 17.5 4 15.7091 4 13.5V6C4 2.96243 6.46243 0.5 9.5 0.5C12.5376 0.5 15 2.96243 15 6V13.5C15 14.0523 14.5523 14.5 14 14.5C13.4477 14.5 13 14.0523 13 13.5Z"/>
|
|
306
|
+
</svg>`,
|
|
307
|
+
|
|
308
|
+
"table":
|
|
309
|
+
`<svg viewBox="0 0 18 18" xmlns="http://www.w3.org/2000/svg">
|
|
310
|
+
<path d="M15 1C16.1046 1 17 1.89543 17 3V15C17 16.1046 16.1046 17 15 17H3C1.89543 17 1 16.1046 1 15V3C1 1.89543 1.89543 1 3 1H15ZM3 15H8V10H3V15ZM10 10V15H15V10H10ZM10 8H15V3H10V8ZM3 8H8V3H3V8Z"/>
|
|
311
|
+
</svg>`,
|
|
312
|
+
|
|
313
|
+
"hr":
|
|
314
|
+
`<svg viewBox="0 0 18 18" xmlns="http://www.w3.org/2000/svg">
|
|
315
|
+
<path d="M12.75 12C13.1642 12 13.5 12.3358 13.5 12.75V14.25C13.5 14.6642 13.1642 15 12.75 15H5.25C4.83579 15 4.5 14.6642 4.5 14.25V12.75C4.5 12.3358 4.83579 12 5.25 12H12.75ZM15.4863 8C16.0461 8 16.5 8.44771 16.5 9C16.5 9.55229 16.0461 10 15.4863 10H2.51367C1.95392 10 1.5 9.55229 1.5 9C1.5 8.44771 1.95392 8 2.51367 8H15.4863ZM12.75 3C13.1642 3 13.5 3.33579 13.5 3.75V5.25C13.5 5.66421 13.1642 6 12.75 6H5.25C4.83579 6 4.5 5.66421 4.5 5.25V3.75C4.5 3.33579 4.83579 3 5.25 3H12.75Z"/>
|
|
316
|
+
</svg>`,
|
|
317
|
+
|
|
318
|
+
"undo":
|
|
319
|
+
`<svg viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
|
|
320
|
+
<path d="M8.36612 5.36612C8.85427 4.87796 9.64554 4.87796 10.1337 5.36612C10.6218 5.85428 10.6218 6.64557 10.1337 7.13369L7.26748 9.9999H15.2499C18.1494 9.99996 20.4999 12.3504 20.4999 15.2499V19.2499C20.4999 19.9402 19.9402 20.4999 19.2499 20.4999C18.5596 20.4999 18 19.9402 17.9999 19.2499V15.2499C17.9999 13.7312 16.7686 12.5 15.2499 12.4999H7.26748L10.1337 15.3661C10.6218 15.8543 10.6218 16.6456 10.1337 17.1337C9.64557 17.6218 8.85428 17.6218 8.36612 17.1337L3.36612 12.1337C2.87796 11.6455 2.87796 10.8543 3.36612 10.3661L8.36612 5.36612Z"/>
|
|
321
|
+
</svg>`,
|
|
322
|
+
|
|
323
|
+
"redo":
|
|
324
|
+
`<svg viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
|
|
325
|
+
<path d="M15.6338 5.1163C15.1456 4.62814 14.3543 4.62814 13.8662 5.1163C13.3781 5.60446 13.3781 6.39575 13.8662 6.88388L16.7324 9.75009H8.74997C5.85052 9.75014 3.49997 12.1006 3.49997 15.0001V19.0001C3.50002 19.6904 4.05969 20.25 4.74997 20.2501C5.4403 20.2501 5.99992 19.6904 5.99997 19.0001V15.0001C5.99997 13.4813 7.23123 12.2501 8.74997 12.2501H16.7324L13.8662 15.1163C13.3781 15.6045 13.3781 16.3958 13.8662 16.8839C14.3543 17.372 15.1456 17.3719 15.6338 16.8839L20.6338 11.8839C21.1219 11.3957 21.1219 10.6045 20.6338 10.1163L15.6338 5.1163Z" />
|
|
326
|
+
</svg>`,
|
|
327
|
+
|
|
328
|
+
"overflow":
|
|
329
|
+
`<svg viewBox="0 0 18 18" xmlns="http://www.w3.org/2000/svg">
|
|
330
|
+
<path d="M3 6.75C4.24264 6.75 5.25 7.75736 5.25 9C5.25 10.2426 4.24264 11.25 3 11.25C1.75736 11.25 0.75 10.2426 0.75 9C0.75 7.75736 1.75736 6.75 3 6.75ZM9 6.75C10.2426 6.75 11.25 7.75736 11.25 9C11.25 10.2426 10.2426 11.25 9 11.25C7.75736 11.25 6.75 10.2426 6.75 9C6.75 7.75736 7.75736 6.75 9 6.75ZM15 6.75C16.2426 6.75 17.25 7.75736 17.25 9C17.25 10.2426 16.2426 11.25 15 11.25C13.7574 11.25 12.75 10.2426 12.75 9C12.75 7.75736 13.7574 6.75 15 6.75Z"/>
|
|
331
|
+
</svg>`
|
|
332
|
+
};
|
|
333
|
+
|
|
246
334
|
class LexicalToolbarElement extends HTMLElement {
|
|
247
335
|
static observedAttributes = [ "connected" ]
|
|
248
336
|
|
|
@@ -556,22 +644,24 @@ class LexicalToolbarElement extends HTMLElement {
|
|
|
556
644
|
static get defaultTemplate() {
|
|
557
645
|
return `
|
|
558
646
|
<button class="lexxy-editor__toolbar-button" type="button" name="bold" data-command="bold" title="Bold">
|
|
559
|
-
|
|
647
|
+
${ToolbarIcons.bold}
|
|
560
648
|
</button>
|
|
561
649
|
|
|
562
650
|
<button class="lexxy-editor__toolbar-button" type="button" name="italic" data-command="italic" title="Italic">
|
|
563
|
-
|
|
651
|
+
${ToolbarIcons.italic}
|
|
652
|
+
</button>
|
|
653
|
+
|
|
654
|
+
<button class="lexxy-editor__toolbar-button lexxy-editor__toolbar-group-end" type="button" name="strikethrough" data-command="strikethrough" title="Strikethrough">
|
|
655
|
+
${ToolbarIcons.strikethrough}
|
|
564
656
|
</button>
|
|
565
657
|
|
|
566
|
-
<button class="lexxy-editor__toolbar-button" type="button" name="
|
|
567
|
-
|
|
568
|
-
<path fill-rule="evenodd" clip-rule="evenodd" d="M4.70588 16.1591C4.81459 19.7901 7.48035 22 11.6668 22C15.9854 22 18.724 19.6296 18.724 15.8779C18.724 15.5007 18.6993 15.1427 18.6474 14.8066H14.3721C14.8637 15.2085 15.0799 15.7037 15.0799 16.3471C15.0799 17.7668 13.7532 18.7984 11.8113 18.7984C9.88053 18.7984 8.38582 17.7531 8.21659 16.1591H4.70588ZM5.23953 9.31962H9.88794C9.10723 8.88889 8.75888 8.33882 8.75888 7.57339C8.75888 6.13992 9.96576 5.18793 11.7631 5.18793C13.5852 5.18793 14.8761 6.1797 14.9959 7.81344H18.4102C18.3485 4.31824 15.8038 2 11.752 2C7.867 2 5.09129 4.35802 5.09129 7.92044C5.09129 8.41838 5.14071 8.88477 5.23953 9.31962ZM2.23529 10.6914C1.90767 10.6914 1.59347 10.8359 1.36181 11.0931C1.13015 11.3504 1 11.6993 1 12.0631C1 12.4269 1.13015 12.7758 1.36181 13.0331C1.59347 13.2903 1.90767 13.4348 2.23529 13.4348H20.7647C21.0923 13.4348 21.4065 13.2903 21.6382 13.0331C21.8699 12.7758 22 12.4269 22 12.0631C22 11.6993 21.8699 11.3504 21.6382 11.0931C21.4065 10.8359 21.0923 10.6914 20.7647 10.6914H2.23529Z"/>
|
|
569
|
-
</svg>
|
|
658
|
+
<button class="lexxy-editor__toolbar-button" type="button" name="heading" data-command="rotateHeadingFormat" title="Heading">
|
|
659
|
+
${ToolbarIcons.heading}
|
|
570
660
|
</button>
|
|
571
661
|
|
|
572
662
|
<details class="lexxy-editor__toolbar-dropdown" name="lexxy-dropdown">
|
|
573
663
|
<summary class="lexxy-editor__toolbar-button" name="highlight" title="Color highlight">
|
|
574
|
-
|
|
664
|
+
${ToolbarIcons.highlight}
|
|
575
665
|
</summary>
|
|
576
666
|
<lexxy-highlight-dropdown class="lexxy-editor__toolbar-dropdown-content">
|
|
577
667
|
<div class="lexxy-highlight-colors"></div>
|
|
@@ -581,7 +671,7 @@ class LexicalToolbarElement extends HTMLElement {
|
|
|
581
671
|
|
|
582
672
|
<details class="lexxy-editor__toolbar-dropdown" name="lexxy-dropdown">
|
|
583
673
|
<summary class="lexxy-editor__toolbar-button" name="link" title="Link" data-hotkey="cmd+k ctrl+k">
|
|
584
|
-
|
|
674
|
+
${ToolbarIcons.link}
|
|
585
675
|
</summary>
|
|
586
676
|
<lexxy-link-dropdown class="lexxy-editor__toolbar-dropdown-content">
|
|
587
677
|
<form method="dialog">
|
|
@@ -595,49 +685,45 @@ class LexicalToolbarElement extends HTMLElement {
|
|
|
595
685
|
</details>
|
|
596
686
|
|
|
597
687
|
<button class="lexxy-editor__toolbar-button" type="button" name="quote" data-command="insertQuoteBlock" title="Quote">
|
|
598
|
-
|
|
599
|
-
</button>
|
|
600
|
-
|
|
601
|
-
<button class="lexxy-editor__toolbar-button" type="button" name="heading" data-command="rotateHeadingFormat" title="Heading">
|
|
602
|
-
<svg viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path d="M15.322 5.315H9.64V22H5.684V5.315H0v-3.31h15.322v3.31z"/><path d="M23.957 11.79H19.92V22h-3.402V11.79H12.48V9.137h11.477v2.653z"/></svg>
|
|
688
|
+
${ToolbarIcons.quote}
|
|
603
689
|
</button>
|
|
604
690
|
|
|
605
|
-
<button class="lexxy-editor__toolbar-button" type="button" name="code" data-command="insertCodeBlock" title="Code">
|
|
606
|
-
|
|
691
|
+
<button class="lexxy-editor__toolbar-button lexxy-editor__toolbar-group-end" type="button" name="code" data-command="insertCodeBlock" title="Code">
|
|
692
|
+
${ToolbarIcons.code}
|
|
607
693
|
</button>
|
|
608
694
|
|
|
609
695
|
<button class="lexxy-editor__toolbar-button" type="button" name="unordered-list" data-command="insertUnorderedList" title="Bullet list">
|
|
610
|
-
|
|
696
|
+
${ToolbarIcons.ul}
|
|
611
697
|
</button>
|
|
612
698
|
|
|
613
|
-
<button class="lexxy-editor__toolbar-button" type="button" name="ordered-list" data-command="insertOrderedList" title="Numbered list">
|
|
614
|
-
|
|
699
|
+
<button class="lexxy-editor__toolbar-button lexxy-editor__toolbar-group-end" type="button" name="ordered-list" data-command="insertOrderedList" title="Numbered list">
|
|
700
|
+
${ToolbarIcons.ol}
|
|
615
701
|
</button>
|
|
616
702
|
|
|
617
703
|
<button class="lexxy-editor__toolbar-button" type="button" name="upload" data-command="uploadAttachments" title="Upload file">
|
|
618
|
-
|
|
704
|
+
${ToolbarIcons.attachment}
|
|
619
705
|
</button>
|
|
620
706
|
|
|
621
707
|
<button class="lexxy-editor__toolbar-button" type="button" name="table" data-command="insertTable" title="Insert a table">
|
|
622
|
-
|
|
708
|
+
${ToolbarIcons.table}
|
|
623
709
|
</button>
|
|
624
710
|
|
|
625
711
|
<button class="lexxy-editor__toolbar-button" type="button" name="divider" data-command="insertHorizontalDivider" title="Insert a divider">
|
|
626
|
-
|
|
712
|
+
${ToolbarIcons.hr}
|
|
627
713
|
</button>
|
|
628
714
|
|
|
629
715
|
<div class="lexxy-editor__toolbar-spacer" role="separator"></div>
|
|
630
716
|
|
|
631
717
|
<button class="lexxy-editor__toolbar-button" type="button" name="undo" data-command="undo" title="Undo">
|
|
632
|
-
|
|
718
|
+
${ToolbarIcons.undo}
|
|
633
719
|
</button>
|
|
634
720
|
|
|
635
721
|
<button class="lexxy-editor__toolbar-button" type="button" name="redo" data-command="redo" title="Redo">
|
|
636
|
-
|
|
722
|
+
${ToolbarIcons.redo}
|
|
637
723
|
</button>
|
|
638
724
|
|
|
639
725
|
<details class="lexxy-editor__toolbar-dropdown lexxy-editor__toolbar-overflow" name="lexxy-dropdown">
|
|
640
|
-
<summary class="lexxy-editor__toolbar-button" aria-label="Show more toolbar buttons"
|
|
726
|
+
<summary class="lexxy-editor__toolbar-button" aria-label="Show more toolbar buttons">${ToolbarIcons.overflow}</summary>
|
|
641
727
|
<div class="lexxy-editor__toolbar-dropdown-content lexxy-editor__toolbar-overflow-menu" aria-label="More toolbar buttons"></div>
|
|
642
728
|
</details>
|
|
643
729
|
`
|
|
@@ -717,127 +803,54 @@ var theme = {
|
|
|
717
803
|
}
|
|
718
804
|
};
|
|
719
805
|
|
|
720
|
-
|
|
721
|
-
if (bytes === 0) return "0 B"
|
|
722
|
-
const sizes = [ "B", "KB", "MB", "GB", "TB", "PB" ];
|
|
723
|
-
const i = Math.floor(Math.log(bytes) / Math.log(1024));
|
|
724
|
-
const value = bytes / Math.pow(1024, i);
|
|
725
|
-
return `${ value.toFixed(2) } ${ sizes[i] }`
|
|
726
|
-
}
|
|
727
|
-
|
|
728
|
-
class ActionTextAttachmentNode extends DecoratorNode {
|
|
806
|
+
class HorizontalDividerNode extends DecoratorNode {
|
|
729
807
|
static getType() {
|
|
730
|
-
return "
|
|
808
|
+
return "horizontal_divider"
|
|
731
809
|
}
|
|
732
810
|
|
|
733
811
|
static clone(node) {
|
|
734
|
-
return new
|
|
812
|
+
return new HorizontalDividerNode(node.__key)
|
|
735
813
|
}
|
|
736
814
|
|
|
737
815
|
static importJSON(serializedNode) {
|
|
738
|
-
return new
|
|
816
|
+
return new HorizontalDividerNode()
|
|
739
817
|
}
|
|
740
818
|
|
|
741
819
|
static importDOM() {
|
|
742
820
|
return {
|
|
743
|
-
|
|
744
|
-
return {
|
|
745
|
-
conversion: (attachment) => ({
|
|
746
|
-
node: new ActionTextAttachmentNode({
|
|
747
|
-
sgid: attachment.getAttribute("sgid"),
|
|
748
|
-
src: attachment.getAttribute("url"),
|
|
749
|
-
previewable: attachment.getAttribute("previewable"),
|
|
750
|
-
altText: attachment.getAttribute("alt"),
|
|
751
|
-
caption: attachment.getAttribute("caption"),
|
|
752
|
-
contentType: attachment.getAttribute("content-type"),
|
|
753
|
-
fileName: attachment.getAttribute("filename"),
|
|
754
|
-
fileSize: attachment.getAttribute("filesize"),
|
|
755
|
-
width: attachment.getAttribute("width"),
|
|
756
|
-
height: attachment.getAttribute("height")
|
|
757
|
-
})
|
|
758
|
-
}), priority: 1
|
|
759
|
-
}
|
|
760
|
-
},
|
|
761
|
-
"img": () => {
|
|
762
|
-
return {
|
|
763
|
-
conversion: (img) => ({
|
|
764
|
-
node: new ActionTextAttachmentNode({
|
|
765
|
-
src: img.getAttribute("src"),
|
|
766
|
-
caption: img.getAttribute("alt") || "",
|
|
767
|
-
contentType: "image/*",
|
|
768
|
-
width: img.getAttribute("width"),
|
|
769
|
-
height: img.getAttribute("height")
|
|
770
|
-
})
|
|
771
|
-
}), priority: 1
|
|
772
|
-
}
|
|
773
|
-
},
|
|
774
|
-
"video": () => {
|
|
821
|
+
"hr": (hr) => {
|
|
775
822
|
return {
|
|
776
|
-
conversion: (
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
return {
|
|
782
|
-
node: new ActionTextAttachmentNode({
|
|
783
|
-
src: videoSource,
|
|
784
|
-
fileName: fileName,
|
|
785
|
-
contentType: contentType
|
|
786
|
-
})
|
|
787
|
-
}
|
|
788
|
-
}, priority: 1
|
|
823
|
+
conversion: () => ({
|
|
824
|
+
node: new HorizontalDividerNode()
|
|
825
|
+
}),
|
|
826
|
+
priority: 1
|
|
789
827
|
}
|
|
790
828
|
}
|
|
791
829
|
}
|
|
792
830
|
}
|
|
793
831
|
|
|
794
|
-
|
|
795
|
-
return Lexxy.global.get("attachmentTagName")
|
|
796
|
-
}
|
|
797
|
-
|
|
798
|
-
constructor({ tagName, sgid, src, previewable, altText, caption, contentType, fileName, fileSize, width, height }, key) {
|
|
832
|
+
constructor(key) {
|
|
799
833
|
super(key);
|
|
800
|
-
|
|
801
|
-
this.tagName = tagName || ActionTextAttachmentNode.TAG_NAME;
|
|
802
|
-
this.sgid = sgid;
|
|
803
|
-
this.src = src;
|
|
804
|
-
this.previewable = previewable;
|
|
805
|
-
this.altText = altText || "";
|
|
806
|
-
this.caption = caption || "";
|
|
807
|
-
this.contentType = contentType || "";
|
|
808
|
-
this.fileName = fileName || "";
|
|
809
|
-
this.fileSize = fileSize;
|
|
810
|
-
this.width = width;
|
|
811
|
-
this.height = height;
|
|
812
|
-
|
|
813
|
-
this.editor = $getEditor();
|
|
814
834
|
}
|
|
815
835
|
|
|
816
836
|
createDOM() {
|
|
817
|
-
const figure =
|
|
837
|
+
const figure = createElement("figure", { className: "horizontal-divider" });
|
|
838
|
+
const hr = createElement("hr");
|
|
818
839
|
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
figure.appendChild(this.#createDOMForFile());
|
|
824
|
-
figure.appendChild(this.#createDOMForNotImage());
|
|
825
|
-
}
|
|
840
|
+
figure.appendChild(hr);
|
|
841
|
+
|
|
842
|
+
const deleteButton = createElement("lexxy-node-delete-button");
|
|
843
|
+
figure.appendChild(deleteButton);
|
|
826
844
|
|
|
827
845
|
return figure
|
|
828
846
|
}
|
|
829
847
|
|
|
830
|
-
updateDOM(
|
|
831
|
-
|
|
832
|
-
if (caption && this.caption) {
|
|
833
|
-
caption.value = this.caption;
|
|
834
|
-
}
|
|
835
|
-
|
|
836
|
-
return false
|
|
848
|
+
updateDOM() {
|
|
849
|
+
return true
|
|
837
850
|
}
|
|
838
851
|
|
|
839
852
|
getTextContent() {
|
|
840
|
-
return
|
|
853
|
+
return "┄\n\n"
|
|
841
854
|
}
|
|
842
855
|
|
|
843
856
|
isInline() {
|
|
@@ -845,135 +858,23 @@ class ActionTextAttachmentNode extends DecoratorNode {
|
|
|
845
858
|
}
|
|
846
859
|
|
|
847
860
|
exportDOM() {
|
|
848
|
-
const
|
|
849
|
-
|
|
850
|
-
previewable: this.previewable || null,
|
|
851
|
-
url: this.src,
|
|
852
|
-
alt: this.altText,
|
|
853
|
-
caption: this.caption,
|
|
854
|
-
"content-type": this.contentType,
|
|
855
|
-
filename: this.fileName,
|
|
856
|
-
filesize: this.fileSize,
|
|
857
|
-
width: this.width,
|
|
858
|
-
height: this.height,
|
|
859
|
-
presentation: "gallery"
|
|
860
|
-
});
|
|
861
|
-
|
|
862
|
-
return { element: attachment }
|
|
861
|
+
const hr = createElement("hr");
|
|
862
|
+
return { element: hr }
|
|
863
863
|
}
|
|
864
864
|
|
|
865
865
|
exportJSON() {
|
|
866
866
|
return {
|
|
867
|
-
type: "
|
|
868
|
-
version: 1
|
|
869
|
-
tagName: this.tagName,
|
|
870
|
-
sgid: this.sgid,
|
|
871
|
-
src: this.src,
|
|
872
|
-
previewable: this.previewable,
|
|
873
|
-
altText: this.altText,
|
|
874
|
-
caption: this.caption,
|
|
875
|
-
contentType: this.contentType,
|
|
876
|
-
fileName: this.fileName,
|
|
877
|
-
fileSize: this.fileSize,
|
|
878
|
-
width: this.width,
|
|
879
|
-
height: this.height
|
|
867
|
+
type: "horizontal_divider",
|
|
868
|
+
version: 1
|
|
880
869
|
}
|
|
881
870
|
}
|
|
882
871
|
|
|
883
872
|
decorate() {
|
|
884
873
|
return null
|
|
885
874
|
}
|
|
886
|
-
|
|
887
|
-
createAttachmentFigure() {
|
|
888
|
-
return createAttachmentFigure(this.contentType, this.isPreviewableAttachment, this.fileName)
|
|
889
|
-
}
|
|
890
|
-
|
|
891
|
-
get #isPreviewableImage() {
|
|
892
|
-
return isPreviewableImage(this.contentType)
|
|
893
|
-
}
|
|
894
|
-
|
|
895
|
-
get isPreviewableAttachment() {
|
|
896
|
-
return this.#isPreviewableImage || this.previewable
|
|
897
|
-
}
|
|
898
|
-
|
|
899
|
-
#createDOMForImage() {
|
|
900
|
-
return createElement("img", { src: this.src, alt: this.altText, ...this.#imageDimensions })
|
|
901
|
-
}
|
|
902
|
-
|
|
903
|
-
get #imageDimensions() {
|
|
904
|
-
if (this.width && this.height) {
|
|
905
|
-
return { width: this.width, height: this.height }
|
|
906
|
-
} else {
|
|
907
|
-
return {}
|
|
908
|
-
}
|
|
909
|
-
}
|
|
910
|
-
|
|
911
|
-
#createDOMForFile() {
|
|
912
|
-
const extension = this.fileName ? this.fileName.split(".").pop().toLowerCase() : "unknown";
|
|
913
|
-
return createElement("span", { className: "attachment__icon", textContent: `${extension}` })
|
|
914
|
-
}
|
|
915
|
-
|
|
916
|
-
#createDOMForNotImage() {
|
|
917
|
-
const figcaption = createElement("figcaption", { className: "attachment__caption" });
|
|
918
|
-
|
|
919
|
-
const nameTag = createElement("strong", { className: "attachment__name", textContent: this.caption || this.fileName });
|
|
920
|
-
|
|
921
|
-
figcaption.appendChild(nameTag);
|
|
922
|
-
|
|
923
|
-
if (this.fileSize) {
|
|
924
|
-
const sizeSpan = createElement("span", { className: "attachment__size", textContent: bytesToHumanSize(this.fileSize) });
|
|
925
|
-
figcaption.appendChild(sizeSpan);
|
|
926
|
-
}
|
|
927
|
-
|
|
928
|
-
return figcaption
|
|
929
|
-
}
|
|
930
|
-
|
|
931
|
-
#createEditableCaption() {
|
|
932
|
-
const caption = createElement("figcaption", { className: "attachment__caption" });
|
|
933
|
-
const input = createElement("textarea", {
|
|
934
|
-
value: this.caption,
|
|
935
|
-
placeholder: this.fileName,
|
|
936
|
-
rows: "1"
|
|
937
|
-
});
|
|
938
|
-
|
|
939
|
-
input.addEventListener("focusin", () => input.placeholder = "Add caption...");
|
|
940
|
-
input.addEventListener("blur", (event) => this.#handleCaptionInputBlurred(event));
|
|
941
|
-
input.addEventListener("keydown", (event) => this.#handleCaptionInputKeydown(event));
|
|
942
|
-
|
|
943
|
-
caption.appendChild(input);
|
|
944
|
-
|
|
945
|
-
return caption
|
|
946
|
-
}
|
|
947
|
-
|
|
948
|
-
#handleCaptionInputBlurred(event) {
|
|
949
|
-
this.#updateCaptionValueFromInput(event.target);
|
|
950
|
-
}
|
|
951
|
-
|
|
952
|
-
#updateCaptionValueFromInput(input) {
|
|
953
|
-
input.placeholder = this.fileName;
|
|
954
|
-
this.editor.update(() => {
|
|
955
|
-
this.getWritable().caption = input.value;
|
|
956
|
-
});
|
|
957
|
-
}
|
|
958
|
-
|
|
959
|
-
#handleCaptionInputKeydown(event) {
|
|
960
|
-
if (event.key === "Enter") {
|
|
961
|
-
event.preventDefault();
|
|
962
|
-
event.stopPropagation();
|
|
963
|
-
event.target.blur();
|
|
964
|
-
|
|
965
|
-
this.editor.update(() => {
|
|
966
|
-
// Place the cursor after the current image
|
|
967
|
-
this.selectNext(0, 0);
|
|
968
|
-
}, {
|
|
969
|
-
tag: HISTORY_MERGE_TAG
|
|
970
|
-
});
|
|
971
|
-
}
|
|
972
|
-
|
|
973
|
-
}
|
|
974
875
|
}
|
|
975
876
|
|
|
976
|
-
const SILENT_UPDATE_TAGS = [ HISTORY_MERGE_TAG,
|
|
877
|
+
const SILENT_UPDATE_TAGS = [ HISTORY_MERGE_TAG, SKIP_SCROLL_INTO_VIEW_TAG ];
|
|
977
878
|
|
|
978
879
|
function $createNodeSelectionWith(...nodes) {
|
|
979
880
|
const selection = $createNodeSelection();
|
|
@@ -981,11 +882,34 @@ function $createNodeSelectionWith(...nodes) {
|
|
|
981
882
|
return selection
|
|
982
883
|
}
|
|
983
884
|
|
|
885
|
+
function $makeSafeForRoot(node) {
|
|
886
|
+
if ($isTextNode(node)) {
|
|
887
|
+
return $wrapNodeInElement(node, $createParagraphNode)
|
|
888
|
+
} else if (node.isParentRequired()) {
|
|
889
|
+
const parent = node.createRequiredParent();
|
|
890
|
+
return $wrapNodeInElement(node, parent)
|
|
891
|
+
} else {
|
|
892
|
+
return node
|
|
893
|
+
}
|
|
894
|
+
}
|
|
895
|
+
|
|
984
896
|
function getListType(node) {
|
|
985
897
|
const list = $getNearestNodeOfType(node, ListNode);
|
|
986
898
|
return list?.getListType() ?? null
|
|
987
899
|
}
|
|
988
900
|
|
|
901
|
+
function $isAtNodeEdge(point, atStart = null) {
|
|
902
|
+
if (atStart === null) {
|
|
903
|
+
return $isAtNodeEdge(point, true) || $isAtNodeEdge(point, false)
|
|
904
|
+
} else {
|
|
905
|
+
return atStart ? $isAtNodeStart(point) : $isAtNodeEnd(point)
|
|
906
|
+
}
|
|
907
|
+
}
|
|
908
|
+
|
|
909
|
+
function $isAtNodeStart(point) {
|
|
910
|
+
return point.offset === 0
|
|
911
|
+
}
|
|
912
|
+
|
|
989
913
|
function extendTextNodeConversion(conversionName, ...callbacks) {
|
|
990
914
|
return extendConversion(TextNode, conversionName, (conversionOutput, element) => ({
|
|
991
915
|
...conversionOutput,
|
|
@@ -1017,1928 +941,2452 @@ function extendConversion(nodeKlass, conversionName, callback = (output => outpu
|
|
|
1017
941
|
}
|
|
1018
942
|
}
|
|
1019
943
|
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
const reader = new FileReader();
|
|
1023
|
-
|
|
1024
|
-
image.addEventListener("load", () => {
|
|
1025
|
-
resolve(image);
|
|
1026
|
-
});
|
|
944
|
+
function isSelectionHighlighted(selection) {
|
|
945
|
+
if (!$isRangeSelection(selection)) return false
|
|
1027
946
|
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
947
|
+
if (selection.isCollapsed()) {
|
|
948
|
+
return hasHighlightStyles(selection.style)
|
|
949
|
+
} else {
|
|
950
|
+
return selection.hasFormat("highlight")
|
|
951
|
+
}
|
|
952
|
+
}
|
|
1031
953
|
|
|
1032
|
-
|
|
1033
|
-
|
|
954
|
+
function hasHighlightStyles(cssOrStyles) {
|
|
955
|
+
const styles = typeof cssOrStyles === "string" ? getStyleObjectFromCSS(cssOrStyles) : cssOrStyles;
|
|
956
|
+
return !!(styles.color || styles["background-color"])
|
|
1034
957
|
}
|
|
1035
958
|
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
return
|
|
1039
|
-
}
|
|
959
|
+
function applyCanonicalizers(styles, canonicalizers = []) {
|
|
960
|
+
return canonicalizers.reduce((css, canonicalizer) => {
|
|
961
|
+
return canonicalizer.applyCanonicalization(css)
|
|
962
|
+
}, styles)
|
|
963
|
+
}
|
|
1040
964
|
|
|
1041
|
-
|
|
1042
|
-
|
|
965
|
+
class StyleCanonicalizer {
|
|
966
|
+
constructor(property, allowedValues= []) {
|
|
967
|
+
this._property = property;
|
|
968
|
+
this._allowedValues = allowedValues;
|
|
969
|
+
this._canonicalValues = this.#allowedValuesIdentityObject;
|
|
1043
970
|
}
|
|
1044
971
|
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
}
|
|
972
|
+
applyCanonicalization(css) {
|
|
973
|
+
const styles = { ...getStyleObjectFromCSS(css) };
|
|
1048
974
|
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
975
|
+
styles[this._property] = this.getCanonicalAllowedValue(styles[this._property]);
|
|
976
|
+
if (!styles[this._property]) {
|
|
977
|
+
delete styles[this._property];
|
|
978
|
+
}
|
|
1053
979
|
|
|
1054
|
-
|
|
1055
|
-
const { file, uploadUrl, blobUrlTemplate, progress, width, height, uploadError } = node;
|
|
1056
|
-
super({ ...node, contentType: file.type }, key);
|
|
1057
|
-
this.file = file;
|
|
1058
|
-
this.uploadUrl = uploadUrl;
|
|
1059
|
-
this.blobUrlTemplate = blobUrlTemplate;
|
|
1060
|
-
this.progress = progress ?? null;
|
|
1061
|
-
this.width = width;
|
|
1062
|
-
this.height = height;
|
|
1063
|
-
this.uploadError = uploadError;
|
|
980
|
+
return getCSSFromStyleObject(styles)
|
|
1064
981
|
}
|
|
1065
982
|
|
|
1066
|
-
|
|
1067
|
-
|
|
983
|
+
getCanonicalAllowedValue(value) {
|
|
984
|
+
return this._canonicalValues[value] ||= this.#resolveCannonicalValue(value)
|
|
985
|
+
}
|
|
1068
986
|
|
|
1069
|
-
|
|
1070
|
-
// uploads through cloning. The upload is guarded from restarting in case the
|
|
1071
|
-
// node is reloaded from saved state such as from history.
|
|
1072
|
-
this.#startUploadIfNeeded();
|
|
987
|
+
// Private
|
|
1073
988
|
|
|
1074
|
-
|
|
989
|
+
get #allowedValuesIdentityObject() {
|
|
990
|
+
return this._allowedValues.reduce((object, value) => ({ ...object, [value]: value }), {})
|
|
991
|
+
}
|
|
1075
992
|
|
|
1076
|
-
|
|
1077
|
-
|
|
993
|
+
#resolveCannonicalValue(value) {
|
|
994
|
+
let index = this.#computedAllowedValues.indexOf(value);
|
|
995
|
+
index ||= this.#computedAllowedValues.indexOf(getComputedStyleForProperty(this._property, value));
|
|
996
|
+
return index === -1 ? null : this._allowedValues[index]
|
|
997
|
+
}
|
|
1078
998
|
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
999
|
+
get #computedAllowedValues() {
|
|
1000
|
+
return this._computedAllowedValues ||= this._allowedValues.map(
|
|
1001
|
+
value => getComputedStyleForProperty(this._property, value)
|
|
1002
|
+
)
|
|
1003
|
+
}
|
|
1004
|
+
}
|
|
1084
1005
|
|
|
1085
|
-
|
|
1086
|
-
|
|
1006
|
+
function getComputedStyleForProperty(property, value) {
|
|
1007
|
+
const style = `${property}: ${value};`;
|
|
1087
1008
|
|
|
1088
|
-
|
|
1089
|
-
}
|
|
1009
|
+
// the element has to be attached to the DOM have computed styles
|
|
1010
|
+
const element = document.body.appendChild(createElement("span", { style: "display: none;" + style }));
|
|
1011
|
+
const computedStyle = window.getComputedStyle(element).getPropertyValue(property);
|
|
1012
|
+
element.remove();
|
|
1090
1013
|
|
|
1091
|
-
|
|
1092
|
-
|
|
1014
|
+
return computedStyle
|
|
1015
|
+
}
|
|
1093
1016
|
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
progress.value = this.progress ?? 0;
|
|
1097
|
-
}
|
|
1017
|
+
class LexxyExtension {
|
|
1018
|
+
#editorElement
|
|
1098
1019
|
|
|
1099
|
-
|
|
1020
|
+
constructor(editorElement) {
|
|
1021
|
+
this.#editorElement = editorElement;
|
|
1100
1022
|
}
|
|
1101
1023
|
|
|
1102
|
-
|
|
1103
|
-
return
|
|
1024
|
+
get editorElement() {
|
|
1025
|
+
return this.#editorElement
|
|
1104
1026
|
}
|
|
1105
1027
|
|
|
1106
|
-
|
|
1107
|
-
return
|
|
1108
|
-
...super.exportJSON(),
|
|
1109
|
-
type: "action_text_attachment_upload",
|
|
1110
|
-
version: 1,
|
|
1111
|
-
uploadUrl: this.uploadUrl,
|
|
1112
|
-
blobUrlTemplate: this.blobUrlTemplate,
|
|
1113
|
-
progress: this.progress,
|
|
1114
|
-
width: this.width,
|
|
1115
|
-
height: this.height,
|
|
1116
|
-
uploadError: this.uploadError
|
|
1117
|
-
}
|
|
1028
|
+
get editorConfig() {
|
|
1029
|
+
return this.#editorElement.config
|
|
1118
1030
|
}
|
|
1119
1031
|
|
|
1120
|
-
|
|
1121
|
-
|
|
1032
|
+
// optional: defaults to true
|
|
1033
|
+
get enabled() {
|
|
1034
|
+
return true
|
|
1122
1035
|
}
|
|
1123
1036
|
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
figure.classList.add("attachment--error");
|
|
1127
|
-
figure.appendChild(createElement("div", { innerText: `Error uploading ${this.file?.name ?? "file"}` }));
|
|
1128
|
-
return figure
|
|
1037
|
+
get lexicalExtension() {
|
|
1038
|
+
return null
|
|
1129
1039
|
}
|
|
1130
1040
|
|
|
1131
|
-
|
|
1132
|
-
return createElement("img")
|
|
1133
|
-
}
|
|
1041
|
+
initializeToolbar(_lexxyToolbar) {
|
|
1134
1042
|
|
|
1135
|
-
#createDOMForFile() {
|
|
1136
|
-
const extension = this.#getFileExtension();
|
|
1137
|
-
const span = createElement("span", { className: "attachment__icon", textContent: extension });
|
|
1138
|
-
return span
|
|
1139
1043
|
}
|
|
1044
|
+
}
|
|
1140
1045
|
|
|
1141
|
-
|
|
1142
|
-
|
|
1046
|
+
const TOGGLE_HIGHLIGHT_COMMAND = createCommand();
|
|
1047
|
+
const REMOVE_HIGHLIGHT_COMMAND = createCommand();
|
|
1048
|
+
const BLANK_STYLES = { "color": null, "background-color": null };
|
|
1049
|
+
|
|
1050
|
+
const hasPastedStylesState = createState("hasPastedStyles", {
|
|
1051
|
+
parse: (value) => value || false
|
|
1052
|
+
});
|
|
1053
|
+
|
|
1054
|
+
class HighlightExtension extends LexxyExtension {
|
|
1055
|
+
get enabled() {
|
|
1056
|
+
return this.editorElement.supportsRichText
|
|
1143
1057
|
}
|
|
1144
1058
|
|
|
1145
|
-
|
|
1146
|
-
const
|
|
1059
|
+
get lexicalExtension() {
|
|
1060
|
+
const extension = defineExtension({
|
|
1061
|
+
dependencies: [ RichTextExtension ],
|
|
1062
|
+
name: "lexxy/highlight",
|
|
1063
|
+
config: {
|
|
1064
|
+
color: { buttons: [], permit: [] },
|
|
1065
|
+
"background-color": { buttons: [], permit: [] }
|
|
1066
|
+
},
|
|
1067
|
+
html: {
|
|
1068
|
+
import: {
|
|
1069
|
+
mark: $markConversion
|
|
1070
|
+
}
|
|
1071
|
+
},
|
|
1072
|
+
register(editor, config) {
|
|
1073
|
+
// keep the ref to the canonicalizers for optimized css conversion
|
|
1074
|
+
const canonicalizers = buildCanonicalizers(config);
|
|
1147
1075
|
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1076
|
+
return mergeRegister(
|
|
1077
|
+
editor.registerCommand(TOGGLE_HIGHLIGHT_COMMAND, $toggleSelectionStyles, COMMAND_PRIORITY_NORMAL),
|
|
1078
|
+
editor.registerCommand(REMOVE_HIGHLIGHT_COMMAND, () => $toggleSelectionStyles(BLANK_STYLES), COMMAND_PRIORITY_NORMAL),
|
|
1079
|
+
editor.registerNodeTransform(TextNode, $syncHighlightWithStyle),
|
|
1080
|
+
editor.registerNodeTransform(TextNode, (textNode) => $canonicalizePastedStyles(textNode, canonicalizers))
|
|
1081
|
+
)
|
|
1082
|
+
}
|
|
1083
|
+
});
|
|
1152
1084
|
|
|
1153
|
-
return
|
|
1085
|
+
return [ extension, this.editorConfig.get("highlight") ]
|
|
1154
1086
|
}
|
|
1087
|
+
}
|
|
1155
1088
|
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1089
|
+
function $applyHighlightStyle(textNode, element) {
|
|
1090
|
+
const elementStyles = {
|
|
1091
|
+
color: element.style?.color,
|
|
1092
|
+
"background-color": element.style?.backgroundColor
|
|
1093
|
+
};
|
|
1159
1094
|
|
|
1160
|
-
|
|
1161
|
-
|
|
1095
|
+
if ($hasUpdateTag(PASTE_TAG)) { $setPastedStyles(textNode); }
|
|
1096
|
+
const highlightStyle = getCSSFromStyleObject(elementStyles);
|
|
1162
1097
|
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
writable.width = width;
|
|
1166
|
-
writable.height = height;
|
|
1167
|
-
}, { tag: SILENT_UPDATE_TAGS });
|
|
1098
|
+
if (highlightStyle.length) {
|
|
1099
|
+
return textNode.setStyle(textNode.getStyle() + highlightStyle)
|
|
1168
1100
|
}
|
|
1101
|
+
}
|
|
1169
1102
|
|
|
1170
|
-
|
|
1171
|
-
|
|
1103
|
+
function $markConversion() {
|
|
1104
|
+
return {
|
|
1105
|
+
conversion: extendTextNodeConversion("mark", $applyHighlightStyle),
|
|
1106
|
+
priority: 1
|
|
1172
1107
|
}
|
|
1108
|
+
}
|
|
1173
1109
|
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1110
|
+
function buildCanonicalizers(config) {
|
|
1111
|
+
return [
|
|
1112
|
+
new StyleCanonicalizer("color", [ ...config.buttons.color, ...config.permit.color ]),
|
|
1113
|
+
new StyleCanonicalizer("background-color", [ ...config.buttons["background-color"], ...config.permit["background-color"] ])
|
|
1114
|
+
]
|
|
1115
|
+
}
|
|
1178
1116
|
|
|
1179
|
-
|
|
1117
|
+
function $toggleSelectionStyles(styles) {
|
|
1118
|
+
const selection = $getSelection();
|
|
1119
|
+
if (!$isRangeSelection(selection)) return
|
|
1180
1120
|
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
this.#handleUploadError(error);
|
|
1186
|
-
} else {
|
|
1187
|
-
this.#showUploadedAttachment(blob);
|
|
1188
|
-
}
|
|
1189
|
-
});
|
|
1121
|
+
const patch = {};
|
|
1122
|
+
for (const property in styles) {
|
|
1123
|
+
const oldValue = $getSelectionStyleValueForProperty(selection, property);
|
|
1124
|
+
patch[property] = toggleOrReplace(oldValue, styles[property]);
|
|
1190
1125
|
}
|
|
1191
1126
|
|
|
1192
|
-
|
|
1193
|
-
|
|
1127
|
+
$patchStyleText(selection, patch);
|
|
1128
|
+
}
|
|
1194
1129
|
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
},
|
|
1199
|
-
directUploadWillStoreFileWithXHR: (request) => {
|
|
1200
|
-
if (shouldAuthenticateUploads) request.withCredentials = true;
|
|
1130
|
+
function toggleOrReplace(oldValue, newValue) {
|
|
1131
|
+
return oldValue === newValue ? null : newValue
|
|
1132
|
+
}
|
|
1201
1133
|
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
}
|
|
1206
|
-
}
|
|
1207
|
-
|
|
1208
|
-
#setUploadStarted() {
|
|
1209
|
-
this.#setProgress(1);
|
|
1134
|
+
function $syncHighlightWithStyle(textNode) {
|
|
1135
|
+
if (hasHighlightStyles(textNode.getStyle()) !== textNode.hasFormat("highlight")) {
|
|
1136
|
+
textNode.toggleFormat("highlight");
|
|
1210
1137
|
}
|
|
1138
|
+
}
|
|
1211
1139
|
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
|
|
1140
|
+
function $canonicalizePastedStyles(textNode, canonicalizers = []) {
|
|
1141
|
+
if ($hasPastedStyles(textNode)) {
|
|
1142
|
+
$setPastedStyles(textNode, false);
|
|
1215
1143
|
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
this.getWritable().progress = progress;
|
|
1219
|
-
}, { tag: SILENT_UPDATE_TAGS });
|
|
1220
|
-
}
|
|
1144
|
+
const canonicalizedCSS = applyCanonicalizers(textNode.getStyle(), canonicalizers);
|
|
1145
|
+
textNode.setStyle(canonicalizedCSS);
|
|
1221
1146
|
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
}
|
|
1147
|
+
const selection = $getSelection();
|
|
1148
|
+
if (textNode.isSelected(selection)) {
|
|
1149
|
+
selection.setStyle(textNode.getStyle());
|
|
1150
|
+
selection.setFormat(textNode.getFormat());
|
|
1151
|
+
}
|
|
1227
1152
|
}
|
|
1153
|
+
}
|
|
1228
1154
|
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
}, { tag: SILENT_UPDATE_TAGS });
|
|
1233
|
-
}
|
|
1155
|
+
function $setPastedStyles(textNode, value = true) {
|
|
1156
|
+
$setState(textNode, hasPastedStylesState, value);
|
|
1157
|
+
}
|
|
1234
1158
|
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
return conversion.toAttachmentNode()
|
|
1238
|
-
}
|
|
1159
|
+
function $hasPastedStyles(textNode) {
|
|
1160
|
+
return $getState(textNode, hasPastedStylesState)
|
|
1239
1161
|
}
|
|
1240
1162
|
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
1163
|
+
const COMMANDS = [
|
|
1164
|
+
"bold",
|
|
1165
|
+
"italic",
|
|
1166
|
+
"strikethrough",
|
|
1167
|
+
"link",
|
|
1168
|
+
"unlink",
|
|
1169
|
+
"toggleHighlight",
|
|
1170
|
+
"removeHighlight",
|
|
1171
|
+
"rotateHeadingFormat",
|
|
1172
|
+
"insertUnorderedList",
|
|
1173
|
+
"insertOrderedList",
|
|
1174
|
+
"insertQuoteBlock",
|
|
1175
|
+
"insertCodeBlock",
|
|
1176
|
+
"insertHorizontalDivider",
|
|
1177
|
+
"uploadAttachments",
|
|
1178
|
+
|
|
1179
|
+
"insertTable",
|
|
1180
|
+
|
|
1181
|
+
"undo",
|
|
1182
|
+
"redo"
|
|
1183
|
+
];
|
|
1184
|
+
|
|
1185
|
+
class CommandDispatcher {
|
|
1186
|
+
static configureFor(editorElement) {
|
|
1187
|
+
new CommandDispatcher(editorElement);
|
|
1245
1188
|
}
|
|
1246
1189
|
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
|
|
1190
|
+
constructor(editorElement) {
|
|
1191
|
+
this.editorElement = editorElement;
|
|
1192
|
+
this.editor = editorElement.editor;
|
|
1193
|
+
this.selection = editorElement.selection;
|
|
1194
|
+
this.contents = editorElement.contents;
|
|
1195
|
+
this.clipboard = editorElement.clipboard;
|
|
1196
|
+
|
|
1197
|
+
this.#registerCommands();
|
|
1198
|
+
this.#registerKeyboardCommands();
|
|
1199
|
+
this.#registerDragAndDropHandlers();
|
|
1253
1200
|
}
|
|
1254
1201
|
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
return {
|
|
1258
|
-
sgid: blob.attachable_sgid,
|
|
1259
|
-
altText: blob.filename,
|
|
1260
|
-
contentType: blob.content_type,
|
|
1261
|
-
fileName: blob.filename,
|
|
1262
|
-
fileSize: blob.byte_size,
|
|
1263
|
-
previewable: blob.previewable,
|
|
1264
|
-
}
|
|
1202
|
+
dispatchPaste(event) {
|
|
1203
|
+
return this.clipboard.paste(event)
|
|
1265
1204
|
}
|
|
1266
1205
|
|
|
1267
|
-
|
|
1268
|
-
|
|
1206
|
+
dispatchBold() {
|
|
1207
|
+
this.editor.dispatchCommand(FORMAT_TEXT_COMMAND, "bold");
|
|
1269
1208
|
}
|
|
1270
1209
|
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
.replace(":signed_id", this.blob.signed_id)
|
|
1274
|
-
.replace(":filename", encodeURIComponent(this.blob.filename))
|
|
1210
|
+
dispatchItalic() {
|
|
1211
|
+
this.editor.dispatchCommand(FORMAT_TEXT_COMMAND, "italic");
|
|
1275
1212
|
}
|
|
1276
|
-
}
|
|
1277
1213
|
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
return "horizontal_divider"
|
|
1214
|
+
dispatchStrikethrough() {
|
|
1215
|
+
this.editor.dispatchCommand(FORMAT_TEXT_COMMAND, "strikethrough");
|
|
1281
1216
|
}
|
|
1282
1217
|
|
|
1283
|
-
|
|
1284
|
-
|
|
1218
|
+
dispatchToggleHighlight(styles) {
|
|
1219
|
+
this.editor.dispatchCommand(TOGGLE_HIGHLIGHT_COMMAND, styles);
|
|
1285
1220
|
}
|
|
1286
1221
|
|
|
1287
|
-
|
|
1288
|
-
|
|
1222
|
+
dispatchRemoveHighlight() {
|
|
1223
|
+
this.editor.dispatchCommand(REMOVE_HIGHLIGHT_COMMAND);
|
|
1289
1224
|
}
|
|
1290
1225
|
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
|
|
1226
|
+
dispatchLink(url) {
|
|
1227
|
+
this.editor.update(() => {
|
|
1228
|
+
const selection = $getSelection();
|
|
1229
|
+
if (!$isRangeSelection(selection)) return
|
|
1230
|
+
|
|
1231
|
+
if (selection.isCollapsed()) {
|
|
1232
|
+
const autoLinkNode = $createAutoLinkNode(url);
|
|
1233
|
+
const textNode = $createTextNode(url);
|
|
1234
|
+
autoLinkNode.append(textNode);
|
|
1235
|
+
selection.insertNodes([ autoLinkNode ]);
|
|
1236
|
+
} else {
|
|
1237
|
+
$toggleLink(url);
|
|
1300
1238
|
}
|
|
1301
|
-
}
|
|
1239
|
+
});
|
|
1302
1240
|
}
|
|
1303
1241
|
|
|
1304
|
-
|
|
1305
|
-
|
|
1242
|
+
dispatchUnlink() {
|
|
1243
|
+
this.#toggleLink(null);
|
|
1306
1244
|
}
|
|
1307
1245
|
|
|
1308
|
-
|
|
1309
|
-
const
|
|
1310
|
-
|
|
1246
|
+
dispatchInsertUnorderedList() {
|
|
1247
|
+
const selection = $getSelection();
|
|
1248
|
+
if (!selection) return
|
|
1311
1249
|
|
|
1312
|
-
|
|
1250
|
+
const anchorNode = selection.anchor.getNode();
|
|
1313
1251
|
|
|
1314
|
-
|
|
1252
|
+
if (this.selection.isInsideList && anchorNode && getListType(anchorNode) === "bullet") {
|
|
1253
|
+
this.contents.unwrapSelectedListItems();
|
|
1254
|
+
} else {
|
|
1255
|
+
this.editor.dispatchCommand(INSERT_UNORDERED_LIST_COMMAND, undefined);
|
|
1256
|
+
}
|
|
1315
1257
|
}
|
|
1316
1258
|
|
|
1317
|
-
|
|
1318
|
-
|
|
1259
|
+
dispatchInsertOrderedList() {
|
|
1260
|
+
const selection = $getSelection();
|
|
1261
|
+
if (!selection) return
|
|
1262
|
+
|
|
1263
|
+
const anchorNode = selection.anchor.getNode();
|
|
1264
|
+
|
|
1265
|
+
if (this.selection.isInsideList && anchorNode && getListType(anchorNode) === "number") {
|
|
1266
|
+
this.contents.unwrapSelectedListItems();
|
|
1267
|
+
} else {
|
|
1268
|
+
this.editor.dispatchCommand(INSERT_ORDERED_LIST_COMMAND, undefined);
|
|
1269
|
+
}
|
|
1319
1270
|
}
|
|
1320
1271
|
|
|
1321
|
-
|
|
1322
|
-
|
|
1272
|
+
dispatchInsertQuoteBlock() {
|
|
1273
|
+
this.contents.toggleNodeWrappingAllSelectedNodes((node) => $isQuoteNode(node), () => $createQuoteNode());
|
|
1323
1274
|
}
|
|
1324
1275
|
|
|
1325
|
-
|
|
1326
|
-
|
|
1276
|
+
dispatchInsertCodeBlock() {
|
|
1277
|
+
this.editor.update(() => {
|
|
1278
|
+
if (this.selection.hasSelectedWordsInSingleLine) {
|
|
1279
|
+
this.editor.dispatchCommand(FORMAT_TEXT_COMMAND, "code");
|
|
1280
|
+
} else {
|
|
1281
|
+
this.contents.toggleNodeWrappingAllSelectedLines((node) => $isCodeNode(node), () => new CodeNode("plain"));
|
|
1282
|
+
}
|
|
1283
|
+
});
|
|
1327
1284
|
}
|
|
1328
1285
|
|
|
1329
|
-
|
|
1330
|
-
|
|
1331
|
-
|
|
1286
|
+
dispatchInsertHorizontalDivider() {
|
|
1287
|
+
this.contents.insertAtCursorEnsuringLineBelow(new HorizontalDividerNode());
|
|
1288
|
+
this.editor.focus();
|
|
1332
1289
|
}
|
|
1333
1290
|
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
|
|
1337
|
-
|
|
1291
|
+
dispatchRotateHeadingFormat() {
|
|
1292
|
+
const selection = $getSelection();
|
|
1293
|
+
if (!$isRangeSelection(selection)) return
|
|
1294
|
+
|
|
1295
|
+
if ($isRootOrShadowRoot(selection.anchor.getNode())) {
|
|
1296
|
+
selection.insertNodes([ $createHeadingNode("h2") ]);
|
|
1297
|
+
return
|
|
1298
|
+
}
|
|
1299
|
+
|
|
1300
|
+
const topLevelElement = selection.anchor.getNode().getTopLevelElementOrThrow();
|
|
1301
|
+
let nextTag = "h2";
|
|
1302
|
+
if ($isHeadingNode(topLevelElement)) {
|
|
1303
|
+
const currentTag = topLevelElement.getTag();
|
|
1304
|
+
if (currentTag === "h2") {
|
|
1305
|
+
nextTag = "h3";
|
|
1306
|
+
} else if (currentTag === "h3") {
|
|
1307
|
+
nextTag = "h4";
|
|
1308
|
+
} else if (currentTag === "h4") {
|
|
1309
|
+
nextTag = null;
|
|
1310
|
+
} else {
|
|
1311
|
+
nextTag = "h2";
|
|
1312
|
+
}
|
|
1313
|
+
}
|
|
1314
|
+
|
|
1315
|
+
if (nextTag) {
|
|
1316
|
+
this.contents.insertNodeWrappingEachSelectedLine(() => $createHeadingNode(nextTag));
|
|
1317
|
+
} else {
|
|
1318
|
+
this.contents.removeFormattingFromSelectedLines();
|
|
1338
1319
|
}
|
|
1339
1320
|
}
|
|
1340
1321
|
|
|
1341
|
-
|
|
1342
|
-
|
|
1322
|
+
dispatchUploadAttachments() {
|
|
1323
|
+
const input = createElement("input", {
|
|
1324
|
+
type: "file",
|
|
1325
|
+
multiple: true,
|
|
1326
|
+
style: "display: none;",
|
|
1327
|
+
onchange: ({ target: { files } }) => {
|
|
1328
|
+
this.contents.uploadFiles(files, { selectLast: true });
|
|
1329
|
+
}
|
|
1330
|
+
});
|
|
1331
|
+
|
|
1332
|
+
// Append and remove to make testable
|
|
1333
|
+
this.editorElement.appendChild(input);
|
|
1334
|
+
input.click();
|
|
1335
|
+
setTimeout(() => input.remove(), 1000);
|
|
1343
1336
|
}
|
|
1344
|
-
}
|
|
1345
1337
|
|
|
1346
|
-
|
|
1347
|
-
|
|
1338
|
+
dispatchInsertTable() {
|
|
1339
|
+
this.editor.dispatchCommand(INSERT_TABLE_COMMAND, { "rows": 3, "columns": 3, "includeHeaders": true });
|
|
1340
|
+
}
|
|
1348
1341
|
|
|
1349
|
-
|
|
1350
|
-
|
|
1351
|
-
} else {
|
|
1352
|
-
return selection.hasFormat("highlight")
|
|
1342
|
+
dispatchUndo() {
|
|
1343
|
+
this.editor.dispatchCommand(UNDO_COMMAND, undefined);
|
|
1353
1344
|
}
|
|
1354
|
-
}
|
|
1355
1345
|
|
|
1356
|
-
|
|
1357
|
-
|
|
1358
|
-
return !!(styles.color || styles["background-color"])
|
|
1359
|
-
}
|
|
1360
|
-
|
|
1361
|
-
function applyCanonicalizers(styles, canonicalizers = []) {
|
|
1362
|
-
return canonicalizers.reduce((css, canonicalizer) => {
|
|
1363
|
-
return canonicalizer.applyCanonicalization(css)
|
|
1364
|
-
}, styles)
|
|
1365
|
-
}
|
|
1366
|
-
|
|
1367
|
-
class StyleCanonicalizer {
|
|
1368
|
-
constructor(property, allowedValues= []) {
|
|
1369
|
-
this._property = property;
|
|
1370
|
-
this._allowedValues = allowedValues;
|
|
1371
|
-
this._canonicalValues = this.#allowedValuesIdentityObject;
|
|
1346
|
+
dispatchRedo() {
|
|
1347
|
+
this.editor.dispatchCommand(REDO_COMMAND, undefined);
|
|
1372
1348
|
}
|
|
1373
1349
|
|
|
1374
|
-
|
|
1375
|
-
const
|
|
1376
|
-
|
|
1377
|
-
|
|
1378
|
-
if (!styles[this._property]) {
|
|
1379
|
-
delete styles[this._property];
|
|
1350
|
+
#registerCommands() {
|
|
1351
|
+
for (const command of COMMANDS) {
|
|
1352
|
+
const methodName = `dispatch${capitalize(command)}`;
|
|
1353
|
+
this.#registerCommandHandler(command, 0, this[methodName].bind(this));
|
|
1380
1354
|
}
|
|
1381
1355
|
|
|
1382
|
-
|
|
1356
|
+
this.#registerCommandHandler(PASTE_COMMAND, COMMAND_PRIORITY_LOW, this.dispatchPaste.bind(this));
|
|
1383
1357
|
}
|
|
1384
1358
|
|
|
1385
|
-
|
|
1386
|
-
|
|
1359
|
+
#registerCommandHandler(command, priority, handler) {
|
|
1360
|
+
this.editor.registerCommand(command, handler, priority);
|
|
1387
1361
|
}
|
|
1388
1362
|
|
|
1389
|
-
|
|
1390
|
-
|
|
1391
|
-
get #allowedValuesIdentityObject() {
|
|
1392
|
-
return this._allowedValues.reduce((object, value) => ({ ...object, [value]: value }), {})
|
|
1363
|
+
#registerKeyboardCommands() {
|
|
1364
|
+
this.editor.registerCommand(KEY_TAB_COMMAND, this.#handleTabKey.bind(this), COMMAND_PRIORITY_NORMAL);
|
|
1393
1365
|
}
|
|
1394
1366
|
|
|
1395
|
-
#
|
|
1396
|
-
|
|
1397
|
-
|
|
1398
|
-
|
|
1367
|
+
#registerDragAndDropHandlers() {
|
|
1368
|
+
if (this.editorElement.supportsAttachments) {
|
|
1369
|
+
this.dragCounter = 0;
|
|
1370
|
+
this.editor.getRootElement().addEventListener("dragover", this.#handleDragOver.bind(this));
|
|
1371
|
+
this.editor.getRootElement().addEventListener("drop", this.#handleDrop.bind(this));
|
|
1372
|
+
this.editor.getRootElement().addEventListener("dragenter", this.#handleDragEnter.bind(this));
|
|
1373
|
+
this.editor.getRootElement().addEventListener("dragleave", this.#handleDragLeave.bind(this));
|
|
1374
|
+
}
|
|
1399
1375
|
}
|
|
1400
1376
|
|
|
1401
|
-
|
|
1402
|
-
|
|
1403
|
-
|
|
1404
|
-
|
|
1377
|
+
#handleDragEnter(event) {
|
|
1378
|
+
this.dragCounter++;
|
|
1379
|
+
if (this.dragCounter === 1) {
|
|
1380
|
+
this.editor.getRootElement().classList.add("lexxy-editor--drag-over");
|
|
1381
|
+
}
|
|
1405
1382
|
}
|
|
1406
|
-
}
|
|
1407
1383
|
|
|
1408
|
-
|
|
1409
|
-
|
|
1384
|
+
#handleDragLeave(event) {
|
|
1385
|
+
this.dragCounter--;
|
|
1386
|
+
if (this.dragCounter === 0) {
|
|
1387
|
+
this.editor.getRootElement().classList.remove("lexxy-editor--drag-over");
|
|
1388
|
+
}
|
|
1389
|
+
}
|
|
1410
1390
|
|
|
1411
|
-
|
|
1412
|
-
|
|
1413
|
-
|
|
1414
|
-
element.remove();
|
|
1391
|
+
#handleDragOver(event) {
|
|
1392
|
+
event.preventDefault();
|
|
1393
|
+
}
|
|
1415
1394
|
|
|
1416
|
-
|
|
1417
|
-
|
|
1395
|
+
#handleDrop(event) {
|
|
1396
|
+
event.preventDefault();
|
|
1418
1397
|
|
|
1419
|
-
|
|
1420
|
-
|
|
1398
|
+
this.dragCounter = 0;
|
|
1399
|
+
this.editor.getRootElement().classList.remove("lexxy-editor--drag-over");
|
|
1421
1400
|
|
|
1422
|
-
|
|
1423
|
-
|
|
1424
|
-
}
|
|
1401
|
+
const dataTransfer = event.dataTransfer;
|
|
1402
|
+
if (!dataTransfer) return
|
|
1425
1403
|
|
|
1426
|
-
|
|
1427
|
-
return
|
|
1428
|
-
}
|
|
1404
|
+
const files = Array.from(dataTransfer.files);
|
|
1405
|
+
if (!files.length) return
|
|
1429
1406
|
|
|
1430
|
-
|
|
1431
|
-
return this.#editorElement.config
|
|
1432
|
-
}
|
|
1407
|
+
this.contents.uploadFiles(files, { selectLast: true });
|
|
1433
1408
|
|
|
1434
|
-
|
|
1435
|
-
get enabled() {
|
|
1436
|
-
return true
|
|
1409
|
+
this.editor.focus();
|
|
1437
1410
|
}
|
|
1438
1411
|
|
|
1439
|
-
|
|
1440
|
-
|
|
1412
|
+
#handleTabKey(event) {
|
|
1413
|
+
if (this.selection.isInsideList) {
|
|
1414
|
+
return this.#handleTabForList(event)
|
|
1415
|
+
} else if (this.selection.isInsideCodeBlock) {
|
|
1416
|
+
return this.#handleTabForCode()
|
|
1417
|
+
}
|
|
1418
|
+
return false
|
|
1441
1419
|
}
|
|
1442
1420
|
|
|
1443
|
-
|
|
1421
|
+
#handleTabForList(event) {
|
|
1422
|
+
if (event.shiftKey && !this.selection.isIndentedList) return false
|
|
1444
1423
|
|
|
1424
|
+
event.preventDefault();
|
|
1425
|
+
const command = event.shiftKey? OUTDENT_CONTENT_COMMAND : INDENT_CONTENT_COMMAND;
|
|
1426
|
+
return this.editor.dispatchCommand(command)
|
|
1445
1427
|
}
|
|
1446
|
-
}
|
|
1447
|
-
|
|
1448
|
-
const TOGGLE_HIGHLIGHT_COMMAND = createCommand();
|
|
1449
|
-
const REMOVE_HIGHLIGHT_COMMAND = createCommand();
|
|
1450
|
-
const BLANK_STYLES = { "color": null, "background-color": null };
|
|
1451
|
-
|
|
1452
|
-
const hasPastedStylesState = createState("hasPastedStyles", {
|
|
1453
|
-
parse: (value) => value || false
|
|
1454
|
-
});
|
|
1455
1428
|
|
|
1456
|
-
|
|
1457
|
-
|
|
1458
|
-
return
|
|
1429
|
+
#handleTabForCode() {
|
|
1430
|
+
const selection = $getSelection();
|
|
1431
|
+
return $isRangeSelection(selection) && selection.isCollapsed()
|
|
1459
1432
|
}
|
|
1460
1433
|
|
|
1461
|
-
|
|
1462
|
-
|
|
1463
|
-
|
|
1464
|
-
|
|
1465
|
-
|
|
1466
|
-
|
|
1467
|
-
|
|
1468
|
-
},
|
|
1469
|
-
html: {
|
|
1470
|
-
import: {
|
|
1471
|
-
mark: $markConversion
|
|
1472
|
-
}
|
|
1473
|
-
},
|
|
1474
|
-
register(editor, config) {
|
|
1475
|
-
// keep the ref to the canonicalizers for optimized css conversion
|
|
1476
|
-
const canonicalizers = buildCanonicalizers(config);
|
|
1477
|
-
|
|
1478
|
-
return mergeRegister(
|
|
1479
|
-
editor.registerCommand(TOGGLE_HIGHLIGHT_COMMAND, $toggleSelectionStyles, COMMAND_PRIORITY_NORMAL),
|
|
1480
|
-
editor.registerCommand(REMOVE_HIGHLIGHT_COMMAND, () => $toggleSelectionStyles(BLANK_STYLES), COMMAND_PRIORITY_NORMAL),
|
|
1481
|
-
editor.registerNodeTransform(TextNode, $syncHighlightWithStyle),
|
|
1482
|
-
editor.registerNodeTransform(TextNode, (textNode) => $canonicalizePastedStyles(textNode, canonicalizers))
|
|
1483
|
-
)
|
|
1434
|
+
// Not using TOGGLE_LINK_COMMAND because it's not handled unless you use React/LinkPlugin
|
|
1435
|
+
#toggleLink(url) {
|
|
1436
|
+
this.editor.update(() => {
|
|
1437
|
+
if (url === null) {
|
|
1438
|
+
$toggleLink(null);
|
|
1439
|
+
} else {
|
|
1440
|
+
$toggleLink(url);
|
|
1484
1441
|
}
|
|
1485
1442
|
});
|
|
1486
|
-
|
|
1487
|
-
return [ extension, this.editorConfig.get("highlight") ]
|
|
1488
1443
|
}
|
|
1489
1444
|
}
|
|
1490
1445
|
|
|
1491
|
-
function
|
|
1492
|
-
|
|
1493
|
-
|
|
1494
|
-
"background-color": element.style?.backgroundColor
|
|
1495
|
-
};
|
|
1446
|
+
function capitalize(str) {
|
|
1447
|
+
return str.charAt(0).toUpperCase() + str.slice(1)
|
|
1448
|
+
}
|
|
1496
1449
|
|
|
1497
|
-
|
|
1498
|
-
|
|
1450
|
+
function debounceAsync(fn, wait) {
|
|
1451
|
+
let timeout;
|
|
1499
1452
|
|
|
1500
|
-
|
|
1501
|
-
|
|
1502
|
-
}
|
|
1503
|
-
}
|
|
1453
|
+
return (...args) => {
|
|
1454
|
+
clearTimeout(timeout);
|
|
1504
1455
|
|
|
1505
|
-
|
|
1506
|
-
|
|
1507
|
-
|
|
1508
|
-
|
|
1456
|
+
return new Promise((resolve, reject) => {
|
|
1457
|
+
timeout = setTimeout(async () => {
|
|
1458
|
+
try {
|
|
1459
|
+
const result = await fn(...args);
|
|
1460
|
+
resolve(result);
|
|
1461
|
+
} catch (err) {
|
|
1462
|
+
reject(err);
|
|
1463
|
+
}
|
|
1464
|
+
}, wait);
|
|
1465
|
+
})
|
|
1509
1466
|
}
|
|
1510
1467
|
}
|
|
1511
1468
|
|
|
1512
|
-
function
|
|
1513
|
-
return
|
|
1514
|
-
new StyleCanonicalizer("color", [ ...config.buttons.color, ...config.permit.color ]),
|
|
1515
|
-
new StyleCanonicalizer("background-color", [ ...config.buttons["background-color"], ...config.permit["background-color"] ])
|
|
1516
|
-
]
|
|
1469
|
+
function nextFrame() {
|
|
1470
|
+
return new Promise(requestAnimationFrame)
|
|
1517
1471
|
}
|
|
1518
1472
|
|
|
1519
|
-
function
|
|
1520
|
-
|
|
1521
|
-
|
|
1522
|
-
|
|
1523
|
-
const
|
|
1524
|
-
|
|
1525
|
-
const oldValue = $getSelectionStyleValueForProperty(selection, property);
|
|
1526
|
-
patch[property] = toggleOrReplace(oldValue, styles[property]);
|
|
1527
|
-
}
|
|
1528
|
-
|
|
1529
|
-
$patchStyleText(selection, patch);
|
|
1473
|
+
function bytesToHumanSize(bytes) {
|
|
1474
|
+
if (bytes === 0) return "0 B"
|
|
1475
|
+
const sizes = [ "B", "KB", "MB", "GB", "TB", "PB" ];
|
|
1476
|
+
const i = Math.floor(Math.log(bytes) / Math.log(1024));
|
|
1477
|
+
const value = bytes / Math.pow(1024, i);
|
|
1478
|
+
return `${ value.toFixed(2) } ${ sizes[i] }`
|
|
1530
1479
|
}
|
|
1531
1480
|
|
|
1532
|
-
function
|
|
1533
|
-
return
|
|
1481
|
+
function extractFileName(string) {
|
|
1482
|
+
return string.split("/").pop()
|
|
1534
1483
|
}
|
|
1535
1484
|
|
|
1536
|
-
|
|
1537
|
-
|
|
1538
|
-
|
|
1485
|
+
class ActionTextAttachmentNode extends DecoratorNode {
|
|
1486
|
+
static getType() {
|
|
1487
|
+
return "action_text_attachment"
|
|
1539
1488
|
}
|
|
1540
|
-
}
|
|
1541
|
-
|
|
1542
|
-
function $canonicalizePastedStyles(textNode, canonicalizers = []) {
|
|
1543
|
-
if ($hasPastedStyles(textNode)) {
|
|
1544
|
-
$setPastedStyles(textNode, false);
|
|
1545
|
-
|
|
1546
|
-
const canonicalizedCSS = applyCanonicalizers(textNode.getStyle(), canonicalizers);
|
|
1547
|
-
textNode.setStyle(canonicalizedCSS);
|
|
1548
1489
|
|
|
1549
|
-
|
|
1550
|
-
|
|
1551
|
-
selection.setStyle(textNode.getStyle());
|
|
1552
|
-
selection.setFormat(textNode.getFormat());
|
|
1553
|
-
}
|
|
1490
|
+
static clone(node) {
|
|
1491
|
+
return new ActionTextAttachmentNode({ ...node }, node.__key)
|
|
1554
1492
|
}
|
|
1555
|
-
}
|
|
1556
|
-
|
|
1557
|
-
function $setPastedStyles(textNode, value = true) {
|
|
1558
|
-
$setState(textNode, hasPastedStylesState, value);
|
|
1559
|
-
}
|
|
1560
1493
|
|
|
1561
|
-
|
|
1562
|
-
|
|
1563
|
-
}
|
|
1494
|
+
static importJSON(serializedNode) {
|
|
1495
|
+
return new ActionTextAttachmentNode({ ...serializedNode })
|
|
1496
|
+
}
|
|
1564
1497
|
|
|
1565
|
-
|
|
1566
|
-
|
|
1567
|
-
|
|
1568
|
-
|
|
1569
|
-
|
|
1570
|
-
|
|
1571
|
-
|
|
1572
|
-
|
|
1573
|
-
|
|
1574
|
-
|
|
1575
|
-
|
|
1576
|
-
|
|
1577
|
-
|
|
1578
|
-
|
|
1579
|
-
|
|
1498
|
+
static importDOM() {
|
|
1499
|
+
return {
|
|
1500
|
+
[this.TAG_NAME]: () => {
|
|
1501
|
+
return {
|
|
1502
|
+
conversion: (attachment) => ({
|
|
1503
|
+
node: new ActionTextAttachmentNode({
|
|
1504
|
+
sgid: attachment.getAttribute("sgid"),
|
|
1505
|
+
src: attachment.getAttribute("url"),
|
|
1506
|
+
previewable: attachment.getAttribute("previewable"),
|
|
1507
|
+
altText: attachment.getAttribute("alt"),
|
|
1508
|
+
caption: attachment.getAttribute("caption"),
|
|
1509
|
+
contentType: attachment.getAttribute("content-type"),
|
|
1510
|
+
fileName: attachment.getAttribute("filename"),
|
|
1511
|
+
fileSize: attachment.getAttribute("filesize"),
|
|
1512
|
+
width: attachment.getAttribute("width"),
|
|
1513
|
+
height: attachment.getAttribute("height")
|
|
1514
|
+
})
|
|
1515
|
+
}), priority: 1
|
|
1516
|
+
}
|
|
1517
|
+
},
|
|
1518
|
+
"img": () => {
|
|
1519
|
+
return {
|
|
1520
|
+
conversion: (img) => {
|
|
1521
|
+
const fileName = extractFileName(img.getAttribute("src") ?? "");
|
|
1522
|
+
return {
|
|
1523
|
+
node: new ActionTextAttachmentNode({
|
|
1524
|
+
src: img.getAttribute("src"),
|
|
1525
|
+
fileName: fileName,
|
|
1526
|
+
caption: img.getAttribute("alt") || "",
|
|
1527
|
+
contentType: "image/*",
|
|
1528
|
+
width: img.getAttribute("width"),
|
|
1529
|
+
height: img.getAttribute("height")
|
|
1530
|
+
})
|
|
1531
|
+
}
|
|
1532
|
+
}, priority: 1
|
|
1533
|
+
}
|
|
1534
|
+
},
|
|
1535
|
+
"video": () => {
|
|
1536
|
+
return {
|
|
1537
|
+
conversion: (video) => {
|
|
1538
|
+
const videoSource = video.getAttribute("src") || video.querySelector("source")?.src;
|
|
1539
|
+
const fileName = videoSource?.split("/")?.pop();
|
|
1540
|
+
const contentType = video.querySelector("source")?.getAttribute("content-type") || "video/*";
|
|
1541
|
+
|
|
1542
|
+
return {
|
|
1543
|
+
node: new ActionTextAttachmentNode({
|
|
1544
|
+
src: videoSource,
|
|
1545
|
+
fileName: fileName,
|
|
1546
|
+
contentType: contentType
|
|
1547
|
+
})
|
|
1548
|
+
}
|
|
1549
|
+
}, priority: 1
|
|
1550
|
+
}
|
|
1551
|
+
}
|
|
1552
|
+
}
|
|
1553
|
+
}
|
|
1554
|
+
|
|
1555
|
+
static get TAG_NAME() {
|
|
1556
|
+
return Lexxy.global.get("attachmentTagName")
|
|
1557
|
+
}
|
|
1558
|
+
|
|
1559
|
+
constructor({ tagName, sgid, src, previewable, altText, caption, contentType, fileName, fileSize, width, height }, key) {
|
|
1560
|
+
super(key);
|
|
1561
|
+
|
|
1562
|
+
this.tagName = tagName || ActionTextAttachmentNode.TAG_NAME;
|
|
1563
|
+
this.sgid = sgid;
|
|
1564
|
+
this.src = src;
|
|
1565
|
+
this.previewable = previewable;
|
|
1566
|
+
this.altText = altText || "";
|
|
1567
|
+
this.caption = caption || "";
|
|
1568
|
+
this.contentType = contentType || "";
|
|
1569
|
+
this.fileName = fileName || "";
|
|
1570
|
+
this.fileSize = fileSize;
|
|
1571
|
+
this.width = width;
|
|
1572
|
+
this.height = height;
|
|
1573
|
+
|
|
1574
|
+
this.editor = $getEditor();
|
|
1575
|
+
}
|
|
1576
|
+
|
|
1577
|
+
createDOM() {
|
|
1578
|
+
const figure = this.createAttachmentFigure();
|
|
1579
|
+
|
|
1580
|
+
if (this.isPreviewableAttachment) {
|
|
1581
|
+
figure.appendChild(this.#createDOMForImage());
|
|
1582
|
+
figure.appendChild(this.#createEditableCaption());
|
|
1583
|
+
} else {
|
|
1584
|
+
figure.appendChild(this.#createDOMForFile());
|
|
1585
|
+
figure.appendChild(this.#createDOMForNotImage());
|
|
1586
|
+
}
|
|
1587
|
+
|
|
1588
|
+
return figure
|
|
1589
|
+
}
|
|
1590
|
+
|
|
1591
|
+
updateDOM(_prevNode, dom) {
|
|
1592
|
+
const caption = dom.querySelector("figcaption textarea");
|
|
1593
|
+
if (caption && this.caption) {
|
|
1594
|
+
caption.value = this.caption;
|
|
1595
|
+
}
|
|
1596
|
+
|
|
1597
|
+
return false
|
|
1598
|
+
}
|
|
1599
|
+
|
|
1600
|
+
getTextContent() {
|
|
1601
|
+
return `[${this.caption || this.fileName}]\n\n`
|
|
1602
|
+
}
|
|
1603
|
+
|
|
1604
|
+
isInline() {
|
|
1605
|
+
return this.isAttached() && !this.getParent().is($getNearestRootOrShadowRoot(this))
|
|
1606
|
+
}
|
|
1607
|
+
|
|
1608
|
+
exportDOM() {
|
|
1609
|
+
const attachment = createElement(this.tagName, {
|
|
1610
|
+
sgid: this.sgid,
|
|
1611
|
+
previewable: this.previewable || null,
|
|
1612
|
+
url: this.src,
|
|
1613
|
+
alt: this.altText,
|
|
1614
|
+
caption: this.caption,
|
|
1615
|
+
"content-type": this.contentType,
|
|
1616
|
+
filename: this.fileName,
|
|
1617
|
+
filesize: this.fileSize,
|
|
1618
|
+
width: this.width,
|
|
1619
|
+
height: this.height,
|
|
1620
|
+
presentation: "gallery"
|
|
1621
|
+
});
|
|
1622
|
+
|
|
1623
|
+
return { element: attachment }
|
|
1624
|
+
}
|
|
1625
|
+
|
|
1626
|
+
exportJSON() {
|
|
1627
|
+
return {
|
|
1628
|
+
type: "action_text_attachment",
|
|
1629
|
+
version: 1,
|
|
1630
|
+
tagName: this.tagName,
|
|
1631
|
+
sgid: this.sgid,
|
|
1632
|
+
src: this.src,
|
|
1633
|
+
previewable: this.previewable,
|
|
1634
|
+
altText: this.altText,
|
|
1635
|
+
caption: this.caption,
|
|
1636
|
+
contentType: this.contentType,
|
|
1637
|
+
fileName: this.fileName,
|
|
1638
|
+
fileSize: this.fileSize,
|
|
1639
|
+
width: this.width,
|
|
1640
|
+
height: this.height
|
|
1641
|
+
}
|
|
1642
|
+
}
|
|
1643
|
+
|
|
1644
|
+
decorate() {
|
|
1645
|
+
return null
|
|
1646
|
+
}
|
|
1647
|
+
|
|
1648
|
+
createAttachmentFigure() {
|
|
1649
|
+
const figure = createAttachmentFigure(this.contentType, this.isPreviewableAttachment, this.fileName);
|
|
1650
|
+
|
|
1651
|
+
const deleteButton = createElement("lexxy-node-delete-button");
|
|
1652
|
+
figure.appendChild(deleteButton);
|
|
1653
|
+
|
|
1654
|
+
return figure
|
|
1655
|
+
}
|
|
1656
|
+
|
|
1657
|
+
get isPreviewableAttachment() {
|
|
1658
|
+
return this.isPreviewableImage || this.previewable
|
|
1659
|
+
}
|
|
1660
|
+
|
|
1661
|
+
get isPreviewableImage() {
|
|
1662
|
+
return isPreviewableImage(this.contentType)
|
|
1663
|
+
}
|
|
1664
|
+
|
|
1665
|
+
#createDOMForImage(options = {}) {
|
|
1666
|
+
const img = createElement("img", { src: this.src, draggable: false, alt: this.altText, ...this.#imageDimensions, ...options });
|
|
1667
|
+
const container = createElement("div", { className: "attachment__container" });
|
|
1668
|
+
container.appendChild(img);
|
|
1669
|
+
return container
|
|
1670
|
+
}
|
|
1671
|
+
|
|
1672
|
+
get #imageDimensions() {
|
|
1673
|
+
if (this.width && this.height) {
|
|
1674
|
+
return { width: this.width, height: this.height }
|
|
1675
|
+
} else {
|
|
1676
|
+
return {}
|
|
1677
|
+
}
|
|
1678
|
+
}
|
|
1679
|
+
|
|
1680
|
+
#createDOMForFile() {
|
|
1681
|
+
const extension = this.fileName ? this.fileName.split(".").pop().toLowerCase() : "unknown";
|
|
1682
|
+
return createElement("span", { className: "attachment__icon", textContent: `${extension}` })
|
|
1683
|
+
}
|
|
1684
|
+
|
|
1685
|
+
#createDOMForNotImage() {
|
|
1686
|
+
const figcaption = createElement("figcaption", { className: "attachment__caption" });
|
|
1687
|
+
|
|
1688
|
+
const nameTag = createElement("strong", { className: "attachment__name", textContent: this.caption || this.fileName });
|
|
1689
|
+
|
|
1690
|
+
figcaption.appendChild(nameTag);
|
|
1691
|
+
|
|
1692
|
+
if (this.fileSize) {
|
|
1693
|
+
const sizeSpan = createElement("span", { className: "attachment__size", textContent: bytesToHumanSize(this.fileSize) });
|
|
1694
|
+
figcaption.appendChild(sizeSpan);
|
|
1695
|
+
}
|
|
1696
|
+
|
|
1697
|
+
return figcaption
|
|
1698
|
+
}
|
|
1699
|
+
|
|
1700
|
+
#createEditableCaption() {
|
|
1701
|
+
const caption = createElement("figcaption", { className: "attachment__caption" });
|
|
1702
|
+
const input = createElement("textarea", {
|
|
1703
|
+
value: this.caption,
|
|
1704
|
+
placeholder: this.fileName,
|
|
1705
|
+
rows: "1"
|
|
1706
|
+
});
|
|
1707
|
+
|
|
1708
|
+
input.addEventListener("focusin", () => input.placeholder = "Add caption...");
|
|
1709
|
+
input.addEventListener("blur", (event) => this.#handleCaptionInputBlurred(event));
|
|
1710
|
+
input.addEventListener("keydown", (event) => this.#handleCaptionInputKeydown(event));
|
|
1711
|
+
|
|
1712
|
+
caption.appendChild(input);
|
|
1713
|
+
|
|
1714
|
+
return caption
|
|
1715
|
+
}
|
|
1716
|
+
|
|
1717
|
+
#handleCaptionInputBlurred(event) {
|
|
1718
|
+
this.#updateCaptionValueFromInput(event.target);
|
|
1719
|
+
}
|
|
1720
|
+
|
|
1721
|
+
#updateCaptionValueFromInput(input) {
|
|
1722
|
+
input.placeholder = this.fileName;
|
|
1723
|
+
this.editor.update(() => {
|
|
1724
|
+
this.getWritable().caption = input.value;
|
|
1725
|
+
});
|
|
1726
|
+
}
|
|
1727
|
+
|
|
1728
|
+
#handleCaptionInputKeydown(event) {
|
|
1729
|
+
if (event.key === "Enter") {
|
|
1730
|
+
event.preventDefault();
|
|
1731
|
+
event.stopPropagation();
|
|
1732
|
+
event.target.blur();
|
|
1733
|
+
|
|
1734
|
+
this.editor.update(() => {
|
|
1735
|
+
// Place the cursor after the current image
|
|
1736
|
+
this.selectNext(0, 0);
|
|
1737
|
+
}, {
|
|
1738
|
+
tag: HISTORY_MERGE_TAG
|
|
1739
|
+
});
|
|
1740
|
+
}
|
|
1741
|
+
|
|
1742
|
+
}
|
|
1743
|
+
}
|
|
1744
|
+
|
|
1745
|
+
function $createActionTextAttachmentNode(...args) {
|
|
1746
|
+
return new ActionTextAttachmentNode(...args)
|
|
1747
|
+
}
|
|
1748
|
+
|
|
1749
|
+
function $isActionTextAttachmentNode(node) {
|
|
1750
|
+
return node instanceof ActionTextAttachmentNode
|
|
1751
|
+
}
|
|
1752
|
+
|
|
1753
|
+
class Selection {
|
|
1754
|
+
constructor(editorElement) {
|
|
1755
|
+
this.editorElement = editorElement;
|
|
1756
|
+
this.editorContentElement = editorElement.editorContentElement;
|
|
1757
|
+
this.editor = this.editorElement.editor;
|
|
1758
|
+
this.previouslySelectedKeys = new Set();
|
|
1759
|
+
|
|
1760
|
+
this.#listenForNodeSelections();
|
|
1761
|
+
this.#processSelectionChangeCommands();
|
|
1762
|
+
this.#containEditorFocus();
|
|
1763
|
+
}
|
|
1764
|
+
|
|
1765
|
+
set current(selection) {
|
|
1766
|
+
this.editor.update(() => {
|
|
1767
|
+
this.#syncSelectedClasses();
|
|
1768
|
+
});
|
|
1769
|
+
}
|
|
1770
|
+
|
|
1771
|
+
get hasNodeSelection() {
|
|
1772
|
+
return this.editor.getEditorState().read(() => {
|
|
1773
|
+
const selection = $getSelection();
|
|
1774
|
+
return selection !== null && $isNodeSelection(selection)
|
|
1775
|
+
})
|
|
1776
|
+
}
|
|
1777
|
+
|
|
1778
|
+
get cursorPosition() {
|
|
1779
|
+
let position = { x: 0, y: 0 };
|
|
1780
|
+
|
|
1781
|
+
this.editor.getEditorState().read(() => {
|
|
1782
|
+
const range = this.#getValidSelectionRange();
|
|
1783
|
+
if (!range) return
|
|
1784
|
+
|
|
1785
|
+
const rect = this.#getReliableRectFromRange(range);
|
|
1786
|
+
if (!rect) return
|
|
1787
|
+
|
|
1788
|
+
position = this.#calculateCursorPosition(rect, range);
|
|
1789
|
+
});
|
|
1790
|
+
|
|
1791
|
+
return position
|
|
1792
|
+
}
|
|
1793
|
+
|
|
1794
|
+
placeCursorAtTheEnd() {
|
|
1795
|
+
this.editor.update(() => {
|
|
1796
|
+
const root = $getRoot();
|
|
1797
|
+
const lastDescendant = root.getLastDescendant();
|
|
1798
|
+
|
|
1799
|
+
if (lastDescendant && $isTextNode(lastDescendant)) {
|
|
1800
|
+
lastDescendant.selectEnd();
|
|
1801
|
+
} else {
|
|
1802
|
+
root.selectEnd();
|
|
1803
|
+
}
|
|
1804
|
+
});
|
|
1805
|
+
}
|
|
1806
|
+
|
|
1807
|
+
selectedNodeWithOffset() {
|
|
1808
|
+
const selection = $getSelection();
|
|
1809
|
+
if (!selection) return { node: null, offset: 0 }
|
|
1810
|
+
|
|
1811
|
+
if ($isRangeSelection(selection)) {
|
|
1812
|
+
return {
|
|
1813
|
+
node: selection.anchor.getNode(),
|
|
1814
|
+
offset: selection.anchor.offset
|
|
1815
|
+
}
|
|
1816
|
+
} else if ($isNodeSelection(selection)) {
|
|
1817
|
+
const [ node ] = selection.getNodes();
|
|
1818
|
+
return {
|
|
1819
|
+
node,
|
|
1820
|
+
offset: 0
|
|
1821
|
+
}
|
|
1822
|
+
}
|
|
1823
|
+
|
|
1824
|
+
return { node: null, offset: 0 }
|
|
1825
|
+
}
|
|
1826
|
+
|
|
1827
|
+
preservingSelection(fn) {
|
|
1828
|
+
let selectionState = null;
|
|
1829
|
+
|
|
1830
|
+
this.editor.getEditorState().read(() => {
|
|
1831
|
+
const selection = $getSelection();
|
|
1832
|
+
if (selection && $isRangeSelection(selection)) {
|
|
1833
|
+
selectionState = {
|
|
1834
|
+
anchor: { key: selection.anchor.key, offset: selection.anchor.offset },
|
|
1835
|
+
focus: { key: selection.focus.key, offset: selection.focus.offset }
|
|
1836
|
+
};
|
|
1837
|
+
}
|
|
1838
|
+
});
|
|
1839
|
+
|
|
1840
|
+
fn();
|
|
1841
|
+
|
|
1842
|
+
if (selectionState) {
|
|
1843
|
+
this.editor.update(() => {
|
|
1844
|
+
const selection = $getSelection();
|
|
1845
|
+
if (selection && $isRangeSelection(selection)) {
|
|
1846
|
+
selection.anchor.set(selectionState.anchor.key, selectionState.anchor.offset, "text");
|
|
1847
|
+
selection.focus.set(selectionState.focus.key, selectionState.focus.offset, "text");
|
|
1848
|
+
}
|
|
1849
|
+
});
|
|
1850
|
+
}
|
|
1851
|
+
}
|
|
1852
|
+
|
|
1853
|
+
getFormat() {
|
|
1854
|
+
const selection = $getSelection();
|
|
1855
|
+
if (!$isRangeSelection(selection)) return {}
|
|
1856
|
+
|
|
1857
|
+
const anchorNode = selection.anchor.getNode();
|
|
1858
|
+
if (!anchorNode.getParent()) return {}
|
|
1859
|
+
|
|
1860
|
+
const topLevelElement = anchorNode.getTopLevelElementOrThrow();
|
|
1861
|
+
const listType = getListType(anchorNode);
|
|
1580
1862
|
|
|
1581
|
-
|
|
1863
|
+
return {
|
|
1864
|
+
isBold: selection.hasFormat("bold"),
|
|
1865
|
+
isItalic: selection.hasFormat("italic"),
|
|
1866
|
+
isStrikethrough: selection.hasFormat("strikethrough"),
|
|
1867
|
+
isHighlight: isSelectionHighlighted(selection),
|
|
1868
|
+
isInLink: $getNearestNodeOfType(anchorNode, LinkNode) !== null,
|
|
1869
|
+
isInQuote: $isQuoteNode(topLevelElement),
|
|
1870
|
+
isInHeading: $isHeadingNode(topLevelElement),
|
|
1871
|
+
isInCode: selection.hasFormat("code") || $getNearestNodeOfType(anchorNode, CodeNode) !== null,
|
|
1872
|
+
isInList: listType !== null,
|
|
1873
|
+
listType,
|
|
1874
|
+
isInTable: $getTableCellNodeFromLexicalNode(anchorNode) !== null
|
|
1875
|
+
}
|
|
1876
|
+
}
|
|
1582
1877
|
|
|
1583
|
-
|
|
1584
|
-
|
|
1585
|
-
|
|
1878
|
+
nearestNodeOfType(nodeType) {
|
|
1879
|
+
const anchorNode = $getSelection()?.anchor?.getNode();
|
|
1880
|
+
return $getNearestNodeOfType(anchorNode, nodeType)
|
|
1881
|
+
}
|
|
1586
1882
|
|
|
1587
|
-
|
|
1588
|
-
|
|
1589
|
-
|
|
1883
|
+
get hasSelectedWordsInSingleLine() {
|
|
1884
|
+
const selection = $getSelection();
|
|
1885
|
+
if (!$isRangeSelection(selection)) return false
|
|
1886
|
+
|
|
1887
|
+
if (selection.isCollapsed()) return false
|
|
1888
|
+
|
|
1889
|
+
const anchorNode = selection.anchor.getNode();
|
|
1890
|
+
const focusNode = selection.focus.getNode();
|
|
1891
|
+
|
|
1892
|
+
if (anchorNode.getTopLevelElement() !== focusNode.getTopLevelElement()) {
|
|
1893
|
+
return false
|
|
1894
|
+
}
|
|
1895
|
+
|
|
1896
|
+
const anchorElement = anchorNode.getTopLevelElement();
|
|
1897
|
+
if (!anchorElement) return false
|
|
1898
|
+
|
|
1899
|
+
const nodes = selection.getNodes();
|
|
1900
|
+
for (const node of nodes) {
|
|
1901
|
+
if ($isLineBreakNode(node)) {
|
|
1902
|
+
return false
|
|
1903
|
+
}
|
|
1904
|
+
}
|
|
1905
|
+
|
|
1906
|
+
return true
|
|
1590
1907
|
}
|
|
1591
1908
|
|
|
1592
|
-
|
|
1593
|
-
this.
|
|
1594
|
-
|
|
1595
|
-
this.selection = editorElement.selection;
|
|
1596
|
-
this.contents = editorElement.contents;
|
|
1597
|
-
this.clipboard = editorElement.clipboard;
|
|
1909
|
+
get isInsideList() {
|
|
1910
|
+
return this.nearestNodeOfType(ListItemNode)
|
|
1911
|
+
}
|
|
1598
1912
|
|
|
1599
|
-
|
|
1600
|
-
this
|
|
1601
|
-
|
|
1913
|
+
get isIndentedList() {
|
|
1914
|
+
const closestListNode = this.nearestNodeOfType(ListNode);
|
|
1915
|
+
return closestListNode && ($getListDepth(closestListNode) > 1)
|
|
1602
1916
|
}
|
|
1603
1917
|
|
|
1604
|
-
|
|
1605
|
-
return this.
|
|
1918
|
+
get isInsideCodeBlock() {
|
|
1919
|
+
return this.nearestNodeOfType(CodeNode) !== null
|
|
1606
1920
|
}
|
|
1607
1921
|
|
|
1608
|
-
|
|
1609
|
-
this.
|
|
1922
|
+
get isTableCellSelected() {
|
|
1923
|
+
return this.nearestNodeOfType(TableCellNode) !== null
|
|
1610
1924
|
}
|
|
1611
1925
|
|
|
1612
|
-
|
|
1613
|
-
|
|
1926
|
+
get isOnPreviewableImage() {
|
|
1927
|
+
const selection = $getSelection();
|
|
1928
|
+
const firstNode = selection?.getNodes().at(0);
|
|
1929
|
+
return $isActionTextAttachmentNode(firstNode) && firstNode.isPreviewableImage
|
|
1614
1930
|
}
|
|
1615
1931
|
|
|
1616
|
-
|
|
1617
|
-
this
|
|
1932
|
+
get nodeAfterCursor() {
|
|
1933
|
+
const { anchorNode, offset } = this.#getCollapsedSelectionData();
|
|
1934
|
+
if (!anchorNode) return null
|
|
1935
|
+
|
|
1936
|
+
if ($isTextNode(anchorNode)) {
|
|
1937
|
+
return this.#getNodeAfterTextNode(anchorNode, offset)
|
|
1938
|
+
}
|
|
1939
|
+
|
|
1940
|
+
if ($isElementNode(anchorNode)) {
|
|
1941
|
+
return this.#getNodeAfterElementNode(anchorNode, offset)
|
|
1942
|
+
}
|
|
1943
|
+
|
|
1944
|
+
return this.#findNextSiblingUp(anchorNode)
|
|
1945
|
+
}
|
|
1946
|
+
|
|
1947
|
+
get topLevelNodeAfterCursor() {
|
|
1948
|
+
const { anchorNode, offset } = this.#getCollapsedSelectionData();
|
|
1949
|
+
if (!anchorNode) return null
|
|
1950
|
+
|
|
1951
|
+
if ($isTextNode(anchorNode)) {
|
|
1952
|
+
return this.#getNextNodeFromTextEnd(anchorNode)
|
|
1953
|
+
}
|
|
1954
|
+
|
|
1955
|
+
if ($isElementNode(anchorNode)) {
|
|
1956
|
+
return this.#getNodeAfterElementNode(anchorNode, offset)
|
|
1957
|
+
}
|
|
1958
|
+
|
|
1959
|
+
return this.#findNextSiblingUp(anchorNode)
|
|
1960
|
+
}
|
|
1961
|
+
|
|
1962
|
+
get nodeBeforeCursor() {
|
|
1963
|
+
const { anchorNode, offset } = this.#getCollapsedSelectionData();
|
|
1964
|
+
if (!anchorNode) return null
|
|
1965
|
+
|
|
1966
|
+
if ($isTextNode(anchorNode)) {
|
|
1967
|
+
return this.#getNodeBeforeTextNode(anchorNode, offset)
|
|
1968
|
+
}
|
|
1969
|
+
|
|
1970
|
+
if ($isElementNode(anchorNode)) {
|
|
1971
|
+
return this.#getNodeBeforeElementNode(anchorNode, offset)
|
|
1972
|
+
}
|
|
1973
|
+
|
|
1974
|
+
return this.#findPreviousSiblingUp(anchorNode)
|
|
1975
|
+
}
|
|
1976
|
+
|
|
1977
|
+
get topLevelNodeBeforeCursor() {
|
|
1978
|
+
const { anchorNode, offset } = this.#getCollapsedSelectionData();
|
|
1979
|
+
if (!anchorNode) return null
|
|
1980
|
+
|
|
1981
|
+
if ($isTextNode(anchorNode)) {
|
|
1982
|
+
return this.#getPreviousNodeFromTextStart(anchorNode)
|
|
1983
|
+
}
|
|
1984
|
+
|
|
1985
|
+
if ($isElementNode(anchorNode)) {
|
|
1986
|
+
return this.#getNodeBeforeElementNode(anchorNode, offset)
|
|
1987
|
+
}
|
|
1988
|
+
|
|
1989
|
+
return this.#findPreviousSiblingUp(anchorNode)
|
|
1990
|
+
}
|
|
1991
|
+
|
|
1992
|
+
get #currentlySelectedKeys() {
|
|
1993
|
+
if (this.currentlySelectedKeys) { return this.currentlySelectedKeys }
|
|
1994
|
+
|
|
1995
|
+
this.currentlySelectedKeys = new Set();
|
|
1996
|
+
|
|
1997
|
+
const selection = $getSelection();
|
|
1998
|
+
if (selection && $isNodeSelection(selection)) {
|
|
1999
|
+
for (const node of selection.getNodes()) {
|
|
2000
|
+
this.currentlySelectedKeys.add(node.getKey());
|
|
2001
|
+
}
|
|
2002
|
+
}
|
|
2003
|
+
|
|
2004
|
+
return this.currentlySelectedKeys
|
|
2005
|
+
}
|
|
2006
|
+
|
|
2007
|
+
#processSelectionChangeCommands() {
|
|
2008
|
+
this.editor.registerCommand(KEY_ARROW_LEFT_COMMAND, this.#selectPreviousNode.bind(this), COMMAND_PRIORITY_LOW);
|
|
2009
|
+
this.editor.registerCommand(KEY_ARROW_RIGHT_COMMAND, this.#selectNextNode.bind(this), COMMAND_PRIORITY_LOW);
|
|
2010
|
+
this.editor.registerCommand(KEY_ARROW_UP_COMMAND, this.#selectPreviousTopLevelNode.bind(this), COMMAND_PRIORITY_LOW);
|
|
2011
|
+
this.editor.registerCommand(KEY_ARROW_DOWN_COMMAND, this.#selectNextTopLevelNode.bind(this), COMMAND_PRIORITY_LOW);
|
|
2012
|
+
|
|
2013
|
+
this.editor.registerCommand(DELETE_CHARACTER_COMMAND, this.#selectDecoratorNodeBeforeDeletion.bind(this), COMMAND_PRIORITY_LOW);
|
|
2014
|
+
|
|
2015
|
+
this.editor.registerCommand(SELECTION_CHANGE_COMMAND, () => {
|
|
2016
|
+
this.current = $getSelection();
|
|
2017
|
+
}, COMMAND_PRIORITY_LOW);
|
|
2018
|
+
}
|
|
2019
|
+
|
|
2020
|
+
#listenForNodeSelections() {
|
|
2021
|
+
this.editor.registerCommand(CLICK_COMMAND, ({ target }) => {
|
|
2022
|
+
if (!isDOMNode(target)) return false
|
|
2023
|
+
|
|
2024
|
+
const targetNode = $getNearestNodeFromDOMNode(target);
|
|
2025
|
+
return $isDecoratorNode(targetNode) && this.#selectInLexical(targetNode)
|
|
2026
|
+
}, COMMAND_PRIORITY_LOW);
|
|
2027
|
+
|
|
2028
|
+
this.editor.getRootElement().addEventListener("lexxy:internal:move-to-next-line", (event) => {
|
|
2029
|
+
this.#selectOrAppendNextLine();
|
|
2030
|
+
});
|
|
2031
|
+
}
|
|
2032
|
+
|
|
2033
|
+
#containEditorFocus() {
|
|
2034
|
+
// Workaround for a bizarre Chrome bug where the cursor abandons the editor to focus on not-focusable elements
|
|
2035
|
+
// above when navigating UP/DOWN when Lexical shows its fake cursor on custom decorator nodes.
|
|
2036
|
+
this.editorContentElement.addEventListener("keydown", (event) => {
|
|
2037
|
+
if (event.key === "ArrowUp") {
|
|
2038
|
+
const lexicalCursor = this.editor.getRootElement().querySelector("[data-lexical-cursor]");
|
|
2039
|
+
|
|
2040
|
+
if (lexicalCursor) {
|
|
2041
|
+
let currentElement = lexicalCursor.previousElementSibling;
|
|
2042
|
+
while (currentElement && currentElement.hasAttribute("data-lexical-cursor")) {
|
|
2043
|
+
currentElement = currentElement.previousElementSibling;
|
|
2044
|
+
}
|
|
2045
|
+
|
|
2046
|
+
if (!currentElement) {
|
|
2047
|
+
event.preventDefault();
|
|
2048
|
+
}
|
|
2049
|
+
}
|
|
2050
|
+
}
|
|
2051
|
+
|
|
2052
|
+
if (event.key === "ArrowDown") {
|
|
2053
|
+
const lexicalCursor = this.editor.getRootElement().querySelector("[data-lexical-cursor]");
|
|
2054
|
+
|
|
2055
|
+
if (lexicalCursor) {
|
|
2056
|
+
let currentElement = lexicalCursor.nextElementSibling;
|
|
2057
|
+
while (currentElement && currentElement.hasAttribute("data-lexical-cursor")) {
|
|
2058
|
+
currentElement = currentElement.nextElementSibling;
|
|
2059
|
+
}
|
|
2060
|
+
|
|
2061
|
+
if (!currentElement) {
|
|
2062
|
+
event.preventDefault();
|
|
2063
|
+
}
|
|
2064
|
+
}
|
|
2065
|
+
}
|
|
2066
|
+
}, true);
|
|
2067
|
+
}
|
|
2068
|
+
|
|
2069
|
+
#syncSelectedClasses() {
|
|
2070
|
+
this.#clearPreviouslyHighlightedItems();
|
|
2071
|
+
this.#highlightNewItems();
|
|
2072
|
+
|
|
2073
|
+
this.previouslySelectedKeys = this.#currentlySelectedKeys;
|
|
2074
|
+
this.currentlySelectedKeys = null;
|
|
2075
|
+
}
|
|
2076
|
+
|
|
2077
|
+
#clearPreviouslyHighlightedItems() {
|
|
2078
|
+
for (const key of this.previouslySelectedKeys) {
|
|
2079
|
+
if (!this.#currentlySelectedKeys.has(key)) {
|
|
2080
|
+
const dom = this.editor.getElementByKey(key);
|
|
2081
|
+
if (dom) dom.classList.remove("node--selected");
|
|
2082
|
+
}
|
|
2083
|
+
}
|
|
2084
|
+
}
|
|
2085
|
+
|
|
2086
|
+
#highlightNewItems() {
|
|
2087
|
+
for (const key of this.#currentlySelectedKeys) {
|
|
2088
|
+
if (!this.previouslySelectedKeys.has(key)) {
|
|
2089
|
+
const nodeElement = this.editor.getElementByKey(key);
|
|
2090
|
+
if (nodeElement) nodeElement.classList.add("node--selected");
|
|
2091
|
+
}
|
|
2092
|
+
}
|
|
2093
|
+
}
|
|
2094
|
+
|
|
2095
|
+
async #selectPreviousNode() {
|
|
2096
|
+
if (this.hasNodeSelection) {
|
|
2097
|
+
return await this.#withCurrentNode((currentNode) => currentNode.selectPrevious())
|
|
2098
|
+
} else {
|
|
2099
|
+
return this.#selectInLexical(this.nodeBeforeCursor)
|
|
2100
|
+
}
|
|
2101
|
+
}
|
|
2102
|
+
|
|
2103
|
+
async #selectNextNode() {
|
|
2104
|
+
if (this.hasNodeSelection) {
|
|
2105
|
+
return await this.#withCurrentNode((currentNode) => currentNode.selectNext(0, 0))
|
|
2106
|
+
} else {
|
|
2107
|
+
return this.#selectInLexical(this.nodeAfterCursor)
|
|
2108
|
+
}
|
|
2109
|
+
}
|
|
2110
|
+
|
|
2111
|
+
async #selectPreviousTopLevelNode() {
|
|
2112
|
+
if (this.hasNodeSelection) {
|
|
2113
|
+
return await this.#withCurrentNode((currentNode) => currentNode.getTopLevelElement().selectPrevious())
|
|
2114
|
+
} else {
|
|
2115
|
+
return this.#selectInLexical(this.topLevelNodeBeforeCursor)
|
|
2116
|
+
}
|
|
1618
2117
|
}
|
|
1619
2118
|
|
|
1620
|
-
|
|
1621
|
-
this.
|
|
2119
|
+
async #selectNextTopLevelNode() {
|
|
2120
|
+
if (this.hasNodeSelection) {
|
|
2121
|
+
return await this.#withCurrentNode((currentNode) => currentNode.getTopLevelElement().selectNext(0, 0))
|
|
2122
|
+
} else {
|
|
2123
|
+
return this.#selectInLexical(this.topLevelNodeAfterCursor)
|
|
2124
|
+
}
|
|
1622
2125
|
}
|
|
1623
2126
|
|
|
1624
|
-
|
|
1625
|
-
|
|
2127
|
+
async #withCurrentNode(fn) {
|
|
2128
|
+
await nextFrame();
|
|
2129
|
+
if (this.hasNodeSelection) {
|
|
2130
|
+
this.editor.update(() => {
|
|
2131
|
+
fn($getSelection().getNodes()[0]);
|
|
2132
|
+
this.editor.focus();
|
|
2133
|
+
});
|
|
2134
|
+
}
|
|
1626
2135
|
}
|
|
1627
2136
|
|
|
1628
|
-
|
|
2137
|
+
async #selectOrAppendNextLine() {
|
|
1629
2138
|
this.editor.update(() => {
|
|
1630
|
-
const
|
|
1631
|
-
if (
|
|
2139
|
+
const topLevelElement = this.#getTopLevelElementFromSelection();
|
|
2140
|
+
if (!topLevelElement) return
|
|
1632
2141
|
|
|
1633
|
-
|
|
1634
|
-
const autoLinkNode = $createAutoLinkNode(url);
|
|
1635
|
-
const textNode = $createTextNode(url);
|
|
1636
|
-
autoLinkNode.append(textNode);
|
|
1637
|
-
selection.insertNodes([ autoLinkNode ]);
|
|
1638
|
-
} else {
|
|
1639
|
-
$toggleLink(url);
|
|
1640
|
-
}
|
|
2142
|
+
this.#moveToOrCreateNextLine(topLevelElement);
|
|
1641
2143
|
});
|
|
1642
2144
|
}
|
|
1643
2145
|
|
|
1644
|
-
|
|
1645
|
-
this.#toggleLink(null);
|
|
1646
|
-
}
|
|
1647
|
-
|
|
1648
|
-
dispatchInsertUnorderedList() {
|
|
2146
|
+
#getTopLevelElementFromSelection() {
|
|
1649
2147
|
const selection = $getSelection();
|
|
1650
|
-
if (!selection) return
|
|
1651
|
-
|
|
1652
|
-
const anchorNode = selection.anchor.getNode();
|
|
2148
|
+
if (!selection) return null
|
|
1653
2149
|
|
|
1654
|
-
if (
|
|
1655
|
-
this
|
|
1656
|
-
} else {
|
|
1657
|
-
this.editor.dispatchCommand(INSERT_UNORDERED_LIST_COMMAND, undefined);
|
|
2150
|
+
if ($isNodeSelection(selection)) {
|
|
2151
|
+
return this.#getTopLevelFromNodeSelection(selection)
|
|
1658
2152
|
}
|
|
1659
|
-
}
|
|
1660
2153
|
|
|
1661
|
-
|
|
1662
|
-
|
|
1663
|
-
if (!selection) return
|
|
1664
|
-
|
|
1665
|
-
const anchorNode = selection.anchor.getNode();
|
|
1666
|
-
|
|
1667
|
-
if (this.selection.isInsideList && anchorNode && getListType(anchorNode) === "number") {
|
|
1668
|
-
this.contents.unwrapSelectedListItems();
|
|
1669
|
-
} else {
|
|
1670
|
-
this.editor.dispatchCommand(INSERT_ORDERED_LIST_COMMAND, undefined);
|
|
2154
|
+
if ($isRangeSelection(selection)) {
|
|
2155
|
+
return this.#getTopLevelFromRangeSelection(selection)
|
|
1671
2156
|
}
|
|
1672
|
-
}
|
|
1673
2157
|
|
|
1674
|
-
|
|
1675
|
-
this.contents.toggleNodeWrappingAllSelectedNodes((node) => $isQuoteNode(node), () => $createQuoteNode());
|
|
2158
|
+
return null
|
|
1676
2159
|
}
|
|
1677
2160
|
|
|
1678
|
-
|
|
1679
|
-
|
|
1680
|
-
|
|
1681
|
-
this.editor.dispatchCommand(FORMAT_TEXT_COMMAND, "code");
|
|
1682
|
-
} else {
|
|
1683
|
-
this.contents.toggleNodeWrappingAllSelectedLines((node) => $isCodeNode(node), () => new CodeNode("plain"));
|
|
1684
|
-
}
|
|
1685
|
-
});
|
|
2161
|
+
#getTopLevelFromNodeSelection(selection) {
|
|
2162
|
+
const nodes = selection.getNodes();
|
|
2163
|
+
return nodes.length > 0 ? nodes[0].getTopLevelElement() : null
|
|
1686
2164
|
}
|
|
1687
2165
|
|
|
1688
|
-
|
|
1689
|
-
|
|
1690
|
-
|
|
2166
|
+
#getTopLevelFromRangeSelection(selection) {
|
|
2167
|
+
const anchorNode = selection.anchor.getNode();
|
|
2168
|
+
return anchorNode.getTopLevelElement()
|
|
1691
2169
|
}
|
|
1692
2170
|
|
|
1693
|
-
|
|
1694
|
-
const
|
|
1695
|
-
if (!$isRangeSelection(selection)) return
|
|
2171
|
+
#moveToOrCreateNextLine(topLevelElement) {
|
|
2172
|
+
const nextSibling = topLevelElement.getNextSibling();
|
|
1696
2173
|
|
|
1697
|
-
if (
|
|
1698
|
-
|
|
1699
|
-
|
|
2174
|
+
if (nextSibling) {
|
|
2175
|
+
nextSibling.selectStart();
|
|
2176
|
+
} else {
|
|
2177
|
+
this.#createAndSelectNewParagraph();
|
|
1700
2178
|
}
|
|
2179
|
+
}
|
|
1701
2180
|
|
|
1702
|
-
|
|
1703
|
-
|
|
1704
|
-
|
|
1705
|
-
|
|
1706
|
-
|
|
1707
|
-
|
|
1708
|
-
} else if (currentTag === "h3") {
|
|
1709
|
-
nextTag = "h4";
|
|
1710
|
-
} else if (currentTag === "h4") {
|
|
1711
|
-
nextTag = null;
|
|
1712
|
-
} else {
|
|
1713
|
-
nextTag = "h2";
|
|
1714
|
-
}
|
|
1715
|
-
}
|
|
2181
|
+
#createAndSelectNewParagraph() {
|
|
2182
|
+
const root = $getRoot();
|
|
2183
|
+
const newParagraph = $createParagraphNode();
|
|
2184
|
+
root.append(newParagraph);
|
|
2185
|
+
newParagraph.selectStart();
|
|
2186
|
+
}
|
|
1716
2187
|
|
|
1717
|
-
|
|
1718
|
-
|
|
2188
|
+
#selectInLexical(node) {
|
|
2189
|
+
if ($isDecoratorNode(node)) {
|
|
2190
|
+
const selection = $createNodeSelectionWith(node);
|
|
2191
|
+
$setSelection(selection);
|
|
2192
|
+
return selection
|
|
1719
2193
|
} else {
|
|
1720
|
-
|
|
2194
|
+
return false
|
|
1721
2195
|
}
|
|
1722
2196
|
}
|
|
1723
2197
|
|
|
1724
|
-
|
|
1725
|
-
const
|
|
1726
|
-
|
|
1727
|
-
multiple: true,
|
|
1728
|
-
style: "display: none;",
|
|
1729
|
-
onchange: ({ target }) => {
|
|
1730
|
-
const files = Array.from(target.files);
|
|
1731
|
-
if (!files.length) return
|
|
2198
|
+
#selectDecoratorNodeBeforeDeletion(backwards) {
|
|
2199
|
+
const node = backwards ? this.nodeBeforeCursor : this.nodeAfterCursor;
|
|
2200
|
+
if (!$isDecoratorNode(node)) return false
|
|
1732
2201
|
|
|
1733
|
-
|
|
1734
|
-
this.contents.uploadFile(file);
|
|
1735
|
-
}
|
|
1736
|
-
}
|
|
1737
|
-
});
|
|
2202
|
+
this.#removeEmptyElementAnchorNode();
|
|
1738
2203
|
|
|
1739
|
-
this
|
|
1740
|
-
|
|
1741
|
-
setTimeout(() => input.remove(), 1000);
|
|
2204
|
+
const selection = this.#selectInLexical(node);
|
|
2205
|
+
return Boolean(selection)
|
|
1742
2206
|
}
|
|
1743
2207
|
|
|
1744
|
-
|
|
1745
|
-
|
|
2208
|
+
#removeEmptyElementAnchorNode(anchor = $getSelection()?.anchor) {
|
|
2209
|
+
const anchorNode = anchor?.getNode();
|
|
2210
|
+
if ($isElementNode(anchorNode) && anchorNode?.isEmpty()) anchorNode.remove();
|
|
1746
2211
|
}
|
|
1747
2212
|
|
|
1748
|
-
|
|
1749
|
-
|
|
1750
|
-
|
|
2213
|
+
#getValidSelectionRange() {
|
|
2214
|
+
const lexicalSelection = $getSelection();
|
|
2215
|
+
if (!lexicalSelection || !lexicalSelection.isCollapsed()) return null
|
|
1751
2216
|
|
|
1752
|
-
|
|
1753
|
-
|
|
2217
|
+
const nativeSelection = window.getSelection();
|
|
2218
|
+
if (!nativeSelection || nativeSelection.rangeCount === 0) return null
|
|
2219
|
+
|
|
2220
|
+
return nativeSelection.getRangeAt(0)
|
|
1754
2221
|
}
|
|
1755
2222
|
|
|
1756
|
-
#
|
|
1757
|
-
|
|
1758
|
-
|
|
1759
|
-
|
|
2223
|
+
#getReliableRectFromRange(range) {
|
|
2224
|
+
let rect = range.getBoundingClientRect();
|
|
2225
|
+
|
|
2226
|
+
if (this.#isRectUnreliable(rect)) {
|
|
2227
|
+
const marker = this.#createAndInsertMarker(range);
|
|
2228
|
+
rect = marker.getBoundingClientRect();
|
|
2229
|
+
this.#restoreSelectionAfterMarker(marker);
|
|
2230
|
+
marker.remove();
|
|
1760
2231
|
}
|
|
1761
2232
|
|
|
1762
|
-
|
|
2233
|
+
return rect
|
|
1763
2234
|
}
|
|
1764
2235
|
|
|
1765
|
-
#
|
|
1766
|
-
|
|
2236
|
+
#isRectUnreliable(rect) {
|
|
2237
|
+
return rect.width === 0 && rect.height === 0 || rect.top === 0 && rect.left === 0
|
|
1767
2238
|
}
|
|
1768
2239
|
|
|
1769
|
-
#
|
|
1770
|
-
|
|
2240
|
+
#createAndInsertMarker(range) {
|
|
2241
|
+
const marker = this.#createMarker();
|
|
2242
|
+
range.insertNode(marker);
|
|
2243
|
+
return marker
|
|
1771
2244
|
}
|
|
1772
2245
|
|
|
1773
|
-
#
|
|
1774
|
-
|
|
1775
|
-
|
|
1776
|
-
|
|
1777
|
-
|
|
1778
|
-
|
|
1779
|
-
|
|
1780
|
-
|
|
2246
|
+
#createMarker() {
|
|
2247
|
+
const marker = document.createElement("span");
|
|
2248
|
+
marker.textContent = "\u200b";
|
|
2249
|
+
marker.style.display = "inline-block";
|
|
2250
|
+
marker.style.width = "1px";
|
|
2251
|
+
marker.style.height = "1em";
|
|
2252
|
+
marker.style.lineHeight = "normal";
|
|
2253
|
+
marker.setAttribute("nonce", getNonce());
|
|
2254
|
+
return marker
|
|
1781
2255
|
}
|
|
1782
2256
|
|
|
1783
|
-
#
|
|
1784
|
-
|
|
1785
|
-
|
|
1786
|
-
|
|
1787
|
-
|
|
2257
|
+
#restoreSelectionAfterMarker(marker) {
|
|
2258
|
+
const nativeSelection = window.getSelection();
|
|
2259
|
+
nativeSelection.removeAllRanges();
|
|
2260
|
+
const newRange = document.createRange();
|
|
2261
|
+
newRange.setStartAfter(marker);
|
|
2262
|
+
newRange.collapse(true);
|
|
2263
|
+
nativeSelection.addRange(newRange);
|
|
1788
2264
|
}
|
|
1789
2265
|
|
|
1790
|
-
#
|
|
1791
|
-
this.
|
|
1792
|
-
|
|
1793
|
-
|
|
2266
|
+
#calculateCursorPosition(rect, range) {
|
|
2267
|
+
const rootRect = this.editor.getRootElement().getBoundingClientRect();
|
|
2268
|
+
const x = rect.left - rootRect.left;
|
|
2269
|
+
let y = rect.top - rootRect.top;
|
|
2270
|
+
|
|
2271
|
+
const fontSize = this.#getFontSizeForCursor(range);
|
|
2272
|
+
if (!isNaN(fontSize)) {
|
|
2273
|
+
y += fontSize;
|
|
1794
2274
|
}
|
|
2275
|
+
|
|
2276
|
+
return { x, y, fontSize }
|
|
1795
2277
|
}
|
|
1796
2278
|
|
|
1797
|
-
#
|
|
1798
|
-
|
|
2279
|
+
#getFontSizeForCursor(range) {
|
|
2280
|
+
const nativeSelection = window.getSelection();
|
|
2281
|
+
const anchorNode = nativeSelection.anchorNode;
|
|
2282
|
+
const parentElement = this.#getElementFromNode(anchorNode);
|
|
2283
|
+
|
|
2284
|
+
if (parentElement instanceof HTMLElement) {
|
|
2285
|
+
const computed = window.getComputedStyle(parentElement);
|
|
2286
|
+
return parseFloat(computed.fontSize)
|
|
2287
|
+
}
|
|
2288
|
+
|
|
2289
|
+
return 0
|
|
1799
2290
|
}
|
|
1800
2291
|
|
|
1801
|
-
#
|
|
1802
|
-
|
|
2292
|
+
#getElementFromNode(node) {
|
|
2293
|
+
return node?.nodeType === Node.TEXT_NODE ? node.parentElement : node
|
|
2294
|
+
}
|
|
1803
2295
|
|
|
1804
|
-
|
|
1805
|
-
|
|
2296
|
+
#getCollapsedSelectionData() {
|
|
2297
|
+
const selection = $getSelection();
|
|
2298
|
+
if (!$isRangeSelection(selection) || !selection.isCollapsed()) {
|
|
2299
|
+
return { anchorNode: null, offset: 0 }
|
|
2300
|
+
}
|
|
1806
2301
|
|
|
1807
|
-
const
|
|
1808
|
-
|
|
2302
|
+
const { anchor } = selection;
|
|
2303
|
+
return { anchorNode: anchor.getNode(), offset: anchor.offset }
|
|
2304
|
+
}
|
|
1809
2305
|
|
|
1810
|
-
|
|
1811
|
-
if (
|
|
2306
|
+
#getNodeAfterTextNode(anchorNode, offset) {
|
|
2307
|
+
if (offset === anchorNode.getTextContentSize()) {
|
|
2308
|
+
return this.#getNextNodeFromTextEnd(anchorNode)
|
|
2309
|
+
}
|
|
2310
|
+
return null
|
|
2311
|
+
}
|
|
1812
2312
|
|
|
1813
|
-
|
|
1814
|
-
|
|
2313
|
+
#getNextNodeFromTextEnd(anchorNode) {
|
|
2314
|
+
if (anchorNode.getNextSibling() instanceof DecoratorNode) {
|
|
2315
|
+
return anchorNode.getNextSibling()
|
|
1815
2316
|
}
|
|
2317
|
+
const parent = anchorNode.getParent();
|
|
2318
|
+
return parent ? parent.getNextSibling() : null
|
|
2319
|
+
}
|
|
1816
2320
|
|
|
1817
|
-
|
|
2321
|
+
#getNodeAfterElementNode(anchorNode, offset) {
|
|
2322
|
+
if (offset < anchorNode.getChildrenSize()) {
|
|
2323
|
+
return anchorNode.getChildAtIndex(offset)
|
|
2324
|
+
}
|
|
2325
|
+
return this.#findNextSiblingUp(anchorNode)
|
|
1818
2326
|
}
|
|
1819
2327
|
|
|
1820
|
-
#
|
|
1821
|
-
if (
|
|
1822
|
-
return this.#
|
|
1823
|
-
} else if (this.selection.isInsideCodeBlock) {
|
|
1824
|
-
return this.#handleTabForCode()
|
|
2328
|
+
#getNodeBeforeTextNode(anchorNode, offset) {
|
|
2329
|
+
if (offset === 0) {
|
|
2330
|
+
return this.#getPreviousNodeFromTextStart(anchorNode)
|
|
1825
2331
|
}
|
|
1826
|
-
return
|
|
2332
|
+
return null
|
|
1827
2333
|
}
|
|
1828
2334
|
|
|
1829
|
-
#
|
|
1830
|
-
if (
|
|
2335
|
+
#getPreviousNodeFromTextStart(anchorNode) {
|
|
2336
|
+
if (anchorNode.getPreviousSibling() instanceof DecoratorNode) {
|
|
2337
|
+
return anchorNode.getPreviousSibling()
|
|
2338
|
+
}
|
|
2339
|
+
const parent = anchorNode.getParent();
|
|
2340
|
+
return parent.getPreviousSibling()
|
|
2341
|
+
}
|
|
1831
2342
|
|
|
1832
|
-
|
|
1833
|
-
|
|
1834
|
-
|
|
2343
|
+
#getNodeBeforeElementNode(anchorNode, offset) {
|
|
2344
|
+
if (offset > 0) {
|
|
2345
|
+
return anchorNode.getChildAtIndex(offset - 1)
|
|
2346
|
+
}
|
|
2347
|
+
return this.#findPreviousSiblingUp(anchorNode)
|
|
1835
2348
|
}
|
|
1836
2349
|
|
|
1837
|
-
#
|
|
1838
|
-
|
|
1839
|
-
|
|
2350
|
+
#findNextSiblingUp(node) {
|
|
2351
|
+
let current = node;
|
|
2352
|
+
while (current && current.getNextSibling() == null) {
|
|
2353
|
+
current = current.getParent();
|
|
2354
|
+
}
|
|
2355
|
+
return current ? current.getNextSibling() : null
|
|
1840
2356
|
}
|
|
1841
2357
|
|
|
1842
|
-
|
|
1843
|
-
|
|
1844
|
-
|
|
1845
|
-
|
|
1846
|
-
|
|
1847
|
-
|
|
1848
|
-
$toggleLink(url);
|
|
1849
|
-
}
|
|
1850
|
-
});
|
|
2358
|
+
#findPreviousSiblingUp(node) {
|
|
2359
|
+
let current = node;
|
|
2360
|
+
while (current && current.getPreviousSibling() == null) {
|
|
2361
|
+
current = current.getParent();
|
|
2362
|
+
}
|
|
2363
|
+
return current ? current.getPreviousSibling() : null
|
|
1851
2364
|
}
|
|
1852
2365
|
}
|
|
1853
2366
|
|
|
1854
|
-
function
|
|
1855
|
-
return
|
|
2367
|
+
function sanitize(html) {
|
|
2368
|
+
return DOMPurify.sanitize(html, buildConfig())
|
|
1856
2369
|
}
|
|
1857
2370
|
|
|
1858
|
-
function
|
|
1859
|
-
|
|
1860
|
-
|
|
1861
|
-
return (...args) => {
|
|
1862
|
-
clearTimeout(timeout);
|
|
2371
|
+
function dasherize(value) {
|
|
2372
|
+
return value.replace(/([A-Z])/g, (_, char) => `-${char.toLowerCase()}`)
|
|
2373
|
+
}
|
|
1863
2374
|
|
|
1864
|
-
|
|
1865
|
-
|
|
1866
|
-
|
|
1867
|
-
|
|
1868
|
-
|
|
1869
|
-
|
|
1870
|
-
reject(err);
|
|
1871
|
-
}
|
|
1872
|
-
}, wait);
|
|
1873
|
-
})
|
|
2375
|
+
function isUrl(string) {
|
|
2376
|
+
try {
|
|
2377
|
+
new URL(string);
|
|
2378
|
+
return true
|
|
2379
|
+
} catch {
|
|
2380
|
+
return false
|
|
1874
2381
|
}
|
|
1875
2382
|
}
|
|
1876
2383
|
|
|
1877
|
-
function
|
|
1878
|
-
return
|
|
2384
|
+
function normalizeFilteredText(string) {
|
|
2385
|
+
return string
|
|
2386
|
+
.toLowerCase()
|
|
2387
|
+
.normalize("NFD").replace(/[\u0300-\u036f]/g, "") // Remove diacritics
|
|
1879
2388
|
}
|
|
1880
2389
|
|
|
1881
|
-
|
|
1882
|
-
|
|
1883
|
-
|
|
1884
|
-
this.editorContentElement = editorElement.editorContentElement;
|
|
1885
|
-
this.editor = this.editorElement.editor;
|
|
1886
|
-
this.previouslySelectedKeys = new Set();
|
|
2390
|
+
function filterMatches(text, potentialMatch) {
|
|
2391
|
+
return normalizeFilteredText(text).includes(normalizeFilteredText(potentialMatch))
|
|
2392
|
+
}
|
|
1887
2393
|
|
|
1888
|
-
|
|
1889
|
-
|
|
1890
|
-
|
|
1891
|
-
}
|
|
2394
|
+
function upcaseFirst(string) {
|
|
2395
|
+
return string.charAt(0).toUpperCase() + string.slice(1)
|
|
2396
|
+
}
|
|
1892
2397
|
|
|
1893
|
-
|
|
1894
|
-
|
|
1895
|
-
|
|
1896
|
-
});
|
|
1897
|
-
}
|
|
2398
|
+
class EditorConfiguration {
|
|
2399
|
+
#editorElement
|
|
2400
|
+
#config
|
|
1898
2401
|
|
|
1899
|
-
|
|
1900
|
-
|
|
1901
|
-
|
|
1902
|
-
|
|
1903
|
-
|
|
2402
|
+
constructor(editorElement) {
|
|
2403
|
+
this.#editorElement = editorElement;
|
|
2404
|
+
this.#config = new Configuration(
|
|
2405
|
+
Lexxy.presets.get("default"),
|
|
2406
|
+
Lexxy.presets.get(editorElement.preset),
|
|
2407
|
+
this.#overrides
|
|
2408
|
+
);
|
|
1904
2409
|
}
|
|
1905
2410
|
|
|
1906
|
-
get
|
|
1907
|
-
|
|
1908
|
-
|
|
1909
|
-
this.editor.getEditorState().read(() => {
|
|
1910
|
-
const range = this.#getValidSelectionRange();
|
|
1911
|
-
if (!range) return
|
|
1912
|
-
|
|
1913
|
-
const rect = this.#getReliableRectFromRange(range);
|
|
1914
|
-
if (!rect) return
|
|
1915
|
-
|
|
1916
|
-
position = this.#calculateCursorPosition(rect, range);
|
|
1917
|
-
});
|
|
1918
|
-
|
|
1919
|
-
return position
|
|
2411
|
+
get(path) {
|
|
2412
|
+
return this.#config.get(path)
|
|
1920
2413
|
}
|
|
1921
2414
|
|
|
1922
|
-
|
|
1923
|
-
|
|
1924
|
-
|
|
1925
|
-
const
|
|
1926
|
-
|
|
1927
|
-
|
|
1928
|
-
lastDescendant.selectEnd();
|
|
1929
|
-
} else {
|
|
1930
|
-
root.selectEnd();
|
|
2415
|
+
get #overrides() {
|
|
2416
|
+
const overrides = {};
|
|
2417
|
+
for (const option of this.#defaultOptions) {
|
|
2418
|
+
const attribute = dasherize(option);
|
|
2419
|
+
if (this.#editorElement.hasAttribute(attribute)) {
|
|
2420
|
+
overrides[option] = this.#parseAttribute(attribute);
|
|
1931
2421
|
}
|
|
1932
|
-
}
|
|
2422
|
+
}
|
|
2423
|
+
return overrides
|
|
1933
2424
|
}
|
|
1934
2425
|
|
|
1935
|
-
|
|
1936
|
-
|
|
1937
|
-
|
|
2426
|
+
get #defaultOptions() {
|
|
2427
|
+
return Object.keys(Lexxy.presets.get("default"))
|
|
2428
|
+
}
|
|
1938
2429
|
|
|
1939
|
-
|
|
1940
|
-
|
|
1941
|
-
|
|
1942
|
-
|
|
1943
|
-
|
|
1944
|
-
|
|
1945
|
-
const [ node ] = selection.getNodes();
|
|
1946
|
-
return {
|
|
1947
|
-
node,
|
|
1948
|
-
offset: 0
|
|
1949
|
-
}
|
|
2430
|
+
#parseAttribute(attribute) {
|
|
2431
|
+
const value = this.#editorElement.getAttribute(attribute);
|
|
2432
|
+
try {
|
|
2433
|
+
return JSON.parse(value)
|
|
2434
|
+
} catch {
|
|
2435
|
+
return value
|
|
1950
2436
|
}
|
|
2437
|
+
}
|
|
2438
|
+
}
|
|
1951
2439
|
|
|
1952
|
-
|
|
2440
|
+
class CustomActionTextAttachmentNode extends DecoratorNode {
|
|
2441
|
+
static getType() {
|
|
2442
|
+
return "custom_action_text_attachment"
|
|
1953
2443
|
}
|
|
1954
2444
|
|
|
1955
|
-
|
|
1956
|
-
|
|
2445
|
+
static clone(node) {
|
|
2446
|
+
return new CustomActionTextAttachmentNode({ ...node }, node.__key)
|
|
2447
|
+
}
|
|
1957
2448
|
|
|
1958
|
-
|
|
1959
|
-
|
|
1960
|
-
|
|
1961
|
-
selectionState = {
|
|
1962
|
-
anchor: { key: selection.anchor.key, offset: selection.anchor.offset },
|
|
1963
|
-
focus: { key: selection.focus.key, offset: selection.focus.offset }
|
|
1964
|
-
};
|
|
1965
|
-
}
|
|
1966
|
-
});
|
|
2449
|
+
static importJSON(serializedNode) {
|
|
2450
|
+
return new CustomActionTextAttachmentNode({ ...serializedNode })
|
|
2451
|
+
}
|
|
1967
2452
|
|
|
1968
|
-
|
|
2453
|
+
static importDOM() {
|
|
1969
2454
|
|
|
1970
|
-
|
|
1971
|
-
this.
|
|
1972
|
-
|
|
1973
|
-
|
|
1974
|
-
selection.anchor.set(selectionState.anchor.key, selectionState.anchor.offset, "text");
|
|
1975
|
-
selection.focus.set(selectionState.focus.key, selectionState.focus.offset, "text");
|
|
2455
|
+
return {
|
|
2456
|
+
[this.TAG_NAME]: (element) => {
|
|
2457
|
+
if (!element.getAttribute("content")) {
|
|
2458
|
+
return null
|
|
1976
2459
|
}
|
|
1977
|
-
});
|
|
1978
|
-
}
|
|
1979
|
-
}
|
|
1980
|
-
|
|
1981
|
-
getFormat() {
|
|
1982
|
-
const selection = $getSelection();
|
|
1983
|
-
if (!$isRangeSelection(selection)) return {}
|
|
1984
2460
|
|
|
1985
|
-
|
|
1986
|
-
|
|
2461
|
+
return {
|
|
2462
|
+
conversion: (attachment) => {
|
|
2463
|
+
// Preserve initial space if present since Lexical removes it
|
|
2464
|
+
const nodes = [];
|
|
2465
|
+
const previousSibling = attachment.previousSibling;
|
|
2466
|
+
if (previousSibling && previousSibling.nodeType === Node.TEXT_NODE && /\s$/.test(previousSibling.textContent)) {
|
|
2467
|
+
nodes.push($createTextNode(" "));
|
|
2468
|
+
}
|
|
1987
2469
|
|
|
1988
|
-
|
|
1989
|
-
|
|
2470
|
+
nodes.push(new CustomActionTextAttachmentNode({
|
|
2471
|
+
sgid: attachment.getAttribute("sgid"),
|
|
2472
|
+
innerHtml: JSON.parse(attachment.getAttribute("content")),
|
|
2473
|
+
contentType: attachment.getAttribute("content-type")
|
|
2474
|
+
}));
|
|
1990
2475
|
|
|
1991
|
-
|
|
1992
|
-
|
|
1993
|
-
|
|
1994
|
-
|
|
1995
|
-
|
|
1996
|
-
|
|
1997
|
-
|
|
1998
|
-
isInHeading: $isHeadingNode(topLevelElement),
|
|
1999
|
-
isInCode: selection.hasFormat("code") || $getNearestNodeOfType(anchorNode, CodeNode) !== null,
|
|
2000
|
-
isInList: listType !== null,
|
|
2001
|
-
listType,
|
|
2002
|
-
isInTable: $getTableCellNodeFromLexicalNode(anchorNode) !== null
|
|
2476
|
+
nodes.push($createTextNode(" "));
|
|
2477
|
+
|
|
2478
|
+
return { node: nodes }
|
|
2479
|
+
},
|
|
2480
|
+
priority: 2
|
|
2481
|
+
}
|
|
2482
|
+
}
|
|
2003
2483
|
}
|
|
2004
2484
|
}
|
|
2005
2485
|
|
|
2006
|
-
|
|
2007
|
-
|
|
2008
|
-
return $getNearestNodeOfType(anchorNode, nodeType)
|
|
2486
|
+
static get TAG_NAME() {
|
|
2487
|
+
return Lexxy.global.get("attachmentTagName")
|
|
2009
2488
|
}
|
|
2010
2489
|
|
|
2011
|
-
|
|
2012
|
-
|
|
2013
|
-
if (!$isRangeSelection(selection)) return false
|
|
2014
|
-
|
|
2015
|
-
if (selection.isCollapsed()) return false
|
|
2490
|
+
constructor({ tagName, sgid, contentType, innerHtml }, key) {
|
|
2491
|
+
super(key);
|
|
2016
2492
|
|
|
2017
|
-
const
|
|
2018
|
-
const focusNode = selection.focus.getNode();
|
|
2493
|
+
const contentTypeNamespace = Lexxy.global.get("attachmentContentTypeNamespace");
|
|
2019
2494
|
|
|
2020
|
-
|
|
2021
|
-
|
|
2022
|
-
}
|
|
2495
|
+
this.tagName = tagName || CustomActionTextAttachmentNode.TAG_NAME;
|
|
2496
|
+
this.sgid = sgid;
|
|
2497
|
+
this.contentType = contentType || `application/vnd.${contentTypeNamespace}.unknown`;
|
|
2498
|
+
this.innerHtml = innerHtml;
|
|
2499
|
+
}
|
|
2023
2500
|
|
|
2024
|
-
|
|
2025
|
-
|
|
2501
|
+
createDOM() {
|
|
2502
|
+
const figure = createElement(this.tagName, { "content-type": this.contentType, "data-lexxy-decorator": true });
|
|
2026
2503
|
|
|
2027
|
-
|
|
2028
|
-
for (const node of nodes) {
|
|
2029
|
-
if ($isLineBreakNode(node)) {
|
|
2030
|
-
return false
|
|
2031
|
-
}
|
|
2032
|
-
}
|
|
2504
|
+
figure.insertAdjacentHTML("beforeend", this.innerHtml);
|
|
2033
2505
|
|
|
2034
|
-
|
|
2035
|
-
|
|
2506
|
+
const deleteButton = createElement("lexxy-node-delete-button");
|
|
2507
|
+
figure.appendChild(deleteButton);
|
|
2036
2508
|
|
|
2037
|
-
|
|
2038
|
-
return this.nearestNodeOfType(ListItemNode)
|
|
2509
|
+
return figure
|
|
2039
2510
|
}
|
|
2040
2511
|
|
|
2041
|
-
|
|
2042
|
-
|
|
2043
|
-
return closestListNode && ($getListDepth(closestListNode) > 1)
|
|
2512
|
+
updateDOM() {
|
|
2513
|
+
return false
|
|
2044
2514
|
}
|
|
2045
2515
|
|
|
2046
|
-
|
|
2047
|
-
return this.
|
|
2516
|
+
getTextContent() {
|
|
2517
|
+
return this.createDOM().textContent.trim() || `[${this.contentType}]`
|
|
2048
2518
|
}
|
|
2049
2519
|
|
|
2050
|
-
|
|
2051
|
-
return
|
|
2520
|
+
isInline() {
|
|
2521
|
+
return true
|
|
2052
2522
|
}
|
|
2053
2523
|
|
|
2054
|
-
|
|
2055
|
-
const
|
|
2056
|
-
|
|
2524
|
+
exportDOM() {
|
|
2525
|
+
const attachment = createElement(this.tagName, {
|
|
2526
|
+
sgid: this.sgid,
|
|
2527
|
+
content: JSON.stringify(this.innerHtml),
|
|
2528
|
+
"content-type": this.contentType
|
|
2529
|
+
});
|
|
2057
2530
|
|
|
2058
|
-
|
|
2059
|
-
|
|
2060
|
-
}
|
|
2531
|
+
return { element: attachment }
|
|
2532
|
+
}
|
|
2061
2533
|
|
|
2062
|
-
|
|
2063
|
-
|
|
2534
|
+
exportJSON() {
|
|
2535
|
+
return {
|
|
2536
|
+
type: "custom_action_text_attachment",
|
|
2537
|
+
version: 1,
|
|
2538
|
+
tagName: this.tagName,
|
|
2539
|
+
sgid: this.sgid,
|
|
2540
|
+
contentType: this.contentType,
|
|
2541
|
+
innerHtml: this.innerHtml
|
|
2064
2542
|
}
|
|
2065
|
-
|
|
2066
|
-
return this.#findNextSiblingUp(anchorNode)
|
|
2067
2543
|
}
|
|
2068
2544
|
|
|
2069
|
-
|
|
2070
|
-
|
|
2071
|
-
|
|
2072
|
-
|
|
2073
|
-
if ($isTextNode(anchorNode)) {
|
|
2074
|
-
return this.#getNextNodeFromTextEnd(anchorNode)
|
|
2075
|
-
}
|
|
2545
|
+
decorate() {
|
|
2546
|
+
return null
|
|
2547
|
+
}
|
|
2548
|
+
}
|
|
2076
2549
|
|
|
2077
|
-
|
|
2078
|
-
|
|
2079
|
-
|
|
2550
|
+
class FormatEscaper {
|
|
2551
|
+
constructor(editorElement) {
|
|
2552
|
+
this.editorElement = editorElement;
|
|
2553
|
+
this.editor = editorElement.editor;
|
|
2554
|
+
}
|
|
2080
2555
|
|
|
2081
|
-
|
|
2556
|
+
monitor() {
|
|
2557
|
+
this.editor.registerCommand(
|
|
2558
|
+
KEY_ENTER_COMMAND,
|
|
2559
|
+
(event) => this.#handleEnterKey(event),
|
|
2560
|
+
COMMAND_PRIORITY_HIGH
|
|
2561
|
+
);
|
|
2082
2562
|
}
|
|
2083
2563
|
|
|
2084
|
-
|
|
2085
|
-
const
|
|
2086
|
-
if (
|
|
2564
|
+
#handleEnterKey(event) {
|
|
2565
|
+
const selection = $getSelection();
|
|
2566
|
+
if (!$isRangeSelection(selection)) return false
|
|
2087
2567
|
|
|
2088
|
-
|
|
2089
|
-
return this.#getNodeBeforeTextNode(anchorNode, offset)
|
|
2090
|
-
}
|
|
2568
|
+
const anchorNode = selection.anchor.getNode();
|
|
2091
2569
|
|
|
2092
|
-
if (
|
|
2093
|
-
return this.#getNodeBeforeElementNode(anchorNode, offset)
|
|
2094
|
-
}
|
|
2570
|
+
if (!this.#isInsideBlockquote(anchorNode)) return false
|
|
2095
2571
|
|
|
2096
|
-
return this.#
|
|
2572
|
+
return this.#handleLists(event, anchorNode)
|
|
2573
|
+
|| this.#handleBlockquotes(event, anchorNode)
|
|
2097
2574
|
}
|
|
2098
2575
|
|
|
2099
|
-
|
|
2100
|
-
|
|
2101
|
-
|
|
2102
|
-
|
|
2103
|
-
|
|
2104
|
-
return this.#getPreviousNodeFromTextStart(anchorNode)
|
|
2576
|
+
#handleLists(event, anchorNode) {
|
|
2577
|
+
if (this.#shouldEscapeFromEmptyListItem(anchorNode) || this.#shouldEscapeFromEmptyParagraphInListItem(anchorNode)) {
|
|
2578
|
+
event.preventDefault();
|
|
2579
|
+
this.#escapeFromList(anchorNode);
|
|
2580
|
+
return true
|
|
2105
2581
|
}
|
|
2106
2582
|
|
|
2107
|
-
|
|
2108
|
-
|
|
2583
|
+
return false
|
|
2584
|
+
}
|
|
2585
|
+
|
|
2586
|
+
#handleBlockquotes(event, anchorNode) {
|
|
2587
|
+
if (this.#shouldEscapeFromEmptyParagraphInBlockquote(anchorNode)) {
|
|
2588
|
+
event.preventDefault();
|
|
2589
|
+
this.#escapeFromBlockquote(anchorNode);
|
|
2590
|
+
return true
|
|
2109
2591
|
}
|
|
2110
2592
|
|
|
2111
|
-
return
|
|
2593
|
+
return false
|
|
2112
2594
|
}
|
|
2113
2595
|
|
|
2114
|
-
|
|
2115
|
-
|
|
2116
|
-
|
|
2117
|
-
this.currentlySelectedKeys = new Set();
|
|
2596
|
+
#isInsideBlockquote(node) {
|
|
2597
|
+
let currentNode = node;
|
|
2118
2598
|
|
|
2119
|
-
|
|
2120
|
-
|
|
2121
|
-
|
|
2122
|
-
this.currentlySelectedKeys.add(node.getKey());
|
|
2599
|
+
while (currentNode) {
|
|
2600
|
+
if ($isQuoteNode(currentNode)) {
|
|
2601
|
+
return true
|
|
2123
2602
|
}
|
|
2603
|
+
currentNode = currentNode.getParent();
|
|
2124
2604
|
}
|
|
2125
2605
|
|
|
2126
|
-
return
|
|
2606
|
+
return false
|
|
2127
2607
|
}
|
|
2128
2608
|
|
|
2129
|
-
#
|
|
2130
|
-
|
|
2131
|
-
|
|
2132
|
-
this.editor.registerCommand(KEY_ARROW_UP_COMMAND, this.#selectPreviousTopLevelNode.bind(this), COMMAND_PRIORITY_LOW);
|
|
2133
|
-
this.editor.registerCommand(KEY_ARROW_DOWN_COMMAND, this.#selectNextTopLevelNode.bind(this), COMMAND_PRIORITY_LOW);
|
|
2134
|
-
|
|
2135
|
-
this.editor.registerCommand(DELETE_CHARACTER_COMMAND, this.#selectDecoratorNodeBeforeDeletion.bind(this), COMMAND_PRIORITY_LOW);
|
|
2609
|
+
#shouldEscapeFromEmptyListItem(node) {
|
|
2610
|
+
const listItem = this.#getListItemNode(node);
|
|
2611
|
+
if (!listItem) return false
|
|
2136
2612
|
|
|
2137
|
-
this
|
|
2138
|
-
this.current = $getSelection();
|
|
2139
|
-
}, COMMAND_PRIORITY_LOW);
|
|
2613
|
+
return this.#isNodeEmpty(listItem)
|
|
2140
2614
|
}
|
|
2141
2615
|
|
|
2142
|
-
#
|
|
2143
|
-
|
|
2144
|
-
|
|
2616
|
+
#shouldEscapeFromEmptyParagraphInListItem(node) {
|
|
2617
|
+
const paragraph = this.#getParagraphNode(node);
|
|
2618
|
+
if (!paragraph) return false
|
|
2145
2619
|
|
|
2146
|
-
|
|
2147
|
-
return $isDecoratorNode(targetNode) && this.#selectInLexical(targetNode)
|
|
2148
|
-
}, COMMAND_PRIORITY_LOW);
|
|
2620
|
+
if (!this.#isNodeEmpty(paragraph)) return false
|
|
2149
2621
|
|
|
2150
|
-
|
|
2151
|
-
|
|
2152
|
-
});
|
|
2622
|
+
const parent = paragraph.getParent();
|
|
2623
|
+
return parent && $isListItemNode(parent)
|
|
2153
2624
|
}
|
|
2154
2625
|
|
|
2155
|
-
#
|
|
2156
|
-
|
|
2157
|
-
// above when navigating UP/DOWN when Lexical shows its fake cursor on custom decorator nodes.
|
|
2158
|
-
this.editorContentElement.addEventListener("keydown", (event) => {
|
|
2159
|
-
if (event.key === "ArrowUp") {
|
|
2160
|
-
const lexicalCursor = this.editor.getRootElement().querySelector("[data-lexical-cursor]");
|
|
2161
|
-
|
|
2162
|
-
if (lexicalCursor) {
|
|
2163
|
-
let currentElement = lexicalCursor.previousElementSibling;
|
|
2164
|
-
while (currentElement && currentElement.hasAttribute("data-lexical-cursor")) {
|
|
2165
|
-
currentElement = currentElement.previousElementSibling;
|
|
2166
|
-
}
|
|
2167
|
-
|
|
2168
|
-
if (!currentElement) {
|
|
2169
|
-
event.preventDefault();
|
|
2170
|
-
}
|
|
2171
|
-
}
|
|
2172
|
-
}
|
|
2173
|
-
|
|
2174
|
-
if (event.key === "ArrowDown") {
|
|
2175
|
-
const lexicalCursor = this.editor.getRootElement().querySelector("[data-lexical-cursor]");
|
|
2176
|
-
|
|
2177
|
-
if (lexicalCursor) {
|
|
2178
|
-
let currentElement = lexicalCursor.nextElementSibling;
|
|
2179
|
-
while (currentElement && currentElement.hasAttribute("data-lexical-cursor")) {
|
|
2180
|
-
currentElement = currentElement.nextElementSibling;
|
|
2181
|
-
}
|
|
2182
|
-
|
|
2183
|
-
if (!currentElement) {
|
|
2184
|
-
event.preventDefault();
|
|
2185
|
-
}
|
|
2186
|
-
}
|
|
2187
|
-
}
|
|
2188
|
-
}, true);
|
|
2189
|
-
}
|
|
2626
|
+
#isNodeEmpty(node) {
|
|
2627
|
+
if (node.getTextContent().trim() !== "") return false
|
|
2190
2628
|
|
|
2191
|
-
|
|
2192
|
-
|
|
2193
|
-
this.#highlightNewItems();
|
|
2629
|
+
const children = node.getChildren();
|
|
2630
|
+
if (children.length === 0) return true
|
|
2194
2631
|
|
|
2195
|
-
|
|
2196
|
-
|
|
2632
|
+
return children.every(child => {
|
|
2633
|
+
if ($isLineBreakNode(child)) return true
|
|
2634
|
+
return this.#isNodeEmpty(child)
|
|
2635
|
+
})
|
|
2197
2636
|
}
|
|
2198
2637
|
|
|
2199
|
-
#
|
|
2200
|
-
|
|
2201
|
-
if (!this.#currentlySelectedKeys.has(key)) {
|
|
2202
|
-
const dom = this.editor.getElementByKey(key);
|
|
2203
|
-
if (dom) dom.classList.remove("node--selected");
|
|
2204
|
-
}
|
|
2205
|
-
}
|
|
2206
|
-
}
|
|
2638
|
+
#getListItemNode(node) {
|
|
2639
|
+
let currentNode = node;
|
|
2207
2640
|
|
|
2208
|
-
|
|
2209
|
-
|
|
2210
|
-
|
|
2211
|
-
const nodeElement = this.editor.getElementByKey(key);
|
|
2212
|
-
if (nodeElement) nodeElement.classList.add("node--selected");
|
|
2641
|
+
while (currentNode) {
|
|
2642
|
+
if ($isListItemNode(currentNode)) {
|
|
2643
|
+
return currentNode
|
|
2213
2644
|
}
|
|
2645
|
+
currentNode = currentNode.getParent();
|
|
2214
2646
|
}
|
|
2215
|
-
}
|
|
2216
2647
|
|
|
2217
|
-
|
|
2218
|
-
if (this.hasNodeSelection) {
|
|
2219
|
-
return await this.#withCurrentNode((currentNode) => currentNode.selectPrevious())
|
|
2220
|
-
} else {
|
|
2221
|
-
return this.#selectInLexical(this.nodeBeforeCursor)
|
|
2222
|
-
}
|
|
2648
|
+
return null
|
|
2223
2649
|
}
|
|
2224
2650
|
|
|
2225
|
-
|
|
2226
|
-
|
|
2227
|
-
|
|
2228
|
-
} else {
|
|
2229
|
-
return this.#selectInLexical(this.nodeAfterCursor)
|
|
2230
|
-
}
|
|
2231
|
-
}
|
|
2651
|
+
#escapeFromList(anchorNode) {
|
|
2652
|
+
const listItem = this.#getListItemNode(anchorNode);
|
|
2653
|
+
if (!listItem) return
|
|
2232
2654
|
|
|
2233
|
-
|
|
2234
|
-
if (
|
|
2235
|
-
return await this.#withCurrentNode((currentNode) => currentNode.getTopLevelElement().selectPrevious())
|
|
2236
|
-
} else {
|
|
2237
|
-
return this.#selectInLexical(this.topLevelNodeBeforeCursor)
|
|
2238
|
-
}
|
|
2239
|
-
}
|
|
2655
|
+
const parentList = listItem.getParent();
|
|
2656
|
+
if (!parentList || !$isListNode(parentList)) return
|
|
2240
2657
|
|
|
2241
|
-
|
|
2242
|
-
|
|
2243
|
-
return await this.#withCurrentNode((currentNode) => currentNode.getTopLevelElement().selectNext(0, 0))
|
|
2244
|
-
} else {
|
|
2245
|
-
return this.#selectInLexical(this.topLevelNodeAfterCursor)
|
|
2246
|
-
}
|
|
2247
|
-
}
|
|
2658
|
+
const blockquote = parentList.getParent();
|
|
2659
|
+
const isInBlockquote = blockquote && $isQuoteNode(blockquote);
|
|
2248
2660
|
|
|
2249
|
-
|
|
2250
|
-
|
|
2251
|
-
|
|
2252
|
-
|
|
2253
|
-
|
|
2254
|
-
this
|
|
2255
|
-
|
|
2661
|
+
if (isInBlockquote) {
|
|
2662
|
+
const listItemsAfter = this.#getListItemSiblingsAfter(listItem);
|
|
2663
|
+
const nonEmptyListItems = listItemsAfter.filter(item => !this.#isNodeEmpty(item));
|
|
2664
|
+
|
|
2665
|
+
if (nonEmptyListItems.length > 0) {
|
|
2666
|
+
this.#splitBlockquoteWithList(blockquote, parentList, listItem, nonEmptyListItems);
|
|
2667
|
+
return
|
|
2668
|
+
}
|
|
2256
2669
|
}
|
|
2257
|
-
}
|
|
2258
2670
|
|
|
2259
|
-
|
|
2260
|
-
|
|
2261
|
-
const topLevelElement = this.#getTopLevelElementFromSelection();
|
|
2262
|
-
if (!topLevelElement) return
|
|
2671
|
+
const paragraph = $createParagraphNode();
|
|
2672
|
+
parentList.insertAfter(paragraph);
|
|
2263
2673
|
|
|
2264
|
-
|
|
2265
|
-
|
|
2674
|
+
listItem.remove();
|
|
2675
|
+
paragraph.selectStart();
|
|
2266
2676
|
}
|
|
2267
2677
|
|
|
2268
|
-
#
|
|
2269
|
-
const
|
|
2270
|
-
if (!
|
|
2678
|
+
#shouldEscapeFromEmptyParagraphInBlockquote(node) {
|
|
2679
|
+
const paragraph = this.#getParagraphNode(node);
|
|
2680
|
+
if (!paragraph) return false
|
|
2271
2681
|
|
|
2272
|
-
if (
|
|
2273
|
-
return this.#getTopLevelFromNodeSelection(selection)
|
|
2274
|
-
}
|
|
2682
|
+
if (!this.#isNodeEmpty(paragraph)) return false
|
|
2275
2683
|
|
|
2276
|
-
|
|
2277
|
-
|
|
2684
|
+
const parent = paragraph.getParent();
|
|
2685
|
+
return parent && $isQuoteNode(parent)
|
|
2686
|
+
}
|
|
2687
|
+
|
|
2688
|
+
#getParagraphNode(node) {
|
|
2689
|
+
let currentNode = node;
|
|
2690
|
+
|
|
2691
|
+
while (currentNode) {
|
|
2692
|
+
if ($isParagraphNode(currentNode)) {
|
|
2693
|
+
return currentNode
|
|
2694
|
+
}
|
|
2695
|
+
currentNode = currentNode.getParent();
|
|
2278
2696
|
}
|
|
2279
2697
|
|
|
2280
2698
|
return null
|
|
2281
2699
|
}
|
|
2282
2700
|
|
|
2283
|
-
#
|
|
2284
|
-
const
|
|
2285
|
-
|
|
2286
|
-
}
|
|
2701
|
+
#escapeFromBlockquote(anchorNode) {
|
|
2702
|
+
const paragraph = this.#getParagraphNode(anchorNode);
|
|
2703
|
+
if (!paragraph) return
|
|
2287
2704
|
|
|
2288
|
-
|
|
2289
|
-
|
|
2290
|
-
return anchorNode.getTopLevelElement()
|
|
2291
|
-
}
|
|
2705
|
+
const blockquote = paragraph.getParent();
|
|
2706
|
+
if (!blockquote || !$isQuoteNode(blockquote)) return
|
|
2292
2707
|
|
|
2293
|
-
|
|
2294
|
-
const
|
|
2708
|
+
const siblingsAfter = this.#getSiblingsAfter(paragraph);
|
|
2709
|
+
const nonEmptySiblings = siblingsAfter.filter(sibling => !this.#isNodeEmpty(sibling));
|
|
2295
2710
|
|
|
2296
|
-
if (
|
|
2297
|
-
|
|
2711
|
+
if (nonEmptySiblings.length > 0) {
|
|
2712
|
+
this.#splitBlockquote(blockquote, paragraph, nonEmptySiblings);
|
|
2298
2713
|
} else {
|
|
2299
|
-
|
|
2714
|
+
const newParagraph = $createParagraphNode();
|
|
2715
|
+
blockquote.insertAfter(newParagraph);
|
|
2716
|
+
paragraph.remove();
|
|
2717
|
+
newParagraph.selectStart();
|
|
2300
2718
|
}
|
|
2301
2719
|
}
|
|
2302
2720
|
|
|
2303
|
-
#
|
|
2304
|
-
const
|
|
2305
|
-
|
|
2306
|
-
root.append(newParagraph);
|
|
2307
|
-
newParagraph.selectStart();
|
|
2308
|
-
}
|
|
2721
|
+
#getSiblingsAfter(node) {
|
|
2722
|
+
const siblings = [];
|
|
2723
|
+
let sibling = node.getNextSibling();
|
|
2309
2724
|
|
|
2310
|
-
|
|
2311
|
-
|
|
2312
|
-
|
|
2313
|
-
$setSelection(selection);
|
|
2314
|
-
return selection
|
|
2315
|
-
} else {
|
|
2316
|
-
return false
|
|
2725
|
+
while (sibling) {
|
|
2726
|
+
siblings.push(sibling);
|
|
2727
|
+
sibling = sibling.getNextSibling();
|
|
2317
2728
|
}
|
|
2729
|
+
|
|
2730
|
+
return siblings
|
|
2318
2731
|
}
|
|
2319
2732
|
|
|
2320
|
-
#
|
|
2321
|
-
const
|
|
2322
|
-
|
|
2323
|
-
this.#selectInLexical(node);
|
|
2733
|
+
#getListItemSiblingsAfter(listItem) {
|
|
2734
|
+
const siblings = [];
|
|
2735
|
+
let sibling = listItem.getNextSibling();
|
|
2324
2736
|
|
|
2325
|
-
|
|
2326
|
-
|
|
2327
|
-
|
|
2737
|
+
while (sibling) {
|
|
2738
|
+
if ($isListItemNode(sibling)) {
|
|
2739
|
+
siblings.push(sibling);
|
|
2740
|
+
}
|
|
2741
|
+
sibling = sibling.getNextSibling();
|
|
2328
2742
|
}
|
|
2743
|
+
|
|
2744
|
+
return siblings
|
|
2329
2745
|
}
|
|
2330
2746
|
|
|
2331
|
-
#
|
|
2332
|
-
const
|
|
2333
|
-
|
|
2747
|
+
#splitBlockquoteWithList(blockquote, parentList, emptyListItem, listItemsAfter) {
|
|
2748
|
+
const blockquoteSiblingsAfterList = this.#getSiblingsAfter(parentList);
|
|
2749
|
+
const nonEmptyBlockquoteSiblings = blockquoteSiblingsAfterList.filter(sibling => !this.#isNodeEmpty(sibling));
|
|
2334
2750
|
|
|
2335
|
-
const
|
|
2336
|
-
|
|
2751
|
+
const middleParagraph = $createParagraphNode();
|
|
2752
|
+
blockquote.insertAfter(middleParagraph);
|
|
2337
2753
|
|
|
2338
|
-
|
|
2339
|
-
}
|
|
2754
|
+
const newList = $createListNode(parentList.getListType());
|
|
2340
2755
|
|
|
2341
|
-
|
|
2342
|
-
|
|
2756
|
+
const newBlockquote = $createQuoteNode();
|
|
2757
|
+
middleParagraph.insertAfter(newBlockquote);
|
|
2758
|
+
newBlockquote.append(newList);
|
|
2343
2759
|
|
|
2344
|
-
|
|
2345
|
-
|
|
2346
|
-
|
|
2347
|
-
this.#restoreSelectionAfterMarker(marker);
|
|
2348
|
-
marker.remove();
|
|
2349
|
-
}
|
|
2760
|
+
listItemsAfter.forEach(item => {
|
|
2761
|
+
newList.append(item);
|
|
2762
|
+
});
|
|
2350
2763
|
|
|
2351
|
-
|
|
2352
|
-
|
|
2764
|
+
nonEmptyBlockquoteSiblings.forEach(sibling => {
|
|
2765
|
+
newBlockquote.append(sibling);
|
|
2766
|
+
});
|
|
2353
2767
|
|
|
2354
|
-
|
|
2355
|
-
return rect.width === 0 && rect.height === 0 || rect.top === 0 && rect.left === 0
|
|
2356
|
-
}
|
|
2768
|
+
emptyListItem.remove();
|
|
2357
2769
|
|
|
2358
|
-
|
|
2359
|
-
|
|
2360
|
-
|
|
2361
|
-
|
|
2770
|
+
this.#removeTrailingEmptyListItems(parentList);
|
|
2771
|
+
this.#removeTrailingEmptyNodes(newBlockquote);
|
|
2772
|
+
|
|
2773
|
+
if (parentList.getChildrenSize() === 0) {
|
|
2774
|
+
parentList.remove();
|
|
2775
|
+
|
|
2776
|
+
if (blockquote.getChildrenSize() === 0) {
|
|
2777
|
+
blockquote.remove();
|
|
2778
|
+
}
|
|
2779
|
+
} else {
|
|
2780
|
+
this.#removeTrailingEmptyNodes(blockquote);
|
|
2781
|
+
}
|
|
2782
|
+
|
|
2783
|
+
middleParagraph.selectStart();
|
|
2362
2784
|
}
|
|
2363
2785
|
|
|
2364
|
-
#
|
|
2365
|
-
const
|
|
2366
|
-
|
|
2367
|
-
|
|
2368
|
-
|
|
2369
|
-
|
|
2370
|
-
|
|
2371
|
-
|
|
2372
|
-
|
|
2786
|
+
#removeTrailingEmptyListItems(list) {
|
|
2787
|
+
const items = list.getChildren();
|
|
2788
|
+
for (let i = items.length - 1; i >= 0; i--) {
|
|
2789
|
+
const item = items[i];
|
|
2790
|
+
if ($isListItemNode(item) && this.#isNodeEmpty(item)) {
|
|
2791
|
+
item.remove();
|
|
2792
|
+
} else {
|
|
2793
|
+
break
|
|
2794
|
+
}
|
|
2795
|
+
}
|
|
2373
2796
|
}
|
|
2374
2797
|
|
|
2375
|
-
#
|
|
2376
|
-
const
|
|
2377
|
-
|
|
2378
|
-
|
|
2379
|
-
|
|
2380
|
-
|
|
2381
|
-
|
|
2798
|
+
#removeTrailingEmptyNodes(blockquote) {
|
|
2799
|
+
const children = blockquote.getChildren();
|
|
2800
|
+
for (let i = children.length - 1; i >= 0; i--) {
|
|
2801
|
+
const child = children[i];
|
|
2802
|
+
if (this.#isNodeEmpty(child)) {
|
|
2803
|
+
child.remove();
|
|
2804
|
+
} else {
|
|
2805
|
+
break
|
|
2806
|
+
}
|
|
2807
|
+
}
|
|
2382
2808
|
}
|
|
2383
2809
|
|
|
2384
|
-
#
|
|
2385
|
-
const
|
|
2386
|
-
|
|
2387
|
-
let y = rect.top - rootRect.top;
|
|
2810
|
+
#splitBlockquote(blockquote, emptyParagraph, siblingsAfter) {
|
|
2811
|
+
const newParagraph = $createParagraphNode();
|
|
2812
|
+
blockquote.insertAfter(newParagraph);
|
|
2388
2813
|
|
|
2389
|
-
const
|
|
2390
|
-
|
|
2391
|
-
y += fontSize;
|
|
2392
|
-
}
|
|
2814
|
+
const newBlockquote = $createQuoteNode();
|
|
2815
|
+
newParagraph.insertAfter(newBlockquote);
|
|
2393
2816
|
|
|
2394
|
-
|
|
2395
|
-
|
|
2817
|
+
siblingsAfter.forEach(sibling => {
|
|
2818
|
+
newBlockquote.append(sibling);
|
|
2819
|
+
});
|
|
2396
2820
|
|
|
2397
|
-
|
|
2398
|
-
const nativeSelection = window.getSelection();
|
|
2399
|
-
const anchorNode = nativeSelection.anchorNode;
|
|
2400
|
-
const parentElement = this.#getElementFromNode(anchorNode);
|
|
2821
|
+
emptyParagraph.remove();
|
|
2401
2822
|
|
|
2402
|
-
|
|
2403
|
-
|
|
2404
|
-
return parseFloat(computed.fontSize)
|
|
2405
|
-
}
|
|
2823
|
+
this.#removeTrailingEmptyNodes(blockquote);
|
|
2824
|
+
this.#removeTrailingEmptyNodes(newBlockquote);
|
|
2406
2825
|
|
|
2407
|
-
|
|
2826
|
+
newParagraph.selectStart();
|
|
2408
2827
|
}
|
|
2828
|
+
}
|
|
2409
2829
|
|
|
2410
|
-
|
|
2411
|
-
|
|
2412
|
-
|
|
2830
|
+
async function loadFileIntoImage(file, image) {
|
|
2831
|
+
return new Promise((resolve) => {
|
|
2832
|
+
const reader = new FileReader();
|
|
2413
2833
|
|
|
2414
|
-
|
|
2415
|
-
|
|
2416
|
-
|
|
2417
|
-
return { anchorNode: null, offset: 0 }
|
|
2418
|
-
}
|
|
2834
|
+
image.addEventListener("load", () => {
|
|
2835
|
+
resolve(image);
|
|
2836
|
+
});
|
|
2419
2837
|
|
|
2420
|
-
|
|
2421
|
-
|
|
2422
|
-
|
|
2838
|
+
reader.onload = (event) => {
|
|
2839
|
+
image.src = event.target.result || null;
|
|
2840
|
+
};
|
|
2423
2841
|
|
|
2424
|
-
|
|
2425
|
-
|
|
2426
|
-
|
|
2427
|
-
|
|
2428
|
-
|
|
2842
|
+
reader.readAsDataURL(file);
|
|
2843
|
+
})
|
|
2844
|
+
}
|
|
2845
|
+
|
|
2846
|
+
class ActionTextAttachmentUploadNode extends ActionTextAttachmentNode {
|
|
2847
|
+
static getType() {
|
|
2848
|
+
return "action_text_attachment_upload"
|
|
2429
2849
|
}
|
|
2430
2850
|
|
|
2431
|
-
|
|
2432
|
-
|
|
2433
|
-
return anchorNode.getNextSibling()
|
|
2434
|
-
}
|
|
2435
|
-
const parent = anchorNode.getParent();
|
|
2436
|
-
return parent ? parent.getNextSibling() : null
|
|
2851
|
+
static clone(node) {
|
|
2852
|
+
return new ActionTextAttachmentUploadNode({ ...node }, node.__key)
|
|
2437
2853
|
}
|
|
2438
2854
|
|
|
2439
|
-
|
|
2440
|
-
|
|
2441
|
-
return anchorNode.getChildAtIndex(offset)
|
|
2442
|
-
}
|
|
2443
|
-
return this.#findNextSiblingUp(anchorNode)
|
|
2855
|
+
static importJSON(serializedNode) {
|
|
2856
|
+
return new ActionTextAttachmentUploadNode({ ...serializedNode })
|
|
2444
2857
|
}
|
|
2445
2858
|
|
|
2446
|
-
|
|
2447
|
-
|
|
2448
|
-
return this.#getPreviousNodeFromTextStart(anchorNode)
|
|
2449
|
-
}
|
|
2859
|
+
// Should never run since this is a transient node. Defined to remove console warning.
|
|
2860
|
+
static importDOM() {
|
|
2450
2861
|
return null
|
|
2451
2862
|
}
|
|
2452
2863
|
|
|
2453
|
-
|
|
2454
|
-
|
|
2455
|
-
|
|
2456
|
-
|
|
2457
|
-
|
|
2458
|
-
|
|
2864
|
+
constructor(node, key) {
|
|
2865
|
+
const { file, uploadUrl, blobUrlTemplate, progress, width, height, uploadError } = node;
|
|
2866
|
+
super({ ...node, contentType: file.type }, key);
|
|
2867
|
+
this.file = file;
|
|
2868
|
+
this.uploadUrl = uploadUrl;
|
|
2869
|
+
this.blobUrlTemplate = blobUrlTemplate;
|
|
2870
|
+
this.progress = progress ?? null;
|
|
2871
|
+
this.width = width;
|
|
2872
|
+
this.height = height;
|
|
2873
|
+
this.uploadError = uploadError;
|
|
2459
2874
|
}
|
|
2460
2875
|
|
|
2461
|
-
|
|
2462
|
-
if (
|
|
2463
|
-
return anchorNode.getChildAtIndex(offset - 1)
|
|
2464
|
-
}
|
|
2465
|
-
return this.#findPreviousSiblingUp(anchorNode)
|
|
2466
|
-
}
|
|
2876
|
+
createDOM() {
|
|
2877
|
+
if (this.uploadError) return this.#createDOMForError()
|
|
2467
2878
|
|
|
2468
|
-
|
|
2469
|
-
|
|
2470
|
-
|
|
2471
|
-
|
|
2472
|
-
}
|
|
2473
|
-
return current ? current.getNextSibling() : null
|
|
2474
|
-
}
|
|
2879
|
+
// This side-effect is trigged on DOM load to fire only once and avoid multiple
|
|
2880
|
+
// uploads through cloning. The upload is guarded from restarting in case the
|
|
2881
|
+
// node is reloaded from saved state such as from history.
|
|
2882
|
+
this.#startUploadIfNeeded();
|
|
2475
2883
|
|
|
2476
|
-
|
|
2477
|
-
|
|
2478
|
-
|
|
2479
|
-
|
|
2884
|
+
const figure = this.createAttachmentFigure();
|
|
2885
|
+
|
|
2886
|
+
if (this.isPreviewableAttachment) {
|
|
2887
|
+
const img = figure.appendChild(this.#createDOMForImage());
|
|
2888
|
+
|
|
2889
|
+
// load file locally to set dimensions and prevent vertical shifting
|
|
2890
|
+
loadFileIntoImage(this.file, img).then(img => this.#setDimensionsFromImage(img));
|
|
2891
|
+
} else {
|
|
2892
|
+
figure.appendChild(this.#createDOMForFile());
|
|
2480
2893
|
}
|
|
2481
|
-
|
|
2894
|
+
|
|
2895
|
+
figure.appendChild(this.#createCaption());
|
|
2896
|
+
figure.appendChild(this.#createProgressBar());
|
|
2897
|
+
|
|
2898
|
+
return figure
|
|
2482
2899
|
}
|
|
2483
|
-
}
|
|
2484
2900
|
|
|
2485
|
-
|
|
2486
|
-
|
|
2487
|
-
}
|
|
2901
|
+
updateDOM(prevNode, dom) {
|
|
2902
|
+
if (this.uploadError !== prevNode.uploadError) return true
|
|
2488
2903
|
|
|
2489
|
-
|
|
2490
|
-
|
|
2491
|
-
|
|
2904
|
+
if (prevNode.progress !== this.progress) {
|
|
2905
|
+
const progress = dom.querySelector("progress");
|
|
2906
|
+
progress.value = this.progress ?? 0;
|
|
2907
|
+
}
|
|
2492
2908
|
|
|
2493
|
-
function isUrl(string) {
|
|
2494
|
-
try {
|
|
2495
|
-
new URL(string);
|
|
2496
|
-
return true
|
|
2497
|
-
} catch {
|
|
2498
2909
|
return false
|
|
2499
2910
|
}
|
|
2500
|
-
}
|
|
2501
|
-
|
|
2502
|
-
function normalizeFilteredText(string) {
|
|
2503
|
-
return string
|
|
2504
|
-
.toLowerCase()
|
|
2505
|
-
.normalize("NFD").replace(/[\u0300-\u036f]/g, "") // Remove diacritics
|
|
2506
|
-
}
|
|
2507
2911
|
|
|
2508
|
-
|
|
2509
|
-
|
|
2510
|
-
}
|
|
2912
|
+
exportDOM() {
|
|
2913
|
+
return { element: null }
|
|
2914
|
+
}
|
|
2511
2915
|
|
|
2512
|
-
|
|
2513
|
-
|
|
2514
|
-
|
|
2916
|
+
exportJSON() {
|
|
2917
|
+
return {
|
|
2918
|
+
...super.exportJSON(),
|
|
2919
|
+
type: "action_text_attachment_upload",
|
|
2920
|
+
version: 1,
|
|
2921
|
+
uploadUrl: this.uploadUrl,
|
|
2922
|
+
blobUrlTemplate: this.blobUrlTemplate,
|
|
2923
|
+
progress: this.progress,
|
|
2924
|
+
width: this.width,
|
|
2925
|
+
height: this.height,
|
|
2926
|
+
uploadError: this.uploadError
|
|
2927
|
+
}
|
|
2928
|
+
}
|
|
2515
2929
|
|
|
2516
|
-
|
|
2517
|
-
|
|
2518
|
-
|
|
2930
|
+
get #uploadStarted() {
|
|
2931
|
+
return this.progress !== null
|
|
2932
|
+
}
|
|
2519
2933
|
|
|
2520
|
-
|
|
2521
|
-
|
|
2522
|
-
|
|
2523
|
-
|
|
2524
|
-
|
|
2525
|
-
this.#overrides
|
|
2526
|
-
);
|
|
2934
|
+
#createDOMForError() {
|
|
2935
|
+
const figure = this.createAttachmentFigure();
|
|
2936
|
+
figure.classList.add("attachment--error");
|
|
2937
|
+
figure.appendChild(createElement("div", { innerText: `Error uploading ${this.file?.name ?? "file"}` }));
|
|
2938
|
+
return figure
|
|
2527
2939
|
}
|
|
2528
2940
|
|
|
2529
|
-
|
|
2530
|
-
return
|
|
2941
|
+
#createDOMForImage() {
|
|
2942
|
+
return createElement("img")
|
|
2531
2943
|
}
|
|
2532
2944
|
|
|
2533
|
-
|
|
2534
|
-
const
|
|
2535
|
-
|
|
2536
|
-
|
|
2537
|
-
if (this.#editorElement.hasAttribute(attribute)) {
|
|
2538
|
-
overrides[option] = this.#parseAttribute(attribute);
|
|
2539
|
-
}
|
|
2540
|
-
}
|
|
2541
|
-
return overrides
|
|
2945
|
+
#createDOMForFile() {
|
|
2946
|
+
const extension = this.#getFileExtension();
|
|
2947
|
+
const span = createElement("span", { className: "attachment__icon", textContent: extension });
|
|
2948
|
+
return span
|
|
2542
2949
|
}
|
|
2543
2950
|
|
|
2544
|
-
|
|
2545
|
-
return
|
|
2951
|
+
#getFileExtension() {
|
|
2952
|
+
return this.file.name.split(".").pop().toLowerCase()
|
|
2546
2953
|
}
|
|
2547
2954
|
|
|
2548
|
-
#
|
|
2549
|
-
const
|
|
2550
|
-
|
|
2551
|
-
|
|
2552
|
-
|
|
2553
|
-
|
|
2554
|
-
|
|
2955
|
+
#createCaption() {
|
|
2956
|
+
const figcaption = createElement("figcaption", { className: "attachment__caption" });
|
|
2957
|
+
|
|
2958
|
+
const nameSpan = createElement("span", { className: "attachment__name", textContent: this.file.name || "" });
|
|
2959
|
+
const sizeSpan = createElement("span", { className: "attachment__size", textContent: bytesToHumanSize(this.file.size) });
|
|
2960
|
+
figcaption.appendChild(nameSpan);
|
|
2961
|
+
figcaption.appendChild(sizeSpan);
|
|
2962
|
+
|
|
2963
|
+
return figcaption
|
|
2555
2964
|
}
|
|
2556
|
-
}
|
|
2557
2965
|
|
|
2558
|
-
|
|
2559
|
-
|
|
2560
|
-
return "custom_action_text_attachment"
|
|
2966
|
+
#createProgressBar() {
|
|
2967
|
+
return createElement("progress", { value: this.progress ?? 0, max: 100 })
|
|
2561
2968
|
}
|
|
2562
2969
|
|
|
2563
|
-
|
|
2564
|
-
|
|
2970
|
+
#setDimensionsFromImage({ width, height }) {
|
|
2971
|
+
if (this.#hasDimensions) return
|
|
2972
|
+
|
|
2973
|
+
this.editor.update(() => {
|
|
2974
|
+
const writable = this.getWritable();
|
|
2975
|
+
writable.width = width;
|
|
2976
|
+
writable.height = height;
|
|
2977
|
+
}, { tag: SILENT_UPDATE_TAGS });
|
|
2565
2978
|
}
|
|
2566
2979
|
|
|
2567
|
-
|
|
2568
|
-
return
|
|
2980
|
+
get #hasDimensions() {
|
|
2981
|
+
return Boolean(this.width && this.height)
|
|
2569
2982
|
}
|
|
2570
2983
|
|
|
2571
|
-
|
|
2984
|
+
async #startUploadIfNeeded() {
|
|
2985
|
+
if (this.#uploadStarted) return
|
|
2572
2986
|
|
|
2573
|
-
|
|
2574
|
-
[this.TAG_NAME]: (element) => {
|
|
2575
|
-
if (!element.getAttribute("content")) {
|
|
2576
|
-
return null
|
|
2577
|
-
}
|
|
2987
|
+
this.#setUploadStarted();
|
|
2578
2988
|
|
|
2579
|
-
|
|
2580
|
-
conversion: (attachment) => {
|
|
2581
|
-
// Preserve initial space if present since Lexical removes it
|
|
2582
|
-
const nodes = [];
|
|
2583
|
-
const previousSibling = attachment.previousSibling;
|
|
2584
|
-
if (previousSibling && previousSibling.nodeType === Node.TEXT_NODE && /\s$/.test(previousSibling.textContent)) {
|
|
2585
|
-
nodes.push($createTextNode(" "));
|
|
2586
|
-
}
|
|
2989
|
+
const { DirectUpload } = await import('@rails/activestorage');
|
|
2587
2990
|
|
|
2588
|
-
|
|
2589
|
-
|
|
2590
|
-
innerHtml: JSON.parse(attachment.getAttribute("content")),
|
|
2591
|
-
contentType: attachment.getAttribute("content-type")
|
|
2592
|
-
}));
|
|
2991
|
+
const upload = new DirectUpload(this.file, this.uploadUrl, this);
|
|
2992
|
+
upload.delegate = this.#createUploadDelegate();
|
|
2593
2993
|
|
|
2594
|
-
|
|
2994
|
+
this.#dispatchEvent("lexxy:upload-start", { file: this.file });
|
|
2595
2995
|
|
|
2596
|
-
|
|
2597
|
-
|
|
2598
|
-
|
|
2599
|
-
|
|
2996
|
+
upload.create((error, blob) => {
|
|
2997
|
+
if (error) {
|
|
2998
|
+
this.#dispatchEvent("lexxy:upload-end", { file: this.file, error });
|
|
2999
|
+
this.#handleUploadError(error);
|
|
3000
|
+
} else {
|
|
3001
|
+
this.#dispatchEvent("lexxy:upload-end", { file: this.file, error: null });
|
|
3002
|
+
this.#showUploadedAttachment(blob);
|
|
2600
3003
|
}
|
|
2601
|
-
}
|
|
2602
|
-
}
|
|
2603
|
-
|
|
2604
|
-
static get TAG_NAME() {
|
|
2605
|
-
return Lexxy.global.get("attachmentTagName")
|
|
3004
|
+
});
|
|
2606
3005
|
}
|
|
2607
3006
|
|
|
2608
|
-
|
|
2609
|
-
|
|
3007
|
+
#createUploadDelegate() {
|
|
3008
|
+
const shouldAuthenticateUploads = Lexxy.global.get("authenticatedUploads");
|
|
2610
3009
|
|
|
2611
|
-
|
|
3010
|
+
return {
|
|
3011
|
+
directUploadWillCreateBlobWithXHR: (request) => {
|
|
3012
|
+
if (shouldAuthenticateUploads) request.withCredentials = true;
|
|
3013
|
+
},
|
|
3014
|
+
directUploadWillStoreFileWithXHR: (request) => {
|
|
3015
|
+
if (shouldAuthenticateUploads) request.withCredentials = true;
|
|
2612
3016
|
|
|
2613
|
-
|
|
2614
|
-
|
|
2615
|
-
|
|
2616
|
-
|
|
3017
|
+
const uploadProgressHandler = (event) => this.#handleUploadProgress(event);
|
|
3018
|
+
request.upload.addEventListener("progress", uploadProgressHandler);
|
|
3019
|
+
}
|
|
3020
|
+
}
|
|
2617
3021
|
}
|
|
2618
3022
|
|
|
2619
|
-
|
|
2620
|
-
|
|
2621
|
-
|
|
2622
|
-
figure.insertAdjacentHTML("beforeend", this.innerHtml);
|
|
2623
|
-
|
|
2624
|
-
return figure
|
|
3023
|
+
#setUploadStarted() {
|
|
3024
|
+
this.#setProgress(1);
|
|
2625
3025
|
}
|
|
2626
3026
|
|
|
2627
|
-
|
|
2628
|
-
|
|
3027
|
+
#handleUploadProgress(event) {
|
|
3028
|
+
const progress = Math.round(event.loaded / event.total * 100);
|
|
3029
|
+
this.#setProgress(progress);
|
|
3030
|
+
this.#dispatchEvent("lexxy:upload-progress", { file: this.file, progress });
|
|
2629
3031
|
}
|
|
2630
3032
|
|
|
2631
|
-
|
|
2632
|
-
|
|
3033
|
+
#setProgress(progress) {
|
|
3034
|
+
this.editor.update(() => {
|
|
3035
|
+
this.getWritable().progress = progress;
|
|
3036
|
+
}, { tag: SILENT_UPDATE_TAGS });
|
|
2633
3037
|
}
|
|
2634
3038
|
|
|
2635
|
-
|
|
2636
|
-
|
|
3039
|
+
#handleUploadError(error) {
|
|
3040
|
+
console.warn(`Upload error for ${this.file?.name ?? "file"}: ${error}`);
|
|
3041
|
+
this.editor.update(() => {
|
|
3042
|
+
this.getWritable().uploadError = true;
|
|
3043
|
+
}, { tag: SILENT_UPDATE_TAGS });
|
|
2637
3044
|
}
|
|
2638
3045
|
|
|
2639
|
-
|
|
2640
|
-
|
|
2641
|
-
|
|
2642
|
-
|
|
2643
|
-
"content-type": this.contentType
|
|
2644
|
-
});
|
|
2645
|
-
|
|
2646
|
-
return { element: attachment }
|
|
3046
|
+
#showUploadedAttachment(blob) {
|
|
3047
|
+
this.editor.update(() => {
|
|
3048
|
+
this.replace(this.#toActionTextAttachmentNodeWith(blob));
|
|
3049
|
+
}, { tag: SILENT_UPDATE_TAGS });
|
|
2647
3050
|
}
|
|
2648
3051
|
|
|
2649
|
-
|
|
2650
|
-
|
|
2651
|
-
|
|
2652
|
-
version: 1,
|
|
2653
|
-
tagName: this.tagName,
|
|
2654
|
-
sgid: this.sgid,
|
|
2655
|
-
contentType: this.contentType,
|
|
2656
|
-
innerHtml: this.innerHtml
|
|
2657
|
-
}
|
|
3052
|
+
#toActionTextAttachmentNodeWith(blob) {
|
|
3053
|
+
const conversion = new AttachmentNodeConversion(this, blob);
|
|
3054
|
+
return conversion.toAttachmentNode()
|
|
2658
3055
|
}
|
|
2659
3056
|
|
|
2660
|
-
|
|
2661
|
-
|
|
3057
|
+
#dispatchEvent(name, detail) {
|
|
3058
|
+
const figure = this.editor.getElementByKey(this.getKey());
|
|
3059
|
+
if (figure) dispatch(figure, name, detail);
|
|
2662
3060
|
}
|
|
2663
3061
|
}
|
|
2664
3062
|
|
|
2665
|
-
class
|
|
2666
|
-
constructor(
|
|
2667
|
-
this.
|
|
2668
|
-
this.
|
|
3063
|
+
class AttachmentNodeConversion {
|
|
3064
|
+
constructor(uploadNode, blob) {
|
|
3065
|
+
this.uploadNode = uploadNode;
|
|
3066
|
+
this.blob = blob;
|
|
2669
3067
|
}
|
|
2670
3068
|
|
|
2671
|
-
|
|
2672
|
-
|
|
2673
|
-
|
|
2674
|
-
|
|
2675
|
-
|
|
2676
|
-
)
|
|
3069
|
+
toAttachmentNode() {
|
|
3070
|
+
return new ActionTextAttachmentNode({
|
|
3071
|
+
...this.uploadNode,
|
|
3072
|
+
...this.#propertiesFromBlob,
|
|
3073
|
+
src: this.#src
|
|
3074
|
+
})
|
|
2677
3075
|
}
|
|
2678
3076
|
|
|
2679
|
-
#
|
|
2680
|
-
const
|
|
2681
|
-
|
|
2682
|
-
|
|
2683
|
-
|
|
3077
|
+
get #propertiesFromBlob() {
|
|
3078
|
+
const { blob } = this;
|
|
3079
|
+
return {
|
|
3080
|
+
sgid: blob.attachable_sgid,
|
|
3081
|
+
altText: blob.filename,
|
|
3082
|
+
contentType: blob.content_type,
|
|
3083
|
+
fileName: blob.filename,
|
|
3084
|
+
fileSize: blob.byte_size,
|
|
3085
|
+
previewable: blob.previewable,
|
|
3086
|
+
}
|
|
3087
|
+
}
|
|
2684
3088
|
|
|
2685
|
-
|
|
3089
|
+
get #src() {
|
|
3090
|
+
return this.blob.previewable ? this.blob.url : this.#blobSrc
|
|
3091
|
+
}
|
|
2686
3092
|
|
|
2687
|
-
|
|
2688
|
-
|
|
3093
|
+
get #blobSrc() {
|
|
3094
|
+
return this.uploadNode.blobUrlTemplate
|
|
3095
|
+
.replace(":signed_id", this.blob.signed_id)
|
|
3096
|
+
.replace(":filename", encodeURIComponent(this.blob.filename))
|
|
2689
3097
|
}
|
|
3098
|
+
}
|
|
2690
3099
|
|
|
2691
|
-
|
|
2692
|
-
|
|
2693
|
-
|
|
2694
|
-
this.#escapeFromList(anchorNode);
|
|
2695
|
-
return true
|
|
2696
|
-
}
|
|
3100
|
+
function $createActionTextAttachmentUploadNode(...args) {
|
|
3101
|
+
return new ActionTextAttachmentUploadNode(...args)
|
|
3102
|
+
}
|
|
2697
3103
|
|
|
2698
|
-
|
|
3104
|
+
class ImageGalleryNode extends ElementNode {
|
|
3105
|
+
$config() {
|
|
3106
|
+
return this.config("image_gallery", {
|
|
3107
|
+
extends: ElementNode,
|
|
3108
|
+
})
|
|
2699
3109
|
}
|
|
2700
3110
|
|
|
2701
|
-
|
|
2702
|
-
|
|
2703
|
-
|
|
2704
|
-
|
|
2705
|
-
|
|
3111
|
+
static transform() {
|
|
3112
|
+
return (gallery) => {
|
|
3113
|
+
gallery.unwrapEmptyNode()
|
|
3114
|
+
|| gallery.replaceWithSingularChild()
|
|
3115
|
+
|| gallery.splitAroundInvalidChild();
|
|
2706
3116
|
}
|
|
2707
|
-
|
|
2708
|
-
return false
|
|
2709
3117
|
}
|
|
2710
3118
|
|
|
2711
|
-
|
|
2712
|
-
|
|
3119
|
+
static importDOM() {
|
|
3120
|
+
return {
|
|
3121
|
+
div: (element) => {
|
|
3122
|
+
const containsAttachment = element.querySelector(`:scope > :is(${this.#attachmentTags.join()})`);
|
|
3123
|
+
if (!containsAttachment) return null
|
|
2713
3124
|
|
|
2714
|
-
|
|
2715
|
-
|
|
2716
|
-
|
|
3125
|
+
return {
|
|
3126
|
+
conversion: () => {
|
|
3127
|
+
return {
|
|
3128
|
+
node: $createImageGalleryNode(),
|
|
3129
|
+
after: children => $descendantsMatching(children, this.isValidChild)
|
|
3130
|
+
}
|
|
3131
|
+
},
|
|
3132
|
+
priority: 2
|
|
3133
|
+
}
|
|
2717
3134
|
}
|
|
2718
|
-
currentNode = currentNode.getParent();
|
|
2719
3135
|
}
|
|
2720
|
-
|
|
2721
|
-
return false
|
|
2722
3136
|
}
|
|
2723
3137
|
|
|
2724
|
-
|
|
2725
|
-
|
|
2726
|
-
if (!listItem) return false
|
|
2727
|
-
|
|
2728
|
-
return this.#isNodeEmpty(listItem)
|
|
3138
|
+
static canCollapseWith(node) {
|
|
3139
|
+
return $isImageGalleryNode(node) || this.isValidChild(node)
|
|
2729
3140
|
}
|
|
2730
3141
|
|
|
2731
|
-
|
|
2732
|
-
|
|
2733
|
-
|
|
3142
|
+
static isValidChild(node) {
|
|
3143
|
+
return $isActionTextAttachmentNode(node) && node.isPreviewableImage
|
|
3144
|
+
}
|
|
2734
3145
|
|
|
2735
|
-
|
|
3146
|
+
static get #attachmentTags() {
|
|
3147
|
+
return Object.keys(ActionTextAttachmentNode.importDOM())
|
|
3148
|
+
}
|
|
2736
3149
|
|
|
2737
|
-
|
|
2738
|
-
|
|
3150
|
+
createDOM() {
|
|
3151
|
+
const div = document.createElement("div");
|
|
3152
|
+
div.className = this.#galleryClassNames;
|
|
3153
|
+
return div
|
|
2739
3154
|
}
|
|
2740
3155
|
|
|
2741
|
-
|
|
2742
|
-
|
|
3156
|
+
updateDOM(_prevNode, dom) {
|
|
3157
|
+
dom.className = this.#galleryClassNames;
|
|
3158
|
+
return false
|
|
3159
|
+
}
|
|
2743
3160
|
|
|
2744
|
-
|
|
2745
|
-
|
|
3161
|
+
canBeEmpty() {
|
|
3162
|
+
// Return `true` to conform to `$isBlock(node)`
|
|
3163
|
+
// We clean-up empty galleries with a transform
|
|
3164
|
+
return true
|
|
3165
|
+
}
|
|
2746
3166
|
|
|
2747
|
-
|
|
2748
|
-
|
|
2749
|
-
return this.#isNodeEmpty(child)
|
|
2750
|
-
})
|
|
3167
|
+
collapseAtStart(_selection) {
|
|
3168
|
+
return true
|
|
2751
3169
|
}
|
|
2752
3170
|
|
|
2753
|
-
|
|
2754
|
-
|
|
3171
|
+
insertNewAfter(selection, restoreSelection) {
|
|
3172
|
+
const selectionBeforeLastChild = selection.anchor.getNode().is(this) && selection.anchor.offset == this.getChildrenSize() - 1;
|
|
3173
|
+
if (selectionBeforeLastChild) {
|
|
3174
|
+
const paragraph = $createParagraphNode();
|
|
3175
|
+
this.insertAfter(paragraph, false);
|
|
3176
|
+
paragraph.insertAfter(this.getLastChild(), false);
|
|
3177
|
+
paragraph.selectEnd();
|
|
2755
3178
|
|
|
2756
|
-
|
|
2757
|
-
|
|
2758
|
-
return currentNode
|
|
2759
|
-
}
|
|
2760
|
-
currentNode = currentNode.getParent();
|
|
3179
|
+
// return null as selection has been managed
|
|
3180
|
+
return null
|
|
2761
3181
|
}
|
|
2762
3182
|
|
|
2763
|
-
|
|
3183
|
+
const newNode = $createImageGalleryNode();
|
|
3184
|
+
this.insertAfter(newNode, restoreSelection);
|
|
3185
|
+
return newNode
|
|
2764
3186
|
}
|
|
2765
3187
|
|
|
2766
|
-
|
|
2767
|
-
const
|
|
2768
|
-
|
|
2769
|
-
|
|
2770
|
-
const parentList = listItem.getParent();
|
|
2771
|
-
if (!parentList || !$isListNode(parentList)) return
|
|
2772
|
-
|
|
2773
|
-
const blockquote = parentList.getParent();
|
|
2774
|
-
const isInBlockquote = blockquote && $isQuoteNode(blockquote);
|
|
2775
|
-
|
|
2776
|
-
if (isInBlockquote) {
|
|
2777
|
-
const listItemsAfter = this.#getListItemSiblingsAfter(listItem);
|
|
2778
|
-
const nonEmptyListItems = listItemsAfter.filter(item => !this.#isNodeEmpty(item));
|
|
2779
|
-
|
|
2780
|
-
if (nonEmptyListItems.length > 0) {
|
|
2781
|
-
this.#splitBlockquoteWithList(blockquote, parentList, listItem, nonEmptyListItems);
|
|
2782
|
-
return
|
|
2783
|
-
}
|
|
2784
|
-
}
|
|
2785
|
-
|
|
2786
|
-
const paragraph = $createParagraphNode();
|
|
2787
|
-
parentList.insertAfter(paragraph);
|
|
3188
|
+
getImageAttachments() {
|
|
3189
|
+
const children = this.getChildren();
|
|
3190
|
+
return children.filter($isActionTextAttachmentNode)
|
|
3191
|
+
}
|
|
2788
3192
|
|
|
2789
|
-
|
|
2790
|
-
|
|
3193
|
+
exportDOM() {
|
|
3194
|
+
const div = document.createElement("div");
|
|
3195
|
+
div.className = this.#galleryClassNames;
|
|
3196
|
+
return { element: div }
|
|
2791
3197
|
}
|
|
2792
3198
|
|
|
2793
|
-
|
|
2794
|
-
|
|
2795
|
-
if (!paragraph) return false
|
|
3199
|
+
collapseWith(node, backwards) {
|
|
3200
|
+
if (!ImageGalleryNode.canCollapseWith(node)) return false
|
|
2796
3201
|
|
|
2797
|
-
if (
|
|
3202
|
+
if (backwards) {
|
|
3203
|
+
$insertFirst(this, node);
|
|
3204
|
+
} else {
|
|
3205
|
+
this.append(node);
|
|
3206
|
+
}
|
|
2798
3207
|
|
|
2799
|
-
|
|
2800
|
-
return parent && $isQuoteNode(parent)
|
|
2801
|
-
}
|
|
3208
|
+
$unwrapAndFilterDescendants(this, ImageGalleryNode.isValidChild);
|
|
2802
3209
|
|
|
2803
|
-
|
|
2804
|
-
|
|
3210
|
+
return true
|
|
3211
|
+
}
|
|
2805
3212
|
|
|
2806
|
-
|
|
2807
|
-
|
|
2808
|
-
|
|
2809
|
-
|
|
2810
|
-
currentNode = currentNode.getParent();
|
|
3213
|
+
unwrapEmptyNode() {
|
|
3214
|
+
if (this.isEmpty()) {
|
|
3215
|
+
const paragraph = $createParagraphNode();
|
|
3216
|
+
return this.replace(paragraph)
|
|
2811
3217
|
}
|
|
3218
|
+
}
|
|
2812
3219
|
|
|
2813
|
-
|
|
3220
|
+
replaceWithSingularChild() {
|
|
3221
|
+
if (this.#hasSingularChild) {
|
|
3222
|
+
const child = this.getFirstChild();
|
|
3223
|
+
return this.replace(child)
|
|
3224
|
+
}
|
|
2814
3225
|
}
|
|
2815
3226
|
|
|
2816
|
-
|
|
2817
|
-
const
|
|
2818
|
-
|
|
3227
|
+
splitAroundInvalidChild() {
|
|
3228
|
+
for (const child of $firstToLastIterator(this)) {
|
|
3229
|
+
if (ImageGalleryNode.isValidChild(child)) continue
|
|
2819
3230
|
|
|
2820
|
-
|
|
2821
|
-
|
|
3231
|
+
const poppedNode = $makeSafeForRoot(child);
|
|
3232
|
+
const [ topGallery, secondGallery ] = this.splitAtIndex(poppedNode.getIndexWithinParent());
|
|
3233
|
+
topGallery.insertAfter(poppedNode);
|
|
3234
|
+
poppedNode.selectEnd();
|
|
2822
3235
|
|
|
2823
|
-
|
|
2824
|
-
|
|
3236
|
+
// remove an empty gallery rather than let it unwrap to a paragraph
|
|
3237
|
+
if (secondGallery.isEmpty()) secondGallery.remove();
|
|
2825
3238
|
|
|
2826
|
-
|
|
2827
|
-
this.#splitBlockquote(blockquote, paragraph, nonEmptySiblings);
|
|
2828
|
-
} else {
|
|
2829
|
-
const newParagraph = $createParagraphNode();
|
|
2830
|
-
blockquote.insertAfter(newParagraph);
|
|
2831
|
-
paragraph.remove();
|
|
2832
|
-
newParagraph.selectStart();
|
|
3239
|
+
break
|
|
2833
3240
|
}
|
|
2834
3241
|
}
|
|
2835
3242
|
|
|
2836
|
-
|
|
2837
|
-
|
|
2838
|
-
|
|
3243
|
+
splitAtIndex(index) {
|
|
3244
|
+
return $splitNode(this, index)
|
|
3245
|
+
}
|
|
2839
3246
|
|
|
2840
|
-
|
|
2841
|
-
|
|
2842
|
-
|
|
2843
|
-
}
|
|
3247
|
+
get #hasSingularChild() {
|
|
3248
|
+
return this.getChildrenSize() === 1
|
|
3249
|
+
}
|
|
2844
3250
|
|
|
2845
|
-
|
|
3251
|
+
get #galleryClassNames() {
|
|
3252
|
+
return `attachment-gallery attachment-gallery--${this.getChildrenSize()}`
|
|
2846
3253
|
}
|
|
3254
|
+
}
|
|
2847
3255
|
|
|
2848
|
-
|
|
2849
|
-
|
|
2850
|
-
|
|
3256
|
+
function $createImageGalleryNode() {
|
|
3257
|
+
return new ImageGalleryNode()
|
|
3258
|
+
}
|
|
2851
3259
|
|
|
2852
|
-
|
|
2853
|
-
|
|
2854
|
-
|
|
2855
|
-
}
|
|
2856
|
-
sibling = sibling.getNextSibling();
|
|
2857
|
-
}
|
|
3260
|
+
function $isImageGalleryNode(node) {
|
|
3261
|
+
return node instanceof ImageGalleryNode
|
|
3262
|
+
}
|
|
2858
3263
|
|
|
2859
|
-
|
|
2860
|
-
|
|
3264
|
+
function $findOrCreateGalleryForImage(node) {
|
|
3265
|
+
if (!ImageGalleryNode.canCollapseWith(node)) return null
|
|
2861
3266
|
|
|
2862
|
-
|
|
2863
|
-
|
|
2864
|
-
|
|
3267
|
+
const existingGallery = $getNearestNodeOfType(node, ImageGalleryNode);
|
|
3268
|
+
return existingGallery ?? $wrapNodeInElement(node, $createImageGalleryNode)
|
|
3269
|
+
}
|
|
2865
3270
|
|
|
2866
|
-
|
|
2867
|
-
|
|
3271
|
+
class Uploader {
|
|
3272
|
+
#files
|
|
2868
3273
|
|
|
2869
|
-
|
|
3274
|
+
static for(editorElement, files) {
|
|
3275
|
+
const UploaderKlass = GalleryUploader.handle(editorElement, files) ? GalleryUploader : Uploader;
|
|
3276
|
+
return new UploaderKlass(editorElement, files)
|
|
3277
|
+
}
|
|
2870
3278
|
|
|
2871
|
-
|
|
2872
|
-
|
|
2873
|
-
newBlockquote.append(newList);
|
|
3279
|
+
constructor(editorElement, files) {
|
|
3280
|
+
this.#files = files;
|
|
2874
3281
|
|
|
2875
|
-
|
|
2876
|
-
|
|
2877
|
-
|
|
3282
|
+
this.editorElement = editorElement;
|
|
3283
|
+
this.contents = editorElement.contents;
|
|
3284
|
+
this.selection = editorElement.selection;
|
|
3285
|
+
}
|
|
2878
3286
|
|
|
2879
|
-
|
|
2880
|
-
|
|
2881
|
-
|
|
3287
|
+
get files() {
|
|
3288
|
+
return Array.from(this.#files)
|
|
3289
|
+
}
|
|
2882
3290
|
|
|
2883
|
-
|
|
3291
|
+
$uploadFiles() {
|
|
3292
|
+
this.$createUploadNodes();
|
|
3293
|
+
this.$insertUploadNodes();
|
|
3294
|
+
}
|
|
2884
3295
|
|
|
2885
|
-
|
|
2886
|
-
this
|
|
3296
|
+
$createUploadNodes() {
|
|
3297
|
+
this.nodes = this.files.map(file =>
|
|
3298
|
+
$createActionTextAttachmentUploadNode({
|
|
3299
|
+
...this.#nodeUrlProperties,
|
|
3300
|
+
file: file,
|
|
3301
|
+
contentType: file.type
|
|
3302
|
+
})
|
|
3303
|
+
);
|
|
3304
|
+
}
|
|
2887
3305
|
|
|
2888
|
-
|
|
2889
|
-
|
|
3306
|
+
$insertUploadNodes() {
|
|
3307
|
+
this.nodes.forEach(this.contents.insertAtCursor);
|
|
3308
|
+
}
|
|
2890
3309
|
|
|
2891
|
-
|
|
2892
|
-
|
|
2893
|
-
|
|
2894
|
-
|
|
2895
|
-
this.#removeTrailingEmptyNodes(blockquote);
|
|
3310
|
+
get #nodeUrlProperties() {
|
|
3311
|
+
return {
|
|
3312
|
+
uploadUrl: this.editorElement.directUploadUrl,
|
|
3313
|
+
blobUrlTemplate: this.editorElement.blobUrlTemplate
|
|
2896
3314
|
}
|
|
3315
|
+
}
|
|
3316
|
+
}
|
|
2897
3317
|
|
|
2898
|
-
|
|
3318
|
+
class GalleryUploader extends Uploader {
|
|
3319
|
+
#gallery
|
|
3320
|
+
|
|
3321
|
+
static handle(editorElement, files) {
|
|
3322
|
+
return this.#isMultipleImageUpload(files) || this.#gallerySelection(editorElement.selection)
|
|
2899
3323
|
}
|
|
2900
3324
|
|
|
2901
|
-
#
|
|
2902
|
-
|
|
2903
|
-
for (
|
|
2904
|
-
|
|
2905
|
-
if (
|
|
2906
|
-
item.remove();
|
|
2907
|
-
} else {
|
|
2908
|
-
break
|
|
2909
|
-
}
|
|
3325
|
+
static #isMultipleImageUpload(files) {
|
|
3326
|
+
let imageFileCount = 0;
|
|
3327
|
+
for (const file of files) {
|
|
3328
|
+
if (isPreviewableImage(file.type)) imageFileCount++;
|
|
3329
|
+
if (imageFileCount > 1) return true
|
|
2910
3330
|
}
|
|
3331
|
+
return false
|
|
2911
3332
|
}
|
|
2912
3333
|
|
|
2913
|
-
#
|
|
2914
|
-
|
|
2915
|
-
|
|
2916
|
-
|
|
2917
|
-
|
|
2918
|
-
|
|
2919
|
-
|
|
2920
|
-
|
|
2921
|
-
|
|
3334
|
+
static #gallerySelection(selection) {
|
|
3335
|
+
if (selection.isOnPreviewableImage) return true
|
|
3336
|
+
|
|
3337
|
+
const { node: selectedNode } = selection.selectedNodeWithOffset();
|
|
3338
|
+
return $getNearestNodeOfType(selectedNode, ImageGalleryNode) !== null
|
|
3339
|
+
}
|
|
3340
|
+
|
|
3341
|
+
$insertUploadNodes() {
|
|
3342
|
+
this.#findOrCreateGallery();
|
|
3343
|
+
this.#insertImagesInGallery();
|
|
3344
|
+
this.#insertNonImagesAfterGallery();
|
|
3345
|
+
}
|
|
3346
|
+
|
|
3347
|
+
#findOrCreateGallery() {
|
|
3348
|
+
if (this.selection.isOnPreviewableImage) {
|
|
3349
|
+
this.#gallery = $findOrCreateGalleryForImage(this.#selectedNode);
|
|
3350
|
+
} else {
|
|
3351
|
+
this.#gallery = $createImageGalleryNode();
|
|
3352
|
+
this.contents.insertAtCursor(this.#gallery);
|
|
2922
3353
|
}
|
|
2923
3354
|
}
|
|
2924
3355
|
|
|
2925
|
-
#
|
|
2926
|
-
const
|
|
2927
|
-
|
|
3356
|
+
get #selectedNode() {
|
|
3357
|
+
const { node } = this.selection.selectedNodeWithOffset();
|
|
3358
|
+
return node
|
|
3359
|
+
}
|
|
2928
3360
|
|
|
2929
|
-
|
|
2930
|
-
|
|
3361
|
+
get #galleryInsertPosition() {
|
|
3362
|
+
const anchor = $getSelection()?.anchor;
|
|
3363
|
+
const galleryHasElementSelection = anchor?.getNode().is(this.#gallery);
|
|
3364
|
+
if (galleryHasElementSelection) return anchor.offset
|
|
2931
3365
|
|
|
2932
|
-
|
|
2933
|
-
|
|
2934
|
-
|
|
3366
|
+
const selectedNode = this.#selectedNode;
|
|
3367
|
+
const childIndex = this.#gallery.isParentOf(selectedNode) && selectedNode.getIndexWithinParent();
|
|
3368
|
+
return childIndex !== false ? (childIndex + 1) : 0
|
|
3369
|
+
}
|
|
2935
3370
|
|
|
2936
|
-
|
|
3371
|
+
get #imageNodes() {
|
|
3372
|
+
return this.nodes.filter(node => ImageGalleryNode.isValidChild(node))
|
|
3373
|
+
}
|
|
2937
3374
|
|
|
2938
|
-
|
|
2939
|
-
this
|
|
3375
|
+
get #nonImageNodes() {
|
|
3376
|
+
return this.nodes.filter(node => !ImageGalleryNode.isValidChild(node))
|
|
3377
|
+
}
|
|
2940
3378
|
|
|
2941
|
-
|
|
3379
|
+
#insertImagesInGallery() {
|
|
3380
|
+
this.#gallery.splice(this.#galleryInsertPosition, 0, this.#imageNodes);
|
|
3381
|
+
}
|
|
3382
|
+
|
|
3383
|
+
#insertNonImagesAfterGallery() {
|
|
3384
|
+
let beforeNode = this.#gallery;
|
|
3385
|
+
|
|
3386
|
+
for (const node of this.#nonImageNodes) {
|
|
3387
|
+
beforeNode.insertAfter(node);
|
|
3388
|
+
beforeNode = node;
|
|
3389
|
+
}
|
|
2942
3390
|
}
|
|
2943
3391
|
}
|
|
2944
3392
|
|
|
@@ -2951,29 +3399,34 @@ class Contents {
|
|
|
2951
3399
|
}
|
|
2952
3400
|
|
|
2953
3401
|
insertHtml(html, { tag } = {}) {
|
|
3402
|
+
this.insertDOM(parseHtml(html), { tag });
|
|
3403
|
+
}
|
|
3404
|
+
|
|
3405
|
+
insertDOM(doc, { tag } = {}) {
|
|
2954
3406
|
this.editor.update(() => {
|
|
2955
3407
|
const selection = $getSelection();
|
|
2956
3408
|
if (!$isRangeSelection(selection)) return
|
|
2957
3409
|
|
|
2958
|
-
const nodes = $generateNodesFromDOM(this.editor,
|
|
2959
|
-
|
|
3410
|
+
const nodes = $generateNodesFromDOM(this.editor, doc);
|
|
3411
|
+
if (!this.#insertUploadNodes(nodes)) {
|
|
3412
|
+
selection.insertNodes(nodes);
|
|
3413
|
+
}
|
|
2960
3414
|
}, { tag });
|
|
2961
3415
|
}
|
|
2962
3416
|
|
|
2963
3417
|
insertAtCursor(node) {
|
|
2964
|
-
const selection = $getSelection();
|
|
3418
|
+
const selection = $getSelection() ?? $getRoot().selectEnd();
|
|
2965
3419
|
const selectedNodes = selection?.getNodes();
|
|
2966
3420
|
|
|
2967
3421
|
if ($isRangeSelection(selection)) {
|
|
2968
|
-
|
|
2969
|
-
} else if ($isNodeSelection(selection) && selectedNodes
|
|
3422
|
+
selection.insertNodes([ node ]);
|
|
3423
|
+
} else if ($isNodeSelection(selection) && selectedNodes.length > 0) {
|
|
3424
|
+
// Overrides Lexical's default behavior of _removing_ the currently selected nodes
|
|
3425
|
+
// https://github.com/facebook/lexical/blob/v0.38.2/packages/lexical/src/LexicalSelection.ts#L412
|
|
2970
3426
|
const lastNode = selectedNodes.at(-1);
|
|
2971
3427
|
lastNode.insertAfter(node);
|
|
2972
|
-
} else {
|
|
2973
|
-
const root = $getRoot();
|
|
2974
|
-
root.append(node);
|
|
2975
3428
|
}
|
|
2976
|
-
|
|
3429
|
+
}
|
|
2977
3430
|
|
|
2978
3431
|
insertAtCursorEnsuringLineBelow(node) {
|
|
2979
3432
|
this.insertAtCursor(node);
|
|
@@ -3090,6 +3543,7 @@ class Contents {
|
|
|
3090
3543
|
if (!this.hasSelectedText()) return
|
|
3091
3544
|
|
|
3092
3545
|
this.editor.update(() => {
|
|
3546
|
+
$toggleLink(null);
|
|
3093
3547
|
$toggleLink(url);
|
|
3094
3548
|
});
|
|
3095
3549
|
}
|
|
@@ -3181,23 +3635,22 @@ class Contents {
|
|
|
3181
3635
|
}
|
|
3182
3636
|
}
|
|
3183
3637
|
|
|
3184
|
-
|
|
3638
|
+
uploadFiles(files, { selectLast } = {}) {
|
|
3185
3639
|
if (!this.editorElement.supportsAttachments) {
|
|
3186
3640
|
console.warn("This editor does not supports attachments (it's configured with [attachments=false])");
|
|
3187
3641
|
return
|
|
3188
3642
|
}
|
|
3189
|
-
|
|
3190
|
-
if (!this.#shouldUploadFile(file)) {
|
|
3191
|
-
return
|
|
3192
|
-
}
|
|
3193
|
-
|
|
3194
|
-
const uploadUrl = this.editorElement.directUploadUrl;
|
|
3195
|
-
const blobUrlTemplate = this.editorElement.blobUrlTemplate;
|
|
3643
|
+
const validFiles = Array.from(files).filter(this.#shouldUploadFile.bind(this));
|
|
3196
3644
|
|
|
3197
3645
|
this.editor.update(() => {
|
|
3198
|
-
const
|
|
3199
|
-
|
|
3200
|
-
|
|
3646
|
+
const uploader = Uploader.for(this.editorElement, validFiles);
|
|
3647
|
+
uploader.$uploadFiles();
|
|
3648
|
+
|
|
3649
|
+
if (selectLast && uploader.nodes?.length) {
|
|
3650
|
+
const lastNode = uploader.nodes.at(-1);
|
|
3651
|
+
lastNode.selectEnd();
|
|
3652
|
+
}
|
|
3653
|
+
});
|
|
3201
3654
|
}
|
|
3202
3655
|
|
|
3203
3656
|
replaceNodeWithHTML(nodeKey, html, options = {}) {
|
|
@@ -3238,6 +3691,15 @@ class Contents {
|
|
|
3238
3691
|
});
|
|
3239
3692
|
}
|
|
3240
3693
|
|
|
3694
|
+
#insertUploadNodes(nodes) {
|
|
3695
|
+
if (nodes.every($isActionTextAttachmentNode)) {
|
|
3696
|
+
const uploader = Uploader.for(this.editorElement, []);
|
|
3697
|
+
uploader.nodes = nodes;
|
|
3698
|
+
uploader.$insertUploadNodes();
|
|
3699
|
+
return true
|
|
3700
|
+
}
|
|
3701
|
+
}
|
|
3702
|
+
|
|
3241
3703
|
#insertLineBelowIfLastNode(node) {
|
|
3242
3704
|
this.editor.update(() => {
|
|
3243
3705
|
const nextSibling = node.getNextSibling();
|
|
@@ -3357,7 +3819,7 @@ class Contents {
|
|
|
3357
3819
|
wrappingNode.append(...topLevelElement.getChildren());
|
|
3358
3820
|
topLevelElement.replace(wrappingNode);
|
|
3359
3821
|
} else {
|
|
3360
|
-
|
|
3822
|
+
selection.insertNodes([ newNodeFn() ]);
|
|
3361
3823
|
}
|
|
3362
3824
|
}
|
|
3363
3825
|
|
|
@@ -3617,15 +4079,15 @@ class Clipboard {
|
|
|
3617
4079
|
paste(event) {
|
|
3618
4080
|
const clipboardData = event.clipboardData;
|
|
3619
4081
|
|
|
3620
|
-
if (!clipboardData) return false
|
|
4082
|
+
if (!clipboardData || this.#isPastingIntoCodeBlock()) return false
|
|
3621
4083
|
|
|
3622
|
-
if (this.#isPlainTextOrURLPasted(clipboardData)
|
|
4084
|
+
if (this.#isPlainTextOrURLPasted(clipboardData)) {
|
|
3623
4085
|
this.#pastePlainText(clipboardData);
|
|
3624
4086
|
event.preventDefault();
|
|
3625
4087
|
return true
|
|
3626
4088
|
}
|
|
3627
4089
|
|
|
3628
|
-
this.#handlePastedFiles(clipboardData)
|
|
4090
|
+
return this.#handlePastedFiles(clipboardData)
|
|
3629
4091
|
}
|
|
3630
4092
|
|
|
3631
4093
|
#isPlainTextOrURLPasted(clipboardData) {
|
|
@@ -3694,7 +4156,15 @@ class Clipboard {
|
|
|
3694
4156
|
|
|
3695
4157
|
#pasteMarkdown(text) {
|
|
3696
4158
|
const html = marked(text);
|
|
3697
|
-
|
|
4159
|
+
const doc = parseHtml(html);
|
|
4160
|
+
const detail = Object.freeze({
|
|
4161
|
+
markdown: text,
|
|
4162
|
+
document: doc,
|
|
4163
|
+
addBlockSpacing: () => addBlockSpacing(doc)
|
|
4164
|
+
});
|
|
4165
|
+
|
|
4166
|
+
dispatch(this.editorElement, "lexxy:insert-markdown", detail);
|
|
4167
|
+
this.contents.insertDOM(doc, { tag: PASTE_TAG });
|
|
3698
4168
|
}
|
|
3699
4169
|
|
|
3700
4170
|
#pasteRichText(clipboardData) {
|
|
@@ -3705,19 +4175,22 @@ class Clipboard {
|
|
|
3705
4175
|
}
|
|
3706
4176
|
|
|
3707
4177
|
#handlePastedFiles(clipboardData) {
|
|
3708
|
-
if (!this.editorElement.supportsAttachments) return
|
|
4178
|
+
if (!this.editorElement.supportsAttachments) return false
|
|
3709
4179
|
|
|
3710
4180
|
const html = clipboardData.getData("text/html");
|
|
3711
|
-
if (html)
|
|
4181
|
+
if (html) {
|
|
4182
|
+
this.contents.insertHtml(html, { tag: PASTE_TAG });
|
|
4183
|
+
return true
|
|
4184
|
+
}
|
|
3712
4185
|
|
|
3713
4186
|
this.#preservingScrollPosition(() => {
|
|
3714
|
-
|
|
3715
|
-
|
|
3716
|
-
|
|
3717
|
-
|
|
3718
|
-
this.contents.uploadFile(file);
|
|
4187
|
+
const files = clipboardData.files;
|
|
4188
|
+
if (files.length) {
|
|
4189
|
+
this.contents.uploadFiles(files, { selectLast: true });
|
|
3719
4190
|
}
|
|
3720
4191
|
});
|
|
4192
|
+
|
|
4193
|
+
return true
|
|
3721
4194
|
}
|
|
3722
4195
|
|
|
3723
4196
|
// Deals with an issue in Safari where it scrolls to the tops after pasting attachments
|
|
@@ -4091,6 +4564,93 @@ class TablesExtension extends LexxyExtension {
|
|
|
4091
4564
|
}
|
|
4092
4565
|
}
|
|
4093
4566
|
|
|
4567
|
+
class AttachmentsExtension extends LexxyExtension {
|
|
4568
|
+
get enabled() {
|
|
4569
|
+
return this.editorElement.supportsAttachments
|
|
4570
|
+
}
|
|
4571
|
+
|
|
4572
|
+
get lexicalExtension() {
|
|
4573
|
+
return defineExtension({
|
|
4574
|
+
name: "lexxy/action-text-attachments",
|
|
4575
|
+
nodes: [
|
|
4576
|
+
ActionTextAttachmentNode,
|
|
4577
|
+
ActionTextAttachmentUploadNode,
|
|
4578
|
+
ImageGalleryNode
|
|
4579
|
+
],
|
|
4580
|
+
register(editor) {
|
|
4581
|
+
return mergeRegister(
|
|
4582
|
+
editor.registerCommand(DELETE_CHARACTER_COMMAND, $collapseIntoGallery, COMMAND_PRIORITY_NORMAL)
|
|
4583
|
+
)
|
|
4584
|
+
}
|
|
4585
|
+
})
|
|
4586
|
+
}
|
|
4587
|
+
}
|
|
4588
|
+
|
|
4589
|
+
function $collapseIntoGallery(backwards) {
|
|
4590
|
+
const anchor = $getSelection()?.anchor;
|
|
4591
|
+
if (!anchor) return false
|
|
4592
|
+
|
|
4593
|
+
if ($collapseAtGalleryEdge(anchor, backwards)) {
|
|
4594
|
+
return true
|
|
4595
|
+
} else if (backwards) {
|
|
4596
|
+
return $collapseAroundEmptyParagraph(anchor)
|
|
4597
|
+
|| $moveSelectionBeforeGallery(anchor)
|
|
4598
|
+
}
|
|
4599
|
+
|
|
4600
|
+
return false
|
|
4601
|
+
}
|
|
4602
|
+
|
|
4603
|
+
function $collapseAroundEmptyParagraph(anchor) {
|
|
4604
|
+
const anchorNode = anchor.getNode();
|
|
4605
|
+
if (!anchorNode) return false
|
|
4606
|
+
|
|
4607
|
+
const isWithinEmptyParagraph = $isParagraphNode(anchorNode) && anchorNode.isEmpty();
|
|
4608
|
+
const previousSibling = anchorNode.getPreviousSibling();
|
|
4609
|
+
const topGallery = $findOrCreateGalleryForImage(previousSibling);
|
|
4610
|
+
const selectionIndex = topGallery?.getChildrenSize();
|
|
4611
|
+
|
|
4612
|
+
if (isWithinEmptyParagraph && topGallery?.collapseWith(anchorNode.getNextSibling())) {
|
|
4613
|
+
topGallery.select(selectionIndex, selectionIndex);
|
|
4614
|
+
anchorNode.remove();
|
|
4615
|
+
return true
|
|
4616
|
+
} else {
|
|
4617
|
+
return false
|
|
4618
|
+
}
|
|
4619
|
+
}
|
|
4620
|
+
|
|
4621
|
+
function $collapseAtGalleryEdge(anchor, backwards) {
|
|
4622
|
+
const anchorNode = anchor.getNode();
|
|
4623
|
+
if (!$isImageGalleryNode(anchorNode)) return false
|
|
4624
|
+
|
|
4625
|
+
const isAtGalleryEdge = $isAtNodeEdge(anchor, backwards);
|
|
4626
|
+
const sibling = backwards ? anchorNode.getPreviousSibling() : anchorNode.getNextSibling();
|
|
4627
|
+
|
|
4628
|
+
if (isAtGalleryEdge && anchorNode.collapseWith(sibling, backwards)) {
|
|
4629
|
+
const selectionOffset = backwards ? 1 : anchorNode.getChildrenSize() - 1;
|
|
4630
|
+
anchorNode.select(selectionOffset, selectionOffset);
|
|
4631
|
+
return true
|
|
4632
|
+
} else {
|
|
4633
|
+
return false
|
|
4634
|
+
}
|
|
4635
|
+
}
|
|
4636
|
+
|
|
4637
|
+
// Manual selection handling to prevent Lexical merging the gallery with a <p> and unwrapping it
|
|
4638
|
+
function $moveSelectionBeforeGallery(anchor) {
|
|
4639
|
+
const previousNode = anchor.getNode().getPreviousSibling();
|
|
4640
|
+
if (!$isImageGalleryNode(anchor.getNode()) || !$isAtNodeEdge(anchor, true) || !previousNode) return false
|
|
4641
|
+
|
|
4642
|
+
if ($isDecoratorNode(previousNode)) {
|
|
4643
|
+
// Handled by Lexxy decorator selection behavior
|
|
4644
|
+
return false
|
|
4645
|
+
} else if (previousNode.isEmpty()) {
|
|
4646
|
+
previousNode.remove();
|
|
4647
|
+
} else {
|
|
4648
|
+
previousNode.selectEnd();
|
|
4649
|
+
}
|
|
4650
|
+
|
|
4651
|
+
return true
|
|
4652
|
+
}
|
|
4653
|
+
|
|
4094
4654
|
class LexicalEditorElement extends HTMLElement {
|
|
4095
4655
|
static formAssociated = true
|
|
4096
4656
|
static debug = false
|
|
@@ -4180,7 +4740,8 @@ class LexicalEditorElement extends HTMLElement {
|
|
|
4180
4740
|
ProvisionalParagraphExtension,
|
|
4181
4741
|
HighlightExtension,
|
|
4182
4742
|
TrixContentExtension,
|
|
4183
|
-
TablesExtension
|
|
4743
|
+
TablesExtension,
|
|
4744
|
+
AttachmentsExtension
|
|
4184
4745
|
]
|
|
4185
4746
|
}
|
|
4186
4747
|
|
|
@@ -4340,10 +4901,6 @@ class LexicalEditorElement extends HTMLElement {
|
|
|
4340
4901
|
);
|
|
4341
4902
|
}
|
|
4342
4903
|
|
|
4343
|
-
if (this.supportsAttachments) {
|
|
4344
|
-
nodes.push(ActionTextAttachmentNode, ActionTextAttachmentUploadNode);
|
|
4345
|
-
}
|
|
4346
|
-
|
|
4347
4904
|
return nodes
|
|
4348
4905
|
}
|
|
4349
4906
|
|
|
@@ -5129,16 +5686,16 @@ class LexicalPromptElement extends HTMLElement {
|
|
|
5129
5686
|
|
|
5130
5687
|
#registerKeyListeners() {
|
|
5131
5688
|
// We can't use a regular keydown for Enter as Lexical handles it first
|
|
5132
|
-
this.keyListeners.push(this.#editor.registerCommand(KEY_ENTER_COMMAND, this.#handleSelectedOption.bind(this),
|
|
5133
|
-
this.keyListeners.push(this.#editor.registerCommand(KEY_TAB_COMMAND, this.#handleSelectedOption.bind(this),
|
|
5689
|
+
this.keyListeners.push(this.#editor.registerCommand(KEY_ENTER_COMMAND, this.#handleSelectedOption.bind(this), COMMAND_PRIORITY_CRITICAL));
|
|
5690
|
+
this.keyListeners.push(this.#editor.registerCommand(KEY_TAB_COMMAND, this.#handleSelectedOption.bind(this), COMMAND_PRIORITY_CRITICAL));
|
|
5134
5691
|
|
|
5135
5692
|
if (this.#doesSpaceSelect) {
|
|
5136
|
-
this.keyListeners.push(this.#editor.registerCommand(KEY_SPACE_COMMAND, this.#handleSelectedOption.bind(this),
|
|
5693
|
+
this.keyListeners.push(this.#editor.registerCommand(KEY_SPACE_COMMAND, this.#handleSelectedOption.bind(this), COMMAND_PRIORITY_CRITICAL));
|
|
5137
5694
|
}
|
|
5138
5695
|
|
|
5139
|
-
// Register arrow keys with
|
|
5140
|
-
this.keyListeners.push(this.#editor.registerCommand(KEY_ARROW_UP_COMMAND, this.#handleArrowUp.bind(this),
|
|
5141
|
-
this.keyListeners.push(this.#editor.registerCommand(KEY_ARROW_DOWN_COMMAND, this.#handleArrowDown.bind(this),
|
|
5696
|
+
// Register arrow keys with CRITICAL priority to prevent Lexical's selection handlers from running
|
|
5697
|
+
this.keyListeners.push(this.#editor.registerCommand(KEY_ARROW_UP_COMMAND, this.#handleArrowUp.bind(this), COMMAND_PRIORITY_CRITICAL));
|
|
5698
|
+
this.keyListeners.push(this.#editor.registerCommand(KEY_ARROW_DOWN_COMMAND, this.#handleArrowDown.bind(this), COMMAND_PRIORITY_CRITICAL));
|
|
5142
5699
|
}
|
|
5143
5700
|
|
|
5144
5701
|
#handleArrowUp(event) {
|
|
@@ -5412,11 +5969,18 @@ class CodeLanguagePicker extends HTMLElement {
|
|
|
5412
5969
|
connectedCallback() {
|
|
5413
5970
|
this.editorElement = this.closest("lexxy-editor");
|
|
5414
5971
|
this.editor = this.editorElement.editor;
|
|
5972
|
+
this.classList.add("lexxy-floating-controls");
|
|
5415
5973
|
|
|
5416
5974
|
this.#attachLanguagePicker();
|
|
5975
|
+
this.#hide();
|
|
5417
5976
|
this.#monitorForCodeBlockSelection();
|
|
5418
5977
|
}
|
|
5419
5978
|
|
|
5979
|
+
disconnectedCallback() {
|
|
5980
|
+
this.unregisterUpdateListener?.();
|
|
5981
|
+
this.unregisterUpdateListener = null;
|
|
5982
|
+
}
|
|
5983
|
+
|
|
5420
5984
|
#attachLanguagePicker() {
|
|
5421
5985
|
this.languagePickerElement = this.#createLanguagePicker();
|
|
5422
5986
|
|
|
@@ -5471,14 +6035,14 @@ class CodeLanguagePicker extends HTMLElement {
|
|
|
5471
6035
|
}
|
|
5472
6036
|
|
|
5473
6037
|
#monitorForCodeBlockSelection() {
|
|
5474
|
-
this.editor.registerUpdateListener(() => {
|
|
6038
|
+
this.unregisterUpdateListener = this.editor.registerUpdateListener(() => {
|
|
5475
6039
|
this.editor.getEditorState().read(() => {
|
|
5476
6040
|
const codeNode = this.#getCurrentCodeNode();
|
|
5477
6041
|
|
|
5478
6042
|
if (codeNode) {
|
|
5479
6043
|
this.#codeNodeWasSelected(codeNode);
|
|
5480
6044
|
} else {
|
|
5481
|
-
this.#
|
|
6045
|
+
this.#hide();
|
|
5482
6046
|
}
|
|
5483
6047
|
});
|
|
5484
6048
|
});
|
|
@@ -5507,7 +6071,7 @@ class CodeLanguagePicker extends HTMLElement {
|
|
|
5507
6071
|
const language = codeNode.getLanguage();
|
|
5508
6072
|
|
|
5509
6073
|
this.#updateLanguagePickerWith(language);
|
|
5510
|
-
this.#
|
|
6074
|
+
this.#show();
|
|
5511
6075
|
this.#positionLanguagePicker(codeNode);
|
|
5512
6076
|
}
|
|
5513
6077
|
|
|
@@ -5531,15 +6095,66 @@ class CodeLanguagePicker extends HTMLElement {
|
|
|
5531
6095
|
this.style.right = `${relativeRight}px`;
|
|
5532
6096
|
}
|
|
5533
6097
|
|
|
5534
|
-
#
|
|
6098
|
+
#show() {
|
|
5535
6099
|
this.hidden = false;
|
|
5536
6100
|
}
|
|
5537
6101
|
|
|
5538
|
-
#
|
|
6102
|
+
#hide() {
|
|
5539
6103
|
this.hidden = true;
|
|
5540
6104
|
}
|
|
5541
6105
|
}
|
|
5542
6106
|
|
|
6107
|
+
const DELETE_ICON = `<svg viewBox="0 0 18 18" xmlns="http://www.w3.org/2000/svg">
|
|
6108
|
+
<path d="M11.2041 1.01074C12.2128 1.113 13 1.96435 13 3V4H15L15.1025 4.00488C15.6067 4.05621 16 4.48232 16 5C16 5.55228 15.5523 6 15 6H14.8457L14.1416 15.1533C14.0614 16.1953 13.1925 17 12.1475 17H5.85254L5.6582 16.9902C4.76514 16.9041 4.03607 16.2296 3.88184 15.3457L3.8584 15.1533L3.1543 6H3C2.44772 6 2 5.55228 2 5C2 4.44772 2.44772 4 3 4H5V3C5 1.89543 5.89543 1 7 1H11L11.2041 1.01074ZM5.85254 15H12.1475L12.8398 6H5.16016L5.85254 15ZM7 4H11V3H7V4Z"/>
|
|
6109
|
+
</svg>`;
|
|
6110
|
+
|
|
6111
|
+
class NodeDeleteButton extends HTMLElement {
|
|
6112
|
+
connectedCallback() {
|
|
6113
|
+
this.editorElement = this.closest("lexxy-editor");
|
|
6114
|
+
this.editor = this.editorElement.editor;
|
|
6115
|
+
this.classList.add("lexxy-floating-controls");
|
|
6116
|
+
|
|
6117
|
+
if (!this.deleteButton) {
|
|
6118
|
+
this.#attachDeleteButton();
|
|
6119
|
+
}
|
|
6120
|
+
}
|
|
6121
|
+
|
|
6122
|
+
disconnectedCallback() {
|
|
6123
|
+
if (this.deleteButton && this.handleDeleteClick) {
|
|
6124
|
+
this.deleteButton.removeEventListener("click", this.handleDeleteClick);
|
|
6125
|
+
}
|
|
6126
|
+
|
|
6127
|
+
this.handleDeleteClick = null;
|
|
6128
|
+
this.deleteButton = null;
|
|
6129
|
+
this.editor = null;
|
|
6130
|
+
this.editorElement = null;
|
|
6131
|
+
}
|
|
6132
|
+
#attachDeleteButton() {
|
|
6133
|
+
const container = createElement("div", { className: "lexxy-floating-controls__group" });
|
|
6134
|
+
|
|
6135
|
+
this.deleteButton = createElement("button", {
|
|
6136
|
+
className: "lexxy-node-delete",
|
|
6137
|
+
type: "button",
|
|
6138
|
+
"aria-label": "Remove"
|
|
6139
|
+
});
|
|
6140
|
+
this.deleteButton.tabIndex = -1;
|
|
6141
|
+
this.deleteButton.innerHTML = DELETE_ICON;
|
|
6142
|
+
|
|
6143
|
+
this.handleDeleteClick = () => this.#deleteNode();
|
|
6144
|
+
this.deleteButton.addEventListener("click", this.handleDeleteClick);
|
|
6145
|
+
container.appendChild(this.deleteButton);
|
|
6146
|
+
|
|
6147
|
+
this.appendChild(container);
|
|
6148
|
+
}
|
|
6149
|
+
|
|
6150
|
+
#deleteNode() {
|
|
6151
|
+
this.editor.update(() => {
|
|
6152
|
+
const node = $getNearestNodeFromDOMNode(this);
|
|
6153
|
+
node?.remove();
|
|
6154
|
+
});
|
|
6155
|
+
}
|
|
6156
|
+
}
|
|
6157
|
+
|
|
5543
6158
|
class TableController {
|
|
5544
6159
|
constructor(editorElement) {
|
|
5545
6160
|
this.editor = editorElement.editor;
|
|
@@ -5931,20 +6546,22 @@ var TableIcons = {
|
|
|
5931
6546
|
|
|
5932
6547
|
"toggle-column":
|
|
5933
6548
|
`<svg viewBox="0 0 18 18" xmlns="http://www.w3.org/2000/svg">
|
|
5934
|
-
<path fill-rule="evenodd" clip-rule="evenodd" d="M13.6533 17.9922C14.4097 17.9154 15 17.2767 15 16.5L15 3L14.9922 2.84668C14.9205 2.14069 14.3593 1.57949 13.6533 1.50781L13.5 1.5L4.5 1.5L4.34668 1.50781C3.59028 1.58461 3 2.22334 3 3L3 16.5C3 17.2767 3.59028 17.9154 4.34668 17.9922L4.5 18L13.5 18L13.6533 17.9922ZM9 3L13.5 3L13.5 16.5L9 16.5L9 3Z"
|
|
6549
|
+
<path fill-rule="evenodd" clip-rule="evenodd" d="M13.6533 17.9922C14.4097 17.9154 15 17.2767 15 16.5L15 3L14.9922 2.84668C14.9205 2.14069 14.3593 1.57949 13.6533 1.50781L13.5 1.5L4.5 1.5L4.34668 1.50781C3.59028 1.58461 3 2.22334 3 3L3 16.5C3 17.2767 3.59028 17.9154 4.34668 17.9922L4.5 18L13.5 18L13.6533 17.9922ZM9 3L13.5 3L13.5 16.5L9 16.5L9 3Z"/>
|
|
5935
6550
|
</svg>`,
|
|
5936
6551
|
|
|
5937
6552
|
"delete-table":
|
|
5938
|
-
`<svg viewBox="0 0
|
|
5939
|
-
|
|
6553
|
+
`<svg viewBox="0 0 18 18" xmlns="http://www.w3.org/2000/svg">
|
|
6554
|
+
<path d="M11.2041 1.01074C12.2128 1.113 13 1.96435 13 3V4H15L15.1025 4.00488C15.6067 4.05621 16 4.48232 16 5C16 5.55228 15.5523 6 15 6H14.8457L14.1416 15.1533C14.0614 16.1953 13.1925 17 12.1475 17H5.85254L5.6582 16.9902C4.76514 16.9041 4.03607 16.2296 3.88184 15.3457L3.8584 15.1533L3.1543 6H3C2.44772 6 2 5.55228 2 5C2 4.44772 2.44772 4 3 4H5V3C5 1.89543 5.89543 1 7 1H11L11.2041 1.01074ZM5.85254 15H12.1475L12.8398 6H5.16016L5.85254 15ZM7 4H11V3H7V4Z"/>
|
|
5940
6555
|
</svg>`
|
|
5941
6556
|
};
|
|
5942
6557
|
|
|
5943
6558
|
class TableTools extends HTMLElement {
|
|
5944
6559
|
connectedCallback() {
|
|
5945
6560
|
this.tableController = new TableController(this.#editorElement);
|
|
6561
|
+
this.classList.add("lexxy-floating-controls");
|
|
5946
6562
|
|
|
5947
6563
|
this.#setUpButtons();
|
|
6564
|
+
this.#hide();
|
|
5948
6565
|
this.#monitorForTableSelection();
|
|
5949
6566
|
this.#registerKeyboardShortcuts();
|
|
5950
6567
|
}
|
|
@@ -5982,7 +6599,7 @@ class TableTools extends HTMLElement {
|
|
|
5982
6599
|
}
|
|
5983
6600
|
|
|
5984
6601
|
#createButtonsContainer(childType, setCountProperty, moreMenu) {
|
|
5985
|
-
const container = createElement("div", { className: `lexxy-table-control lexxy-table-control--${childType}` });
|
|
6602
|
+
const container = createElement("div", { className: `lexxy-floating-controls__group lexxy-table-control lexxy-table-control--${childType}` });
|
|
5986
6603
|
|
|
5987
6604
|
const plusButton = this.#createButton(`Add ${childType}`, { action: "insert", childType, direction: "after" }, "+");
|
|
5988
6605
|
const minusButton = this.#createButton(`Remove ${childType}`, { action: "delete", childType }, "−");
|
|
@@ -6021,7 +6638,7 @@ class TableTools extends HTMLElement {
|
|
|
6021
6638
|
}
|
|
6022
6639
|
|
|
6023
6640
|
#createMoreMenuSection(childType) {
|
|
6024
|
-
const section = createElement("div", { className: "lexxy-table-control__more-menu-details" });
|
|
6641
|
+
const section = createElement("div", { className: "lexxy-floating-controls__group lexxy-table-control__more-menu-details" });
|
|
6025
6642
|
const addBeforeButton = this.#createButton(`Add ${childType} before`, { action: "insert", childType, direction: "before" });
|
|
6026
6643
|
const addAfterButton = this.#createButton(`Add ${childType} after`, { action: "insert", childType, direction: "after" });
|
|
6027
6644
|
const toggleStyleButton = this.#createButton(`Toggle ${childType} style`, { action: "toggle", childType });
|
|
@@ -6036,7 +6653,7 @@ class TableTools extends HTMLElement {
|
|
|
6036
6653
|
}
|
|
6037
6654
|
|
|
6038
6655
|
#createDeleteTableButton() {
|
|
6039
|
-
const container = createElement("div", { className: "lexxy-table-control" });
|
|
6656
|
+
const container = createElement("div", { className: "lexxy-table-control lexxy-floating-controls__group" });
|
|
6040
6657
|
|
|
6041
6658
|
const deleteTableButton = this.#createButton("Delete this table?", { action: "delete", childType: "table" });
|
|
6042
6659
|
deleteTableButton.classList.add("lexxy-table-control__button--delete-table");
|
|
@@ -6255,6 +6872,7 @@ function defineElements() {
|
|
|
6255
6872
|
"lexxy-highlight-dropdown": HighlightDropdown,
|
|
6256
6873
|
"lexxy-prompt": LexicalPromptElement,
|
|
6257
6874
|
"lexxy-code-language-picker": CodeLanguagePicker,
|
|
6875
|
+
"lexxy-node-delete-button": NodeDeleteButton,
|
|
6258
6876
|
"lexxy-table-tools": TableTools,
|
|
6259
6877
|
};
|
|
6260
6878
|
|
|
@@ -6268,4 +6886,4 @@ const configure = Lexxy.configure;
|
|
|
6268
6886
|
// Pushing elements definition to after the current call stack to allow global configuration to take place first
|
|
6269
6887
|
setTimeout(defineElements, 0);
|
|
6270
6888
|
|
|
6271
|
-
export { ActionTextAttachmentNode, ActionTextAttachmentUploadNode, CustomActionTextAttachmentNode, LexxyExtension as Extension, HorizontalDividerNode, configure };
|
|
6889
|
+
export { $createActionTextAttachmentNode, $createActionTextAttachmentUploadNode, $isActionTextAttachmentNode, ActionTextAttachmentNode, ActionTextAttachmentUploadNode, CustomActionTextAttachmentNode, LexxyExtension as Extension, HorizontalDividerNode, configure };
|