yrby 0.7.0 → 0.8.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 +71 -0
- data/Cargo.lock +29 -29
- data/README.md +582 -380
- data/ext/yrby/Cargo.toml +3 -3
- data/ext/yrby/crates/html-core/Cargo.toml +5 -4
- data/ext/yrby/crates/html-core/src/lib.rs +16 -20
- data/ext/yrby/crates/lexical-html/Cargo.toml +5 -4
- data/ext/yrby/crates/lexical-html/src/lib.rs +57 -48
- data/ext/yrby/crates/prosemirror-html/Cargo.toml +5 -4
- data/ext/yrby/crates/prosemirror-html/src/lib.rs +151 -104
- data/ext/yrby/src/lib.rs +30 -15
- data/ext/yrby/src/protocol.rs +17 -17
- data/ext/yrby/src/read.rs +137 -26
- data/lib/generators/yrby/install/install_generator.rb +24 -13
- data/lib/generators/yrby/install/templates/document_channel.rb +4 -7
- data/lib/generators/yrby/tables/tables_generator.rb +7 -4
- data/lib/generators/yrby/tables/templates/create_y_tables.rb +1 -1
- data/lib/y/collaborative/attribute.rb +53 -0
- data/lib/y/collaborative/helper.rb +48 -0
- data/lib/y/collaborative.rb +98 -0
- data/lib/y/lexxy.rb +2 -2
- data/lib/y/rendering.rb +15 -15
- data/lib/y/tiptap.rb +3 -3
- data/lib/y/version.rb +1 -1
- data/lib/y.rb +1 -1
- metadata +4 -9
- data/ext/yrby/target/debug/build/clang-sys-e3ae45bd384f74c3/out/common.rs +0 -355
- data/ext/yrby/target/debug/build/clang-sys-e3ae45bd384f74c3/out/dynamic.rs +0 -276
- data/ext/yrby/target/debug/build/clang-sys-e3ae45bd384f74c3/out/macros.rs +0 -49
- data/ext/yrby/target/debug/build/rb-sys-4407948463231c4f/out/bindings-0.9.128-mri-arm64-darwin23-3.4.7.rs +0 -8934
- data/ext/yrby/target/debug/build/rb-sys-dbeea42737529c2d/out/bindings-0.9.128-mri-arm64-darwin23-3.4.7.rs +0 -8934
- data/ext/yrby/target/debug/build/serde-58ea0ee887cc2602/out/private.rs +0 -6
- data/ext/yrby/target/debug/build/serde_core-41f407c21c1f205e/out/private.rs +0 -5
- data/ext/yrby/target/debug/build/thiserror-0f1416a82ff26f22/out/private.rs +0 -5
data/ext/yrby/Cargo.toml
CHANGED
|
@@ -14,9 +14,9 @@ magnus = "0.8"
|
|
|
14
14
|
rb-sys = "0.9"
|
|
15
15
|
yrs = { version = "0.27", features = ["sync"] }
|
|
16
16
|
serde_json = "1.0"
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
17
|
+
yjs-html-core = { path = "crates/html-core" }
|
|
18
|
+
lexical-yjs-html = { path = "crates/lexical-html" }
|
|
19
|
+
prosemirror-yjs-html = { path = "crates/prosemirror-html" }
|
|
20
20
|
|
|
21
21
|
[dev-dependencies]
|
|
22
22
|
rb-sys-env = "0.1"
|
|
@@ -1,14 +1,15 @@
|
|
|
1
1
|
[package]
|
|
2
|
-
name = "
|
|
3
|
-
description = "Internal core for the
|
|
2
|
+
name = "yjs-html-core"
|
|
3
|
+
description = "Internal core for the lexical-yjs-html and prosemirror-yjs-html renderer crates; depend on those instead"
|
|
4
4
|
repository = "https://github.com/jpcamara/yrby"
|
|
5
5
|
readme = "README.md"
|
|
6
|
-
version = "0.1.
|
|
6
|
+
version = "0.1.3"
|
|
7
7
|
edition = "2024"
|
|
8
8
|
rust-version = "1.85"
|
|
9
9
|
authors = ["JP Camara <johnpcamara@gmail.com>"]
|
|
10
10
|
license = "MIT"
|
|
11
|
-
|
|
11
|
+
keywords = ["yjs", "crdt", "yrs", "html", "rendering"]
|
|
12
|
+
categories = ["text-processing", "web-programming"]
|
|
12
13
|
|
|
13
14
|
[dependencies]
|
|
14
15
|
yrs = { version = "0.27", features = ["sync"] }
|
|
@@ -1,21 +1,21 @@
|
|
|
1
|
-
//!
|
|
2
|
-
//!
|
|
1
|
+
//! The custom render rules and segmented output that both HTML renderers
|
|
2
|
+
//! share.
|
|
3
3
|
//!
|
|
4
|
-
//! Callers register per
|
|
4
|
+
//! Callers register a rule per node. There are two tiers:
|
|
5
5
|
//!
|
|
6
|
-
//! -
|
|
7
|
-
//! [`NodeRule`]
|
|
6
|
+
//! - A declarative rule (tag, attributes, text, content slot) compiles to a
|
|
7
|
+
//! [`NodeRule`] or [`MarkRule`] and renders natively, inside the document
|
|
8
8
|
//! transaction, at full speed. This covers the tiptap-php `renderHTML`
|
|
9
9
|
//! shape: markup as data.
|
|
10
|
-
//! -
|
|
11
|
-
//! while the document is locked
|
|
12
|
-
//! entries
|
|
13
|
-
//! already-rendered children, and the caller fills them in
|
|
14
|
-
//! render returns.
|
|
15
|
-
//! once the transaction has closed and the GVL is held again.
|
|
10
|
+
//! - A callback rule defers to the caller. The renderer never runs
|
|
11
|
+
//! application code while the document is locked. It emits
|
|
12
|
+
//! [`Segment::Deferred`] entries with the node type, the attributes as
|
|
13
|
+
//! JSON, and the already-rendered children, and the caller fills them in
|
|
14
|
+
//! after the render returns. In the Ruby gem that caller is the app's
|
|
15
|
+
//! block, run once the transaction has closed and the GVL is held again.
|
|
16
16
|
//!
|
|
17
|
-
//! Rules arrive as one JSON document (see `parse`), so
|
|
18
|
-
//!
|
|
17
|
+
//! Rules arrive as one JSON document (see `parse`), so one format serves
|
|
18
|
+
//! every binding and caller.
|
|
19
19
|
|
|
20
20
|
use std::collections::{BTreeMap, BTreeSet, HashMap};
|
|
21
21
|
use yrs::{Any, Out, ReadTxn, Xml};
|
|
@@ -196,11 +196,7 @@ pub fn resolve_parts<F: Fn(&str) -> Option<String>>(
|
|
|
196
196
|
}
|
|
197
197
|
}
|
|
198
198
|
}
|
|
199
|
-
if out.is_empty() {
|
|
200
|
-
None
|
|
201
|
-
} else {
|
|
202
|
-
Some(out)
|
|
203
|
-
}
|
|
199
|
+
if out.is_empty() { None } else { Some(out) }
|
|
204
200
|
}
|
|
205
201
|
|
|
206
202
|
/// An attribute reference on a node: rules say `:kind`; Lexical stores its own
|
|
@@ -384,7 +380,7 @@ fn parse_node_rule(name: &str, spec: &serde_json::Value) -> Result<NodeRule, Str
|
|
|
384
380
|
Some(other) => {
|
|
385
381
|
return Err(format!(
|
|
386
382
|
"rule for {name:?}: unknown content kind {other:?} (blocks|inline|none)"
|
|
387
|
-
))
|
|
383
|
+
));
|
|
388
384
|
}
|
|
389
385
|
};
|
|
390
386
|
if spec
|
|
@@ -431,7 +427,7 @@ fn parse_attrs(
|
|
|
431
427
|
Some(_) => {
|
|
432
428
|
return Err(format!(
|
|
433
429
|
"rule for {name:?}: attrs must be an array of [name, template] pairs"
|
|
434
|
-
))
|
|
430
|
+
));
|
|
435
431
|
}
|
|
436
432
|
};
|
|
437
433
|
for entry in entries {
|
|
@@ -1,16 +1,17 @@
|
|
|
1
1
|
[package]
|
|
2
|
-
name = "
|
|
2
|
+
name = "lexical-yjs-html"
|
|
3
3
|
description = "Render Lexical-shaped yrs documents to HTML, no browser or Node required"
|
|
4
4
|
repository = "https://github.com/jpcamara/yrby"
|
|
5
5
|
readme = "README.md"
|
|
6
|
-
version = "0.1.
|
|
6
|
+
version = "0.1.3"
|
|
7
7
|
edition = "2024"
|
|
8
8
|
rust-version = "1.85"
|
|
9
9
|
authors = ["JP Camara <johnpcamara@gmail.com>"]
|
|
10
10
|
license = "MIT"
|
|
11
|
-
|
|
11
|
+
keywords = ["yjs", "crdt", "lexical", "html", "yrs"]
|
|
12
|
+
categories = ["text-processing", "web-programming"]
|
|
12
13
|
|
|
13
14
|
[dependencies]
|
|
14
15
|
yrs = { version = "0.27", features = ["sync"] }
|
|
15
16
|
serde_json = "1.0"
|
|
16
|
-
|
|
17
|
+
yjs-html-core = { path = "../html-core", version = "0.1.3" }
|
|
@@ -1,48 +1,61 @@
|
|
|
1
|
-
//!
|
|
2
|
-
//!
|
|
1
|
+
//! HTML for Lexical documents, rendered from the Yjs structure the editor
|
|
2
|
+
//! syncs. No Node process and no headless editor. A server that holds the
|
|
3
|
+
//! document bytes produces the markup itself.
|
|
3
4
|
//!
|
|
4
|
-
//! This renders
|
|
5
|
-
//! and list items, tables, horizontal rules, links, and the whole
|
|
6
|
-
//! model.
|
|
5
|
+
//! This renders core Lexical: paragraphs, headings, quotes, code blocks,
|
|
6
|
+
//! lists and list items, tables, horizontal rules, links, and the whole
|
|
7
|
+
//! text-format model. Lexxy's additions live in the Ruby layer, as the
|
|
8
|
+
//! `Y::Lexxy` renderer's rule set: its own node types (attachments,
|
|
7
9
|
//! galleries, `early_escape_code`, `horizontal_divider`) and its decorations
|
|
8
|
-
//! of core nodes (the
|
|
9
|
-
//! nested-list-item class)
|
|
10
|
-
//!
|
|
11
|
-
//!
|
|
12
|
-
//!
|
|
13
|
-
//!
|
|
14
|
-
//!
|
|
15
|
-
//!
|
|
10
|
+
//! of core nodes (the figure around a table, header-cell styling, the
|
|
11
|
+
//! nested-list-item class). That rule set uses the same extension API
|
|
12
|
+
//! applications use. `Y::Lexical` is the core base class. The byte-parity
|
|
13
|
+
//! guarantee is held there too: the Ruby fixture tests and the
|
|
14
|
+
//! headless-Chrome end-to-end run pin `Y::Lexxy#to_html` against a real
|
|
15
|
+
//! editor's serialized value. The tests in this crate pin core output as
|
|
16
|
+
//! regression goldens. Stock Lexical has no canonical serializer to capture
|
|
17
|
+
//! from.
|
|
16
18
|
//!
|
|
17
|
-
//! Prior art: `ueberdosis/tiptap-php` renders ProseMirror JSON to HTML in
|
|
18
|
-
//! PHP
|
|
19
|
-
//!
|
|
19
|
+
//! Prior art: `ueberdosis/tiptap-php` renders ProseMirror JSON to HTML in
|
|
20
|
+
//! plain PHP, outside any JavaScript runtime. This crate does the same job
|
|
21
|
+
//! from the collaborative (Yjs) structure.
|
|
20
22
|
//!
|
|
21
|
-
//!
|
|
22
|
-
//!
|
|
23
|
-
//! (+`__tag`), `quote`, `code` (+`__language`), `list`/`listitem`,
|
|
24
|
-
//! `table`/`tablerow`/`tablecell`, `link`/`autolink` (inline)).
|
|
25
|
-
//! - Text runs are preceded by an embedded `Y.Map` carrying per-run metadata
|
|
26
|
-
//! (`__type: "text" | "code-highlight" | "tab"`, `__format` bitmask).
|
|
27
|
-
//! - `linebreak` is a bare metadata map; `tab` is a map followed by a "\t" run.
|
|
28
|
-
//! - Decorator nodes are `Y.XmlElement`s with their fields as plain
|
|
29
|
-
//! attributes (`horizontalrule` here; app/Lexxy decorators via rules).
|
|
23
|
+
//! How Lexical stores a document, checked against bytes captured from a live
|
|
24
|
+
//! editor:
|
|
30
25
|
//!
|
|
31
|
-
//!
|
|
32
|
-
//!
|
|
33
|
-
//!
|
|
34
|
-
//!
|
|
35
|
-
//!
|
|
36
|
-
//!
|
|
37
|
-
//!
|
|
38
|
-
//!
|
|
39
|
-
//!
|
|
40
|
-
//!
|
|
26
|
+
//! - Blocks are `Y.XmlText` with a `__type` attribute: `paragraph`,
|
|
27
|
+
//! `heading` (plus `__tag`), `quote`, `code` (plus `__language`), `list`
|
|
28
|
+
//! and `listitem`, `table`, `tablerow` and `tablecell`, and the inline
|
|
29
|
+
//! `link` and `autolink`.
|
|
30
|
+
//! - Each text run is preceded by an embedded `Y.Map` with its metadata: a
|
|
31
|
+
//! `__type` of `text`, `code-highlight`, or `tab`, and a `__format`
|
|
32
|
+
//! bitmask.
|
|
33
|
+
//! - A `linebreak` is a bare metadata map. A `tab` is a map followed by a
|
|
34
|
+
//! `"\t"` run.
|
|
35
|
+
//! - Decorator nodes are `Y.XmlElement`s whose fields are plain attributes.
|
|
36
|
+
//! `horizontalrule` is handled here. Application and Lexxy decorators come
|
|
37
|
+
//! in through rules.
|
|
41
38
|
//!
|
|
42
|
-
//!
|
|
43
|
-
//!
|
|
44
|
-
//!
|
|
45
|
-
//! `
|
|
39
|
+
//! Text formatting follows the export pipeline Lexxy runs: Lexical's
|
|
40
|
+
//! `$generateHtmlFromNodes`, then sanitize. That output is the only
|
|
41
|
+
//! formatting truth that can be pinned from outside. The inner tag is
|
|
42
|
+
//! `strong` for bold, or `em` for italic without bold. The outer tag is
|
|
43
|
+
//! `code`, `mark`, `sub`, or `sup`. An `<i>` wraps only when bold and italic
|
|
44
|
+
//! combine, because the `em` slot is taken. `<s>` and `<u>` wrap whenever
|
|
45
|
+
//! present. `<span>`s are unwrapped, so unformatted text is bare. A run's
|
|
46
|
+
//! `__style` (highlight colors) survives on the createDOM tag, filtered to
|
|
47
|
+
//! color and background-color. On a plain run, or one with only strike or
|
|
48
|
+
//! underline, it goes away with the span. The case-transform format bits
|
|
49
|
+
//! never render; their text-transform style is outside the sanitize
|
|
50
|
+
//! whitelist.
|
|
51
|
+
//!
|
|
52
|
+
//! A node type with no rule still renders its text and child blocks, just
|
|
53
|
+
//! unwrapped.
|
|
54
|
+
//!
|
|
55
|
+
//! Custom nodes register rules by `__type` (see `yjs-html-core`). A rule is
|
|
56
|
+
//! consulted before the built-in arms, so it can extend the schema or
|
|
57
|
+
//! replace a built-in. Declarative rules render here. Callback rules emit
|
|
58
|
+
//! `Segment::Deferred` for the caller to fill in after the render returns.
|
|
46
59
|
|
|
47
60
|
// README examples are living code: compile-checked on every cargo test.
|
|
48
61
|
#[cfg(doctest)]
|
|
@@ -50,8 +63,8 @@
|
|
|
50
63
|
mod readme_examples {}
|
|
51
64
|
|
|
52
65
|
// The full rules surface, re-exported: depend on this crate alone;
|
|
53
|
-
//
|
|
54
|
-
pub use
|
|
66
|
+
// yjs-html-core is an internal implementation crate.
|
|
67
|
+
pub use yjs_html_core::*;
|
|
55
68
|
use yrs::types::text::YChange;
|
|
56
69
|
use yrs::{
|
|
57
70
|
Any, GetString, Map, Out, ReadTxn, Text, Xml, XmlElementRef, XmlFragment, XmlFragmentRef,
|
|
@@ -130,7 +143,7 @@ pub fn render_segments<T: ReadTxn>(
|
|
|
130
143
|
/// callback rules, segments always flatten.
|
|
131
144
|
pub fn render<T: ReadTxn>(txn: &T, fragment: &XmlFragmentRef) -> Option<String> {
|
|
132
145
|
render_segments(txn, fragment, &Rules::empty()).map(|segs| {
|
|
133
|
-
|
|
146
|
+
yjs_html_core::flatten(segs)
|
|
134
147
|
.into_html()
|
|
135
148
|
.expect("no callback rules registered")
|
|
136
149
|
})
|
|
@@ -773,11 +786,7 @@ fn lexxy_style(style: &str) -> Option<String> {
|
|
|
773
786
|
css.push(';');
|
|
774
787
|
}
|
|
775
788
|
}
|
|
776
|
-
if css.is_empty() {
|
|
777
|
-
None
|
|
778
|
-
} else {
|
|
779
|
-
Some(css)
|
|
780
|
-
}
|
|
789
|
+
if css.is_empty() { None } else { Some(css) }
|
|
781
790
|
}
|
|
782
791
|
|
|
783
792
|
/// `link` / `autolink`: Lexxy's sanitize keeps only `href` and `title`
|
|
@@ -1301,7 +1310,7 @@ mod tests {
|
|
|
1301
1310
|
let txn = doc.transact();
|
|
1302
1311
|
let frag = txn.get_xml_fragment("root").unwrap();
|
|
1303
1312
|
let segs = render_segments(&txn, &frag, &rules).unwrap();
|
|
1304
|
-
let html =
|
|
1313
|
+
let html = yjs_html_core::flatten(segs).into_html().unwrap();
|
|
1305
1314
|
assert_eq!(
|
|
1306
1315
|
html,
|
|
1307
1316
|
"<p><a class=\"app-link\" href=\"https://x.example\">site</a>\
|
|
@@ -1,16 +1,17 @@
|
|
|
1
1
|
[package]
|
|
2
|
-
name = "
|
|
2
|
+
name = "prosemirror-yjs-html"
|
|
3
3
|
description = "Render ProseMirror-shaped yrs documents to HTML, no browser or Node required"
|
|
4
4
|
repository = "https://github.com/jpcamara/yrby"
|
|
5
5
|
readme = "README.md"
|
|
6
|
-
version = "0.1.
|
|
6
|
+
version = "0.1.4"
|
|
7
7
|
edition = "2024"
|
|
8
8
|
rust-version = "1.85"
|
|
9
9
|
authors = ["JP Camara <johnpcamara@gmail.com>"]
|
|
10
10
|
license = "MIT"
|
|
11
|
-
|
|
11
|
+
keywords = ["yjs", "crdt", "prosemirror", "tiptap", "html"]
|
|
12
|
+
categories = ["text-processing", "web-programming"]
|
|
12
13
|
|
|
13
14
|
[dependencies]
|
|
14
15
|
yrs = { version = "0.27", features = ["sync"] }
|
|
15
16
|
serde_json = "1.0"
|
|
16
|
-
|
|
17
|
+
yjs-html-core = { path = "../html-core", version = "0.1.3" }
|