lexxy 0.1.25.beta → 0.7.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: dad8a8750fa3768dec22db124d53c86c503c0e22a8a161f9255f7b7d693bcdfe
4
- data.tar.gz: 8d890586e2d4f4579945b0c236fdfefe7d4ac91e005d923781297c776d7badf6
3
+ metadata.gz: c682103f0942df07f6b0bc8f9b9168fafd2d504b7ddc94d8dfbf47f48eff9d5c
4
+ data.tar.gz: 54b5eb6b7182a12c45383049b1b4bc323b46774c0e6323b939321f48e034841d
5
5
  SHA512:
6
- metadata.gz: 6e6d60e274a8b0a3cb91dc3ca5f45ee62b27a35058cc4be2a028626d9293ddf8c03489837a568ed611f29c3bfb671d69b7884f0652b6e5bbbb6b832b2b73fb97
7
- data.tar.gz: c945dff9eea87287117e078cc21bfd3da2c6644c07ba09ae19b61600a8efb17ce2baaae2764646b1cfdc80c698554bc6e3693be05d88e7df242461d1df56729d
6
+ metadata.gz: c7095c4419e06c9ca7063ec8c045001a1ec673edd5f50716de86e1412b3383477808c061da9aecaa6cf2c022b585e6be81de41191d5082f81acbaaf3ef6dc794
7
+ data.tar.gz: e9eb3b0e4d8d42ab195cb9138eff1f96aad19fe7220719ce02d70c622312cae56443bfa85095f52e85e875aef89044e10af48a59b3cf89794926b069558e6aac
data/README.md CHANGED
@@ -18,418 +18,22 @@ A modern rich text editor for Rails.
18
18
 
19
19
  ![Lexxy editor screenshot](docs/images/home.screenshot.png)
20
20
 
21
- ## Installation
21
+ ## Documentation
22
22
 
