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
data/ext/yrby/src/lib.rs
CHANGED
|
@@ -6,6 +6,8 @@ use yrs::updates::decoder::Decode;
|
|
|
6
6
|
use yrs::updates::encoder::Encode;
|
|
7
7
|
use yrs::{Doc, GetString, ReadTxn, Transact};
|
|
8
8
|
|
|
9
|
+
mod lexical_html;
|
|
10
|
+
mod prosemirror_html;
|
|
9
11
|
mod protocol;
|
|
10
12
|
mod read;
|
|
11
13
|
use protocol::{
|
|
@@ -30,6 +32,8 @@ struct RbDoc(Doc);
|
|
|
30
32
|
fn assert_thread_safe() {
|
|
31
33
|
fn is_send_sync<T: Send + Sync>() {}
|
|
32
34
|
is_send_sync::<Doc>();
|
|
35
|
+
is_send_sync::<RbLexical>();
|
|
36
|
+
is_send_sync::<RbProseMirror>();
|
|
33
37
|
}
|
|
34
38
|
|
|
35
39
|
/// Run `f` with the GVL (Global VM Lock) released, so other Ruby threads,
|
|
@@ -329,6 +333,105 @@ impl RbDoc {
|
|
|
329
333
|
}
|
|
330
334
|
}
|
|
331
335
|
|
|
336
|
+
// ============================================================================
|
|
337
|
+
// Y::Lexical — schema-pinned rendering of Lexical/Lexxy documents
|
|
338
|
+
// ============================================================================
|
|
339
|
+
|
|
340
|
+
/// A Lexical/Lexxy view over a `Y::Doc`. The schema knowledge (Lexxy 0.9.x
|
|
341
|
+
/// node set and serializer semantics) lives here rather than on the
|
|
342
|
+
/// schema-agnostic `Doc`. Holds a cheap clone of the doc (yrs `Doc` is an Arc
|
|
343
|
+
/// handle), so it reads live state.
|
|
344
|
+
///
|
|
345
|
+
/// Thread safety matches `Y::Doc`: every method opens its own transaction
|
|
346
|
+
/// inside `nogvl` and holds no lock across the GVL boundary.
|
|
347
|
+
#[magnus::wrap(class = "Y::Lexical", free_immediately, size)]
|
|
348
|
+
struct RbLexical(Doc);
|
|
349
|
+
|
|
350
|
+
impl RbLexical {
|
|
351
|
+
/// `Y::Lexical.new(doc)`
|
|
352
|
+
fn new(doc: &RbDoc) -> Self {
|
|
353
|
+
RbLexical(doc.0.clone())
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
/// Render the document's XML root (default `"root"`, Lexical's standard
|
|
357
|
+
/// collab root name) to HTML, natively — no Node process or headless
|
|
358
|
+
/// editor. Output matches Lexxy's own serializer (the HTML a lexxy-editor
|
|
359
|
+
/// submits to Rails) byte-for-byte on the reference fixture; see
|
|
360
|
+
/// `lexical_html.rs` for the schema coverage and caveats. Returns nil when
|
|
361
|
+
/// the root is missing or not Lexical-shaped, e.g. a ProseMirror document.
|
|
362
|
+
fn to_html(&self, args: &[Value]) -> Result<Option<String>, Error> {
|
|
363
|
+
if args.len() > 1 {
|
|
364
|
+
let ruby = Ruby::get().unwrap();
|
|
365
|
+
return Err(Error::new(
|
|
366
|
+
ruby.exception_arg_error(),
|
|
367
|
+
format!(
|
|
368
|
+
"wrong number of arguments (given {}, expected 0..1)",
|
|
369
|
+
args.len()
|
|
370
|
+
),
|
|
371
|
+
));
|
|
372
|
+
}
|
|
373
|
+
let name: String = match args.first() {
|
|
374
|
+
Some(arg) => TryConvert::try_convert(*arg)?,
|
|
375
|
+
None => "root".to_string(),
|
|
376
|
+
};
|
|
377
|
+
let doc = &self.0;
|
|
378
|
+
Ok(nogvl(move || {
|
|
379
|
+
let txn = doc.transact();
|
|
380
|
+
let fragment = txn.get_xml_fragment(name.as_str())?;
|
|
381
|
+
lexical_html::render(&txn, &fragment)
|
|
382
|
+
}))
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
// ============================================================================
|
|
387
|
+
// Y::ProseMirror — schema-pinned rendering of ProseMirror/Tiptap documents
|
|
388
|
+
// ============================================================================
|
|
389
|
+
|
|
390
|
+
/// A ProseMirror/Tiptap view over a `Y::Doc`. The schema knowledge (node/mark
|
|
391
|
+
/// names, Tiptap's serializer semantics) lives here rather than on the
|
|
392
|
+
/// schema-agnostic `Doc`. Holds a cheap clone of the doc (yrs `Doc` is an Arc
|
|
393
|
+
/// handle), so it reads live state.
|
|
394
|
+
///
|
|
395
|
+
/// Thread safety matches `Y::Doc`: every method opens its own transaction
|
|
396
|
+
/// inside `nogvl` and holds no lock across the GVL boundary.
|
|
397
|
+
#[magnus::wrap(class = "Y::ProseMirror", free_immediately, size)]
|
|
398
|
+
struct RbProseMirror(Doc);
|
|
399
|
+
|
|
400
|
+
impl RbProseMirror {
|
|
401
|
+
/// `Y::ProseMirror.new(doc)`
|
|
402
|
+
fn new(doc: &RbDoc) -> Self {
|
|
403
|
+
RbProseMirror(doc.0.clone())
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
/// Render an XML root (default `"default"`, the fragment name Tiptap's
|
|
407
|
+
/// Collaboration extension uses) to HTML. Output follows tiptap-php and
|
|
408
|
+
/// matches Tiptap's `getHTML()` on the reference fixture; see
|
|
409
|
+
/// `prosemirror_html.rs` for coverage and caveats. Returns nil when the
|
|
410
|
+
/// root is missing or not ProseMirror-shaped (e.g. a Lexical document).
|
|
411
|
+
fn to_html(&self, args: &[Value]) -> Result<Option<String>, Error> {
|
|
412
|
+
if args.len() > 1 {
|
|
413
|
+
let ruby = Ruby::get().unwrap();
|
|
414
|
+
return Err(Error::new(
|
|
415
|
+
ruby.exception_arg_error(),
|
|
416
|
+
format!(
|
|
417
|
+
"wrong number of arguments (given {}, expected 0..1)",
|
|
418
|
+
args.len()
|
|
419
|
+
),
|
|
420
|
+
));
|
|
421
|
+
}
|
|
422
|
+
let name: String = match args.first() {
|
|
423
|
+
Some(arg) => TryConvert::try_convert(*arg)?,
|
|
424
|
+
None => "default".to_string(),
|
|
425
|
+
};
|
|
426
|
+
let doc = &self.0;
|
|
427
|
+
Ok(nogvl(move || {
|
|
428
|
+
let txn = doc.transact();
|
|
429
|
+
let fragment = txn.get_xml_fragment(name.as_str())?;
|
|
430
|
+
prosemirror_html::render(&txn, &fragment)
|
|
431
|
+
}))
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
|
|
332
435
|
// ============================================================================
|
|
333
436
|
// Protocol codec (stateless), exposed as `Y` module functions
|
|
334
437
|
// ============================================================================
|
|
@@ -409,6 +512,15 @@ fn init(ruby: &Ruby) -> Result<(), Error> {
|
|
|
409
512
|
"handle_sync_message",
|
|
410
513
|
method!(RbDoc::handle_sync_message, 1),
|
|
411
514
|
)?;
|
|
515
|
+
// Y::Lexical: schema-pinned Lexical/Lexxy rendering over a Doc.
|
|
516
|
+
let lexical_class = module.define_class("Lexical", ruby.class_object())?;
|
|
517
|
+
lexical_class.define_singleton_method("new", function!(RbLexical::new, 1))?;
|
|
518
|
+
lexical_class.define_method("to_html", method!(RbLexical::to_html, -1))?;
|
|
519
|
+
// Y::ProseMirror: schema-pinned ProseMirror/Tiptap rendering over a Doc.
|
|
520
|
+
let prosemirror_class = module.define_class("ProseMirror", ruby.class_object())?;
|
|
521
|
+
prosemirror_class.define_singleton_method("new", function!(RbProseMirror::new, 1))?;
|
|
522
|
+
prosemirror_class.define_method("to_html", method!(RbProseMirror::to_html, -1))?;
|
|
523
|
+
|
|
412
524
|
// Stateless protocol codec, as Y module functions.
|
|
413
525
|
module.define_module_function("wrap_update", function!(wrap_update, 1))?;
|
|
414
526
|
module.define_module_function("message_kind", function!(message_kind, 1))?;
|