diff --git a/app/helpers/gitlab_markdown_helper.rb b/app/helpers/gitlab_markdown_helper.rb index aa1de2f50ef..aff7011edd0 100644 --- a/app/helpers/gitlab_markdown_helper.rb +++ b/app/helpers/gitlab_markdown_helper.rb @@ -74,6 +74,7 @@ module GitlabMarkdownHelper end end + # TODO (rspeicher): This should be its own filter def create_relative_links(text) paths = extract_paths(text) diff --git a/app/helpers/issues_helper.rb b/app/helpers/issues_helper.rb index ad4a7612724..c3b4731dff3 100644 --- a/app/helpers/issues_helper.rb +++ b/app/helpers/issues_helper.rb @@ -108,4 +108,7 @@ module IssuesHelper xml.summary issue.title end end + + # Required for Gitlab::Markdown::IssueReferenceFilter + module_function :url_for_issue, :title_for_issue end diff --git a/app/helpers/labels_helper.rb b/app/helpers/labels_helper.rb index 32ef2e7ca84..8272c177d59 100644 --- a/app/helpers/labels_helper.rb +++ b/app/helpers/labels_helper.rb @@ -1,4 +1,6 @@ module LabelsHelper + include ActionView::Helpers::TagHelper + def project_label_names @project.labels.pluck(:title) end @@ -7,9 +9,13 @@ module LabelsHelper label_color = label.color || Label::DEFAULT_COLOR text_color = text_color_for_bg(label_color) - content_tag :span, class: 'label color-label', style: "background-color:#{label_color};color:#{text_color}" do - label.name - end + # Intentionally not using content_tag here so that this method can be called + # by LabelReferenceFilter + span = %() + + escape_once(label.name) + '' + + span.html_safe end def suggested_colors @@ -42,13 +48,16 @@ module LabelsHelper r, g, b = bg_color.slice(1,7).scan(/.{2}/).map(&:hex) if (r + g + b) > 500 - "#333" + '#333333' else - "#FFF" + '#FFFFFF' end end def project_labels_options(project) options_from_collection_for_select(project.labels, 'name', 'name', params[:label_name]) end + + # Required for Gitlab::Markdown::LabelReferenceFilter + module_function :render_colored_label, :text_color_for_bg, :escape_once end diff --git a/app/models/label.rb b/app/models/label.rb index 1f22ed23d42..eee28acefc1 100644 --- a/app/models/label.rb +++ b/app/models/label.rb @@ -27,7 +27,7 @@ class Label < ActiveRecord::Base # Don't allow '?', '&', and ',' for label titles validates :title, presence: true, - format: { with: /\A[^&\?,&]+\z/ }, + format: { with: /\A[^&\?,]+\z/ }, uniqueness: { scope: :project_id } default_scope { order(title: :asc) } diff --git a/doc/markdown/markdown.md b/doc/markdown/markdown.md index 1d5fd4c8b0d..8ec5a20035f 100644 --- a/doc/markdown/markdown.md +++ b/doc/markdown/markdown.md @@ -163,7 +163,7 @@ Consult the [Emoji Cheat Sheet](http://emoji.codes) for a list of all supported ## Special GitLab References -GFM recognized special references. +GFM recognizes special references. You can easily reference e.g. an issue, a commit, a team member or even the whole team within a project. @@ -171,19 +171,30 @@ GFM will turn that reference into a link so you can navigate between them easily GFM will recognize the following: -- @foo : for specific team members or groups -- @all : for the whole team -- #123 : for issues -- !123 : for merge requests -- $123 : for snippets -- 1234567 : for commits -- \[file\](path/to/file) : for file references +| input | references | +|-----------------------:|:---------------------------| +| `@user_name` | specific user | +| `@group_name` | specific group | +| `@all` | entire team | +| `#123` | issue | +| `!123` | merge request | +| `$123` | snippet | +| `~123` | label by ID | +| `~bug` | one-word label by name | +| `~"feature request"` | multi-word label by name | +| `9ba12248` | specific commit | +| `9ba12248...b19a04f5` | commit range comparison | +| `[README](doc/README)` | repository file references | -GFM also recognizes references to commits, issues, and merge requests in other projects: +GFM also recognizes certain cross-project references: -- namespace/project#123 : for issues -- namespace/project!123 : for merge requests -- namespace/project@1234567 : for commits +| input | references | +|----------------------------------------:|:------------------------| +| `namespace/project#123` | issue | +| `namespace/project!123` | merge request | +| `namespace/project$123` | snippet | +| `namespace/project@9ba12248` | specific commit | +| `namespace/project@9ba12248...b19a04f5` | commit range comparison | ## Task Lists diff --git a/lib/gitlab/markdown.rb b/lib/gitlab/markdown.rb index 47c456d8dc7..f2302015437 100644 --- a/lib/gitlab/markdown.rb +++ b/lib/gitlab/markdown.rb @@ -10,11 +10,11 @@ module Gitlab # Supported reference formats are: # * @foo for team members # * #123 for issues - # * #JIRA-123 for Jira issues + # * JIRA-123 for Jira issues # * !123 for merge requests # * $123 for snippets - # * 123456 for commits - # * 123456...7890123 for commit ranges (comparisons) + # * 1c002d for specific commit + # * 1c002d...35cfb2 for commit ranges (comparisons) # # It also parses Emoji codes to insert images. See # http://www.emoji-cheat-sheet.com/ for a list of the supported icons. @@ -30,10 +30,6 @@ module Gitlab # >> gfm(":trollface:") # => "\":trollface:\" module Markdown - include IssuesHelper - - attr_reader :options, :html_options - # Public: Parse the provided text with GitLab-Flavored Markdown # # text - the source text @@ -65,42 +61,14 @@ module Gitlab reference_only_path: true ) - @options = options - @html_options = html_options - - # TODO: add popups with additional information - - # Used markdown pipelines in GitLab: - # GitlabEmojiFilter - performs emoji replacement. - # SanitizationFilter - remove unsafe HTML tags and attributes - # - # see https://gitlab.com/gitlab-org/html-pipeline-gitlab for more filters - filters = [ - HTML::Pipeline::Gitlab::GitlabEmojiFilter, - HTML::Pipeline::SanitizationFilter - ] - - whitelist = HTML::Pipeline::SanitizationFilter::WHITELIST - whitelist[:attributes][:all].push('class', 'id') - whitelist[:elements].push('span') - - # Remove the rel attribute that the sanitize gem adds, and remove the - # href attribute if it contains inline javascript - fix_anchors = lambda do |env| - name, node = env[:node_name], env[:node] - if name == 'a' - node.remove_attribute('rel') - if node['href'] && node['href'].match('javascript:') - node.remove_attribute('href') - end - end - end - whitelist[:transformers].push(fix_anchors) - markdown_context = { - asset_root: Gitlab.config.gitlab.url, - asset_host: Gitlab::Application.config.asset_host, - whitelist: whitelist + asset_root: Gitlab.config.gitlab.url, + asset_host: Gitlab::Application.config.asset_host, + whitelist: sanitization_whitelist, + reference_class: html_options[:class], + only_path: options[:reference_only_path], + current_user: current_user, + project: project } markdown_pipeline = HTML::Pipeline::Gitlab.new(filters).pipeline @@ -114,21 +82,6 @@ module Gitlab text = result[:output].to_html(save_with: save_options) - # Extract pre blocks so they are not altered - # from http://github.github.com/github-flavored-markdown/ - text.gsub!(%r{
.*?
|.*?}m) { |match| extract_piece(match) } - # Extract links with probably parsable hrefs - text.gsub!(%r{.*?}m) { |match| extract_piece(match) } - # Extract images with probably parsable src - text.gsub!(%r{}m) { |match| extract_piece(match) } - - text = parse(text, project) - - # Insert pre block extractions - text.gsub!(/\{gfm-extraction-(\h{32})\}/) do - insert_piece($1) - end - if options[:parse_tasks] text = parse_tasks(text) end @@ -138,242 +91,53 @@ module Gitlab private - def extract_piece(text) - @extractions ||= {} - - md5 = Digest::MD5.hexdigest(text) - @extractions[md5] = text - "{gfm-extraction-#{md5}}" - end - - def insert_piece(id) - @extractions[id] - end - - # Private: Parses text for references + # Custom filters for html-pipeline: # - # text - Text to parse + # SanitizationFilter should come first so that all generated reference HTML + # goes through untouched. # - # Returns parsed text - def parse(text, project = @project) - parse_references(text, project) if project + # See https://gitlab.com/gitlab-org/html-pipeline-gitlab for more filters + def filters + [ + HTML::Pipeline::SanitizationFilter, - text + Gitlab::Markdown::UserReferenceFilter, + Gitlab::Markdown::IssueReferenceFilter, + Gitlab::Markdown::ExternalIssueReferenceFilter, + Gitlab::Markdown::MergeRequestReferenceFilter, + Gitlab::Markdown::SnippetReferenceFilter, + Gitlab::Markdown::CommitRangeReferenceFilter, + Gitlab::Markdown::CommitReferenceFilter, + Gitlab::Markdown::LabelReferenceFilter, + + HTML::Pipeline::Gitlab::GitlabEmojiFilter + ] end - NAME_STR = Gitlab::Regex::NAMESPACE_REGEX_STR - PROJ_STR = "(?#{NAME_STR}/#{NAME_STR})" - - REFERENCE_PATTERN = %r{ - (?\W)? # Prefix - ( # Reference - @(?#{NAME_STR}) # User name - |~(?