gollum-lib 5.0.a.4-java → 5.0.1-java

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.
Files changed (43) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile +2 -1
  3. data/README.md +12 -7
  4. data/Rakefile +7 -7
  5. data/adapter_dependencies.rb +7 -0
  6. data/gemspec.rb +18 -10
  7. data/gollum-lib.gemspec +2 -6
  8. data/gollum-lib_java.gemspec +2 -2
  9. data/lib/gollum-lib.rb +9 -9
  10. data/lib/gollum-lib/blob_entry.rb +2 -8
  11. data/lib/gollum-lib/committer.rb +22 -60
  12. data/lib/gollum-lib/file.rb +105 -82
  13. data/lib/gollum-lib/file_view.rb +8 -4
  14. data/lib/gollum-lib/filter.rb +12 -0
  15. data/lib/gollum-lib/filter/code.rb +9 -13
  16. data/lib/gollum-lib/filter/critic_markup.rb +97 -0
  17. data/lib/gollum-lib/filter/emoji.rb +10 -8
  18. data/lib/gollum-lib/filter/macro.rb +5 -2
  19. data/lib/gollum-lib/filter/plantuml.rb +1 -1
  20. data/lib/gollum-lib/filter/remote_code.rb +3 -2
  21. data/lib/gollum-lib/filter/render.rb +25 -2
  22. data/lib/gollum-lib/filter/sanitize.rb +1 -8
  23. data/lib/gollum-lib/filter/tags.rb +57 -47
  24. data/lib/gollum-lib/filter/toc.rb +17 -21
  25. data/lib/gollum-lib/filter/yaml.rb +1 -1
  26. data/lib/gollum-lib/git_access.rb +0 -25
  27. data/lib/gollum-lib/helpers.rb +13 -3
  28. data/lib/gollum-lib/macro/audio.rb +9 -0
  29. data/lib/gollum-lib/macro/global_toc.rb +2 -1
  30. data/lib/gollum-lib/macro/navigation.rb +8 -6
  31. data/lib/gollum-lib/macro/note.rb +19 -0
  32. data/lib/gollum-lib/macro/octicon.rb +12 -0
  33. data/lib/gollum-lib/macro/warn.rb +11 -0
  34. data/lib/gollum-lib/markup.rb +17 -32
  35. data/lib/gollum-lib/markups.rb +11 -7
  36. data/lib/gollum-lib/page.rb +79 -165
  37. data/lib/gollum-lib/pagination.rb +7 -6
  38. data/lib/gollum-lib/redirects.rb +38 -0
  39. data/lib/gollum-lib/sanitization.rb +32 -357
  40. data/lib/gollum-lib/version.rb +1 -1
  41. data/lib/gollum-lib/wiki.rb +216 -404
  42. metadata +125 -79
  43. data/ROADMAP +0 -6
@@ -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 html
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
- # return the completed html
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
@@ -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]*#\+BEGIN_SRC#{org_lang}\n}
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[3], Regexp.last_match[4])
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!(/^([ \t]*)(~~~+) ?([^\r\n]+)?\r?\n(.+?)\r?\n\1(~~~+)[ \t\r]*$/m) do
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
- # start and finish tilde fence must be the same length
30
- next '' if m_start.length != m_end.length
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!(/^([ \t]*)``` ?([^\r\n]+)?\r?\n(.+?)\r?\n\1```[ \t]*\r?$/m) do
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'>&nbsp;</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'>&nbsp;</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'>&nbsp;</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>&nbsp;</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 "=EEMMOOJJII=#{$~[:name]}=IIJJOOMMEE="
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! PROCESS_PATTERN, %q(<img src="/emoji/\k<name>" alt="\k<name>" class="emoji">)
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
- "!!!Macro Error: #{e.message}!!!"
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.find_file(uri, @markup.wiki.ref))
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
- resp = http.get uri.path, {
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
- data = GitHub::Markup.render(@markup.name, data)
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
- end
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
- if @markup.sanitize
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!(/TAG[a-f0-9]+TAG/) do |id|
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 = "TAG#{Digest::SHA1.hexdigest(tag)}TAG"
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.expand_path(page_name, "#{::File::SEPARATOR}#{@markup.dir}")
124
+ resolved_page_name = ::File.join(@markup.dir, page_name)
126
125
  if @markup.include_levels > 0
127
- page = find_page_from_path(resolved_page_name)
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 = @markup.find_file(path)
150
- generate_image(generate_href_for_path(file.path), opts)
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
- accepted_protocols = @markup.wiki.sanitization.protocols['a']['href'].dup
177
- if accepted_protocols.include?(:relative)
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
- if file = @markup.find_file(link_part)
199
- generate_link(file.path, pretty_name, nil, :file)
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 = find_page_from_path(link)
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 (page.nil? && pos = link.rindex('#'))
219
- extra = link[pos..-1]
220
- link = link[0...pos]
221
- page = find_page_from_path(link)
222
- end
223
- presence = :page_present if page
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
- name = pretty_name ? pretty_name : link
226
- link = page ? page.escaped_url_path : CGI.escape(link)
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 find_page_from_path(path)
236
- slash = path.rindex('/')
237
-
238
- unless slash.nil?
239
- name = path[slash+1..-1]
240
- path = path[0..slash]
241
- @markup.wiki.paged(name, path)
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.page(path)
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
- "#{trim_leading_slash(::File.join(@markup.wiki.base_path, path))}#{extra}"
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 an Array of CSS classes, a Hash of CSS attributes, and a Boolean indicating whether or not the image is containered.
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 whatever the outermost container is
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 ||= 'left'
327
- if %w{left right}.include?(align)
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
- classes << "align-#{align}"
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 << 'frame'
351
+ classes[:nested] << 'border p-4'
342
352
  end
343
353
 
344
354
  attrs[:alt] = options[:alt] if options[:alt]