markbridge 0.4.3 → 0.5.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/lib/markbridge/ast/image.rb +9 -1
- data/lib/markbridge/ast/url.rb +29 -4
- data/lib/markbridge/errors.rb +9 -0
- data/lib/markbridge/parsers/bbcode/handlers/image_handler.rb +2 -1
- data/lib/markbridge/parsers/bbcode/handlers/url_handler.rb +26 -2
- data/lib/markbridge/parsers/html/handlers/image_handler.rb +4 -1
- data/lib/markbridge/parsers/text_formatter/handlers/attachment_handler.rb +3 -2
- data/lib/markbridge/parsers/text_formatter/handlers/image_handler.rb +4 -2
- data/lib/markbridge/parsers/text_formatter/parser.rb +107 -14
- data/lib/markbridge/renderers/discourse/html_block.rb +50 -7
- data/lib/markbridge/renderers/discourse/inline/joins.rb +382 -0
- data/lib/markbridge/renderers/discourse/inline/serializer.rb +738 -0
- data/lib/markbridge/renderers/discourse/inline/tokens.rb +182 -0
- data/lib/markbridge/renderers/discourse/link_destination.rb +43 -0
- data/lib/markbridge/renderers/discourse/markdown_escaper.rb +105 -45
- data/lib/markbridge/renderers/discourse/paragraphs.rb +88 -0
- data/lib/markbridge/renderers/discourse/postprocessor.rb +183 -7
- data/lib/markbridge/renderers/discourse/render_context.rb +38 -12
- data/lib/markbridge/renderers/discourse/renderer.rb +336 -92
- data/lib/markbridge/renderers/discourse/rendering_interface.rb +123 -44
- data/lib/markbridge/renderers/discourse/tag.rb +53 -9
- data/lib/markbridge/renderers/discourse/tags/bold_tag.rb +4 -6
- data/lib/markbridge/renderers/discourse/tags/code_tag.rb +48 -10
- data/lib/markbridge/renderers/discourse/tags/color_tag.rb +73 -13
- data/lib/markbridge/renderers/discourse/tags/details_tag.rb +62 -15
- data/lib/markbridge/renderers/discourse/tags/email_tag.rb +8 -9
- data/lib/markbridge/renderers/discourse/tags/event_tag.rb +31 -9
- data/lib/markbridge/renderers/discourse/tags/heading_tag.rb +20 -1
- data/lib/markbridge/renderers/discourse/tags/horizontal_rule_tag.rb +12 -1
- data/lib/markbridge/renderers/discourse/tags/image_tag.rb +13 -27
- data/lib/markbridge/renderers/discourse/tags/italic_tag.rb +4 -6
- data/lib/markbridge/renderers/discourse/tags/line_break_tag.rb +9 -2
- data/lib/markbridge/renderers/discourse/tags/list_item_tag.rb +75 -9
- data/lib/markbridge/renderers/discourse/tags/list_tag.rb +29 -7
- data/lib/markbridge/renderers/discourse/tags/mention_tag.rb +12 -0
- data/lib/markbridge/renderers/discourse/tags/paragraph_tag.rb +13 -6
- data/lib/markbridge/renderers/discourse/tags/poll_tag.rb +52 -9
- data/lib/markbridge/renderers/discourse/tags/quote_tag.rb +26 -1
- data/lib/markbridge/renderers/discourse/tags/size_tag.rb +78 -13
- data/lib/markbridge/renderers/discourse/tags/spoiler_tag.rb +72 -14
- data/lib/markbridge/renderers/discourse/tags/strikethrough_tag.rb +5 -6
- data/lib/markbridge/renderers/discourse/tags/subscript_tag.rb +11 -5
- data/lib/markbridge/renderers/discourse/tags/superscript_tag.rb +11 -5
- data/lib/markbridge/renderers/discourse/tags/table_tag.rb +48 -28
- data/lib/markbridge/renderers/discourse/tags/underline_tag.rb +3 -10
- data/lib/markbridge/renderers/discourse/tags/upload_tag.rb +90 -43
- data/lib/markbridge/renderers/discourse/tags/url_tag.rb +41 -31
- data/lib/markbridge/renderers/discourse.rb +6 -0
- data/lib/markbridge/rspec.rb +4 -2
- data/lib/markbridge/version.rb +1 -1
- data/lib/markbridge.rb +90 -1
- metadata +7 -1
|
@@ -4,12 +4,12 @@ module Markbridge
|
|
|
4
4
|
module Renderers
|
|
5
5
|
module Discourse
|
|
6
6
|
module Tags
|
|
7
|
-
#
|
|
8
|
-
#
|
|
9
|
-
#
|
|
10
|
-
#
|
|
11
|
-
#
|
|
12
|
-
#
|
|
7
|
+
# Links. A bare link (no text of its own, or a text that is the
|
|
8
|
+
# href, or a label with nothing visible in it) is a bare URL token:
|
|
9
|
+
# the serializer decides from the neighbors whether it can stay
|
|
10
|
+
# bare, which oneboxes in Discourse, or has to be written as
|
|
11
|
+
# `[url](url)`. Any other link is a link token pair around the
|
|
12
|
+
# children.
|
|
13
13
|
class UrlTag < Tag
|
|
14
14
|
# Schemes that are safe to link. Anything else that *looks*
|
|
15
15
|
# like a scheme (javascript:, data:, vbscript:, …) is dropped;
|
|
@@ -17,41 +17,51 @@ module Markbridge
|
|
|
17
17
|
# URLs) pass through — common in forum exports and harmless.
|
|
18
18
|
ALLOWED_SCHEMES = /\A(?:https?|ftps?|mailto):/i
|
|
19
19
|
SCHEME_LIKE = /\A[a-z][a-z0-9+.-]*:/i
|
|
20
|
-
|
|
20
|
+
# The whitespace in front of the text and after it.
|
|
21
|
+
EDGES = /\A([[:space:]]*).*?([[:space:]]*)\z/m
|
|
22
|
+
private_constant :ALLOWED_SCHEMES, :SCHEME_LIKE, :EDGES
|
|
21
23
|
|
|
22
24
|
def render(element, interface)
|
|
23
|
-
child_context = interface.with_parent(element)
|
|
24
|
-
text = interface.render_children(element, context: child_context)
|
|
25
25
|
href = element.href
|
|
26
|
+
linkable = linkable?(href)
|
|
27
|
+
# Url#bare? judges the AST, so label escaping cannot confuse it.
|
|
28
|
+
return bare_tokens(element, href) if linkable && element.bare?
|
|
26
29
|
|
|
27
|
-
|
|
30
|
+
tokens = interface.render_tokens(element, context: interface.with_parent(element))
|
|
31
|
+
return tokens unless linkable
|
|
32
|
+
# A label that renders to nothing (an empty formatting child)
|
|
33
|
+
# or to whitespace only is no label either.
|
|
34
|
+
return [Inline::Tokens.bare_url(href)] unless Inline::Tokens.visible?(tokens)
|
|
28
35
|
|
|
29
|
-
|
|
30
|
-
%(<a href="#{HtmlEscaper.escape(href)}">#{text}</a>)
|
|
31
|
-
elsif element.bare? || blank?(text)
|
|
32
|
-
# Url#bare? judges the AST (so label escaping can't confuse
|
|
33
|
-
# it); the rendered-text check additionally catches labels
|
|
34
|
-
# that render to nothing (e.g. an empty formatting child)
|
|
35
|
-
# or to whitespace only, which wrap_inline would leave
|
|
36
|
-
# unlinked.
|
|
37
|
-
href
|
|
38
|
-
else
|
|
39
|
-
interface.wrap_inline(text, "[", "](#{markdown_destination(href)})")
|
|
40
|
-
end
|
|
36
|
+
tokens.unshift(Inline::Tokens.link_open(href)) << Inline::Tokens.link_close
|
|
41
37
|
end
|
|
42
38
|
|
|
43
39
|
private
|
|
44
40
|
|
|
45
|
-
#
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
41
|
+
# The bare URL in place of the text. Whitespace at the edges of
|
|
42
|
+
# the text and the line breaks in front of it and after it stay
|
|
43
|
+
# outside the URL, so `[url] https://e.com [/url]` keeps its
|
|
44
|
+
# spaces. A bare link has only Text and LineBreak children (see
|
|
45
|
+
# Url#bare?); the line breaks are written as hard break tokens
|
|
46
|
+
# here, the serializer picks their form.
|
|
47
|
+
def bare_tokens(element, href)
|
|
48
|
+
bare = Inline::Tokens.bare_url(href)
|
|
49
|
+
return [bare] if element.children.empty?
|
|
50
|
+
|
|
51
|
+
element
|
|
52
|
+
.children
|
|
53
|
+
.each_with_object([]) do |child, tokens|
|
|
54
|
+
next tokens << Inline::Tokens.hard_break if child.instance_of?(AST::LineBreak)
|
|
55
|
+
|
|
56
|
+
label_text = child.text
|
|
57
|
+
# Most bare links have no whitespace to keep.
|
|
58
|
+
next tokens << bare if label_text == href
|
|
49
59
|
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
60
|
+
lead, trail = label_text.match(EDGES).captures
|
|
61
|
+
tokens << Inline::Tokens.text(lead) unless lead.empty?
|
|
62
|
+
tokens << bare
|
|
63
|
+
tokens << Inline::Tokens.text(trail) unless trail.empty?
|
|
64
|
+
end
|
|
55
65
|
end
|
|
56
66
|
|
|
57
67
|
def linkable?(href)
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require_relative "../errors"
|
|
3
4
|
require_relative "discourse/tag"
|
|
4
5
|
require_relative "discourse/tag_library"
|
|
5
6
|
require_relative "discourse/render_context"
|
|
@@ -8,6 +9,11 @@ require_relative "discourse/markdown_escaper"
|
|
|
8
9
|
require_relative "discourse/identity_escaper"
|
|
9
10
|
require_relative "discourse/html_escaper"
|
|
10
11
|
require_relative "discourse/html_block"
|
|
12
|
+
require_relative "discourse/paragraphs"
|
|
13
|
+
require_relative "discourse/link_destination"
|
|
14
|
+
require_relative "discourse/inline/tokens"
|
|
15
|
+
require_relative "discourse/inline/joins"
|
|
16
|
+
require_relative "discourse/inline/serializer"
|
|
11
17
|
require_relative "discourse/postprocessor"
|
|
12
18
|
|
|
13
19
|
# Builders
|
data/lib/markbridge/rspec.rb
CHANGED
|
@@ -38,9 +38,11 @@ RSpec.shared_examples "an html_mode safe tag" do
|
|
|
38
38
|
|
|
39
39
|
it "renders html_mode output that is safe inside an HTML block" do
|
|
40
40
|
output = tag.render(element, markbridge_html_mode_interface)
|
|
41
|
+
output = markbridge_renderer.serialize(output, markbridge_html_mode_interface.context)
|
|
41
42
|
|
|
42
43
|
expect(Markbridge::Renderers::Discourse::HtmlBlock.safe?(output)).to be(true),
|
|
43
|
-
"Expected #{tag.class} to render raw HTML or a
|
|
44
|
-
"Markdown island in html_mode,
|
|
44
|
+
"Expected #{tag.class} to render raw HTML without a blank line, or a " \
|
|
45
|
+
"Markdown island in a <div> (interface.markdown_island) in html_mode, " \
|
|
46
|
+
"got: #{output.inspect}"
|
|
45
47
|
end
|
|
46
48
|
end
|
data/lib/markbridge/version.rb
CHANGED
data/lib/markbridge.rb
CHANGED
|
@@ -9,6 +9,26 @@ require_relative "markbridge/normalizer"
|
|
|
9
9
|
require_relative "markbridge/renderers/discourse"
|
|
10
10
|
|
|
11
11
|
module Markbridge
|
|
12
|
+
# The tags of the plugins bundled with Discourse, by feature. The
|
|
13
|
+
# discourse-bbcode plugin (:bbcode) has its own registration, its tags
|
|
14
|
+
# take the mappings.
|
|
15
|
+
FEATURE_TAGS = {
|
|
16
|
+
details: {
|
|
17
|
+
AST::Details => Renderers::Discourse::Tags::DetailsTag,
|
|
18
|
+
},
|
|
19
|
+
spoiler: {
|
|
20
|
+
AST::Spoiler => Renderers::Discourse::Tags::SpoilerTag,
|
|
21
|
+
},
|
|
22
|
+
poll: {
|
|
23
|
+
AST::Poll => Renderers::Discourse::Tags::PollTag,
|
|
24
|
+
},
|
|
25
|
+
post_event: {
|
|
26
|
+
AST::Event => Renderers::Discourse::Tags::EventTag,
|
|
27
|
+
},
|
|
28
|
+
}.freeze
|
|
29
|
+
FEATURES = (FEATURE_TAGS.keys + [:bbcode]).freeze
|
|
30
|
+
private_constant :FEATURE_TAGS, :FEATURES
|
|
31
|
+
|
|
12
32
|
class << self
|
|
13
33
|
# Parse BBCode to AST.
|
|
14
34
|
#
|
|
@@ -297,7 +317,32 @@ module Markbridge
|
|
|
297
317
|
# {Renderers::Discourse::Postprocessor} when no explicit
|
|
298
318
|
# +postprocessor:+ is given. Strips NBSP and zero-width format
|
|
299
319
|
# characters from the end of each line.
|
|
320
|
+
# @param features [Hash{Symbol => Boolean}, nil] the plugins of the
|
|
321
|
+
# target site. +:details+, +:spoiler+, +:poll+ and +:post_event+ are
|
|
322
|
+
# bundled with Discourse and on by default. With one of them off,
|
|
323
|
+
# the tag writes a plain form instead of the plugin's BBCode: the
|
|
324
|
+
# title in bold and the content for a details block, the content
|
|
325
|
+
# for a spoiler, the options as a list for a poll, a paragraph with
|
|
326
|
+
# the name and the dates for an event. +:bbcode+ is the
|
|
327
|
+
# discourse-bbcode plugin, which needs an install, and starts
|
|
328
|
+
# undecided: a color or size in a post raises
|
|
329
|
+
# {UndecidedFeatureError} until it is +false+ (the text only) or
|
|
330
|
+
# +true+ (the BBCode, with +sizes:+). Mappings in +tags:+ still win.
|
|
331
|
+
# @param sizes [Hash, #call, nil] with +bbcode: true+, the mapping
|
|
332
|
+
# from the size values of the source forum to the percent value
|
|
333
|
+
# the plugin reads: a whole number from 1 to 999, or nil to drop a
|
|
334
|
+
# size. A Hash is read with +fetch+, so an unknown value raises
|
|
335
|
+
# KeyError. Required with +bbcode: true+, because every forum has
|
|
336
|
+
# its own scale.
|
|
337
|
+
# @param colors [Hash, #call, nil] with +bbcode: true+, a mapping from
|
|
338
|
+
# the color values of the source forum to +#rgb+, +#rrggbb+ or a
|
|
339
|
+
# name, or nil to drop a color. Without it the values are written
|
|
340
|
+
# as they are, and a value the plugin does not accept raises
|
|
341
|
+
# ArgumentError.
|
|
300
342
|
# @return [Renderers::Discourse::Renderer]
|
|
343
|
+
# @raise [ArgumentError] for a feature that does not exist, for
|
|
344
|
+
# +bbcode: true+ without +sizes:+, and for a mapping without
|
|
345
|
+
# +bbcode: true+
|
|
301
346
|
def discourse_renderer(
|
|
302
347
|
tags: nil,
|
|
303
348
|
tag_library: nil,
|
|
@@ -307,7 +352,10 @@ module Markbridge
|
|
|
307
352
|
escape_hard_line_breaks: false,
|
|
308
353
|
allow: nil,
|
|
309
354
|
postprocessor: nil,
|
|
310
|
-
strip_trailing_invisibles: false
|
|
355
|
+
strip_trailing_invisibles: false,
|
|
356
|
+
features: nil,
|
|
357
|
+
sizes: nil,
|
|
358
|
+
colors: nil
|
|
311
359
|
)
|
|
312
360
|
# Dup the caller's library before mutating so successive
|
|
313
361
|
# +discourse_renderer+ calls against the same +tag_library:+ don't
|
|
@@ -315,6 +363,7 @@ module Markbridge
|
|
|
315
363
|
# a fresh instance, so the dup is only needed in the explicit
|
|
316
364
|
# +tag_library:+ branch.
|
|
317
365
|
library = tag_library ? tag_library.dup : Renderers::Discourse::TagLibrary.default
|
|
366
|
+
register_features(library, features || {}, sizes:, colors:)
|
|
318
367
|
library.merge!(tags) if tags
|
|
319
368
|
Array(unregister).each { |klass| library.unregister(klass) }
|
|
320
369
|
|
|
@@ -326,6 +375,46 @@ module Markbridge
|
|
|
326
375
|
|
|
327
376
|
private
|
|
328
377
|
|
|
378
|
+
# Registers the tags of the features named, so the other tags of the
|
|
379
|
+
# library stay as they are.
|
|
380
|
+
def register_features(library, features, sizes:, colors:)
|
|
381
|
+
features.each do |feature, enabled|
|
|
382
|
+
unless FEATURES.include?(feature)
|
|
383
|
+
raise ArgumentError,
|
|
384
|
+
"unknown feature #{feature.inspect}, the features are #{FEATURES.join(", ")}"
|
|
385
|
+
end
|
|
386
|
+
unless [true, false].include?(enabled)
|
|
387
|
+
raise ArgumentError, "feature #{feature} must be true or false, got #{enabled.inspect}"
|
|
388
|
+
end
|
|
389
|
+
next if feature == :bbcode
|
|
390
|
+
|
|
391
|
+
FEATURE_TAGS
|
|
392
|
+
.fetch(feature)
|
|
393
|
+
.each { |klass, tag_class| library.register(klass, tag_class.new(bbcode: enabled)) }
|
|
394
|
+
end
|
|
395
|
+
|
|
396
|
+
register_bbcode_feature(library, features[:bbcode], sizes:, colors:)
|
|
397
|
+
end
|
|
398
|
+
|
|
399
|
+
def register_bbcode_feature(library, bbcode, sizes:, colors:)
|
|
400
|
+
if bbcode && sizes.nil?
|
|
401
|
+
raise ArgumentError,
|
|
402
|
+
"features: { bbcode: true } needs sizes:, the mapping from the size values of " \
|
|
403
|
+
"the source forum to percent"
|
|
404
|
+
end
|
|
405
|
+
{ sizes:, colors: }.each do |name, mapping|
|
|
406
|
+
next if mapping.nil?
|
|
407
|
+
unless mapping.respond_to?(:call) || mapping.respond_to?(:fetch)
|
|
408
|
+
raise ArgumentError, "#{name}: must be a Hash or respond to call"
|
|
409
|
+
end
|
|
410
|
+
raise ArgumentError, "#{name}: needs features: { bbcode: true }" unless bbcode
|
|
411
|
+
end
|
|
412
|
+
return if bbcode.nil?
|
|
413
|
+
|
|
414
|
+
library.register(AST::Color, Renderers::Discourse::Tags::ColorTag.new(bbcode:, colors:))
|
|
415
|
+
library.register(AST::Size, Renderers::Discourse::Tags::SizeTag.new(bbcode:, sizes:))
|
|
416
|
+
end
|
|
417
|
+
|
|
329
418
|
def bbcode_diagnostics(parser)
|
|
330
419
|
{
|
|
331
420
|
auto_closed_tags_count: parser.auto_closed_tags_count,
|
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.
|
|
4
|
+
version: 0.5.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Discourse Team
|
|
@@ -55,6 +55,7 @@ files:
|
|
|
55
55
|
- lib/markbridge/ast/url.rb
|
|
56
56
|
- lib/markbridge/bbcode.rb
|
|
57
57
|
- lib/markbridge/conversion.rb
|
|
58
|
+
- lib/markbridge/errors.rb
|
|
58
59
|
- lib/markbridge/gem_loader.rb
|
|
59
60
|
- lib/markbridge/html.rb
|
|
60
61
|
- lib/markbridge/mediawiki.rb
|
|
@@ -141,7 +142,12 @@ files:
|
|
|
141
142
|
- lib/markbridge/renderers/discourse/html_block.rb
|
|
142
143
|
- lib/markbridge/renderers/discourse/html_escaper.rb
|
|
143
144
|
- lib/markbridge/renderers/discourse/identity_escaper.rb
|
|
145
|
+
- lib/markbridge/renderers/discourse/inline/joins.rb
|
|
146
|
+
- lib/markbridge/renderers/discourse/inline/serializer.rb
|
|
147
|
+
- lib/markbridge/renderers/discourse/inline/tokens.rb
|
|
148
|
+
- lib/markbridge/renderers/discourse/link_destination.rb
|
|
144
149
|
- lib/markbridge/renderers/discourse/markdown_escaper.rb
|
|
150
|
+
- lib/markbridge/renderers/discourse/paragraphs.rb
|
|
145
151
|
- lib/markbridge/renderers/discourse/postprocessor.rb
|
|
146
152
|
- lib/markbridge/renderers/discourse/render_context.rb
|
|
147
153
|
- lib/markbridge/renderers/discourse/renderer.rb
|