Commit ed41333a authored by Douwe Maan's avatar Douwe Maan

Use Gitlab::Markdown.render with :pipeline option rather than different methods

parent 4a5b7718
......@@ -20,7 +20,7 @@ module GitlabMarkdownHelper
end
user = current_user if defined?(current_user)
gfm_body = Gitlab::Markdown.gfm(escaped_body, project: @project, current_user: user)
gfm_body = Gitlab::Markdown.render(escaped_body, project: @project, current_user: user, pipeline: :single_line)
fragment = Nokogiri::HTML::DocumentFragment.parse(gfm_body)
if fragment.children.size == 1 && fragment.children[0].name == 'a'
......@@ -48,37 +48,20 @@ module GitlabMarkdownHelper
def markdown(text, context = {})
return "" unless text.present?
context.reverse_merge!(
path: @path,
pipeline: :default,
project: @project,
project_wiki: @project_wiki,
ref: @ref
)
user = current_user if defined?(current_user)
context[:project] ||= @project
html = Gitlab::Markdown.render(text, context)
Gitlab::Markdown.post_process(html, pipeline: context[:pipeline], project: @project, user: user)
end
# TODO (rspeicher): Remove all usages of this helper and just call `markdown`
# with a custom pipeline depending on the content being rendered
def gfm(text, options = {})
return "" unless text.present?
context.merge!(
current_user: (current_user if defined?(current_user)),
options.reverse_merge!(
path: @path,
pipeline: :default,
project: @project,
project_wiki: @project_wiki,
ref: @ref
# RelativeLinkFilter
requested_path: @path,
project_wiki: @project_wiki,
ref: @ref
)
user = current_user if defined?(current_user)
html = Gitlab::Markdown.gfm(text, options)
Gitlab::Markdown.post_process(html, pipeline: options[:pipeline], project: @project, user: user)
Gitlab::Markdown.post_process(html, context)
end
def asciidoc(text)
......
......@@ -2,4 +2,4 @@
.commit-row-title
= link_to truncate_sha(commit[:id]), namespace_project_commit_path(project.namespace, project, commit[:id]), class: "commit_short_id", alt: ''
·
= gfm event_commit_title(commit[:message]), project: project
= markdown event_commit_title(commit[:message]), project: project, pipeline: :single_line
......@@ -50,10 +50,10 @@
.commit-box.gray-content-block.middle-block
%h3.commit-title
= gfm escape_once(@commit.title)
= markdown escape_once(@commit.title), pipeline: :single_line
- if @commit.description.present?
%pre.commit-description
= preserve(gfm(escape_once(@commit.description)))
= preserve(markdown(escape_once(@commit.description), pipeline: :single_line))
:coffeescript
$(".commit-info-row.branches").load("#{branches_namespace_project_commit_path(@project.namespace, @project, @commit.id)}")
......@@ -32,7 +32,7 @@
- if commit.description?
.commit-row-description.js-toggle-content
%pre
= preserve(gfm(escape_once(commit.description)))
= preserve(markdown(escape_once(commit.description), pipeline: :single_line))
.commit-row-info
= commit_author_link(commit, avatar: true, size: 24)
......
......@@ -37,7 +37,7 @@
.gray-content-block.middle-block
%h2.issue-title
= gfm escape_once(@issue.title)
= markdown escape_once(@issue.title), pipeline: :single_line
%div
- if @issue.description.present?
.description{class: can?(current_user, :update_issue, @issue) ? 'js-task-list-container' : ''}
......
.gray-content-block.middle-block
%h2.issue-title
= gfm escape_once(@merge_request.title)
= markdown escape_once(@merge_request.title), pipeline: :single_line
%div
- if @merge_request.description.present?
......
......@@ -24,4 +24,4 @@
%i.fa.fa-check
Accepting this merge request will close #{"issue".pluralize(@closes_issues.size)}
= succeed '.' do
!= gfm(issues_sentence(@closes_issues))
!= markdown issues_sentence(@closes_issues), pipeline: :gfm
......@@ -31,7 +31,7 @@
%span All issues for this milestone are closed. You may close milestone now.
%h3.issue-title
= gfm escape_once(@milestone.title)
= markdown escape_once(@milestone.title), pipeline: :single_line
%div
- if @milestone.description.present?
.description
......
......@@ -12,7 +12,7 @@
= link_to namespace_project_commits_path(@project.namespace, @project, commit.id) do
%code= commit.short_id
= image_tag avatar_icon(commit.author_email), class: "", width: 16, alt: ''
= gfm escape_once(truncate(commit.title, length: 40))
= markdown escape_once(truncate(commit.title, length: 40)), pipeline: :single_line
%td
%span.pull-right.cgray
= time_ago_with_tooltip(commit.committed_date)
......@@ -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
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
......@@ -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
......
......@@ -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
......
......@@ -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
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment