summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJakub Jirutka <jakub@jirutka.cz>2015-05-11 01:34:32 +0200
committerJakub Jirutka <jakub@jirutka.cz>2015-05-11 02:07:26 +0200
commitf7adac87fe824a453197f0be547c35684d468dd7 (patch)
tree9f3954377289efaca06177bab214c4069f176dee
parent6fdc51f81d013a1e01c0742e81cdd0fa5476e2d6 (diff)
downloadgitlab-ce-f7adac87fe824a453197f0be547c35684d468dd7.tar.gz
Extract handling of relative file links to RelativeLinkFilter
-rw-r--r--app/helpers/gitlab_markdown_helper.rb140
-rw-r--r--lib/gitlab/markdown.rb10
-rw-r--r--lib/gitlab/markdown/relative_link_filter.rb117
-rw-r--r--lib/redcarpet/render/gitlab_html.rb4
4 files changed, 126 insertions, 145 deletions
diff --git a/app/helpers/gitlab_markdown_helper.rb b/app/helpers/gitlab_markdown_helper.rb
index 24263a0f619..c309f890d96 100644
--- a/app/helpers/gitlab_markdown_helper.rb
+++ b/app/helpers/gitlab_markdown_helper.rb
@@ -72,146 +72,6 @@ module GitlabMarkdownHelper
end
end
- # TODO (rspeicher): This should be its own filter
- def create_relative_links(text)
- paths = extract_paths(text)
-
- paths.uniq.each do |file_path|
- # If project does not have repository
- # its nothing to rebuild
- #
- # TODO: pass project variable to markdown helper instead of using
- # instance variable. Right now it generates invalid path for pages out
- # of project scope. Example: search results where can be rendered markdown
- # from different projects
- if @repository && @repository.exists? && !@repository.empty?
- new_path = rebuild_path(file_path)
- # Finds quoted path so we don't replace other mentions of the string
- # eg. "doc/api" will be replaced and "/home/doc/api/text" won't
- text.gsub!("\"#{file_path}\"", "\"/#{new_path}\"")
- end
- end
-
- text
- end
-
- def extract_paths(text)
- links = substitute_links(text)
- image_links = substitute_image_links(text)
- links + image_links
- end
-
- def substitute_links(text)
- links = text.scan(/<a href=\"([^"]*)\">/)
- relative_links = links.flatten.reject{ |link| link_to_ignore? link }
- relative_links
- end
-
- def substitute_image_links(text)
- links = text.scan(/<img src=\"([^"]*)\"/)
- relative_links = links.flatten.reject{ |link| link_to_ignore? link }
- relative_links
- end
-
- def link_to_ignore?(link)
- if link =~ /\A\#\w+/
- # ignore anchors like <a href="#my-header">
- true
- else
- ignored_protocols.map{ |protocol| link.include?(protocol) }.any?
- end
- end
-
- def ignored_protocols
- ["http://","https://", "ftp://", "mailto:", "smb://"]
- end
-
- def rebuild_path(file_path)
- file_path = file_path.dup
- file_path.gsub!(/(#.*)/, "")
- id = $1 || ""
- file_path = relative_file_path(file_path)
- file_path = sanitize_slashes(file_path)
-
- [
- Gitlab.config.gitlab.relative_url_root,
- @project.path_with_namespace,
- path_with_ref(file_path),
- file_path
- ].compact.join("/").gsub(/\A\/*|\/*\z/, '') + id
- end
-
- def sanitize_slashes(path)
- path[0] = "" if path.start_with?("/")
- path.chop if path.end_with?("/")
- path
- end
-
- def relative_file_path(path)
- requested_path = @path
- nested_path = build_nested_path(path, requested_path)
- return nested_path if file_exists?(nested_path)
- path
- end
-
- # Covering a special case, when the link is referencing file in the same directory eg:
- # If we are at doc/api/README.md and the README.md contains relative links like [Users](users.md)
- # this takes the request path(doc/api/README.md), and replaces the README.md with users.md so the path looks like doc/api/users.md
- # If we are at doc/api and the README.md shown in below the tree view
- # this takes the request path(doc/api) and adds users.md so the path looks like doc/api/users.md
- def build_nested_path(path, request_path)
- return request_path if path == ""
- return path unless request_path
- if local_path(request_path) == "tree"
- base = request_path.split("/").push(path)
- base.join("/")
- else
- base = request_path.split("/")
- base.pop
- base.push(path).join("/")
- end
- end
-
- # Checks if the path exists in the repo
- # eg. checks if doc/README.md exists, if not then link to blob
- def path_with_ref(path)
- if file_exists?(path)
- "#{local_path(path)}/#{correct_ref}"
- else
- "blob/#{correct_ref}"
- end
- end
-
- def file_exists?(path)
- return false if path.nil?
- @repository.blob_at(current_sha, path).present? || @repository.tree(current_sha, path).entries.any?
- end
-
- # Check if the path is pointing to a directory(tree) or a file(blob)
- # eg. doc/api is directory and doc/README.md is file
- def local_path(path)
- return "tree" if @repository.tree(current_sha, path).entries.any?
- return "raw" if @repository.blob_at(current_sha, path).image?
- "blob"
- end
-
- def current_sha
- if @commit
- @commit.id
- elsif @repository && !@repository.empty?
- if @ref
- @repository.commit(@ref).try(:sha)
- else
- @repository.head_commit.sha
- end
- end
- end
-
- # We will assume that if no ref exists we can point to master
- def correct_ref
- @ref ? @ref : "master"
- end
-
private
# Return +text+, truncated to +max_chars+ characters, excluding any HTML
diff --git a/lib/gitlab/markdown.rb b/lib/gitlab/markdown.rb
index 63294aa54c0..dadd0e0d64b 100644
--- a/lib/gitlab/markdown.rb
+++ b/lib/gitlab/markdown.rb
@@ -15,6 +15,7 @@ module Gitlab
autoload :IssueReferenceFilter, 'gitlab/markdown/issue_reference_filter'
autoload :LabelReferenceFilter, 'gitlab/markdown/label_reference_filter'
autoload :MergeRequestReferenceFilter, 'gitlab/markdown/merge_request_reference_filter'
+ autoload :RelativeLinkFilter, 'gitlab/markdown/relative_link_filter'
autoload :SanitizationFilter, 'gitlab/markdown/sanitization_filter'
autoload :SnippetReferenceFilter, 'gitlab/markdown/snippet_reference_filter'
autoload :TableOfContentsFilter, 'gitlab/markdown/table_of_contents_filter'
@@ -64,7 +65,12 @@ module Gitlab
current_user: current_user,
only_path: options[:reference_only_path],
project: project,
- reference_class: html_options[:class]
+ reference_class: html_options[:class],
+
+ # RelativeLinkFilter
+ ref: @ref,
+ requested_path: @path,
+ project_wiki: @project_wiki
}
result = pipeline.call(text, context)
@@ -91,6 +97,8 @@ module Gitlab
[
Gitlab::Markdown::SanitizationFilter,
+ Gitlab::Markdown::RelativeLinkFilter,
+
Gitlab::Markdown::EmojiFilter,
Gitlab::Markdown::TableOfContentsFilter,
Gitlab::Markdown::AutolinkFilter,
diff --git a/lib/gitlab/markdown/relative_link_filter.rb b/lib/gitlab/markdown/relative_link_filter.rb
new file mode 100644
index 00000000000..084f84b25f7
--- /dev/null
+++ b/lib/gitlab/markdown/relative_link_filter.rb
@@ -0,0 +1,117 @@
+require 'uri'
+
+module Gitlab
+ module Markdown
+ # HTML filter that "fixes" relative links to files in a repository.
+ #
+ # Context options:
+ # :commit
+ # :project
+ # :project_wiki
+ # :requested_path
+ # :ref
+ class RelativeLinkFilter < HTML::Pipeline::Filter
+
+ def call
+ if !project_wiki && repository.try(:exists?) && !repository.empty?
+ doc.search('a').each do |el|
+ process_link_attr el.attribute('href')
+ end
+
+ doc.search('img').each do |el|
+ process_link_attr el.attribute('src')
+ end
+ end
+
+ doc
+ end
+
+ protected
+
+ def process_link_attr(html_attr)
+ return if html_attr.blank?
+
+ uri = URI(html_attr.value)
+ if uri.relative? && uri.path.present?
+ html_attr.value = rebuild_relative_uri(uri).to_s
+ end
+ end
+
+ def rebuild_relative_uri(uri)
+ file_path = relative_file_path(uri.path)
+
+ uri.path = [
+ relative_url_root,
+ project.path_with_namespace,
+ path_type(file_path),
+ ref || 'master', # assume that if no ref exists we can point to master
+ file_path
+ ].compact.join('/').squeeze('/').chomp('/')
+
+ uri
+ end
+
+ def relative_file_path(path)
+ nested_path = build_nested_path(path, requested_path)
+ file_exists?(nested_path) ? nested_path : path
+ end
+
+ # Covering a special case, when the link is referencing file in the same
+ # directory.
+ # If we are at doc/api/README.md and the README.md contains relative
+ # links like [Users](users.md), this takes the request
+ # path(doc/api/README.md) and replaces the README.md with users.md so the
+ # path looks like doc/api/users.md.
+ # If we are at doc/api and the README.md shown in below the tree view
+ # this takes the request path(doc/api) and adds users.md so the path
+ # looks like doc/api/users.md
+ def build_nested_path(path, request_path)
+ return request_path if path.empty?
+ return path unless request_path
+
+ parts = request_path.split('/')
+ parts.pop if path_type(request_path) != 'tree'
+ parts.push(path).join('/')
+ end
+
+ def file_exists?(path)
+ return false if path.nil?
+ repository.blob_at(current_sha, path).present? ||
+ repository.tree(current_sha, path).entries.any?
+ end
+
+ # Check if the path is pointing to a directory(tree) or a file(blob)
+ # eg. doc/api is directory and doc/README.md is file.
+ def path_type(path)
+ return 'tree' if repository.tree(current_sha, path).entries.any?
+ return 'raw' if repository.blob_at(current_sha, path).try(:image?)
+ 'blob'
+ end
+
+ def current_sha
+ if commit
+ commit.id
+ elsif ref
+ repository.commit(ref).try(:sha)
+ else
+ repository.head_commit.sha
+ end
+ end
+
+ def relative_url_root
+ Gitlab.config.gitlab.relative_url_root.presence || '/'
+ end
+
+ [:commit, :project, :project_wiki, :requested_path, :ref].each do |name|
+ define_method(name) do
+ context[name]
+ end
+ end
+
+ def repository
+ return if project.nil?
+ project.repository
+ end
+ end
+ end
+end
diff --git a/lib/redcarpet/render/gitlab_html.rb b/lib/redcarpet/render/gitlab_html.rb
index bea66e6cdc1..15eb6effe08 100644
--- a/lib/redcarpet/render/gitlab_html.rb
+++ b/lib/redcarpet/render/gitlab_html.rb
@@ -36,10 +36,6 @@ class Redcarpet::Render::GitlabHTML < Redcarpet::Render::HTML
end
def postprocess(full_document)
- unless @template.instance_variable_get("@project_wiki") || @project.nil?
- full_document = h.create_relative_links(full_document)
- end
-
h.gfm_with_options(full_document, @options)
end
end