markbridge 0.1.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 +7 -0
- data/LICENSE.txt +21 -0
- data/lib/markbridge/all.rb +9 -0
- data/lib/markbridge/ast/align.rb +24 -0
- data/lib/markbridge/ast/attachment.rb +42 -0
- data/lib/markbridge/ast/bold.rb +13 -0
- data/lib/markbridge/ast/code.rb +27 -0
- data/lib/markbridge/ast/color.rb +25 -0
- data/lib/markbridge/ast/document.rb +27 -0
- data/lib/markbridge/ast/element.rb +47 -0
- data/lib/markbridge/ast/email.rb +27 -0
- data/lib/markbridge/ast/event.rb +59 -0
- data/lib/markbridge/ast/heading.rb +23 -0
- data/lib/markbridge/ast/horizontal_rule.rb +12 -0
- data/lib/markbridge/ast/image.rb +35 -0
- data/lib/markbridge/ast/italic.rb +13 -0
- data/lib/markbridge/ast/line_break.rb +12 -0
- data/lib/markbridge/ast/list.rb +52 -0
- data/lib/markbridge/ast/list_item.rb +13 -0
- data/lib/markbridge/ast/markdown_text.rb +37 -0
- data/lib/markbridge/ast/mention.rb +29 -0
- data/lib/markbridge/ast/node.rb +19 -0
- data/lib/markbridge/ast/paragraph.rb +13 -0
- data/lib/markbridge/ast/poll.rb +74 -0
- data/lib/markbridge/ast/quote.rb +46 -0
- data/lib/markbridge/ast/size.rb +25 -0
- data/lib/markbridge/ast/spoiler.rb +27 -0
- data/lib/markbridge/ast/strikethrough.rb +13 -0
- data/lib/markbridge/ast/subscript.rb +13 -0
- data/lib/markbridge/ast/superscript.rb +13 -0
- data/lib/markbridge/ast/text.rb +38 -0
- data/lib/markbridge/ast/underline.rb +13 -0
- data/lib/markbridge/ast/upload.rb +74 -0
- data/lib/markbridge/ast/url.rb +27 -0
- data/lib/markbridge/ast.rb +42 -0
- data/lib/markbridge/configuration.rb +11 -0
- data/lib/markbridge/gem_loader.rb +23 -0
- data/lib/markbridge/parsers/bbcode/closing_strategies/base.rb +37 -0
- data/lib/markbridge/parsers/bbcode/closing_strategies/reordering.rb +17 -0
- data/lib/markbridge/parsers/bbcode/closing_strategies/strict.rb +12 -0
- data/lib/markbridge/parsers/bbcode/closing_strategies/tag_reconciler.rb +121 -0
- data/lib/markbridge/parsers/bbcode/errors/max_depth_exceeded_error.rb +13 -0
- data/lib/markbridge/parsers/bbcode/handler_registry.rb +160 -0
- data/lib/markbridge/parsers/bbcode/handlers/align_handler.rb +26 -0
- data/lib/markbridge/parsers/bbcode/handlers/attachment_handler.rb +104 -0
- data/lib/markbridge/parsers/bbcode/handlers/base_handler.rb +44 -0
- data/lib/markbridge/parsers/bbcode/handlers/code_handler.rb +25 -0
- data/lib/markbridge/parsers/bbcode/handlers/color_handler.rb +31 -0
- data/lib/markbridge/parsers/bbcode/handlers/email_handler.rb +25 -0
- data/lib/markbridge/parsers/bbcode/handlers/image_handler.rb +51 -0
- data/lib/markbridge/parsers/bbcode/handlers/list_handler.rb +36 -0
- data/lib/markbridge/parsers/bbcode/handlers/list_item_handler.rb +26 -0
- data/lib/markbridge/parsers/bbcode/handlers/quote_handler.rb +64 -0
- data/lib/markbridge/parsers/bbcode/handlers/raw_handler.rb +48 -0
- data/lib/markbridge/parsers/bbcode/handlers/self_closing_handler.rb +28 -0
- data/lib/markbridge/parsers/bbcode/handlers/simple_handler.rb +28 -0
- data/lib/markbridge/parsers/bbcode/handlers/size_handler.rb +31 -0
- data/lib/markbridge/parsers/bbcode/handlers/spoiler_handler.rb +28 -0
- data/lib/markbridge/parsers/bbcode/handlers/url_handler.rb +24 -0
- data/lib/markbridge/parsers/bbcode/parser.rb +123 -0
- data/lib/markbridge/parsers/bbcode/parser_state.rb +93 -0
- data/lib/markbridge/parsers/bbcode/peekable_enumerator.rb +126 -0
- data/lib/markbridge/parsers/bbcode/raw_content_collector.rb +35 -0
- data/lib/markbridge/parsers/bbcode/raw_content_result.rb +25 -0
- data/lib/markbridge/parsers/bbcode/scanner.rb +231 -0
- data/lib/markbridge/parsers/bbcode/tokens/tag_end_token.rb +21 -0
- data/lib/markbridge/parsers/bbcode/tokens/tag_start_token.rb +23 -0
- data/lib/markbridge/parsers/bbcode/tokens/text_token.rb +23 -0
- data/lib/markbridge/parsers/bbcode/tokens/token.rb +16 -0
- data/lib/markbridge/parsers/bbcode.rb +56 -0
- data/lib/markbridge/parsers/html/handler_registry.rb +87 -0
- data/lib/markbridge/parsers/html/handlers/base_handler.rb +27 -0
- data/lib/markbridge/parsers/html/handlers/image_handler.rb +40 -0
- data/lib/markbridge/parsers/html/handlers/list_handler.rb +29 -0
- data/lib/markbridge/parsers/html/handlers/list_item_handler.rb +26 -0
- data/lib/markbridge/parsers/html/handlers/paragraph_handler.rb +17 -0
- data/lib/markbridge/parsers/html/handlers/quote_handler.rb +28 -0
- data/lib/markbridge/parsers/html/handlers/raw_handler.rb +33 -0
- data/lib/markbridge/parsers/html/handlers/simple_handler.rb +26 -0
- data/lib/markbridge/parsers/html/handlers/url_handler.rb +27 -0
- data/lib/markbridge/parsers/html/parser.rb +113 -0
- data/lib/markbridge/parsers/html.rb +30 -0
- data/lib/markbridge/parsers/media_wiki/inline_parser.rb +332 -0
- data/lib/markbridge/parsers/media_wiki/parser.rb +279 -0
- data/lib/markbridge/parsers/media_wiki.rb +15 -0
- data/lib/markbridge/parsers/text_formatter/handler_registry.rb +130 -0
- data/lib/markbridge/parsers/text_formatter/handlers/attachment_handler.rb +33 -0
- data/lib/markbridge/parsers/text_formatter/handlers/attribute_handler.rb +40 -0
- data/lib/markbridge/parsers/text_formatter/handlers/base_handler.rb +45 -0
- data/lib/markbridge/parsers/text_formatter/handlers/code_handler.rb +28 -0
- data/lib/markbridge/parsers/text_formatter/handlers/email_handler.rb +27 -0
- data/lib/markbridge/parsers/text_formatter/handlers/image_handler.rb +32 -0
- data/lib/markbridge/parsers/text_formatter/handlers/list_handler.rb +31 -0
- data/lib/markbridge/parsers/text_formatter/handlers/quote_handler.rb +33 -0
- data/lib/markbridge/parsers/text_formatter/handlers/simple_handler.rb +37 -0
- data/lib/markbridge/parsers/text_formatter/handlers/url_handler.rb +29 -0
- data/lib/markbridge/parsers/text_formatter/parser.rb +132 -0
- data/lib/markbridge/parsers/text_formatter.rb +31 -0
- data/lib/markbridge/processors/discourse_markdown/code_block_tracker.rb +199 -0
- data/lib/markbridge/processors/discourse_markdown/detectors/base.rb +57 -0
- data/lib/markbridge/processors/discourse_markdown/detectors/event.rb +73 -0
- data/lib/markbridge/processors/discourse_markdown/detectors/mention.rb +57 -0
- data/lib/markbridge/processors/discourse_markdown/detectors/poll.rb +90 -0
- data/lib/markbridge/processors/discourse_markdown/detectors/upload.rb +123 -0
- data/lib/markbridge/processors/discourse_markdown/scanner.rb +199 -0
- data/lib/markbridge/processors/discourse_markdown.rb +16 -0
- data/lib/markbridge/processors.rb +8 -0
- data/lib/markbridge/renderers/discourse/builders/list_item_builder.rb +83 -0
- data/lib/markbridge/renderers/discourse/markdown_escaper.rb +468 -0
- data/lib/markbridge/renderers/discourse/render_context.rb +80 -0
- data/lib/markbridge/renderers/discourse/renderer.rb +63 -0
- data/lib/markbridge/renderers/discourse/rendering_interface.rb +86 -0
- data/lib/markbridge/renderers/discourse/tag.rb +29 -0
- data/lib/markbridge/renderers/discourse/tag_library.rb +67 -0
- data/lib/markbridge/renderers/discourse/tags/align_tag.rb +24 -0
- data/lib/markbridge/renderers/discourse/tags/attachment_tag.rb +46 -0
- data/lib/markbridge/renderers/discourse/tags/bold_tag.rb +18 -0
- data/lib/markbridge/renderers/discourse/tags/code_tag.rb +54 -0
- data/lib/markbridge/renderers/discourse/tags/color_tag.rb +27 -0
- data/lib/markbridge/renderers/discourse/tags/email_tag.rb +24 -0
- data/lib/markbridge/renderers/discourse/tags/event_tag.rb +49 -0
- data/lib/markbridge/renderers/discourse/tags/heading_tag.rb +21 -0
- data/lib/markbridge/renderers/discourse/tags/horizontal_rule_tag.rb +16 -0
- data/lib/markbridge/renderers/discourse/tags/image_tag.rb +29 -0
- data/lib/markbridge/renderers/discourse/tags/italic_tag.rb +18 -0
- data/lib/markbridge/renderers/discourse/tags/line_break_tag.rb +16 -0
- data/lib/markbridge/renderers/discourse/tags/list_item_tag.rb +87 -0
- data/lib/markbridge/renderers/discourse/tags/list_tag.rb +39 -0
- data/lib/markbridge/renderers/discourse/tags/mention_tag.rb +34 -0
- data/lib/markbridge/renderers/discourse/tags/paragraph_tag.rb +21 -0
- data/lib/markbridge/renderers/discourse/tags/poll_tag.rb +51 -0
- data/lib/markbridge/renderers/discourse/tags/quote_tag.rb +32 -0
- data/lib/markbridge/renderers/discourse/tags/size_tag.rb +27 -0
- data/lib/markbridge/renderers/discourse/tags/spoiler_tag.rb +24 -0
- data/lib/markbridge/renderers/discourse/tags/strikethrough_tag.rb +18 -0
- data/lib/markbridge/renderers/discourse/tags/subscript_tag.rb +19 -0
- data/lib/markbridge/renderers/discourse/tags/superscript_tag.rb +19 -0
- data/lib/markbridge/renderers/discourse/tags/underline_tag.rb +19 -0
- data/lib/markbridge/renderers/discourse/tags/upload_tag.rb +80 -0
- data/lib/markbridge/renderers/discourse/tags/url_tag.rb +24 -0
- data/lib/markbridge/renderers/discourse.rb +50 -0
- data/lib/markbridge/version.rb +5 -0
- data/lib/markbridge.rb +201 -0
- metadata +186 -0
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Markbridge
|
|
4
|
+
module Renderers
|
|
5
|
+
module Discourse
|
|
6
|
+
module Tags
|
|
7
|
+
# Placeholder tag for rendering polls.
|
|
8
|
+
#
|
|
9
|
+
# This is a STUB implementation that outputs the original raw BBCode.
|
|
10
|
+
# Applications using Markbridge should provide their own custom tag
|
|
11
|
+
# to render polls as placeholders or convert them to another format.
|
|
12
|
+
#
|
|
13
|
+
# @example Custom renderer with placeholder
|
|
14
|
+
# class MyPollTag < Markbridge::Renderers::Discourse::Tags::PollTag
|
|
15
|
+
# def render(element, interface)
|
|
16
|
+
# id = register_poll(element)
|
|
17
|
+
# "<<POLL:#{id}>>"
|
|
18
|
+
# end
|
|
19
|
+
# end
|
|
20
|
+
class PollTag < Tag
|
|
21
|
+
def render(element, interface)
|
|
22
|
+
# Return raw BBCode if available, otherwise reconstruct
|
|
23
|
+
return element.raw if element.raw
|
|
24
|
+
|
|
25
|
+
build_poll_bbcode(element)
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
private
|
|
29
|
+
|
|
30
|
+
def build_poll_bbcode(element)
|
|
31
|
+
attrs = build_attributes(element)
|
|
32
|
+
options = element.options.map { |opt| "* #{opt}" }.join("\n")
|
|
33
|
+
|
|
34
|
+
"[poll#{attrs}]\n#{options}\n[/poll]"
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def build_attributes(element)
|
|
38
|
+
parts = []
|
|
39
|
+
parts << %( name="#{element.name}") if element.name && element.name != "poll"
|
|
40
|
+
parts << %( type="#{element.type}") if element.type
|
|
41
|
+
parts << %( results="#{element.results}") if element.results
|
|
42
|
+
parts << %( public="true") if element.public
|
|
43
|
+
parts << %( chartType="#{element.chart_type}") if element.chart_type
|
|
44
|
+
|
|
45
|
+
parts.join
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
end
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Markbridge
|
|
4
|
+
module Renderers
|
|
5
|
+
module Discourse
|
|
6
|
+
module Tags
|
|
7
|
+
# Tag for rendering quotes
|
|
8
|
+
# Renders to Discourse BBCode quote format to preserve attribution
|
|
9
|
+
class QuoteTag < Tag
|
|
10
|
+
def render(element, interface)
|
|
11
|
+
child_context = interface.with_parent(element)
|
|
12
|
+
content = interface.render_children(element, context: child_context)
|
|
13
|
+
|
|
14
|
+
# Build Discourse quote BBCode
|
|
15
|
+
# Format: [quote="username, post:123, topic:456"]content[/quote]
|
|
16
|
+
if element.post && element.topic && element.username
|
|
17
|
+
# Full Discourse quote with context
|
|
18
|
+
"[quote=\"#{element.username}, post:#{element.post}, topic:#{element.topic}\"]\n#{content}\n[/quote]"
|
|
19
|
+
elsif element.author
|
|
20
|
+
# Quote with author attribution only
|
|
21
|
+
"[quote=\"#{element.author}\"]\n#{content}\n[/quote]"
|
|
22
|
+
else
|
|
23
|
+
# Plain quote - could use Markdown blockquote or BBCode
|
|
24
|
+
# Using Markdown blockquote for plain quotes
|
|
25
|
+
content.split("\n").map { |line| "> #{line}" }.join("\n")
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Markbridge
|
|
4
|
+
module Renderers
|
|
5
|
+
module Discourse
|
|
6
|
+
module Tags
|
|
7
|
+
# Tag for rendering sized text
|
|
8
|
+
# Note: Discourse doesn't support inline size changes by default
|
|
9
|
+
# Renders as plain text with HTML comment noting the size was lost
|
|
10
|
+
class SizeTag < Tag
|
|
11
|
+
def render(element, interface)
|
|
12
|
+
child_context = interface.with_parent(element)
|
|
13
|
+
content = interface.render_children(element, context: child_context)
|
|
14
|
+
|
|
15
|
+
if element.size
|
|
16
|
+
# Render as HTML span with style - requires HTML to be enabled
|
|
17
|
+
# Alternative: just output the text without size
|
|
18
|
+
"<span style=\"font-size: #{element.size}px\">#{content}</span>"
|
|
19
|
+
else
|
|
20
|
+
content
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Markbridge
|
|
4
|
+
module Renderers
|
|
5
|
+
module Discourse
|
|
6
|
+
module Tags
|
|
7
|
+
# Tag for rendering spoilers
|
|
8
|
+
# Renders to Discourse BBCode spoiler format
|
|
9
|
+
class SpoilerTag < Tag
|
|
10
|
+
def render(element, interface)
|
|
11
|
+
child_context = interface.with_parent(element)
|
|
12
|
+
content = interface.render_children(element, context: child_context)
|
|
13
|
+
|
|
14
|
+
if element.title
|
|
15
|
+
"[spoiler=#{element.title}]#{content}[/spoiler]"
|
|
16
|
+
else
|
|
17
|
+
"[spoiler]#{content}[/spoiler]"
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
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 strikethrough text
|
|
8
|
+
class StrikethroughTag < Tag
|
|
9
|
+
def render(element, interface)
|
|
10
|
+
child_context = interface.with_parent(element)
|
|
11
|
+
content = interface.render_children(element, context: child_context)
|
|
12
|
+
interface.wrap_inline(content, "~~")
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Markbridge
|
|
4
|
+
module Renderers
|
|
5
|
+
module Discourse
|
|
6
|
+
module Tags
|
|
7
|
+
# Tag for rendering subscript text
|
|
8
|
+
# Renders to HTML <sub> tag
|
|
9
|
+
class SubscriptTag < Tag
|
|
10
|
+
def render(element, interface)
|
|
11
|
+
child_context = interface.with_parent(element)
|
|
12
|
+
content = interface.render_children(element, context: child_context)
|
|
13
|
+
"<sub>#{content}</sub>"
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Markbridge
|
|
4
|
+
module Renderers
|
|
5
|
+
module Discourse
|
|
6
|
+
module Tags
|
|
7
|
+
# Tag for rendering superscript text
|
|
8
|
+
# Renders to HTML <sup> tag
|
|
9
|
+
class SuperscriptTag < Tag
|
|
10
|
+
def render(element, interface)
|
|
11
|
+
child_context = interface.with_parent(element)
|
|
12
|
+
content = interface.render_children(element, context: child_context)
|
|
13
|
+
"<sup>#{content}</sup>"
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Markbridge
|
|
4
|
+
module Renderers
|
|
5
|
+
module Discourse
|
|
6
|
+
module Tags
|
|
7
|
+
# Tag for rendering underline text
|
|
8
|
+
class UnderlineTag < Tag
|
|
9
|
+
def render(element, interface)
|
|
10
|
+
child_context = interface.with_parent(element)
|
|
11
|
+
content = interface.render_children(element, context: child_context)
|
|
12
|
+
# Discourse uses HTML for underline
|
|
13
|
+
"<u>#{content}</u>"
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Markbridge
|
|
4
|
+
module Renderers
|
|
5
|
+
module Discourse
|
|
6
|
+
module Tags
|
|
7
|
+
# Placeholder tag for rendering Discourse uploads.
|
|
8
|
+
#
|
|
9
|
+
# This is a STUB implementation that outputs the original raw Markdown.
|
|
10
|
+
# Applications using Markbridge should provide their own custom tag
|
|
11
|
+
# to render uploads as placeholders or resolve upload:// URLs.
|
|
12
|
+
#
|
|
13
|
+
# @example Custom renderer with placeholder
|
|
14
|
+
# class MyUploadTag < Markbridge::Renderers::Discourse::Tags::UploadTag
|
|
15
|
+
# def render(element, interface)
|
|
16
|
+
# "<<UPLOAD:#{element.sha1}>>"
|
|
17
|
+
# end
|
|
18
|
+
# end
|
|
19
|
+
#
|
|
20
|
+
# @example Custom renderer that resolves URLs
|
|
21
|
+
# class MyUploadTag < Markbridge::Renderers::Discourse::Tags::UploadTag
|
|
22
|
+
# def render(element, interface)
|
|
23
|
+
# url = resolve_upload(element.sha1)
|
|
24
|
+
# if element.type == :image
|
|
25
|
+
# ""
|
|
26
|
+
# else
|
|
27
|
+
# "[#{element.filename}](#{url})"
|
|
28
|
+
# end
|
|
29
|
+
# end
|
|
30
|
+
# end
|
|
31
|
+
class UploadTag < Tag
|
|
32
|
+
def render(element, interface)
|
|
33
|
+
# Return raw Markdown if available, otherwise reconstruct
|
|
34
|
+
return element.raw if element.raw
|
|
35
|
+
|
|
36
|
+
build_upload_markdown(element)
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
private
|
|
40
|
+
|
|
41
|
+
def build_upload_markdown(element)
|
|
42
|
+
if element.type == :image
|
|
43
|
+
build_image_markdown(element)
|
|
44
|
+
else
|
|
45
|
+
build_attachment_markdown(element)
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def build_image_markdown(element)
|
|
50
|
+
alt = build_alt(element)
|
|
51
|
+
url = build_upload_url(element)
|
|
52
|
+
|
|
53
|
+
""
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def build_attachment_markdown(element)
|
|
57
|
+
filename = element.filename || "attachment"
|
|
58
|
+
url = build_upload_url(element)
|
|
59
|
+
size_part = element.size ? " (#{element.size})" : ""
|
|
60
|
+
|
|
61
|
+
"[#{filename}|attachment](#{url})#{size_part}"
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def build_alt(element)
|
|
65
|
+
parts = []
|
|
66
|
+
parts << element.alt if element.alt
|
|
67
|
+
parts << element.dimensions if element.dimensions
|
|
68
|
+
|
|
69
|
+
parts.join("|")
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def build_upload_url(element)
|
|
73
|
+
filename = element.filename || element.sha1
|
|
74
|
+
"upload://#{filename}"
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
end
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Markbridge
|
|
4
|
+
module Renderers
|
|
5
|
+
module Discourse
|
|
6
|
+
module Tags
|
|
7
|
+
# Tag for rendering URLs
|
|
8
|
+
class UrlTag < Tag
|
|
9
|
+
def render(element, interface)
|
|
10
|
+
child_context = interface.with_parent(element)
|
|
11
|
+
text = interface.render_children(element, context: child_context)
|
|
12
|
+
href = element.href
|
|
13
|
+
|
|
14
|
+
if href&.match?(/^(https?|ftps?|mailto):/i)
|
|
15
|
+
"[#{text}](#{href})"
|
|
16
|
+
else
|
|
17
|
+
text
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "discourse/tag"
|
|
4
|
+
require_relative "discourse/tag_library"
|
|
5
|
+
require_relative "discourse/render_context"
|
|
6
|
+
require_relative "discourse/rendering_interface"
|
|
7
|
+
require_relative "discourse/markdown_escaper"
|
|
8
|
+
|
|
9
|
+
# Builders
|
|
10
|
+
require_relative "discourse/builders/list_item_builder"
|
|
11
|
+
|
|
12
|
+
# Tags
|
|
13
|
+
require_relative "discourse/tags/align_tag"
|
|
14
|
+
require_relative "discourse/tags/attachment_tag"
|
|
15
|
+
require_relative "discourse/tags/bold_tag"
|
|
16
|
+
require_relative "discourse/tags/code_tag"
|
|
17
|
+
require_relative "discourse/tags/color_tag"
|
|
18
|
+
require_relative "discourse/tags/email_tag"
|
|
19
|
+
require_relative "discourse/tags/heading_tag"
|
|
20
|
+
require_relative "discourse/tags/horizontal_rule_tag"
|
|
21
|
+
require_relative "discourse/tags/line_break_tag"
|
|
22
|
+
require_relative "discourse/tags/image_tag"
|
|
23
|
+
require_relative "discourse/tags/italic_tag"
|
|
24
|
+
require_relative "discourse/tags/list_tag"
|
|
25
|
+
require_relative "discourse/tags/list_item_tag"
|
|
26
|
+
require_relative "discourse/tags/paragraph_tag"
|
|
27
|
+
require_relative "discourse/tags/quote_tag"
|
|
28
|
+
require_relative "discourse/tags/size_tag"
|
|
29
|
+
require_relative "discourse/tags/spoiler_tag"
|
|
30
|
+
require_relative "discourse/tags/strikethrough_tag"
|
|
31
|
+
require_relative "discourse/tags/subscript_tag"
|
|
32
|
+
require_relative "discourse/tags/superscript_tag"
|
|
33
|
+
require_relative "discourse/tags/underline_tag"
|
|
34
|
+
require_relative "discourse/tags/url_tag"
|
|
35
|
+
|
|
36
|
+
# Discourse-specific tags
|
|
37
|
+
require_relative "discourse/tags/event_tag"
|
|
38
|
+
require_relative "discourse/tags/mention_tag"
|
|
39
|
+
require_relative "discourse/tags/poll_tag"
|
|
40
|
+
require_relative "discourse/tags/upload_tag"
|
|
41
|
+
|
|
42
|
+
# Renderer itself
|
|
43
|
+
require_relative "discourse/renderer"
|
|
44
|
+
|
|
45
|
+
module Markbridge
|
|
46
|
+
module Renderers
|
|
47
|
+
module Discourse
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
end
|
data/lib/markbridge.rb
ADDED
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "markbridge/version"
|
|
4
|
+
require_relative "markbridge/configuration"
|
|
5
|
+
|
|
6
|
+
require_relative "markbridge/ast"
|
|
7
|
+
require_relative "markbridge/renderers/discourse"
|
|
8
|
+
require_relative "markbridge/parsers/media_wiki"
|
|
9
|
+
require_relative "markbridge/parsers/text_formatter"
|
|
10
|
+
require_relative "markbridge/processors"
|
|
11
|
+
|
|
12
|
+
module Markbridge
|
|
13
|
+
class << self
|
|
14
|
+
# Parse BBCode to AST
|
|
15
|
+
# @param input [String] BBCode source
|
|
16
|
+
# @param handlers [HandlerRegistry, nil] custom handler registry or use default
|
|
17
|
+
# @return [AST::Document]
|
|
18
|
+
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
|
+
handlers ||= default_handlers
|
|
23
|
+
|
|
24
|
+
parser = Parsers::BBCode::Parser.new(handlers:)
|
|
25
|
+
parser.parse(input)
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
# Convert BBCode to Discourse Markdown
|
|
29
|
+
# @param input [String] BBCode source
|
|
30
|
+
# @param handlers [HandlerRegistry, nil] custom handler registry or use default
|
|
31
|
+
# @param tag_library [TagLibrary, nil] custom tag library or use default
|
|
32
|
+
# @return [String] Markdown output
|
|
33
|
+
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
|
+
ast = parse_bbcode(input, handlers:)
|
|
40
|
+
renderer = build_renderer(tag_library:)
|
|
41
|
+
|
|
42
|
+
# Clean up output
|
|
43
|
+
result = renderer.render(ast)
|
|
44
|
+
cleanup_markdown(result)
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
# Parse HTML to AST
|
|
48
|
+
# @param input [String] HTML source
|
|
49
|
+
# @param handlers [HandlerRegistry, nil] custom handler registry or use default
|
|
50
|
+
# @return [AST::Document]
|
|
51
|
+
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
|
+
handlers ||= default_html_handlers
|
|
56
|
+
|
|
57
|
+
parser = Parsers::HTML::Parser.new(handlers:)
|
|
58
|
+
parser.parse(input)
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
# Convert HTML to Discourse Markdown
|
|
62
|
+
# @param input [String] HTML source
|
|
63
|
+
# @param handlers [HandlerRegistry, nil] custom handler registry or use default
|
|
64
|
+
# @param tag_library [TagLibrary, nil] custom tag library or use default
|
|
65
|
+
# @return [String] Markdown output
|
|
66
|
+
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
|
+
ast = parse_html(input, handlers:)
|
|
73
|
+
renderer = build_renderer(tag_library:)
|
|
74
|
+
|
|
75
|
+
# Clean up output
|
|
76
|
+
result = renderer.render(ast)
|
|
77
|
+
cleanup_markdown(result)
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
# Parse s9e/TextFormatter XML to AST
|
|
81
|
+
# @param input [String] XML source in s9e/TextFormatter format
|
|
82
|
+
# @param handlers [Parsers::TextFormatter::HandlerRegistry, nil] custom handler registry or use default
|
|
83
|
+
# @return [AST::Document]
|
|
84
|
+
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
|
+
handlers ||= default_text_formatter_handlers
|
|
89
|
+
|
|
90
|
+
parser = Parsers::TextFormatter::Parser.new(handlers:)
|
|
91
|
+
parser.parse(input)
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
# Convert s9e/TextFormatter XML to Discourse Markdown
|
|
95
|
+
# @param input [String] XML source in s9e/TextFormatter format
|
|
96
|
+
# @param handlers [Parsers::TextFormatter::HandlerRegistry, nil] custom handler registry or use default
|
|
97
|
+
# @param tag_library [TagLibrary, nil] custom tag library or use default
|
|
98
|
+
# @return [String] Markdown output
|
|
99
|
+
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
|
+
ast = parse_text_formatter_xml(input, handlers:)
|
|
106
|
+
renderer = build_renderer(tag_library:)
|
|
107
|
+
|
|
108
|
+
result = renderer.render(ast)
|
|
109
|
+
cleanup_markdown(result)
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
# Parse MediaWiki wikitext to AST
|
|
113
|
+
# @param input [String] MediaWiki source
|
|
114
|
+
# @return [AST::Document]
|
|
115
|
+
def parse_mediawiki(input)
|
|
116
|
+
raise ArgumentError, "input cannot be nil" if input.nil?
|
|
117
|
+
|
|
118
|
+
input = input.to_s
|
|
119
|
+
parser = Parsers::MediaWiki::Parser.new
|
|
120
|
+
parser.parse(input)
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
# Convert MediaWiki wikitext to Discourse Markdown
|
|
124
|
+
# @param input [String] MediaWiki source
|
|
125
|
+
# @param tag_library [TagLibrary, nil] custom tag library or use default
|
|
126
|
+
# @return [String] Markdown output
|
|
127
|
+
def mediawiki_to_markdown(input, tag_library: nil)
|
|
128
|
+
raise ArgumentError, "input cannot be nil" if input.nil?
|
|
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)
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
# Get default handler registry
|
|
140
|
+
# @return [Parsers::BBCode::HandlerRegistry]
|
|
141
|
+
def default_handlers
|
|
142
|
+
@default_handlers ||= Parsers::BBCode::HandlerRegistry.default
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
# Get default HTML handler registry
|
|
146
|
+
# @return [Parsers::HTML::HandlerRegistry]
|
|
147
|
+
def default_html_handlers
|
|
148
|
+
@default_html_handlers ||= Parsers::HTML::HandlerRegistry.default
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
# Get default tag library
|
|
152
|
+
# @return [Renderers::Discourse::TagLibrary]
|
|
153
|
+
def default_tag_library
|
|
154
|
+
@default_tag_library ||= Renderers::Discourse::TagLibrary.default
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
# Get default s9e/TextFormatter handler registry
|
|
158
|
+
# @return [Parsers::TextFormatter::HandlerRegistry]
|
|
159
|
+
def default_text_formatter_handlers
|
|
160
|
+
@default_text_formatter_handlers ||= Parsers::TextFormatter::HandlerRegistry.default
|
|
161
|
+
end
|
|
162
|
+
|
|
163
|
+
# Get the global configuration
|
|
164
|
+
# @return [Configuration]
|
|
165
|
+
def configuration
|
|
166
|
+
@configuration ||= Configuration.new
|
|
167
|
+
end
|
|
168
|
+
|
|
169
|
+
# Configure Markbridge with a block
|
|
170
|
+
# @yield [Configuration]
|
|
171
|
+
def configure
|
|
172
|
+
yield configuration
|
|
173
|
+
end
|
|
174
|
+
|
|
175
|
+
# Reset defaults (useful for testing)
|
|
176
|
+
def reset_defaults!
|
|
177
|
+
@default_handlers = nil
|
|
178
|
+
@default_html_handlers = nil
|
|
179
|
+
@default_tag_library = nil
|
|
180
|
+
@default_text_formatter_handlers = nil
|
|
181
|
+
@configuration = nil
|
|
182
|
+
end
|
|
183
|
+
|
|
184
|
+
private
|
|
185
|
+
|
|
186
|
+
def build_renderer(tag_library:)
|
|
187
|
+
escaper =
|
|
188
|
+
Renderers::Discourse::MarkdownEscaper.new(
|
|
189
|
+
escape_hard_line_breaks: configuration.escape_hard_line_breaks,
|
|
190
|
+
)
|
|
191
|
+
Renderers::Discourse::Renderer.new(tag_library:, escaper:)
|
|
192
|
+
end
|
|
193
|
+
|
|
194
|
+
def cleanup_markdown(text)
|
|
195
|
+
text
|
|
196
|
+
.gsub(/\n{3,}/, "\n\n") # Max 2 consecutive newlines
|
|
197
|
+
.gsub(/^[ \t]+$/m, "") # Remove whitespace-only lines
|
|
198
|
+
.strip # Trim leading/trailing whitespace
|
|
199
|
+
end
|
|
200
|
+
end
|
|
201
|
+
end
|