23
- Add this line to your application's Gemfile:
24
-
25
- ```ruby
26
- gem 'lexxy', '~> 0.1.23.beta' # Need to specify the version since it's a pre-release
27
- ```
28
-
29
- And then execute:
30
-
31
- ```bash
32
- bundle install
33
- ```
34
-
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):
38
-
39
- ```ruby
40
- # importmap.rb
41
- pin "lexxy", to: "lexxy.js"
42
- pin "@rails/activestorage", to: "activestorage.esm.js" # to support attachments
43
- ```
44
-
45
- Then import it in your JavaScript entry point:
46
-
47
- ```javascript
48
- // app/javascript/application.js
49
- import "lexxy"
50
- ```
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
-
84
- For the CSS, you can include it with the standard Rails helper:
85
-
86
- ```erb
87
- <%= stylesheet_link_tag "lexxy" %>
88
- ```
89
-
90
- You can copy the CSS to your project and adapt it to your needs.
91
-
92
- #### Custom styles and dark mode
93
-
94
- All of Lexxy's color styles are defiend as CSS variables in `app/stylesheets/lexxy-variables.css`. This enables a straightforward way to customize Lexxy to match your application's theme. You can see an example implementation of a custom dark mode style in the Sandbox's stylesheet at `test/dummy/app/assets/stylesheets/sandbox.css`.
95
-
96
- #### Rendered Action Text content
97
-
98
- 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`:
99
-
100
- ```erb
101
- <div class="lexxy-content">
102
- <%= yield -%>
103
- </div>
104
- ```
105
-
106
- 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`:
107
-
108
- ```javascript
109
- import { Controller } from "@hotwired/stimulus"
110
- import { highlightAll } from "lexxy"
111
- // Or if you installed via a javascript bundler:
112
- // import { highlightAll } from "@37signals/lexxy"
113
-
114
- export default class extends Controller {
115
- connect() {
116
- highlightAll()
117
- }
118
- }
119
- ```
120
-
121
- Then update the Action Text Content template to include the `data-controller` attribute:
122
-
123
- ```erb
124
- <div data-controller="syntax-highlight" class="lexxy-content">
125
- <%= yield -%>
126
- </div>
127
- ```
128
-
129
- ## Configuration
130
-
131
- You can add a Lexxy instance using the regular Action Text form helper:
132
-
133
- ```erb
134
- <%= form_with model: @post do |form| %>
135
- <%= form.rich_text_area :content %>
136
- <% end %>
137
- ```
138
-
139
- Under the hood, this will insert a `<lexxy-editor>` tag, that will be a first-class form control:
140
-
141
- ```html
142
- <lexxy-editor name="post[body]"...>...</lexxy-editor>
143
- ```
144
-
145
- ## Options
146
-
147
- The `<lexxy-editor>` element supports these options:
148
-
149
- - `placeholder`: Text displayed when the editor is empty.
150
- - `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.
151
- - `attachments`: Pass `"false"` to disable attachments completely. By default, attachments are supported, including paste and Drag & Drop support.
152
-
153
- 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.
154
-
155
- ## Prompts
156
-
157
- Prompts let you implement features like @mentions, /commands, or any other trigger-based suggestions. When you select an item from the prompt, you have two options:
158
-
159
- 1. Insert the item as an [Action Text custom attachment](https://guides.rubyonrails.org/action_text_overview.html#signed-globalid). This allows you to use standard Action Text to customize how it renders or processes them on the server side.
160
- 2. Insert the item as free text in the editor.
161
-
162
- Lexxy also lets you configure how to load the items: inline or remotely, and how to do the filtering (locally or on the server).
163
-
164
- ### General setup
165
-
166
- The first thing to do is to add a `<lexxy-prompt>` element to the editor:
167
-
168
- ```erb
169
- <%= form.rich_text_area :body do %>
170
- <lexxy-prompt trigger="@">
171
- </lexxy-prompt>
172
- <% end %>
173
- ```
174
-
175
- The `trigger` option determines which key will open the prompt. A prompt can load its items from two sources:
176
-
177
- - Inline, by defining the items inside the `<lexxy-prompt>` element.
178
- - Remotely, by setting a `src` attribute with an endpoint to load the items.
179
-
180
- Regardless of the source, the prompt items are defined using `<lexxy-prompt-item>` elements. A basic prompt item looks like this:
181
-
182
- ```html
183
- <lexxy-prompt-item search="...">
184
- <template type="menu">...</template>
185
- <template type="editor">
186
- ...
187
- </template>
188
- </lexxy-prompt-item>
189
- ```
190
-
191
- Where:
192
-
193
- * `search` contains the text to match against when filtering.
194
- * `template[type= "menu"]` defines how the item appears in the dropdown menu.
195
- * `template[type= "editor"]` defines how the item appears in the editor when selected.
196
-
197
- ### Custom attachments with inline loading
198
-
199
- 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.
200
-
201
- 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:
202
-
203
- ```ruby
204
- # app/models/person.rb
205
- class Person < ApplicationRecord
206
- include ActionText::Attachable
207
-
208
- def content_type
209
- "application/vnd.actiontext.mention"
210
- end
211
- end
212
- ```
213
-
214
- By default, the partial to render the attachment will be looked up in `app/views/[model plural]/_[model singular].html.erb`. You can customize this by implementing `#to_attachable_partial_path` in the model. Let's go with the default and render a simple view that renders the person's name and initials:
215
-
216
- ```erb
217
- # app/views/people/_person.html.erb
218
- <em><%= person.name %></em> (<strong><%= person.initials %></strong>)
219
- ```
220
-
221
- On the editor side, let's start with the *inline* approach by rendering all the prompt items inside the `<lexxy-prompt>` element:
222
-
223
- ```erb
224
- <%= form.rich_text_area :body do %>
225
- <lexxy-prompt trigger="@" name="mention">
226
- <%= render partial: "people/prompt_item", collection: Person.all, as: :person %>
227
- </lexxy-prompt>
228
- <% end %>
229
- ```
230
-
231
- With `app/views/people/_prompt_item.html.erb` defining each prompt item:
232
-
233
- ```erb
234
- <lexxy-prompt-item search="<%= "#{person.name} #{person.initials}" %>" sgid="<%= person.attachable_sgid %>">
235
- <template type="menu"><%= person.name %></template>
236
- <template type="editor">
237
- <%= render "people/person", person: person %>
238
- </template>
239
- </lexxy-prompt-item>
240
- ```
241
-
242
- Notice how the template for rendering the editor representation (`type=" editor") uses the same template as the attachment partial. This way, you ensure consistency between how the mention looks in the editor and how it will render when displaying the text in view mode with Action Text.
243
-
244
- Two important additional notes to use action text with custom attachments:
245
-
246
- * Each `<lexxy-prompt-item>` must include a `sgid` attribute with the [global id that Action Text will use to find the associated model](https://guides.rubyonrails.org/action_text_overview.html#signed-globalid).
247
- * The `<lexxy-prompt>` must include a `name` attribute that will determine the content type of the attachment. For example, for `name= "mention"`, the attachment will be saved as `application/vnd.actiontext.mention`.
248
-
249
- ### Custom attachments with remote loading
250
-
251
- For moderately large sets, you can configure Lexxy to load all the options from a remote endpoint once, and filter them locally as the user types. This is a good balance between performance and responsiveness.
252
-
253
- Continuing with the mentions example, we could have a controller action that returns all people as prompt items, and configure it as the remote source via the `src` attribute:
254
-
255
- ```erb
256
- <lexxy-prompt trigger="@" src="<%= people_path %>" name="mention">
257
- </lexxy-prompt>
258
- ```
259
-
260
- We could define the controller action to serve the prompt items like this:
261
-
262
- ```ruby
263
- class PeopleController < ApplicationController
264
- def index
265
- @people = Person.all
266
-
267
- render layout: false
268
- end
269
- end
270
- ```
271
-
272
- And the action would just list the prompt items:
273
-
274
- ```erb
275
- <%= render partial: "people/prompt_item", collection: @people %>
276
- ```
277
-
278
- ### Free HTML attachments
279
-
280
- If you don't want to use custom action text attachments, you can configure prompts to simply insert the prompt item HTML directly in the editor. This is useful for things like hashtags, emojis, or other inline elements that don't require server-side processing.
281
-
282
- To enable these, you must add the `insert-editable-text` attribute to the `<lexxy-prompt>` element:
283
-
284
- ```erb
285
- <lexxy-prompt trigger="@" src="<%= people_path %>" insert-editable-text>
286
- </lexxy-prompt>
287
- ```
288
-
289
- When configured like this,if you select an item from the prompt, the content of the `template[type= "editor"]` will be inserted directly in the editor as HTML you can edit freely, instead of as an `<action-text-attachment>` element. Notice that in this case, you need to make sure that the HTML is compatible with the tags that Lexxy supports.
290
-
291
- ### Remote filtering
292
-
293
- There are scenarios where you want to query the server for filtering, instead of loading all options at once. This is useful for large datasets or complex searches. In this case, you must add the `remote-filtering` attribute to the `<lexxy-prompt>` element:
294
-
295
- ```erb
296
- <lexxy-prompt trigger="@" src="<%= people_path %>" name="mention" remote-filtering>
297
- </lexxy-prompt>
298
- ```
299
-
300
- By default, the `SPACE` key will select the current item in the prompt. If you want to allow spaces in the search query, you can add the `supports-space-in-searches` attribute to the prompt. This can be handy to search by full names in combination with remote filtering.
301
-
302
- ### Prompt Options reference
303
-
304
- #### `<lexxy-prompt>`
305
-
306
- - `trigger`: The character that activates the prompt (e.g., "@", "#", "/").
307
- - `src`: Path or URL to load items remotely.
308
- - `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`.
309
- - `empty-results`: Message shown when no matches found. By default it is "Nothing found".
310
- - `remote-filtering`: Enable server-side filtering instead of loading all options at once.
311
- - `insert-editable-text`: Insert prompt item HTML directly as editable text instead of Action Text attachments.
312
- - `supports-space-in-searches`: Allow spaces in search queries (useful with remote filtering for full name searches).
313
-
314
- #### `<lexxy-prompt-item>`
315
-
316
- - `search`: The text to match against when filtering (can include multiple fields for better search).
317
- - `sgid`: The signed GlobalID for Action Text attachments (use `attachable_sgid` helper). Mandatory unless using `insert-editable-text`.
23
+ Visit the **[documentation site](https://basecamp.github.io/lexxy)**.
318
24
 
319
25
  ## Roadmap
320
26
 
321
27
  This is an early beta. Here's what's coming next:
322
28
 
323
- - Configurable editors in Action Text: Choose your editor like you choose your database.
324
- - More editing features:
325
- - Tables
326
- - Text highlighting
327
- - Image galleries: The only remaining feature for full Action Text compatibility
328
- - Install task that generates the necessary JS and adds stylesheets.
329
- - Configuration hooks.
330
- - Standalone JS package: to use in non-Rails environments.
331
-
332
- ## Development
333
-
334
- To build the JS source when it changes, run:
335
-
336
- ```bash
337
- yarn build -w
338
- ```
339
-
340
- To the sandbox app:
341
-
342
- ```bash
343
- bin/rails server
344
- ```
345
-
346
- The sandbox app is available at http://localhost:3000. There is also a CRUD example at http://localhost:3000/posts.
347
-
348
- ## Events
349
-
350
- Lexxy fires a handful of custom events that you can hook into.
351
- Each event is dispatched on the `<lexxy-editor>` element.
352
-
353
- ### `lexxy:initialize`
354
-
355
- Fired when the `<lexxy-editor>` element is attached to the DOM and ready for use.
356
- This is useful for one-time setup.
357
-
358
- ### lexxy:focus and lexxy:blur
359
-
360
- Fired whenever the editor element gains or loses focus.
361
- Useful to show or hide accessory UI state.
362
-
363
- ### `lexxy:change`
364
-
365
- Fired whenever the editor content changes.
366
- You can use this to sync the editor state with your application.
367
-
368
- ### `lexxy:file-accept`
369
-
370
- Fired when a file is dropped or inserted into the editor.
371
-
372
- - Access the file via `event.detail.file`.
373
- - Call `event.preventDefault()` to cancel the upload and prevent attaching the file.
374
-
375
- ### `lexxy:insert-link`
376
-
377
- Fired when a plain text link is pasted into the editor.
378
- Access the link’s URL via `event.detail.url`.
379
-
380
- You also get a handful of callback helpers on `event.detail`:
381
-
382
- - **`replaceLinkWith(html, options)`** – replace the pasted link with your own HTML.
383
- - **`insertBelowLink(html, options)`** – insert custom HTML below the link.
384
- - **Attachment rendering** – pass `{ attachment: true }` in `options` to render as non-editable content,
385
- or `{ attachment: { sgid: "your-sgid-here" } }` to provide a custom SGID.
386
-
387
- #### Example: Link Unfurling with Stimulus
388
-
389
- When a user pastes a link, you may want to turn it into a preview or embed.
390
- Here’s a Stimulus controller that sends the URL to your app, retrieves metadata,
391
- and replaces the plain text link with a richer version:
392
-
393
- ```javascript
394
- // app/javascript/controllers/link_unfurl_controller.js
395
- import { Controller } from "@hotwired/stimulus"
396
- import { post } from "@rails/request.js"
397
-
398
- export default class extends Controller {
399
- static values = {
400
- url: String, // endpoint that handles unfurling
401
- }
402
-
403
- unfurl(event) {
404
- this.#unfurlLink(event.detail.url, event.detail)
405
- }
406
-
407
- async #unfurlLink(url, callbacks) {
408
- const { response } = await post(this.urlValue, {
409
- body: JSON.stringify({ url }),
410
- headers: {
411
- "Content-Type": "application/json",
412
- "Accept": "application/json"
413
- }
414
- })
415
-
416
- const metadata = await response.json()
417
- this.#insertUnfurledLink(metadata, callbacks)
418
- }
419
-
420
- #insertUnfurledLink(metadata, callbacks) {
421
- // Replace the pasted link with your custom HTML
422
- callbacks.replaceLinkWith(this.#renderUnfurledLinkHTML(metadata))
423
-
424
- // Or, insert below the link as an attachment:
425
- // callbacks.insertBelowLink(this.#renderUnfurledLinkHTML(metadata), { attachment: true })
426
- }
427
-
428
- #renderUnfurledLinkHTML(link) {
429
- return `<a href="${link.canonical_url}">${link.title}</a>`
430
- }
431
- }
432
- ```
29
+ - [x] Configurable editors in Action Text: Choose your editor like you choose your database.
30
+ - [x] More editing features:
31
+ - [x] Tables
32
+ - [x] Text highlighting
33
+ - [x] Configuration hooks.
34
+ - [x] Standalone JS package: to use in non-Rails environments.
35
+ - [ ] Image galleries: The only remaining feature for full Action Text compatibility
36
+ - [ ] Install task that generates the necessary JS and adds stylesheets.
433
37
 
434
38
  ## Contributing
435
39