yrby 0.6.1 → 0.7.1

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.
data/lib/y/decoder.rb ADDED
@@ -0,0 +1,64 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "y"
4
+
5
+ module Y
6
+ # Plain-text reconstruction of a stored Yjs document, for search indexing
7
+ # and previews. It reads the text out of the shared type the editor uses
8
+ # (Lexical's `Y.XmlText`, plain `Y.Text`, or ProseMirror's `Y.XmlFragment`)
9
+ # in-process, on the same native extension as `Doc`: no Node, no
10
+ # subprocess.
11
+ #
12
+ # state = doc.encode_state_as_update # opaque CRDT bytes from the store
13
+ # Y::Decoder.text(state) # => "hello world"
14
+ # Y::Decoder.preview(state, 280) # => "hello world…"
15
+ #
16
+ # For full-fidelity HTML, `Y::Lexxy` and `Y::Tiptap` render the document
17
+ # with the editor's own semantics; this module is the cheap plain-text
18
+ # path.
19
+ module Decoder
20
+ class Error < Y::Error; end
21
+
22
+ module_function
23
+
24
+ # Plain text of the document. `field` pins the root key (Lexical: the editor
25
+ # id; ProseMirror: "default"); omit it to use the document's sole root.
26
+ def text(state, field: nil)
27
+ field ||= Y::Doc.new.tap { |d| d.apply_update(state) }.root_names.first
28
+ return "" unless field
29
+
30
+ # A plain `Y.Text` root (a simple shared-text editor) reads straight out.
31
+ # (A yrs root's type is fixed by its first typed access, so each reader
32
+ # gets a fresh doc to try a different shared type against the same state.)
33
+ direct = load(state).read_text(field)
34
+ return normalize(direct) if direct && !direct.strip.empty?
35
+
36
+ # Lexical (each block a sibling `Y.XmlText`) and ProseMirror (blocks are
37
+ # `Y.XmlElement`s) both come back from read_xml as block-per-line markup;
38
+ # strip any element tags to plain text.
39
+ markup = load(state).read_xml(field)
40
+ markup ? normalize(strip_tags(markup)) : ""
41
+ end
42
+
43
+ # A compact, single-line preview for list UIs.
44
+ def preview(state, limit: 280, field: nil)
45
+ body = text(state, field: field).gsub(/\s+/, " ").strip
46
+ body.length > limit ? "#{body[0, limit].rstrip}…" : body
47
+ end
48
+
49
+ def load(state)
50
+ Y::Doc.new.tap { |doc| doc.apply_update(state) }
51
+ end
52
+
53
+ def strip_tags(markup)
54
+ markup.gsub(/<[^>]*>/, " ")
55
+ end
56
+
57
+ def normalize(text)
58
+ text.gsub(/[ \t]+/, " ") # collapse runs of spaces/tabs
59
+ .gsub(/ *\n */, "\n") # trim spaces left around block separators
60
+ .gsub(/\n{3,}/, "\n\n") # cap blank-line runs
61
+ .strip
62
+ end
63
+ end
64
+ end
data/lib/y/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Y
4
- VERSION = "0.6.1"
4
+ VERSION = "0.7.1"
5
5
  end
data/lib/y.rb CHANGED
@@ -15,9 +15,10 @@ end
15
15
  require_relative "y/rendering"
16
16
  require_relative "y/lexxy"
17
17
  require_relative "y/tiptap"
18
+ require_relative "y/decoder"
18
19
 
19
20
  module Y
20
21
  # Doc, Error, and the protocol module functions are defined in the Rust
21
22
  # extension. The ActionCable integration (Y::ActionCable::Sync) lives in the
22
- # separate `yrby-actioncable` gem; require "y/action_cable".
23
+ # `yrby-rails` gem; require "y/action_cable".
23
24
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: yrby
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.6.1
4
+ version: 0.7.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - JP Camara
@@ -105,6 +105,7 @@ files:
105
105
  - lib/generators/yrby/tables/tables_generator.rb
106
106
  - lib/generators/yrby/tables/templates/create_y_tables.rb
107
107
  - lib/y.rb
108
+ - lib/y/decoder.rb
108
109
  - lib/y/lexxy.rb
109
110
  - lib/y/rendering.rb
110
111
  - lib/y/tiptap.rb