Module: Y::Decoder
- Defined in:
- lib/y/decoder.rb
Overview
Plain-text reconstruction of a stored Yjs document, for search indexing
and previews. It reads the text out of the shared type the editor uses
(Lexical's Y.XmlText, plain Y.Text, or ProseMirror's Y.XmlFragment)
in-process, on the same native extension as Doc: no Node, no
subprocess.
state = doc.encode_state_as_update # opaque CRDT bytes from the store
Y::Decoder.text(state) # => "hello world"
Y::Decoder.preview(state, 280) # => "hello world…"
For full-fidelity HTML, Y::Lexxy and Y::Tiptap render the document
with the editor's own semantics; this module is the cheap plain-text
path.
Defined Under Namespace
Classes: Error
Class Method Summary collapse
- .load(state) ⇒ Object
- .normalize(text) ⇒ Object
-
.preview(state, limit: 280, field: nil) ⇒ Object
A compact, single-line preview for list UIs.
- .strip_tags(markup) ⇒ Object
-
.text(state, field: nil) ⇒ Object
Plain text of the document.
Class Method Details
.load(state) ⇒ Object
49 50 51 |
# File 'lib/y/decoder.rb', line 49 def load(state) Y::Doc.new.tap { |doc| doc.apply_update(state) } end |
.normalize(text) ⇒ Object
57 58 59 60 61 62 |
# File 'lib/y/decoder.rb', line 57 def normalize(text) text.gsub(/[ \t]+/, " ") # collapse runs of spaces/tabs .gsub(/ *\n */, "\n") # trim spaces left around block separators .gsub(/\n{3,}/, "\n\n") # cap blank-line runs .strip end |
.preview(state, limit: 280, field: nil) ⇒ Object
A compact, single-line preview for list UIs.
44 45 46 47 |
# File 'lib/y/decoder.rb', line 44 def preview(state, limit: 280, field: nil) body = text(state, field: field).gsub(/\s+/, " ").strip body.length > limit ? "#{body[0, limit].rstrip}…" : body end |
.strip_tags(markup) ⇒ Object
53 54 55 |
# File 'lib/y/decoder.rb', line 53 def (markup) markup.gsub(/<[^>]*>/, " ") end |
.text(state, field: nil) ⇒ Object
Plain text of the document. field pins the root key (Lexical: the editor
id; ProseMirror: "default"); omit it to use the document's sole root.
26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 |
# File 'lib/y/decoder.rb', line 26 def text(state, field: nil) field ||= Y::Doc.new.tap { |d| d.apply_update(state) }.root_names.first return "" unless field # A plain `Y.Text` root (a simple shared-text editor) reads straight out. # (A yrs root's type is fixed by its first typed access, so each reader # gets a fresh doc to try a different shared type against the same state.) direct = load(state).read_text(field) return normalize(direct) if direct && !direct.strip.empty? # Lexical (each block a sibling `Y.XmlText`) and ProseMirror (blocks are # `Y.XmlElement`s) both come back from read_xml as block-per-line markup; # strip any element tags to plain text. markup = load(state).read_xml(field) markup ? normalize((markup)) : "" end |