prosereflect 0.1.1 → 0.3.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/.github/workflows/docs.yml +63 -0
- data/.github/workflows/links.yml +97 -0
- data/.github/workflows/rake.yml +4 -0
- data/.github/workflows/release.yml +5 -0
- data/.gitignore +4 -0
- data/.rubocop.yml +19 -1
- data/.rubocop_todo.yml +119 -183
- data/CLAUDE.md +78 -0
- data/Gemfile +8 -4
- data/README.adoc +2 -0
- data/Rakefile +3 -3
- data/docs/Gemfile +10 -0
- data/docs/INDEX.adoc +45 -0
- data/docs/_advanced/index.adoc +15 -0
- data/docs/_advanced/schema.adoc +112 -0
- data/docs/_advanced/step-map.adoc +66 -0
- data/docs/_advanced/steps.adoc +88 -0
- data/docs/_advanced/test-builder.adoc +61 -0
- data/docs/_advanced/transform.adoc +92 -0
- data/docs/_config.yml +174 -0
- data/docs/_features/html-input.adoc +69 -0
- data/docs/_features/html-output.adoc +45 -0
- data/docs/_features/index.adoc +15 -0
- data/docs/_features/marks.adoc +86 -0
- data/docs/_features/node-types.adoc +124 -0
- data/docs/_features/user-mentions.adoc +47 -0
- data/docs/_guides/custom-nodes.adoc +107 -0
- data/docs/_guides/index.adoc +13 -0
- data/docs/_guides/round-trip-html.adoc +91 -0
- data/docs/_guides/serialization.adoc +109 -0
- data/docs/_pages/index.adoc +67 -0
- data/docs/_reference/document-api.adoc +49 -0
- data/docs/_reference/index.adoc +14 -0
- data/docs/_reference/node-api.adoc +79 -0
- data/docs/_reference/schema-api.adoc +95 -0
- data/docs/_reference/transform-api.adoc +77 -0
- data/docs/_understanding/document-model.adoc +65 -0
- data/docs/_understanding/fragment.adoc +52 -0
- data/docs/_understanding/index.adoc +14 -0
- data/docs/_understanding/resolved-position.adoc +53 -0
- data/docs/_understanding/slice.adoc +54 -0
- data/docs/lychee.toml +63 -0
- data/lib/prosereflect/attribute/base.rb +4 -6
- data/lib/prosereflect/attribute/bold.rb +2 -4
- data/lib/prosereflect/attribute/href.rb +1 -3
- data/lib/prosereflect/attribute/id.rb +7 -7
- data/lib/prosereflect/attribute.rb +4 -7
- data/lib/prosereflect/blockquote.rb +19 -11
- data/lib/prosereflect/bullet_list.rb +36 -29
- data/lib/prosereflect/code_block.rb +23 -27
- data/lib/prosereflect/code_block_wrapper.rb +12 -13
- data/lib/prosereflect/document.rb +14 -22
- data/lib/prosereflect/fragment.rb +249 -0
- data/lib/prosereflect/hard_break.rb +6 -6
- data/lib/prosereflect/heading.rb +14 -15
- data/lib/prosereflect/horizontal_rule.rb +23 -14
- data/lib/prosereflect/image.rb +32 -23
- data/lib/prosereflect/input/html.rb +179 -104
- data/lib/prosereflect/input.rb +7 -0
- data/lib/prosereflect/list_item.rb +11 -12
- data/lib/prosereflect/mark/base.rb +9 -11
- data/lib/prosereflect/mark/bold.rb +1 -3
- data/lib/prosereflect/mark/code.rb +1 -3
- data/lib/prosereflect/mark/italic.rb +1 -3
- data/lib/prosereflect/mark/link.rb +1 -3
- data/lib/prosereflect/mark/strike.rb +1 -3
- data/lib/prosereflect/mark/subscript.rb +1 -3
- data/lib/prosereflect/mark/superscript.rb +1 -3
- data/lib/prosereflect/mark/underline.rb +1 -3
- data/lib/prosereflect/mark.rb +9 -5
- data/lib/prosereflect/node.rb +171 -33
- data/lib/prosereflect/ordered_list.rb +17 -14
- data/lib/prosereflect/output/html.rb +279 -50
- data/lib/prosereflect/output.rb +7 -0
- data/lib/prosereflect/paragraph.rb +11 -13
- data/lib/prosereflect/parser.rb +56 -66
- data/lib/prosereflect/resolved_pos.rb +256 -0
- data/lib/prosereflect/schema/attribute.rb +57 -0
- data/lib/prosereflect/schema/content_match.rb +656 -0
- data/lib/prosereflect/schema/fragment.rb +166 -0
- data/lib/prosereflect/schema/mark.rb +121 -0
- data/lib/prosereflect/schema/mark_type.rb +130 -0
- data/lib/prosereflect/schema/node.rb +236 -0
- data/lib/prosereflect/schema/node_type.rb +274 -0
- data/lib/prosereflect/schema/schema_main.rb +190 -0
- data/lib/prosereflect/schema/spec.rb +92 -0
- data/lib/prosereflect/schema.rb +39 -0
- data/lib/prosereflect/table.rb +12 -13
- data/lib/prosereflect/table_cell.rb +13 -13
- data/lib/prosereflect/table_header.rb +17 -17
- data/lib/prosereflect/table_row.rb +12 -12
- data/lib/prosereflect/text.rb +35 -11
- data/lib/prosereflect/transform/attr_step.rb +157 -0
- data/lib/prosereflect/transform/insert_step.rb +115 -0
- data/lib/prosereflect/transform/mapping.rb +82 -0
- data/lib/prosereflect/transform/mark_step.rb +269 -0
- data/lib/prosereflect/transform/replace_around_step.rb +181 -0
- data/lib/prosereflect/transform/replace_step.rb +157 -0
- data/lib/prosereflect/transform/slice.rb +91 -0
- data/lib/prosereflect/transform/step.rb +89 -0
- data/lib/prosereflect/transform/step_map.rb +126 -0
- data/lib/prosereflect/transform/structure.rb +120 -0
- data/lib/prosereflect/transform/transform.rb +341 -0
- data/lib/prosereflect/transform.rb +26 -0
- data/lib/prosereflect/user.rb +15 -15
- data/lib/prosereflect/version.rb +1 -1
- data/lib/prosereflect.rb +30 -17
- data/prosereflect.gemspec +17 -16
- data/spec/fixtures/documents/formatted_text.yaml +14 -0
- data/spec/fixtures/documents/heading_paragraph.yaml +16 -0
- data/spec/fixtures/documents/lists_doc.yaml +32 -0
- data/spec/fixtures/documents/mixed_content.yaml +40 -0
- data/spec/fixtures/documents/nested_doc.yaml +20 -0
- data/spec/fixtures/documents/simple_doc.yaml +6 -0
- data/spec/fixtures/documents/table_doc.yaml +32 -0
- data/spec/fixtures/documents/transform_test.yaml +14 -0
- data/spec/fixtures/schema/custom_schema.rb +37 -0
- data/spec/fixtures/schema/test_schema.rb +46 -0
- data/spec/fixtures/test_builder/helpers.rb +212 -0
- data/spec/prosereflect/document_spec.rb +332 -330
- data/spec/prosereflect/fragment_spec.rb +273 -0
- data/spec/prosereflect/hard_break_spec.rb +125 -125
- data/spec/prosereflect/input/html_spec.rb +718 -522
- data/spec/prosereflect/node_spec.rb +311 -182
- data/spec/prosereflect/output/html_spec.rb +105 -105
- data/spec/prosereflect/output/whitespace_spec.rb +248 -0
- data/spec/prosereflect/paragraph_spec.rb +275 -274
- data/spec/prosereflect/parser/round_trip_spec.rb +472 -0
- data/spec/prosereflect/parser_spec.rb +185 -180
- data/spec/prosereflect/resolved_pos_spec.rb +74 -0
- data/spec/prosereflect/schema/conftest.rb +68 -0
- data/spec/prosereflect/schema/content_match_spec.rb +237 -0
- data/spec/prosereflect/schema/mark_spec.rb +274 -0
- data/spec/prosereflect/schema/mark_type_spec.rb +86 -0
- data/spec/prosereflect/schema/node_type_spec.rb +142 -0
- data/spec/prosereflect/schema/schema_spec.rb +194 -0
- data/spec/prosereflect/table_cell_spec.rb +183 -183
- data/spec/prosereflect/table_row_spec.rb +149 -149
- data/spec/prosereflect/table_spec.rb +320 -318
- data/spec/prosereflect/test_builder/marks_spec.rb +127 -0
- data/spec/prosereflect/text_spec.rb +133 -132
- data/spec/prosereflect/transform/equivalence_spec.rb +487 -0
- data/spec/prosereflect/transform/mapping_spec.rb +226 -0
- data/spec/prosereflect/transform/replace_spec.rb +832 -0
- data/spec/prosereflect/transform/replace_step_spec.rb +157 -0
- data/spec/prosereflect/transform/slice_spec.rb +48 -0
- data/spec/prosereflect/transform/step_map_spec.rb +70 -0
- data/spec/prosereflect/transform/step_spec.rb +211 -0
- data/spec/prosereflect/transform/structure_spec.rb +98 -0
- data/spec/prosereflect/transform/transform_spec.rb +238 -0
- data/spec/prosereflect/user_spec.rb +31 -28
- data/spec/prosereflect_spec.rb +28 -26
- data/spec/spec_helper.rb +7 -6
- data/spec/support/matchers.rb +6 -6
- data/spec/support/shared_examples.rb +49 -49
- metadata +96 -5
- data/spec/prosereflect/version_spec.rb +0 -11
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
---
|
|
2
|
+
layout: default
|
|
3
|
+
title: Understanding
|
|
4
|
+
nav_order: 4
|
|
5
|
+
has_children: true
|
|
6
|
+
---
|
|
7
|
+
= Understanding
|
|
8
|
+
|
|
9
|
+
This section explains the core concepts behind prosereflect's document model.
|
|
10
|
+
|
|
11
|
+
* x:document-model[Document Model] -- Node hierarchy and content model
|
|
12
|
+
* x:fragment[Fragment] -- Flat content collections
|
|
13
|
+
* x:resolved-position[Resolved Positions] -- Position resolution in the document tree
|
|
14
|
+
* x:slice[Slice] -- Document slices with open boundaries
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
---
|
|
2
|
+
layout: default
|
|
3
|
+
title: Resolved Positions
|
|
4
|
+
parent: Understanding
|
|
5
|
+
nav_order: 3
|
|
6
|
+
---
|
|
7
|
+
= Resolved Positions
|
|
8
|
+
|
|
9
|
+
`Prosereflect::ResolvedPos` represents a document position that has been resolved to a specific location in the document tree. Resolving a position gives you information about the surrounding context.
|
|
10
|
+
|
|
11
|
+
== Creating a ResolvedPos
|
|
12
|
+
|
|
13
|
+
[source,ruby]
|
|
14
|
+
----
|
|
15
|
+
doc = Prosereflect::Parser.parse_document(data)
|
|
16
|
+
resolved = doc.resolve(3)
|
|
17
|
+
----
|
|
18
|
+
|
|
19
|
+
== Properties
|
|
20
|
+
|
|
21
|
+
* `pos` -- the absolute document position
|
|
22
|
+
* `depth` -- how deep in the tree this position is (0 = root)
|
|
23
|
+
* `parent` -- the parent node at current depth
|
|
24
|
+
* `parent_offset` -- offset within the parent node (`pos - start`)
|
|
25
|
+
|
|
26
|
+
== Depth-based Access
|
|
27
|
+
|
|
28
|
+
These methods accept an optional `depth` parameter (default: current depth):
|
|
29
|
+
|
|
30
|
+
* `node(depth)` -- node at the given depth
|
|
31
|
+
* `index(depth)` -- index within parent at depth
|
|
32
|
+
* `start(depth)` -- start position of node at depth
|
|
33
|
+
* `end_(depth)` -- end position of node at depth
|
|
34
|
+
|
|
35
|
+
== Boundary Checks
|
|
36
|
+
|
|
37
|
+
* `block?` -- whether the parent is a block node
|
|
38
|
+
* `inline?` -- whether the parent is an inline node
|
|
39
|
+
* `text_block?` -- whether the parent is a text block
|
|
40
|
+
* `start_of_parent?` -- whether at start of parent
|
|
41
|
+
* `end_of_parent?` -- whether at end of parent
|
|
42
|
+
* `before?` -- whether at the start of the current node
|
|
43
|
+
* `after?` -- whether at the end of the current node
|
|
44
|
+
|
|
45
|
+
== Comparing Positions
|
|
46
|
+
|
|
47
|
+
* `shared_depth(other)` -- the deepest depth shared with another position
|
|
48
|
+
* `block_range(other)` -- create a NodeRange between two positions
|
|
49
|
+
|
|
50
|
+
== Marks
|
|
51
|
+
|
|
52
|
+
* `marks` -- marks at this position
|
|
53
|
+
* `marks_between(from, to, marks)` -- collect marks between positions
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
---
|
|
2
|
+
layout: default
|
|
3
|
+
title: Slice
|
|
4
|
+
parent: Understanding
|
|
5
|
+
nav_order: 4
|
|
6
|
+
---
|
|
7
|
+
= Slice
|
|
8
|
+
|
|
9
|
+
`Prosereflect::Transform::Slice` represents a contiguous portion of a document that can be inserted, deleted, or moved. Slices track "open" boundaries for proper node joining during replacements.
|
|
10
|
+
|
|
11
|
+
== Creating Slices
|
|
12
|
+
|
|
13
|
+
[source,ruby]
|
|
14
|
+
----
|
|
15
|
+
# From a fragment
|
|
16
|
+
slice = Prosereflect::Transform::Slice.new(fragment)
|
|
17
|
+
|
|
18
|
+
# With open boundaries
|
|
19
|
+
slice = Prosereflect::Transform::Slice.new(fragment, open_start: 1, open_end: 1)
|
|
20
|
+
|
|
21
|
+
# Empty slice
|
|
22
|
+
slice = Prosereflect::Transform::Slice.empty
|
|
23
|
+
----
|
|
24
|
+
|
|
25
|
+
== Open Boundaries
|
|
26
|
+
|
|
27
|
+
`open_start` and `open_end` indicate how many levels of the slice's first/last nodes are "open" (not included in the content). This is used when splitting or joining nodes during transforms:
|
|
28
|
+
|
|
29
|
+
* `open_start = 0` -- the first node is complete
|
|
30
|
+
* `open_start = 1` -- the first node is open at the top (its content extends beyond the slice)
|
|
31
|
+
|
|
32
|
+
== Properties
|
|
33
|
+
|
|
34
|
+
* `content` -- the `Fragment` containing the slice's nodes
|
|
35
|
+
* `open_start` -- number of open levels at the start
|
|
36
|
+
* `open_end` -- number of open levels at the end
|
|
37
|
+
* `size` -- total size including open boundaries (`content_size + open_start + open_end`)
|
|
38
|
+
* `content_size` -- size of just the content fragment
|
|
39
|
+
* `empty?` -- whether the slice has no content and no open boundaries
|
|
40
|
+
|
|
41
|
+
== Methods
|
|
42
|
+
|
|
43
|
+
* `cut(from, to)` -- return a sub-slice
|
|
44
|
+
* `eq?(other)` -- structural equality check
|
|
45
|
+
|
|
46
|
+
== Use in Transforms
|
|
47
|
+
|
|
48
|
+
Slices are used as the replacement content in `ReplaceStep` and `ReplaceAroundStep`:
|
|
49
|
+
|
|
50
|
+
[source,ruby]
|
|
51
|
+
----
|
|
52
|
+
tx = Prosereflect::Transform::Transform.new(doc)
|
|
53
|
+
tx.replace(2, 5, Prosereflect::Transform::Slice.new(fragment))
|
|
54
|
+
----
|
data/docs/lychee.toml
ADDED
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
# Lychee Link Checker Configuration
|
|
2
|
+
# For Prosereflect Documentation
|
|
3
|
+
# https://github.com/lycheeverse/lychee
|
|
4
|
+
|
|
5
|
+
# Cache results to avoid re-checking same URLs
|
|
6
|
+
cache = true
|
|
7
|
+
max_cache_age = "1d"
|
|
8
|
+
|
|
9
|
+
# Check both source files and built site
|
|
10
|
+
include_verbatim = true
|
|
11
|
+
|
|
12
|
+
# File types to check (regex patterns)
|
|
13
|
+
include = [
|
|
14
|
+
"_site/**/*.html",
|
|
15
|
+
".*\\.adoc$",
|
|
16
|
+
".*\\.md$"
|
|
17
|
+
]
|
|
18
|
+
|
|
19
|
+
# Excluded paths
|
|
20
|
+
exclude = [
|
|
21
|
+
".git",
|
|
22
|
+
".github",
|
|
23
|
+
"node_modules",
|
|
24
|
+
"vendor",
|
|
25
|
+
".bundle",
|
|
26
|
+
".sass-cache",
|
|
27
|
+
".jekyll-cache",
|
|
28
|
+
"_site/.jekyll-cache",
|
|
29
|
+
"Gemfile.lock"
|
|
30
|
+
]
|
|
31
|
+
|
|
32
|
+
# Link checking behavior
|
|
33
|
+
max_redirects = 10
|
|
34
|
+
max_retries = 3
|
|
35
|
+
timeout = 30
|
|
36
|
+
|
|
37
|
+
# Accept status codes
|
|
38
|
+
accept = [
|
|
39
|
+
"100..=103", # Informational
|
|
40
|
+
"200..=299", # Success
|
|
41
|
+
"429" # Too Many Requests (retry handled by max_retries)
|
|
42
|
+
]
|
|
43
|
+
|
|
44
|
+
# User agent to identify ourselves
|
|
45
|
+
user_agent = "lychee/prosereflect-docs-link-checker"
|
|
46
|
+
|
|
47
|
+
# Check HTTP, HTTPS, and file:// schemes
|
|
48
|
+
scheme = ["https", "http", "file"]
|
|
49
|
+
|
|
50
|
+
# Handle different link types
|
|
51
|
+
include_mail = false # Don't check mailto: links
|
|
52
|
+
|
|
53
|
+
# Maximum concurrent requests
|
|
54
|
+
max_concurrency = 10
|
|
55
|
+
|
|
56
|
+
# Verbose output for debugging
|
|
57
|
+
verbose = "warn"
|
|
58
|
+
|
|
59
|
+
# Require HTTPS where possible
|
|
60
|
+
require_https = false # Don't enforce
|
|
61
|
+
|
|
62
|
+
# Index files for directory URLs
|
|
63
|
+
index_files = ["index.html"]
|
|
@@ -1,24 +1,22 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require_relative '../attribute'
|
|
4
|
-
|
|
5
3
|
module Prosereflect
|
|
6
4
|
module Attribute
|
|
7
5
|
class Base < Lutaml::Model::Serializable
|
|
8
|
-
PM_TYPE =
|
|
6
|
+
PM_TYPE = "attribute"
|
|
9
7
|
|
|
10
8
|
attribute :type, :string, default: lambda {
|
|
11
9
|
begin
|
|
12
10
|
self.class.const_get(:PM_TYPE)
|
|
13
11
|
rescue StandardError
|
|
14
|
-
|
|
12
|
+
"attribute"
|
|
15
13
|
end
|
|
16
14
|
}
|
|
17
15
|
attribute :value, :string
|
|
18
16
|
|
|
19
17
|
key_value do
|
|
20
|
-
map
|
|
21
|
-
map
|
|
18
|
+
map "type", to: :type, render_default: true
|
|
19
|
+
map "value", to: :value
|
|
22
20
|
end
|
|
23
21
|
|
|
24
22
|
def self.create(type, value)
|
|
@@ -1,15 +1,13 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require_relative 'base'
|
|
4
|
-
|
|
5
3
|
module Prosereflect
|
|
6
4
|
module Attribute
|
|
7
5
|
class Bold < Base
|
|
8
|
-
PM_TYPE =
|
|
6
|
+
PM_TYPE = "bold"
|
|
9
7
|
|
|
10
8
|
def initialize(options = {})
|
|
11
9
|
super
|
|
12
|
-
self.type =
|
|
10
|
+
self.type = "bold"
|
|
13
11
|
end
|
|
14
12
|
|
|
15
13
|
def attrs
|
|
@@ -1,23 +1,23 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require_relative 'base'
|
|
4
|
-
|
|
5
3
|
module Prosereflect
|
|
6
4
|
module Attribute
|
|
7
5
|
class Id < Base
|
|
8
|
-
PM_TYPE =
|
|
6
|
+
PM_TYPE = "id"
|
|
9
7
|
|
|
10
|
-
attribute :type, :string, default: -> {
|
|
8
|
+
attribute :type, :string, default: -> {
|
|
9
|
+
self.class.send(:const_get, "PM_TYPE")
|
|
10
|
+
}
|
|
11
11
|
attribute :id, :string
|
|
12
12
|
|
|
13
13
|
key_value do
|
|
14
|
-
map
|
|
15
|
-
map
|
|
14
|
+
map "type", to: :type, render_default: true
|
|
15
|
+
map "id", to: :id
|
|
16
16
|
end
|
|
17
17
|
|
|
18
18
|
# Convert to hash for serialization
|
|
19
19
|
def to_h
|
|
20
|
-
{
|
|
20
|
+
{ "id" => id }
|
|
21
21
|
end
|
|
22
22
|
end
|
|
23
23
|
end
|
|
@@ -1,13 +1,10 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require 'lutaml/model'
|
|
4
|
-
|
|
5
3
|
module Prosereflect
|
|
6
4
|
module Attribute
|
|
5
|
+
autoload :Base, "#{__dir__}/attribute/base"
|
|
6
|
+
autoload :Href, "#{__dir__}/attribute/href"
|
|
7
|
+
autoload :Id, "#{__dir__}/attribute/id"
|
|
8
|
+
autoload :Bold, "#{__dir__}/attribute/bold"
|
|
7
9
|
end
|
|
8
10
|
end
|
|
9
|
-
|
|
10
|
-
require_relative 'attribute/base'
|
|
11
|
-
require_relative 'attribute/href'
|
|
12
|
-
require_relative 'attribute/id'
|
|
13
|
-
require_relative 'attribute/bold'
|
|
@@ -1,21 +1,20 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require_relative 'node'
|
|
4
|
-
require_relative 'paragraph'
|
|
5
|
-
|
|
6
3
|
module Prosereflect
|
|
7
4
|
# It can contain other block-level content like paragraphs, lists, etc.
|
|
8
5
|
class Blockquote < Node
|
|
9
|
-
PM_TYPE =
|
|
6
|
+
PM_TYPE = "blockquote"
|
|
10
7
|
|
|
11
|
-
attribute :type, :string, default: -> {
|
|
8
|
+
attribute :type, :string, default: -> {
|
|
9
|
+
self.class.send(:const_get, "PM_TYPE")
|
|
10
|
+
}
|
|
12
11
|
attribute :citation, :string
|
|
13
12
|
attribute :attrs, :hash
|
|
14
13
|
|
|
15
14
|
key_value do
|
|
16
|
-
map
|
|
17
|
-
map
|
|
18
|
-
map
|
|
15
|
+
map "type", to: :type, render_default: true
|
|
16
|
+
map "content", to: :content
|
|
17
|
+
map "attrs", to: :attrs
|
|
19
18
|
end
|
|
20
19
|
|
|
21
20
|
def initialize(attributes = {})
|
|
@@ -56,12 +55,12 @@ module Prosereflect
|
|
|
56
55
|
# Update citation/source for the blockquote
|
|
57
56
|
def citation=(source)
|
|
58
57
|
self.attrs ||= {}
|
|
59
|
-
attrs[
|
|
58
|
+
attrs["citation"] = source
|
|
60
59
|
end
|
|
61
60
|
|
|
62
61
|
# Get citation/source of the blockquote
|
|
63
62
|
def citation
|
|
64
|
-
attrs&.[](
|
|
63
|
+
attrs&.[]("citation")
|
|
65
64
|
end
|
|
66
65
|
|
|
67
66
|
# Check if blockquote has a citation
|
|
@@ -72,7 +71,16 @@ module Prosereflect
|
|
|
72
71
|
# Remove citation
|
|
73
72
|
def remove_citation
|
|
74
73
|
self.attrs ||= {}
|
|
75
|
-
attrs.delete(
|
|
74
|
+
attrs.delete("citation")
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def to_h
|
|
78
|
+
hash = super
|
|
79
|
+
if hash["attrs"]&.key?("citation") && hash["attrs"]["citation"].nil?
|
|
80
|
+
hash["attrs"].delete("citation")
|
|
81
|
+
hash.delete("attrs") if hash["attrs"].empty?
|
|
82
|
+
end
|
|
83
|
+
hash
|
|
76
84
|
end
|
|
77
85
|
|
|
78
86
|
def add_paragraph(text)
|
|
@@ -1,41 +1,42 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require_relative 'node'
|
|
4
|
-
require_relative 'list_item'
|
|
5
|
-
|
|
6
3
|
module Prosereflect
|
|
7
4
|
# BulletList class represents an unordered list in ProseMirror.
|
|
8
5
|
class BulletList < Node
|
|
9
|
-
PM_TYPE =
|
|
6
|
+
PM_TYPE = "bullet_list"
|
|
10
7
|
|
|
11
|
-
attribute :type, :string, default: -> {
|
|
8
|
+
attribute :type, :string, default: -> {
|
|
9
|
+
self.class.send(:const_get, "PM_TYPE")
|
|
10
|
+
}
|
|
12
11
|
attribute :bullet_style, :string
|
|
13
12
|
attribute :attrs, :hash
|
|
14
13
|
|
|
15
14
|
key_value do
|
|
16
|
-
map
|
|
17
|
-
map
|
|
18
|
-
map
|
|
15
|
+
map "type", to: :type, render_default: true
|
|
16
|
+
map "attrs", to: :attrs
|
|
17
|
+
map "content", to: :content
|
|
19
18
|
end
|
|
20
19
|
|
|
21
20
|
def initialize(attributes = {})
|
|
22
21
|
attributes[:content] ||= []
|
|
23
|
-
|
|
22
|
+
# Only apply default if attrs key is completely absent
|
|
23
|
+
unless attributes.key?(:attrs) || attributes.key?("attrs")
|
|
24
|
+
attributes[:attrs] = { "bullet_style" => nil }
|
|
25
|
+
end
|
|
24
26
|
super
|
|
25
27
|
end
|
|
26
28
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
@bullet_style || attrs&.[]('bullet_style')
|
|
29
|
+
# Use *args to distinguish between create (no args) and create(nil)
|
|
30
|
+
# create with no args -> defaults applied
|
|
31
|
+
# create(nil) from parser -> no defaults, attrs explicitly nil
|
|
32
|
+
def self.create(*args)
|
|
33
|
+
if args.empty?
|
|
34
|
+
# No attrs provided - let initialize apply defaults
|
|
35
|
+
new(type: PM_TYPE)
|
|
36
|
+
else
|
|
37
|
+
attrs = args[0]
|
|
38
|
+
new({ type: PM_TYPE, attrs: attrs })
|
|
39
|
+
end
|
|
39
40
|
end
|
|
40
41
|
|
|
41
42
|
def add_item(text)
|
|
@@ -67,17 +68,23 @@ module Prosereflect
|
|
|
67
68
|
|
|
68
69
|
# Get text content with proper formatting
|
|
69
70
|
def text_content
|
|
70
|
-
return
|
|
71
|
+
return "" unless content
|
|
71
72
|
|
|
72
|
-
content.map
|
|
73
|
+
content.map do |item|
|
|
74
|
+
item.respond_to?(:text_content) ? item.text_content : ""
|
|
75
|
+
end.join("\n")
|
|
73
76
|
end
|
|
74
77
|
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
78
|
+
def bullet_style=(value)
|
|
79
|
+
@bullet_style = value
|
|
80
|
+
return if value.nil?
|
|
81
|
+
|
|
82
|
+
self.attrs ||= {}
|
|
83
|
+
attrs["bullet_style"] = value
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
def bullet_style
|
|
87
|
+
@bullet_style || attrs&.[]("bullet_style")
|
|
81
88
|
end
|
|
82
89
|
end
|
|
83
90
|
end
|
|
@@ -1,27 +1,27 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require_relative 'node'
|
|
4
|
-
|
|
5
3
|
module Prosereflect
|
|
6
4
|
# CodeBlock class represents a code block in ProseMirror.
|
|
7
5
|
class CodeBlock < Node
|
|
8
|
-
PM_TYPE =
|
|
6
|
+
PM_TYPE = "code_block"
|
|
9
7
|
|
|
10
|
-
attribute :type, :string, default: -> {
|
|
8
|
+
attribute :type, :string, default: -> {
|
|
9
|
+
self.class.send(:const_get, "PM_TYPE")
|
|
10
|
+
}
|
|
11
11
|
attribute :language, :string
|
|
12
12
|
attribute :line_numbers, :boolean
|
|
13
13
|
attribute :attrs, :hash
|
|
14
14
|
|
|
15
15
|
key_value do
|
|
16
|
-
map
|
|
17
|
-
map
|
|
18
|
-
map
|
|
16
|
+
map "type", to: :type, render_default: true
|
|
17
|
+
map "attrs", to: :attrs
|
|
18
|
+
map "content", to: :content
|
|
19
19
|
end
|
|
20
20
|
|
|
21
21
|
def initialize(attributes = {})
|
|
22
22
|
attributes[:attrs] ||= {
|
|
23
|
-
|
|
24
|
-
|
|
23
|
+
"content" => nil,
|
|
24
|
+
"language" => nil,
|
|
25
25
|
}
|
|
26
26
|
super
|
|
27
27
|
end
|
|
@@ -33,38 +33,38 @@ module Prosereflect
|
|
|
33
33
|
def language=(value)
|
|
34
34
|
@language = value
|
|
35
35
|
self.attrs ||= {}
|
|
36
|
-
attrs[
|
|
36
|
+
attrs["language"] = value
|
|
37
37
|
end
|
|
38
38
|
|
|
39
39
|
def language
|
|
40
|
-
@language || attrs&.[](
|
|
40
|
+
@language || attrs&.[]("language")
|
|
41
41
|
end
|
|
42
42
|
|
|
43
43
|
def line_numbers=(value)
|
|
44
44
|
@line_numbers = value
|
|
45
45
|
self.attrs ||= {}
|
|
46
|
-
attrs[
|
|
46
|
+
attrs["line_numbers"] = value
|
|
47
47
|
end
|
|
48
48
|
|
|
49
49
|
def line_numbers
|
|
50
|
-
@line_numbers || attrs&.[](
|
|
50
|
+
@line_numbers || attrs&.[]("line_numbers") || false
|
|
51
51
|
end
|
|
52
52
|
|
|
53
53
|
def content=(value)
|
|
54
54
|
@content = value
|
|
55
55
|
self.attrs ||= {}
|
|
56
|
-
attrs[
|
|
56
|
+
attrs["content"] = value
|
|
57
57
|
end
|
|
58
58
|
|
|
59
59
|
def content
|
|
60
|
-
@content || attrs&.[](
|
|
60
|
+
@content || attrs&.[]("content")
|
|
61
61
|
end
|
|
62
62
|
|
|
63
63
|
attr_reader :highlight_lines_str
|
|
64
64
|
|
|
65
65
|
def highlight_lines=(lines)
|
|
66
66
|
@highlight_lines_str = if lines.is_a?(Array)
|
|
67
|
-
lines.join(
|
|
67
|
+
lines.join(",")
|
|
68
68
|
else
|
|
69
69
|
lines.to_s
|
|
70
70
|
end
|
|
@@ -73,7 +73,7 @@ module Prosereflect
|
|
|
73
73
|
def highlight_lines
|
|
74
74
|
return [] unless @highlight_lines_str
|
|
75
75
|
|
|
76
|
-
@highlight_lines_str.split(
|
|
76
|
+
@highlight_lines_str.split(",").map(&:to_i)
|
|
77
77
|
end
|
|
78
78
|
|
|
79
79
|
def text_content
|
|
@@ -82,12 +82,8 @@ module Prosereflect
|
|
|
82
82
|
|
|
83
83
|
def to_h
|
|
84
84
|
hash = super
|
|
85
|
-
hash[
|
|
86
|
-
|
|
87
|
-
'language' => language
|
|
88
|
-
}
|
|
89
|
-
hash['attrs']['line_numbers'] = line_numbers if line_numbers
|
|
90
|
-
hash.delete('content')
|
|
85
|
+
hash["attrs"] = { "language" => language }
|
|
86
|
+
hash["attrs"]["line_numbers"] = line_numbers if line_numbers
|
|
91
87
|
hash
|
|
92
88
|
end
|
|
93
89
|
|
|
@@ -96,7 +92,7 @@ module Prosereflect
|
|
|
96
92
|
{
|
|
97
93
|
language: language,
|
|
98
94
|
line_numbers: line_numbers,
|
|
99
|
-
highlight_lines: highlight_lines
|
|
95
|
+
highlight_lines: highlight_lines,
|
|
100
96
|
}.compact
|
|
101
97
|
end
|
|
102
98
|
|
|
@@ -118,14 +114,14 @@ module Prosereflect
|
|
|
118
114
|
return content if lines.empty?
|
|
119
115
|
|
|
120
116
|
min_indent = lines.reject(&:empty?)
|
|
121
|
-
|
|
122
|
-
|
|
117
|
+
.map { |line| line[/^\s*/].length }
|
|
118
|
+
.min || 0
|
|
123
119
|
|
|
124
120
|
normalized_lines = lines.map do |line|
|
|
125
121
|
if line.empty?
|
|
126
122
|
line
|
|
127
123
|
else
|
|
128
|
-
line[min_indent..] ||
|
|
124
|
+
line[min_indent..] || ""
|
|
129
125
|
end
|
|
130
126
|
end
|
|
131
127
|
|
|
@@ -1,27 +1,26 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require_relative 'node'
|
|
4
|
-
require_relative 'code_block'
|
|
5
|
-
|
|
6
3
|
module Prosereflect
|
|
7
4
|
# CodeBlockWrapper class represents a pre tag that wraps code blocks in ProseMirror.
|
|
8
5
|
class CodeBlockWrapper < Node
|
|
9
|
-
PM_TYPE =
|
|
6
|
+
PM_TYPE = "code_block_wrapper"
|
|
10
7
|
|
|
11
|
-
attribute :type, :string, default: -> {
|
|
8
|
+
attribute :type, :string, default: -> {
|
|
9
|
+
self.class.send(:const_get, "PM_TYPE")
|
|
10
|
+
}
|
|
12
11
|
attribute :line_numbers, :boolean
|
|
13
12
|
attribute :attrs, :hash
|
|
14
13
|
|
|
15
14
|
key_value do
|
|
16
|
-
map
|
|
17
|
-
map
|
|
18
|
-
map
|
|
15
|
+
map "type", to: :type, render_default: true
|
|
16
|
+
map "attrs", to: :attrs
|
|
17
|
+
map "content", to: :content
|
|
19
18
|
end
|
|
20
19
|
|
|
21
20
|
def initialize(attributes = {})
|
|
22
21
|
attributes[:content] ||= []
|
|
23
22
|
attributes[:attrs] = {
|
|
24
|
-
|
|
23
|
+
"line_numbers" => false,
|
|
25
24
|
}
|
|
26
25
|
super
|
|
27
26
|
end
|
|
@@ -33,11 +32,11 @@ module Prosereflect
|
|
|
33
32
|
def line_numbers=(value)
|
|
34
33
|
@line_numbers = value
|
|
35
34
|
self.attrs ||= {}
|
|
36
|
-
attrs[
|
|
35
|
+
attrs["line_numbers"] = value
|
|
37
36
|
end
|
|
38
37
|
|
|
39
38
|
def line_numbers
|
|
40
|
-
@line_numbers || attrs&.[](
|
|
39
|
+
@line_numbers || attrs&.[]("line_numbers") || false
|
|
41
40
|
end
|
|
42
41
|
|
|
43
42
|
def add_code_block(code = nil)
|
|
@@ -57,8 +56,8 @@ module Prosereflect
|
|
|
57
56
|
|
|
58
57
|
def to_h
|
|
59
58
|
hash = super
|
|
60
|
-
hash[
|
|
61
|
-
|
|
59
|
+
hash["attrs"] = {
|
|
60
|
+
"line_numbers" => line_numbers,
|
|
62
61
|
}
|
|
63
62
|
hash
|
|
64
63
|
end
|