@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 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.20.beta' # Need to specify the version since it's a pre-release
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, $forEachSelectedTextNode, $getSelectionStyleValueForProperty, $patchStyleText } from '@lexical/selection';
3
- import { $isTextNode, TextNode, $getSelection, $isRangeSelection, 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';
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.hasFormat("highlight");
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-token__property",
524
+ coord: "code-token__comment",
504
525
  decorator: "code-token__function",
505
- deleted: "code-token__property",
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-token__variable",
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": undefined, "background-color": undefined });
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
- const patch = { [property]: this.#toggleOrReplace(oldValue, styles[property]) };
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 (this.#hasHighlightStyles(node) !== node.hasFormat("highlight")) {
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 null default, so "" indicates mixed highlighting
3991
- const textColor = $getSelectionStyleValueForProperty(selection, "color", null);
3992
- const backgroundColor = $getSelectionStyleValueForProperty(selection, "background-color", null);
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 !== null || backgroundColor !== null;
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));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@37signals/lexxy",
3
- "version": "0.1.21-beta",
3
+ "version": "0.1.22-beta",
4
4
  "description": "Lexxy - A modern rich text editor for Rails.",
5
5
  "module": "dist/lexxy.esm.js",
6
6
  "type": "module",