From ed41333a6ecbfcc04781a47a3dc71064c92d837b Mon Sep 17 00:00:00 2001 From: Douwe Maan Date: Wed, 14 Oct 2015 19:27:23 +0200 Subject: Use Gitlab::Markdown.render with :pipeline option rather than different methods --- lib/gitlab/markdown.rb | 236 +++++++++++++--------------- lib/gitlab/markdown/markdown_filter.rb | 39 +++++ lib/gitlab/markdown/relative_link_filter.rb | 2 +- lib/gitlab/markdown/sanitization_filter.rb | 6 +- lib/gitlab/reference_extractor.rb | 20 ++- 5 files changed, 162 insertions(+), 141 deletions(-) create mode 100644 lib/gitlab/markdown/markdown_filter.rb (limited to 'lib') diff --git a/lib/gitlab/markdown.rb b/lib/gitlab/markdown.rb index d5b0060dd56..773ebddba2b 100644 --- a/lib/gitlab/markdown.rb +++ b/lib/gitlab/markdown.rb @@ -19,51 +19,15 @@ module Gitlab # context - Hash of context options passed to our HTML Pipeline # # Returns an HTML-safe String - def self.render(markdown, context = {}) - html = renderer.render(markdown) - html = gfm(html, context) + def self.render(text, context = {}) + pipeline = context[:pipeline] || :full - html.html_safe - end + html_pipeline = html_pipelines[pipeline] - # Convert a Markdown String into HTML without going through the HTML - # Pipeline. - # - # Note that because the pipeline is skipped, SanitizationFilter is as well. - # Do not output the result of this method to the user. - # - # markdown - Markdown String - # - # Returns a String - def self.render_without_gfm(markdown) - renderer.render(markdown) - end + transformers = get_context_transformers(pipeline) + context = transformers.reduce(context) { |context, transformer| transformer.call(context) } - # Perform post-processing on an HTML String - # - # This method is used to perform state-dependent changes to a String of - # HTML, such as removing references that the current user doesn't have - # permission to make (`RedactorFilter`). - # - # html - String to process - # options - Hash of options to customize output - # :pipeline - Symbol pipeline type - # :project - Project - # :user - User object - # - # Returns an HTML-safe String - def self.post_process(html, options) - context = { - project: options[:project], - current_user: options[:user] - } - doc = post_processor.to_document(html, context) - - if options[:pipeline] == :atom - doc.to_html(save_with: Nokogiri::XML::Node::SaveOptions::AS_XHTML) - else - doc.to_html - end.html_safe + html_pipeline.to_html(text, context) end # Provide autoload paths for filters to prevent a circular dependency error @@ -75,6 +39,7 @@ module Gitlab autoload :ExternalLinkFilter, 'gitlab/markdown/external_link_filter' autoload :IssueReferenceFilter, 'gitlab/markdown/issue_reference_filter' autoload :LabelReferenceFilter, 'gitlab/markdown/label_reference_filter' + autoload :MarkdownFilter, 'gitlab/markdown/markdown_filter' autoload :MergeRequestReferenceFilter, 'gitlab/markdown/merge_request_reference_filter' autoload :RedactorFilter, 'gitlab/markdown/redactor_filter' autoload :RelativeLinkFilter, 'gitlab/markdown/relative_link_filter' @@ -85,98 +50,38 @@ module Gitlab autoload :TaskListFilter, 'gitlab/markdown/task_list_filter' autoload :UserReferenceFilter, 'gitlab/markdown/user_reference_filter' - # Public: Parse the provided HTML with GitLab-Flavored Markdown + # Perform post-processing on an HTML String + # + # This method is used to perform state-dependent changes to a String of + # HTML, such as removing references that the current user doesn't have + # permission to make (`RedactorFilter`). # - # html - HTML String - # options - A Hash of options used to customize output (default: {}) - # :no_header_anchors - Disable header anchors in TableOfContentsFilter - # :path - Current path String - # :pipeline - Symbol pipeline type - # :project - Current Project object - # :project_wiki - Current ProjectWiki object - # :ref - Current ref String + # html - String to process + # context - Hash of options to customize output + # :pipeline - Symbol pipeline type + # :project - Project + # :user - User object # # Returns an HTML-safe String - def self.gfm(html, options = {}) - return '' unless html.present? - - @pipeline ||= HTML::Pipeline.new(filters) - - context = { - # SanitizationFilter - pipeline: options[:pipeline], - - # EmojiFilter - asset_host: Gitlab::Application.config.asset_host, - asset_root: Gitlab.config.gitlab.base_url, - - # ReferenceFilter - only_path: only_path_pipeline?(options[:pipeline]), - project: options[:project], - - # RelativeLinkFilter - project_wiki: options[:project_wiki], - ref: options[:ref], - requested_path: options[:path], - - # TableOfContentsFilter - no_header_anchors: options[:no_header_anchors] - } + def self.post_process(html, context) + doc = html_pipelines[:post_process].to_document(html, context) - @pipeline.to_html(html, context).html_safe - end - - private - - # Check if a pipeline enables the `only_path` context option - # - # Returns Boolean - def self.only_path_pipeline?(pipeline) - case pipeline - when :atom, :email - false + if context[:xhtml] + doc.to_html(save_with: Nokogiri::XML::Node::SaveOptions::AS_XHTML) else - true - end - end - - def self.redcarpet_options - # https://github.com/vmg/redcarpet#and-its-like-really-simple-to-use - @redcarpet_options ||= { - fenced_code_blocks: true, - footnotes: true, - lax_spacing: true, - no_intra_emphasis: true, - space_after_headers: true, - strikethrough: true, - superscript: true, - tables: true - }.freeze - end - - def self.renderer - @markdown ||= begin - renderer = Redcarpet::Render::HTML.new - Redcarpet::Markdown.new(renderer, redcarpet_options) - end - end - - def self.post_processor - @post_processor ||= HTML::Pipeline.new([Gitlab::Markdown::RedactorFilter]) + doc.to_html + end.html_safe end - # Filters used in our pipeline - # - # SanitizationFilter should come first so that all generated reference HTML - # goes through untouched. - # - # See https://github.com/jch/html-pipeline#filters for more filters. - def self.filters - [ + private + FILTERS = { + plain_markdown: [ + Gitlab::Markdown::MarkdownFilter + ], + gfm: [ Gitlab::Markdown::SyntaxHighlightFilter, Gitlab::Markdown::SanitizationFilter, - Gitlab::Markdown::RelativeLinkFilter, Gitlab::Markdown::EmojiFilter, Gitlab::Markdown::TableOfContentsFilter, Gitlab::Markdown::AutolinkFilter, @@ -192,7 +97,90 @@ module Gitlab Gitlab::Markdown::LabelReferenceFilter, Gitlab::Markdown::TaskListFilter + ], + + full: [:plain_markdown, :gfm], + atom: :full, + email: :full, + description: :full, + single_line: :gfm, + + post_process: [ + Gitlab::Markdown::RelativeLinkFilter, + Gitlab::Markdown::RedactorFilter ] + } + + CONTEXT_TRANSFORMERS = { + gfm: { + only_path: true, + + # EmojiFilter + asset_host: Gitlab::Application.config.asset_host, + asset_root: Gitlab.config.gitlab.base_url + }, + full: :gfm, + + atom: [ + :full, + { + only_path: false, + xhtml: true + } + ], + email: [ + :full, + { + only_path: false + } + ], + description: [ + :full, + { + # SanitizationFilter + inline_sanitization: true + } + ], + single_line: :gfm, + + post_process: { + post_process: true + } + } + + def self.html_pipelines + @html_pipelines ||= Hash.new do |hash, pipeline| + filters = get_filters(pipeline) + HTML::Pipeline.new(filters) + end + end + + def self.get_filters(pipelines) + Array.wrap(pipelines).flat_map do |pipeline| + case pipeline + when Class + pipeline + when Symbol + get_filters(FILTERS[pipeline]) + when Array + get_filters(pipeline) + end + end.compact + end + + def self.get_context_transformers(pipelines) + Array.wrap(pipelines).flat_map do |pipeline| + case pipeline + when Hash + ->(context) { context.merge(pipeline) } + when Proc + pipeline + when Symbol + get_context_transformers(CONTEXT_TRANSFORMERS[pipeline]) + when Array + get_context_transformers(pipeline) + end + end.compact end end end diff --git a/lib/gitlab/markdown/markdown_filter.rb b/lib/gitlab/markdown/markdown_filter.rb new file mode 100644 index 00000000000..921e2a0794e --- /dev/null +++ b/lib/gitlab/markdown/markdown_filter.rb @@ -0,0 +1,39 @@ +module Gitlab + module Markdown + class MarkdownFilter < HTML::Pipeline::TextFilter + def initialize(text, context = nil, result = nil) + super text, context, result + @text = @text.gsub "\r", '' + end + + def call + html = self.class.renderer.render(@text) + html.rstrip! + html + end + + private + + def self.redcarpet_options + # https://github.com/vmg/redcarpet#and-its-like-really-simple-to-use + @redcarpet_options ||= { + fenced_code_blocks: true, + footnotes: true, + lax_spacing: true, + no_intra_emphasis: true, + space_after_headers: true, + strikethrough: true, + superscript: true, + tables: true + }.freeze + end + + def self.renderer + @renderer ||= begin + renderer = Redcarpet::Render::HTML.new + Redcarpet::Markdown.new(renderer, redcarpet_options) + end + end + end + end +end diff --git a/lib/gitlab/markdown/relative_link_filter.rb b/lib/gitlab/markdown/relative_link_filter.rb index 6ee3d1ce039..3e9909d2f33 100644 --- a/lib/gitlab/markdown/relative_link_filter.rb +++ b/lib/gitlab/markdown/relative_link_filter.rb @@ -16,7 +16,7 @@ module Gitlab def call return doc unless linkable_files? - doc.search('a').each do |el| + doc.search('a:not(.gfm)').each do |el| process_link_attr el.attribute('href') end diff --git a/lib/gitlab/markdown/sanitization_filter.rb b/lib/gitlab/markdown/sanitization_filter.rb index e368de7d848..550dfafca85 100644 --- a/lib/gitlab/markdown/sanitization_filter.rb +++ b/lib/gitlab/markdown/sanitization_filter.rb @@ -11,7 +11,7 @@ module Gitlab def whitelist # Descriptions are more heavily sanitized, allowing only a few elements. # See http://git.io/vkuAN - if pipeline == :description + if context[:inline_sanitization] whitelist = LIMITED whitelist[:elements] -= %w(pre code img ol ul li) else @@ -25,10 +25,6 @@ module Gitlab private - def pipeline - context[:pipeline] || :default - end - def customized?(transformers) transformers.last.source_location[0] == __FILE__ end diff --git a/lib/gitlab/reference_extractor.rb b/lib/gitlab/reference_extractor.rb index 66016ecc877..633c988d025 100644 --- a/lib/gitlab/reference_extractor.rb +++ b/lib/gitlab/reference_extractor.rb @@ -13,7 +13,8 @@ module Gitlab def analyze(text) references.clear - @text = Gitlab::Markdown.render_without_gfm(text) + + @document = Gitlab::Markdown.render(text, project: project) end %i(user label issue merge_request snippet commit commit_range).each do |type| @@ -44,20 +45,13 @@ module Gitlab filter = Gitlab::Markdown.const_get(klass) context = { - project: project, - current_user: current_user, - - # We don't actually care about the links generated - only_path: true, - ignore_blockquotes: true, - - # ReferenceGathererFilter + project: project, + current_user: current_user, load_lazy_references: false, reference_filter: filter } - pipeline = HTML::Pipeline.new([filter, Gitlab::Markdown::ReferenceGathererFilter], context) - result = pipeline.call(@text) + result = self.class.pipeline.call(@document, context) values = result[:references][filter_type].uniq @@ -67,5 +61,9 @@ module Gitlab values end + + def self.pipeline + @pipeline ||= HTML::Pipeline.new([Gitlab::Markdown::ReferenceGathererFilter]) + end end end -- cgit v1.2.1 From da9746e59a46005a0948d34bfd94ae715143c3f7 Mon Sep 17 00:00:00 2001 From: Douwe Maan Date: Wed, 14 Oct 2015 19:28:46 +0200 Subject: Enable caching of Gitlab::Markdown rendered result --- lib/gitlab/markdown.rb | 35 +++++++++++++++++++++++++---------- lib/gitlab/reference_extractor.rb | 4 ++-- 2 files changed, 27 insertions(+), 12 deletions(-) (limited to 'lib') diff --git a/lib/gitlab/markdown.rb b/lib/gitlab/markdown.rb index 773ebddba2b..21416f0fa02 100644 --- a/lib/gitlab/markdown.rb +++ b/lib/gitlab/markdown.rb @@ -20,14 +20,16 @@ module Gitlab # # Returns an HTML-safe String def self.render(text, context = {}) - pipeline = context[:pipeline] || :full - - html_pipeline = html_pipelines[pipeline] - - transformers = get_context_transformers(pipeline) - context = transformers.reduce(context) { |context, transformer| transformer.call(context) } + cache_key = context.delete(:cache_key) - html_pipeline.to_html(text, context) + if cache_key + cache_key = full_cache_key(cache_key, context[:pipeline]) + Rails.cache.fetch(cache_key) do + cacheless_render(text, context) + end + else + cacheless_render(text, context) + end end # Provide autoload paths for filters to prevent a circular dependency error @@ -130,9 +132,7 @@ module Gitlab ], email: [ :full, - { - only_path: false - } + { only_path: false } ], description: [ :full, @@ -155,6 +155,17 @@ module Gitlab end end + def self.cacheless_render(text, context = {}) + pipeline = context[:pipeline] || :full + + html_pipeline = html_pipelines[pipeline] + + transformers = get_context_transformers(pipeline) + context = transformers.reduce(context) { |context, transformer| transformer.call(context) } + + html_pipeline.to_html(text, context) + end + def self.get_filters(pipelines) Array.wrap(pipelines).flat_map do |pipeline| case pipeline @@ -182,5 +193,9 @@ module Gitlab end end.compact end + + def self.full_cache_key(cache_key, pipeline = :full) + ["markdown", *cache_key, pipeline] + end end end diff --git a/lib/gitlab/reference_extractor.rb b/lib/gitlab/reference_extractor.rb index 633c988d025..2262e5a168a 100644 --- a/lib/gitlab/reference_extractor.rb +++ b/lib/gitlab/reference_extractor.rb @@ -11,10 +11,10 @@ module Gitlab @load_lazy_references = load_lazy_references end - def analyze(text) + def analyze(text, cache_key: nil) references.clear - @document = Gitlab::Markdown.render(text, project: project) + @document = Gitlab::Markdown.render(text, project: project, cache_key: cache_key) end %i(user label issue merge_request snippet commit commit_range).each do |type| -- cgit v1.2.1 From b093cb35d1614a48e980c5ccfca3a8d307d732f7 Mon Sep 17 00:00:00 2001 From: Douwe Maan Date: Wed, 14 Oct 2015 20:18:49 +0200 Subject: Use Gitlab::Markdown for Asciidoc and ReferenceExtractor pipelines --- lib/gitlab/asciidoc.rb | 27 +---- lib/gitlab/markdown.rb | 226 +++++++++++++++++++++++--------------- lib/gitlab/reference_extractor.rb | 18 +-- 3 files changed, 154 insertions(+), 117 deletions(-) (limited to 'lib') diff --git a/lib/gitlab/asciidoc.rb b/lib/gitlab/asciidoc.rb index bf33e5b1b1e..330d3342dd1 100644 --- a/lib/gitlab/asciidoc.rb +++ b/lib/gitlab/asciidoc.rb @@ -1,14 +1,10 @@ require 'asciidoctor' -require 'html/pipeline' module Gitlab # Parser/renderer for the AsciiDoc format that uses Asciidoctor and filters # the resulting HTML through HTML pipeline filters. module Asciidoc - # Provide autoload paths for filters to prevent a circular dependency error - autoload :RelativeLinkFilter, 'gitlab/markdown/relative_link_filter' - DEFAULT_ADOC_ATTRS = [ 'showtitle', 'idprefix=user-content-', 'idseparator=-', 'env=gitlab', 'env-gitlab', 'source-highlighter=html-pipeline' @@ -24,13 +20,11 @@ module Gitlab # :requested_path # :ref # asciidoc_opts - a Hash of options to pass to the Asciidoctor converter - # html_opts - a Hash of options for HTML output: - # :xhtml - output XHTML instead of HTML # - def self.render(input, context, asciidoc_opts = {}, html_opts = {}) - asciidoc_opts = asciidoc_opts.reverse_merge( + def self.render(input, context, asciidoc_opts = {}) + asciidoc_opts.reverse_merge!( safe: :secure, - backend: html_opts[:xhtml] ? :xhtml5 : :html5, + backend: :html5, attributes: [] ) asciidoc_opts[:attributes].unshift(*DEFAULT_ADOC_ATTRS) @@ -38,23 +32,10 @@ module Gitlab html = ::Asciidoctor.convert(input, asciidoc_opts) if context[:project] - result = HTML::Pipeline.new(filters).call(html, context) - - save_opts = html_opts[:xhtml] ? - Nokogiri::XML::Node::SaveOptions::AS_XHTML : 0 - - html = result[:output].to_html(save_with: save_opts) + html = Gitlab::Markdown.render(html, context.merge(pipeline: :asciidoc)) end html.html_safe end - - private - - def self.filters - [ - Gitlab::Markdown::RelativeLinkFilter - ] - end end end diff --git a/lib/gitlab/markdown.rb b/lib/gitlab/markdown.rb index 21416f0fa02..dc6751fa1c2 100644 --- a/lib/gitlab/markdown.rb +++ b/lib/gitlab/markdown.rb @@ -21,9 +21,9 @@ module Gitlab # Returns an HTML-safe String def self.render(text, context = {}) cache_key = context.delete(:cache_key) + cache_key = full_cache_key(cache_key, context[:pipeline]) if cache_key - cache_key = full_cache_key(cache_key, context[:pipeline]) Rails.cache.fetch(cache_key) do cacheless_render(text, context) end @@ -32,25 +32,21 @@ module Gitlab end end - # Provide autoload paths for filters to prevent a circular dependency error - autoload :AutolinkFilter, 'gitlab/markdown/autolink_filter' - autoload :CommitRangeReferenceFilter, 'gitlab/markdown/commit_range_reference_filter' - autoload :CommitReferenceFilter, 'gitlab/markdown/commit_reference_filter' - autoload :EmojiFilter, 'gitlab/markdown/emoji_filter' - autoload :ExternalIssueReferenceFilter, 'gitlab/markdown/external_issue_reference_filter' - autoload :ExternalLinkFilter, 'gitlab/markdown/external_link_filter' - autoload :IssueReferenceFilter, 'gitlab/markdown/issue_reference_filter' - autoload :LabelReferenceFilter, 'gitlab/markdown/label_reference_filter' - autoload :MarkdownFilter, 'gitlab/markdown/markdown_filter' - autoload :MergeRequestReferenceFilter, 'gitlab/markdown/merge_request_reference_filter' - autoload :RedactorFilter, 'gitlab/markdown/redactor_filter' - autoload :RelativeLinkFilter, 'gitlab/markdown/relative_link_filter' - autoload :SanitizationFilter, 'gitlab/markdown/sanitization_filter' - autoload :SnippetReferenceFilter, 'gitlab/markdown/snippet_reference_filter' - autoload :SyntaxHighlightFilter, 'gitlab/markdown/syntax_highlight_filter' - autoload :TableOfContentsFilter, 'gitlab/markdown/table_of_contents_filter' - autoload :TaskListFilter, 'gitlab/markdown/task_list_filter' - autoload :UserReferenceFilter, 'gitlab/markdown/user_reference_filter' + def self.render_result(text, context = {}) + pipeline = context[:pipeline] || :full + + html_pipeline = html_pipelines[pipeline] + + transformers = context_transformers[pipeline] + context = transformers.reduce(context) { |context, transformer| transformer.call(context) } + + html_pipeline.call(text, context) + end + + def self.cached?(cache_key, pipeline: :full) + cache_key = full_cache_key(cache_key, pipeline) + cache_key ? Rails.cache.exist?(cache_key) : false + end # Perform post-processing on an HTML String # @@ -66,21 +62,39 @@ module Gitlab # # Returns an HTML-safe String def self.post_process(html, context) - doc = html_pipelines[:post_process].to_document(html, context) + html_pipeline = html_pipelines[:post_process] if context[:xhtml] - doc.to_html(save_with: Nokogiri::XML::Node::SaveOptions::AS_XHTML) + html_pipeline.to_document(html, context).to_html(save_with: Nokogiri::XML::Node::SaveOptions::AS_XHTML) else - doc.to_html + html_pipeline.to_html(html, context) end.html_safe end private - FILTERS = { - plain_markdown: [ - Gitlab::Markdown::MarkdownFilter - ], - gfm: [ + + # Provide autoload paths for filters to prevent a circular dependency error + autoload :AutolinkFilter, 'gitlab/markdown/autolink_filter' + autoload :CommitRangeReferenceFilter, 'gitlab/markdown/commit_range_reference_filter' + autoload :CommitReferenceFilter, 'gitlab/markdown/commit_reference_filter' + autoload :EmojiFilter, 'gitlab/markdown/emoji_filter' + autoload :ExternalIssueReferenceFilter, 'gitlab/markdown/external_issue_reference_filter' + autoload :ExternalLinkFilter, 'gitlab/markdown/external_link_filter' + autoload :IssueReferenceFilter, 'gitlab/markdown/issue_reference_filter' + autoload :LabelReferenceFilter, 'gitlab/markdown/label_reference_filter' + autoload :MarkdownFilter, 'gitlab/markdown/markdown_filter' + autoload :MergeRequestReferenceFilter, 'gitlab/markdown/merge_request_reference_filter' + autoload :RedactorFilter, 'gitlab/markdown/redactor_filter' + autoload :RelativeLinkFilter, 'gitlab/markdown/relative_link_filter' + autoload :SanitizationFilter, 'gitlab/markdown/sanitization_filter' + autoload :SnippetReferenceFilter, 'gitlab/markdown/snippet_reference_filter' + autoload :SyntaxHighlightFilter, 'gitlab/markdown/syntax_highlight_filter' + autoload :TableOfContentsFilter, 'gitlab/markdown/table_of_contents_filter' + autoload :TaskListFilter, 'gitlab/markdown/task_list_filter' + autoload :UserReferenceFilter, 'gitlab/markdown/user_reference_filter' + + def self.gfm_filters + @gfm_filters ||= [ Gitlab::Markdown::SyntaxHighlightFilter, Gitlab::Markdown::SanitizationFilter, @@ -99,71 +113,99 @@ module Gitlab Gitlab::Markdown::LabelReferenceFilter, Gitlab::Markdown::TaskListFilter - ], + ] + end - full: [:plain_markdown, :gfm], - atom: :full, - email: :full, - description: :full, - single_line: :gfm, + def self.all_filters + @all_filters ||= { + plain_markdown: [ + Gitlab::Markdown::MarkdownFilter + ], + gfm: gfm_filters, + + full: [:plain_markdown, :gfm], + atom: :full, + email: :full, + description: :full, + single_line: :gfm, + + asciidoc: [ + Gitlab::Markdown::RelativeLinkFilter + ], + + post_process: [ + Gitlab::Markdown::RelativeLinkFilter, + Gitlab::Markdown::RedactorFilter + ], + + reference_extraction: [ + Gitlab::Markdown::ReferenceGathererFilter + ] + } + end - post_process: [ - Gitlab::Markdown::RelativeLinkFilter, - Gitlab::Markdown::RedactorFilter - ] - } - - CONTEXT_TRANSFORMERS = { - gfm: { - only_path: true, - - # EmojiFilter - asset_host: Gitlab::Application.config.asset_host, - asset_root: Gitlab.config.gitlab.base_url - }, - full: :gfm, - - atom: [ - :full, - { - only_path: false, - xhtml: true - } - ], - email: [ - :full, - { only_path: false } - ], - description: [ - :full, - { - # SanitizationFilter - inline_sanitization: true + def self.all_context_transformers + @all_context_transformers ||= { + gfm: { + only_path: true, + + # EmojiFilter + asset_host: Gitlab::Application.config.asset_host, + asset_root: Gitlab.config.gitlab.base_url + }, + full: :gfm, + + atom: [ + :full, + { + only_path: false, + xhtml: true + } + ], + email: [ + :full, + { + only_path: false + } + ], + description: [ + :full, + { + # SanitizationFilter + inline_sanitization: true + } + ], + single_line: :gfm, + + post_process: { + post_process: true } - ], - single_line: :gfm, - - post_process: { - post_process: true } - } + end + + def self.html_filters + @html_filters ||= Hash.new do |hash, pipeline| + filters = get_filters(pipeline) + hash[pipeline] = filters if pipeline.is_a?(Symbol) + filters + end + end def self.html_pipelines @html_pipelines ||= Hash.new do |hash, pipeline| filters = get_filters(pipeline) - HTML::Pipeline.new(filters) + html_pipeline = HTML::Pipeline.new(filters) + hash[pipeline] = html_pipeline if pipeline.is_a?(Symbol) + html_pipeline end end - def self.cacheless_render(text, context = {}) - pipeline = context[:pipeline] || :full - - html_pipeline = html_pipelines[pipeline] - - transformers = get_context_transformers(pipeline) - context = transformers.reduce(context) { |context, transformer| transformer.call(context) } - - html_pipeline.to_html(text, context) + def self.context_transformers + @context_transformers ||= Hash.new do |hash, pipeline| + transformers = get_context_transformers(pipeline) + hash[pipeline] = transformers if pipeline.is_a?(Symbol) + transformers + end end def self.get_filters(pipelines) @@ -172,9 +214,9 @@ module Gitlab when Class pipeline when Symbol - get_filters(FILTERS[pipeline]) + html_filters[all_filters[pipeline]] when Array - get_filters(pipeline) + html_filters[pipeline] end end.compact end @@ -187,14 +229,26 @@ module Gitlab when Proc pipeline when Symbol - get_context_transformers(CONTEXT_TRANSFORMERS[pipeline]) + context_transformers[all_context_transformers[pipeline]] when Array - get_context_transformers(pipeline) + context_transformers[pipeline] end end.compact end + def self.cacheless_render(text, context = {}) + result = render_result(text, context) + output = result[:output] + if output.respond_to?(:to_html) + output.to_html + else + output.to_s + end + end + def self.full_cache_key(cache_key, pipeline = :full) + return unless cache_key && pipeline.is_a?(Symbol) + ["markdown", *cache_key, pipeline] end end diff --git a/lib/gitlab/reference_extractor.rb b/lib/gitlab/reference_extractor.rb index 2262e5a168a..f34bf7d1d0e 100644 --- a/lib/gitlab/reference_extractor.rb +++ b/lib/gitlab/reference_extractor.rb @@ -14,7 +14,8 @@ module Gitlab def analyze(text, cache_key: nil) references.clear - @document = Gitlab::Markdown.render(text, project: project, cache_key: cache_key) + @pipeline = Gitlab::Markdown.cached?(cache_key, pipeline: :full) ? :full : :plain_markdown + @html = Gitlab::Markdown.render(text, project: project, cache_key: cache_key, pipeline: @pipeline) end %i(user label issue merge_request snippet commit commit_range).each do |type| @@ -45,14 +46,19 @@ module Gitlab filter = Gitlab::Markdown.const_get(klass) context = { - project: project, - current_user: current_user, + pipeline: [:reference_extraction], + + project: project, + current_user: current_user, + + # ReferenceGathererFilter load_lazy_references: false, reference_filter: filter } - result = self.class.pipeline.call(@document, context) + context[:pipeline].unshift(filter) unless @pipeline == :full + result = Gitlab::Markdown.render_result(@html, context) values = result[:references][filter_type].uniq if @load_lazy_references @@ -61,9 +67,5 @@ module Gitlab values end - - def self.pipeline - @pipeline ||= HTML::Pipeline.new([Gitlab::Markdown::ReferenceGathererFilter]) - end end end -- cgit v1.2.1 From 925dc5925b672718ea1f0f08c5bdd492b5ab3e5d Mon Sep 17 00:00:00 2001 From: Douwe Maan Date: Wed, 14 Oct 2015 21:29:35 +0200 Subject: Cache rendered contents of issues, MRs and notes --- lib/gitlab/markdown.rb | 12 +++++++++++- lib/gitlab/reference_extractor.rb | 20 ++++++++++---------- 2 files changed, 21 insertions(+), 11 deletions(-) (limited to 'lib') diff --git a/lib/gitlab/markdown.rb b/lib/gitlab/markdown.rb index d1f22070cba..b9615f95012 100644 --- a/lib/gitlab/markdown.rb +++ b/lib/gitlab/markdown.rb @@ -20,6 +20,8 @@ module Gitlab # # Returns an HTML-safe String def self.render(text, context = {}) + context[:pipeline] ||= :full + cache_key = context.delete(:cache_key) cache_key = full_cache_key(cache_key, context[:pipeline]) @@ -33,7 +35,7 @@ module Gitlab end def self.render_result(text, context = {}) - pipeline = context[:pipeline] || :full + pipeline = context[:pipeline] ||= :full html_pipeline = html_pipelines[pipeline] @@ -129,6 +131,7 @@ module Gitlab atom: :full, email: :full, description: :full, + note: :full, single_line: :gfm, asciidoc: [ @@ -170,6 +173,13 @@ module Gitlab only_path: false } ], + note: [ + :full, + { + # TableOfContentsFilter + no_header_anchors: true + } + ], description: [ :full, { diff --git a/lib/gitlab/reference_extractor.rb b/lib/gitlab/reference_extractor.rb index f34bf7d1d0e..37141efb4c3 100644 --- a/lib/gitlab/reference_extractor.rb +++ b/lib/gitlab/reference_extractor.rb @@ -9,13 +9,12 @@ module Gitlab @project = project @current_user = current_user @load_lazy_references = load_lazy_references - end - def analyze(text, cache_key: nil) - references.clear + @texts = [] + end - @pipeline = Gitlab::Markdown.cached?(cache_key, pipeline: :full) ? :full : :plain_markdown - @html = Gitlab::Markdown.render(text, project: project, cache_key: cache_key, pipeline: @pipeline) + def analyze(text, options = {}) + @texts << Gitlab::Markdown.render(text, options.merge(project: project)) end %i(user label issue merge_request snippet commit commit_range).each do |type| @@ -46,7 +45,7 @@ module Gitlab filter = Gitlab::Markdown.const_get(klass) context = { - pipeline: [:reference_extraction], + pipeline: :reference_extraction, project: project, current_user: current_user, @@ -56,10 +55,11 @@ module Gitlab reference_filter: filter } - context[:pipeline].unshift(filter) unless @pipeline == :full - - result = Gitlab::Markdown.render_result(@html, context) - values = result[:references][filter_type].uniq + values = @texts.flat_map do |html| + text_context = context.dup + result = Gitlab::Markdown.render_result(html, text_context) + result[:references][filter_type] + end.uniq if @load_lazy_references values = Gitlab::Markdown::ReferenceFilter::LazyReference.load(values).uniq -- cgit v1.2.1 From 528d2823e9888ae09aa03b72c2e61cd0b9a39937 Mon Sep 17 00:00:00 2001 From: Douwe Maan Date: Wed, 14 Oct 2015 21:52:41 +0200 Subject: Remove unused Gitlab::Markdown#cached? method --- lib/gitlab/markdown.rb | 5 ----- 1 file changed, 5 deletions(-) (limited to 'lib') diff --git a/lib/gitlab/markdown.rb b/lib/gitlab/markdown.rb index b9615f95012..5c4481e6497 100644 --- a/lib/gitlab/markdown.rb +++ b/lib/gitlab/markdown.rb @@ -45,11 +45,6 @@ module Gitlab html_pipeline.call(text, context) end - def self.cached?(cache_key, pipeline: :full) - cache_key = full_cache_key(cache_key, pipeline) - cache_key ? Rails.cache.exist?(cache_key) : false - end - # Perform post-processing on an HTML String # # This method is used to perform state-dependent changes to a String of -- cgit v1.2.1 From abbca6151d66ebc227c883af35cd01cf4d7e24eb Mon Sep 17 00:00:00 2001 From: Douwe Maan Date: Wed, 21 Oct 2015 12:18:23 +0200 Subject: Make pipelines actually make sense --- lib/gitlab/markdown.rb | 216 ++++----------------- lib/gitlab/markdown/asciidoc_pipeline.rb | 13 ++ lib/gitlab/markdown/atom_pipeline.rb | 14 ++ lib/gitlab/markdown/combined_pipeline.rb | 25 +++ lib/gitlab/markdown/description_pipeline.rb | 14 ++ lib/gitlab/markdown/email_pipeline.rb | 13 ++ lib/gitlab/markdown/full_pipeline.rb | 9 + lib/gitlab/markdown/gfm_pipeline.rb | 41 ++++ lib/gitlab/markdown/note_pipeline.rb | 14 ++ lib/gitlab/markdown/pipeline.rb | 29 +++ lib/gitlab/markdown/plain_markdown_pipeline.rb | 13 ++ lib/gitlab/markdown/post_process_pipeline.rb | 20 ++ .../markdown/reference_extraction_pipeline.rb | 13 ++ lib/gitlab/markdown/single_line_pipeline.rb | 9 + 14 files changed, 265 insertions(+), 178 deletions(-) create mode 100644 lib/gitlab/markdown/asciidoc_pipeline.rb create mode 100644 lib/gitlab/markdown/atom_pipeline.rb create mode 100644 lib/gitlab/markdown/combined_pipeline.rb create mode 100644 lib/gitlab/markdown/description_pipeline.rb create mode 100644 lib/gitlab/markdown/email_pipeline.rb create mode 100644 lib/gitlab/markdown/full_pipeline.rb create mode 100644 lib/gitlab/markdown/gfm_pipeline.rb create mode 100644 lib/gitlab/markdown/note_pipeline.rb create mode 100644 lib/gitlab/markdown/pipeline.rb create mode 100644 lib/gitlab/markdown/plain_markdown_pipeline.rb create mode 100644 lib/gitlab/markdown/post_process_pipeline.rb create mode 100644 lib/gitlab/markdown/reference_extraction_pipeline.rb create mode 100644 lib/gitlab/markdown/single_line_pipeline.rb (limited to 'lib') diff --git a/lib/gitlab/markdown.rb b/lib/gitlab/markdown.rb index 5c4481e6497..3ae4ccfacb6 100644 --- a/lib/gitlab/markdown.rb +++ b/lib/gitlab/markdown.rb @@ -35,14 +35,8 @@ module Gitlab end def self.render_result(text, context = {}) - pipeline = context[:pipeline] ||= :full - - html_pipeline = html_pipelines[pipeline] - - transformers = context_transformers[pipeline] - context = transformers.reduce(context) { |context, transformer| transformer.call(context) } - - html_pipeline.call(text, context) + pipeline_type = context[:pipeline] ||= :full + pipeline_by_type(pipeline_type).call(text, context) end # Perform post-processing on an HTML String @@ -59,17 +53,37 @@ module Gitlab # # Returns an HTML-safe String def self.post_process(html, context) - html_pipeline = html_pipelines[:post_process] + pipeline = pipeline_by_type(:post_process) if context[:xhtml] - html_pipeline.to_document(html, context).to_html(save_with: Nokogiri::XML::Node::SaveOptions::AS_XHTML) + pipeline.to_document(html, context).to_html(save_with: Nokogiri::XML::Node::SaveOptions::AS_XHTML) else - html_pipeline.to_html(html, context) + pipeline.to_html(html, context) end.html_safe end private + def self.cacheless_render(text, context = {}) + result = render_result(text, context) + output = result[:output] + if output.respond_to?(:to_html) + output.to_html + else + output.to_s + end + end + + def self.full_cache_key(cache_key, pipeline = :full) + return unless cache_key + + ["markdown", *cache_key, pipeline] + end + + def self.pipeline_by_type(pipeline_type) + const_get("#{pipeline_type.to_s.camelize}Pipeline") + end + # Provide autoload paths for filters to prevent a circular dependency error autoload :AutolinkFilter, 'gitlab/markdown/autolink_filter' autoload :CommitRangeReferenceFilter, 'gitlab/markdown/commit_range_reference_filter' @@ -91,172 +105,18 @@ module Gitlab autoload :UserReferenceFilter, 'gitlab/markdown/user_reference_filter' autoload :UploadLinkFilter, 'gitlab/markdown/upload_link_filter' - def self.gfm_filters - @gfm_filters ||= [ - Gitlab::Markdown::SyntaxHighlightFilter, - Gitlab::Markdown::SanitizationFilter, - - Gitlab::Markdown::UploadLinkFilter, - Gitlab::Markdown::EmojiFilter, - Gitlab::Markdown::TableOfContentsFilter, - Gitlab::Markdown::AutolinkFilter, - Gitlab::Markdown::ExternalLinkFilter, - - Gitlab::Markdown::UserReferenceFilter, - Gitlab::Markdown::IssueReferenceFilter, - Gitlab::Markdown::ExternalIssueReferenceFilter, - Gitlab::Markdown::MergeRequestReferenceFilter, - Gitlab::Markdown::SnippetReferenceFilter, - Gitlab::Markdown::CommitRangeReferenceFilter, - Gitlab::Markdown::CommitReferenceFilter, - Gitlab::Markdown::LabelReferenceFilter, - - Gitlab::Markdown::TaskListFilter - ] - end - - def self.all_filters - @all_filters ||= { - plain_markdown: [ - Gitlab::Markdown::MarkdownFilter - ], - gfm: gfm_filters, - - full: [:plain_markdown, :gfm], - atom: :full, - email: :full, - description: :full, - note: :full, - single_line: :gfm, - - asciidoc: [ - Gitlab::Markdown::RelativeLinkFilter - ], - - post_process: [ - Gitlab::Markdown::RelativeLinkFilter, - Gitlab::Markdown::RedactorFilter - ], - - reference_extraction: [ - Gitlab::Markdown::ReferenceGathererFilter - ] - } - end - - def self.all_context_transformers - @all_context_transformers ||= { - gfm: { - only_path: true, - - # EmojiFilter - asset_host: Gitlab::Application.config.asset_host, - asset_root: Gitlab.config.gitlab.base_url - }, - full: :gfm, - - atom: [ - :full, - { - only_path: false, - xhtml: true - } - ], - email: [ - :full, - { - only_path: false - } - ], - note: [ - :full, - { - # TableOfContentsFilter - no_header_anchors: true - } - ], - description: [ - :full, - { - # SanitizationFilter - inline_sanitization: true - } - ], - single_line: :gfm, - - post_process: { - post_process: true - } - } - end - - def self.html_filters - @html_filters ||= Hash.new do |hash, pipeline| - filters = get_filters(pipeline) - hash[pipeline] = filters if pipeline.is_a?(Symbol) - filters - end - end - - def self.html_pipelines - @html_pipelines ||= Hash.new do |hash, pipeline| - filters = get_filters(pipeline) - html_pipeline = HTML::Pipeline.new(filters) - hash[pipeline] = html_pipeline if pipeline.is_a?(Symbol) - html_pipeline - end - end - - def self.context_transformers - @context_transformers ||= Hash.new do |hash, pipeline| - transformers = get_context_transformers(pipeline) - hash[pipeline] = transformers if pipeline.is_a?(Symbol) - transformers - end - end - - def self.get_filters(pipelines) - Array.wrap(pipelines).flat_map do |pipeline| - case pipeline - when Class - pipeline - when Symbol - html_filters[all_filters[pipeline]] - when Array - html_filters[pipeline] - end - end.compact - end - - def self.get_context_transformers(pipelines) - Array.wrap(pipelines).flat_map do |pipeline| - case pipeline - when Hash - ->(context) { context.merge(pipeline) } - when Proc - pipeline - when Symbol - context_transformers[all_context_transformers[pipeline]] - when Array - context_transformers[pipeline] - end - end.compact - end - - def self.cacheless_render(text, context = {}) - result = render_result(text, context) - output = result[:output] - if output.respond_to?(:to_html) - output.to_html - else - output.to_s - end - end - - def self.full_cache_key(cache_key, pipeline = :full) - return unless cache_key && pipeline.is_a?(Symbol) - - ["markdown", *cache_key, pipeline] - end + autoload :AsciidocPipeline, 'gitlab/markdown/asciidoc_pipeline' + autoload :AtomPipeline, 'gitlab/markdown/atom_pipeline' + autoload :CombinedPipeline, 'gitlab/markdown/combined_pipeline' + autoload :DescriptionPipeline, 'gitlab/markdown/description_pipeline' + autoload :EmailPipeline, 'gitlab/markdown/email_pipeline' + autoload :FullPipeline, 'gitlab/markdown/full_pipeline' + autoload :GfmPipeline, 'gitlab/markdown/gfm_pipeline' + autoload :NotePipeline, 'gitlab/markdown/note_pipeline' + autoload :Pipeline, 'gitlab/markdown/pipeline' + autoload :PlainMarkdownPipeline, 'gitlab/markdown/plain_markdown_pipeline' + autoload :PostProcessPipeline, 'gitlab/markdown/post_process_pipeline' + autoload :ReferenceExtractionPipeline, 'gitlab/markdown/reference_extraction_pipeline' + autoload :SingleLinePipeline, 'gitlab/markdown/single_line_pipeline' end end diff --git a/lib/gitlab/markdown/asciidoc_pipeline.rb b/lib/gitlab/markdown/asciidoc_pipeline.rb new file mode 100644 index 00000000000..6829b4acb95 --- /dev/null +++ b/lib/gitlab/markdown/asciidoc_pipeline.rb @@ -0,0 +1,13 @@ +require 'gitlab/markdown' + +module Gitlab + module Markdown + class AsciidocPipeline < Pipeline + def self.filters + [ + Gitlab::Markdown::RelativeLinkFilter + ] + end + end + end +end diff --git a/lib/gitlab/markdown/atom_pipeline.rb b/lib/gitlab/markdown/atom_pipeline.rb new file mode 100644 index 00000000000..e151f8f5e5a --- /dev/null +++ b/lib/gitlab/markdown/atom_pipeline.rb @@ -0,0 +1,14 @@ +require 'gitlab/markdown' + +module Gitlab + module Markdown + class AtomPipeline < FullPipeline + def self.transform_context(context) + super(context).merge( + only_path: false, + xhtml: true + ) + end + end + end +end diff --git a/lib/gitlab/markdown/combined_pipeline.rb b/lib/gitlab/markdown/combined_pipeline.rb new file mode 100644 index 00000000000..a3d717550f5 --- /dev/null +++ b/lib/gitlab/markdown/combined_pipeline.rb @@ -0,0 +1,25 @@ +require 'gitlab/markdown' + +module Gitlab + module Markdown + module CombinedPipeline + def self.new(*pipelines) + Class.new(Pipeline) do + const_set :PIPELINES, pipelines + + def self.filters + pipelines.flat_map(&:filters) + end + + def self.transform_context(context) + pipelines.reduce(context) { |context, pipeline| pipeline.transform_context(context) } + end + + def self.pipelines + self::PIPELINES + end + end + end + end + end +end diff --git a/lib/gitlab/markdown/description_pipeline.rb b/lib/gitlab/markdown/description_pipeline.rb new file mode 100644 index 00000000000..76f6948af8f --- /dev/null +++ b/lib/gitlab/markdown/description_pipeline.rb @@ -0,0 +1,14 @@ +require 'gitlab/markdown' + +module Gitlab + module Markdown + class DescriptionPipeline < FullPipeline + def self.transform_context(context) + super(context).merge( + # SanitizationFilter + inline_sanitization: true + ) + end + end + end +end diff --git a/lib/gitlab/markdown/email_pipeline.rb b/lib/gitlab/markdown/email_pipeline.rb new file mode 100644 index 00000000000..b88cb790270 --- /dev/null +++ b/lib/gitlab/markdown/email_pipeline.rb @@ -0,0 +1,13 @@ +require 'gitlab/markdown' + +module Gitlab + module Markdown + class EmailPipeline < FullPipeline + def self.transform_context(context) + super(context).merge( + only_path: false + ) + end + end + end +end diff --git a/lib/gitlab/markdown/full_pipeline.rb b/lib/gitlab/markdown/full_pipeline.rb new file mode 100644 index 00000000000..553e9367c1c --- /dev/null +++ b/lib/gitlab/markdown/full_pipeline.rb @@ -0,0 +1,9 @@ +require 'gitlab/markdown' + +module Gitlab + module Markdown + class FullPipeline < CombinedPipeline.new(PlainMarkdownPipeline, GfmPipeline) + + end + end +end diff --git a/lib/gitlab/markdown/gfm_pipeline.rb b/lib/gitlab/markdown/gfm_pipeline.rb new file mode 100644 index 00000000000..ca90bd75d77 --- /dev/null +++ b/lib/gitlab/markdown/gfm_pipeline.rb @@ -0,0 +1,41 @@ +require 'gitlab/markdown' + +module Gitlab + module Markdown + class GfmPipeline < Pipeline + def self.filters + @filters ||= [ + Gitlab::Markdown::SyntaxHighlightFilter, + Gitlab::Markdown::SanitizationFilter, + + Gitlab::Markdown::UploadLinkFilter, + Gitlab::Markdown::EmojiFilter, + Gitlab::Markdown::TableOfContentsFilter, + Gitlab::Markdown::AutolinkFilter, + Gitlab::Markdown::ExternalLinkFilter, + + Gitlab::Markdown::UserReferenceFilter, + Gitlab::Markdown::IssueReferenceFilter, + Gitlab::Markdown::ExternalIssueReferenceFilter, + Gitlab::Markdown::MergeRequestReferenceFilter, + Gitlab::Markdown::SnippetReferenceFilter, + Gitlab::Markdown::CommitRangeReferenceFilter, + Gitlab::Markdown::CommitReferenceFilter, + Gitlab::Markdown::LabelReferenceFilter, + + Gitlab::Markdown::TaskListFilter + ] + end + + def self.transform_context(context) + context.merge( + only_path: true, + + # EmojiFilter + asset_host: Gitlab::Application.config.asset_host, + asset_root: Gitlab.config.gitlab.base_url + ) + end + end + end +end diff --git a/lib/gitlab/markdown/note_pipeline.rb b/lib/gitlab/markdown/note_pipeline.rb new file mode 100644 index 00000000000..a8bf5f42d8e --- /dev/null +++ b/lib/gitlab/markdown/note_pipeline.rb @@ -0,0 +1,14 @@ +require 'gitlab/markdown' + +module Gitlab + module Markdown + class NotePipeline < FullPipeline + def self.transform_context(context) + super(context).merge( + # TableOfContentsFilter + no_header_anchors: true + ) + end + end + end +end diff --git a/lib/gitlab/markdown/pipeline.rb b/lib/gitlab/markdown/pipeline.rb new file mode 100644 index 00000000000..3c0b676a073 --- /dev/null +++ b/lib/gitlab/markdown/pipeline.rb @@ -0,0 +1,29 @@ +require 'gitlab/markdown' + +module Gitlab + module Markdown + class Pipeline + def self.filters + [] + end + + def self.transform_context(context) + context + end + + def self.html_pipeline + @html_pipeline ||= HTML::Pipeline.new(filters) + end + + class << self + %i(call to_document to_html).each do |meth| + define_method(meth) do |text, context| + context = transform_context(context) + + html_pipeline.send(meth, text, context) + end + end + end + end + end +end diff --git a/lib/gitlab/markdown/plain_markdown_pipeline.rb b/lib/gitlab/markdown/plain_markdown_pipeline.rb new file mode 100644 index 00000000000..0abb93f8a03 --- /dev/null +++ b/lib/gitlab/markdown/plain_markdown_pipeline.rb @@ -0,0 +1,13 @@ +require 'gitlab/markdown' + +module Gitlab + module Markdown + class PlainMarkdownPipeline < Pipeline + def self.filters + [ + Gitlab::Markdown::MarkdownFilter + ] + end + end + end +end diff --git a/lib/gitlab/markdown/post_process_pipeline.rb b/lib/gitlab/markdown/post_process_pipeline.rb new file mode 100644 index 00000000000..60cc32f490e --- /dev/null +++ b/lib/gitlab/markdown/post_process_pipeline.rb @@ -0,0 +1,20 @@ +require 'gitlab/markdown' + +module Gitlab + module Markdown + class PostProcessPipeline < Pipeline + def self.filters + [ + Gitlab::Markdown::RelativeLinkFilter, + Gitlab::Markdown::RedactorFilter + ] + end + + def self.transform_context(context) + context.merge( + post_process: true + ) + end + end + end +end diff --git a/lib/gitlab/markdown/reference_extraction_pipeline.rb b/lib/gitlab/markdown/reference_extraction_pipeline.rb new file mode 100644 index 00000000000..a89ab462bac --- /dev/null +++ b/lib/gitlab/markdown/reference_extraction_pipeline.rb @@ -0,0 +1,13 @@ +require 'gitlab/markdown' + +module Gitlab + module Markdown + class ReferenceExtractionPipeline < Pipeline + def self.filters + [ + Gitlab::Markdown::ReferenceGathererFilter + ] + end + end + end +end diff --git a/lib/gitlab/markdown/single_line_pipeline.rb b/lib/gitlab/markdown/single_line_pipeline.rb new file mode 100644 index 00000000000..2f24927b879 --- /dev/null +++ b/lib/gitlab/markdown/single_line_pipeline.rb @@ -0,0 +1,9 @@ +require 'gitlab/markdown' + +module Gitlab + module Markdown + class SingleLinePipeline < GfmPipeline + + end + end +end -- cgit v1.2.1 From f65840bc842b86cd71e70af8b9acdb1a786d9054 Mon Sep 17 00:00:00 2001 From: Douwe Maan Date: Thu, 22 Oct 2015 11:00:34 +0200 Subject: Fix Markdown XHTML context param --- lib/gitlab/markdown.rb | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) (limited to 'lib') diff --git a/lib/gitlab/markdown.rb b/lib/gitlab/markdown.rb index 3ae4ccfacb6..5f1a6c8ceec 100644 --- a/lib/gitlab/markdown.rb +++ b/lib/gitlab/markdown.rb @@ -20,8 +20,6 @@ module Gitlab # # Returns an HTML-safe String def self.render(text, context = {}) - context[:pipeline] ||= :full - cache_key = context.delete(:cache_key) cache_key = full_cache_key(cache_key, context[:pipeline]) @@ -35,8 +33,7 @@ module Gitlab end def self.render_result(text, context = {}) - pipeline_type = context[:pipeline] ||= :full - pipeline_by_type(pipeline_type).call(text, context) + pipeline_by_name(context[:pipeline]).call(text, context) end # Perform post-processing on an HTML String @@ -53,7 +50,10 @@ module Gitlab # # Returns an HTML-safe String def self.post_process(html, context) - pipeline = pipeline_by_type(:post_process) + pipeline = pipeline_by_name(context[:pipeline]) + context = pipeline.transform_context(context) + + pipeline = pipeline_by_name(:post_process) if context[:xhtml] pipeline.to_document(html, context).to_html(save_with: Nokogiri::XML::Node::SaveOptions::AS_XHTML) @@ -74,14 +74,15 @@ module Gitlab end end - def self.full_cache_key(cache_key, pipeline = :full) + def self.full_cache_key(cache_key, pipeline_name) return unless cache_key - + pipeline_name ||= :full ["markdown", *cache_key, pipeline] end - def self.pipeline_by_type(pipeline_type) - const_get("#{pipeline_type.to_s.camelize}Pipeline") + def self.pipeline_by_name(pipeline_name) + pipeline_name ||= :full + const_get("#{pipeline_name.to_s.camelize}Pipeline") end # Provide autoload paths for filters to prevent a circular dependency error -- cgit v1.2.1 From 51ed8fd706802fe0fffe7a2db0b5adc1f2196e50 Mon Sep 17 00:00:00 2001 From: Douwe Maan Date: Thu, 22 Oct 2015 15:40:21 +0200 Subject: Use correct var name --- lib/gitlab/markdown.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'lib') diff --git a/lib/gitlab/markdown.rb b/lib/gitlab/markdown.rb index 5f1a6c8ceec..969607558ef 100644 --- a/lib/gitlab/markdown.rb +++ b/lib/gitlab/markdown.rb @@ -77,7 +77,7 @@ module Gitlab def self.full_cache_key(cache_key, pipeline_name) return unless cache_key pipeline_name ||= :full - ["markdown", *cache_key, pipeline] + ["markdown", *cache_key, pipeline_name] end def self.pipeline_by_name(pipeline_name) -- cgit v1.2.1 From aa4007a597ea6c7de11a8f37de2fd889aa6e6646 Mon Sep 17 00:00:00 2001 From: Douwe Maan Date: Thu, 22 Oct 2015 15:40:36 +0200 Subject: Slight refactoring --- lib/gitlab/reference_extractor.rb | 12 ++---------- 1 file changed, 2 insertions(+), 10 deletions(-) (limited to 'lib') diff --git a/lib/gitlab/reference_extractor.rb b/lib/gitlab/reference_extractor.rb index 69c7a0f4779..e5cafebdbc0 100644 --- a/lib/gitlab/reference_extractor.rb +++ b/lib/gitlab/reference_extractor.rb @@ -11,6 +11,7 @@ module Gitlab @load_lazy_references = load_lazy_references @texts = [] + @references = {} end def analyze(text, options = {}) @@ -19,21 +20,12 @@ module Gitlab %i(user label issue merge_request snippet commit commit_range).each do |type| define_method("#{type}s") do - references[type] + @references[type] ||= pipeline_result(type) end end private - def references - @references ||= Hash.new do |references, type| - type = type.to_sym - next references[type] if references.has_key?(type) - - references[type] = pipeline_result(type) - end - end - # Instantiate and call HTML::Pipeline with a single reference filter type, # returning the result # -- cgit v1.2.1 From e1e67d383e4b56c9e1848aebc175402a71502e82 Mon Sep 17 00:00:00 2001 From: Douwe Maan Date: Thu, 19 Nov 2015 16:18:13 +0100 Subject: Move Markdown filters and pipelines into folders. --- lib/gitlab/markdown.rb | 80 +++++------ lib/gitlab/markdown/asciidoc_pipeline.rb | 13 -- lib/gitlab/markdown/atom_pipeline.rb | 14 -- lib/gitlab/markdown/autolink_filter.rb | 107 -------------- .../markdown/commit_range_reference_filter.rb | 92 ------------ lib/gitlab/markdown/commit_reference_filter.rb | 88 ------------ lib/gitlab/markdown/description_pipeline.rb | 14 -- lib/gitlab/markdown/email_pipeline.rb | 13 -- lib/gitlab/markdown/emoji_filter.rb | 80 ----------- .../markdown/external_issue_reference_filter.rb | 63 --------- lib/gitlab/markdown/external_link_filter.rb | 34 ----- lib/gitlab/markdown/filter/autolink_filter.rb | 107 ++++++++++++++ .../filter/commit_range_reference_filter.rb | 92 ++++++++++++ .../markdown/filter/commit_reference_filter.rb | 88 ++++++++++++ lib/gitlab/markdown/filter/emoji_filter.rb | 80 +++++++++++ .../filter/external_issue_reference_filter.rb | 63 +++++++++ lib/gitlab/markdown/filter/external_link_filter.rb | 34 +++++ .../markdown/filter/issue_reference_filter.rb | 23 +++ .../markdown/filter/label_reference_filter.rb | 87 ++++++++++++ lib/gitlab/markdown/filter/markdown_filter.rb | 39 +++++ .../filter/merge_request_reference_filter.rb | 25 ++++ lib/gitlab/markdown/filter/redactor_filter.rb | 40 ++++++ .../markdown/filter/reference_gatherer_filter.rb | 63 +++++++++ lib/gitlab/markdown/filter/relative_link_filter.rb | 157 +++++++++++++++++++++ lib/gitlab/markdown/filter/sanitization_filter.rb | 99 +++++++++++++ .../markdown/filter/snippet_reference_filter.rb | 25 ++++ .../markdown/filter/syntax_highlight_filter.rb | 45 ++++++ .../markdown/filter/table_of_contents_filter.rb | 63 +++++++++ lib/gitlab/markdown/filter/task_list_filter.rb | 24 ++++ lib/gitlab/markdown/filter/upload_link_filter.rb | 47 ++++++ .../markdown/filter/user_reference_filter.rb | 125 ++++++++++++++++ lib/gitlab/markdown/full_pipeline.rb | 9 -- lib/gitlab/markdown/gfm_pipeline.rb | 41 ------ lib/gitlab/markdown/issue_reference_filter.rb | 23 --- lib/gitlab/markdown/label_reference_filter.rb | 87 ------------ lib/gitlab/markdown/markdown_filter.rb | 39 ----- .../markdown/merge_request_reference_filter.rb | 25 ---- lib/gitlab/markdown/note_pipeline.rb | 14 -- lib/gitlab/markdown/pipeline.rb | 5 + lib/gitlab/markdown/pipeline/asciidoc_pipeline.rb | 13 ++ lib/gitlab/markdown/pipeline/atom_pipeline.rb | 14 ++ .../markdown/pipeline/description_pipeline.rb | 14 ++ lib/gitlab/markdown/pipeline/email_pipeline.rb | 13 ++ lib/gitlab/markdown/pipeline/full_pipeline.rb | 9 ++ lib/gitlab/markdown/pipeline/gfm_pipeline.rb | 41 ++++++ lib/gitlab/markdown/pipeline/note_pipeline.rb | 14 ++ .../markdown/pipeline/plain_markdown_pipeline.rb | 13 ++ .../markdown/pipeline/post_process_pipeline.rb | 20 +++ .../pipeline/reference_extraction_pipeline.rb | 13 ++ .../markdown/pipeline/single_line_pipeline.rb | 9 ++ lib/gitlab/markdown/plain_markdown_pipeline.rb | 13 -- lib/gitlab/markdown/post_process_pipeline.rb | 20 --- lib/gitlab/markdown/redactor_filter.rb | 40 ------ .../markdown/reference_extraction_pipeline.rb | 13 -- lib/gitlab/markdown/reference_filter.rb | 4 + lib/gitlab/markdown/reference_gatherer_filter.rb | 63 --------- lib/gitlab/markdown/relative_link_filter.rb | 157 --------------------- lib/gitlab/markdown/sanitization_filter.rb | 99 ------------- lib/gitlab/markdown/single_line_pipeline.rb | 9 -- lib/gitlab/markdown/snippet_reference_filter.rb | 25 ---- lib/gitlab/markdown/syntax_highlight_filter.rb | 45 ------ lib/gitlab/markdown/table_of_contents_filter.rb | 63 --------- lib/gitlab/markdown/task_list_filter.rb | 24 ---- lib/gitlab/markdown/upload_link_filter.rb | 47 ------ lib/gitlab/markdown/user_reference_filter.rb | 125 ---------------- lib/gitlab/reference_extractor.rb | 3 +- 66 files changed, 1545 insertions(+), 1545 deletions(-) delete mode 100644 lib/gitlab/markdown/asciidoc_pipeline.rb delete mode 100644 lib/gitlab/markdown/atom_pipeline.rb delete mode 100644 lib/gitlab/markdown/autolink_filter.rb delete mode 100644 lib/gitlab/markdown/commit_range_reference_filter.rb delete mode 100644 lib/gitlab/markdown/commit_reference_filter.rb delete mode 100644 lib/gitlab/markdown/description_pipeline.rb delete mode 100644 lib/gitlab/markdown/email_pipeline.rb delete mode 100644 lib/gitlab/markdown/emoji_filter.rb delete mode 100644 lib/gitlab/markdown/external_issue_reference_filter.rb delete mode 100644 lib/gitlab/markdown/external_link_filter.rb create mode 100644 lib/gitlab/markdown/filter/autolink_filter.rb create mode 100644 lib/gitlab/markdown/filter/commit_range_reference_filter.rb create mode 100644 lib/gitlab/markdown/filter/commit_reference_filter.rb create mode 100644 lib/gitlab/markdown/filter/emoji_filter.rb create mode 100644 lib/gitlab/markdown/filter/external_issue_reference_filter.rb create mode 100644 lib/gitlab/markdown/filter/external_link_filter.rb create mode 100644 lib/gitlab/markdown/filter/issue_reference_filter.rb create mode 100644 lib/gitlab/markdown/filter/label_reference_filter.rb create mode 100644 lib/gitlab/markdown/filter/markdown_filter.rb create mode 100644 lib/gitlab/markdown/filter/merge_request_reference_filter.rb create mode 100644 lib/gitlab/markdown/filter/redactor_filter.rb create mode 100644 lib/gitlab/markdown/filter/reference_gatherer_filter.rb create mode 100644 lib/gitlab/markdown/filter/relative_link_filter.rb create mode 100644 lib/gitlab/markdown/filter/sanitization_filter.rb create mode 100644 lib/gitlab/markdown/filter/snippet_reference_filter.rb create mode 100644 lib/gitlab/markdown/filter/syntax_highlight_filter.rb create mode 100644 lib/gitlab/markdown/filter/table_of_contents_filter.rb create mode 100644 lib/gitlab/markdown/filter/task_list_filter.rb create mode 100644 lib/gitlab/markdown/filter/upload_link_filter.rb create mode 100644 lib/gitlab/markdown/filter/user_reference_filter.rb delete mode 100644 lib/gitlab/markdown/full_pipeline.rb delete mode 100644 lib/gitlab/markdown/gfm_pipeline.rb delete mode 100644 lib/gitlab/markdown/issue_reference_filter.rb delete mode 100644 lib/gitlab/markdown/label_reference_filter.rb delete mode 100644 lib/gitlab/markdown/markdown_filter.rb delete mode 100644 lib/gitlab/markdown/merge_request_reference_filter.rb delete mode 100644 lib/gitlab/markdown/note_pipeline.rb create mode 100644 lib/gitlab/markdown/pipeline/asciidoc_pipeline.rb create mode 100644 lib/gitlab/markdown/pipeline/atom_pipeline.rb create mode 100644 lib/gitlab/markdown/pipeline/description_pipeline.rb create mode 100644 lib/gitlab/markdown/pipeline/email_pipeline.rb create mode 100644 lib/gitlab/markdown/pipeline/full_pipeline.rb create mode 100644 lib/gitlab/markdown/pipeline/gfm_pipeline.rb create mode 100644 lib/gitlab/markdown/pipeline/note_pipeline.rb create mode 100644 lib/gitlab/markdown/pipeline/plain_markdown_pipeline.rb create mode 100644 lib/gitlab/markdown/pipeline/post_process_pipeline.rb create mode 100644 lib/gitlab/markdown/pipeline/reference_extraction_pipeline.rb create mode 100644 lib/gitlab/markdown/pipeline/single_line_pipeline.rb delete mode 100644 lib/gitlab/markdown/plain_markdown_pipeline.rb delete mode 100644 lib/gitlab/markdown/post_process_pipeline.rb delete mode 100644 lib/gitlab/markdown/redactor_filter.rb delete mode 100644 lib/gitlab/markdown/reference_extraction_pipeline.rb delete mode 100644 lib/gitlab/markdown/reference_gatherer_filter.rb delete mode 100644 lib/gitlab/markdown/relative_link_filter.rb delete mode 100644 lib/gitlab/markdown/sanitization_filter.rb delete mode 100644 lib/gitlab/markdown/single_line_pipeline.rb delete mode 100644 lib/gitlab/markdown/snippet_reference_filter.rb delete mode 100644 lib/gitlab/markdown/syntax_highlight_filter.rb delete mode 100644 lib/gitlab/markdown/table_of_contents_filter.rb delete mode 100644 lib/gitlab/markdown/task_list_filter.rb delete mode 100644 lib/gitlab/markdown/upload_link_filter.rb delete mode 100644 lib/gitlab/markdown/user_reference_filter.rb (limited to 'lib') diff --git a/lib/gitlab/markdown.rb b/lib/gitlab/markdown.rb index 969607558ef..f4e2cefca51 100644 --- a/lib/gitlab/markdown.rb +++ b/lib/gitlab/markdown.rb @@ -33,7 +33,7 @@ module Gitlab end def self.render_result(text, context = {}) - pipeline_by_name(context[:pipeline]).call(text, context) + Pipeline[context[:pipeline]].call(text, context) end # Perform post-processing on an HTML String @@ -50,11 +50,9 @@ module Gitlab # # Returns an HTML-safe String def self.post_process(html, context) - pipeline = pipeline_by_name(context[:pipeline]) - context = pipeline.transform_context(context) - - pipeline = pipeline_by_name(:post_process) + context = Pipeline[context[:pipeline]].transform_context(context) + pipeline = Pipeline[:post_process] if context[:xhtml] pipeline.to_document(html, context).to_html(save_with: Nokogiri::XML::Node::SaveOptions::AS_XHTML) else @@ -66,6 +64,7 @@ module Gitlab def self.cacheless_render(text, context = {}) result = render_result(text, context) + output = result[:output] if output.respond_to?(:to_html) output.to_html @@ -76,48 +75,41 @@ module Gitlab def self.full_cache_key(cache_key, pipeline_name) return unless cache_key - pipeline_name ||= :full - ["markdown", *cache_key, pipeline_name] - end - - def self.pipeline_by_name(pipeline_name) - pipeline_name ||= :full - const_get("#{pipeline_name.to_s.camelize}Pipeline") + ["markdown", *cache_key, pipeline_name || :full] end # Provide autoload paths for filters to prevent a circular dependency error - autoload :AutolinkFilter, 'gitlab/markdown/autolink_filter' - autoload :CommitRangeReferenceFilter, 'gitlab/markdown/commit_range_reference_filter' - autoload :CommitReferenceFilter, 'gitlab/markdown/commit_reference_filter' - autoload :EmojiFilter, 'gitlab/markdown/emoji_filter' - autoload :ExternalIssueReferenceFilter, 'gitlab/markdown/external_issue_reference_filter' - autoload :ExternalLinkFilter, 'gitlab/markdown/external_link_filter' - autoload :IssueReferenceFilter, 'gitlab/markdown/issue_reference_filter' - autoload :LabelReferenceFilter, 'gitlab/markdown/label_reference_filter' - autoload :MarkdownFilter, 'gitlab/markdown/markdown_filter' - autoload :MergeRequestReferenceFilter, 'gitlab/markdown/merge_request_reference_filter' - autoload :RedactorFilter, 'gitlab/markdown/redactor_filter' - autoload :RelativeLinkFilter, 'gitlab/markdown/relative_link_filter' - autoload :SanitizationFilter, 'gitlab/markdown/sanitization_filter' - autoload :SnippetReferenceFilter, 'gitlab/markdown/snippet_reference_filter' - autoload :SyntaxHighlightFilter, 'gitlab/markdown/syntax_highlight_filter' - autoload :TableOfContentsFilter, 'gitlab/markdown/table_of_contents_filter' - autoload :TaskListFilter, 'gitlab/markdown/task_list_filter' - autoload :UserReferenceFilter, 'gitlab/markdown/user_reference_filter' - autoload :UploadLinkFilter, 'gitlab/markdown/upload_link_filter' + autoload :AutolinkFilter, 'gitlab/markdown/filter/autolink_filter' + autoload :CommitRangeReferenceFilter, 'gitlab/markdown/filter/commit_range_reference_filter' + autoload :CommitReferenceFilter, 'gitlab/markdown/filter/commit_reference_filter' + autoload :EmojiFilter, 'gitlab/markdown/filter/emoji_filter' + autoload :ExternalIssueReferenceFilter, 'gitlab/markdown/filter/external_issue_reference_filter' + autoload :ExternalLinkFilter, 'gitlab/markdown/filter/external_link_filter' + autoload :IssueReferenceFilter, 'gitlab/markdown/filter/issue_reference_filter' + autoload :LabelReferenceFilter, 'gitlab/markdown/filter/label_reference_filter' + autoload :MarkdownFilter, 'gitlab/markdown/filter/markdown_filter' + autoload :MergeRequestReferenceFilter, 'gitlab/markdown/filter/merge_request_reference_filter' + autoload :RedactorFilter, 'gitlab/markdown/filter/redactor_filter' + autoload :ReferenceGathererFilter, 'gitlab/markdown/filter/reference_gatherer_filter' + autoload :RelativeLinkFilter, 'gitlab/markdown/filter/relative_link_filter' + autoload :SanitizationFilter, 'gitlab/markdown/filter/sanitization_filter' + autoload :SnippetReferenceFilter, 'gitlab/markdown/filter/snippet_reference_filter' + autoload :SyntaxHighlightFilter, 'gitlab/markdown/filter/syntax_highlight_filter' + autoload :TableOfContentsFilter, 'gitlab/markdown/filter/table_of_contents_filter' + autoload :TaskListFilter, 'gitlab/markdown/filter/task_list_filter' + autoload :UserReferenceFilter, 'gitlab/markdown/filter/user_reference_filter' + autoload :UploadLinkFilter, 'gitlab/markdown/filter/upload_link_filter' - autoload :AsciidocPipeline, 'gitlab/markdown/asciidoc_pipeline' - autoload :AtomPipeline, 'gitlab/markdown/atom_pipeline' - autoload :CombinedPipeline, 'gitlab/markdown/combined_pipeline' - autoload :DescriptionPipeline, 'gitlab/markdown/description_pipeline' - autoload :EmailPipeline, 'gitlab/markdown/email_pipeline' - autoload :FullPipeline, 'gitlab/markdown/full_pipeline' - autoload :GfmPipeline, 'gitlab/markdown/gfm_pipeline' - autoload :NotePipeline, 'gitlab/markdown/note_pipeline' - autoload :Pipeline, 'gitlab/markdown/pipeline' - autoload :PlainMarkdownPipeline, 'gitlab/markdown/plain_markdown_pipeline' - autoload :PostProcessPipeline, 'gitlab/markdown/post_process_pipeline' - autoload :ReferenceExtractionPipeline, 'gitlab/markdown/reference_extraction_pipeline' - autoload :SingleLinePipeline, 'gitlab/markdown/single_line_pipeline' + autoload :AsciidocPipeline, 'gitlab/markdown/pipeline/asciidoc_pipeline' + autoload :AtomPipeline, 'gitlab/markdown/pipeline/atom_pipeline' + autoload :DescriptionPipeline, 'gitlab/markdown/pipeline/description_pipeline' + autoload :EmailPipeline, 'gitlab/markdown/pipeline/email_pipeline' + autoload :FullPipeline, 'gitlab/markdown/pipeline/full_pipeline' + autoload :GfmPipeline, 'gitlab/markdown/pipeline/gfm_pipeline' + autoload :NotePipeline, 'gitlab/markdown/pipeline/note_pipeline' + autoload :PlainMarkdownPipeline, 'gitlab/markdown/pipeline/plain_markdown_pipeline' + autoload :PostProcessPipeline, 'gitlab/markdown/pipeline/post_process_pipeline' + autoload :ReferenceExtractionPipeline, 'gitlab/markdown/pipeline/reference_extraction_pipeline' + autoload :SingleLinePipeline, 'gitlab/markdown/pipeline/single_line_pipeline' end end diff --git a/lib/gitlab/markdown/asciidoc_pipeline.rb b/lib/gitlab/markdown/asciidoc_pipeline.rb deleted file mode 100644 index 6829b4acb95..00000000000 --- a/lib/gitlab/markdown/asciidoc_pipeline.rb +++ /dev/null @@ -1,13 +0,0 @@ -require 'gitlab/markdown' - -module Gitlab - module Markdown - class AsciidocPipeline < Pipeline - def self.filters - [ - Gitlab::Markdown::RelativeLinkFilter - ] - end - end - end -end diff --git a/lib/gitlab/markdown/atom_pipeline.rb b/lib/gitlab/markdown/atom_pipeline.rb deleted file mode 100644 index e151f8f5e5a..00000000000 --- a/lib/gitlab/markdown/atom_pipeline.rb +++ /dev/null @@ -1,14 +0,0 @@ -require 'gitlab/markdown' - -module Gitlab - module Markdown - class AtomPipeline < FullPipeline - def self.transform_context(context) - super(context).merge( - only_path: false, - xhtml: true - ) - end - end - end -end diff --git a/lib/gitlab/markdown/autolink_filter.rb b/lib/gitlab/markdown/autolink_filter.rb deleted file mode 100644 index c37c3bc55bf..00000000000 --- a/lib/gitlab/markdown/autolink_filter.rb +++ /dev/null @@ -1,107 +0,0 @@ -require 'gitlab/markdown' -require 'html/pipeline/filter' -require 'uri' - -module Gitlab - module Markdown - # HTML Filter for auto-linking URLs in HTML. - # - # Based on HTML::Pipeline::AutolinkFilter - # - # Context options: - # :autolink - Boolean, skips all processing done by this filter when false - # :link_attr - Hash of attributes for the generated links - # - class AutolinkFilter < HTML::Pipeline::Filter - include ActionView::Helpers::TagHelper - - # Pattern to match text that should be autolinked. - # - # A URI scheme begins with a letter and may contain letters, numbers, - # plus, period and hyphen. Schemes are case-insensitive but we're being - # picky here and allowing only lowercase for autolinks. - # - # See http://en.wikipedia.org/wiki/URI_scheme - # - # The negative lookbehind ensures that users can paste a URL followed by a - # period or comma for punctuation without those characters being included - # in the generated link. - # - # Rubular: http://rubular.com/r/cxjPyZc7Sb - LINK_PATTERN = %r{([a-z][a-z0-9\+\.-]+://\S+)(?#{commit_range}" - # end - # - # text - String text to search. - # - # Yields the String match, the String commit range, and an optional String - # of the external project reference. - # - # Returns a String replaced with the return of the block. - def self.references_in(text) - text.gsub(CommitRange.reference_pattern) do |match| - yield match, $~[:commit_range], $~[:project] - end - end - - def self.referenced_by(node) - project = Project.find(node.attr("data-project")) rescue nil - return unless project - - id = node.attr("data-commit-range") - range = CommitRange.new(id, project) - - return unless range.valid_commits? - - { commit_range: range } - end - - def initialize(*args) - super - - @commit_map = {} - end - - def call - replace_text_nodes_matching(CommitRange.reference_pattern) do |content| - commit_range_link_filter(content) - end - end - - # Replace commit range references in text with links to compare the commit - # ranges. - # - # text - String text to replace references in. - # - # Returns a String with commit range references replaced with links. All - # links have `gfm` and `gfm-commit_range` class names attached for - # styling. - def commit_range_link_filter(text) - self.class.references_in(text) do |match, id, project_ref| - project = self.project_from_ref(project_ref) - - range = CommitRange.new(id, project) - - if range.valid_commits? - url = url_for_commit_range(project, range) - - title = range.reference_title - klass = reference_class(:commit_range) - data = data_attribute(project: project.id, commit_range: id) - - project_ref += '@' if project_ref - - %(#{project_ref}#{range}) - else - match - end - end - end - - def url_for_commit_range(project, range) - h = Gitlab::Application.routes.url_helpers - h.namespace_project_compare_url(project.namespace, project, - range.to_param.merge(only_path: context[:only_path])) - end - end - end -end diff --git a/lib/gitlab/markdown/commit_reference_filter.rb b/lib/gitlab/markdown/commit_reference_filter.rb deleted file mode 100644 index 8cdbeb1f9cf..00000000000 --- a/lib/gitlab/markdown/commit_reference_filter.rb +++ /dev/null @@ -1,88 +0,0 @@ -require 'gitlab/markdown' - -module Gitlab - module Markdown - # HTML filter that replaces commit references with links. - # - # This filter supports cross-project references. - class CommitReferenceFilter < ReferenceFilter - include CrossProjectReference - - # Public: Find commit references in text - # - # CommitReferenceFilter.references_in(text) do |match, commit, project_ref| - # "#{commit}" - # end - # - # text - String text to search. - # - # Yields the String match, the String commit identifier, and an optional - # String of the external project reference. - # - # Returns a String replaced with the return of the block. - def self.references_in(text) - text.gsub(Commit.reference_pattern) do |match| - yield match, $~[:commit], $~[:project] - end - end - - def self.referenced_by(node) - project = Project.find(node.attr("data-project")) rescue nil - return unless project - - id = node.attr("data-commit") - commit = commit_from_ref(project, id) - - return unless commit - - { commit: commit } - end - - def call - replace_text_nodes_matching(Commit.reference_pattern) do |content| - commit_link_filter(content) - end - end - - # Replace commit references in text with links to the commit specified. - # - # text - String text to replace references in. - # - # Returns a String with commit references replaced with links. All links - # have `gfm` and `gfm-commit` class names attached for styling. - def commit_link_filter(text) - self.class.references_in(text) do |match, id, project_ref| - project = self.project_from_ref(project_ref) - - if commit = self.class.commit_from_ref(project, id) - url = url_for_commit(project, commit) - - title = escape_once(commit.link_title) - klass = reference_class(:commit) - data = data_attribute(project: project.id, commit: id) - - project_ref += '@' if project_ref - - %(#{project_ref}#{commit.short_id}) - else - match - end - end - end - - def self.commit_from_ref(project, id) - if project && project.valid_repo? - project.commit(id) - end - end - - def url_for_commit(project, commit) - h = Gitlab::Application.routes.url_helpers - h.namespace_project_commit_url(project.namespace, project, commit, - only_path: context[:only_path]) - end - end - end -end diff --git a/lib/gitlab/markdown/description_pipeline.rb b/lib/gitlab/markdown/description_pipeline.rb deleted file mode 100644 index 76f6948af8f..00000000000 --- a/lib/gitlab/markdown/description_pipeline.rb +++ /dev/null @@ -1,14 +0,0 @@ -require 'gitlab/markdown' - -module Gitlab - module Markdown - class DescriptionPipeline < FullPipeline - def self.transform_context(context) - super(context).merge( - # SanitizationFilter - inline_sanitization: true - ) - end - end - end -end diff --git a/lib/gitlab/markdown/email_pipeline.rb b/lib/gitlab/markdown/email_pipeline.rb deleted file mode 100644 index b88cb790270..00000000000 --- a/lib/gitlab/markdown/email_pipeline.rb +++ /dev/null @@ -1,13 +0,0 @@ -require 'gitlab/markdown' - -module Gitlab - module Markdown - class EmailPipeline < FullPipeline - def self.transform_context(context) - super(context).merge( - only_path: false - ) - end - end - end -end diff --git a/lib/gitlab/markdown/emoji_filter.rb b/lib/gitlab/markdown/emoji_filter.rb deleted file mode 100644 index da10e4d3760..00000000000 --- a/lib/gitlab/markdown/emoji_filter.rb +++ /dev/null @@ -1,80 +0,0 @@ -require 'action_controller' -require 'gitlab/markdown' -require 'gitlab_emoji' -require 'html/pipeline/filter' - -module Gitlab - module Markdown - # HTML filter that replaces :emoji: with images. - # - # Based on HTML::Pipeline::EmojiFilter - # - # Context options: - # :asset_root - # :asset_host - class EmojiFilter < HTML::Pipeline::Filter - IGNORED_ANCESTOR_TAGS = %w(pre code tt).to_set - - def call - search_text_nodes(doc).each do |node| - content = node.to_html - next unless content.include?(':') - next if has_ancestor?(node, IGNORED_ANCESTOR_TAGS) - - html = emoji_image_filter(content) - - next if html == content - - node.replace(html) - end - - doc - end - - # Replace :emoji: with corresponding images. - # - # text - String text to replace :emoji: in. - # - # Returns a String with :emoji: replaced with images. - def emoji_image_filter(text) - text.gsub(emoji_pattern) do |match| - name = $1 - ":#{name}:" - end - end - - private - - def emoji_url(name) - emoji_path = "emoji/#{emoji_filename(name)}" - if context[:asset_host] - # Asset host is specified. - url_to_image(emoji_path) - elsif context[:asset_root] - # Gitlab url is specified - File.join(context[:asset_root], url_to_image(emoji_path)) - else - # All other cases - url_to_image(emoji_path) - end - end - - def url_to_image(image) - ActionController::Base.helpers.url_to_image(image) - end - - # Build a regexp that matches all valid :emoji: names. - def self.emoji_pattern - @emoji_pattern ||= /:(#{Emoji.emojis_names.map { |name| Regexp.escape(name) }.join('|')}):/ - end - - def emoji_pattern - self.class.emoji_pattern - end - - def emoji_filename(name) - "#{Emoji.emoji_filename(name)}.png" - end - end - end -end diff --git a/lib/gitlab/markdown/external_issue_reference_filter.rb b/lib/gitlab/markdown/external_issue_reference_filter.rb deleted file mode 100644 index 8f86f13976a..00000000000 --- a/lib/gitlab/markdown/external_issue_reference_filter.rb +++ /dev/null @@ -1,63 +0,0 @@ -require 'gitlab/markdown' - -module Gitlab - module Markdown - # HTML filter that replaces external issue tracker references with links. - # References are ignored if the project doesn't use an external issue - # tracker. - class ExternalIssueReferenceFilter < ReferenceFilter - # Public: Find `JIRA-123` issue references in text - # - # ExternalIssueReferenceFilter.references_in(text) do |match, issue| - # "##{issue}" - # end - # - # text - String text to search. - # - # Yields the String match and the String issue reference. - # - # Returns a String replaced with the return of the block. - def self.references_in(text) - text.gsub(ExternalIssue.reference_pattern) do |match| - yield match, $~[:issue] - end - end - - def call - # Early return if the project isn't using an external tracker - return doc if project.nil? || project.default_issues_tracker? - - replace_text_nodes_matching(ExternalIssue.reference_pattern) do |content| - issue_link_filter(content) - end - end - - # Replace `JIRA-123` issue references in text with links to the referenced - # issue's details page. - # - # text - String text to replace references in. - # - # Returns a String with `JIRA-123` references replaced with links. All - # links have `gfm` and `gfm-issue` class names attached for styling. - def issue_link_filter(text) - project = context[:project] - - self.class.references_in(text) do |match, issue| - url = url_for_issue(issue, project, only_path: context[:only_path]) - - title = escape_once("Issue in #{project.external_issue_tracker.title}") - klass = reference_class(:issue) - data = data_attribute(project: project.id) - - %(#{match}) - end - end - - def url_for_issue(*args) - IssuesHelper.url_for_issue(*args) - end - end - end -end diff --git a/lib/gitlab/markdown/external_link_filter.rb b/lib/gitlab/markdown/external_link_filter.rb deleted file mode 100644 index 29e51b6ade6..00000000000 --- a/lib/gitlab/markdown/external_link_filter.rb +++ /dev/null @@ -1,34 +0,0 @@ -require 'gitlab/markdown' -require 'html/pipeline/filter' - -module Gitlab - module Markdown - # HTML Filter to add a `rel="nofollow"` attribute to external links - # - class ExternalLinkFilter < HTML::Pipeline::Filter - def call - doc.search('a').each do |node| - next unless node.has_attribute?('href') - - link = node.attribute('href').value - - # Skip non-HTTP(S) links - next unless link.start_with?('http') - - # Skip internal links - next if link.start_with?(internal_url) - - node.set_attribute('rel', 'nofollow') - end - - doc - end - - private - - def internal_url - @internal_url ||= Gitlab.config.gitlab.url - end - end - end -end diff --git a/lib/gitlab/markdown/filter/autolink_filter.rb b/lib/gitlab/markdown/filter/autolink_filter.rb new file mode 100644 index 00000000000..c37c3bc55bf --- /dev/null +++ b/lib/gitlab/markdown/filter/autolink_filter.rb @@ -0,0 +1,107 @@ +require 'gitlab/markdown' +require 'html/pipeline/filter' +require 'uri' + +module Gitlab + module Markdown + # HTML Filter for auto-linking URLs in HTML. + # + # Based on HTML::Pipeline::AutolinkFilter + # + # Context options: + # :autolink - Boolean, skips all processing done by this filter when false + # :link_attr - Hash of attributes for the generated links + # + class AutolinkFilter < HTML::Pipeline::Filter + include ActionView::Helpers::TagHelper + + # Pattern to match text that should be autolinked. + # + # A URI scheme begins with a letter and may contain letters, numbers, + # plus, period and hyphen. Schemes are case-insensitive but we're being + # picky here and allowing only lowercase for autolinks. + # + # See http://en.wikipedia.org/wiki/URI_scheme + # + # The negative lookbehind ensures that users can paste a URL followed by a + # period or comma for punctuation without those characters being included + # in the generated link. + # + # Rubular: http://rubular.com/r/cxjPyZc7Sb + LINK_PATTERN = %r{([a-z][a-z0-9\+\.-]+://\S+)(?#{commit_range}" + # end + # + # text - String text to search. + # + # Yields the String match, the String commit range, and an optional String + # of the external project reference. + # + # Returns a String replaced with the return of the block. + def self.references_in(text) + text.gsub(CommitRange.reference_pattern) do |match| + yield match, $~[:commit_range], $~[:project] + end + end + + def self.referenced_by(node) + project = Project.find(node.attr("data-project")) rescue nil + return unless project + + id = node.attr("data-commit-range") + range = CommitRange.new(id, project) + + return unless range.valid_commits? + + { commit_range: range } + end + + def initialize(*args) + super + + @commit_map = {} + end + + def call + replace_text_nodes_matching(CommitRange.reference_pattern) do |content| + commit_range_link_filter(content) + end + end + + # Replace commit range references in text with links to compare the commit + # ranges. + # + # text - String text to replace references in. + # + # Returns a String with commit range references replaced with links. All + # links have `gfm` and `gfm-commit_range` class names attached for + # styling. + def commit_range_link_filter(text) + self.class.references_in(text) do |match, id, project_ref| + project = self.project_from_ref(project_ref) + + range = CommitRange.new(id, project) + + if range.valid_commits? + url = url_for_commit_range(project, range) + + title = range.reference_title + klass = reference_class(:commit_range) + data = data_attribute(project: project.id, commit_range: id) + + project_ref += '@' if project_ref + + %(#{project_ref}#{range}) + else + match + end + end + end + + def url_for_commit_range(project, range) + h = Gitlab::Application.routes.url_helpers + h.namespace_project_compare_url(project.namespace, project, + range.to_param.merge(only_path: context[:only_path])) + end + end + end +end diff --git a/lib/gitlab/markdown/filter/commit_reference_filter.rb b/lib/gitlab/markdown/filter/commit_reference_filter.rb new file mode 100644 index 00000000000..8cdbeb1f9cf --- /dev/null +++ b/lib/gitlab/markdown/filter/commit_reference_filter.rb @@ -0,0 +1,88 @@ +require 'gitlab/markdown' + +module Gitlab + module Markdown + # HTML filter that replaces commit references with links. + # + # This filter supports cross-project references. + class CommitReferenceFilter < ReferenceFilter + include CrossProjectReference + + # Public: Find commit references in text + # + # CommitReferenceFilter.references_in(text) do |match, commit, project_ref| + # "#{commit}" + # end + # + # text - String text to search. + # + # Yields the String match, the String commit identifier, and an optional + # String of the external project reference. + # + # Returns a String replaced with the return of the block. + def self.references_in(text) + text.gsub(Commit.reference_pattern) do |match| + yield match, $~[:commit], $~[:project] + end + end + + def self.referenced_by(node) + project = Project.find(node.attr("data-project")) rescue nil + return unless project + + id = node.attr("data-commit") + commit = commit_from_ref(project, id) + + return unless commit + + { commit: commit } + end + + def call + replace_text_nodes_matching(Commit.reference_pattern) do |content| + commit_link_filter(content) + end + end + + # Replace commit references in text with links to the commit specified. + # + # text - String text to replace references in. + # + # Returns a String with commit references replaced with links. All links + # have `gfm` and `gfm-commit` class names attached for styling. + def commit_link_filter(text) + self.class.references_in(text) do |match, id, project_ref| + project = self.project_from_ref(project_ref) + + if commit = self.class.commit_from_ref(project, id) + url = url_for_commit(project, commit) + + title = escape_once(commit.link_title) + klass = reference_class(:commit) + data = data_attribute(project: project.id, commit: id) + + project_ref += '@' if project_ref + + %(#{project_ref}#{commit.short_id}) + else + match + end + end + end + + def self.commit_from_ref(project, id) + if project && project.valid_repo? + project.commit(id) + end + end + + def url_for_commit(project, commit) + h = Gitlab::Application.routes.url_helpers + h.namespace_project_commit_url(project.namespace, project, commit, + only_path: context[:only_path]) + end + end + end +end diff --git a/lib/gitlab/markdown/filter/emoji_filter.rb b/lib/gitlab/markdown/filter/emoji_filter.rb new file mode 100644 index 00000000000..da10e4d3760 --- /dev/null +++ b/lib/gitlab/markdown/filter/emoji_filter.rb @@ -0,0 +1,80 @@ +require 'action_controller' +require 'gitlab/markdown' +require 'gitlab_emoji' +require 'html/pipeline/filter' + +module Gitlab + module Markdown + # HTML filter that replaces :emoji: with images. + # + # Based on HTML::Pipeline::EmojiFilter + # + # Context options: + # :asset_root + # :asset_host + class EmojiFilter < HTML::Pipeline::Filter + IGNORED_ANCESTOR_TAGS = %w(pre code tt).to_set + + def call + search_text_nodes(doc).each do |node| + content = node.to_html + next unless content.include?(':') + next if has_ancestor?(node, IGNORED_ANCESTOR_TAGS) + + html = emoji_image_filter(content) + + next if html == content + + node.replace(html) + end + + doc + end + + # Replace :emoji: with corresponding images. + # + # text - String text to replace :emoji: in. + # + # Returns a String with :emoji: replaced with images. + def emoji_image_filter(text) + text.gsub(emoji_pattern) do |match| + name = $1 + ":#{name}:" + end + end + + private + + def emoji_url(name) + emoji_path = "emoji/#{emoji_filename(name)}" + if context[:asset_host] + # Asset host is specified. + url_to_image(emoji_path) + elsif context[:asset_root] + # Gitlab url is specified + File.join(context[:asset_root], url_to_image(emoji_path)) + else + # All other cases + url_to_image(emoji_path) + end + end + + def url_to_image(image) + ActionController::Base.helpers.url_to_image(image) + end + + # Build a regexp that matches all valid :emoji: names. + def self.emoji_pattern + @emoji_pattern ||= /:(#{Emoji.emojis_names.map { |name| Regexp.escape(name) }.join('|')}):/ + end + + def emoji_pattern + self.class.emoji_pattern + end + + def emoji_filename(name) + "#{Emoji.emoji_filename(name)}.png" + end + end + end +end diff --git a/lib/gitlab/markdown/filter/external_issue_reference_filter.rb b/lib/gitlab/markdown/filter/external_issue_reference_filter.rb new file mode 100644 index 00000000000..8f86f13976a --- /dev/null +++ b/lib/gitlab/markdown/filter/external_issue_reference_filter.rb @@ -0,0 +1,63 @@ +require 'gitlab/markdown' + +module Gitlab + module Markdown + # HTML filter that replaces external issue tracker references with links. + # References are ignored if the project doesn't use an external issue + # tracker. + class ExternalIssueReferenceFilter < ReferenceFilter + # Public: Find `JIRA-123` issue references in text + # + # ExternalIssueReferenceFilter.references_in(text) do |match, issue| + # "##{issue}" + # end + # + # text - String text to search. + # + # Yields the String match and the String issue reference. + # + # Returns a String replaced with the return of the block. + def self.references_in(text) + text.gsub(ExternalIssue.reference_pattern) do |match| + yield match, $~[:issue] + end + end + + def call + # Early return if the project isn't using an external tracker + return doc if project.nil? || project.default_issues_tracker? + + replace_text_nodes_matching(ExternalIssue.reference_pattern) do |content| + issue_link_filter(content) + end + end + + # Replace `JIRA-123` issue references in text with links to the referenced + # issue's details page. + # + # text - String text to replace references in. + # + # Returns a String with `JIRA-123` references replaced with links. All + # links have `gfm` and `gfm-issue` class names attached for styling. + def issue_link_filter(text) + project = context[:project] + + self.class.references_in(text) do |match, issue| + url = url_for_issue(issue, project, only_path: context[:only_path]) + + title = escape_once("Issue in #{project.external_issue_tracker.title}") + klass = reference_class(:issue) + data = data_attribute(project: project.id) + + %(#{match}) + end + end + + def url_for_issue(*args) + IssuesHelper.url_for_issue(*args) + end + end + end +end diff --git a/lib/gitlab/markdown/filter/external_link_filter.rb b/lib/gitlab/markdown/filter/external_link_filter.rb new file mode 100644 index 00000000000..29e51b6ade6 --- /dev/null +++ b/lib/gitlab/markdown/filter/external_link_filter.rb @@ -0,0 +1,34 @@ +require 'gitlab/markdown' +require 'html/pipeline/filter' + +module Gitlab + module Markdown + # HTML Filter to add a `rel="nofollow"` attribute to external links + # + class ExternalLinkFilter < HTML::Pipeline::Filter + def call + doc.search('a').each do |node| + next unless node.has_attribute?('href') + + link = node.attribute('href').value + + # Skip non-HTTP(S) links + next unless link.start_with?('http') + + # Skip internal links + next if link.start_with?(internal_url) + + node.set_attribute('rel', 'nofollow') + end + + doc + end + + private + + def internal_url + @internal_url ||= Gitlab.config.gitlab.url + end + end + end +end diff --git a/lib/gitlab/markdown/filter/issue_reference_filter.rb b/lib/gitlab/markdown/filter/issue_reference_filter.rb new file mode 100644 index 00000000000..1ed69e2f431 --- /dev/null +++ b/lib/gitlab/markdown/filter/issue_reference_filter.rb @@ -0,0 +1,23 @@ +require 'gitlab/markdown' + +module Gitlab + module Markdown + # HTML filter that replaces issue references with links. References to + # issues that do not exist are ignored. + # + # This filter supports cross-project references. + class IssueReferenceFilter < AbstractReferenceFilter + def self.object_class + Issue + end + + def find_object(project, id) + project.get_issue(id) + end + + def url_for_object(issue, project) + IssuesHelper.url_for_issue(issue.iid, project, only_path: context[:only_path]) + end + end + end +end diff --git a/lib/gitlab/markdown/filter/label_reference_filter.rb b/lib/gitlab/markdown/filter/label_reference_filter.rb new file mode 100644 index 00000000000..618acb7a578 --- /dev/null +++ b/lib/gitlab/markdown/filter/label_reference_filter.rb @@ -0,0 +1,87 @@ +require 'gitlab/markdown' + +module Gitlab + module Markdown + # HTML filter that replaces label references with links. + class LabelReferenceFilter < ReferenceFilter + # Public: Find label references in text + # + # LabelReferenceFilter.references_in(text) do |match, id, name| + # "#{Label.find(id)}" + # end + # + # text - String text to search. + # + # Yields the String match, an optional Integer label ID, and an optional + # String label name. + # + # Returns a String replaced with the return of the block. + def self.references_in(text) + text.gsub(Label.reference_pattern) do |match| + yield match, $~[:label_id].to_i, $~[:label_name] + end + end + + def self.referenced_by(node) + { label: LazyReference.new(Label, node.attr("data-label")) } + end + + def call + replace_text_nodes_matching(Label.reference_pattern) do |content| + label_link_filter(content) + end + end + + # Replace label references in text with links to the label specified. + # + # text - String text to replace references in. + # + # Returns a String with label references replaced with links. All links + # have `gfm` and `gfm-label` class names attached for styling. + def label_link_filter(text) + project = context[:project] + + self.class.references_in(text) do |match, id, name| + params = label_params(id, name) + + if label = project.labels.find_by(params) + url = url_for_label(project, label) + klass = reference_class(:label) + data = data_attribute(project: project.id, label: label.id) + + %(#{render_colored_label(label)}) + else + match + end + end + end + + def url_for_label(project, label) + h = Gitlab::Application.routes.url_helpers + h.namespace_project_issues_path(project.namespace, project, + label_name: label.name, + only_path: context[:only_path]) + end + + def render_colored_label(label) + LabelsHelper.render_colored_label(label) + end + + # Parameters to pass to `Label.find_by` based on the given arguments + # + # id - Integer ID to pass. If present, returns {id: id} + # name - String name to pass. If `id` is absent, finds by name without + # surrounding quotes. + # + # Returns a Hash. + def label_params(id, name) + if name + { name: name.tr('"', '') } + else + { id: id } + end + end + end + end +end diff --git a/lib/gitlab/markdown/filter/markdown_filter.rb b/lib/gitlab/markdown/filter/markdown_filter.rb new file mode 100644 index 00000000000..921e2a0794e --- /dev/null +++ b/lib/gitlab/markdown/filter/markdown_filter.rb @@ -0,0 +1,39 @@ +module Gitlab + module Markdown + class MarkdownFilter < HTML::Pipeline::TextFilter + def initialize(text, context = nil, result = nil) + super text, context, result + @text = @text.gsub "\r", '' + end + + def call + html = self.class.renderer.render(@text) + html.rstrip! + html + end + + private + + def self.redcarpet_options + # https://github.com/vmg/redcarpet#and-its-like-really-simple-to-use + @redcarpet_options ||= { + fenced_code_blocks: true, + footnotes: true, + lax_spacing: true, + no_intra_emphasis: true, + space_after_headers: true, + strikethrough: true, + superscript: true, + tables: true + }.freeze + end + + def self.renderer + @renderer ||= begin + renderer = Redcarpet::Render::HTML.new + Redcarpet::Markdown.new(renderer, redcarpet_options) + end + end + end + end +end diff --git a/lib/gitlab/markdown/filter/merge_request_reference_filter.rb b/lib/gitlab/markdown/filter/merge_request_reference_filter.rb new file mode 100644 index 00000000000..1f47f03c94e --- /dev/null +++ b/lib/gitlab/markdown/filter/merge_request_reference_filter.rb @@ -0,0 +1,25 @@ +require 'gitlab/markdown' + +module Gitlab + module Markdown + # HTML filter that replaces merge request references with links. References + # to merge requests that do not exist are ignored. + # + # This filter supports cross-project references. + class MergeRequestReferenceFilter < AbstractReferenceFilter + def self.object_class + MergeRequest + end + + def find_object(project, id) + project.merge_requests.find_by(iid: id) + end + + def url_for_object(mr, project) + h = Gitlab::Application.routes.url_helpers + h.namespace_project_merge_request_url(project.namespace, project, mr, + only_path: context[:only_path]) + end + end + end +end diff --git a/lib/gitlab/markdown/filter/redactor_filter.rb b/lib/gitlab/markdown/filter/redactor_filter.rb new file mode 100644 index 00000000000..a1f3a8a8ebf --- /dev/null +++ b/lib/gitlab/markdown/filter/redactor_filter.rb @@ -0,0 +1,40 @@ +require 'gitlab/markdown' +require 'html/pipeline/filter' + +module Gitlab + module Markdown + # HTML filter that removes references to records that the current user does + # not have permission to view. + # + # Expected to be run in its own post-processing pipeline. + # + class RedactorFilter < HTML::Pipeline::Filter + def call + doc.css('a.gfm').each do |node| + unless user_can_reference?(node) + node.replace(node.text) + end + end + + doc + end + + private + + def user_can_reference?(node) + if node.has_attribute?('data-reference-filter') + reference_type = node.attr('data-reference-filter') + reference_filter = reference_type.constantize + + reference_filter.user_can_reference?(current_user, node, context) + else + true + end + end + + def current_user + context[:current_user] + end + end + end +end diff --git a/lib/gitlab/markdown/filter/reference_gatherer_filter.rb b/lib/gitlab/markdown/filter/reference_gatherer_filter.rb new file mode 100644 index 00000000000..00f983675e6 --- /dev/null +++ b/lib/gitlab/markdown/filter/reference_gatherer_filter.rb @@ -0,0 +1,63 @@ +require 'gitlab/markdown' +require 'html/pipeline/filter' + +module Gitlab + module Markdown + # HTML filter that gathers all referenced records that the current user has + # permission to view. + # + # Expected to be run in its own post-processing pipeline. + # + class ReferenceGathererFilter < HTML::Pipeline::Filter + def initialize(*) + super + + result[:references] ||= Hash.new { |hash, type| hash[type] = [] } + end + + def call + doc.css('a.gfm').each do |node| + gather_references(node) + end + + load_lazy_references unless context[:load_lazy_references] == false + + doc + end + + private + + def gather_references(node) + return unless node.has_attribute?('data-reference-filter') + + reference_type = node.attr('data-reference-filter') + reference_filter = reference_type.constantize + + return if context[:reference_filter] && reference_filter != context[:reference_filter] + + return unless reference_filter.user_can_reference?(current_user, node, context) + + references = reference_filter.referenced_by(node) + return unless references + + references.each do |type, values| + Array.wrap(values).each do |value| + result[:references][type] << value + end + end + end + + # Will load all references of one type using one query. + def load_lazy_references + refs = result[:references] + refs.each do |type, values| + refs[type] = ReferenceFilter::LazyReference.load(values) + end + end + + def current_user + context[:current_user] + end + end + end +end diff --git a/lib/gitlab/markdown/filter/relative_link_filter.rb b/lib/gitlab/markdown/filter/relative_link_filter.rb new file mode 100644 index 00000000000..81f60120fcd --- /dev/null +++ b/lib/gitlab/markdown/filter/relative_link_filter.rb @@ -0,0 +1,157 @@ +require 'gitlab/markdown' +require 'html/pipeline/filter' +require 'uri' + +module Gitlab + module Markdown + # HTML filter that "fixes" relative links to files in a repository. + # + # Context options: + # :commit + # :project + # :project_wiki + # :ref + # :requested_path + class RelativeLinkFilter < HTML::Pipeline::Filter + def call + return doc unless linkable_files? + + doc.search('a:not(.gfm)').each do |el| + process_link_attr el.attribute('href') + end + + doc.search('img').each do |el| + process_link_attr el.attribute('src') + end + + doc + end + + protected + + def linkable_files? + context[:project_wiki].nil? && repository.try(:exists?) && !repository.empty? + end + + 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 + rescue URI::Error + # noop + end + + def rebuild_relative_uri(uri) + file_path = relative_file_path(uri.path) + + uri.path = [ + relative_url_root, + context[:project].path_with_namespace, + path_type(file_path), + ref || context[:project].default_branch, # if no ref exists, point to the default branch + file_path + ].compact.join('/').squeeze('/').chomp('/') + + uri + end + + def relative_file_path(path) + nested_path = build_relative_path(path, context[:requested_path]) + file_exists?(nested_path) ? nested_path : path + end + + # Convert a relative path into its correct location based on the currently + # requested path + # + # path - Relative path String + # request_path - Currently-requested path String + # + # Examples: + # + # # File in the same directory as the current path + # build_relative_path("users.md", "doc/api/README.md") + # # => "doc/api/users.md" + # + # # File in the same directory, which is also the current path + # build_relative_path("users.md", "doc/api") + # # => "doc/api/users.md" + # + # # Going up one level to a different directory + # build_relative_path("../update/7.14-to-8.0.md", "doc/api/README.md") + # # => "doc/update/7.14-to-8.0.md" + # + # Returns a String + def build_relative_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' + + while parts.length > 1 && path.start_with?('../') + parts.pop + path.sub!('../', '') + end + + 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 + + # Get the type of the given path + # + # path - String path to check + # + # Examples: + # + # path_type('doc/README.md') # => 'blob' + # path_type('doc/logo.png') # => 'raw' + # path_type('doc/api') # => 'tree' + # + # Returns a String + def path_type(path) + unescaped_path = Addressable::URI.unescape(path) + + if tree?(unescaped_path) + 'tree' + elsif image?(unescaped_path) + 'raw' + else + 'blob' + end + end + + def tree?(path) + repository.tree(current_sha, path).entries.any? + end + + def image?(path) + repository.blob_at(current_sha, path).try(:image?) + end + + def current_sha + context[:commit].try(:id) || + ref ? repository.commit(ref).try(:sha) : repository.head_commit.sha + end + + def relative_url_root + Gitlab.config.gitlab.relative_url_root.presence || '/' + end + + def ref + context[:ref] + end + + def repository + context[:project].try(:repository) + end + end + end +end diff --git a/lib/gitlab/markdown/filter/sanitization_filter.rb b/lib/gitlab/markdown/filter/sanitization_filter.rb new file mode 100644 index 00000000000..cf153f30622 --- /dev/null +++ b/lib/gitlab/markdown/filter/sanitization_filter.rb @@ -0,0 +1,99 @@ +require 'gitlab/markdown' +require 'html/pipeline/filter' +require 'html/pipeline/sanitization_filter' + +module Gitlab + module Markdown + # Sanitize HTML + # + # Extends HTML::Pipeline::SanitizationFilter with a custom whitelist. + class SanitizationFilter < HTML::Pipeline::SanitizationFilter + def whitelist + # Descriptions are more heavily sanitized, allowing only a few elements. + # See http://git.io/vkuAN + if context[:inline_sanitization] + whitelist = LIMITED + whitelist[:elements] -= %w(pre code img ol ul li) + else + whitelist = super + end + + customize_whitelist(whitelist) + + whitelist + end + + private + + def customized?(transformers) + transformers.last.source_location[0] == __FILE__ + end + + def customize_whitelist(whitelist) + # Only push these customizations once + return if customized?(whitelist[:transformers]) + + # Allow code highlighting + whitelist[:attributes]['pre'] = %w(class) + whitelist[:attributes]['span'] = %w(class) + + # Allow table alignment + whitelist[:attributes]['th'] = %w(style) + whitelist[:attributes]['td'] = %w(style) + + # Allow span elements + whitelist[:elements].push('span') + + # Allow any protocol in `a` elements... + whitelist[:protocols].delete('a') + + # ...but then remove links with the `javascript` protocol + whitelist[:transformers].push(remove_javascript_links) + + # Remove `rel` attribute from `a` elements + whitelist[:transformers].push(remove_rel) + + # Remove `class` attribute from non-highlight spans + whitelist[:transformers].push(clean_spans) + + whitelist + end + + def remove_javascript_links + lambda do |env| + node = env[:node] + + return unless node.name == 'a' + return unless node.has_attribute?('href') + + if node['href'].start_with?('javascript', ':javascript') + node.remove_attribute('href') + end + end + end + + def remove_rel + lambda do |env| + if env[:node_name] == 'a' + env[:node].remove_attribute('rel') + end + end + end + + def clean_spans + lambda do |env| + node = env[:node] + + return unless node.name == 'span' + return unless node.has_attribute?('class') + + unless has_ancestor?(node, 'pre') + node.remove_attribute('class') + end + + { node_whitelist: [node] } + end + end + end + end +end diff --git a/lib/gitlab/markdown/filter/snippet_reference_filter.rb b/lib/gitlab/markdown/filter/snippet_reference_filter.rb new file mode 100644 index 00000000000..f7bd07c2a34 --- /dev/null +++ b/lib/gitlab/markdown/filter/snippet_reference_filter.rb @@ -0,0 +1,25 @@ +require 'gitlab/markdown' + +module Gitlab + module Markdown + # HTML filter that replaces snippet references with links. References to + # snippets that do not exist are ignored. + # + # This filter supports cross-project references. + class SnippetReferenceFilter < AbstractReferenceFilter + def self.object_class + Snippet + end + + def find_object(project, id) + project.snippets.find_by(id: id) + end + + def url_for_object(snippet, project) + h = Gitlab::Application.routes.url_helpers + h.namespace_project_snippet_url(project.namespace, project, snippet, + only_path: context[:only_path]) + end + end + end +end diff --git a/lib/gitlab/markdown/filter/syntax_highlight_filter.rb b/lib/gitlab/markdown/filter/syntax_highlight_filter.rb new file mode 100644 index 00000000000..8597e02f0de --- /dev/null +++ b/lib/gitlab/markdown/filter/syntax_highlight_filter.rb @@ -0,0 +1,45 @@ +require 'gitlab/markdown' +require 'html/pipeline/filter' +require 'rouge/plugins/redcarpet' + +module Gitlab + module Markdown + # HTML Filter to highlight fenced code blocks + # + class SyntaxHighlightFilter < HTML::Pipeline::Filter + include Rouge::Plugins::Redcarpet + + def call + doc.search('pre > code').each do |node| + highlight_node(node) + end + + doc + end + + def highlight_node(node) + language = node.attr('class') + code = node.text + + begin + highlighted = block_code(code, language) + rescue + # Gracefully handle syntax highlighter bugs/errors to ensure + # users can still access an issue/comment/etc. + highlighted = "
#{code}
" + end + + # Replace the parent `pre` element with the entire highlighted block + node.parent.replace(highlighted) + end + + private + + # Override Rouge::Plugins::Redcarpet#rouge_formatter + def rouge_formatter(lexer) + Rouge::Formatters::HTMLGitlab.new( + cssclass: "code highlight js-syntax-highlight #{lexer.tag}") + end + end + end +end diff --git a/lib/gitlab/markdown/filter/table_of_contents_filter.rb b/lib/gitlab/markdown/filter/table_of_contents_filter.rb new file mode 100644 index 00000000000..bbb3bf7fc8b --- /dev/null +++ b/lib/gitlab/markdown/filter/table_of_contents_filter.rb @@ -0,0 +1,63 @@ +require 'gitlab/markdown' +require 'html/pipeline/filter' + +module Gitlab + module Markdown + # HTML filter that adds an anchor child element to all Headers in a + # document, so that they can be linked to. + # + # Generates the Table of Contents with links to each header. See Results. + # + # Based on HTML::Pipeline::TableOfContentsFilter. + # + # Context options: + # :no_header_anchors - Skips all processing done by this filter. + # + # Results: + # :toc - String containing Table of Contents data as a `ul` element with + # `li` child elements. + class TableOfContentsFilter < HTML::Pipeline::Filter + PUNCTUATION_REGEXP = /[^\p{Word}\- ]/u + + def call + return doc if context[:no_header_anchors] + + result[:toc] = "" + + headers = Hash.new(0) + + doc.css('h1, h2, h3, h4, h5, h6').each do |node| + text = node.text + + id = text.downcase + id.gsub!(PUNCTUATION_REGEXP, '') # remove punctuation + id.gsub!(' ', '-') # replace spaces with dash + id.squeeze!('-') # replace multiple dashes with one + + uniq = (headers[id] > 0) ? "-#{headers[id]}" : '' + headers[id] += 1 + + if header_content = node.children.first + href = "#{id}#{uniq}" + push_toc(href, text) + header_content.add_previous_sibling(anchor_tag(href)) + end + end + + result[:toc] = %Q{
    \n#{result[:toc]}
} unless result[:toc].empty? + + doc + end + + private + + def anchor_tag(href) + %Q{} + end + + def push_toc(href, text) + result[:toc] << %Q{
  • #{text}
  • \n} + end + end + end +end diff --git a/lib/gitlab/markdown/filter/task_list_filter.rb b/lib/gitlab/markdown/filter/task_list_filter.rb new file mode 100644 index 00000000000..2f133ae8500 --- /dev/null +++ b/lib/gitlab/markdown/filter/task_list_filter.rb @@ -0,0 +1,24 @@ +require 'gitlab/markdown' +require 'task_list/filter' + +module Gitlab + module Markdown + # Work around a bug in the default TaskList::Filter that adds a `task-list` + # class to every list element, regardless of whether or not it contains a + # task list. + # + # This is a (hopefully) temporary fix, pending a new release of the + # task_list gem. + # + # See https://github.com/github/task_list/pull/60 + class TaskListFilter < TaskList::Filter + def add_css_class(node, *new_class_names) + if new_class_names.include?('task-list') + super if node.children.any? { |c| c['class'] == 'task-list-item' } + else + super + end + end + end + end +end diff --git a/lib/gitlab/markdown/filter/upload_link_filter.rb b/lib/gitlab/markdown/filter/upload_link_filter.rb new file mode 100644 index 00000000000..fbada73ab86 --- /dev/null +++ b/lib/gitlab/markdown/filter/upload_link_filter.rb @@ -0,0 +1,47 @@ +require 'gitlab/markdown' +require 'html/pipeline/filter' +require 'uri' + +module Gitlab + module Markdown + # HTML filter that "fixes" relative upload links to files. + # Context options: + # :project (required) - Current project + # + class UploadLinkFilter < HTML::Pipeline::Filter + def call + 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 + + doc + end + + protected + + def process_link_attr(html_attr) + return if html_attr.blank? + + uri = html_attr.value + if uri.starts_with?("/uploads/") + html_attr.value = build_url(uri).to_s + end + end + + def build_url(uri) + File.join(Gitlab.config.gitlab.url, context[:project].path_with_namespace, uri) + end + + # Ensure that a :project key exists in context + # + # Note that while the key might exist, its value could be nil! + def validate + needs :project + end + end + end +end diff --git a/lib/gitlab/markdown/filter/user_reference_filter.rb b/lib/gitlab/markdown/filter/user_reference_filter.rb new file mode 100644 index 00000000000..ab5e1f6fe9e --- /dev/null +++ b/lib/gitlab/markdown/filter/user_reference_filter.rb @@ -0,0 +1,125 @@ +require 'gitlab/markdown' + +module Gitlab + module Markdown + # HTML filter that replaces user or group references with links. + # + # A special `@all` reference is also supported. + class UserReferenceFilter < ReferenceFilter + # Public: Find `@user` user references in text + # + # UserReferenceFilter.references_in(text) do |match, username| + # "@#{user}" + # end + # + # text - String text to search. + # + # Yields the String match, and the String user name. + # + # Returns a String replaced with the return of the block. + def self.references_in(text) + text.gsub(User.reference_pattern) do |match| + yield match, $~[:user] + end + end + + def self.referenced_by(node) + if node.has_attribute?('data-group') + group = Group.find(node.attr('data-group')) rescue nil + return unless group + + { user: group.users } + elsif node.has_attribute?('data-user') + { user: LazyReference.new(User, node.attr('data-user')) } + elsif node.has_attribute?('data-project') + project = Project.find(node.attr('data-project')) rescue nil + return unless project + + { user: project.team.members.flatten } + end + end + + def self.user_can_reference?(user, node, context) + if node.has_attribute?('data-group') + group = Group.find(node.attr('data-group')) rescue nil + Ability.abilities.allowed?(user, :read_group, group) + else + super + end + end + + def call + replace_text_nodes_matching(User.reference_pattern) do |content| + user_link_filter(content) + end + end + + # Replace `@user` user references in text with links to the referenced + # user's profile page. + # + # text - String text to replace references in. + # + # Returns a String with `@user` references replaced with links. All links + # have `gfm` and `gfm-project_member` class names attached for styling. + def user_link_filter(text) + self.class.references_in(text) do |match, username| + if username == 'all' + link_to_all + elsif namespace = Namespace.find_by(path: username) + link_to_namespace(namespace) || match + else + match + end + end + end + + private + + def urls + Gitlab::Application.routes.url_helpers + end + + def link_class + reference_class(:project_member) + end + + def link_to_all + project = context[:project] + url = urls.namespace_project_url(project.namespace, project, + only_path: context[:only_path]) + data = data_attribute(project: project.id) + text = User.reference_prefix + 'all' + + link_tag(url, data, text) + end + + def link_to_namespace(namespace) + if namespace.is_a?(Group) + link_to_group(namespace.path, namespace) + else + link_to_user(namespace.path, namespace) + end + end + + def link_to_group(group, namespace) + url = urls.group_url(group, only_path: context[:only_path]) + data = data_attribute(group: namespace.id) + text = Group.reference_prefix + group + + link_tag(url, data, text) + end + + def link_to_user(user, namespace) + url = urls.user_url(user, only_path: context[:only_path]) + data = data_attribute(user: namespace.owner_id) + text = User.reference_prefix + user + + link_tag(url, data, text) + end + + def link_tag(url, data, text) + %(#{text}) + end + end + end +end diff --git a/lib/gitlab/markdown/full_pipeline.rb b/lib/gitlab/markdown/full_pipeline.rb deleted file mode 100644 index 553e9367c1c..00000000000 --- a/lib/gitlab/markdown/full_pipeline.rb +++ /dev/null @@ -1,9 +0,0 @@ -require 'gitlab/markdown' - -module Gitlab - module Markdown - class FullPipeline < CombinedPipeline.new(PlainMarkdownPipeline, GfmPipeline) - - end - end -end diff --git a/lib/gitlab/markdown/gfm_pipeline.rb b/lib/gitlab/markdown/gfm_pipeline.rb deleted file mode 100644 index ca90bd75d77..00000000000 --- a/lib/gitlab/markdown/gfm_pipeline.rb +++ /dev/null @@ -1,41 +0,0 @@ -require 'gitlab/markdown' - -module Gitlab - module Markdown - class GfmPipeline < Pipeline - def self.filters - @filters ||= [ - Gitlab::Markdown::SyntaxHighlightFilter, - Gitlab::Markdown::SanitizationFilter, - - Gitlab::Markdown::UploadLinkFilter, - Gitlab::Markdown::EmojiFilter, - Gitlab::Markdown::TableOfContentsFilter, - Gitlab::Markdown::AutolinkFilter, - Gitlab::Markdown::ExternalLinkFilter, - - Gitlab::Markdown::UserReferenceFilter, - Gitlab::Markdown::IssueReferenceFilter, - Gitlab::Markdown::ExternalIssueReferenceFilter, - Gitlab::Markdown::MergeRequestReferenceFilter, - Gitlab::Markdown::SnippetReferenceFilter, - Gitlab::Markdown::CommitRangeReferenceFilter, - Gitlab::Markdown::CommitReferenceFilter, - Gitlab::Markdown::LabelReferenceFilter, - - Gitlab::Markdown::TaskListFilter - ] - end - - def self.transform_context(context) - context.merge( - only_path: true, - - # EmojiFilter - asset_host: Gitlab::Application.config.asset_host, - asset_root: Gitlab.config.gitlab.base_url - ) - end - end - end -end diff --git a/lib/gitlab/markdown/issue_reference_filter.rb b/lib/gitlab/markdown/issue_reference_filter.rb deleted file mode 100644 index 1ed69e2f431..00000000000 --- a/lib/gitlab/markdown/issue_reference_filter.rb +++ /dev/null @@ -1,23 +0,0 @@ -require 'gitlab/markdown' - -module Gitlab - module Markdown - # HTML filter that replaces issue references with links. References to - # issues that do not exist are ignored. - # - # This filter supports cross-project references. - class IssueReferenceFilter < AbstractReferenceFilter - def self.object_class - Issue - end - - def find_object(project, id) - project.get_issue(id) - end - - def url_for_object(issue, project) - IssuesHelper.url_for_issue(issue.iid, project, only_path: context[:only_path]) - end - end - end -end diff --git a/lib/gitlab/markdown/label_reference_filter.rb b/lib/gitlab/markdown/label_reference_filter.rb deleted file mode 100644 index 618acb7a578..00000000000 --- a/lib/gitlab/markdown/label_reference_filter.rb +++ /dev/null @@ -1,87 +0,0 @@ -require 'gitlab/markdown' - -module Gitlab - module Markdown - # HTML filter that replaces label references with links. - class LabelReferenceFilter < ReferenceFilter - # Public: Find label references in text - # - # LabelReferenceFilter.references_in(text) do |match, id, name| - # "#{Label.find(id)}" - # end - # - # text - String text to search. - # - # Yields the String match, an optional Integer label ID, and an optional - # String label name. - # - # Returns a String replaced with the return of the block. - def self.references_in(text) - text.gsub(Label.reference_pattern) do |match| - yield match, $~[:label_id].to_i, $~[:label_name] - end - end - - def self.referenced_by(node) - { label: LazyReference.new(Label, node.attr("data-label")) } - end - - def call - replace_text_nodes_matching(Label.reference_pattern) do |content| - label_link_filter(content) - end - end - - # Replace label references in text with links to the label specified. - # - # text - String text to replace references in. - # - # Returns a String with label references replaced with links. All links - # have `gfm` and `gfm-label` class names attached for styling. - def label_link_filter(text) - project = context[:project] - - self.class.references_in(text) do |match, id, name| - params = label_params(id, name) - - if label = project.labels.find_by(params) - url = url_for_label(project, label) - klass = reference_class(:label) - data = data_attribute(project: project.id, label: label.id) - - %(#{render_colored_label(label)}) - else - match - end - end - end - - def url_for_label(project, label) - h = Gitlab::Application.routes.url_helpers - h.namespace_project_issues_path(project.namespace, project, - label_name: label.name, - only_path: context[:only_path]) - end - - def render_colored_label(label) - LabelsHelper.render_colored_label(label) - end - - # Parameters to pass to `Label.find_by` based on the given arguments - # - # id - Integer ID to pass. If present, returns {id: id} - # name - String name to pass. If `id` is absent, finds by name without - # surrounding quotes. - # - # Returns a Hash. - def label_params(id, name) - if name - { name: name.tr('"', '') } - else - { id: id } - end - end - end - end -end diff --git a/lib/gitlab/markdown/markdown_filter.rb b/lib/gitlab/markdown/markdown_filter.rb deleted file mode 100644 index 921e2a0794e..00000000000 --- a/lib/gitlab/markdown/markdown_filter.rb +++ /dev/null @@ -1,39 +0,0 @@ -module Gitlab - module Markdown - class MarkdownFilter < HTML::Pipeline::TextFilter - def initialize(text, context = nil, result = nil) - super text, context, result - @text = @text.gsub "\r", '' - end - - def call - html = self.class.renderer.render(@text) - html.rstrip! - html - end - - private - - def self.redcarpet_options - # https://github.com/vmg/redcarpet#and-its-like-really-simple-to-use - @redcarpet_options ||= { - fenced_code_blocks: true, - footnotes: true, - lax_spacing: true, - no_intra_emphasis: true, - space_after_headers: true, - strikethrough: true, - superscript: true, - tables: true - }.freeze - end - - def self.renderer - @renderer ||= begin - renderer = Redcarpet::Render::HTML.new - Redcarpet::Markdown.new(renderer, redcarpet_options) - end - end - end - end -end diff --git a/lib/gitlab/markdown/merge_request_reference_filter.rb b/lib/gitlab/markdown/merge_request_reference_filter.rb deleted file mode 100644 index 1f47f03c94e..00000000000 --- a/lib/gitlab/markdown/merge_request_reference_filter.rb +++ /dev/null @@ -1,25 +0,0 @@ -require 'gitlab/markdown' - -module Gitlab - module Markdown - # HTML filter that replaces merge request references with links. References - # to merge requests that do not exist are ignored. - # - # This filter supports cross-project references. - class MergeRequestReferenceFilter < AbstractReferenceFilter - def self.object_class - MergeRequest - end - - def find_object(project, id) - project.merge_requests.find_by(iid: id) - end - - def url_for_object(mr, project) - h = Gitlab::Application.routes.url_helpers - h.namespace_project_merge_request_url(project.namespace, project, mr, - only_path: context[:only_path]) - end - end - end -end diff --git a/lib/gitlab/markdown/note_pipeline.rb b/lib/gitlab/markdown/note_pipeline.rb deleted file mode 100644 index a8bf5f42d8e..00000000000 --- a/lib/gitlab/markdown/note_pipeline.rb +++ /dev/null @@ -1,14 +0,0 @@ -require 'gitlab/markdown' - -module Gitlab - module Markdown - class NotePipeline < FullPipeline - def self.transform_context(context) - super(context).merge( - # TableOfContentsFilter - no_header_anchors: true - ) - end - end - end -end diff --git a/lib/gitlab/markdown/pipeline.rb b/lib/gitlab/markdown/pipeline.rb index 3c0b676a073..d683756f95a 100644 --- a/lib/gitlab/markdown/pipeline.rb +++ b/lib/gitlab/markdown/pipeline.rb @@ -3,6 +3,11 @@ require 'gitlab/markdown' module Gitlab module Markdown class Pipeline + def self.[](name) + name ||= :full + Markdown.const_get("#{name.to_s.camelize}Pipeline") + end + def self.filters [] end diff --git a/lib/gitlab/markdown/pipeline/asciidoc_pipeline.rb b/lib/gitlab/markdown/pipeline/asciidoc_pipeline.rb new file mode 100644 index 00000000000..6829b4acb95 --- /dev/null +++ b/lib/gitlab/markdown/pipeline/asciidoc_pipeline.rb @@ -0,0 +1,13 @@ +require 'gitlab/markdown' + +module Gitlab + module Markdown + class AsciidocPipeline < Pipeline + def self.filters + [ + Gitlab::Markdown::RelativeLinkFilter + ] + end + end + end +end diff --git a/lib/gitlab/markdown/pipeline/atom_pipeline.rb b/lib/gitlab/markdown/pipeline/atom_pipeline.rb new file mode 100644 index 00000000000..e151f8f5e5a --- /dev/null +++ b/lib/gitlab/markdown/pipeline/atom_pipeline.rb @@ -0,0 +1,14 @@ +require 'gitlab/markdown' + +module Gitlab + module Markdown + class AtomPipeline < FullPipeline + def self.transform_context(context) + super(context).merge( + only_path: false, + xhtml: true + ) + end + end + end +end diff --git a/lib/gitlab/markdown/pipeline/description_pipeline.rb b/lib/gitlab/markdown/pipeline/description_pipeline.rb new file mode 100644 index 00000000000..76f6948af8f --- /dev/null +++ b/lib/gitlab/markdown/pipeline/description_pipeline.rb @@ -0,0 +1,14 @@ +require 'gitlab/markdown' + +module Gitlab + module Markdown + class DescriptionPipeline < FullPipeline + def self.transform_context(context) + super(context).merge( + # SanitizationFilter + inline_sanitization: true + ) + end + end + end +end diff --git a/lib/gitlab/markdown/pipeline/email_pipeline.rb b/lib/gitlab/markdown/pipeline/email_pipeline.rb new file mode 100644 index 00000000000..b88cb790270 --- /dev/null +++ b/lib/gitlab/markdown/pipeline/email_pipeline.rb @@ -0,0 +1,13 @@ +require 'gitlab/markdown' + +module Gitlab + module Markdown + class EmailPipeline < FullPipeline + def self.transform_context(context) + super(context).merge( + only_path: false + ) + end + end + end +end diff --git a/lib/gitlab/markdown/pipeline/full_pipeline.rb b/lib/gitlab/markdown/pipeline/full_pipeline.rb new file mode 100644 index 00000000000..553e9367c1c --- /dev/null +++ b/lib/gitlab/markdown/pipeline/full_pipeline.rb @@ -0,0 +1,9 @@ +require 'gitlab/markdown' + +module Gitlab + module Markdown + class FullPipeline < CombinedPipeline.new(PlainMarkdownPipeline, GfmPipeline) + + end + end +end diff --git a/lib/gitlab/markdown/pipeline/gfm_pipeline.rb b/lib/gitlab/markdown/pipeline/gfm_pipeline.rb new file mode 100644 index 00000000000..ca90bd75d77 --- /dev/null +++ b/lib/gitlab/markdown/pipeline/gfm_pipeline.rb @@ -0,0 +1,41 @@ +require 'gitlab/markdown' + +module Gitlab + module Markdown + class GfmPipeline < Pipeline + def self.filters + @filters ||= [ + Gitlab::Markdown::SyntaxHighlightFilter, + Gitlab::Markdown::SanitizationFilter, + + Gitlab::Markdown::UploadLinkFilter, + Gitlab::Markdown::EmojiFilter, + Gitlab::Markdown::TableOfContentsFilter, + Gitlab::Markdown::AutolinkFilter, + Gitlab::Markdown::ExternalLinkFilter, + + Gitlab::Markdown::UserReferenceFilter, + Gitlab::Markdown::IssueReferenceFilter, + Gitlab::Markdown::ExternalIssueReferenceFilter, + Gitlab::Markdown::MergeRequestReferenceFilter, + Gitlab::Markdown::SnippetReferenceFilter, + Gitlab::Markdown::CommitRangeReferenceFilter, + Gitlab::Markdown::CommitReferenceFilter, + Gitlab::Markdown::LabelReferenceFilter, + + Gitlab::Markdown::TaskListFilter + ] + end + + def self.transform_context(context) + context.merge( + only_path: true, + + # EmojiFilter + asset_host: Gitlab::Application.config.asset_host, + asset_root: Gitlab.config.gitlab.base_url + ) + end + end + end +end diff --git a/lib/gitlab/markdown/pipeline/note_pipeline.rb b/lib/gitlab/markdown/pipeline/note_pipeline.rb new file mode 100644 index 00000000000..a8bf5f42d8e --- /dev/null +++ b/lib/gitlab/markdown/pipeline/note_pipeline.rb @@ -0,0 +1,14 @@ +require 'gitlab/markdown' + +module Gitlab + module Markdown + class NotePipeline < FullPipeline + def self.transform_context(context) + super(context).merge( + # TableOfContentsFilter + no_header_anchors: true + ) + end + end + end +end diff --git a/lib/gitlab/markdown/pipeline/plain_markdown_pipeline.rb b/lib/gitlab/markdown/pipeline/plain_markdown_pipeline.rb new file mode 100644 index 00000000000..0abb93f8a03 --- /dev/null +++ b/lib/gitlab/markdown/pipeline/plain_markdown_pipeline.rb @@ -0,0 +1,13 @@ +require 'gitlab/markdown' + +module Gitlab + module Markdown + class PlainMarkdownPipeline < Pipeline + def self.filters + [ + Gitlab::Markdown::MarkdownFilter + ] + end + end + end +end diff --git a/lib/gitlab/markdown/pipeline/post_process_pipeline.rb b/lib/gitlab/markdown/pipeline/post_process_pipeline.rb new file mode 100644 index 00000000000..60cc32f490e --- /dev/null +++ b/lib/gitlab/markdown/pipeline/post_process_pipeline.rb @@ -0,0 +1,20 @@ +require 'gitlab/markdown' + +module Gitlab + module Markdown + class PostProcessPipeline < Pipeline + def self.filters + [ + Gitlab::Markdown::RelativeLinkFilter, + Gitlab::Markdown::RedactorFilter + ] + end + + def self.transform_context(context) + context.merge( + post_process: true + ) + end + end + end +end diff --git a/lib/gitlab/markdown/pipeline/reference_extraction_pipeline.rb b/lib/gitlab/markdown/pipeline/reference_extraction_pipeline.rb new file mode 100644 index 00000000000..a89ab462bac --- /dev/null +++ b/lib/gitlab/markdown/pipeline/reference_extraction_pipeline.rb @@ -0,0 +1,13 @@ +require 'gitlab/markdown' + +module Gitlab + module Markdown + class ReferenceExtractionPipeline < Pipeline + def self.filters + [ + Gitlab::Markdown::ReferenceGathererFilter + ] + end + end + end +end diff --git a/lib/gitlab/markdown/pipeline/single_line_pipeline.rb b/lib/gitlab/markdown/pipeline/single_line_pipeline.rb new file mode 100644 index 00000000000..2f24927b879 --- /dev/null +++ b/lib/gitlab/markdown/pipeline/single_line_pipeline.rb @@ -0,0 +1,9 @@ +require 'gitlab/markdown' + +module Gitlab + module Markdown + class SingleLinePipeline < GfmPipeline + + end + end +end diff --git a/lib/gitlab/markdown/plain_markdown_pipeline.rb b/lib/gitlab/markdown/plain_markdown_pipeline.rb deleted file mode 100644 index 0abb93f8a03..00000000000 --- a/lib/gitlab/markdown/plain_markdown_pipeline.rb +++ /dev/null @@ -1,13 +0,0 @@ -require 'gitlab/markdown' - -module Gitlab - module Markdown - class PlainMarkdownPipeline < Pipeline - def self.filters - [ - Gitlab::Markdown::MarkdownFilter - ] - end - end - end -end diff --git a/lib/gitlab/markdown/post_process_pipeline.rb b/lib/gitlab/markdown/post_process_pipeline.rb deleted file mode 100644 index 60cc32f490e..00000000000 --- a/lib/gitlab/markdown/post_process_pipeline.rb +++ /dev/null @@ -1,20 +0,0 @@ -require 'gitlab/markdown' - -module Gitlab - module Markdown - class PostProcessPipeline < Pipeline - def self.filters - [ - Gitlab::Markdown::RelativeLinkFilter, - Gitlab::Markdown::RedactorFilter - ] - end - - def self.transform_context(context) - context.merge( - post_process: true - ) - end - end - end -end diff --git a/lib/gitlab/markdown/redactor_filter.rb b/lib/gitlab/markdown/redactor_filter.rb deleted file mode 100644 index a1f3a8a8ebf..00000000000 --- a/lib/gitlab/markdown/redactor_filter.rb +++ /dev/null @@ -1,40 +0,0 @@ -require 'gitlab/markdown' -require 'html/pipeline/filter' - -module Gitlab - module Markdown - # HTML filter that removes references to records that the current user does - # not have permission to view. - # - # Expected to be run in its own post-processing pipeline. - # - class RedactorFilter < HTML::Pipeline::Filter - def call - doc.css('a.gfm').each do |node| - unless user_can_reference?(node) - node.replace(node.text) - end - end - - doc - end - - private - - def user_can_reference?(node) - if node.has_attribute?('data-reference-filter') - reference_type = node.attr('data-reference-filter') - reference_filter = reference_type.constantize - - reference_filter.user_can_reference?(current_user, node, context) - else - true - end - end - - def current_user - context[:current_user] - end - end - end -end diff --git a/lib/gitlab/markdown/reference_extraction_pipeline.rb b/lib/gitlab/markdown/reference_extraction_pipeline.rb deleted file mode 100644 index a89ab462bac..00000000000 --- a/lib/gitlab/markdown/reference_extraction_pipeline.rb +++ /dev/null @@ -1,13 +0,0 @@ -require 'gitlab/markdown' - -module Gitlab - module Markdown - class ReferenceExtractionPipeline < Pipeline - def self.filters - [ - Gitlab::Markdown::ReferenceGathererFilter - ] - end - end - end -end diff --git a/lib/gitlab/markdown/reference_filter.rb b/lib/gitlab/markdown/reference_filter.rb index a4c560f578c..22fe4440be3 100644 --- a/lib/gitlab/markdown/reference_filter.rb +++ b/lib/gitlab/markdown/reference_filter.rb @@ -29,6 +29,10 @@ module Gitlab end end + def self.[](name) + Markdown.const_get("#{name.to_s.camelize}ReferenceFilter") + end + def self.user_can_reference?(user, node, context) if node.has_attribute?('data-project') project_id = node.attr('data-project').to_i diff --git a/lib/gitlab/markdown/reference_gatherer_filter.rb b/lib/gitlab/markdown/reference_gatherer_filter.rb deleted file mode 100644 index 00f983675e6..00000000000 --- a/lib/gitlab/markdown/reference_gatherer_filter.rb +++ /dev/null @@ -1,63 +0,0 @@ -require 'gitlab/markdown' -require 'html/pipeline/filter' - -module Gitlab - module Markdown - # HTML filter that gathers all referenced records that the current user has - # permission to view. - # - # Expected to be run in its own post-processing pipeline. - # - class ReferenceGathererFilter < HTML::Pipeline::Filter - def initialize(*) - super - - result[:references] ||= Hash.new { |hash, type| hash[type] = [] } - end - - def call - doc.css('a.gfm').each do |node| - gather_references(node) - end - - load_lazy_references unless context[:load_lazy_references] == false - - doc - end - - private - - def gather_references(node) - return unless node.has_attribute?('data-reference-filter') - - reference_type = node.attr('data-reference-filter') - reference_filter = reference_type.constantize - - return if context[:reference_filter] && reference_filter != context[:reference_filter] - - return unless reference_filter.user_can_reference?(current_user, node, context) - - references = reference_filter.referenced_by(node) - return unless references - - references.each do |type, values| - Array.wrap(values).each do |value| - result[:references][type] << value - end - end - end - - # Will load all references of one type using one query. - def load_lazy_references - refs = result[:references] - refs.each do |type, values| - refs[type] = ReferenceFilter::LazyReference.load(values) - end - end - - def current_user - context[:current_user] - end - end - end -end diff --git a/lib/gitlab/markdown/relative_link_filter.rb b/lib/gitlab/markdown/relative_link_filter.rb deleted file mode 100644 index 81f60120fcd..00000000000 --- a/lib/gitlab/markdown/relative_link_filter.rb +++ /dev/null @@ -1,157 +0,0 @@ -require 'gitlab/markdown' -require 'html/pipeline/filter' -require 'uri' - -module Gitlab - module Markdown - # HTML filter that "fixes" relative links to files in a repository. - # - # Context options: - # :commit - # :project - # :project_wiki - # :ref - # :requested_path - class RelativeLinkFilter < HTML::Pipeline::Filter - def call - return doc unless linkable_files? - - doc.search('a:not(.gfm)').each do |el| - process_link_attr el.attribute('href') - end - - doc.search('img').each do |el| - process_link_attr el.attribute('src') - end - - doc - end - - protected - - def linkable_files? - context[:project_wiki].nil? && repository.try(:exists?) && !repository.empty? - end - - 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 - rescue URI::Error - # noop - end - - def rebuild_relative_uri(uri) - file_path = relative_file_path(uri.path) - - uri.path = [ - relative_url_root, - context[:project].path_with_namespace, - path_type(file_path), - ref || context[:project].default_branch, # if no ref exists, point to the default branch - file_path - ].compact.join('/').squeeze('/').chomp('/') - - uri - end - - def relative_file_path(path) - nested_path = build_relative_path(path, context[:requested_path]) - file_exists?(nested_path) ? nested_path : path - end - - # Convert a relative path into its correct location based on the currently - # requested path - # - # path - Relative path String - # request_path - Currently-requested path String - # - # Examples: - # - # # File in the same directory as the current path - # build_relative_path("users.md", "doc/api/README.md") - # # => "doc/api/users.md" - # - # # File in the same directory, which is also the current path - # build_relative_path("users.md", "doc/api") - # # => "doc/api/users.md" - # - # # Going up one level to a different directory - # build_relative_path("../update/7.14-to-8.0.md", "doc/api/README.md") - # # => "doc/update/7.14-to-8.0.md" - # - # Returns a String - def build_relative_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' - - while parts.length > 1 && path.start_with?('../') - parts.pop - path.sub!('../', '') - end - - 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 - - # Get the type of the given path - # - # path - String path to check - # - # Examples: - # - # path_type('doc/README.md') # => 'blob' - # path_type('doc/logo.png') # => 'raw' - # path_type('doc/api') # => 'tree' - # - # Returns a String - def path_type(path) - unescaped_path = Addressable::URI.unescape(path) - - if tree?(unescaped_path) - 'tree' - elsif image?(unescaped_path) - 'raw' - else - 'blob' - end - end - - def tree?(path) - repository.tree(current_sha, path).entries.any? - end - - def image?(path) - repository.blob_at(current_sha, path).try(:image?) - end - - def current_sha - context[:commit].try(:id) || - ref ? repository.commit(ref).try(:sha) : repository.head_commit.sha - end - - def relative_url_root - Gitlab.config.gitlab.relative_url_root.presence || '/' - end - - def ref - context[:ref] - end - - def repository - context[:project].try(:repository) - end - end - end -end diff --git a/lib/gitlab/markdown/sanitization_filter.rb b/lib/gitlab/markdown/sanitization_filter.rb deleted file mode 100644 index cf153f30622..00000000000 --- a/lib/gitlab/markdown/sanitization_filter.rb +++ /dev/null @@ -1,99 +0,0 @@ -require 'gitlab/markdown' -require 'html/pipeline/filter' -require 'html/pipeline/sanitization_filter' - -module Gitlab - module Markdown - # Sanitize HTML - # - # Extends HTML::Pipeline::SanitizationFilter with a custom whitelist. - class SanitizationFilter < HTML::Pipeline::SanitizationFilter - def whitelist - # Descriptions are more heavily sanitized, allowing only a few elements. - # See http://git.io/vkuAN - if context[:inline_sanitization] - whitelist = LIMITED - whitelist[:elements] -= %w(pre code img ol ul li) - else - whitelist = super - end - - customize_whitelist(whitelist) - - whitelist - end - - private - - def customized?(transformers) - transformers.last.source_location[0] == __FILE__ - end - - def customize_whitelist(whitelist) - # Only push these customizations once - return if customized?(whitelist[:transformers]) - - # Allow code highlighting - whitelist[:attributes]['pre'] = %w(class) - whitelist[:attributes]['span'] = %w(class) - - # Allow table alignment - whitelist[:attributes]['th'] = %w(style) - whitelist[:attributes]['td'] = %w(style) - - # Allow span elements - whitelist[:elements].push('span') - - # Allow any protocol in `a` elements... - whitelist[:protocols].delete('a') - - # ...but then remove links with the `javascript` protocol - whitelist[:transformers].push(remove_javascript_links) - - # Remove `rel` attribute from `a` elements - whitelist[:transformers].push(remove_rel) - - # Remove `class` attribute from non-highlight spans - whitelist[:transformers].push(clean_spans) - - whitelist - end - - def remove_javascript_links - lambda do |env| - node = env[:node] - - return unless node.name == 'a' - return unless node.has_attribute?('href') - - if node['href'].start_with?('javascript', ':javascript') - node.remove_attribute('href') - end - end - end - - def remove_rel - lambda do |env| - if env[:node_name] == 'a' - env[:node].remove_attribute('rel') - end - end - end - - def clean_spans - lambda do |env| - node = env[:node] - - return unless node.name == 'span' - return unless node.has_attribute?('class') - - unless has_ancestor?(node, 'pre') - node.remove_attribute('class') - end - - { node_whitelist: [node] } - end - end - end - end -end diff --git a/lib/gitlab/markdown/single_line_pipeline.rb b/lib/gitlab/markdown/single_line_pipeline.rb deleted file mode 100644 index 2f24927b879..00000000000 --- a/lib/gitlab/markdown/single_line_pipeline.rb +++ /dev/null @@ -1,9 +0,0 @@ -require 'gitlab/markdown' - -module Gitlab - module Markdown - class SingleLinePipeline < GfmPipeline - - end - end -end diff --git a/lib/gitlab/markdown/snippet_reference_filter.rb b/lib/gitlab/markdown/snippet_reference_filter.rb deleted file mode 100644 index f7bd07c2a34..00000000000 --- a/lib/gitlab/markdown/snippet_reference_filter.rb +++ /dev/null @@ -1,25 +0,0 @@ -require 'gitlab/markdown' - -module Gitlab - module Markdown - # HTML filter that replaces snippet references with links. References to - # snippets that do not exist are ignored. - # - # This filter supports cross-project references. - class SnippetReferenceFilter < AbstractReferenceFilter - def self.object_class - Snippet - end - - def find_object(project, id) - project.snippets.find_by(id: id) - end - - def url_for_object(snippet, project) - h = Gitlab::Application.routes.url_helpers - h.namespace_project_snippet_url(project.namespace, project, snippet, - only_path: context[:only_path]) - end - end - end -end diff --git a/lib/gitlab/markdown/syntax_highlight_filter.rb b/lib/gitlab/markdown/syntax_highlight_filter.rb deleted file mode 100644 index 8597e02f0de..00000000000 --- a/lib/gitlab/markdown/syntax_highlight_filter.rb +++ /dev/null @@ -1,45 +0,0 @@ -require 'gitlab/markdown' -require 'html/pipeline/filter' -require 'rouge/plugins/redcarpet' - -module Gitlab - module Markdown - # HTML Filter to highlight fenced code blocks - # - class SyntaxHighlightFilter < HTML::Pipeline::Filter - include Rouge::Plugins::Redcarpet - - def call - doc.search('pre > code').each do |node| - highlight_node(node) - end - - doc - end - - def highlight_node(node) - language = node.attr('class') - code = node.text - - begin - highlighted = block_code(code, language) - rescue - # Gracefully handle syntax highlighter bugs/errors to ensure - # users can still access an issue/comment/etc. - highlighted = "
    #{code}
    " - end - - # Replace the parent `pre` element with the entire highlighted block - node.parent.replace(highlighted) - end - - private - - # Override Rouge::Plugins::Redcarpet#rouge_formatter - def rouge_formatter(lexer) - Rouge::Formatters::HTMLGitlab.new( - cssclass: "code highlight js-syntax-highlight #{lexer.tag}") - end - end - end -end diff --git a/lib/gitlab/markdown/table_of_contents_filter.rb b/lib/gitlab/markdown/table_of_contents_filter.rb deleted file mode 100644 index bbb3bf7fc8b..00000000000 --- a/lib/gitlab/markdown/table_of_contents_filter.rb +++ /dev/null @@ -1,63 +0,0 @@ -require 'gitlab/markdown' -require 'html/pipeline/filter' - -module Gitlab - module Markdown - # HTML filter that adds an anchor child element to all Headers in a - # document, so that they can be linked to. - # - # Generates the Table of Contents with links to each header. See Results. - # - # Based on HTML::Pipeline::TableOfContentsFilter. - # - # Context options: - # :no_header_anchors - Skips all processing done by this filter. - # - # Results: - # :toc - String containing Table of Contents data as a `ul` element with - # `li` child elements. - class TableOfContentsFilter < HTML::Pipeline::Filter - PUNCTUATION_REGEXP = /[^\p{Word}\- ]/u - - def call - return doc if context[:no_header_anchors] - - result[:toc] = "" - - headers = Hash.new(0) - - doc.css('h1, h2, h3, h4, h5, h6').each do |node| - text = node.text - - id = text.downcase - id.gsub!(PUNCTUATION_REGEXP, '') # remove punctuation - id.gsub!(' ', '-') # replace spaces with dash - id.squeeze!('-') # replace multiple dashes with one - - uniq = (headers[id] > 0) ? "-#{headers[id]}" : '' - headers[id] += 1 - - if header_content = node.children.first - href = "#{id}#{uniq}" - push_toc(href, text) - header_content.add_previous_sibling(anchor_tag(href)) - end - end - - result[:toc] = %Q{
      \n#{result[:toc]}
    } unless result[:toc].empty? - - doc - end - - private - - def anchor_tag(href) - %Q{} - end - - def push_toc(href, text) - result[:toc] << %Q{
  • #{text}
  • \n} - end - end - end -end diff --git a/lib/gitlab/markdown/task_list_filter.rb b/lib/gitlab/markdown/task_list_filter.rb deleted file mode 100644 index 2f133ae8500..00000000000 --- a/lib/gitlab/markdown/task_list_filter.rb +++ /dev/null @@ -1,24 +0,0 @@ -require 'gitlab/markdown' -require 'task_list/filter' - -module Gitlab - module Markdown - # Work around a bug in the default TaskList::Filter that adds a `task-list` - # class to every list element, regardless of whether or not it contains a - # task list. - # - # This is a (hopefully) temporary fix, pending a new release of the - # task_list gem. - # - # See https://github.com/github/task_list/pull/60 - class TaskListFilter < TaskList::Filter - def add_css_class(node, *new_class_names) - if new_class_names.include?('task-list') - super if node.children.any? { |c| c['class'] == 'task-list-item' } - else - super - end - end - end - end -end diff --git a/lib/gitlab/markdown/upload_link_filter.rb b/lib/gitlab/markdown/upload_link_filter.rb deleted file mode 100644 index fbada73ab86..00000000000 --- a/lib/gitlab/markdown/upload_link_filter.rb +++ /dev/null @@ -1,47 +0,0 @@ -require 'gitlab/markdown' -require 'html/pipeline/filter' -require 'uri' - -module Gitlab - module Markdown - # HTML filter that "fixes" relative upload links to files. - # Context options: - # :project (required) - Current project - # - class UploadLinkFilter < HTML::Pipeline::Filter - def call - 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 - - doc - end - - protected - - def process_link_attr(html_attr) - return if html_attr.blank? - - uri = html_attr.value - if uri.starts_with?("/uploads/") - html_attr.value = build_url(uri).to_s - end - end - - def build_url(uri) - File.join(Gitlab.config.gitlab.url, context[:project].path_with_namespace, uri) - end - - # Ensure that a :project key exists in context - # - # Note that while the key might exist, its value could be nil! - def validate - needs :project - end - end - end -end diff --git a/lib/gitlab/markdown/user_reference_filter.rb b/lib/gitlab/markdown/user_reference_filter.rb deleted file mode 100644 index ab5e1f6fe9e..00000000000 --- a/lib/gitlab/markdown/user_reference_filter.rb +++ /dev/null @@ -1,125 +0,0 @@ -require 'gitlab/markdown' - -module Gitlab - module Markdown - # HTML filter that replaces user or group references with links. - # - # A special `@all` reference is also supported. - class UserReferenceFilter < ReferenceFilter - # Public: Find `@user` user references in text - # - # UserReferenceFilter.references_in(text) do |match, username| - # "@#{user}" - # end - # - # text - String text to search. - # - # Yields the String match, and the String user name. - # - # Returns a String replaced with the return of the block. - def self.references_in(text) - text.gsub(User.reference_pattern) do |match| - yield match, $~[:user] - end - end - - def self.referenced_by(node) - if node.has_attribute?('data-group') - group = Group.find(node.attr('data-group')) rescue nil - return unless group - - { user: group.users } - elsif node.has_attribute?('data-user') - { user: LazyReference.new(User, node.attr('data-user')) } - elsif node.has_attribute?('data-project') - project = Project.find(node.attr('data-project')) rescue nil - return unless project - - { user: project.team.members.flatten } - end - end - - def self.user_can_reference?(user, node, context) - if node.has_attribute?('data-group') - group = Group.find(node.attr('data-group')) rescue nil - Ability.abilities.allowed?(user, :read_group, group) - else - super - end - end - - def call - replace_text_nodes_matching(User.reference_pattern) do |content| - user_link_filter(content) - end - end - - # Replace `@user` user references in text with links to the referenced - # user's profile page. - # - # text - String text to replace references in. - # - # Returns a String with `@user` references replaced with links. All links - # have `gfm` and `gfm-project_member` class names attached for styling. - def user_link_filter(text) - self.class.references_in(text) do |match, username| - if username == 'all' - link_to_all - elsif namespace = Namespace.find_by(path: username) - link_to_namespace(namespace) || match - else - match - end - end - end - - private - - def urls - Gitlab::Application.routes.url_helpers - end - - def link_class - reference_class(:project_member) - end - - def link_to_all - project = context[:project] - url = urls.namespace_project_url(project.namespace, project, - only_path: context[:only_path]) - data = data_attribute(project: project.id) - text = User.reference_prefix + 'all' - - link_tag(url, data, text) - end - - def link_to_namespace(namespace) - if namespace.is_a?(Group) - link_to_group(namespace.path, namespace) - else - link_to_user(namespace.path, namespace) - end - end - - def link_to_group(group, namespace) - url = urls.group_url(group, only_path: context[:only_path]) - data = data_attribute(group: namespace.id) - text = Group.reference_prefix + group - - link_tag(url, data, text) - end - - def link_to_user(user, namespace) - url = urls.user_url(user, only_path: context[:only_path]) - data = data_attribute(user: namespace.owner_id) - text = User.reference_prefix + user - - link_tag(url, data, text) - end - - def link_tag(url, data, text) - %(#{text}) - end - end - end -end diff --git a/lib/gitlab/reference_extractor.rb b/lib/gitlab/reference_extractor.rb index e5cafebdbc0..e83167fa7d7 100644 --- a/lib/gitlab/reference_extractor.rb +++ b/lib/gitlab/reference_extractor.rb @@ -33,8 +33,7 @@ module Gitlab # # Returns the results Array for the requested filter type def pipeline_result(filter_type) - klass = "#{filter_type.to_s.camelize}ReferenceFilter" - filter = Gitlab::Markdown.const_get(klass) + filter = Gitlab::Markdown::ReferenceFilter[filter_type] context = { pipeline: :reference_extraction, -- cgit v1.2.1 From 611912fe6870cac43fef9eea389f1229945f186a Mon Sep 17 00:00:00 2001 From: Robert Speicher Date: Mon, 7 Dec 2015 19:40:46 -0500 Subject: Store the demodulized reference filter name in data attribute --- lib/gitlab/markdown/filter/reference_gatherer_filter.rb | 2 +- lib/gitlab/markdown/reference_filter.rb | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) (limited to 'lib') diff --git a/lib/gitlab/markdown/filter/reference_gatherer_filter.rb b/lib/gitlab/markdown/filter/reference_gatherer_filter.rb index 00f983675e6..62f241b4967 100644 --- a/lib/gitlab/markdown/filter/reference_gatherer_filter.rb +++ b/lib/gitlab/markdown/filter/reference_gatherer_filter.rb @@ -31,7 +31,7 @@ module Gitlab return unless node.has_attribute?('data-reference-filter') reference_type = node.attr('data-reference-filter') - reference_filter = reference_type.constantize + reference_filter = Gitlab::Markdown.const_get(reference_type) return if context[:reference_filter] && reference_filter != context[:reference_filter] diff --git a/lib/gitlab/markdown/reference_filter.rb b/lib/gitlab/markdown/reference_filter.rb index 1b741d647d1..3b83b8bd8f8 100644 --- a/lib/gitlab/markdown/reference_filter.rb +++ b/lib/gitlab/markdown/reference_filter.rb @@ -57,14 +57,14 @@ module Gitlab # Examples: # # data_attribute(project: 1, issue: 2) - # # => "data-reference-filter=\"Gitlab::Markdown::SomeReferenceFilter\" data-project=\"1\" data-issue=\"2\"" + # # => "data-reference-filter=\"SomeReferenceFilter\" data-project=\"1\" data-issue=\"2\"" # # data_attribute(project: 3, merge_request: 4) - # # => "data-reference-filter=\"Gitlab::Markdown::SomeReferenceFilter\" data-project=\"3\" data-merge-request=\"4\"" + # # => "data-reference-filter=\"SomeReferenceFilter\" data-project=\"3\" data-merge-request=\"4\"" # # Returns a String def data_attribute(attributes = {}) - attributes[:reference_filter] = self.class.name + attributes[:reference_filter] = self.class.name.demodulize attributes.map { |key, value| %Q(data-#{key.to_s.dasherize}="#{value}") }.join(" ") end -- cgit v1.2.1 From 13f44822d97e643b55047cf619f7bb1f354f165b Mon Sep 17 00:00:00 2001 From: Douwe Maan Date: Tue, 8 Dec 2015 12:29:48 +0100 Subject: Move CombinedPipeline methods around --- lib/gitlab/markdown/combined_pipeline.rb | 12 +++++++----- lib/gitlab/markdown/pipeline/full_pipeline.rb | 2 +- 2 files changed, 8 insertions(+), 6 deletions(-) (limited to 'lib') diff --git a/lib/gitlab/markdown/combined_pipeline.rb b/lib/gitlab/markdown/combined_pipeline.rb index a3d717550f5..6b08a5e9f72 100644 --- a/lib/gitlab/markdown/combined_pipeline.rb +++ b/lib/gitlab/markdown/combined_pipeline.rb @@ -7,16 +7,18 @@ module Gitlab Class.new(Pipeline) do const_set :PIPELINES, pipelines + def self.pipelines + self::PIPELINES + end + def self.filters pipelines.flat_map(&:filters) end def self.transform_context(context) - pipelines.reduce(context) { |context, pipeline| pipeline.transform_context(context) } - end - - def self.pipelines - self::PIPELINES + pipelines.reduce(context) do |context, pipeline| + pipeline.transform_context(context) + end end end end diff --git a/lib/gitlab/markdown/pipeline/full_pipeline.rb b/lib/gitlab/markdown/pipeline/full_pipeline.rb index 553e9367c1c..b3b7a3c27c0 100644 --- a/lib/gitlab/markdown/pipeline/full_pipeline.rb +++ b/lib/gitlab/markdown/pipeline/full_pipeline.rb @@ -3,7 +3,7 @@ require 'gitlab/markdown' module Gitlab module Markdown class FullPipeline < CombinedPipeline.new(PlainMarkdownPipeline, GfmPipeline) - + end end end -- cgit v1.2.1 From 1a10945066d0da1801bb4cf89ce5f54996f1756f Mon Sep 17 00:00:00 2001 From: Douwe Maan Date: Tue, 8 Dec 2015 13:40:23 +0100 Subject: Fix RedactorFilter --- lib/gitlab/markdown/filter/redactor_filter.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'lib') diff --git a/lib/gitlab/markdown/filter/redactor_filter.rb b/lib/gitlab/markdown/filter/redactor_filter.rb index bea714a01e7..33ef7ce18b5 100644 --- a/lib/gitlab/markdown/filter/redactor_filter.rb +++ b/lib/gitlab/markdown/filter/redactor_filter.rb @@ -27,7 +27,7 @@ module Gitlab def user_can_reference?(node) if node.has_attribute?('data-reference-filter') reference_type = node.attr('data-reference-filter') - reference_filter = reference_type.constantize + reference_filter = Gitlab::Markdown.const_get(reference_type) reference_filter.user_can_reference?(current_user, node, context) else -- cgit v1.2.1