@37signals/lexxy 0.1.4-beta → 0.1.6-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
@@ -2,7 +2,7 @@
2
2
 
3
3
  A modern rich text editor for Rails.
4
4
 
5
- > [!IMPORTANT]
5
+ > [!IMPORTANT]
6
6
  > This is an early beta. It hasn't been battle-tested yet. Please try it out and report any issues you find.
7
7
 
8
8
  ## Features
@@ -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.3.beta' # Need to specify the version since it's a pre-release
26
+ gem 'lexxy', '~> 0.1.4.beta' # Need to specify the version since it's a pre-release
27
27
  ```
28
28
 
29
29
  And then execute:
@@ -32,7 +32,7 @@ And then execute:
32
32
  bundle install
33
33
  ```
34
34
 
35
- ### Import maps
35
+ ### With import maps
36
36
 
37
37
  If you are using [propshaft](https://github.com/rails/propshaft) and [import maps](https://github.com/rails/importmap-rails):
38
38
 
@@ -46,12 +46,12 @@ Then import it in your JavaScript entry point:
46
46
 
47
47
  ```javascript
48
48
  // app/javascript/application.js
49
- import "@37signals/lexxy"
49
+ import "lexxy"
50
50
  ```
51
51
 
52
- ### JavaScript Bundling (jsbundling-rails, Vite, etc.)
52
+ ### With javascript bundlers
53
53
 
54
- If you're using [jsbundling-rails](https://github.com/rails/jsbundling-rails) with esbuild, webpack, or any other JavaScript bundler, you'll need to install the NPM package:
54
+ If you're using [jsbundling-rails](https://github.com/rails/jsbundling-rails), esbuild, webpack, or any other JavaScript bundler, you can install the NPM package:
55
55
 
56
56
  ```bash
57
57
  yarn add @37signals/lexxy
@@ -65,6 +65,20 @@ Then import it in your JavaScript entry point:
65
65
  import "@37signals/lexxy"
66
66
  ```
67
67
 
68
+ ### Override Action Text defaults
69
+
70
+ By default, the gem overrides Action Text form helpers, so that if you use `form.rich_text_area`, it will render a Lexxy editor instead of the default Trix editor.
71
+
72
+ You can opt out of this behavior by disabling this option in `application.rb`:
73
+
74
+ ```ruby# config/application.rb
75
+ config.lexxy.override_action_text_defaults = false
76
+ ```
77
+
78
+ If you do this, you can invoke Lexxy explicitly using the same helpers with a `lexxy` preffix: `lexxy_rich_textarea_tag` and `form.lexxy_rich_text_area`.
79
+
80
+ This path is meant to let you incrementally move to Lexxy, or to use it in specific places while keeping Trix in others.
81
+
68
82
  ### CSS Setup
69
83
 
70
84
  For the CSS, you can include it with the standard Rails helper:
@@ -73,6 +87,10 @@ For the CSS, you can include it with the standard Rails helper:
73
87
  <%= stylesheet_link_tag "lexxy" %>
74
88
  ```
75
89
 
90
+ Of course, you can copy the CSS to your project and adapt it to your needs.
91
+
92
+ #### Rendered Action Text content
93
+
76
94
  For applying the same styles to rendered Action Text content, you need to override the current default by adding this template `app/views/layouts/action_text/contents/_content.html.erb`:
77
95
 
78
96
  ```erb
@@ -81,10 +99,26 @@ For applying the same styles to rendered Action Text content, you need to overri
81
99
  </div>
82
100
  ```
83
101
 
84
- Of course, you can copy the CSS to your project and adapt it to your needs.
102
+ To apply syntax highlighting to rendered Action Text content, you need to call the `highlightAll` function from Lexxy. For example, create a Stimulus controller in `app/javascript/controllers/syntax_highlight_controller.js`:
85
103
 
86
- > [!NOTE]
87
- > We'll streamline the configuration process as we work towards a final release.
104
+ ```javascript
105
+ import { Controller } from "@hotwired/stimulus"
106
+ import { highlightAll } from "lexxy"
107
+
108
+ export default class extends Controller {
109
+ connect() {
110
+ highlightAll()
111
+ }
112
+ }
113
+ ```
114
+
115
+ Then update the Action Text Content template to include the `data-controller` attribute:
116
+
117
+ ```erb
118
+ <div data-controller="syntax-highlight" class="lexxy-content">
119
+ <%= yield -%>
120
+ </div>
121
+ ```
88
122
 
89
123
  ## Configuration
90
124
 
@@ -158,12 +192,16 @@ Where:
158
192
 
159
193
  Imagine you want to implement a *mentions* feature, where users can type "@" and select a person to mention. You want to save mentions as action text attachments for further server-side processing when the form is submitted.
160
194
 
161
- First, you need to include the `ActionText::Attachable` concern in your model.
195
+ First, you need to include the `ActionText::Attachable` concern in your model, and you need to define the `#content_type` method to return a value like `application/vnd.actiontext.<prompt name>`, where `<prompt name>` is the value of the `name` attribute you will set in the `<lexxy-prompt>` element later. Let's use `mention` as the prompt name:
162
196
 
163
197
  ```ruby
164
198
  # app/models/person.rb
165
199
  class Person < ApplicationRecord
166
200
  include ActionText::Attachable
201
+
202
+ def content_type
203
+ "application/vnd.actiontext.mention"
204
+ end
167
205
  end
168
206
  ```
169
207
 
@@ -219,7 +257,7 @@ We could define the controller action to serve the prompt items like this:
219
257
  class PeopleController < ApplicationController
220
258
  def index
221
259
  @people = Person.all
222
-
260
+
223
261
  render layout: false
224
262
  end
225
263
  end
@@ -305,7 +343,9 @@ The sandbox app is available at http://localhost:3000. There is also a CRUD exam
305
343
 
306
344
  ## Events
307
345
 
346
+ * `lexxy:initialize`: Fired whenever the `<lexxy-editor>` element is attached to the DOM and is ready for use.
308
347
  * `lexxy:change`: Fired whenever the editor content changes.
348
+ * `lexxy:file-accept`: Fired whenever a file is dropped or inserted into the editor. You can access the `File` object through the `file` property. Call `preventDefault` on the event to cancel upload and prevent attaching the file.
309
349
 
310
350
  ## Contributing
311
351
 
package/dist/lexxy.esm.js CHANGED
@@ -241,17 +241,18 @@ class LexicalToolbarElement extends HTMLElement {
241
241
 
242
242
  static get defaultTemplate() {
243
243
  return `
244
- <button type="button" name="bold" data-command="bold" title="Bold">
245
- <svg viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"> <path d="m4.1 23c-.5 0-.7-.4-.7-.7v-20.6c0-.4.4-.7.7-.7h8.9c2 0 3.8.6 4.9 1.5 1.2 1 1.8 2.4 1.8 4.1s-.9 3.2-2.3 4.1c-.2 0-.3.3-.3.5s0 .4.3.5c1.9.8 3.2 2.7 3.2 5s-.7 3.6-2.1 4.7-3.3 1.7-5.6 1.7h-8.8zm4.2-18.1v5.1h3c1.2 0 2-.3 2.7-.7.6-.5.9-1.1.9-1.9s-.3-1.4-.8-1.8-1.3-.6-2.3-.6-2.4 0-3.5 0zm0 8.5v5.8h3.7c1.3 0 2.2-.3 2.8-.7s.9-1.2.9-2.2-.4-1.7-1-2.1-1.7-.7-2.9-.7-2.4 0-3.5 0z" fill-rule="evenodd"/> </svg>
244
+ <button class="lexxy-editor__toolbar-button" type="button" name="bold" data-command="bold" title="Bold">
245
+ <svg viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path d="M5 22V2h8.183c1.764 0 3.174.435 4.228 1.304 1.055.87 1.582 2.076 1.582 3.62 0 .8-.148 1.503-.445 2.109a3.94 3.94 0 01-1.194 1.465 4.866 4.866 0 01-1.726.806v.176c.786.078 1.51.312 2.172.703a4.293 4.293 0 011.596 1.627c.403.693.604 1.543.604 2.549 0 1.192-.292 2.207-.877 3.048-.585.84-1.39 1.484-2.416 1.934-1.026.44-2.206.659-3.538.659H5zM8.854 4.974v5.348h2.56c.873 0 1.582-.107 2.129-.322.556-.215.963-.523 1.222-.923.269-.41.403-.904.403-1.48 0-.82-.254-1.46-.762-1.92-.499-.468-1.204-.703-2.115-.703H8.854zm0 8.103v5.949h2.877c1.534 0 2.636-.245 3.307-.733.671-.498 1.007-1.221 1.007-2.168 0-.635-.134-1.178-.403-1.627-.268-.459-.666-.81-1.193-1.055-.518-.244-1.156-.366-1.913-.366H8.854z"/></svg>
246
246
  </button>
247
247
 
248
- <button type="button" name="italic" data-command="italic" title="Italic">
249
- <svg viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path d="m18.3 1.7c-1.7.4-2.5 1.3-2.7 2.6l-2.4 15.6c-.2 1.1.5 2.1 1.6 2.4.2 0 .2.2.2.3v.7h-.1c0 .1-.2.3-.3.3h-9c-.2 0-.3-.1-.3-.3v-.7h.1c0-.1.1-.2.2-.2 1.7-.4 2.5-1.3 2.7-2.6l2.4-15.6c.2-1.1-.5-2.1-1.6-2.4-.2 0-.2-.2-.2-.3v-.7h.1c0-.1.2-.3.3-.3h9c.2 0 .3.1.3.3v.7h-.1c0 .1-.1.2-.2.2z" fill-rule="evenodd"/></svg>
248
+ <button class="lexxy-editor__toolbar-button" type="button" name="italic" data-command="italic" title="Italic">
249
+ <svg viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path d="M17.1 4h-1.5l-3.2 16h1.5l-.4 2h-7l.4-2h1.5l3.2-16h-1.5l.4-2h7l-.4 2z"/></svg>
250
250
  </button>
251
251
 
252
- <button type="button" name="link" title="Link" data-dialog-target="link-dialog" data-hotkey="cmd+k ctrl+k">
253
- <svg viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path d="m22.2 1.8c-1.1-1.2-2.5-1.8-4.3-1.8s-3.1.6-4.4 1.8l-4.4 4.4c-1.2 1.2-1.8 2.7-1.8 4.3s.6 3.1 1.8 4.3h.2c.2.3.5.4 1 .4.7 0 1.1-.3 1.2-.6.3-.3.4-.7.4-1.1s-.2-.8-.5-1.2c-.5-.5-.8-1.2-.8-1.9s.3-1.4.8-1.9l4.4-4.2c.6-.5 1.5-.8 2.1-.8s1.4.2 1.9.7.8 1.2.8 1.9-.3 1.4-.8 1.9l-2.6 2.2c-.3.3-.5.7-.5 1.2s.2.8.5 1.2c.8.6 1.7.6 2.4 0l2.6-2.2c2.4-2.4 2.4-6.1 0-8.5z"/><path d="m12.3 9.3c-.3.3-.5.8-.5 1.3 0 .4.2.8.5 1.1.5.5.8 1.2.8 1.9s-.3 1.4-.9 1.9l-4.4 4.4c-.4.4-1.2.7-1.8.7s-1.4-.2-1.9-.7-.8-1.2-.8-1.9.3-1.4.8-1.9l2.5-2.4c.7-.7.7-1.7 0-2.4-.8-.6-1.7-.6-2.4 0l-2.5 2.4c-1 1.1-1.7 2.6-1.7 4.2s.6 3.1 1.8 4.3c1.3 1.2 2.7 1.8 4.2 1.8s3.2-.7 4.3-1.8l4.4-4.4c2.4-2.4 2.4-6.1 0-8.6-.8-.6-1.7-.6-2.4 0z"/></svg>
252
+ <button class="lexxy-editor__toolbar-button" type="button" name="link" title="Link" data-dialog-target="link-dialog" data-hotkey="cmd+k ctrl+k">
253
+ <svg viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path d="M12.111 9.546a1.5 1.5 0 012.121 0 5.5 5.5 0 010 7.778l-2.828 2.828a5.5 5.5 0 01-7.778 0 5.498 5.498 0 010-7.777l2.828-2.83a1.5 1.5 0 01.355-.262 6.52 6.52 0 00.351 3.799l-1.413 1.414a2.499 2.499 0 000 3.535 2.499 2.499 0 003.535 0l2.83-2.828a2.5 2.5 0 000-3.536 1.5 1.5 0 010-2.121z"/><path d="M12.111 3.89a5.5 5.5 0 117.778 7.777l-2.828 2.829a1.496 1.496 0 01-.355.262 6.522 6.522 0 00-.351-3.8l1.413-1.412a2.5 2.5 0 10-3.536-3.535l-2.828 2.828a2.5 2.5 0 000 3.536 1.5 1.5 0 01-2.122 2.12 5.5 5.5 0 010-7.777l2.83-2.829z"/></svg>
254
254
  </button>
255
+
255
256
  <lexxy-link-dialog class="lexxy-link-dialog">
256
257
  <dialog id="link-dialog" closedby="any">
257
258
  <form method="dialog">
@@ -264,32 +265,32 @@ class LexicalToolbarElement extends HTMLElement {
264
265
  </dialog>
265
266
  </lexxy-link-dialog>
266
267
 
267
- <button type="button" name="quote" data-command="insertQuoteBlock" title="Quote">
268
- <svg viewBox="0 0 24 22" xmlns="http://www.w3.org/2000/svg"> <path d="m1.1 5.2c.6-.7 1.4-1.3 2.4-1.4 2.6-.4 4.2.4 5.3 1.9 2 2.3 1.9 5.1.6 7.6-1.3 2.4-4 4.6-7.2 5.1-.4 0-.7-.1-1-.4-.1-.3-.1-.7.3-1.1l1.1-1.1c.3-.4.6-.7.7-1.1s.3-.9 0-1.3c0-.4-.6-.7-1-1-1.2-.8-2.3-2.2-2.3-4.1.1-1.4.4-2.4 1.1-3.1z"/> <path d="m14.6 5.2c.6-.7 1.6-1.1 2.6-1.4 2.4-.4 4.2.4 5.3 1.9 2 2.3 1.9 5.1.6 7.6-1.3 2.4-4 4.6-7.2 5.1-.4 0-.7-.1-1-.4-.1-.3-.1-.7.3-1.1l1.1-1.1c.3-.4.6-.7.7-1.1s.3-.9 0-1.3c-.1-.4-.6-.7-1-1-1.3-.6-2.4-2-2.4-3.9s.4-2.6 1-3.3z"/> </svg>
268
+ <button class="lexxy-editor__toolbar-button" type="button" name="quote" data-command="insertQuoteBlock" title="Quote">
269
+ <svg viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path d="M6.5 5C8.985 5 11 7.09 11 9.667c0 2.694-.962 5.005-2.187 6.644-.613.82-1.3 1.481-1.978 1.943-.668.454-1.375.746-2.022.746a.563.563 0 01-.52-.36.602.602 0 01.067-.57l.055-.066.009-.009.041-.048a4.25 4.25 0 00.168-.21c.143-.188.336-.47.53-.84a6.743 6.743 0 00.75-2.605C3.705 13.994 2 12.038 2 9.667 2 7.089 4.015 5 6.5 5zM17.5 5C19.985 5 22 7.09 22 9.667c0 2.694-.962 5.005-2.187 6.644-.613.82-1.3 1.481-1.978 1.943-.668.454-1.375.746-2.023.746a.563.563 0 01-.52-.36.602.602 0 01.068-.57l.055-.066.009-.009.041-.048c.039-.045.097-.115.168-.21a6.16 6.16 0 00.53-.84 6.745 6.745 0 00.75-2.605C14.705 13.994 13 12.038 13 9.667 13 7.089 15.015 5 17.5 5z"/></svg>
269
270
  </button>
270
271
 
271
- <button type="button" name="heading" data-command="rotateHeadingFormat" title="Heading">
272
- <svg viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path d="m5.7 6.2v16.3h3.8v-16.3h5.7v-3.8h-15.2v3.8zm10.1 16.4h3.8v-8.8h4.4v-3.8h-12.6v3.8h4.4z" fill-rule="evenodd"/></svg>
272
+ <button class="lexxy-editor__toolbar-button" type="button" name="heading" data-command="rotateHeadingFormat" title="Heading">
273
+ <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>
273
274
  </button>
274
275
 
275
- <button type="button" name="code" data-command="insertCodeBlock" title="Code">
276
- <svg viewBox="0 0 24 22" xmlns="http://www.w3.org/2000/svg"><path d="m8 4.7c-.6-.6-1.6-.6-2.4 0l-5.1 5.2c-.3.3-.5.7-.5 1.2s.2.9.5 1.2l5.1 5.1c.3.3.7.5 1.2.5s.9-.2 1.2-.5c.6-.6.6-1.7 0-2.4l-4-4 4-4c.6-.6.6-1.7 0-2.4z"/><path d="m23.5 9.9-5.1-5.1c-.6-.6-1.8-.6-2.4 0-.3.3-.5.7-.5 1.2s.2.9.5 1.2l4 4-4 4c-.3.3-.5.7-.5 1.2s.2.9.5 1.2c.3.2.7.4 1.1.4s.9-.2 1.2-.5l5.1-5.1c.3-.3.5-.7.5-1.2s-.2-.9-.5-1.2z"/></svg>
276
+ <button class="lexxy-editor__toolbar-button" type="button" name="code" data-command="insertCodeBlock" title="Code">
277
+ <svg viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path d="M10.121 6l-6 6 6 6-2.12 2.121-7.061-7.06a1.5 1.5 0 010-2.121L8 3.879 10.121 6zM23.06 10.94a1.5 1.5 0 010 2.12L16 20.121 13.88 18l6-6-6-6L16 3.879l7.06 7.06z"/></svg>
277
278
  </button>
278
279
 
279
- <button type="button" name="unordered-list" data-command="insertUnorderedList" title="Bullet list">
280
- <svg viewBox="0 0 24 22" xmlns="http://www.w3.org/2000/svg"> <path d="m2.1 4.8c1.1 0 2.1-.9 2.1-2.1s-1-2-2.1-2-2.1.9-2.1 2.1.9 2 2.1 2zm4.1-2c0-.8.6-1.4 1.4-1.4h15.1c.7 0 1.3.6 1.3 1.4s-.6 1.4-1.4 1.4h-15.1c-.7 0-1.3-.7-1.3-1.4zm1.3 6.8c-.8 0-1.4.6-1.4 1.4s.6 1.4 1.4 1.4h15.1c.8 0 1.4-.6 1.4-1.4s-.6-1.4-1.4-1.4zm0 8.3c-.8 0-1.4.6-1.4 1.4s.6 1.4 1.4 1.4h15.1c.8 0 1.4-.6 1.4-1.4s-.6-1.4-1.4-1.4zm-3.4-6.9c0 1.1-.9 2.1-2.1 2.1s-2-1-2-2.1.9-2.1 2.1-2.1 2 1 2 2.1zm-2 10.3c1.1 0 2.1-.9 2.1-2.1s-.9-2.1-2.1-2.1-2.1 1-2.1 2.1.9 2.1 2.1 2.1z" fill-rule="evenodd"/> </svg>
280
+ <button class="lexxy-editor__toolbar-button" type="button" name="unordered-list" data-command="insertUnorderedList" title="Bullet list">
281
+ <svg viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path d="M5 5a2 2 0 11-4 0 2 2 0 014 0zM5 12a2 2 0 11-4 0 2 2 0 014 0zM5 19a2 2 0 11-4 0 2 2 0 014 0zM7 5.25C7 4.56 7.56 4 8.25 4h13.5a1.25 1.25 0 110 2.5H8.25C7.56 6.5 7 5.94 7 5.25zM7 12.25c0-.69.56-1.25 1.25-1.25h13.5a1.25 1.25 0 110 2.5H8.25c-.69 0-1.25-.56-1.25-1.25zM7 19.25c0-.69.56-1.25 1.25-1.25h13.5a1.25 1.25 0 110 2.5H8.25c-.69 0-1.25-.56-1.25-1.25z"/></svg>
281
282
  </button>
282
283
 
283
- <button type="button" name="ordered-list" data-command="insertOrderedList" title="Numbered list">
284
- <svg viewBox="0 0 24 22" xmlns="http://www.w3.org/2000/svg"><path d="m6.7 3c0-.7.6-1.3 1.3-1.3h14.7c.7 0 1.3.6 1.3 1.3s-.6 1.3-1.3 1.3h-14.7c-.7 0-1.3-.6-1.3-1.3zm1.3 6.7c-.7 0-1.3.6-1.3 1.3s.6 1.3 1.3 1.3h14.7c.7 0 1.3-.6 1.3-1.3s-.6-1.3-1.3-1.3zm0 8c-.7 0-1.3.6-1.3 1.3s.6 1.3 1.3 1.3h14.7c.7 0 1.3-.6 1.3-1.3s-.6-1.3-1.3-1.3z" fill-rule="evenodd"/><path d="m1.5 19.6v-.9h.5c.4 0 .8-.3.8-.7s-.3-.7-.8-.7-.8.3-.8.7h-1.2c0-.9.8-1.6 2-1.6s2 .5 2 1.5-.4 1.1-1.1 1.2c.7 0 1.2.7 1.2 1.3 0 1.1-1.1 1.6-2.1 1.6s-2-.8-2-1.6h1.2c0 .4.4.7.8.7s.8-.3.8-.7-.3-.7-.8-.7h-.7.1zm0-9.7h-1.2c0-.9.7-1.7 2-1.7s2 .7 2 1.6-.5 1.2-.9 1.7l-1.1 1.2h2.1v1.1h-3.9v-.8l2-2c.3-.3.5-.7.5-1.1s-.3-.7-.7-.7-.7.3-.7.7h-.3zm1.7-4.4h-1.3v-4.3l-1.2.8v-1.2l1.3-.8h1.3v5.5z"/></svg>
284
+ <button class="lexxy-editor__toolbar-button" type="button" name="ordered-list" data-command="insertOrderedList" title="Numbered list">
285
+ <svg viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path d="M7 5.25C7 4.56 7.56 4 8.25 4h13.5a1.25 1.25 0 110 2.5H8.25C7.56 6.5 7 5.94 7 5.25zM7 12.25c0-.69.56-1.25 1.25-1.25h13.5a1.25 1.25 0 110 2.5H8.25c-.69 0-1.25-.56-1.25-1.25zM7 19.25c0-.69.56-1.25 1.25-1.25h13.5a1.25 1.25 0 110 2.5H8.25c-.69 0-1.25-.56-1.25-1.25zM4.438 8H3.39V3.684H3.34c-.133.093-.267.188-.402.285l-.407.289a129.5 129.5 0 00-.402.285v-.969l.633-.453c.21-.15.42-.302.629-.453h1.046V8zM2.672 11.258h-1v-.051c0-.206.036-.405.11-.598.075-.195.188-.37.34-.527.15-.156.339-.281.566-.375.229-.094.498-.14.808-.14.367 0 .688.065.961.195s.484.308.633.535c.15.224.226.478.226.762 0 .244-.046.463-.14.656-.091.19-.209.368-.352.535-.14.164-.289.332-.445.504L3.168 14.09v.05h2.238V15H1.723v-.656l1.949-2.102c.096-.101.19-.207.281-.316.091-.112.167-.232.227-.36a.953.953 0 00.09-.41.712.712 0 00-.387-.648.845.845 0 00-.41-.098.81.81 0 00-.43.11.75.75 0 00-.277.293.824.824 0 00-.094.386V11.258zM2.852 19.66v-.812h.562a.917.917 0 00.43-.098.742.742 0 00.293-.266.673.673 0 00.101-.379.654.654 0 00-.234-.523.87.87 0 00-.59-.2.987.987 0 00-.336.055.837.837 0 00-.258.149.712.712 0 00-.172.215.66.66 0 00-.066.25h-.98c.007-.209.053-.403.136-.582.084-.18.203-.336.36-.469.156-.135.346-.24.57-.316.227-.076.486-.115.777-.118a2.33 2.33 0 01.965.176c.271.12.48.285.63.496.15.209.227.448.23.719a1.11 1.11 0 01-.16.637 1.28 1.28 0 01-.825.586v.054c.162.016.33.07.504.164.177.094.328.232.453.415.125.18.189.411.192.695a1.37 1.37 0 01-.157.676c-.104.197-.25.365-.437.503-.188.136-.404.24-.649.313-.242.07-.5.105-.777.105-.401 0-.743-.067-1.027-.203a1.608 1.608 0 01-.649-.547 1.46 1.46 0 01-.238-.75h.969c.01.128.057.243.14.344a.885.885 0 00.332.238c.141.058.3.088.477.09.195 0 .366-.034.512-.101a.798.798 0 00.336-.29.744.744 0 00.117-.425.74.74 0 00-.446-.695 1.082 1.082 0 00-.496-.106h-.59z"/></svg>
285
286
  </button>
286
287
 
287
- <button type="button" name="upload" data-command="uploadAttachments" title="Upload file">
288
- <svg viewBox="0 0 24 20" xmlns="http://www.w3.org/2000/svg"> <path d="m22 20h-20c-1.1 0-2-.9-2-2.1v-15.8c0-1.2.9-2.1 2-2.1h20c1.1 0 2 .9 2 2.1v15.8c0 1.1-.9 2.1-2 2.1zm0-2.9v-14.5c0-.3-.2-.5-.5-.5h-19c-.3 0-.5.2-.5.5v14.5c0 .1.1.2.2.2s.2 0 .2-.1l2.2-3.3c.1-.2.3-.3.5-.3h.7l2.6-4c.1-.2.3-.3.5-.3h.7c.2 0 .4.1.5.3l5.3 8c0 .1.2.2.3.2h.3c.2 0 .4-.2.4-.4s0-.2 0-.2l-1.3-1.9c-.2-.2-.2-.6 0-.8l1.2-1.6c.1-.2.3-.3.5-.3h1.1c.2 0 .4 0 .5.3l3.2 4.4c0 .1.3.2.4 0 .2 0 .2 0 .2-.2zm-5.5-7.6c-1.4 0-2.5-1.2-2.5-2.6s1.1-2.6 2.5-2.6 2.5 1.2 2.5 2.6-1.1 2.6-2.5 2.6z" fill-rule="evenodd"/> </svg>
288
+ <button class="lexxy-editor__toolbar-button" type="button" name="upload" data-command="uploadAttachments" title="Upload file">
289
+ <svg viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path d="M16 8a2 2 0 110 4 2 2 0 010-4z""/><path d="M22 2a1 1 0 011 1v18a1 1 0 01-1 1H2a1 1 0 01-1-1V3a1 1 0 011-1h20zM3 18.714L9 11l5.25 6.75L17 15l4 4V4H3v14.714z"/></svg>
289
290
  </button>
290
291
 
291
292
  <details class="lexxy-editor__toolbar-overflow">
292
- <summary aria-label="Show more toolbar buttons">•••</summary>
293
+ <summary class="lexxy-editor__toolbar-button" aria-label="Show more toolbar buttons">•••</summary>
293
294
  <div class="lexxy-editor__toolbar-overflow-menu" aria-label="More toolbar buttons"></div>
294
295
  </details>
295
296
  `
@@ -597,7 +598,7 @@ class ActionTextAttachmentNode extends DecoratorNode {
597
598
  }
598
599
 
599
600
  #select(figure) {
600
- dispatchCustomEvent(figure, "lexxy:node-selected", { key: this.getKey() });
601
+ dispatchCustomEvent(figure, "lexxy:internal:select-node", { key: this.getKey() });
601
602
  }
602
603
 
603
604
  #createEditableCaption() {
@@ -629,13 +630,13 @@ class ActionTextAttachmentNode extends DecoratorNode {
629
630
  }
630
631
 
631
632
  #updateCaptionValueFromInput(input) {
632
- dispatchCustomEvent(input, "lexxy:node-invalidated", { key: this.getKey(), values: { caption: input.value } });
633
+ dispatchCustomEvent(input, "lexxy:internal:invalidate-node", { key: this.getKey(), values: { caption: input.value } });
633
634
  }
634
635
 
635
636
  #handleCaptionInputKeydown(event) {
636
637
  if (event.key === "Enter") {
637
638
  this.#updateCaptionValueFromInput(event.target);
638
- dispatchCustomEvent(event.target, "lexxy:move-to-next-line");
639
+ dispatchCustomEvent(event.target, "lexxy:internal:move-to-next-line");
639
640
  event.preventDefault();
640
641
  }
641
642
  event.stopPropagation();
@@ -757,10 +758,6 @@ class ActionTextAttachmentUploadNode extends ActionTextAttachmentNode {
757
758
  if (error) {
758
759
  this.#handleUploadError(figure);
759
760
  } else {
760
- this.src = this.blobUrlTemplate
761
- .replace(":signed_id", blob.signed_id)
762
- .replace(":filename", encodeURIComponent(blob.filename));
763
-
764
761
  this.#loadFigurePreviewFromBlob(blob, figure).then(() => {
765
762
  this.#showUploadedAttachment(figure, blob);
766
763
  });
@@ -778,11 +775,14 @@ class ActionTextAttachmentUploadNode extends ActionTextAttachmentNode {
778
775
  this.editor.update(() => {
779
776
  const image = figure.querySelector("img");
780
777
 
778
+ const src = this.blobUrlTemplate
779
+ .replace(":signed_id", blob.signed_id)
780
+ .replace(":filename", encodeURIComponent(blob.filename));
781
781
  const latest = $getNodeByKey(this.getKey());
782
782
  if (latest) {
783
783
  latest.replace(new ActionTextAttachmentNode({
784
784
  sgid: blob.attachable_sgid,
785
- src: blob.previewable ? blob.url : this.src,
785
+ src: blob.previewable ? blob.url : src,
786
786
  altText: blob.filename,
787
787
  contentType: blob.content_type,
788
788
  fileName: blob.filename,
@@ -1193,7 +1193,7 @@ class Selection {
1193
1193
  }
1194
1194
 
1195
1195
  #listenForNodeSelections() {
1196
- this.editor.getRootElement().addEventListener("lexxy:node-selected", async (event) => {
1196
+ this.editor.getRootElement().addEventListener("lexxy:internal:select-node", async (event) => {
1197
1197
  await nextFrame();
1198
1198
 
1199
1199
  const { key } = event.detail;
@@ -1208,7 +1208,7 @@ class Selection {
1208
1208
  });
1209
1209
  });
1210
1210
 
1211
- this.editor.getRootElement().addEventListener("lexxy:move-to-next-line", (event) => {
1211
+ this.editor.getRootElement().addEventListener("lexxy:internal:move-to-next-line", (event) => {
1212
1212
  this.#selectOrAppendNextLine();
1213
1213
  });
1214
1214
  }
@@ -1282,7 +1282,7 @@ class Selection {
1282
1282
  if ($isNodeSelection(selection)) {
1283
1283
  return this.#getTopLevelFromNodeSelection(selection)
1284
1284
  }
1285
-
1285
+
1286
1286
  if ($isRangeSelection(selection)) {
1287
1287
  return this.#getTopLevelFromRangeSelection(selection)
1288
1288
  }
@@ -1361,7 +1361,7 @@ class Selection {
1361
1361
 
1362
1362
  #getReliableRectFromRange(range) {
1363
1363
  let rect = range.getBoundingClientRect();
1364
-
1364
+
1365
1365
  if (this.#isRectUnreliable(rect)) {
1366
1366
  const marker = this.#createAndInsertMarker(range);
1367
1367
  rect = marker.getBoundingClientRect();
@@ -1418,12 +1418,12 @@ class Selection {
1418
1418
  const nativeSelection = window.getSelection();
1419
1419
  const anchorNode = nativeSelection.anchorNode;
1420
1420
  const parentElement = this.#getElementFromNode(anchorNode);
1421
-
1421
+
1422
1422
  if (parentElement instanceof HTMLElement) {
1423
1423
  const computed = window.getComputedStyle(parentElement);
1424
1424
  return parseFloat(computed.fontSize)
1425
1425
  }
1426
-
1426
+
1427
1427
  return 0
1428
1428
  }
1429
1429
 
@@ -1703,6 +1703,10 @@ class Contents {
1703
1703
  return
1704
1704
  }
1705
1705
 
1706
+ if (!this.#shouldUploadFile(file)) {
1707
+ return
1708
+ }
1709
+
1706
1710
  const uploadUrl = this.editorElement.directUploadUrl;
1707
1711
  const blobUrlTemplate = this.editorElement.blobUrlTemplate;
1708
1712
 
@@ -1991,6 +1995,10 @@ class Contents {
1991
1995
  }
1992
1996
  }
1993
1997
  }
1998
+
1999
+ #shouldUploadFile(file) {
2000
+ return dispatch(this.editorElement, 'lexxy:file-accept', { file }, true)
2001
+ }
1994
2002
  }
1995
2003
 
1996
2004
  function isUrl(string) {
@@ -2139,7 +2147,7 @@ class CustomActionTextAttachmentNode extends DecoratorNode {
2139
2147
  const figure = createElement("action-text-attachment", { "content-type": this.contentType, "data-lexxy-decorator": true });
2140
2148
 
2141
2149
  figure.addEventListener("click", (event) => {
2142
- dispatchCustomEvent(figure, "lexxy:node-selected", { key: this.getKey() });
2150
+ dispatchCustomEvent(figure, "lexxy:internal:select-node", { key: this.getKey() });
2143
2151
  });
2144
2152
 
2145
2153
  figure.insertAdjacentHTML("beforeend", this.innerHtml);
@@ -2202,6 +2210,8 @@ class LexicalEditorElement extends HTMLElement {
2202
2210
 
2203
2211
  CommandDispatcher.configureFor(this);
2204
2212
  this.#initialize();
2213
+
2214
+ requestAnimationFrame(() => dispatch(this, "lexxy:initialize"));
2205
2215
  this.toggleAttribute("connected", true);
2206
2216
 
2207
2217
  this.valueBeforeDisconnect = null;
@@ -2434,7 +2444,7 @@ class LexicalEditorElement extends HTMLElement {
2434
2444
  }
2435
2445
 
2436
2446
  #listenForInvalidatedNodes() {
2437
- this.editor.getRootElement().addEventListener("lexxy:node-invalidated", (event) => {
2447
+ this.editor.getRootElement().addEventListener("lexxy:internal:invalidate-node", (event) => {
2438
2448
  const { key, values } = event.detail;
2439
2449
 
2440
2450
  this.editor.update(() => {
@@ -3212,4 +3222,9 @@ function highlightElement(preElement) {
3212
3222
  preElement.replaceWith(codeElement);
3213
3223
  }
3214
3224
 
3225
+ // Manual highlighting mode to prevent invocation on every page. See https://prismjs.com/docs/prism
3226
+ // This must happen before importing any Prism components
3227
+ window.Prism = window.Prism || {};
3228
+ Prism.manual = true;
3229
+
3215
3230
  export { highlightAll };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@37signals/lexxy",
3
- "version": "0.1.4-beta",
3
+ "version": "0.1.6-beta",
4
4
  "description": "Lexxy - A modern rich text editor for Rails.",
5
5
  "module": "dist/lexxy.esm.js",
6
6
  "type": "module",