#{text}"
diff --git a/app/helpers/gitlab_markdown_helper.rb b/app/helpers/gitlab_markdown_helper.rb
new file mode 100644
index 000000000..6a9ad45e9
--- /dev/null
+++ b/app/helpers/gitlab_markdown_helper.rb
@@ -0,0 +1,62 @@
+# This module is based on
+# https://github.com/gitlabhq/gitlabhq/blob/7665b1de7eed4addd7b94786c84e6674710e6377/app/helpers/gitlab_markdown_helper.rb
+module GitlabMarkdownHelper
+ include Modules::Models::Markdown
+
+ # Use this in places where you would normally use link_to(gfm(...), ...).
+ #
+ # It solves a problem occurring with nested links (i.e.
+ # "
outer text gfm ref more outer text"). This will not be
+ # interpreted as intended. Browsers will parse something like
+ # "
outer text gfm ref more outer text" (notice the last part is
+ # not linked any more). link_to_gfm corrects that. It wraps all parts to
+ # explicitly produce the correct linking behavior (i.e.
+ # "
outer text gfm ref more outer text").
+ def link_to_gfm(body, url, html_options = {})
+ return "" if body.blank?
+
+ escaped_body = if body =~ /^\
.*?}m) do |match|
+ "#{match}#{link_to("", url, html_options)[0..-5]}" # "".length +1
+ end
+
+ link_to(gfm_body.html_safe, url, html_options)
+ end
+
+ def markdown(text)
+ unless @markdown
+ gitlab_renderer = Redcarpet::Render::GitlabHTML.new(self,
+ # see https://github.com/vmg/redcarpet#darling-i-packed-you-a-couple-renderers-for-lunch-
+ filter_html: true,
+ with_toc_data: true,
+ hard_wrap: true)
+ @markdown = Redcarpet::Markdown.new(gitlab_renderer,
+ # see https://github.com/vmg/redcarpet#and-its-like-really-simple-to-use
+ no_intra_emphasis: true,
+ tables: true,
+ fenced_code_blocks: true,
+ autolink: true,
+ strikethrough: true,
+ lax_html_blocks: true,
+ space_after_headers: true,
+ superscript: true)
+ end
+
+ @markdown.render(text).html_safe
+ end
+
+ def render_wiki_content(wiki_page)
+ if wiki_page.format == :markdown
+ markdown(wiki_page.content)
+ else
+ wiki_page.formatted_content.html_safe
+ end
+ end
+end
diff --git a/config/initializers/setup.rb b/config/initializers/setup.rb
index bb9d9b5c0..39f5bf3f3 100644
--- a/config/initializers/setup.rb
+++ b/config/initializers/setup.rb
@@ -10,3 +10,6 @@ Rosa::Application.config.middleware.insert_after ::Rails::Rack::Logger, ::Grack:
Rosa::Application.config.middleware.insert_before ::Grack::Handler, ::Grack::Auth
Rosa::Application.config.action_mailer.default_url_options = { :host => APP_CONFIG['action_mailer_host'] } if APP_CONFIG['action_mailer_host']
+
+# Workaround for https://github.com/github/gemoji/pull/18
+Rosa::Application.config.assets.paths << Emoji.images_path
\ No newline at end of file
diff --git a/config/locales/models/issue.en.yml b/config/locales/models/issue.en.yml
index c06dfd908..b9930d4e2 100644
--- a/config/locales/models/issue.en.yml
+++ b/config/locales/models/issue.en.yml
@@ -1,5 +1,7 @@
en:
activerecord:
+ models:
+ issue: Issue
attributes:
issue:
title: Name
diff --git a/config/locales/models/issue.ru.yml b/config/locales/models/issue.ru.yml
index 1e1e3c190..51bd8db5e 100644
--- a/config/locales/models/issue.ru.yml
+++ b/config/locales/models/issue.ru.yml
@@ -1,5 +1,7 @@
ru:
activerecord:
+ models:
+ issue: Задача
attributes:
issue:
title: Название
diff --git a/config/locales/models/pull_request.en.yml b/config/locales/models/pull_request.en.yml
index 156bf8192..a27122c72 100644
--- a/config/locales/models/pull_request.en.yml
+++ b/config/locales/models/pull_request.en.yml
@@ -45,6 +45,8 @@ en:
save_error: Unable to save pull request
activerecord:
+ models:
+ pull_request: Pull Request
attributes:
pull_requests:
to_ref: Source
diff --git a/config/locales/models/pull_request.ru.yml b/config/locales/models/pull_request.ru.yml
index 26dd0f761..e67132b06 100644
--- a/config/locales/models/pull_request.ru.yml
+++ b/config/locales/models/pull_request.ru.yml
@@ -46,6 +46,8 @@ ru:
save_error: Не удалось сохранить пул реквест
activerecord:
+ models:
+ pull_request: Пул реквест
attributes:
pull_requests:
to_ref: Приемник
diff --git a/lib/modules/models/markdown.rb b/lib/modules/models/markdown.rb
new file mode 100644
index 000000000..743976da1
--- /dev/null
+++ b/lib/modules/models/markdown.rb
@@ -0,0 +1,192 @@
+# This module is based on
+# https://github.com/gitlabhq/gitlabhq/blob/397c3da9758c03a215a308c011f94261d9c61cfa/lib/gitlab/markdown.rb
+module Modules
+ module Models
+ # Custom parser for GitLab-flavored Markdown
+ #
+ # It replaces references in the text with links to the appropriate items in
+ # GitLab.
+ #
+ # Supported reference formats are:
+ # * @foo for team members
+ # * #123 for issues
+ # * !123 for pull requests
+ # * 123456 for commits
+ #
+ # It also parses Emoji codes to insert images. See
+ # http://www.emoji-cheat-sheet.com/ for a list of the supported icons.
+ #
+ # Examples
+ #
+ # >> gfm("Hey @david, can you fix this?")
+ # => "Hey
@david, can you fix this?"
+ #
+ # >> gfm("Commit 35d5f7c closes #1234")
+ # => "Commit
35d5f7c closes
#1234"
+ #
+ # >> gfm(":trollface:")
+ # => "
+ module Markdown
+ include IssuesHelper
+
+ attr_reader :html_options
+
+ # Public: Parse the provided text with GitLab-Flavored Markdown
+ #
+ # text - the source text
+ # html_options - extra options for the reference links as given to link_to
+ #
+ # Note: reference links will only be generated if @project is set
+ def gfm(text, html_options = {})
+ return text if text.nil?
+
+ # Duplicate the string so we don't alter the original, then call to_str
+ # to cast it back to a String instead of a SafeBuffer. This is required
+ # for gsub calls to work as we need them to.
+ text = text.dup.to_str
+
+ @html_options = html_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) }
+
+ # TODO: add popups with additional information
+
+ text = parse(text)
+
+ # Insert pre block extractions
+ text.gsub!(/\{gfm-extraction-(\h{32})\}/) do
+ insert_piece($1)
+ end
+
+ sanitize text.html_safe, attributes: ActionView::Base.sanitized_allowed_attributes + %w(id class)
+ end
+
+ 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 and emoji
+ #
+ # text - Text to parse
+ #
+ # Note: reference links will only be generated if @project is set
+ #
+ # Returns parsed text
+ def parse(text)
+ parse_references(text) if @project
+ parse_emoji(text)
+
+ text
+ end
+
+ REFERENCE_PATTERN = %r{
+ (?\W)? # Prefix
+ ( # Reference
+ @(?[a-zA-Z][a-zA-Z0-9_\-\.]*) # User uname
+ |\#(?\d+) # Issue ID
+ |!(?\d+) # PR ID
+ |(?[\h]{6,40}) # Commit ID
+ )
+ (?\W)? # Suffix
+ }x.freeze
+
+ TYPES = [:user, :issue, :pull_request, :commit].freeze
+
+ def parse_references(text)
+ # parse reference links
+ text.gsub!(REFERENCE_PATTERN) do |match|
+ prefix = $~[:prefix]
+ suffix = $~[:suffix]
+ type = TYPES.select{|t| !$~[t].nil?}.first
+ identifier = $~[type]
+
+ # Avoid HTML entities
+ if prefix && suffix && prefix[0] == '&' && suffix[-1] == ';'
+ match
+ elsif ref_link = reference_link(type, identifier)
+ "#{prefix}#{ref_link}#{suffix}"
+ else
+ match
+ end
+ end
+ end
+
+ EMOJI_PATTERN = %r{(:(\S+):)}.freeze
+
+ def parse_emoji(text)
+ # parse emoji
+ text.gsub!(EMOJI_PATTERN) do |match|
+ if valid_emoji?($2)
+ image_tag(image_path("emoji/#{$2}.png"), class: 'emoji', title: $1, alt: $1, size: "20x20")
+ else
+ match
+ end
+ end
+ end
+
+ # Private: Checks if an emoji icon exists in the image asset directory
+ #
+ # emoji - Identifier of the emoji as a string (e.g., "+1", "heart")
+ #
+ # Returns boolean
+ def valid_emoji?(emoji)
+ Emoji.names.include? emoji
+ end
+
+ # Private: Dispatches to a dedicated processing method based on reference
+ #
+ # reference - Object reference ("@1234", "!567", etc.)
+ # identifier - Object identifier (Issue ID, SHA hash, etc.)
+ #
+ # Returns string rendered by the processing method
+ def reference_link(type, identifier)
+ send("reference_#{type}", identifier)
+ end
+
+ def reference_user(identifier)
+ if member = @project.all_members.select {|u| u.uname == identifier} #.joins(:user).where(users: {uname: identifier}).first
+ link_to("@#{identifier}", user_path(identifier), html_options.merge(class: "gfm gfm-team_member #{html_options[:class]}")) if member
+ end
+ end
+
+ def reference_issue(identifier)
+ if issue = @project.issues.where(serial_id: identifier).first
+ url = project_issue_path(@project.owner, @project.name, issue.serial_id)
+ title = "#{Issue.model_name.human}: #{issue.title}"
+ link_to("##{identifier}", url, html_options.merge(title: title, class: "gfm gfm-issue #{html_options[:class]}"))
+ end
+ end
+
+ def reference_pull_request(identifier)
+ if pull_request = @project.pull_requests.includes(:issue).where(issues: {serial_id: identifier}).first
+ title = "#{PullRequest.model_name.human}: #{pull_request.title}"
+ link_to("!#{identifier}", project_pull_request_path(@project, pull_request), html_options.merge(title: title, class: "gfm gfm-pull_request #{html_options[:class]}"))
+ end
+ end
+
+ def reference_commit(identifier)
+ if commit = @project.repo.commit(identifier)
+ link_to shortest_hash_id(@commit.id), commit_path(options[:project], @commit.id)
+ title = GitPresenters::CommitAsMessagePresenter.present(commit, :project => @project).caption
+ link_to(identifier, commit_path(@project, commit), html_options.merge(title: title, class: "gfm gfm-commit #{html_options[:class]}"))
+ end
+ end
+ end
+ end
+end
\ No newline at end of file
diff --git a/lib/redcarpet/render/gitlab_html.rb b/lib/redcarpet/render/gitlab_html.rb
new file mode 100644
index 000000000..732d2ae7a
--- /dev/null
+++ b/lib/redcarpet/render/gitlab_html.rb
@@ -0,0 +1,39 @@
+# This class is based on
+# https://github.com/gitlabhq/gitlabhq/blob/2bc78739a7aa9d7e5109281fc45dbd41a1a576d4/lib/gitlab/markdown.rb
+class Redcarpet::Render::GitlabHTML < Redcarpet::Render::HTML
+
+ attr_reader :template
+ alias_method :h, :template
+
+ def initialize(template, options = {})
+ @template = template
+ @project = @template.instance_variable_get("@project")
+ super options
+ end
+
+ def block_code(code, language)
+ options = { options: {encoding: 'utf-8'} }
+ options.merge!(lexer: language.downcase) if Pygments::Lexer.find(language)
+
+ # New lines are placed to fix an rendering issue
+ # with code wrapped inside tag for next case:
+ #
+ # # Title kinda h1
+ #
+ # ruby code here
+ #
+ <<-HTML
+
+
#{Pygments.highlight(code, options)}
+
+ HTML
+ end
+
+ def link(link, title, content)
+ h.link_to_gfm(content, link, title: title)
+ end
+
+ def postprocess(full_document)
+ h.gfm(full_document)
+ end
+end