prosemirror 0.1.0 → 0.2.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 +10 -0
- data/README.md +31 -45
- data/ROADMAP.md +10 -6
- data/TYPES.md +201 -0
- data/lib/prosemirror/content_match.rb +241 -0
- data/lib/prosemirror/html/builder.rb +146 -0
- data/lib/prosemirror/html/element.rb +84 -0
- data/lib/prosemirror/html/entities.rb +87 -0
- data/lib/prosemirror/html/entities.txt +2231 -0
- data/lib/prosemirror/html/selector.rb +95 -0
- data/lib/prosemirror/html/splitter.rb +68 -0
- data/lib/prosemirror/html/text.rb +23 -0
- data/lib/prosemirror/html_parser.rb +498 -0
- data/lib/prosemirror/json_parser.rb +1 -12
- data/lib/prosemirror/mark_type.rb +11 -0
- data/lib/prosemirror/marks/basic/code.rb +16 -0
- data/lib/prosemirror/marks/basic/em.rb +4 -0
- data/lib/prosemirror/marks/basic/link.rb +1 -0
- data/lib/prosemirror/marks/basic/strong.rb +4 -0
- data/lib/prosemirror/marks/bold.rb +8 -0
- data/lib/prosemirror/marks/code.rb +5 -0
- data/lib/prosemirror/marks/highlight.rb +3 -1
- data/lib/prosemirror/marks/italic.rb +4 -0
- data/lib/prosemirror/marks/link.rb +1 -0
- data/lib/prosemirror/marks/strike.rb +5 -0
- data/lib/prosemirror/marks/subscript.rb +2 -0
- data/lib/prosemirror/marks/superscript.rb +2 -0
- data/lib/prosemirror/marks/underline.rb +3 -0
- data/lib/prosemirror/node.rb +12 -0
- data/lib/prosemirror/node_type.rb +22 -0
- data/lib/prosemirror/nodes/basic/code_block.rb +1 -0
- data/lib/prosemirror/nodes/basic/image.rb +1 -0
- data/lib/prosemirror/nodes/basic/ordered_list.rb +4 -1
- data/lib/prosemirror/nodes/blockquote.rb +1 -0
- data/lib/prosemirror/nodes/bullet_list.rb +1 -0
- data/lib/prosemirror/nodes/code_block.rb +6 -1
- data/lib/prosemirror/nodes/hard_break.rb +2 -0
- data/lib/prosemirror/nodes/heading.rb +1 -0
- data/lib/prosemirror/nodes/horizontal_rule.rb +1 -0
- data/lib/prosemirror/nodes/image.rb +1 -0
- data/lib/prosemirror/nodes/list_item.rb +1 -0
- data/lib/prosemirror/nodes/ordered_list.rb +20 -2
- data/lib/prosemirror/nodes/paragraph.rb +1 -0
- data/lib/prosemirror/nodes/table.rb +1 -0
- data/lib/prosemirror/nodes/table_cell.rb +12 -2
- data/lib/prosemirror/nodes/table_header.rb +2 -0
- data/lib/prosemirror/nodes/table_row.rb +1 -0
- data/lib/prosemirror/parse_rule.rb +50 -0
- data/lib/prosemirror/schema.rb +66 -15
- data/lib/prosemirror/type_definition.rb +34 -4
- data/lib/prosemirror/version.rb +1 -1
- data/lib/prosemirror.rb +11 -1
- metadata +13 -1
|
@@ -27,6 +27,17 @@ module Prosemirror
|
|
|
27
27
|
inherited_setting(:@excludes, nil)
|
|
28
28
|
end
|
|
29
29
|
|
|
30
|
+
# Whether this mark can't coexist with marks of the other type.
|
|
31
|
+
def excludes?(other)
|
|
32
|
+
case excludes
|
|
33
|
+
when nil then other.type_name == type_name
|
|
34
|
+
when "_" then true
|
|
35
|
+
else
|
|
36
|
+
names = excludes.split.map(&:to_sym)
|
|
37
|
+
names.include?(other.type_name) || names.intersect?(other.group)
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
|
|
30
41
|
def group(*names)
|
|
31
42
|
return @group = names.flat_map { |n| n.to_s.split }.map(&:to_sym) unless names.empty?
|
|
32
43
|
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Prosemirror
|
|
4
|
+
module Marks
|
|
5
|
+
module Basic
|
|
6
|
+
# ProseMirror's inline code, which can be combined with other marks.
|
|
7
|
+
class Code < MarkType
|
|
8
|
+
code
|
|
9
|
+
|
|
10
|
+
parse_html tag: "code"
|
|
11
|
+
|
|
12
|
+
def view_template(&) = code(&)
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
|
@@ -5,6 +5,10 @@ module Prosemirror
|
|
|
5
5
|
module Basic
|
|
6
6
|
class Strong < MarkType
|
|
7
7
|
aliases :bold
|
|
8
|
+
parse_html tag: "strong"
|
|
9
|
+
parse_html(tag: "b") { |element| element.style["font-weight"] != "normal" && {} }
|
|
10
|
+
parse_html style: "font-weight=400", clear_mark: true
|
|
11
|
+
parse_html(style: "font-weight") { |value| value.match?(Bold::BOLD) && {} }
|
|
8
12
|
|
|
9
13
|
def view_template(&) = strong(&)
|
|
10
14
|
end
|
|
@@ -3,8 +3,16 @@
|
|
|
3
3
|
module Prosemirror
|
|
4
4
|
module Marks
|
|
5
5
|
class Bold < MarkType
|
|
6
|
+
# The `font-weight` values that mean bold.
|
|
7
|
+
BOLD = /\A(bold(er)?|[5-9]\d{2,})\z/
|
|
8
|
+
|
|
6
9
|
aliases :strong
|
|
7
10
|
|
|
11
|
+
parse_html tag: "strong"
|
|
12
|
+
parse_html(tag: "b") { |element| element.style["font-weight"] != "normal" && {} }
|
|
13
|
+
parse_html style: "font-weight=400", clear_mark: true
|
|
14
|
+
parse_html(style: "font-weight") { |value| value.match?(BOLD) && {} }
|
|
15
|
+
|
|
8
16
|
def view_template(&) = strong(&)
|
|
9
17
|
end
|
|
10
18
|
end
|
|
@@ -6,7 +6,9 @@ module Prosemirror
|
|
|
6
6
|
class Highlight < MarkType
|
|
7
7
|
COLOR = %r{\A(\#\h{3,8}|[a-z]+|(rgb|hsl)a?\([\d\s.,%/]+\))\z}i
|
|
8
8
|
|
|
9
|
-
prop :color, _String?(COLOR)
|
|
9
|
+
prop :color, _String?(COLOR), parse_html: ->(element) { element["data-color"] || element.style["background-color"] }
|
|
10
|
+
|
|
11
|
+
parse_html tag: "mark"
|
|
10
12
|
|
|
11
13
|
def view_template(&)
|
|
12
14
|
mark(data_color: @color, style: @color && "background-color: #{@color}; color: inherit", &)
|
|
@@ -4,6 +4,10 @@ module Prosemirror
|
|
|
4
4
|
module Marks
|
|
5
5
|
class Italic < MarkType
|
|
6
6
|
aliases :em
|
|
7
|
+
parse_html tag: "em"
|
|
8
|
+
parse_html(tag: "i") { |element| element.style["font-style"] != "normal" && {} }
|
|
9
|
+
parse_html style: "font-style=normal", clear_mark: true
|
|
10
|
+
parse_html style: "font-style=italic"
|
|
7
11
|
|
|
8
12
|
def view_template(&) = em(&)
|
|
9
13
|
end
|
|
@@ -3,6 +3,11 @@
|
|
|
3
3
|
module Prosemirror
|
|
4
4
|
module Marks
|
|
5
5
|
class Strike < MarkType
|
|
6
|
+
parse_html tag: "s"
|
|
7
|
+
parse_html tag: "del"
|
|
8
|
+
parse_html tag: "strike"
|
|
9
|
+
parse_html(style: "text-decoration", consuming: false) { |value| value.include?("line-through") ? {} : false }
|
|
10
|
+
|
|
6
11
|
def view_template(&) = s(&)
|
|
7
12
|
end
|
|
8
13
|
end
|
data/lib/prosemirror/node.rb
CHANGED
|
@@ -18,6 +18,18 @@ module Prosemirror
|
|
|
18
18
|
prop :marks, _Array(Mark), default: [].freeze, &Coerce::FROZEN
|
|
19
19
|
prop :text, _String?, &Coerce::FROZEN
|
|
20
20
|
|
|
21
|
+
# The nodes with adjacent text nodes that have the same marks joined, as in ProseMirror.
|
|
22
|
+
def self.join_text(nodes)
|
|
23
|
+
nodes.each_with_object([]) do |node, joined|
|
|
24
|
+
previous = joined.last
|
|
25
|
+
if previous&.text? && node.text? && previous.marks == node.marks
|
|
26
|
+
joined[-1] = previous.with(text: previous.text + node.text)
|
|
27
|
+
else
|
|
28
|
+
joined << node
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
|
|
21
33
|
# A copy with some properties changed.
|
|
22
34
|
def with(**changes)
|
|
23
35
|
self.class.new(**to_h, **changes)
|
|
@@ -86,6 +86,28 @@ module Prosemirror
|
|
|
86
86
|
value = leaf_text
|
|
87
87
|
value.respond_to?(:call) ? value.call(node).to_s : value.to_s
|
|
88
88
|
end
|
|
89
|
+
|
|
90
|
+
# This inline node stands for a newline, as ProseMirror's `linebreakReplacement`: when importing
|
|
91
|
+
# HTML whose whitespace is preserved, newlines become nodes of this type.
|
|
92
|
+
def linebreak_replacement(value = true)
|
|
93
|
+
@linebreak_replacement = value
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
def linebreak_replacement?
|
|
97
|
+
inherited_setting(:@linebreak_replacement, false)
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
# Whether marks of this type can be applied inside this node, according to its `marks` option.
|
|
101
|
+
def allows_mark?(mark_type, schema)
|
|
102
|
+
case marks
|
|
103
|
+
when nil then schema.content_match(self).inline_content?
|
|
104
|
+
when "_" then true
|
|
105
|
+
when "" then false
|
|
106
|
+
else
|
|
107
|
+
names = marks.split.map(&:to_sym)
|
|
108
|
+
names.include?(mark_type.type_name) || names.intersect?(mark_type.group)
|
|
109
|
+
end
|
|
110
|
+
end
|
|
89
111
|
end
|
|
90
112
|
|
|
91
113
|
# The node being rendered.
|
|
@@ -8,7 +8,10 @@ module Prosemirror
|
|
|
8
8
|
group :block, :list
|
|
9
9
|
content "list_item+"
|
|
10
10
|
|
|
11
|
-
prop :order, Integer, default: 1, aliases: ["start"],
|
|
11
|
+
prop :order, Integer, default: 1, aliases: ["start"],
|
|
12
|
+
parse_html: ->(element) { element.key?("start") ? element["start"].to_i : 1 }, &Coerce::INTEGER
|
|
13
|
+
|
|
14
|
+
parse_html tag: "ol"
|
|
12
15
|
|
|
13
16
|
def view_template(&)
|
|
14
17
|
ol(start: (@order unless @order == 1), &)
|
|
@@ -9,7 +9,12 @@ module Prosemirror
|
|
|
9
9
|
marks ""
|
|
10
10
|
code
|
|
11
11
|
|
|
12
|
-
|
|
12
|
+
# From the `language-*` class of the `<code>` inside `<pre>`.
|
|
13
|
+
prop :language, _String?(/\A[\w#+.-]+\z/), aliases: ["params"], parse_html: lambda { |element|
|
|
14
|
+
element.first_element_child&.classes&.find { |name| name.start_with?("language-") }&.delete_prefix("language-")
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
parse_html tag: "pre", preserve_whitespace: :full
|
|
13
18
|
|
|
14
19
|
def view_template(&)
|
|
15
20
|
pre { code(class: @language && "language-#{@language}", &) }
|
|
@@ -4,11 +4,29 @@ module Prosemirror
|
|
|
4
4
|
module Nodes
|
|
5
5
|
# Tiptap's ordered list. ProseMirror's `order` attribute is read as `start`.
|
|
6
6
|
class OrderedList < NodeType
|
|
7
|
+
# CSS list styles, used by Google Docs and Word, and their `type`.
|
|
8
|
+
LIST_STYLE_TYPES = {
|
|
9
|
+
"upper-roman" => "I", "lower-roman" => "i", "upper-alpha" => "A", "upper-latin" => "A",
|
|
10
|
+
"lower-alpha" => "a", "lower-latin" => "a"
|
|
11
|
+
}.freeze
|
|
12
|
+
|
|
7
13
|
group :block, :list
|
|
8
14
|
content "list_item+"
|
|
9
15
|
|
|
10
|
-
prop :start, Integer, default: 1, aliases: ["order"],
|
|
11
|
-
|
|
16
|
+
prop :start, Integer, default: 1, aliases: ["order"],
|
|
17
|
+
parse_html: ->(element) { element.key?("start") ? element["start"].to_i : 1 }, &Coerce::INTEGER
|
|
18
|
+
prop :type, _Union?("1", "a", "A", "i", "I"), parse_html: ->(element) { OrderedList.type_from_html(element) }
|
|
19
|
+
|
|
20
|
+
parse_html tag: "ol"
|
|
21
|
+
|
|
22
|
+
# From the `type` attribute, or the list style of the list or of its first item.
|
|
23
|
+
def self.type_from_html(element)
|
|
24
|
+
element["type"] || list_style_type(element) || element.query("li")&.then { |item| list_style_type(item) }
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def self.list_style_type(element)
|
|
28
|
+
LIST_STYLE_TYPES[element.style["list-style-type"].to_s.downcase]
|
|
29
|
+
end
|
|
12
30
|
|
|
13
31
|
def view_template(&)
|
|
14
32
|
ol(start: (@start unless @start == 1), type: @type, &)
|
|
@@ -3,11 +3,20 @@
|
|
|
3
3
|
module Prosemirror
|
|
4
4
|
module Nodes
|
|
5
5
|
class TableCell < NodeType
|
|
6
|
+
ALIGNMENTS = %w[left right center].freeze
|
|
7
|
+
|
|
6
8
|
content "block+"
|
|
7
9
|
|
|
8
10
|
prop :colspan, _Integer(1..), default: 1, &Coerce::INTEGER
|
|
9
11
|
prop :rowspan, _Integer(1..), default: 1, &Coerce::INTEGER
|
|
10
|
-
prop :colwidth, _Array?(_Integer(1..))
|
|
12
|
+
prop :colwidth, _Array?(_Integer(1..)),
|
|
13
|
+
parse_html: ->(element) { element["colwidth"]&.split(",")&.map(&:to_i) }
|
|
14
|
+
# From the `text-align` style, or the old `align` attribute.
|
|
15
|
+
prop :align, _Union?(*ALIGNMENTS), parse_html: lambda { |element|
|
|
16
|
+
(element.style["text-align"] || element["align"]).to_s.strip.downcase.then { |value| value if ALIGNMENTS.include?(value) }
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
parse_html tag: "td"
|
|
11
20
|
|
|
12
21
|
def view_template(&)
|
|
13
22
|
td(**cell_attributes, &)
|
|
@@ -16,7 +25,8 @@ module Prosemirror
|
|
|
16
25
|
private
|
|
17
26
|
|
|
18
27
|
def cell_attributes
|
|
19
|
-
{colspan: (@colspan unless @colspan == 1), rowspan: (@rowspan unless @rowspan == 1)
|
|
28
|
+
{colspan: (@colspan unless @colspan == 1), rowspan: (@rowspan unless @rowspan == 1),
|
|
29
|
+
style: @align && "text-align: #{@align}"}
|
|
20
30
|
end
|
|
21
31
|
end
|
|
22
32
|
end
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Prosemirror
|
|
4
|
+
# How to recognize a node or a mark in HTML, as ProseMirror's ParseRule:
|
|
5
|
+
# https://prosemirror.net/docs/ref/#model.ParseRule
|
|
6
|
+
#
|
|
7
|
+
# - `tag`: a CSS selector the element must match (see HTML::Selector), or
|
|
8
|
+
# - `style`: a CSS property (`"font-style"`), or a property and value (`"font-style=italic"`), for marks;
|
|
9
|
+
# - `attrs`: attributes to give the node or mark;
|
|
10
|
+
# - `get_attrs`: a block called with the element (or the style's value), returning attributes, nil for
|
|
11
|
+
# the defaults, or false when the rule doesn't match;
|
|
12
|
+
# - `priority` (default 50): rules with a higher priority are tried first;
|
|
13
|
+
# - `consuming` (default true): false lets the following rules match the element too;
|
|
14
|
+
# - `context`: the parents the rule needs, as `"list_item/"` or `"blockquote//"`;
|
|
15
|
+
# - `ignore`: skip the element and its content;
|
|
16
|
+
# - `skip`: skip the element, but parse its content;
|
|
17
|
+
# - `close_parent`: close the current node;
|
|
18
|
+
# - `content_element`: a selector for the descendant holding the content;
|
|
19
|
+
# - `get_content`: a block called with the element and the schema, returning the nodes to use as content;
|
|
20
|
+
# - `preserve_whitespace`: true to keep spaces, `:full` to also keep newlines;
|
|
21
|
+
# - `clear_mark`: for style rules, true to remove the type's marks, or a block called with each mark.
|
|
22
|
+
ParseRule = Data.define(
|
|
23
|
+
:type, :tag, :style, :attrs, :get_attrs, :priority, :consuming, :context, :ignore, :skip,
|
|
24
|
+
:close_parent, :content_element, :get_content, :preserve_whitespace, :clear_mark
|
|
25
|
+
) do
|
|
26
|
+
def initialize(type:, tag: nil, style: nil, attrs: nil, get_attrs: nil, priority: 50, consuming: true,
|
|
27
|
+
context: nil, ignore: false, skip: false, close_parent: false, content_element: nil, get_content: nil,
|
|
28
|
+
preserve_whitespace: nil, clear_mark: nil)
|
|
29
|
+
raise ArgumentError, "a parse rule needs a tag or a style" if tag.nil? == style.nil?
|
|
30
|
+
|
|
31
|
+
HTML::Selector.parse(tag) if tag
|
|
32
|
+
super
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def node?
|
|
36
|
+
type < NodeType
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
# The style's property, without the value.
|
|
40
|
+
def style_property
|
|
41
|
+
style&.split("=", 2)&.first
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
# Whether a style rule applies to this property and value.
|
|
45
|
+
def style_matches?(property, value)
|
|
46
|
+
name, expected = style.split("=", 2)
|
|
47
|
+
name == property && (expected.nil? || expected == value)
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
end
|
data/lib/prosemirror/schema.rb
CHANGED
|
@@ -32,8 +32,9 @@ module Prosemirror
|
|
|
32
32
|
Nodes::OrderedList, Nodes::ListItem, Nodes::CodeBlock, Nodes::HardBreak, Nodes::HorizontalRule,
|
|
33
33
|
Nodes::Image, Nodes::Table, Nodes::TableRow, Nodes::TableHeader, Nodes::TableCell
|
|
34
34
|
],
|
|
35
|
+
# In Tiptap's order, which sorts and nests marks.
|
|
35
36
|
marks: [
|
|
36
|
-
Marks::Link, Marks::Bold, Marks::
|
|
37
|
+
Marks::Link, Marks::Bold, Marks::Code, Marks::Italic, Marks::Strike, Marks::Underline,
|
|
37
38
|
Marks::Subscript, Marks::Superscript, Marks::Highlight
|
|
38
39
|
],
|
|
39
40
|
**
|
|
@@ -48,7 +49,7 @@ module Prosemirror
|
|
|
48
49
|
Nodes::Basic::CodeBlock, Nodes::Text, Nodes::Basic::Image, Nodes::HardBreak, Nodes::Basic::OrderedList,
|
|
49
50
|
Nodes::BulletList, Nodes::ListItem
|
|
50
51
|
],
|
|
51
|
-
marks: [Marks::Basic::Link, Marks::Basic::Em, Marks::Basic::Strong, Marks::Code],
|
|
52
|
+
marks: [Marks::Basic::Link, Marks::Basic::Em, Marks::Basic::Strong, Marks::Basic::Code],
|
|
52
53
|
**
|
|
53
54
|
)
|
|
54
55
|
end
|
|
@@ -57,9 +58,32 @@ module Prosemirror
|
|
|
57
58
|
@node_lookup = lookup(@nodes)
|
|
58
59
|
@mark_lookup = lookup(@marks)
|
|
59
60
|
@textblocks = @nodes.to_h { |type| [type, textblock_type?(type)] }
|
|
61
|
+
@content_matches = @nodes.to_h { |type| [type, ContentMatch.compile(type.content, self)] }
|
|
60
62
|
raise ArgumentError, "the schema has no #{@top_node} node type" unless node_type(@top_node)
|
|
61
63
|
end
|
|
62
64
|
|
|
65
|
+
# The start state of a node type's content expression.
|
|
66
|
+
def content_match(type)
|
|
67
|
+
@content_matches.fetch(type)
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
# The node types with this name or in this group, in schema order.
|
|
71
|
+
def node_types_named(name)
|
|
72
|
+
@nodes.select { |type| type.type_name == name || type.group.include?(name) }
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
# A node of this type, created empty and filled with the content it requires, or nil when its
|
|
76
|
+
# type, or one it requires, has required attributes.
|
|
77
|
+
def create_and_fill(type)
|
|
78
|
+
return nil if type.required_attrs?
|
|
79
|
+
|
|
80
|
+
fill = content_match(type).fill_before([], self, to_end: true)
|
|
81
|
+
content = fill&.map { |child| create_and_fill(child) }
|
|
82
|
+
return nil if content.nil? || content.include?(nil)
|
|
83
|
+
|
|
84
|
+
Node.new(type: type.type_name, attrs: type.default_attrs, content: content)
|
|
85
|
+
end
|
|
86
|
+
|
|
63
87
|
# A copy of the schema with other options.
|
|
64
88
|
def with_options(**)
|
|
65
89
|
self.class.new(**to_h, **)
|
|
@@ -88,11 +112,51 @@ module Prosemirror
|
|
|
88
112
|
Document.new(JSONParser.new(self).parse(json), schema: self)
|
|
89
113
|
end
|
|
90
114
|
|
|
115
|
+
# Build a document from HTML, with the parse rules of the schema's types. Needs the `herb` gem.
|
|
116
|
+
def parse_html(html)
|
|
117
|
+
Document.new(HTMLParser.new(self).parse(html), schema: self)
|
|
118
|
+
end
|
|
119
|
+
|
|
91
120
|
# The type name as written in JSON.
|
|
92
121
|
def json_type_name(name)
|
|
93
122
|
(@case == :camel) ? Naming.camel(name) : name.to_s
|
|
94
123
|
end
|
|
95
124
|
|
|
125
|
+
# The set of marks with this mark added, in schema order, as ProseMirror's `Mark#addToSet`: a mark
|
|
126
|
+
# replaces the marks it excludes, and isn't added when a mark of the set excludes it.
|
|
127
|
+
def add_mark(mark, set)
|
|
128
|
+
type = mark_type(mark.type)
|
|
129
|
+
result = []
|
|
130
|
+
placed = false
|
|
131
|
+
set.each do |other|
|
|
132
|
+
return set if other == mark
|
|
133
|
+
|
|
134
|
+
other_type = mark_type(other.type)
|
|
135
|
+
next if type.excludes?(other_type)
|
|
136
|
+
return set if other_type.excludes?(type)
|
|
137
|
+
|
|
138
|
+
if !placed && mark_rank(other.type) > mark_rank(mark.type)
|
|
139
|
+
result << mark
|
|
140
|
+
placed = true
|
|
141
|
+
end
|
|
142
|
+
result << other
|
|
143
|
+
end
|
|
144
|
+
result << mark unless placed
|
|
145
|
+
result
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
# Whether a URL's scheme is allowed, or the URL is relative. Browsers ignore leading control characters
|
|
149
|
+
# and spaces, and tabs and newlines anywhere in a URL.
|
|
150
|
+
def allowed_url?(url)
|
|
151
|
+
return false unless url.is_a?(String)
|
|
152
|
+
|
|
153
|
+
normalized = url.delete("\t\n\r").sub(/\A[\x00-\x20]+/, "")
|
|
154
|
+
scheme = normalized[%r{\A([^/?#]*):}, 1]
|
|
155
|
+
return true if scheme.nil?
|
|
156
|
+
|
|
157
|
+
scheme.match?(/\A[a-z][a-z\d+.-]*\z/i) && @url_schemes.include?(scheme.downcase)
|
|
158
|
+
end
|
|
159
|
+
|
|
96
160
|
# The order of marks in a node. Marks of unknown types come last.
|
|
97
161
|
def mark_rank(name)
|
|
98
162
|
@marks.index(mark_type(name)) || @marks.size
|
|
@@ -158,19 +222,6 @@ module Prosemirror
|
|
|
158
222
|
end
|
|
159
223
|
end
|
|
160
224
|
|
|
161
|
-
def node_types_named(name)
|
|
162
|
-
@nodes.select { |type| type.type_name == name || type.group.include?(name) }
|
|
163
|
-
end
|
|
164
|
-
|
|
165
|
-
# Browsers ignore leading control characters and spaces, and tabs and newlines anywhere in a URL.
|
|
166
|
-
def allowed_url?(url)
|
|
167
|
-
normalized = url.delete("\t\n\r").sub(/\A[\x00-\x20]+/, "")
|
|
168
|
-
scheme = normalized[%r{\A([^/?#]*):}, 1]
|
|
169
|
-
return true if scheme.nil?
|
|
170
|
-
|
|
171
|
-
scheme.match?(/\A[a-z][a-z\d+.-]*\z/i) && @url_schemes.include?(scheme.downcase)
|
|
172
|
-
end
|
|
173
|
-
|
|
174
225
|
# Declared attributes with their JSON names, then the unknown ones, as they were.
|
|
175
226
|
def serialize_node_attrs(node)
|
|
176
227
|
type = node_type(node.type)
|