debian-mirror-gitlab/app/helpers/markup_helper.rb

Ignoring revisions in .git-blame-ignore-revs. Click here to bypass and see the normal blame view.

259 lines
8 KiB
Ruby
Raw Normal View History

2018-12-05 23:21:45 +05:30
# frozen_string_literal: true
2015-09-11 14:41:01 +05:30
require 'nokogiri'
2017-08-17 22:00:37 +05:30
module MarkupHelper
2019-09-30 21:07:59 +05:30
include ActionView::Helpers::TextHelper
2020-03-13 15:44:24 +05:30
include ActionView::Context
2017-08-17 22:00:37 +05:30
2014-09-02 18:07:02 +05:30
# Use this in places where you would normally use link_to(gfm(...), ...).
2018-03-17 18:26:18 +05:30
def link_to_markdown(body, url, html_options = {})
return '' if body.blank?
link_to_html(markdown(body, pipeline: :single_line), url, html_options)
end
def link_to_markdown_field(object, field, url, html_options = {})
rendered_field = markdown_field(object, field)
link_to_html(rendered_field, url, html_options)
end
2014-09-02 18:07:02 +05:30
# It solves a problem occurring with nested links (i.e.
# "<a>outer text <a>gfm ref</a> more outer text</a>"). This will not be
# interpreted as intended. Browsers will parse something like
# "<a>outer text </a><a>gfm ref</a> more outer text" (notice the last part is
2018-03-17 18:26:18 +05:30
# not linked any more). link_to_html corrects that. It wraps all parts to
2014-09-02 18:07:02 +05:30
# explicitly produce the correct linking behavior (i.e.
# "<a>outer text </a><a>gfm ref</a><a> more outer text</a>").
2018-03-17 18:26:18 +05:30
def link_to_html(redacted, url, html_options = {})
fragment = Nokogiri::HTML::DocumentFragment.parse(redacted)
2014-09-02 18:07:02 +05:30
2015-09-11 14:41:01 +05:30
if fragment.children.size == 1 && fragment.children[0].name == 'a'
# Fragment has only one node, and it's a link generated by `gfm`.
# Replace it with our requested link.
text = fragment.children[0].text
fragment.children[0].replace(link_to(text, url, html_options))
else
2019-12-26 22:10:19 +05:30
# Traverse the fragment's first generation of children looking for
# either pure text or emojis, wrapping anything found in the
# requested link
2015-09-11 14:41:01 +05:30
fragment.children.each do |node|
2019-12-26 22:10:19 +05:30
if node.text?
node.replace(link_to(node.text, url, html_options))
elsif node.name == 'gl-emoji'
node.replace(link_to(node.to_html.html_safe, url, html_options))
end
2015-09-11 14:41:01 +05:30
end
2014-09-02 18:07:02 +05:30
end
2015-09-25 12:07:36 +05:30
# Add any custom CSS classes to the GFM-generated reference links
if html_options[:class]
fragment.css('a.gfm').add_class(html_options[:class])
end
2015-09-11 14:41:01 +05:30
fragment.to_html.html_safe
2014-09-02 18:07:02 +05:30
end
2017-08-17 22:00:37 +05:30
# Return the first line of +text+, up to +max_chars+, after parsing the line
# as Markdown. HTML tags in the parsed output are not counted toward the
# +max_chars+ limit. If the length limit falls within a tag's contents, then
# the tag contents are truncated without removing the closing tag.
2023-03-04 22:38:38 +05:30
def first_line_in_markdown(object, attribute, max_chars = nil, is_todo: false, **options)
2020-03-13 15:44:24 +05:30
md = markdown_field(object, attribute, options.merge(post_process: false))
2019-07-07 11:18:12 +05:30
return unless md.present?
2018-03-17 18:26:18 +05:30
2023-03-04 22:38:38 +05:30
includes_code = false
2020-04-08 14:13:33 +05:30
tags = %w(a gl-emoji b strong i em pre code p span)
2023-03-04 22:38:38 +05:30
if is_todo
fragment = Nokogiri::HTML.fragment(md)
includes_code = fragment.css('code').any?
md = fragment
end
2017-08-17 22:00:37 +05:30
2022-11-25 23:54:43 +05:30
context = markdown_field_render_context(object, attribute, options)
context.reverse_merge!(truncate_visible_max_chars: max_chars || md.length)
text = prepare_for_rendering(md, context)
2018-12-05 23:21:45 +05:30
text = sanitize(
2018-03-17 18:26:18 +05:30
text,
2018-12-05 23:21:45 +05:30
tags: tags,
2019-07-07 11:18:12 +05:30
attributes: Rails::Html::WhiteListSanitizer.allowed_attributes +
2022-07-23 23:45:48 +05:30
%w(
style data-src data-name data-unicode-version data-html
data-reference-type data-project-path data-iid data-mr-title
2023-03-04 22:38:38 +05:30
data-user
2022-07-23 23:45:48 +05:30
)
2018-03-17 18:26:18 +05:30
)
2018-12-05 23:21:45 +05:30
2023-03-04 22:38:38 +05:30
# Extra span with relative positioning relative due to system font being behind
# background color when username is first word of mention
if is_todo && !includes_code
text = "<span class=\"gl-relative\">\"</span>#{text}<span class=\"gl-relative\">\"</span>"
end
2018-12-05 23:21:45 +05:30
# since <img> tags are stripped, this can leave empty <a> tags hanging around
# (as our markdown wraps images in links)
2023-03-04 22:38:38 +05:30
strip_empty_link_tags(text).html_safe
2017-08-17 22:00:37 +05:30
end
2015-09-25 12:07:36 +05:30
def markdown(text, context = {})
2017-08-17 22:00:37 +05:30
return '' unless text.present?
2015-10-24 18:46:33 +05:30
2015-12-23 02:04:40 +05:30
context[:project] ||= @project
2018-03-17 18:26:18 +05:30
context[:group] ||= @group
2023-01-13 00:05:48 +05:30
html = Markup::RenderingService.new(text, context: context, postprocess_context: postprocess_context).execute
Hamlit::RailsHelpers.preserve(html)
2016-11-03 12:29:30 +05:30
end
2015-10-24 18:46:33 +05:30
2018-03-17 18:26:18 +05:30
def markdown_field(object, field, context = {})
2016-11-03 12:29:30 +05:30
object = object.for_display if object.respond_to?(:for_display)
2017-08-17 22:00:37 +05:30
return '' unless object.present?
2015-04-26 12:48:37 +05:30
2020-03-13 15:44:24 +05:30
redacted_field_html = object.try(:"redacted_#{field}_html")
return redacted_field_html if redacted_field_html
2018-03-17 18:26:18 +05:30
2020-03-13 15:44:24 +05:30
render_markdown_field(object, field, context)
2014-09-02 18:07:02 +05:30
end
2017-08-17 22:00:37 +05:30
def markup(file_name, text, context = {})
context[:project] ||= @project
2021-06-02 17:11:27 +05:30
context[:text_source] ||= :blob
2023-01-13 00:05:48 +05:30
prepare_asciidoc_context(file_name, context)
html = Markup::RenderingService
.new(text, file_name: file_name, context: context, postprocess_context: postprocess_context)
.execute
Hamlit::RailsHelpers.preserve(html)
2016-04-02 18:10:28 +05:30
end
2018-11-20 20:47:30 +05:30
def render_wiki_content(wiki_page, context = {})
2017-08-17 22:00:37 +05:30
text = wiki_page.content
return '' unless text.present?
2022-05-07 20:08:51 +05:30
context = render_wiki_content_context(wiki_page.wiki, wiki_page, context)
2023-01-13 00:05:48 +05:30
prepare_asciidoc_context(wiki_page.path, context)
2015-04-26 12:48:37 +05:30
2023-01-13 00:05:48 +05:30
html = Markup::RenderingService
.new(text, file_name: wiki_page.path, context: context, postprocess_context: postprocess_context)
.execute
2020-03-13 15:44:24 +05:30
2023-01-13 00:05:48 +05:30
Hamlit::RailsHelpers.preserve(html)
2015-04-26 12:48:37 +05:30
end
2017-08-17 22:00:37 +05:30
# Returns the text necessary to reference `entity` across projects
#
# project - Project to reference
# entity - Object that responds to `to_reference`
#
# Examples:
#
# cross_project_reference(project, project.issues.first)
# # => 'namespace1/project1#123'
#
# cross_project_reference(project, project.merge_requests.first)
# # => 'namespace1/project1!345'
#
# Returns a String
def cross_project_reference(project, entity)
if entity.respond_to?(:to_reference)
entity.to_reference(project, full: true)
2014-09-02 18:07:02 +05:30
else
2017-08-17 22:00:37 +05:30
''
2014-09-02 18:07:02 +05:30
end
end
2015-04-26 12:48:37 +05:30
private
2021-02-22 17:27:13 +05:30
def render_wiki_content_context(wiki, wiki_page, context)
context.merge(
pipeline: :wiki,
wiki: wiki,
repository: wiki.repository,
page_slug: wiki_page.slug,
2022-05-07 20:08:51 +05:30
issuable_reference_expansion_enabled: true,
requested_path: wiki_page.path
2021-02-22 17:27:13 +05:30
).merge(render_wiki_content_context_container(wiki))
end
def render_wiki_content_context_container(wiki)
{ project: wiki.container }
end
2018-12-05 23:21:45 +05:30
def strip_empty_link_tags(text)
scrubber = Loofah::Scrubber.new do |node|
2020-04-08 14:13:33 +05:30
node.remove if node.name == 'a' && node.children.empty?
2018-12-05 23:21:45 +05:30
end
2019-07-31 22:56:46 +05:30
sanitize text, scrubber: scrubber
2018-12-05 23:21:45 +05:30
end
2016-08-24 12:49:21 +05:30
def markdown_toolbar_button(options = {})
2017-08-17 22:00:37 +05:30
data = options[:data].merge({ container: 'body' })
2022-08-27 11:52:29 +05:30
css_classes = %w[gl-button btn btn-default-tertiary btn-icon js-md has-tooltip] << options[:css_class].to_s
2016-08-24 12:49:21 +05:30
content_tag :button,
2017-08-17 22:00:37 +05:30
type: 'button',
2022-08-27 11:52:29 +05:30
class: css_classes.join(' '),
2016-08-24 12:49:21 +05:30
data: data,
title: options[:title],
aria: { label: options[:title] } do
2018-03-17 18:26:18 +05:30
sprite_icon(options[:icon])
2016-08-24 12:49:21 +05:30
end
end
2016-11-03 12:29:30 +05:30
2020-03-13 15:44:24 +05:30
def render_markdown_field(object, field, context = {})
post_process = context.delete(:post_process)
post_process = true if post_process.nil?
html = Banzai.render_field(object, field, context)
return html unless post_process
prepare_for_rendering(html, markdown_field_render_context(object, field, context))
end
def markdown_field_render_context(object, field, base_context = {})
return base_context unless object.respond_to?(:banzai_render_context)
base_context.reverse_merge(object.banzai_render_context(field))
end
2017-08-17 22:00:37 +05:30
def prepare_for_rendering(html, context = {})
return '' unless html.present?
2023-01-13 00:05:48 +05:30
context.reverse_merge!(postprocess_context)
html = Banzai.post_process(html, context)
Hamlit::RailsHelpers.preserve(html)
end
def postprocess_context
{
2018-05-09 12:01:36 +05:30
current_user: (current_user if defined?(current_user)),
2016-11-03 12:29:30 +05:30
2020-03-13 15:44:24 +05:30
# RepositoryLinkFilter and UploadLinkFilter
2022-08-27 11:52:29 +05:30
commit: @commit,
wiki: @wiki,
ref: @ref,
2017-08-17 22:00:37 +05:30
requested_path: @path
2023-01-13 00:05:48 +05:30
}
end
2016-11-03 12:29:30 +05:30
2023-01-13 00:05:48 +05:30
def prepare_asciidoc_context(file_name, context)
return unless Gitlab::MarkupHelper.asciidoc?(file_name)
2017-08-17 22:00:37 +05:30
2023-01-13 00:05:48 +05:30
context.reverse_merge!(commit: @commit, ref: @ref, requested_path: @path)
2016-11-03 12:29:30 +05:30
end
2017-08-17 22:00:37 +05:30
extend self
2014-09-02 18:07:02 +05:30
end
2021-02-22 17:27:13 +05:30
2021-06-08 01:23:25 +05:30
MarkupHelper.prepend_mod_with('MarkupHelper')