@37signals/lexxy 0.1.21-beta → 0.1.22-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/README.md +1 -1
- package/dist/lexxy.esm.js +64 -21
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -23,7 +23,7 @@ A modern rich text editor for Rails.
|
|
|
23
23
|
Add this line to your application's Gemfile:
|
|
24
24
|
|
|
25
25
|
```ruby
|
|
26
|
-
gem 'lexxy', '~> 0.1.
|
|
26
|
+
gem 'lexxy', '~> 0.1.21.beta' # Need to specify the version since it's a pre-release
|
|
27
27
|
```
|
|
28
28
|
|
|
29
29
|
And then execute:
|
package/dist/lexxy.esm.js
CHANGED
|
@@ -1,16 +1,21 @@
|
|
|
1
1
|
import DOMPurify from 'dompurify';
|
|
2
|
-
import { getStyleObjectFromCSS, getCSSFromStyleObject, $
|
|
3
|
-
import { $isTextNode, TextNode, $
|
|
2
|
+
import { getStyleObjectFromCSS, getCSSFromStyleObject, $getSelectionStyleValueForProperty, $patchStyleText } from '@lexical/selection';
|
|
3
|
+
import { $isTextNode, TextNode, $isRangeSelection, $getSelection, DecoratorNode, $getNodeByKey, HISTORY_MERGE_TAG, FORMAT_TEXT_COMMAND, $createTextNode, UNDO_COMMAND, REDO_COMMAND, PASTE_COMMAND, COMMAND_PRIORITY_LOW, $isNodeSelection, $getRoot, $isLineBreakNode, $isElementNode, KEY_ARROW_LEFT_COMMAND, KEY_ARROW_RIGHT_COMMAND, KEY_ARROW_UP_COMMAND, KEY_ARROW_DOWN_COMMAND, KEY_DELETE_COMMAND, KEY_BACKSPACE_COMMAND, SELECTION_CHANGE_COMMAND, $createNodeSelection, $setSelection, $createParagraphNode, KEY_ENTER_COMMAND, COMMAND_PRIORITY_HIGH, $isParagraphNode, $insertNodes, $createLineBreakNode, CLEAR_HISTORY_COMMAND, $addUpdateTag, SKIP_DOM_SELECTION_TAG, createEditor, COMMAND_PRIORITY_NORMAL, BLUR_COMMAND, FOCUS_COMMAND, KEY_TAB_COMMAND, KEY_SPACE_COMMAND } from 'lexical';
|
|
4
4
|
import { $isListNode, $isListItemNode, INSERT_UNORDERED_LIST_COMMAND, INSERT_ORDERED_LIST_COMMAND, $createListNode, ListNode, ListItemNode, registerList } from '@lexical/list';
|
|
5
5
|
import { $isQuoteNode, $isHeadingNode, $createQuoteNode, $createHeadingNode, QuoteNode, HeadingNode, registerRichText } from '@lexical/rich-text';
|
|
6
6
|
import { $isCodeNode, CodeNode, normalizeCodeLang, CodeHighlightNode, registerCodeHighlighting, CODE_LANGUAGE_FRIENDLY_NAME_MAP } from '@lexical/code';
|
|
7
7
|
import { $isLinkNode, $createAutoLinkNode, $toggleLink, $createLinkNode, LinkNode, AutoLinkNode } from '@lexical/link';
|
|
8
|
+
import 'prismjs/components/prism-ruby';
|
|
9
|
+
import 'prismjs/components/prism-php';
|
|
10
|
+
import 'prismjs/components/prism-go';
|
|
11
|
+
import 'prismjs/components/prism-bash';
|
|
12
|
+
import 'prismjs/components/prism-json';
|
|
13
|
+
import 'prismjs/components/prism-diff';
|
|
8
14
|
import { $generateNodesFromDOM, $generateHtmlFromNodes } from '@lexical/html';
|
|
9
15
|
import { registerMarkdownShortcuts, TRANSFORMERS } from '@lexical/markdown';
|
|
10
16
|
import { createEmptyHistoryState, registerHistory } from '@lexical/history';
|
|
11
17
|
import { DirectUpload } from '@rails/activestorage';
|
|
12
18
|
import { marked } from 'marked';
|
|
13
|
-
import 'prismjs/components/prism-ruby';
|
|
14
19
|
|
|
15
20
|
const ALLOWED_HTML_TAGS = [ "a", "action-text-attachment", "b", "blockquote", "br", "code", "em",
|
|
16
21
|
"figcaption", "figure", "h1", "h2", "h3", "h4", "h5", "h6", "hr", "i", "img", "li", "mark", "ol", "p", "pre", "q", "s", "strong", "ul" ];
|
|
@@ -115,6 +120,21 @@ function extendConversion(nodeKlass, conversionName, callback = (output => outpu
|
|
|
115
120
|
}
|
|
116
121
|
}
|
|
117
122
|
|
|
123
|
+
function isSelectionHighlighted(selection) {
|
|
124
|
+
if (!$isRangeSelection(selection)) return false
|
|
125
|
+
|
|
126
|
+
if (selection.isCollapsed()) {
|
|
127
|
+
return hasHighlightStyles(selection.style)
|
|
128
|
+
} else {
|
|
129
|
+
return selection.hasFormat("highlight")
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
function hasHighlightStyles(cssOrStyles) {
|
|
134
|
+
const styles = typeof cssOrStyles === "string" ? getStyleObjectFromCSS(cssOrStyles) : cssOrStyles;
|
|
135
|
+
return !!(styles.color || styles["background-color"])
|
|
136
|
+
}
|
|
137
|
+
|
|
118
138
|
class LexicalToolbarElement extends HTMLElement {
|
|
119
139
|
constructor() {
|
|
120
140
|
super();
|
|
@@ -267,7 +287,7 @@ class LexicalToolbarElement extends HTMLElement {
|
|
|
267
287
|
const isBold = selection.hasFormat("bold");
|
|
268
288
|
const isItalic = selection.hasFormat("italic");
|
|
269
289
|
const isStrikethrough = selection.hasFormat("strikethrough");
|
|
270
|
-
const isHighlight = selection
|
|
290
|
+
const isHighlight = isSelectionHighlighted(selection);
|
|
271
291
|
const isInLink = this.#isInLink(anchorNode);
|
|
272
292
|
const isInQuote = $isQuoteNode(topLevelElement);
|
|
273
293
|
const isInHeading = $isHeadingNode(topLevelElement);
|
|
@@ -486,6 +506,7 @@ var theme = {
|
|
|
486
506
|
}
|
|
487
507
|
},
|
|
488
508
|
codeHighlight: {
|
|
509
|
+
addition: "code-token__selector",
|
|
489
510
|
atrule: "code-token__attr",
|
|
490
511
|
attr: "code-token__attr",
|
|
491
512
|
"attr-name": "code-token__attr",
|
|
@@ -500,24 +521,30 @@ var theme = {
|
|
|
500
521
|
color: "code-token__property",
|
|
501
522
|
comment: "code-token__comment",
|
|
502
523
|
constant: "code-token__property",
|
|
503
|
-
coord: "code-
|
|
524
|
+
coord: "code-token__comment",
|
|
504
525
|
decorator: "code-token__function",
|
|
505
|
-
deleted: "code-
|
|
526
|
+
deleted: "code-token__operator",
|
|
527
|
+
deletion: "code-token__operator",
|
|
528
|
+
directive: "code-token__attr",
|
|
529
|
+
"directive-hash": "code-token__property",
|
|
506
530
|
doctype: "code-token__comment",
|
|
507
531
|
entity: "code-token__operator",
|
|
508
532
|
function: "code-token__function",
|
|
509
533
|
hexcode: "code-token__property",
|
|
510
|
-
important: "code-
|
|
534
|
+
important: "code-token__function",
|
|
511
535
|
inserted: "code-token__selector",
|
|
512
536
|
italic: "code-token__comment",
|
|
513
537
|
keyword: "code-token__attr",
|
|
538
|
+
line: "code-token__selector",
|
|
514
539
|
namespace: "code-token__variable",
|
|
515
540
|
number: "code-token__property",
|
|
541
|
+
macro: "code-token__function",
|
|
516
542
|
operator: "code-token__operator",
|
|
517
543
|
parameter: "code-token__variable",
|
|
518
544
|
prolog: "code-token__comment",
|
|
519
545
|
property: "code-token__property",
|
|
520
546
|
punctuation: "code-token__punctuation",
|
|
547
|
+
"raw-string": "code-token__operator",
|
|
521
548
|
regex: "code-token__variable",
|
|
522
549
|
script: "code-token__function",
|
|
523
550
|
selector: "code-token__selector",
|
|
@@ -526,6 +553,7 @@ var theme = {
|
|
|
526
553
|
symbol: "code-token__property",
|
|
527
554
|
tag: "code-token__property",
|
|
528
555
|
title: "code-token__function",
|
|
556
|
+
"type-definition": "code-token__function",
|
|
529
557
|
url: "code-token__operator",
|
|
530
558
|
variable: "code-token__variable",
|
|
531
559
|
}
|
|
@@ -3234,27 +3262,37 @@ class Clipboard {
|
|
|
3234
3262
|
class Highlighter {
|
|
3235
3263
|
constructor(editorElement) {
|
|
3236
3264
|
this.editor = editorElement.editor;
|
|
3265
|
+
|
|
3266
|
+
this.#registerHighlightTransform();
|
|
3237
3267
|
}
|
|
3238
3268
|
|
|
3239
3269
|
toggle(styles) {
|
|
3240
3270
|
this.editor.update(() => {
|
|
3241
3271
|
this.#toggleSelectionStyles(styles);
|
|
3242
|
-
$forEachSelectedTextNode(node => this.#syncHighlightWithStyle(node));
|
|
3243
3272
|
});
|
|
3244
3273
|
}
|
|
3245
3274
|
|
|
3246
3275
|
remove() {
|
|
3247
|
-
this.toggle({ "color":
|
|
3276
|
+
this.toggle({ "color": null, "background-color": null });
|
|
3277
|
+
}
|
|
3278
|
+
|
|
3279
|
+
#registerHighlightTransform() {
|
|
3280
|
+
return this.editor.registerNodeTransform(TextNode, (textNode) => {
|
|
3281
|
+
this.#syncHighlightWithStyle(textNode);
|
|
3282
|
+
})
|
|
3248
3283
|
}
|
|
3249
3284
|
|
|
3250
3285
|
#toggleSelectionStyles(styles) {
|
|
3251
3286
|
const selection = $getSelection();
|
|
3287
|
+
if (!$isRangeSelection(selection)) return
|
|
3252
3288
|
|
|
3289
|
+
const patch = {};
|
|
3253
3290
|
for (const property in styles) {
|
|
3254
3291
|
const oldValue = $getSelectionStyleValueForProperty(selection, property);
|
|
3255
|
-
|
|
3256
|
-
$patchStyleText(selection, patch);
|
|
3292
|
+
patch[property] = this.#toggleOrReplace(oldValue, styles[property]);
|
|
3257
3293
|
}
|
|
3294
|
+
|
|
3295
|
+
$patchStyleText(selection, patch);
|
|
3258
3296
|
}
|
|
3259
3297
|
|
|
3260
3298
|
#toggleOrReplace(oldValue, newValue) {
|
|
@@ -3262,15 +3300,10 @@ class Highlighter {
|
|
|
3262
3300
|
}
|
|
3263
3301
|
|
|
3264
3302
|
#syncHighlightWithStyle(node) {
|
|
3265
|
-
if (
|
|
3303
|
+
if (hasHighlightStyles(node.getStyle()) !== node.hasFormat("highlight")) {
|
|
3266
3304
|
node.toggleFormat("highlight");
|
|
3267
3305
|
}
|
|
3268
3306
|
}
|
|
3269
|
-
|
|
3270
|
-
#hasHighlightStyles(node) {
|
|
3271
|
-
const styles = getStyleObjectFromCSS(node.getStyle());
|
|
3272
|
-
return !!(styles.color || styles["background-color"])
|
|
3273
|
-
}
|
|
3274
3307
|
}
|
|
3275
3308
|
|
|
3276
3309
|
class HighlightNode extends TextNode {
|
|
@@ -3923,6 +3956,11 @@ customElements.define("lexxy-link-dialog", LinkDialog);
|
|
|
3923
3956
|
const APPLY_HIGHLIGHT_SELECTOR = "button.lexxy-highlight-button";
|
|
3924
3957
|
const REMOVE_HIGHLIGHT_SELECTOR = "[data-command='removeHighlight']";
|
|
3925
3958
|
|
|
3959
|
+
// Use Symbol instead of null since $getSelectionStyleValueForProperty
|
|
3960
|
+
// responds differently for backward selections if null is the default
|
|
3961
|
+
// see https://github.com/facebook/lexical/issues/8013
|
|
3962
|
+
const NO_STYLE = Symbol("no_style");
|
|
3963
|
+
|
|
3926
3964
|
class HighlightDialog extends ToolbarDialog {
|
|
3927
3965
|
connectedCallback() {
|
|
3928
3966
|
super.connectedCallback();
|
|
@@ -3987,16 +4025,16 @@ class HighlightDialog extends ToolbarDialog {
|
|
|
3987
4025
|
#updateColorButtonStates(selection) {
|
|
3988
4026
|
if (!$isRangeSelection(selection)) { return }
|
|
3989
4027
|
|
|
3990
|
-
// Use
|
|
3991
|
-
const textColor = $getSelectionStyleValueForProperty(selection, "color",
|
|
3992
|
-
const backgroundColor = $getSelectionStyleValueForProperty(selection, "background-color",
|
|
4028
|
+
// Use non-"" default, so "" indicates mixed highlighting
|
|
4029
|
+
const textColor = $getSelectionStyleValueForProperty(selection, "color", NO_STYLE);
|
|
4030
|
+
const backgroundColor = $getSelectionStyleValueForProperty(selection, "background-color", NO_STYLE);
|
|
3993
4031
|
|
|
3994
4032
|
this.#colorButtons.forEach(button => {
|
|
3995
4033
|
const matchesSelection = button.dataset.value === textColor || button.dataset.value === backgroundColor;
|
|
3996
4034
|
button.setAttribute("aria-pressed", matchesSelection);
|
|
3997
4035
|
});
|
|
3998
4036
|
|
|
3999
|
-
const hasHighlight = textColor !==
|
|
4037
|
+
const hasHighlight = textColor !== NO_STYLE || backgroundColor !== NO_STYLE;
|
|
4000
4038
|
this.querySelector(REMOVE_HIGHLIGHT_SELECTOR).disabled = !hasHighlight;
|
|
4001
4039
|
}
|
|
4002
4040
|
|
|
@@ -4578,6 +4616,11 @@ class CodeLanguagePicker extends HTMLElement {
|
|
|
4578
4616
|
const languages = { ...CODE_LANGUAGE_FRIENDLY_NAME_MAP };
|
|
4579
4617
|
|
|
4580
4618
|
if (!languages.ruby) languages.ruby = "Ruby";
|
|
4619
|
+
if (!languages.php) languages.php = "PHP";
|
|
4620
|
+
if (!languages.go) languages.go = "Go";
|
|
4621
|
+
if (!languages.bash) languages.bash = "Bash";
|
|
4622
|
+
if (!languages.json) languages.json = "JSON";
|
|
4623
|
+
if (!languages.diff) languages.diff = "Diff";
|
|
4581
4624
|
|
|
4582
4625
|
const sortedEntries = Object.entries(languages)
|
|
4583
4626
|
.sort(([ , a ], [ , b ]) => a.localeCompare(b));
|