lexxy 0.1.3.beta → 0.1.5.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.
- checksums.yaml +4 -4
- data/README.md +93 -25
- data/app/assets/javascript/lexxy.js +135 -45
- data/app/assets/javascript/lexxy.js.br +0 -0
- data/app/assets/javascript/lexxy.js.gz +0 -0
- data/app/assets/javascript/lexxy.min.js +3 -3
- data/app/assets/javascript/lexxy.min.js.br +0 -0
- data/app/assets/javascript/lexxy.min.js.gz +0 -0
- data/app/assets/stylesheets/lexxy-content.css +1 -1
- data/app/assets/stylesheets/lexxy-editor.css +53 -24
- data/lib/lexxy/action_text_tag.rb +1 -1
- data/lib/lexxy/engine.rb +5 -0
- data/lib/lexxy/form_builder.rb +3 -3
- data/lib/lexxy/form_helper.rb +2 -2
- data/lib/lexxy/rich_text_area_tag.rb +2 -2
- data/lib/lexxy/version.rb +1 -1
- data/lib/lexxy.rb +16 -1
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: d97469124d8bd00ee856e66f48d8d8e7899188baf9d25b1692e72a1387bb5434
|
4
|
+
data.tar.gz: 0abfd04d0c32c8d4a6ee07d86deef71529370e9ab19755f64745d239e7d89002
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 1331cfd0fd69bb5cbd89e23b398fea9bde0540fbf23e5cfe41a2bd3c40f451fa12d0d680606d50f1d881496bf8011a7e6008636cbffab91a161a6e59b86dcb81
|
7
|
+
data.tar.gz: 215fccc8ae0711d1a644c1f34b21f4c96ec642405ef3947e51f731b921d50c755783c9b1ffe0e903ce41c09c687db0aa83fb8209a51fafe73ba8bd7778428e0f
|
data/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'
|
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,24 +32,65 @@ And then execute:
|
|
32
32
|
bundle install
|
33
33
|
```
|
34
34
|
|
35
|
-
|
35
|
+
### With import maps
|
36
|
+
|
37
|
+
If you are using [propshaft](https://github.com/rails/propshaft) and [import maps](https://github.com/rails/importmap-rails):
|
36
38
|
|
37
39
|
```ruby
|
38
40
|
# importmap.rb
|
39
41
|
pin "lexxy", to: "lexxy.js"
|
42
|
+
pin "@rails/activestorage", to: "activestorage.esm.js" # to support attachments
|
40
43
|
```
|
41
44
|
|
45
|
+
Then import it in your JavaScript entry point:
|
46
|
+
|
42
47
|
```javascript
|
43
|
-
// application.js
|
48
|
+
// app/javascript/application.js
|
44
49
|
import "lexxy"
|
45
50
|
```
|
46
51
|
|
52
|
+
### With javascript bundlers
|
53
|
+
|
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
|
+
|
56
|
+
```bash
|
57
|
+
yarn add @37signals/lexxy
|
58
|
+
yarn add @rails/activestorage # to support attachments
|
59
|
+
```
|
60
|
+
|
61
|
+
Then import it in your JavaScript entry point:
|
62
|
+
|
63
|
+
```javascript
|
64
|
+
// app/javascript/application.js
|
65
|
+
import "@37signals/lexxy"
|
66
|
+
```
|
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
|
+
|
82
|
+
### CSS Setup
|
83
|
+
|
47
84
|
For the CSS, you can include it with the standard Rails helper:
|
48
85
|
|
49
86
|
```erb
|
50
87
|
<%= stylesheet_link_tag "lexxy" %>
|
51
88
|
```
|
52
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
|
+
|
53
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`:
|
54
95
|
|
55
96
|
```erb
|
@@ -58,10 +99,26 @@ For applying the same styles to rendered Action Text content, you need to overri
|
|
58
99
|
</div>
|
59
100
|
```
|
60
101
|
|
61
|
-
|
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`:
|
62
103
|
|
63
|
-
|
64
|
-
|
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
|
+
```
|
65
122
|
|
66
123
|
## Configuration
|
67
124
|
|
@@ -83,9 +140,9 @@ Under the hood, this will insert a `<lexxy-editor>` tag, that will be a first-cl
|
|
83
140
|
|
84
141
|
The `<lexxy-editor>` element supports these options:
|
85
142
|
|
86
|
-
- `placeholder
|
87
|
-
- `toolbar
|
88
|
-
- `attachments
|
143
|
+
- `placeholder`: Text displayed when the editor is empty.
|
144
|
+
- `toolbar`: Pass `"false"` to disable the toolbar entirely, or pass an element ID to render the toolbar in an external element. By default, the toolbar is bootstrapped and displayed above the editor.
|
145
|
+
- `attachments`: Pass `"false"` to disable attachments completely. By default, attachments are supported, including paste and Drag & Drop support.
|
89
146
|
|
90
147
|
Lexxy uses the `ElementInternals` API to participate in HTML forms as any standard control. This means that you can use standard HTML attributes like `name`, `value`, `required`, `disabled`, etc.
|
91
148
|
|
@@ -103,7 +160,7 @@ Lexxy also lets you configure how to load the items: inline or remotely, and how
|
|
103
160
|
The first thing to do is to add a `<lexxy-prompt>` element to the editor:
|
104
161
|
|
105
162
|
```erb
|
106
|
-
<%=
|
163
|
+
<%= form.rich_text_area :body do %>
|
107
164
|
<lexxy-prompt trigger="@">
|
108
165
|
</lexxy-prompt>
|
109
166
|
<% end %>
|
@@ -135,12 +192,16 @@ Where:
|
|
135
192
|
|
136
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.
|
137
194
|
|
138
|
-
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:
|
139
196
|
|
140
197
|
```ruby
|
141
198
|
# app/models/person.rb
|
142
199
|
class Person < ApplicationRecord
|
143
200
|
include ActionText::Attachable
|
201
|
+
|
202
|
+
def content_type
|
203
|
+
"application/vnd.actiontext.mention"
|
204
|
+
end
|
144
205
|
end
|
145
206
|
```
|
146
207
|
|
@@ -196,7 +257,7 @@ We could define the controller action to serve the prompt items like this:
|
|
196
257
|
class PeopleController < ApplicationController
|
197
258
|
def index
|
198
259
|
@people = Person.all
|
199
|
-
|
260
|
+
|
200
261
|
render layout: false
|
201
262
|
end
|
202
263
|
end
|
@@ -238,30 +299,31 @@ By default, the `SPACE` key will select the current item in the prompt. If you w
|
|
238
299
|
|
239
300
|
#### `<lexxy-prompt>`
|
240
301
|
|
241
|
-
- `trigger
|
242
|
-
- `src
|
243
|
-
- `name
|
244
|
-
- `empty-results
|
245
|
-
- `remote-filtering
|
246
|
-
- `insert-editable-text
|
247
|
-
- `supports-space-in-searches
|
302
|
+
- `trigger`: The character that activates the prompt (e.g., "@", "#", "/").
|
303
|
+
- `src`: Path or URL to load items remotely.
|
304
|
+
- `name`: Identifier for the prompt type (determines attachment content type, e.g., `name= "mention"` creates `application/vnd.actiontext.mention`). Mandatory unless using `insert-editable-text`.
|
305
|
+
- `empty-results`: Message shown when no matches found. By default it is "Nothing found".
|
306
|
+
- `remote-filtering`: Enable server-side filtering instead of loading all options at once.
|
307
|
+
- `insert-editable-text`: Insert prompt item HTML directly as editable text instead of Action Text attachments.
|
308
|
+
- `supports-space-in-searches`: Allow spaces in search queries (useful with remote filtering for full name searches).
|
248
309
|
|
249
310
|
#### `<lexxy-prompt-item>`
|
250
311
|
|
251
|
-
- `search
|
252
|
-
- `sgid
|
312
|
+
- `search`: The text to match against when filtering (can include multiple fields for better search).
|
313
|
+
- `sgid`: The signed GlobalID for Action Text attachments (use `attachable_sgid` helper). Mandatory unless using `insert-editable-text`.
|
253
314
|
|
254
315
|
## Roadmap
|
255
316
|
|
256
317
|
This is an early beta. Here's what's coming next:
|
257
318
|
|
258
|
-
- Configurable editors in Action Text
|
319
|
+
- Configurable editors in Action Text: Choose your editor like you choose your database.
|
259
320
|
- More editing features:
|
260
321
|
- Tables
|
261
322
|
- Text highlighting
|
262
|
-
- Image galleries
|
323
|
+
- Image galleries: The only remaining feature for full Action Text compatibility
|
263
324
|
- Install task that generates the necessary JS and adds stylesheets.
|
264
|
-
-
|
325
|
+
- Configuration hooks.
|
326
|
+
- Standalone JS package: to use in non-Rails environments.
|
265
327
|
|
266
328
|
## Development
|
267
329
|
|
@@ -279,6 +341,12 @@ bin/rails server
|
|
279
341
|
|
280
342
|
The sandbox app is available at http://localhost:3000. There is also a CRUD example at http://localhost:3000/posts.
|
281
343
|
|
344
|
+
## Events
|
345
|
+
|
346
|
+
* `lexxy:initialize`: Fired whenever the `<lexxy-editor>` element is attached to the DOM and is ready for use.
|
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.
|
349
|
+
|
282
350
|
## Contributing
|
283
351
|
|
284
352
|
- Bug reports and pull requests are welcome on [GitHub Issues](https://github.com/basecamp/lexxy/issues). Help is especially welcome with [those tagged as "Help Wanted"](https://github.com/basecamp/lexxy/issues?q=is%3Aissue%20state%3Aopen%20label%3A%22help%20wanted%22).
|
@@ -3746,6 +3746,21 @@ function getListType(node) {
|
|
3746
3746
|
}
|
3747
3747
|
|
3748
3748
|
class LexicalToolbarElement extends HTMLElement {
|
3749
|
+
constructor() {
|
3750
|
+
super();
|
3751
|
+
this.internals = this.attachInternals();
|
3752
|
+
this.internals.role = "toolbar";
|
3753
|
+
}
|
3754
|
+
|
3755
|
+
connectedCallback() {
|
3756
|
+
this.#refreshToolbarOverflow();
|
3757
|
+
window.addEventListener("resize", this.#refreshToolbarOverflow);
|
3758
|
+
}
|
3759
|
+
|
3760
|
+
disconnectedCallback() {
|
3761
|
+
window.removeEventListener("resize", this.#refreshToolbarOverflow);
|
3762
|
+
}
|
3763
|
+
|
3749
3764
|
setEditor(editorElement) {
|
3750
3765
|
this.editorElement = editorElement;
|
3751
3766
|
this.editor = editorElement.editor;
|
@@ -3810,13 +3825,12 @@ class LexicalToolbarElement extends HTMLElement {
|
|
3810
3825
|
event.shiftKey ? 'shift' : null,
|
3811
3826
|
].filter(Boolean);
|
3812
3827
|
|
3813
|
-
return [...modifiers, pressedKey].join('+')
|
3828
|
+
return [ ...modifiers, pressedKey ].join('+')
|
3814
3829
|
}
|
3815
3830
|
|
3816
3831
|
#assignButtonTabindex() {
|
3817
3832
|
const baseTabIndex = parseInt(this.editorElement.editorContentElement.getAttribute("tabindex") ?? "0");
|
3818
|
-
|
3819
|
-
buttons.forEach((button, index) => {
|
3833
|
+
this.#buttons.forEach((button, index) => {
|
3820
3834
|
button.setAttribute("tabindex", `${baseTabIndex + index + 1}`);
|
3821
3835
|
});
|
3822
3836
|
}
|
@@ -3895,19 +3909,64 @@ class LexicalToolbarElement extends HTMLElement {
|
|
3895
3909
|
}
|
3896
3910
|
}
|
3897
3911
|
|
3912
|
+
#toolbarIsOverflowing() {
|
3913
|
+
return this.scrollWidth > this.clientWidth
|
3914
|
+
}
|
3915
|
+
|
3916
|
+
#refreshToolbarOverflow = () => {
|
3917
|
+
this.#resetToolbar();
|
3918
|
+
this.#compactMenu();
|
3919
|
+
|
3920
|
+
this.#overflow.style.display = this.#overflowMenu.children.length ? "block" : "none";
|
3921
|
+
}
|
3922
|
+
|
3923
|
+
get #overflow() {
|
3924
|
+
return this.querySelector(".lexxy-editor__toolbar-overflow")
|
3925
|
+
}
|
3926
|
+
|
3927
|
+
get #overflowMenu() {
|
3928
|
+
return this.querySelector(".lexxy-editor__toolbar-overflow-menu")
|
3929
|
+
}
|
3930
|
+
|
3931
|
+
#resetToolbar() {
|
3932
|
+
while (this.#overflowMenu.children.length > 0) {
|
3933
|
+
this.insertBefore(this.#overflowMenu.children[0], this.#overflow);
|
3934
|
+
}
|
3935
|
+
}
|
3936
|
+
|
3937
|
+
#compactMenu() {
|
3938
|
+
const buttons = this.#buttons.reverse();
|
3939
|
+
let movedToOverflow = false;
|
3940
|
+
|
3941
|
+
for (const button of buttons) {
|
3942
|
+
if (this.#toolbarIsOverflowing()) {
|
3943
|
+
this.#overflowMenu.appendChild(button);
|
3944
|
+
movedToOverflow = true;
|
3945
|
+
} else {
|
3946
|
+
if (movedToOverflow) this.#overflowMenu.appendChild(button);
|
3947
|
+
break
|
3948
|
+
}
|
3949
|
+
}
|
3950
|
+
}
|
3951
|
+
|
3952
|
+
get #buttons() {
|
3953
|
+
return Array.from(this.querySelectorAll(":scope > button"))
|
3954
|
+
}
|
3955
|
+
|
3898
3956
|
static get defaultTemplate() {
|
3899
3957
|
return `
|
3900
|
-
<button type="button" name="bold" data-command="bold" title="Bold">
|
3901
|
-
<svg viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"
|
3958
|
+
<button class="lexxy-editor__toolbar-button" type="button" name="bold" data-command="bold" title="Bold">
|
3959
|
+
<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>
|
3902
3960
|
</button>
|
3903
|
-
|
3904
|
-
<button type="button" name="italic" data-command="italic" title="Italic">
|
3905
|
-
<svg viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path d="
|
3961
|
+
|
3962
|
+
<button class="lexxy-editor__toolbar-button" type="button" name="italic" data-command="italic" title="Italic">
|
3963
|
+
<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>
|
3906
3964
|
</button>
|
3907
|
-
|
3908
|
-
<button type="button" name="link" title="Link" data-dialog-target="link-dialog" data-hotkey="cmd+k ctrl+k">
|
3909
|
-
<svg viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path d="
|
3965
|
+
|
3966
|
+
<button class="lexxy-editor__toolbar-button" type="button" name="link" title="Link" data-dialog-target="link-dialog" data-hotkey="cmd+k ctrl+k">
|
3967
|
+
<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>
|
3910
3968
|
</button>
|
3969
|
+
|
3911
3970
|
<lexxy-link-dialog class="lexxy-link-dialog">
|
3912
3971
|
<dialog id="link-dialog" closedby="any">
|
3913
3972
|
<form method="dialog">
|
@@ -3916,33 +3975,38 @@ class LexicalToolbarElement extends HTMLElement {
|
|
3916
3975
|
<button type="submit" class="btn" value="link">Link</button>
|
3917
3976
|
<button type="button" class="btn" value="unlink">Unlink</button>
|
3918
3977
|
</div>
|
3919
|
-
</form>
|
3920
|
-
</dialog>
|
3978
|
+
</form>
|
3979
|
+
</dialog>
|
3921
3980
|
</lexxy-link-dialog>
|
3922
|
-
|
3923
|
-
<button type="button" name="quote" data-command="insertQuoteBlock" title="Quote">
|
3924
|
-
<svg viewBox="0 0 24
|
3981
|
+
|
3982
|
+
<button class="lexxy-editor__toolbar-button" type="button" name="quote" data-command="insertQuoteBlock" title="Quote">
|
3983
|
+
<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>
|
3925
3984
|
</button>
|
3926
|
-
|
3927
|
-
<button type="button" name="heading" data-command="rotateHeadingFormat" title="Heading">
|
3928
|
-
<svg viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path d="
|
3985
|
+
|
3986
|
+
<button class="lexxy-editor__toolbar-button" type="button" name="heading" data-command="rotateHeadingFormat" title="Heading">
|
3987
|
+
<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>
|
3929
3988
|
</button>
|
3930
|
-
|
3931
|
-
<button type="button" name="code" data-command="insertCodeBlock" title="Code">
|
3932
|
-
<svg viewBox="0 0 24
|
3989
|
+
|
3990
|
+
<button class="lexxy-editor__toolbar-button" type="button" name="code" data-command="insertCodeBlock" title="Code">
|
3991
|
+
<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>
|
3933
3992
|
</button>
|
3934
|
-
|
3935
|
-
<button type="button" name="unordered-list" data-command="insertUnorderedList" title="Bullet list">
|
3936
|
-
<svg viewBox="0 0 24
|
3993
|
+
|
3994
|
+
<button class="lexxy-editor__toolbar-button" type="button" name="unordered-list" data-command="insertUnorderedList" title="Bullet list">
|
3995
|
+
<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>
|
3937
3996
|
</button>
|
3938
|
-
|
3939
|
-
<button type="button" name="ordered-list" data-command="insertOrderedList" title="Numbered list">
|
3940
|
-
<svg viewBox="0 0 24
|
3997
|
+
|
3998
|
+
<button class="lexxy-editor__toolbar-button" type="button" name="ordered-list" data-command="insertOrderedList" title="Numbered list">
|
3999
|
+
<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>
|
3941
4000
|
</button>
|
3942
|
-
|
3943
|
-
<button type="button" name="upload" data-command="uploadAttachments" title="Upload file">
|
3944
|
-
<svg viewBox="0 0 24
|
4001
|
+
|
4002
|
+
<button class="lexxy-editor__toolbar-button" type="button" name="upload" data-command="uploadAttachments" title="Upload file">
|
4003
|
+
<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>
|
3945
4004
|
</button>
|
4005
|
+
|
4006
|
+
<details class="lexxy-editor__toolbar-overflow">
|
4007
|
+
<summary class="lexxy-editor__toolbar-button" aria-label="Show more toolbar buttons">•••</summary>
|
4008
|
+
<div class="lexxy-editor__toolbar-overflow-menu" aria-label="More toolbar buttons"></div>
|
4009
|
+
</details>
|
3946
4010
|
`
|
3947
4011
|
}
|
3948
4012
|
}
|
@@ -5603,7 +5667,7 @@ class ActionTextAttachmentNode extends gi {
|
|
5603
5667
|
}
|
5604
5668
|
|
5605
5669
|
#select(figure) {
|
5606
|
-
dispatchCustomEvent(figure, "lexxy:node
|
5670
|
+
dispatchCustomEvent(figure, "lexxy:internal:select-node", { key: this.getKey() });
|
5607
5671
|
}
|
5608
5672
|
|
5609
5673
|
#createEditableCaption() {
|
@@ -5635,13 +5699,13 @@ class ActionTextAttachmentNode extends gi {
|
|
5635
5699
|
}
|
5636
5700
|
|
5637
5701
|
#updateCaptionValueFromInput(input) {
|
5638
|
-
dispatchCustomEvent(input, "lexxy:node
|
5702
|
+
dispatchCustomEvent(input, "lexxy:internal:invalidate-node", { key: this.getKey(), values: { caption: input.value } });
|
5639
5703
|
}
|
5640
5704
|
|
5641
5705
|
#handleCaptionInputKeydown(event) {
|
5642
5706
|
if (event.key === "Enter") {
|
5643
5707
|
this.#updateCaptionValueFromInput(event.target);
|
5644
|
-
dispatchCustomEvent(event.target, "lexxy:move-to-next-line");
|
5708
|
+
dispatchCustomEvent(event.target, "lexxy:internal:move-to-next-line");
|
5645
5709
|
event.preventDefault();
|
5646
5710
|
}
|
5647
5711
|
event.stopPropagation();
|
@@ -6199,7 +6263,7 @@ class Selection {
|
|
6199
6263
|
}
|
6200
6264
|
|
6201
6265
|
#listenForNodeSelections() {
|
6202
|
-
this.editor.getRootElement().addEventListener("lexxy:node
|
6266
|
+
this.editor.getRootElement().addEventListener("lexxy:internal:select-node", async (event) => {
|
6203
6267
|
await nextFrame();
|
6204
6268
|
|
6205
6269
|
const { key } = event.detail;
|
@@ -6214,7 +6278,7 @@ class Selection {
|
|
6214
6278
|
});
|
6215
6279
|
});
|
6216
6280
|
|
6217
|
-
this.editor.getRootElement().addEventListener("lexxy:move-to-next-line", (event) => {
|
6281
|
+
this.editor.getRootElement().addEventListener("lexxy:internal:move-to-next-line", (event) => {
|
6218
6282
|
this.#selectOrAppendNextLine();
|
6219
6283
|
});
|
6220
6284
|
}
|
@@ -6288,7 +6352,7 @@ class Selection {
|
|
6288
6352
|
if (ur(selection)) {
|
6289
6353
|
return this.#getTopLevelFromNodeSelection(selection)
|
6290
6354
|
}
|
6291
|
-
|
6355
|
+
|
6292
6356
|
if (cr(selection)) {
|
6293
6357
|
return this.#getTopLevelFromRangeSelection(selection)
|
6294
6358
|
}
|
@@ -6367,7 +6431,7 @@ class Selection {
|
|
6367
6431
|
|
6368
6432
|
#getReliableRectFromRange(range) {
|
6369
6433
|
let rect = range.getBoundingClientRect();
|
6370
|
-
|
6434
|
+
|
6371
6435
|
if (this.#isRectUnreliable(rect)) {
|
6372
6436
|
const marker = this.#createAndInsertMarker(range);
|
6373
6437
|
rect = marker.getBoundingClientRect();
|
@@ -6424,12 +6488,12 @@ class Selection {
|
|
6424
6488
|
const nativeSelection = window.getSelection();
|
6425
6489
|
const anchorNode = nativeSelection.anchorNode;
|
6426
6490
|
const parentElement = this.#getElementFromNode(anchorNode);
|
6427
|
-
|
6491
|
+
|
6428
6492
|
if (parentElement instanceof HTMLElement) {
|
6429
6493
|
const computed = window.getComputedStyle(parentElement);
|
6430
6494
|
return parseFloat(computed.fontSize)
|
6431
6495
|
}
|
6432
|
-
|
6496
|
+
|
6433
6497
|
return 0
|
6434
6498
|
}
|
6435
6499
|
|
@@ -6709,6 +6773,10 @@ class Contents {
|
|
6709
6773
|
return
|
6710
6774
|
}
|
6711
6775
|
|
6776
|
+
if (!this.#shouldUploadFile(file)) {
|
6777
|
+
return
|
6778
|
+
}
|
6779
|
+
|
6712
6780
|
const uploadUrl = this.editorElement.directUploadUrl;
|
6713
6781
|
const blobUrlTemplate = this.editorElement.blobUrlTemplate;
|
6714
6782
|
|
@@ -6997,6 +7065,10 @@ class Contents {
|
|
6997
7065
|
}
|
6998
7066
|
}
|
6999
7067
|
}
|
7068
|
+
|
7069
|
+
#shouldUploadFile(file) {
|
7070
|
+
return dispatch(this.editorElement, 'lexxy:file-accept', { file }, true)
|
7071
|
+
}
|
7000
7072
|
}
|
7001
7073
|
|
7002
7074
|
/**
|
@@ -7213,7 +7285,7 @@ class CustomActionTextAttachmentNode extends gi {
|
|
7213
7285
|
const figure = createElement("action-text-attachment", { "content-type": this.contentType, "data-lexxy-decorator": true });
|
7214
7286
|
|
7215
7287
|
figure.addEventListener("click", (event) => {
|
7216
|
-
dispatchCustomEvent(figure, "lexxy:node
|
7288
|
+
dispatchCustomEvent(figure, "lexxy:internal:select-node", { key: this.getKey() });
|
7217
7289
|
});
|
7218
7290
|
|
7219
7291
|
figure.insertAdjacentHTML("beforeend", this.innerHtml);
|
@@ -7264,7 +7336,7 @@ class LexicalEditorElement extends HTMLElement {
|
|
7264
7336
|
constructor() {
|
7265
7337
|
super();
|
7266
7338
|
this.internals = this.attachInternals();
|
7267
|
-
this.internals.role = "
|
7339
|
+
this.internals.role = "presentation";
|
7268
7340
|
}
|
7269
7341
|
|
7270
7342
|
connectedCallback() {
|
@@ -7276,6 +7348,8 @@ class LexicalEditorElement extends HTMLElement {
|
|
7276
7348
|
|
7277
7349
|
CommandDispatcher.configureFor(this);
|
7278
7350
|
this.#initialize();
|
7351
|
+
|
7352
|
+
requestAnimationFrame(() => dispatch(this, "lexxy:initialize"));
|
7279
7353
|
this.toggleAttribute("connected", true);
|
7280
7354
|
|
7281
7355
|
this.valueBeforeDisconnect = null;
|
@@ -7416,12 +7490,20 @@ class LexicalEditorElement extends HTMLElement {
|
|
7416
7490
|
}
|
7417
7491
|
|
7418
7492
|
#createEditorContentElement() {
|
7419
|
-
const editorContentElement = createElement("div", {
|
7493
|
+
const editorContentElement = createElement("div", {
|
7494
|
+
classList: "lexxy-editor__content",
|
7495
|
+
contenteditable: true,
|
7496
|
+
role: "textbox",
|
7497
|
+
"aria-multiline": true,
|
7498
|
+
"aria-label": this.#labelText,
|
7499
|
+
placeholder: this.getAttribute("placeholder")
|
7500
|
+
});
|
7420
7501
|
editorContentElement.id = `${this.id}-content`;
|
7502
|
+
this.#ariaAttributes.forEach(attribute => editorContentElement.setAttribute(attribute.name, attribute.value));
|
7421
7503
|
this.appendChild(editorContentElement);
|
7422
7504
|
|
7423
7505
|
if (this.getAttribute("tabindex")) {
|
7424
|
-
|
7506
|
+
editorContentElement.setAttribute("tabindex", this.getAttribute("tabindex"));
|
7425
7507
|
this.removeAttribute("tabindex");
|
7426
7508
|
} else {
|
7427
7509
|
editorContentElement.setAttribute("tabindex", 0);
|
@@ -7430,6 +7512,14 @@ class LexicalEditorElement extends HTMLElement {
|
|
7430
7512
|
return editorContentElement
|
7431
7513
|
}
|
7432
7514
|
|
7515
|
+
get #labelText() {
|
7516
|
+
return Array.from(this.internals.labels).map(label => label.textContent).join(" ")
|
7517
|
+
}
|
7518
|
+
|
7519
|
+
get #ariaAttributes() {
|
7520
|
+
return Array.from(this.attributes).filter(attribute => attribute.name.startsWith("aria-"))
|
7521
|
+
}
|
7522
|
+
|
7433
7523
|
set #internalFormValue(html) {
|
7434
7524
|
const changed = this.#internalFormValue !== undefined && this.#internalFormValue !== this.value;
|
7435
7525
|
|
@@ -7492,7 +7582,7 @@ class LexicalEditorElement extends HTMLElement {
|
|
7492
7582
|
}
|
7493
7583
|
|
7494
7584
|
#listenForInvalidatedNodes() {
|
7495
|
-
this.editor.getRootElement().addEventListener("lexxy:node
|
7585
|
+
this.editor.getRootElement().addEventListener("lexxy:internal:invalidate-node", (event) => {
|
7496
7586
|
const { key, values } = event.detail;
|
7497
7587
|
|
7498
7588
|
this.editor.update(() => {
|
Binary file
|
Binary file
|