yrby 0.3.1 → 0.5.0
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/CHANGELOG.md +35 -0
- data/README.md +61 -15
- data/ext/yrby/src/lexical_html.rs +993 -0
- data/ext/yrby/src/lib.rs +112 -0
- data/ext/yrby/src/prosemirror_html.rs +896 -0
- data/ext/yrby/src/read.rs +71 -4
- data/lib/y/version.rb +1 -1
- metadata +3 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 3c53c83185ba046ddbcb6b232953af9aa2f52f883120aff8bb2fed8867f247df
|
|
4
|
+
data.tar.gz: 6a09a5541cca754d6ff838feb82838ce881d8fea144fed692031f7c957c9850b
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: d44fb5bb2a723b6a0dcf7ac6fd3bc248bd58f1401fe2e62009a214148f4a7b1ef1586d0d25585b39d291667eff04d6c66e911c7cdb5dec4968a0f3192b70a85c
|
|
7
|
+
data.tar.gz: 74da78ebc395e44cbdc9f32dcc51a239b7ea312de24f5ac64ec47db41bc784b47a53425afc83b948d36c9bf1b11922a3f9f3aa6615dcbac72a77759abe046ccc
|
data/CHANGELOG.md
CHANGED
|
@@ -6,6 +6,41 @@ to follow [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
|
6
6
|
|
|
7
7
|
## [Unreleased]
|
|
8
8
|
|
|
9
|
+
## [0.5.0] - 2026-07-08
|
|
10
|
+
|
|
11
|
+
### Added
|
|
12
|
+
|
|
13
|
+
- **`Y::Lexical` — render Lexical/Lexxy documents to HTML.**
|
|
14
|
+
`Y::Lexical.new(doc).to_html` turns a Lexxy document into HTML on the server,
|
|
15
|
+
with no Node process or headless editor. The output is identical to the HTML
|
|
16
|
+
a `lexxy-editor` submits to Rails; the tests check it byte-for-byte against a
|
|
17
|
+
document captured from a real editor. It covers the whole Lexxy 0.9.x node
|
|
18
|
+
set: headings, every text format, links, bullet/numbered/check/nested lists,
|
|
19
|
+
quotes, code blocks, horizontal rules, tables with header cells, image galleries, and
|
|
20
|
+
ActionText attachments. Unknown nodes fall back to a plain paragraph, and a
|
|
21
|
+
root that isn't Lexical (a ProseMirror document, say) returns `nil`. This is
|
|
22
|
+
what `tiptap-php` does for ProseMirror JSON, applied to the Yjs structure.
|
|
23
|
+
- `read_xml` now pulls text out of attachments too: a mention's text goes
|
|
24
|
+
inline, and an upload adds its caption, alt text, or filename. Before, both
|
|
25
|
+
were dropped.
|
|
26
|
+
|
|
27
|
+
## [0.4.0] - 2026-07-07
|
|
28
|
+
|
|
29
|
+
### Added
|
|
30
|
+
|
|
31
|
+
- **`Y::ProseMirror` — render ProseMirror/Tiptap documents to HTML.**
|
|
32
|
+
`Y::ProseMirror.new(doc).to_html` turns a Tiptap document into HTML on the
|
|
33
|
+
server, with no Node process or headless editor. The output matches Tiptap's
|
|
34
|
+
own `getHTML()`; the tests check it byte-for-byte against a document captured
|
|
35
|
+
from a real editor. It follows `ueberdosis/tiptap-php` and reads both name
|
|
36
|
+
styles editors use — Tiptap's `bulletList`/`bold` and prosemirror-schema-basic's
|
|
37
|
+
`bullet_list`/`strong`. Covers paragraphs, headings, blockquotes,
|
|
38
|
+
bullet/ordered/task lists, code blocks, links, images, mentions, details,
|
|
39
|
+
hard breaks, horizontal rules, tables, text styles (color, font family), and
|
|
40
|
+
every text mark. A table renders as semantic `<table><tbody>` without the
|
|
41
|
+
editor's column-width styling. A root that isn't ProseMirror (a Lexical
|
|
42
|
+
document, say) returns `nil`.
|
|
43
|
+
|
|
9
44
|
## [0.3.1] - 2026-07-01
|
|
10
45
|
|
|
11
46
|
Fixes from a full source review.
|
data/README.md
CHANGED
|
@@ -157,6 +157,17 @@ doc.handle_sync_message(data) # => [msg_type, sync_type, response]; answers
|
|
|
157
157
|
# SyncStep2 (never serves pending structs)
|
|
158
158
|
```
|
|
159
159
|
|
|
160
|
+
### Reading document contents
|
|
161
|
+
|
|
162
|
+
Reconstruct a document server-side — search, exports, emails, SSR — with no
|
|
163
|
+
Node process:
|
|
164
|
+
|
|
165
|
+
```ruby
|
|
166
|
+
doc.read_text("prosemirror") # => plain text of a Y.Text root, or nil
|
|
167
|
+
doc.read_xml("root") # => text of an XML root, one block per line
|
|
168
|
+
doc.read_map("state") # => a Y.Map root as a JSON string; JSON.parse it
|
|
169
|
+
```
|
|
170
|
+
|
|
160
171
|
### Pending structs and gap-free state
|
|
161
172
|
|
|
162
173
|
If a doc applies an update whose causally-prior update is missing (a "gappy"
|
|
@@ -175,6 +186,56 @@ guarantees keep serving safe:
|
|
|
175
186
|
keeps its pending), while `encode_state_as_update` stays lossless so you can
|
|
176
187
|
still preserve the raw pending bytes for recovery.
|
|
177
188
|
|
|
189
|
+
### Rendering to HTML
|
|
190
|
+
|
|
191
|
+
Two schema-pinned renderers turn a collaborative document into HTML on the
|
|
192
|
+
server, with no Node process or headless editor: `Y::ProseMirror` for
|
|
193
|
+
ProseMirror/Tiptap documents and `Y::Lexical` for
|
|
194
|
+
[Lexxy](https://github.com/basecamp/lexxy) (Lexical) documents. Each returns
|
|
195
|
+
`nil` for a root that belongs to the other schema.
|
|
196
|
+
|
|
197
|
+
#### `Y::ProseMirror`
|
|
198
|
+
|
|
199
|
+
```ruby
|
|
200
|
+
prosemirror = Y::ProseMirror.new(doc)
|
|
201
|
+
prosemirror.to_html # the "default" fragment (Tiptap's default root)
|
|
202
|
+
prosemirror.to_html("content") # or another XML root
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
The output matches Tiptap's own `getHTML()`, checked byte-for-byte in the tests
|
|
206
|
+
against a document captured from a real editor. It follows
|
|
207
|
+
[`tiptap-php`](https://github.com/ueberdosis/tiptap-php) and reads both name
|
|
208
|
+
styles editors use — Tiptap's `bulletList`/`bold` and prosemirror-schema-basic's
|
|
209
|
+
`bullet_list`/`strong`.
|
|
210
|
+
|
|
211
|
+
It covers paragraphs, headings, blockquotes, bullet/ordered/task lists, code
|
|
212
|
+
blocks, links, images, mentions, details, hard breaks, horizontal rules,
|
|
213
|
+
tables, text styles (color, font family), and every text mark. A table renders
|
|
214
|
+
as semantic `<table><tbody>`, without the column-width styling Tiptap's editor
|
|
215
|
+
view adds.
|
|
216
|
+
|
|
217
|
+
#### `Y::Lexical`
|
|
218
|
+
|
|
219
|
+
```ruby
|
|
220
|
+
lexical = Y::Lexical.new(doc)
|
|
221
|
+
lexical.to_html # the "root" fragment (Lexical's default root name)
|
|
222
|
+
lexical.to_html("notepad") # or another XML root
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
The HTML is identical to what a `lexxy-editor` submits to Rails (its `value`).
|
|
226
|
+
The tests check this byte-for-byte against a document captured from a real
|
|
227
|
+
editor.
|
|
228
|
+
|
|
229
|
+
It handles the whole Lexxy 0.9.x node set: paragraphs, headings, every text
|
|
230
|
+
format and their combinations, links, the four list types and nesting,
|
|
231
|
+
blockquotes, code blocks, tabs and soft breaks, horizontal rules, tables with
|
|
232
|
+
header cells, image galleries, and ActionText attachments (uploads and
|
|
233
|
+
mentions both emit `<action-text-attachment>` elements that ActionText can
|
|
234
|
+
re-render).
|
|
235
|
+
|
|
236
|
+
In both renderers an unknown node keeps its content — text falls back to a
|
|
237
|
+
plain paragraph rather than disappearing.
|
|
238
|
+
|
|
178
239
|
### Protocol codec (module functions)
|
|
179
240
|
|
|
180
241
|
Classifying and unwrapping wire frames is stateless, so it's exposed as
|
|
@@ -242,21 +303,6 @@ servers:
|
|
|
242
303
|
causal dependencies are already in the store (checked against `on_load`); a
|
|
243
304
|
causally-incomplete update triggers a resync instead, so the log always
|
|
244
305
|
rebuilds cleanly.
|
|
245
|
-
- **An unhealable gap is dropped, not resynced forever.** A resync heals a gap
|
|
246
|
-
whose missing dependency is still in flight. But a *permanently*-orphaned update
|
|
247
|
-
(its dependency is gone for good) stays gappy through every resync, and a client
|
|
248
|
-
retransmitting it would loop endlessly (server resyncs → client resends →
|
|
249
|
-
repeat). After `gap_strike_limit` rejections of the same update on one
|
|
250
|
-
connection (default 3, minimum 2), the channel settles it with
|
|
251
|
-
`{ "ack" => id, "dropped" => true }` and drops it instead of resyncing again —
|
|
252
|
-
breaking the loop while never dropping a *healable* gap (those heal within a
|
|
253
|
-
resync or two, and healing frees the strike). The `dropped` flag lets the
|
|
254
|
-
client surface the loss (`yrby-client` reports it via `onError`) instead of
|
|
255
|
-
silently showing synced. Set `gap_strike_limit nil` to disable. Works on both
|
|
256
|
-
transports: plain ActionCable keeps strikes on the channel instance; under
|
|
257
|
-
AnyCable (fresh instance per RPC command) they persist through
|
|
258
|
-
anycable-rails' `state_attr_accessor` (istate), declared automatically when
|
|
259
|
-
anycable-rails is loaded.
|
|
260
306
|
- **`on_change` is at-least-once, and the durable guarantee is that replaying the
|
|
261
307
|
log reconstructs the document.** Every update triggers `on_change` before it's acked or
|
|
262
308
|
broadcast (record-before-distribute). If exactly-once updates matter for you, **you
|