gollum-lib 5.0.a.4 → 5.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/Gemfile +2 -1
- data/README.md +12 -7
- data/Rakefile +5 -5
- data/adapter_dependencies.rb +7 -0
- data/gemspec.rb +18 -10
- data/gollum-lib.gemspec +2 -6
- data/gollum-lib_java.gemspec +2 -2
- data/lib/gollum-lib.rb +9 -9
- data/lib/gollum-lib/blob_entry.rb +2 -8
- data/lib/gollum-lib/committer.rb +22 -60
- data/lib/gollum-lib/file.rb +105 -82
- data/lib/gollum-lib/file_view.rb +8 -4
- data/lib/gollum-lib/filter.rb +12 -0
- data/lib/gollum-lib/filter/code.rb +9 -13
- data/lib/gollum-lib/filter/critic_markup.rb +97 -0
- data/lib/gollum-lib/filter/emoji.rb +10 -8
- data/lib/gollum-lib/filter/macro.rb +5 -2
- data/lib/gollum-lib/filter/plantuml.rb +1 -1
- data/lib/gollum-lib/filter/remote_code.rb +3 -2
- data/lib/gollum-lib/filter/render.rb +25 -2
- data/lib/gollum-lib/filter/sanitize.rb +1 -8
- data/lib/gollum-lib/filter/tags.rb +57 -47
- data/lib/gollum-lib/filter/toc.rb +17 -21
- data/lib/gollum-lib/filter/yaml.rb +1 -1
- data/lib/gollum-lib/git_access.rb +0 -25
- data/lib/gollum-lib/helpers.rb +13 -3
- data/lib/gollum-lib/macro/audio.rb +9 -0
- data/lib/gollum-lib/macro/global_toc.rb +2 -1
- data/lib/gollum-lib/macro/navigation.rb +8 -6
- data/lib/gollum-lib/macro/note.rb +19 -0
- data/lib/gollum-lib/macro/octicon.rb +12 -0
- data/lib/gollum-lib/macro/warn.rb +11 -0
- data/lib/gollum-lib/markup.rb +17 -32
- data/lib/gollum-lib/markups.rb +11 -7
- data/lib/gollum-lib/page.rb +79 -165
- data/lib/gollum-lib/pagination.rb +7 -6
- data/lib/gollum-lib/redirects.rb +38 -0
- data/lib/gollum-lib/sanitization.rb +32 -357
- data/lib/gollum-lib/version.rb +1 -1
- data/lib/gollum-lib/wiki.rb +216 -404
- metadata +73 -28
- data/ROADMAP +0 -6
data/lib/gollum-lib/file_view.rb
CHANGED
@@ -10,12 +10,13 @@ module Gollum
|
|
10
10
|
# set pages to wiki.pages + wiki.files and show_all to true
|
11
11
|
def initialize(pages, options = {})
|
12
12
|
@pages = pages
|
13
|
+
@wiki = @pages.first ? @pages.first.wiki : nil
|
13
14
|
@show_all = options[:show_all] || false
|
14
15
|
@checked = options[:collapse_tree] ? '' : "checked"
|
15
16
|
end
|
16
17
|
|
17
18
|
def enclose_tree(string)
|
18
|
-
%Q(<ol class="tree">\n) + string + %Q(</ol>)
|
19
|
+
sanitize_html(%Q(<ol class="tree">\n) + string + %Q(</ol>))
|
19
20
|
end
|
20
21
|
|
21
22
|
def new_page(page)
|
@@ -96,7 +97,7 @@ module Gollum
|
|
96
97
|
</li>
|
97
98
|
HTML
|
98
99
|
|
99
|
-
return enclose_tree
|
100
|
+
return enclose_tree(html)
|
100
101
|
end
|
101
102
|
|
102
103
|
sorted_folders = []
|
@@ -153,8 +154,11 @@ module Gollum
|
|
153
154
|
changed = false
|
154
155
|
end
|
155
156
|
|
156
|
-
|
157
|
-
enclose_tree html
|
157
|
+
enclose_tree(html)
|
158
158
|
end # end render_files
|
159
|
+
|
160
|
+
def sanitize_html(data)
|
161
|
+
@wiki ? @wiki.sanitizer.clean(data) : data
|
162
|
+
end
|
159
163
|
end # end FileView class
|
160
164
|
end # end Gollum module
|
data/lib/gollum-lib/filter.rb
CHANGED
@@ -47,14 +47,21 @@ module Gollum
|
|
47
47
|
class Filter
|
48
48
|
include Gollum::Helpers
|
49
49
|
|
50
|
+
PLACEHOLDER_PATTERN = /%(\S+)%.+=\1=/
|
51
|
+
|
50
52
|
# Setup the object. Sets `@markup` to be the instance of Gollum::Markup that
|
51
53
|
# is running this filter chain, and sets `@map` to be an empty hash (for use
|
52
54
|
# in your extract/process operations).
|
55
|
+
|
53
56
|
def initialize(markup)
|
54
57
|
@markup = markup
|
55
58
|
@map = {}
|
59
|
+
@open_pattern = "%#{self.class.to_s.split('::').last}%"
|
60
|
+
@close_pattern = "=#{self.class.to_s.split('::').last}="
|
56
61
|
end
|
57
62
|
|
63
|
+
attr_reader :open_pattern, :close_pattern
|
64
|
+
|
58
65
|
def extract(data)
|
59
66
|
raise RuntimeError,
|
60
67
|
"#{self.class} has not implemented ##extract!"
|
@@ -66,6 +73,11 @@ module Gollum
|
|
66
73
|
end
|
67
74
|
|
68
75
|
protected
|
76
|
+
|
77
|
+
def sanitize(data)
|
78
|
+
@markup.wiki.sanitizer.clean(data, @markup.historical)
|
79
|
+
end
|
80
|
+
|
69
81
|
# Render a (presumably) non-fatal error as HTML
|
70
82
|
#
|
71
83
|
def html_error(message)
|
@@ -14,31 +14,27 @@ class Gollum::Filter::Code < Gollum::Filter
|
|
14
14
|
org_headers = %r{([ \t]*#\+HEADER[S]?:[^\r\n]*\n)*}
|
15
15
|
org_name = %r{([ \t]*#\+NAME:[^\r\n]*\n)?}
|
16
16
|
org_lang = %r{[ ]*([^\n \r]*)[ ]*[^\r\n]*}
|
17
|
-
org_begin = %r{[ \t]
|
18
|
-
org_end = %r{\n[ \t]*#\+END_SRC[ \t]*}
|
17
|
+
org_begin = %r{([ \t]*)#\+BEGIN_SRC#{org_lang}\r?\n}
|
18
|
+
org_end = %r{\r?\n[ \t]*#\+END_SRC[ \t\r]*}
|
19
19
|
data.gsub!(/^#{org_headers}#{org_name}#{org_begin}(.+?)#{org_end}$/mi) do
|
20
|
-
cache_codeblock(Regexp.last_match[
|
20
|
+
"#{Regexp.last_match[3]}#{cache_codeblock(Regexp.last_match[4], Regexp.last_match[5])}"
|
21
21
|
end
|
22
22
|
when :markdown
|
23
|
-
data.gsub!(/^([
|
23
|
+
data.gsub!(/^([ ]{0,3})(~~~+) ?([^\r\n]+)?\r?\n(.+?)\r?\n[ ]{0,3}(~~~+)[ \t\r]*$/m) do
|
24
24
|
m_indent = Regexp.last_match[1]
|
25
25
|
m_start = Regexp.last_match[2] # ~~~
|
26
26
|
m_lang = Regexp.last_match[3]
|
27
27
|
m_code = Regexp.last_match[4]
|
28
28
|
m_end = Regexp.last_match[5] # ~~~
|
29
|
-
#
|
30
|
-
next '' if
|
31
|
-
lang = m_lang ? m_lang.strip : nil
|
32
|
-
if lang
|
33
|
-
lang = lang.match(/\.([^}\s]+)/)
|
34
|
-
lang = lang[1] unless lang.nil?
|
35
|
-
end
|
29
|
+
# The closing code fence must be at least as long as the opening fence
|
30
|
+
next '' if m_end.length < m_start.length
|
31
|
+
lang = m_lang ? m_lang.strip.split.first : nil
|
36
32
|
"#{m_indent}#{cache_codeblock(lang, m_code, m_indent)}"
|
37
33
|
end
|
38
34
|
end
|
39
35
|
|
40
36
|
|
41
|
-
data.gsub!(/^([
|
37
|
+
data.gsub!(/^([ ]{0,3})``` ?([^\r\n]+)?\r?\n(.+?)\r?\n[ ]{0,3}```[ \t]*\r?$/m) do
|
42
38
|
"#{Regexp.last_match[1]}#{cache_codeblock(Regexp.last_match[2].to_s.strip, Regexp.last_match[3], Regexp.last_match[1])}" # print the SHA1 ID with the proper indentation
|
43
39
|
end
|
44
40
|
data
|
@@ -133,7 +129,7 @@ class Gollum::Filter::Code < Gollum::Filter
|
|
133
129
|
|
134
130
|
def cache_codeblock(language, code, indent = "")
|
135
131
|
language = language.to_s.empty? ? nil : language
|
136
|
-
id = Digest::SHA1.hexdigest("#{language}.#{code}")
|
132
|
+
id = "#{open_pattern}#{Digest::SHA1.hexdigest("#{language}.#{code}")}#{close_pattern}"
|
137
133
|
cached = @markup.check_cache(:code, id)
|
138
134
|
@map[id] = cached ?
|
139
135
|
{ :output => cached } :
|
@@ -0,0 +1,97 @@
|
|
1
|
+
# ~*~ encoding: utf-8 ~*~
|
2
|
+
|
3
|
+
# CriticMarkup
|
4
|
+
#
|
5
|
+
# Render CriticMarkup
|
6
|
+
|
7
|
+
class Gollum::Filter::CriticMarkup < Gollum::Filter
|
8
|
+
|
9
|
+
# Patterns inspired by https://github.com/DivineDominion/criticmarkup.tmbundle/blob/master/Syntaxes/criticmarkup.tmLanguage
|
10
|
+
# All patterns use multiline matching (m flag)
|
11
|
+
# Logic inspired by https://github.com/CriticMarkup/CriticMarkup-toolkit/blob/master/CLI/criticParser_CLI.py
|
12
|
+
|
13
|
+
ADDITION_PATTERN = %r|{\+\+(?<content>.*?)\+\+[ \t]*(\[(.*?)\])?[ \t]*\}|m
|
14
|
+
DELETION_PATTERN = %r|{--(?<content>.*?)--[ \t]*(\[(.*?)\])?[ \t]*\}|m
|
15
|
+
SUBSTITUTION_PATTERN = %r|{~~(?<oldcontent>.*?)~>(?<newcontent>.*?)~~}|m
|
16
|
+
HIGHLIGHT_PATTERN = %r|{\=\=(?<content>.*?)[ \t]*(\[(.*?)\])?[ \t]*\=\=\}{>>(?<comment>.*?)<<}|m
|
17
|
+
COMMENT_PATTERN = %r|{>>(?<content>.*?)<<}|m
|
18
|
+
|
19
|
+
def extract(data)
|
20
|
+
data.gsub! ADDITION_PATTERN do
|
21
|
+
content = $~[:content]
|
22
|
+
placeholder = generate_placeholder("#{content}#{@map.size}")
|
23
|
+
# Is there a new paragraph followed by new text
|
24
|
+
if content.start_with?("\n\n") && content != "\n\n"
|
25
|
+
html = "\n\n<ins class='critic break'> </ins>\n\n<ins>#{content.gsub('\n', ' ')}</ins>"
|
26
|
+
# Is the addition just a single new paragraph
|
27
|
+
elsif content == "\n\n"
|
28
|
+
html = "\n\n<ins class='critic break'> </ins>\n\n"
|
29
|
+
# Is it added text followed by a new paragraph?
|
30
|
+
elsif content.end_with?("\n\n") && content != "\n\n"
|
31
|
+
html = "<ins>#{content.gsub('\n', ' ')}</ins>\n\n<ins class='critic break'> </ins>\n\n"
|
32
|
+
else
|
33
|
+
html = "<ins>#{content.gsub('\n', ' ')}</ins>"
|
34
|
+
end
|
35
|
+
@map[placeholder] = html
|
36
|
+
placeholder
|
37
|
+
end
|
38
|
+
|
39
|
+
data.gsub! DELETION_PATTERN do
|
40
|
+
content = $~[:content]
|
41
|
+
placeholder = generate_placeholder("#{content}#{@map.size}")
|
42
|
+
if content == "\n\n"
|
43
|
+
html = "<del> </del>"
|
44
|
+
else
|
45
|
+
html = "<del>#{content.gsub('\n\n', ' ')}</del>"
|
46
|
+
end
|
47
|
+
@map[placeholder] = html
|
48
|
+
placeholder
|
49
|
+
end
|
50
|
+
|
51
|
+
data.gsub! SUBSTITUTION_PATTERN do
|
52
|
+
oldcontent = $~[:oldcontent]
|
53
|
+
newcontent = $~[:newcontent]
|
54
|
+
placeholder = generate_placeholder("#{oldcontent}#{newcontent}#{@map.size}")
|
55
|
+
html = "<del>#{oldcontent}</del><ins>#{newcontent}</ins>"
|
56
|
+
@map[placeholder] = html
|
57
|
+
placeholder
|
58
|
+
end
|
59
|
+
|
60
|
+
data.gsub! HIGHLIGHT_PATTERN do
|
61
|
+
content = $~[:content]
|
62
|
+
comment = $~[:comment]
|
63
|
+
placeholder = generate_placeholder("#{content}#{@map.size}")
|
64
|
+
html = "<mark>#{content}</mark><span class='critic comment'>#{comment}</span>"
|
65
|
+
@map[placeholder] = html
|
66
|
+
placeholder
|
67
|
+
end
|
68
|
+
|
69
|
+
data.gsub! COMMENT_PATTERN do
|
70
|
+
content = $~[:content]
|
71
|
+
placeholder = generate_placeholder("#{content}#{@map.size}")
|
72
|
+
html = "<span class='critic comment'>#{content}</span>"
|
73
|
+
@map[placeholder] = html
|
74
|
+
placeholder
|
75
|
+
end
|
76
|
+
|
77
|
+
data
|
78
|
+
end
|
79
|
+
|
80
|
+
def process(data)
|
81
|
+
data.gsub! process_pattern do
|
82
|
+
@map[$~[:placeholder]]
|
83
|
+
end
|
84
|
+
data
|
85
|
+
end
|
86
|
+
|
87
|
+
private
|
88
|
+
|
89
|
+
def process_pattern
|
90
|
+
/(?<placeholder>#{open_pattern}\h{40}#{close_pattern})/
|
91
|
+
end
|
92
|
+
|
93
|
+
def generate_placeholder(content)
|
94
|
+
"#{open_pattern}#{Digest::SHA1.hexdigest(content)}#{close_pattern}"
|
95
|
+
end
|
96
|
+
|
97
|
+
end
|
@@ -14,17 +14,11 @@ class Gollum::Filter::Emoji < Gollum::Filter
|
|
14
14
|
(?!\]{^2})
|
15
15
|
}ix
|
16
16
|
|
17
|
-
PROCESS_PATTERN = %r{
|
18
|
-
=EEMMOOJJII=
|
19
|
-
(?<name>[\w-]+)
|
20
|
-
=IIJJOOMMEE=
|
21
|
-
}ix
|
22
|
-
|
23
17
|
def extract(data)
|
24
18
|
data.gsub! EXTRACT_PATTERN do
|
25
19
|
case
|
26
20
|
when $~[:escape] then $&[1..-1]
|
27
|
-
when emoji_exists?($~[:name]) then "
|
21
|
+
when emoji_exists?($~[:name]) then "#{open_pattern}#{$~[:name]}#{close_pattern}"
|
28
22
|
else $&
|
29
23
|
end
|
30
24
|
end
|
@@ -32,12 +26,20 @@ class Gollum::Filter::Emoji < Gollum::Filter
|
|
32
26
|
end
|
33
27
|
|
34
28
|
def process(data)
|
35
|
-
data.gsub!
|
29
|
+
data.gsub! process_pattern, %q(<img src="/gollum/emoji/\k<name>" alt="\k<name>" class="emoji">)
|
36
30
|
data
|
37
31
|
end
|
38
32
|
|
39
33
|
private
|
40
34
|
|
35
|
+
def process_pattern
|
36
|
+
%r{
|
37
|
+
#{open_pattern}
|
38
|
+
(?<name>[\w-]+)
|
39
|
+
#{close_pattern}
|
40
|
+
}ix
|
41
|
+
end
|
42
|
+
|
41
43
|
def emoji_exists?(name)
|
42
44
|
@index ||= Gemojione::Index.new
|
43
45
|
!!@index.find_by_name(name)
|
@@ -1,4 +1,5 @@
|
|
1
1
|
# ~*~ encoding: utf-8 ~*~
|
2
|
+
require 'octicons'
|
2
3
|
|
3
4
|
# Replace specified tokens with dynamically generated content.
|
4
5
|
class Gollum::Filter::Macro < Gollum::Filter
|
@@ -12,7 +13,7 @@ class Gollum::Filter::Macro < Gollum::Filter
|
|
12
13
|
|
13
14
|
data.gsub(/('?)\<\<\s*([A-Z][A-Za-z0-9]*)\s*\(#{arg_list}\)\s*\>\>/) do
|
14
15
|
next CGI.escape_html($&[1..-1]) unless Regexp.last_match[1].empty?
|
15
|
-
id = Digest::SHA1.hexdigest(Regexp.last_match[2] + Regexp.last_match[3])
|
16
|
+
id = "#{open_pattern}#{Digest::SHA1.hexdigest(Regexp.last_match[2] + Regexp.last_match[3])}#{close_pattern}"
|
16
17
|
macro = Regexp.last_match[2]
|
17
18
|
argstr = Regexp.last_match[3]
|
18
19
|
args = []
|
@@ -47,7 +48,9 @@ class Gollum::Filter::Macro < Gollum::Filter
|
|
47
48
|
begin
|
48
49
|
Gollum::Macro.instance(macro, @markup.wiki, @markup.page).render(*args)
|
49
50
|
rescue StandardError => e
|
50
|
-
|
51
|
+
icon = Octicons::Octicon.new('zap', {width: 24, height: 24})
|
52
|
+
icon.options[:class] << ' mr-2'
|
53
|
+
"<div class='flash flash-error'>#{icon.to_svg}Macro Error for #{macro}: #{e.message}</div>"
|
51
54
|
end
|
52
55
|
end
|
53
56
|
end
|
@@ -70,7 +70,7 @@ class Gollum::Filter::PlantUML < Gollum::Filter
|
|
70
70
|
# placeholders.
|
71
71
|
def extract(data)
|
72
72
|
data.gsub(/(@startuml\r?\n.+?\r?\n@enduml\r?$)/m) do
|
73
|
-
id = Digest::SHA1.hexdigest($1)
|
73
|
+
id = "#{open_pattern}#{Digest::SHA1.hexdigest($1)}#{close_pattern}"
|
74
74
|
@map[id] = { :code => $1 }
|
75
75
|
id
|
76
76
|
end
|
@@ -20,7 +20,7 @@ class Gollum::Filter::RemoteCode < Gollum::Filter
|
|
20
20
|
|
21
21
|
# Detect local file
|
22
22
|
if protocol.nil?
|
23
|
-
if (file = @markup.
|
23
|
+
if (file = @markup.wiki.file(uri, @markup.wiki.ref))
|
24
24
|
contents = file.raw_data
|
25
25
|
else
|
26
26
|
# How do we communicate a render error?
|
@@ -45,7 +45,8 @@ class Gollum::Filter::RemoteCode < Gollum::Filter
|
|
45
45
|
return "Too many redirects or retries" if cut >= 10
|
46
46
|
http = Net::HTTP.new uri.host, uri.port
|
47
47
|
http.use_ssl = true
|
48
|
-
|
48
|
+
path = uri.path.empty? ? '/' : uri.path
|
49
|
+
resp = http.get path, {
|
49
50
|
'Accept' => 'text/plain',
|
50
51
|
'Cache-Control' => 'no-cache',
|
51
52
|
'Connection' => 'keep-alive',
|
@@ -3,7 +3,11 @@
|
|
3
3
|
class Gollum::Filter::Render < Gollum::Filter
|
4
4
|
def extract(data)
|
5
5
|
begin
|
6
|
-
|
6
|
+
working_dir = Pathname.new(@markup.wiki.path).join(@markup.dir)
|
7
|
+
working_dir = working_dir.exist? ? working_dir.to_s : '.'
|
8
|
+
Dir.chdir(working_dir) do
|
9
|
+
data = GitHub::Markup.render_s(@markup.format, data)
|
10
|
+
end
|
7
11
|
if data.nil?
|
8
12
|
raise "There was an error converting #{@markup.name} to HTML."
|
9
13
|
end
|
@@ -15,6 +19,25 @@ class Gollum::Filter::Render < Gollum::Filter
|
|
15
19
|
end
|
16
20
|
|
17
21
|
def process(data)
|
22
|
+
data = add_editable_header_class(data)
|
18
23
|
data
|
19
24
|
end
|
20
|
-
|
25
|
+
|
26
|
+
private
|
27
|
+
|
28
|
+
def add_editable_header_class(data)
|
29
|
+
doc = Nokogiri::HTML::DocumentFragment.parse(data)
|
30
|
+
doc.css('h1,h2,h3,h4,h5,h6').each_with_index do |header, i|
|
31
|
+
next if header.content.empty?
|
32
|
+
next if header.inner_html.match(PLACEHOLDER_PATTERN)
|
33
|
+
klass = header['class']
|
34
|
+
if klass
|
35
|
+
header['class'] = klass << ' editable'
|
36
|
+
else
|
37
|
+
header['class'] = 'editable'
|
38
|
+
end
|
39
|
+
end
|
40
|
+
doc.to_xml(@markup.class.to_xml_opts)
|
41
|
+
end
|
42
|
+
|
43
|
+
end
|
@@ -6,13 +6,6 @@ class Gollum::Filter::Sanitize < Gollum::Filter
|
|
6
6
|
end
|
7
7
|
|
8
8
|
def process(data)
|
9
|
-
|
10
|
-
doc = Nokogiri::HTML::DocumentFragment.parse(data)
|
11
|
-
doc = @markup.sanitize.clean_node!(doc)
|
12
|
-
|
13
|
-
doc.to_xml(@markup.to_xml_opts).gsub(/<p><\/p>/, '')
|
14
|
-
else
|
15
|
-
data
|
16
|
-
end
|
9
|
+
sanitize(data)
|
17
10
|
end
|
18
11
|
end
|
@@ -34,7 +34,7 @@ class Gollum::Filter::Tags < Gollum::Filter
|
|
34
34
|
doc.traverse do |node|
|
35
35
|
if node.text? then
|
36
36
|
content = node.content
|
37
|
-
content.gsub!(
|
37
|
+
content.gsub!(%r{#{open_pattern}[a-f0-9]+#{close_pattern}}) do |id|
|
38
38
|
if (tag = @map[id]) then
|
39
39
|
if is_preformatted?(node) then
|
40
40
|
"[[#{tag}]]"
|
@@ -56,7 +56,7 @@ class Gollum::Filter::Tags < Gollum::Filter
|
|
56
56
|
INCLUDE_TAG = 'include:'
|
57
57
|
|
58
58
|
def register_tag(tag)
|
59
|
-
id = "
|
59
|
+
id = "#{open_pattern}#{Digest::SHA1.hexdigest(tag)}#{close_pattern}"
|
60
60
|
@map[id] = tag
|
61
61
|
id
|
62
62
|
end
|
@@ -77,7 +77,6 @@ class Gollum::Filter::Tags < Gollum::Filter
|
|
77
77
|
return generate_link('', nil, nil, :page_absent) if link_part.nil?
|
78
78
|
img_args = extra ? [extra, link_part] : [link_part]
|
79
79
|
mime = MIME::Types.type_for(::File.extname(img_args.first.to_s)).first
|
80
|
-
|
81
80
|
result = if tag =~ /^_TOC_/
|
82
81
|
%{[[#{tag}]]}
|
83
82
|
elsif link_part =~ /^_$/
|
@@ -122,9 +121,9 @@ class Gollum::Filter::Tags < Gollum::Filter
|
|
122
121
|
len = INCLUDE_TAG.length
|
123
122
|
return html_error('Cannot process include directive: no page name given') if tag.length <= len
|
124
123
|
page_name = tag[len..-1]
|
125
|
-
resolved_page_name = ::File.
|
124
|
+
resolved_page_name = ::File.join(@markup.dir, page_name)
|
126
125
|
if @markup.include_levels > 0
|
127
|
-
page =
|
126
|
+
page = find_page_or_file_from_path(resolved_page_name)
|
128
127
|
if page
|
129
128
|
page.formatted_data(@markup.encoding, @markup.include_levels-1)
|
130
129
|
else
|
@@ -146,8 +145,8 @@ class Gollum::Filter::Tags < Gollum::Filter
|
|
146
145
|
opts = parse_image_tag_options(options)
|
147
146
|
if path =~ /^https?:\/\/.+$/i
|
148
147
|
generate_image(path, opts)
|
149
|
-
elsif file =
|
150
|
-
generate_image(generate_href_for_path(file.
|
148
|
+
elsif file = find_page_or_file_from_path(path, :file)
|
149
|
+
generate_image(generate_href_for_path(file.url_path), opts)
|
151
150
|
else
|
152
151
|
generate_image('', opts)
|
153
152
|
end
|
@@ -173,14 +172,8 @@ class Gollum::Filter::Tags < Gollum::Filter
|
|
173
172
|
# Return the String HTML if the tag is a valid external link tag or
|
174
173
|
# nil if it is not.
|
175
174
|
def process_external_link_tag(url, pretty_name = nil)
|
176
|
-
|
177
|
-
if
|
178
|
-
accepted_protocols.select!{|protocol| protocol != :relative}
|
179
|
-
regexp = %r{^((#{accepted_protocols.join("|")}):)?(//)}
|
180
|
-
else
|
181
|
-
regexp = %r{^((#{accepted_protocols.join("|")}):)}
|
182
|
-
end
|
183
|
-
if url =~ regexp
|
175
|
+
@accepted_protocols_regex ||= %r{^((#{::Gollum::Sanitization.accepted_protocols.join('|')}):)?(//)}
|
176
|
+
if url =~ @accepted_protocols_regex
|
184
177
|
generate_link(url, pretty_name, nil, :external)
|
185
178
|
else
|
186
179
|
nil
|
@@ -195,8 +188,9 @@ class Gollum::Filter::Tags < Gollum::Filter
|
|
195
188
|
# Returns the String HTML if the tag is a valid file link tag or nil
|
196
189
|
# if it is not.
|
197
190
|
def process_file_link_tag(link_part, pretty_name)
|
198
|
-
|
199
|
-
|
191
|
+
return nil if ::Gollum::Page.valid_extension?(link_part)
|
192
|
+
if file = find_page_or_file_from_path(link_part, :file)
|
193
|
+
generate_link(file.url_path, pretty_name, nil, :file)
|
200
194
|
else
|
201
195
|
nil
|
202
196
|
end
|
@@ -212,18 +206,31 @@ class Gollum::Filter::Tags < Gollum::Filter
|
|
212
206
|
def process_page_link_tag(link_part, pretty_name = nil)
|
213
207
|
presence = :page_absent
|
214
208
|
link = link_part
|
215
|
-
page =
|
209
|
+
page = find_page_or_file_from_path(link)
|
216
210
|
|
217
211
|
# If no match yet, try finding page with anchor removed
|
218
|
-
if
|
219
|
-
|
220
|
-
|
221
|
-
|
222
|
-
|
223
|
-
|
212
|
+
if page.nil?
|
213
|
+
if pos = link.rindex('#')
|
214
|
+
extra = link[pos..-1]
|
215
|
+
link = link[0...pos]
|
216
|
+
else
|
217
|
+
extra = nil
|
218
|
+
end
|
219
|
+
|
220
|
+
if link.empty? && extra # Internal anchor link, don't search for the page but return immediately
|
221
|
+
return generate_link(nil, pretty_name, extra, :internal_anchor)
|
222
|
+
end
|
224
223
|
|
225
|
-
|
226
|
-
|
224
|
+
page = find_page_or_file_from_path(link)
|
225
|
+
end
|
226
|
+
presence = :page_present if page
|
227
|
+
|
228
|
+
if pretty_name
|
229
|
+
name = pretty_name
|
230
|
+
else
|
231
|
+
name = page ? path_to_link_text(link) : link
|
232
|
+
end
|
233
|
+
link = page ? page.escaped_url_path : ERB::Util.url_encode(link).force_encoding('utf-8')
|
227
234
|
generate_link(link, name, extra, presence)
|
228
235
|
end
|
229
236
|
|
@@ -232,15 +239,15 @@ class Gollum::Filter::Tags < Gollum::Filter
|
|
232
239
|
# path - The String path to search for.
|
233
240
|
#
|
234
241
|
# Returns a Gollum::Page instance if a page is found, or nil otherwise
|
235
|
-
def
|
236
|
-
|
237
|
-
|
238
|
-
|
239
|
-
|
240
|
-
|
241
|
-
|
242
|
+
def find_page_or_file_from_path(path, kind = :page)
|
243
|
+
if Pathname.new(path).relative?
|
244
|
+
result = @markup.wiki.send(kind, ::File.join(@markup.dir, path))
|
245
|
+
if result.nil? && @markup.wiki.global_tag_lookup # 4.x link compatibility option. Slow!
|
246
|
+
result = @markup.wiki.send(kind, path, nil, true)
|
247
|
+
end
|
248
|
+
result
|
242
249
|
else
|
243
|
-
@markup.wiki.
|
250
|
+
@markup.wiki.send(kind, path)
|
244
251
|
end
|
245
252
|
end
|
246
253
|
|
@@ -264,7 +271,8 @@ class Gollum::Filter::Tags < Gollum::Filter
|
|
264
271
|
#
|
265
272
|
# Returns a String href.
|
266
273
|
def generate_href_for_path(path, extra = nil)
|
267
|
-
|
274
|
+
return extra if !path && extra # Internal anchor link
|
275
|
+
"#{trim_leading_slashes(::File.join(@markup.wiki.base_path, path))}#{extra}"
|
268
276
|
end
|
269
277
|
|
270
278
|
# Construct a CSS class and attribute string for different kinds of links: internal Pages (absent or present) and Files, and External links.
|
@@ -278,6 +286,8 @@ class Gollum::Filter::Tags < Gollum::Filter
|
|
278
286
|
'class="internal absent"'
|
279
287
|
when :page_present
|
280
288
|
'class="internal present"'
|
289
|
+
when :internal_anchor
|
290
|
+
'class="internal anchorlink"'
|
281
291
|
when :file
|
282
292
|
nil
|
283
293
|
when :external
|
@@ -299,10 +309,10 @@ class Gollum::Filter::Tags < Gollum::Filter
|
|
299
309
|
attr_string = attrs.map {|key, value| "#{key}=\"#{value}\""}.join(' ')
|
300
310
|
|
301
311
|
if containered
|
302
|
-
%{<span class="#{classes.join(' ')}">} +
|
303
|
-
%{<span>} +
|
312
|
+
%{<span class="d-flex #{classes[:container].join(' ')}">} +
|
313
|
+
%{<span class="#{classes[:nested].join(' ')}">} +
|
304
314
|
%{<img src="#{path}" #{attr_string}/>} +
|
305
|
-
(attrs[:alt] ? %{<span>#{attrs[:alt]}</span>} : '') +
|
315
|
+
(options[:frame] && attrs[:alt] ? %{<span class="clearfix">#{attrs[:alt]}</span>} : '') +
|
306
316
|
%{</span>} +
|
307
317
|
%{</span>}
|
308
318
|
else
|
@@ -314,31 +324,31 @@ class Gollum::Filter::Tags < Gollum::Filter
|
|
314
324
|
#
|
315
325
|
# options - The Hash of parsed image options.
|
316
326
|
#
|
317
|
-
# Returns
|
327
|
+
# Returns a Hash containing CSS class Arrays, a Hash of CSS attributes, and a Boolean indicating whether or not the image is containered.
|
318
328
|
def generate_image_attributes(options)
|
319
329
|
containered = false
|
320
|
-
classes = [] # applied to
|
330
|
+
classes = {container: [], nested: []} # applied to the container(s)
|
321
331
|
attrs = {} # applied to the image
|
322
332
|
|
323
333
|
align = options[:align]
|
324
334
|
if options[:float]
|
325
335
|
containered = true
|
326
|
-
align
|
327
|
-
|
328
|
-
classes << "float-#{align}"
|
329
|
-
end
|
336
|
+
align = 'left' unless align == 'right'
|
337
|
+
classes[:container] << "float-#{align} pb-4"
|
330
338
|
elsif %w{top texttop middle absmiddle bottom absbottom baseline}.include?(align)
|
331
339
|
attrs[:align] = align
|
332
340
|
elsif align
|
333
341
|
if %w{left center right}.include?(align)
|
334
342
|
containered = true
|
335
|
-
|
343
|
+
text_align = "text-#{align}"
|
344
|
+
align = 'end' if align == 'right'
|
345
|
+
classes[:container] << "flex-justify-#{align} #{text_align}"
|
336
346
|
end
|
337
347
|
end
|
338
348
|
|
339
|
-
if options[:frame]
|
349
|
+
if options[:frame]
|
340
350
|
containered = true
|
341
|
-
classes << '
|
351
|
+
classes[:nested] << 'border p-4'
|
342
352
|
end
|
343
353
|
|
344
354
|
attrs[:alt] = options[:alt] if options[:alt]
|