markbridge 0.1.0 → 0.1.2
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/lib/markbridge/all.rb +4 -7
- data/lib/markbridge/ast/document.rb +1 -1
- data/lib/markbridge/ast/element.rb +2 -2
- data/lib/markbridge/ast/list.rb +2 -2
- data/lib/markbridge/ast/table.rb +61 -0
- data/lib/markbridge/ast/text.rb +5 -1
- data/lib/markbridge/ast.rb +1 -0
- data/lib/markbridge/bbcode.rb +4 -0
- data/lib/markbridge/gem_loader.rb +2 -3
- data/lib/markbridge/html.rb +4 -0
- data/lib/markbridge/mediawiki.rb +4 -0
- data/lib/markbridge/parsers/bbcode/closing_strategies/base.rb +0 -10
- data/lib/markbridge/parsers/bbcode/closing_strategies/reordering.rb +17 -4
- data/lib/markbridge/parsers/bbcode/closing_strategies/tag_reconciler.rb +64 -44
- data/lib/markbridge/parsers/bbcode/handler_registry.rb +26 -11
- data/lib/markbridge/parsers/bbcode/handlers/attachment_handler.rb +17 -12
- data/lib/markbridge/parsers/bbcode/handlers/base_handler.rb +0 -10
- data/lib/markbridge/parsers/bbcode/handlers/code_handler.rb +6 -10
- data/lib/markbridge/parsers/bbcode/handlers/image_handler.rb +13 -19
- data/lib/markbridge/parsers/bbcode/handlers/list_handler.rb +1 -5
- data/lib/markbridge/parsers/bbcode/handlers/list_item_handler.rb +1 -2
- data/lib/markbridge/parsers/bbcode/handlers/quote_handler.rb +30 -35
- data/lib/markbridge/parsers/bbcode/handlers/raw_handler.rb +2 -6
- data/lib/markbridge/parsers/bbcode/handlers/self_closing_handler.rb +4 -4
- data/lib/markbridge/parsers/bbcode/handlers/table_cell_handler.rb +26 -0
- data/lib/markbridge/parsers/bbcode/handlers/table_handler.rb +32 -0
- data/lib/markbridge/parsers/bbcode/handlers/table_row_handler.rb +35 -0
- data/lib/markbridge/parsers/bbcode/parser.rb +5 -8
- data/lib/markbridge/parsers/bbcode/parser_state.rb +12 -18
- data/lib/markbridge/parsers/bbcode/peekable_enumerator.rb +9 -59
- data/lib/markbridge/parsers/bbcode/raw_content_collector.rb +2 -2
- data/lib/markbridge/parsers/bbcode/scanner.rb +49 -63
- data/lib/markbridge/parsers/bbcode/tokens/tag_end_token.rb +1 -5
- data/lib/markbridge/parsers/bbcode/tokens/tag_start_token.rb +1 -6
- data/lib/markbridge/parsers/bbcode/tokens/text_token.rb +1 -7
- data/lib/markbridge/parsers/bbcode/tokens/token.rb +1 -1
- data/lib/markbridge/parsers/bbcode.rb +4 -0
- data/lib/markbridge/parsers/html/handler_registry.rb +32 -44
- data/lib/markbridge/parsers/html/handlers/base_handler.rb +0 -3
- data/lib/markbridge/parsers/html/handlers/image_handler.rb +1 -4
- data/lib/markbridge/parsers/html/handlers/table_cell_handler.rb +24 -0
- data/lib/markbridge/parsers/html/handlers/table_handler.rb +24 -0
- data/lib/markbridge/parsers/html/handlers/table_row_handler.rb +24 -0
- data/lib/markbridge/parsers/html/parser.rb +16 -15
- data/lib/markbridge/parsers/html.rb +3 -0
- data/lib/markbridge/parsers/media_wiki/inline_parser.rb +115 -151
- data/lib/markbridge/parsers/media_wiki/inline_tag_registry.rb +103 -0
- data/lib/markbridge/parsers/media_wiki/parser.rb +174 -71
- data/lib/markbridge/parsers/media_wiki.rb +1 -0
- data/lib/markbridge/parsers/text_formatter/handler_registry.rb +10 -36
- data/lib/markbridge/parsers/text_formatter/handlers/table_cell_handler.rb +26 -0
- data/lib/markbridge/parsers/text_formatter/parser.rb +3 -8
- data/lib/markbridge/parsers/text_formatter.rb +1 -0
- data/lib/markbridge/processors/discourse_markdown/code_block_tracker.rb +111 -92
- data/lib/markbridge/processors/discourse_markdown/detectors/base.rb +13 -7
- data/lib/markbridge/processors/discourse_markdown/detectors/event.rb +11 -20
- data/lib/markbridge/processors/discourse_markdown/detectors/poll.rb +10 -48
- data/lib/markbridge/processors/discourse_markdown/detectors/upload.rb +38 -63
- data/lib/markbridge/processors/discourse_markdown/scanner.rb +36 -41
- data/lib/markbridge/renderers/discourse/builders/list_item_builder.rb +6 -6
- data/lib/markbridge/renderers/discourse/html_escaper.rb +20 -0
- data/lib/markbridge/renderers/discourse/markdown_escaper.rb +262 -205
- data/lib/markbridge/renderers/discourse/render_context.rb +23 -11
- data/lib/markbridge/renderers/discourse/renderer.rb +54 -11
- data/lib/markbridge/renderers/discourse/rendering_interface.rb +12 -4
- data/lib/markbridge/renderers/discourse/tag.rb +14 -1
- data/lib/markbridge/renderers/discourse/tag_library.rb +30 -25
- data/lib/markbridge/renderers/discourse/tags/align_tag.rb +15 -7
- data/lib/markbridge/renderers/discourse/tags/attachment_tag.rb +1 -1
- data/lib/markbridge/renderers/discourse/tags/bold_tag.rb +2 -0
- data/lib/markbridge/renderers/discourse/tags/code_tag.rb +14 -8
- data/lib/markbridge/renderers/discourse/tags/email_tag.rb +5 -3
- data/lib/markbridge/renderers/discourse/tags/event_tag.rb +3 -3
- data/lib/markbridge/renderers/discourse/tags/heading_tag.rb +6 -2
- data/lib/markbridge/renderers/discourse/tags/horizontal_rule_tag.rb +2 -2
- data/lib/markbridge/renderers/discourse/tags/image_tag.rb +12 -1
- data/lib/markbridge/renderers/discourse/tags/italic_tag.rb +2 -0
- data/lib/markbridge/renderers/discourse/tags/line_break_tag.rb +2 -2
- data/lib/markbridge/renderers/discourse/tags/list_item_tag.rb +24 -47
- data/lib/markbridge/renderers/discourse/tags/list_tag.rb +10 -15
- data/lib/markbridge/renderers/discourse/tags/mention_tag.rb +6 -2
- data/lib/markbridge/renderers/discourse/tags/paragraph_tag.rb +10 -0
- data/lib/markbridge/renderers/discourse/tags/poll_tag.rb +9 -4
- data/lib/markbridge/renderers/discourse/tags/quote_tag.rb +17 -11
- data/lib/markbridge/renderers/discourse/tags/spoiler_tag.rb +9 -0
- data/lib/markbridge/renderers/discourse/tags/strikethrough_tag.rb +2 -0
- data/lib/markbridge/renderers/discourse/tags/table_cell_tag.rb +18 -0
- data/lib/markbridge/renderers/discourse/tags/table_row_tag.rb +18 -0
- data/lib/markbridge/renderers/discourse/tags/table_tag.rb +128 -0
- data/lib/markbridge/renderers/discourse/tags/underline_tag.rb +10 -3
- data/lib/markbridge/renderers/discourse/tags/upload_tag.rb +28 -1
- data/lib/markbridge/renderers/discourse/tags/url_tag.rb +5 -3
- data/lib/markbridge/renderers/discourse.rb +4 -0
- data/lib/markbridge/textformatter.rb +4 -0
- data/lib/markbridge/version.rb +1 -1
- data/lib/markbridge.rb +27 -62
- metadata +19 -2
|
@@ -19,10 +19,10 @@ module Markbridge
|
|
|
19
19
|
# end
|
|
20
20
|
class PollTag < Tag
|
|
21
21
|
def render(element, interface)
|
|
22
|
-
|
|
23
|
-
return
|
|
22
|
+
body = element.raw || build_poll_bbcode(element)
|
|
23
|
+
return "\n\n#{body}\n\n" if interface.html_mode?
|
|
24
24
|
|
|
25
|
-
|
|
25
|
+
"#{body}\n\n"
|
|
26
26
|
end
|
|
27
27
|
|
|
28
28
|
private
|
|
@@ -34,9 +34,14 @@ module Markbridge
|
|
|
34
34
|
"[poll#{attrs}]\n#{options}\n[/poll]"
|
|
35
35
|
end
|
|
36
36
|
|
|
37
|
+
NAMES_WITHOUT_ATTRIBUTE = Set[nil, "poll"].freeze
|
|
38
|
+
private_constant :NAMES_WITHOUT_ATTRIBUTE
|
|
39
|
+
|
|
37
40
|
def build_attributes(element)
|
|
38
41
|
parts = []
|
|
39
|
-
|
|
42
|
+
unless NAMES_WITHOUT_ATTRIBUTE.include?(element.name)
|
|
43
|
+
parts << %( name="#{element.name}")
|
|
44
|
+
end
|
|
40
45
|
parts << %( type="#{element.type}") if element.type
|
|
41
46
|
parts << %( results="#{element.results}") if element.results
|
|
42
47
|
parts << %( public="true") if element.public
|
|
@@ -11,19 +11,25 @@ module Markbridge
|
|
|
11
11
|
child_context = interface.with_parent(element)
|
|
12
12
|
content = interface.render_children(element, context: child_context)
|
|
13
13
|
|
|
14
|
+
return "<blockquote>#{content}</blockquote>" if interface.html_mode?
|
|
15
|
+
|
|
14
16
|
# Build Discourse quote BBCode
|
|
15
17
|
# Format: [quote="username, post:123, topic:456"]content[/quote]
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
18
|
+
body =
|
|
19
|
+
if element.post && element.topic && element.username
|
|
20
|
+
# Full Discourse quote with context
|
|
21
|
+
"[quote=\"#{element.username}, post:#{element.post}, topic:#{element.topic}\"]\n#{content}\n[/quote]"
|
|
22
|
+
elsif element.author
|
|
23
|
+
# Quote with author attribution only
|
|
24
|
+
"[quote=\"#{element.author}\"]\n#{content}\n[/quote]"
|
|
25
|
+
else
|
|
26
|
+
# Plain quote rendered as Markdown blockquote
|
|
27
|
+
content.split("\n").map { |line| "> #{line}" }.join("\n")
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
# Trailing blank line so consecutive quotes don't merge and
|
|
31
|
+
# following content starts a new paragraph.
|
|
32
|
+
"#{body}\n\n"
|
|
27
33
|
end
|
|
28
34
|
end
|
|
29
35
|
end
|
|
@@ -11,12 +11,21 @@ module Markbridge
|
|
|
11
11
|
child_context = interface.with_parent(element)
|
|
12
12
|
content = interface.render_children(element, context: child_context)
|
|
13
13
|
|
|
14
|
+
return render_html(element.title, content) if interface.html_mode?
|
|
15
|
+
|
|
14
16
|
if element.title
|
|
15
17
|
"[spoiler=#{element.title}]#{content}[/spoiler]"
|
|
16
18
|
else
|
|
17
19
|
"[spoiler]#{content}[/spoiler]"
|
|
18
20
|
end
|
|
19
21
|
end
|
|
22
|
+
|
|
23
|
+
private
|
|
24
|
+
|
|
25
|
+
def render_html(title, content)
|
|
26
|
+
summary = "<summary>#{title ? HtmlEscaper.escape(title) : "Spoiler"}</summary>"
|
|
27
|
+
"<details>#{summary}#{content}</details>"
|
|
28
|
+
end
|
|
20
29
|
end
|
|
21
30
|
end
|
|
22
31
|
end
|
|
@@ -9,6 +9,8 @@ module Markbridge
|
|
|
9
9
|
def render(element, interface)
|
|
10
10
|
child_context = interface.with_parent(element)
|
|
11
11
|
content = interface.render_children(element, context: child_context)
|
|
12
|
+
return "<s>#{content}</s>" if interface.html_mode?
|
|
13
|
+
|
|
12
14
|
interface.wrap_inline(content, "~~")
|
|
13
15
|
end
|
|
14
16
|
end
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Markbridge
|
|
4
|
+
module Renderers
|
|
5
|
+
module Discourse
|
|
6
|
+
module Tags
|
|
7
|
+
# Tag for rendering table cells (passthrough - renders children only)
|
|
8
|
+
# The TableTag handles cells directly; this is a safety net for standalone rendering.
|
|
9
|
+
class TableCellTag < Tag
|
|
10
|
+
def render(element, interface)
|
|
11
|
+
child_context = interface.with_parent(element)
|
|
12
|
+
interface.render_children(element, context: child_context)
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Markbridge
|
|
4
|
+
module Renderers
|
|
5
|
+
module Discourse
|
|
6
|
+
module Tags
|
|
7
|
+
# Tag for rendering table rows (passthrough - renders children only)
|
|
8
|
+
# The TableTag handles rows directly; this is a safety net for standalone rendering.
|
|
9
|
+
class TableRowTag < Tag
|
|
10
|
+
def render(element, interface)
|
|
11
|
+
child_context = interface.with_parent(element)
|
|
12
|
+
interface.render_children(element, context: child_context)
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Markbridge
|
|
4
|
+
module Renderers
|
|
5
|
+
module Discourse
|
|
6
|
+
module Tags
|
|
7
|
+
# Tag for rendering tables as Markdown pipe tables with HTML fallback
|
|
8
|
+
class TableTag < Tag
|
|
9
|
+
def render(element, interface)
|
|
10
|
+
child_context = interface.with_parent(element)
|
|
11
|
+
rows_data = extract_rows(element, interface, child_context)
|
|
12
|
+
|
|
13
|
+
return "" if rows_data.empty?
|
|
14
|
+
|
|
15
|
+
if markdown_compatible?(rows_data, interface)
|
|
16
|
+
render_markdown(rows_data)
|
|
17
|
+
else
|
|
18
|
+
# Re-render cells in html_mode so inline Markdown like **bold** becomes
|
|
19
|
+
# <strong>bold</strong>; CommonMark would not parse Markdown inside an HTML block.
|
|
20
|
+
html_rows = extract_rows(element, interface, child_context.with_html_mode(true))
|
|
21
|
+
render_html(html_rows)
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
private
|
|
26
|
+
|
|
27
|
+
# Extract rendered cell data from each row
|
|
28
|
+
# @return [Array<Hash>] array of {cells: [{content:, header:}], ...}
|
|
29
|
+
def extract_rows(element, interface, child_context)
|
|
30
|
+
element.children.filter_map do |child|
|
|
31
|
+
next unless child.instance_of?(AST::TableRow)
|
|
32
|
+
|
|
33
|
+
cells =
|
|
34
|
+
child.children.filter_map do |cell|
|
|
35
|
+
next unless cell.instance_of?(AST::TableCell)
|
|
36
|
+
|
|
37
|
+
# Push the cell itself into the parent chain so descendants
|
|
38
|
+
# can detect they're inside a cell via has_parent?.
|
|
39
|
+
cell_context = child_context.with_parent(cell)
|
|
40
|
+
content = interface.render_children(cell, context: cell_context).strip
|
|
41
|
+
{ content:, header: cell.header? }
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
{ cells: } unless cells.empty?
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
# Check if the table can be rendered as Markdown
|
|
49
|
+
def markdown_compatible?(rows_data, interface)
|
|
50
|
+
return false if interface.has_parent?(AST::Table)
|
|
51
|
+
|
|
52
|
+
cell_count = rows_data.first[:cells].length
|
|
53
|
+
rows_data.all? do |row|
|
|
54
|
+
row[:cells].length == cell_count &&
|
|
55
|
+
row[:cells].none? { |c| c[:content].include?("\n") }
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
# Render as Markdown pipe table
|
|
60
|
+
def render_markdown(rows_data)
|
|
61
|
+
header_idx = rows_data.index { |r| r[:cells].all? { |c| c[:header] } }
|
|
62
|
+
header_row = header_idx ? rows_data[header_idx] : rows_data.first
|
|
63
|
+
data_rows =
|
|
64
|
+
(
|
|
65
|
+
if header_idx
|
|
66
|
+
rows_data[0...header_idx] + rows_data[(header_idx + 1)..]
|
|
67
|
+
else
|
|
68
|
+
rows_data[1..]
|
|
69
|
+
end
|
|
70
|
+
)
|
|
71
|
+
|
|
72
|
+
col_count = header_row[:cells].length
|
|
73
|
+
lines = []
|
|
74
|
+
lines << format_row(header_row[:cells])
|
|
75
|
+
lines << "| #{(["---"] * col_count).join(" | ")} |"
|
|
76
|
+
data_rows.each { |row| lines << format_row(row[:cells]) }
|
|
77
|
+
|
|
78
|
+
"\n\n#{lines.join("\n")}\n\n"
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
# Format a single row as a Markdown pipe row
|
|
82
|
+
def format_row(cells)
|
|
83
|
+
# Pipe characters in cell content are already escaped by the markdown escaper
|
|
84
|
+
"| #{cells.map { |c| c[:content] }.join(" | ")} |"
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
# Render as HTML table
|
|
88
|
+
def render_html(rows_data)
|
|
89
|
+
has_header = rows_data.any? { |r| r[:cells].any? { |c| c[:header] } }
|
|
90
|
+
lines = ["<table>"]
|
|
91
|
+
|
|
92
|
+
if has_header
|
|
93
|
+
header_rows, body_rows = rows_data.partition { |r| r[:cells].all? { |c| c[:header] } }
|
|
94
|
+
|
|
95
|
+
unless header_rows.empty?
|
|
96
|
+
lines << "<thead>"
|
|
97
|
+
header_rows.each { |row| lines << html_row(row) }
|
|
98
|
+
lines << "</thead>"
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
unless body_rows.empty?
|
|
102
|
+
lines << "<tbody>"
|
|
103
|
+
body_rows.each { |row| lines << html_row(row) }
|
|
104
|
+
lines << "</tbody>"
|
|
105
|
+
end
|
|
106
|
+
else
|
|
107
|
+
rows_data.each { |row| lines << html_row(row) }
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
lines << "</table>"
|
|
111
|
+
"\n\n#{lines.join("\n")}\n\n"
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
# Render a single HTML table row
|
|
115
|
+
def html_row(row)
|
|
116
|
+
cells_html =
|
|
117
|
+
row[:cells].map do |cell|
|
|
118
|
+
tag = cell[:header] ? "th" : "td"
|
|
119
|
+
"<#{tag}>#{cell[:content]}</#{tag}>"
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
"<tr>#{cells_html.join}</tr>"
|
|
123
|
+
end
|
|
124
|
+
end
|
|
125
|
+
end
|
|
126
|
+
end
|
|
127
|
+
end
|
|
128
|
+
end
|
|
@@ -4,13 +4,20 @@ module Markbridge
|
|
|
4
4
|
module Renderers
|
|
5
5
|
module Discourse
|
|
6
6
|
module Tags
|
|
7
|
-
#
|
|
7
|
+
# Discourse's HTML sanitizer strips raw `<u>`, but `[u]…[/u]` is
|
|
8
|
+
# cooked by the BBCode plugin into `<span class="bbcode-u">`. The
|
|
9
|
+
# BBCode plugin runs on Markdown source, not on raw HTML inside an
|
|
10
|
+
# HTML block, so in html_mode we emit the cooked form directly.
|
|
8
11
|
class UnderlineTag < Tag
|
|
9
12
|
def render(element, interface)
|
|
10
13
|
child_context = interface.with_parent(element)
|
|
11
14
|
content = interface.render_children(element, context: child_context)
|
|
12
|
-
|
|
13
|
-
|
|
15
|
+
|
|
16
|
+
if interface.html_mode?
|
|
17
|
+
%(<span class="bbcode-u">#{content}</span>)
|
|
18
|
+
else
|
|
19
|
+
"[u]#{content}[/u]"
|
|
20
|
+
end
|
|
14
21
|
end
|
|
15
22
|
end
|
|
16
23
|
end
|
|
@@ -30,6 +30,8 @@ module Markbridge
|
|
|
30
30
|
# end
|
|
31
31
|
class UploadTag < Tag
|
|
32
32
|
def render(element, interface)
|
|
33
|
+
return build_upload_html(element) if interface.html_mode?
|
|
34
|
+
|
|
33
35
|
# Return raw Markdown if available, otherwise reconstruct
|
|
34
36
|
return element.raw if element.raw
|
|
35
37
|
|
|
@@ -56,7 +58,7 @@ module Markbridge
|
|
|
56
58
|
def build_attachment_markdown(element)
|
|
57
59
|
filename = element.filename || "attachment"
|
|
58
60
|
url = build_upload_url(element)
|
|
59
|
-
size_part =
|
|
61
|
+
size_part = " (#{element.size})" if element.size
|
|
60
62
|
|
|
61
63
|
"[#{filename}|attachment](#{url})#{size_part}"
|
|
62
64
|
end
|
|
@@ -73,6 +75,31 @@ module Markbridge
|
|
|
73
75
|
filename = element.filename || element.sha1
|
|
74
76
|
"upload://#{filename}"
|
|
75
77
|
end
|
|
78
|
+
|
|
79
|
+
# html_mode reconstructs from the AST fields rather than reusing
|
|
80
|
+
# element.raw — raw is Markdown, which CommonMark passes through
|
|
81
|
+
# unchanged inside an HTML block (so the user would see the
|
|
82
|
+
# literal "" string instead of an image).
|
|
83
|
+
def build_upload_html(element)
|
|
84
|
+
if element.type == :image
|
|
85
|
+
build_image_html(element)
|
|
86
|
+
else
|
|
87
|
+
build_attachment_html(element)
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
def build_image_html(element)
|
|
92
|
+
src = HtmlEscaper.escape(build_upload_url(element))
|
|
93
|
+
alt = HtmlEscaper.escape(element.alt)
|
|
94
|
+
%(<img src="#{src}" alt="#{alt}">)
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
def build_attachment_html(element)
|
|
98
|
+
href = HtmlEscaper.escape(build_upload_url(element))
|
|
99
|
+
filename = HtmlEscaper.escape(element.filename || "attachment")
|
|
100
|
+
size_part = " (#{HtmlEscaper.escape(element.size)})" if element.size
|
|
101
|
+
%(<a href="#{href}">#{filename}</a>#{size_part})
|
|
102
|
+
end
|
|
76
103
|
end
|
|
77
104
|
end
|
|
78
105
|
end
|
|
@@ -11,10 +11,12 @@ module Markbridge
|
|
|
11
11
|
text = interface.render_children(element, context: child_context)
|
|
12
12
|
href = element.href
|
|
13
13
|
|
|
14
|
-
|
|
15
|
-
|
|
14
|
+
return text unless href&.match?(/\A(?:https?|ftps?|mailto):/i)
|
|
15
|
+
|
|
16
|
+
if interface.html_mode?
|
|
17
|
+
%(<a href="#{HtmlEscaper.escape(href)}">#{text}</a>)
|
|
16
18
|
else
|
|
17
|
-
text
|
|
19
|
+
"[#{text}](#{href})"
|
|
18
20
|
end
|
|
19
21
|
end
|
|
20
22
|
end
|
|
@@ -5,6 +5,7 @@ require_relative "discourse/tag_library"
|
|
|
5
5
|
require_relative "discourse/render_context"
|
|
6
6
|
require_relative "discourse/rendering_interface"
|
|
7
7
|
require_relative "discourse/markdown_escaper"
|
|
8
|
+
require_relative "discourse/html_escaper"
|
|
8
9
|
|
|
9
10
|
# Builders
|
|
10
11
|
require_relative "discourse/builders/list_item_builder"
|
|
@@ -30,6 +31,9 @@ require_relative "discourse/tags/spoiler_tag"
|
|
|
30
31
|
require_relative "discourse/tags/strikethrough_tag"
|
|
31
32
|
require_relative "discourse/tags/subscript_tag"
|
|
32
33
|
require_relative "discourse/tags/superscript_tag"
|
|
34
|
+
require_relative "discourse/tags/table_tag"
|
|
35
|
+
require_relative "discourse/tags/table_row_tag"
|
|
36
|
+
require_relative "discourse/tags/table_cell_tag"
|
|
33
37
|
require_relative "discourse/tags/underline_tag"
|
|
34
38
|
require_relative "discourse/tags/url_tag"
|
|
35
39
|
|
data/lib/markbridge/version.rb
CHANGED
data/lib/markbridge.rb
CHANGED
|
@@ -5,8 +5,6 @@ require_relative "markbridge/configuration"
|
|
|
5
5
|
|
|
6
6
|
require_relative "markbridge/ast"
|
|
7
7
|
require_relative "markbridge/renderers/discourse"
|
|
8
|
-
require_relative "markbridge/parsers/media_wiki"
|
|
9
|
-
require_relative "markbridge/parsers/text_formatter"
|
|
10
8
|
require_relative "markbridge/processors"
|
|
11
9
|
|
|
12
10
|
module Markbridge
|
|
@@ -16,13 +14,8 @@ module Markbridge
|
|
|
16
14
|
# @param handlers [HandlerRegistry, nil] custom handler registry or use default
|
|
17
15
|
# @return [AST::Document]
|
|
18
16
|
def parse_bbcode(input, handlers: nil)
|
|
19
|
-
raise ArgumentError, "input cannot be nil" if input.nil?
|
|
20
|
-
|
|
21
|
-
input = input.to_s # Coerce to string
|
|
22
17
|
handlers ||= default_handlers
|
|
23
|
-
|
|
24
|
-
parser = Parsers::BBCode::Parser.new(handlers:)
|
|
25
|
-
parser.parse(input)
|
|
18
|
+
parse_with(Parsers::BBCode::Parser, input, handlers:)
|
|
26
19
|
end
|
|
27
20
|
|
|
28
21
|
# Convert BBCode to Discourse Markdown
|
|
@@ -31,17 +24,8 @@ module Markbridge
|
|
|
31
24
|
# @param tag_library [TagLibrary, nil] custom tag library or use default
|
|
32
25
|
# @return [String] Markdown output
|
|
33
26
|
def bbcode_to_markdown(input, handlers: nil, tag_library: nil)
|
|
34
|
-
raise ArgumentError, "input cannot be nil" if input.nil?
|
|
35
|
-
|
|
36
|
-
handlers ||= default_handlers
|
|
37
|
-
tag_library ||= default_tag_library
|
|
38
|
-
|
|
39
27
|
ast = parse_bbcode(input, handlers:)
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
# Clean up output
|
|
43
|
-
result = renderer.render(ast)
|
|
44
|
-
cleanup_markdown(result)
|
|
28
|
+
render_to_markdown(ast, tag_library:)
|
|
45
29
|
end
|
|
46
30
|
|
|
47
31
|
# Parse HTML to AST
|
|
@@ -49,13 +33,8 @@ module Markbridge
|
|
|
49
33
|
# @param handlers [HandlerRegistry, nil] custom handler registry or use default
|
|
50
34
|
# @return [AST::Document]
|
|
51
35
|
def parse_html(input, handlers: nil)
|
|
52
|
-
raise ArgumentError, "input cannot be nil" if input.nil?
|
|
53
|
-
|
|
54
|
-
input = input.to_s # Coerce to string
|
|
55
36
|
handlers ||= default_html_handlers
|
|
56
|
-
|
|
57
|
-
parser = Parsers::HTML::Parser.new(handlers:)
|
|
58
|
-
parser.parse(input)
|
|
37
|
+
parse_with(Parsers::HTML::Parser, input, handlers:)
|
|
59
38
|
end
|
|
60
39
|
|
|
61
40
|
# Convert HTML to Discourse Markdown
|
|
@@ -64,17 +43,8 @@ module Markbridge
|
|
|
64
43
|
# @param tag_library [TagLibrary, nil] custom tag library or use default
|
|
65
44
|
# @return [String] Markdown output
|
|
66
45
|
def html_to_markdown(input, handlers: nil, tag_library: nil)
|
|
67
|
-
raise ArgumentError, "input cannot be nil" if input.nil?
|
|
68
|
-
|
|
69
|
-
handlers ||= default_html_handlers
|
|
70
|
-
tag_library ||= default_tag_library
|
|
71
|
-
|
|
72
46
|
ast = parse_html(input, handlers:)
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
# Clean up output
|
|
76
|
-
result = renderer.render(ast)
|
|
77
|
-
cleanup_markdown(result)
|
|
47
|
+
render_to_markdown(ast, tag_library:)
|
|
78
48
|
end
|
|
79
49
|
|
|
80
50
|
# Parse s9e/TextFormatter XML to AST
|
|
@@ -82,13 +52,8 @@ module Markbridge
|
|
|
82
52
|
# @param handlers [Parsers::TextFormatter::HandlerRegistry, nil] custom handler registry or use default
|
|
83
53
|
# @return [AST::Document]
|
|
84
54
|
def parse_text_formatter_xml(input, handlers: nil)
|
|
85
|
-
raise ArgumentError, "input cannot be nil" if input.nil?
|
|
86
|
-
|
|
87
|
-
input = input.to_s
|
|
88
55
|
handlers ||= default_text_formatter_handlers
|
|
89
|
-
|
|
90
|
-
parser = Parsers::TextFormatter::Parser.new(handlers:)
|
|
91
|
-
parser.parse(input)
|
|
56
|
+
parse_with(Parsers::TextFormatter::Parser, input, handlers:)
|
|
92
57
|
end
|
|
93
58
|
|
|
94
59
|
# Convert s9e/TextFormatter XML to Discourse Markdown
|
|
@@ -97,43 +62,30 @@ module Markbridge
|
|
|
97
62
|
# @param tag_library [TagLibrary, nil] custom tag library or use default
|
|
98
63
|
# @return [String] Markdown output
|
|
99
64
|
def text_formatter_xml_to_markdown(input, handlers: nil, tag_library: nil)
|
|
100
|
-
raise ArgumentError, "input cannot be nil" if input.nil?
|
|
101
|
-
|
|
102
|
-
handlers ||= default_text_formatter_handlers
|
|
103
|
-
tag_library ||= default_tag_library
|
|
104
|
-
|
|
105
65
|
ast = parse_text_formatter_xml(input, handlers:)
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
result = renderer.render(ast)
|
|
109
|
-
cleanup_markdown(result)
|
|
66
|
+
render_to_markdown(ast, tag_library:)
|
|
110
67
|
end
|
|
111
68
|
|
|
112
69
|
# Parse MediaWiki wikitext to AST
|
|
113
70
|
# @param input [String] MediaWiki source
|
|
71
|
+
# @param inline_tag_registry [Parsers::MediaWiki::InlineTagRegistry, nil] custom registry
|
|
114
72
|
# @return [AST::Document]
|
|
115
|
-
def parse_mediawiki(input)
|
|
73
|
+
def parse_mediawiki(input, inline_tag_registry: nil)
|
|
116
74
|
raise ArgumentError, "input cannot be nil" if input.nil?
|
|
117
75
|
|
|
118
76
|
input = input.to_s
|
|
119
|
-
parser = Parsers::MediaWiki::Parser.new
|
|
77
|
+
parser = Parsers::MediaWiki::Parser.new(inline_tag_registry:)
|
|
120
78
|
parser.parse(input)
|
|
121
79
|
end
|
|
122
80
|
|
|
123
81
|
# Convert MediaWiki wikitext to Discourse Markdown
|
|
124
82
|
# @param input [String] MediaWiki source
|
|
83
|
+
# @param inline_tag_registry [Parsers::MediaWiki::InlineTagRegistry, nil] custom registry
|
|
125
84
|
# @param tag_library [TagLibrary, nil] custom tag library or use default
|
|
126
85
|
# @return [String] Markdown output
|
|
127
|
-
def mediawiki_to_markdown(input, tag_library: nil)
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
tag_library ||= default_tag_library
|
|
131
|
-
|
|
132
|
-
ast = parse_mediawiki(input)
|
|
133
|
-
renderer = build_renderer(tag_library:)
|
|
134
|
-
|
|
135
|
-
result = renderer.render(ast)
|
|
136
|
-
cleanup_markdown(result)
|
|
86
|
+
def mediawiki_to_markdown(input, inline_tag_registry: nil, tag_library: nil)
|
|
87
|
+
ast = parse_mediawiki(input, inline_tag_registry:)
|
|
88
|
+
render_to_markdown(ast, tag_library:)
|
|
137
89
|
end
|
|
138
90
|
|
|
139
91
|
# Get default handler registry
|
|
@@ -183,6 +135,19 @@ module Markbridge
|
|
|
183
135
|
|
|
184
136
|
private
|
|
185
137
|
|
|
138
|
+
def parse_with(parser_class, input, handlers:)
|
|
139
|
+
raise ArgumentError, "input cannot be nil" if input.nil?
|
|
140
|
+
|
|
141
|
+
parser = parser_class.new(handlers:)
|
|
142
|
+
parser.parse(input.to_s)
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
def render_to_markdown(ast, tag_library:)
|
|
146
|
+
tag_library ||= default_tag_library
|
|
147
|
+
renderer = build_renderer(tag_library:)
|
|
148
|
+
cleanup_markdown(renderer.render(ast))
|
|
149
|
+
end
|
|
150
|
+
|
|
186
151
|
def build_renderer(tag_library:)
|
|
187
152
|
escaper =
|
|
188
153
|
Renderers::Discourse::MarkdownEscaper.new(
|
|
@@ -194,7 +159,7 @@ module Markbridge
|
|
|
194
159
|
def cleanup_markdown(text)
|
|
195
160
|
text
|
|
196
161
|
.gsub(/\n{3,}/, "\n\n") # Max 2 consecutive newlines
|
|
197
|
-
.gsub(/^[ \t]
|
|
162
|
+
.gsub(/^[ \t]+$/, "") # Remove whitespace-only lines
|
|
198
163
|
.strip # Trim leading/trailing whitespace
|
|
199
164
|
end
|
|
200
165
|
end
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: markbridge
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.1.
|
|
4
|
+
version: 0.1.2
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Discourse Team
|
|
@@ -47,12 +47,16 @@ files:
|
|
|
47
47
|
- lib/markbridge/ast/strikethrough.rb
|
|
48
48
|
- lib/markbridge/ast/subscript.rb
|
|
49
49
|
- lib/markbridge/ast/superscript.rb
|
|
50
|
+
- lib/markbridge/ast/table.rb
|
|
50
51
|
- lib/markbridge/ast/text.rb
|
|
51
52
|
- lib/markbridge/ast/underline.rb
|
|
52
53
|
- lib/markbridge/ast/upload.rb
|
|
53
54
|
- lib/markbridge/ast/url.rb
|
|
55
|
+
- lib/markbridge/bbcode.rb
|
|
54
56
|
- lib/markbridge/configuration.rb
|
|
55
57
|
- lib/markbridge/gem_loader.rb
|
|
58
|
+
- lib/markbridge/html.rb
|
|
59
|
+
- lib/markbridge/mediawiki.rb
|
|
56
60
|
- lib/markbridge/parsers/bbcode.rb
|
|
57
61
|
- lib/markbridge/parsers/bbcode/closing_strategies/base.rb
|
|
58
62
|
- lib/markbridge/parsers/bbcode/closing_strategies/reordering.rb
|
|
@@ -75,6 +79,9 @@ files:
|
|
|
75
79
|
- lib/markbridge/parsers/bbcode/handlers/simple_handler.rb
|
|
76
80
|
- lib/markbridge/parsers/bbcode/handlers/size_handler.rb
|
|
77
81
|
- lib/markbridge/parsers/bbcode/handlers/spoiler_handler.rb
|
|
82
|
+
- lib/markbridge/parsers/bbcode/handlers/table_cell_handler.rb
|
|
83
|
+
- lib/markbridge/parsers/bbcode/handlers/table_handler.rb
|
|
84
|
+
- lib/markbridge/parsers/bbcode/handlers/table_row_handler.rb
|
|
78
85
|
- lib/markbridge/parsers/bbcode/handlers/url_handler.rb
|
|
79
86
|
- lib/markbridge/parsers/bbcode/parser.rb
|
|
80
87
|
- lib/markbridge/parsers/bbcode/parser_state.rb
|
|
@@ -96,10 +103,14 @@ files:
|
|
|
96
103
|
- lib/markbridge/parsers/html/handlers/quote_handler.rb
|
|
97
104
|
- lib/markbridge/parsers/html/handlers/raw_handler.rb
|
|
98
105
|
- lib/markbridge/parsers/html/handlers/simple_handler.rb
|
|
106
|
+
- lib/markbridge/parsers/html/handlers/table_cell_handler.rb
|
|
107
|
+
- lib/markbridge/parsers/html/handlers/table_handler.rb
|
|
108
|
+
- lib/markbridge/parsers/html/handlers/table_row_handler.rb
|
|
99
109
|
- lib/markbridge/parsers/html/handlers/url_handler.rb
|
|
100
110
|
- lib/markbridge/parsers/html/parser.rb
|
|
101
111
|
- lib/markbridge/parsers/media_wiki.rb
|
|
102
112
|
- lib/markbridge/parsers/media_wiki/inline_parser.rb
|
|
113
|
+
- lib/markbridge/parsers/media_wiki/inline_tag_registry.rb
|
|
103
114
|
- lib/markbridge/parsers/media_wiki/parser.rb
|
|
104
115
|
- lib/markbridge/parsers/text_formatter.rb
|
|
105
116
|
- lib/markbridge/parsers/text_formatter/handler_registry.rb
|
|
@@ -112,6 +123,7 @@ files:
|
|
|
112
123
|
- lib/markbridge/parsers/text_formatter/handlers/list_handler.rb
|
|
113
124
|
- lib/markbridge/parsers/text_formatter/handlers/quote_handler.rb
|
|
114
125
|
- lib/markbridge/parsers/text_formatter/handlers/simple_handler.rb
|
|
126
|
+
- lib/markbridge/parsers/text_formatter/handlers/table_cell_handler.rb
|
|
115
127
|
- lib/markbridge/parsers/text_formatter/handlers/url_handler.rb
|
|
116
128
|
- lib/markbridge/parsers/text_formatter/parser.rb
|
|
117
129
|
- lib/markbridge/processors.rb
|
|
@@ -125,6 +137,7 @@ files:
|
|
|
125
137
|
- lib/markbridge/processors/discourse_markdown/scanner.rb
|
|
126
138
|
- lib/markbridge/renderers/discourse.rb
|
|
127
139
|
- lib/markbridge/renderers/discourse/builders/list_item_builder.rb
|
|
140
|
+
- lib/markbridge/renderers/discourse/html_escaper.rb
|
|
128
141
|
- lib/markbridge/renderers/discourse/markdown_escaper.rb
|
|
129
142
|
- lib/markbridge/renderers/discourse/render_context.rb
|
|
130
143
|
- lib/markbridge/renderers/discourse/renderer.rb
|
|
@@ -154,9 +167,13 @@ files:
|
|
|
154
167
|
- lib/markbridge/renderers/discourse/tags/strikethrough_tag.rb
|
|
155
168
|
- lib/markbridge/renderers/discourse/tags/subscript_tag.rb
|
|
156
169
|
- lib/markbridge/renderers/discourse/tags/superscript_tag.rb
|
|
170
|
+
- lib/markbridge/renderers/discourse/tags/table_cell_tag.rb
|
|
171
|
+
- lib/markbridge/renderers/discourse/tags/table_row_tag.rb
|
|
172
|
+
- lib/markbridge/renderers/discourse/tags/table_tag.rb
|
|
157
173
|
- lib/markbridge/renderers/discourse/tags/underline_tag.rb
|
|
158
174
|
- lib/markbridge/renderers/discourse/tags/upload_tag.rb
|
|
159
175
|
- lib/markbridge/renderers/discourse/tags/url_tag.rb
|
|
176
|
+
- lib/markbridge/textformatter.rb
|
|
160
177
|
- lib/markbridge/version.rb
|
|
161
178
|
homepage: https://github.com/discourse/markbridge
|
|
162
179
|
licenses:
|
|
@@ -172,7 +189,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
|
172
189
|
requirements:
|
|
173
190
|
- - ">="
|
|
174
191
|
- !ruby/object:Gem::Version
|
|
175
|
-
version: 3.
|
|
192
|
+
version: 3.3.0
|
|
176
193
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
177
194
|
requirements:
|
|
178
195
|
- - ">="
|