[#19] add gitlabhq markdown
This commit is contained in:
parent
76e19f717c
commit
e78e73cae8
7
Gemfile
7
Gemfile
|
@ -23,12 +23,11 @@ gem 'state_machine'
|
|||
gem 'grack', :git => 'git://github.com/rdblue/grack.git', :require => 'git_http'
|
||||
gem "grit", :git => 'git://github.com/warpc/grit.git' #, :path => '~/Sites/code/grit'
|
||||
gem 'charlock_holmes', '~> 0.6.9' #, :git => 'git://github.com/brianmario/charlock_holmes.git', :branch => 'bundle-icu'
|
||||
gem 'github-linguist', '~> 2.2.1', :require => 'linguist'
|
||||
gem 'github-linguist', '~> 2.3.4', :require => 'linguist'
|
||||
gem 'diff-display', '~> 0.0.1'
|
||||
|
||||
# Wiki
|
||||
gem "gollum", '~> 2.1.3'
|
||||
gem "redcarpet", "~> 2.1.1"
|
||||
gem 'creole'
|
||||
gem 'rdiscount'
|
||||
# gem 'org-ruby'
|
||||
|
@ -54,6 +53,9 @@ gem 'rest-client', '~> 1.6.6'
|
|||
|
||||
gem 'attr_encrypted', '1.2.1'
|
||||
|
||||
gem "gitlab-pygments.rb", '~> 0.3.2', require: 'pygments.rb'
|
||||
gem "redcarpet", "~> 2.2.2"
|
||||
|
||||
group :assets do
|
||||
gem 'sass-rails', '~> 3.2.5'
|
||||
gem 'coffee-rails', '~> 3.2.2'
|
||||
|
@ -62,6 +64,7 @@ group :assets do
|
|||
gem 'therubyracer', '~> 0.10.2', :platforms => [:mri, :rbx]
|
||||
gem 'therubyrhino', '~> 1.73.1', :platforms => :jruby
|
||||
gem 'turbo-sprockets-rails3'
|
||||
gem "gemoji", "~> 1.2.1", require: 'emoji/railtie'
|
||||
end
|
||||
|
||||
group :production do
|
||||
|
|
17
Gemfile.lock
17
Gemfile.lock
|
@ -121,13 +121,17 @@ GEM
|
|||
railties (>= 3.0.0)
|
||||
ffi (1.0.11)
|
||||
fssm (0.2.10)
|
||||
github-linguist (2.2.1)
|
||||
gemoji (1.2.1)
|
||||
github-linguist (2.3.4)
|
||||
charlock_holmes (~> 0.6.6)
|
||||
escape_utils (~> 0.2.3)
|
||||
mime-types (~> 1.18)
|
||||
mime-types (~> 1.19)
|
||||
pygments.rb (>= 0.2.13)
|
||||
github-markdown (0.5.3)
|
||||
github-markup (0.7.5)
|
||||
gitlab-pygments.rb (0.3.2)
|
||||
posix-spawn (~> 0.3.6)
|
||||
yajl-ruby (~> 1.1.0)
|
||||
gollum (2.1.10)
|
||||
github-markdown
|
||||
github-markup (>= 0.7.0, < 1.0.0)
|
||||
|
@ -257,7 +261,7 @@ GEM
|
|||
rdiscount (2.0.7.1)
|
||||
rdoc (3.12.2)
|
||||
json (~> 1.4)
|
||||
redcarpet (2.1.1)
|
||||
redcarpet (2.2.2)
|
||||
redis (3.0.3)
|
||||
redis-namespace (1.2.1)
|
||||
redis (~> 3.0.0)
|
||||
|
@ -370,6 +374,7 @@ GEM
|
|||
builder
|
||||
expression_parser
|
||||
will_paginate (3.0.4)
|
||||
yajl-ruby (1.1.0)
|
||||
|
||||
PLATFORMS
|
||||
ruby
|
||||
|
@ -391,7 +396,9 @@ DEPENDENCIES
|
|||
devise (~> 2.1.2)
|
||||
diff-display (~> 0.0.1)
|
||||
factory_girl_rails (~> 4.0.0)
|
||||
github-linguist (~> 2.2.1)
|
||||
gemoji (~> 1.2.1)
|
||||
github-linguist (~> 2.3.4)
|
||||
gitlab-pygments.rb (~> 0.3.2)
|
||||
gollum (~> 2.1.3)
|
||||
grack!
|
||||
grit!
|
||||
|
@ -415,7 +422,7 @@ DEPENDENCIES
|
|||
rails3-generators
|
||||
rails3-jquery-autocomplete (~> 1.0.7)
|
||||
rdiscount
|
||||
redcarpet (~> 2.1.1)
|
||||
redcarpet (~> 2.2.2)
|
||||
redhillonrails_core!
|
||||
resque (~> 1.21.0)
|
||||
resque-status (~> 0.3.3)
|
||||
|
|
|
@ -38,14 +38,14 @@ module ApplicationHelper
|
|||
end
|
||||
end
|
||||
|
||||
def markdown(text)
|
||||
unless @redcarpet
|
||||
html_options = {filter_html: true, hard_wrap: true, with_toc_data: true}
|
||||
options = {no_intraemphasis: true, tables: true, fenced_code_blocks: true, autolink: true, strikethrough: true, lax_html_blocks: true}
|
||||
@redcarpet = Redcarpet::Markdown.new(Redcarpet::Render::HTML.new(html_options), options)
|
||||
end
|
||||
@redcarpet.render(text).html_safe
|
||||
end
|
||||
#~ def markdown(text)
|
||||
#~ unless @redcarpet
|
||||
#~ html_options = {filter_html: true, hard_wrap: true, with_toc_data: true}
|
||||
#~ options = {no_intraemphasis: true, tables: true, fenced_code_blocks: true, autolink: true, strikethrough: true, lax_html_blocks: true}
|
||||
#~ @redcarpet = Redcarpet::Markdown.new(Redcarpet::Render::HTML.new(html_options), options)
|
||||
#~ end
|
||||
#~ @redcarpet.render(text).html_safe
|
||||
#~ end
|
||||
|
||||
def local_alert(text, type = 'error')
|
||||
html = "<div class='flash'><div class='alert #{type}'> #{text}"
|
||||
|
|
|
@ -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.
|
||||
# "<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
|
||||
# not linked any more). link_to_gfm corrects that. It wraps all parts to
|
||||
# explicitly produce the correct linking behavior (i.e.
|
||||
# "<a>outer text </a><a>gfm ref</a><a> more outer text</a>").
|
||||
def link_to_gfm(body, url, html_options = {})
|
||||
return "" if body.blank?
|
||||
|
||||
escaped_body = if body =~ /^\<img/
|
||||
body
|
||||
else
|
||||
escape_once(body)
|
||||
end
|
||||
|
||||
gfm_body = gfm(escaped_body, html_options)
|
||||
|
||||
gfm_body.gsub!(%r{<a.*?>.*?</a>}m) do |match|
|
||||
"</a>#{match}#{link_to("", url, html_options)[0..-5]}" # "</a>".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
|
|
@ -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
|
|
@ -1,5 +1,7 @@
|
|||
en:
|
||||
activerecord:
|
||||
models:
|
||||
issue: Issue
|
||||
attributes:
|
||||
issue:
|
||||
title: Name
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
ru:
|
||||
activerecord:
|
||||
models:
|
||||
issue: Задача
|
||||
attributes:
|
||||
issue:
|
||||
title: Название
|
||||
|
|
|
@ -45,6 +45,8 @@ en:
|
|||
save_error: Unable to save pull request
|
||||
|
||||
activerecord:
|
||||
models:
|
||||
pull_request: Pull Request
|
||||
attributes:
|
||||
pull_requests:
|
||||
to_ref: Source
|
||||
|
|
|
@ -46,6 +46,8 @@ ru:
|
|||
save_error: Не удалось сохранить пул реквест
|
||||
|
||||
activerecord:
|
||||
models:
|
||||
pull_request: Пул реквест
|
||||
attributes:
|
||||
pull_requests:
|
||||
to_ref: Приемник
|
||||
|
|
|
@ -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 <a href="/users/david">@david</a>, can you fix this?"
|
||||
#
|
||||
# >> gfm("Commit 35d5f7c closes #1234")
|
||||
# => "Commit <a href="/gitlab/commits/35d5f7c">35d5f7c</a> closes <a href="/gitlab/issues/1234">#1234</a>"
|
||||
#
|
||||
# >> gfm(":trollface:")
|
||||
# => "<img alt=\":trollface:\" class=\"emoji\" src=\"/images/trollface.png" title=\":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{<pre>.*?</pre>|<code>.*?</code>}m) { |match| extract_piece(match) }
|
||||
# Extract links with probably parsable hrefs
|
||||
text.gsub!(%r{<a.*?>.*?</a>}m) { |match| extract_piece(match) }
|
||||
# Extract images with probably parsable src
|
||||
text.gsub!(%r{<img.*?>}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{
|
||||
(?<prefix>\W)? # Prefix
|
||||
( # Reference
|
||||
@(?<user>[a-zA-Z][a-zA-Z0-9_\-\.]*) # User uname
|
||||
|\#(?<issue>\d+) # Issue ID
|
||||
|!(?<pull_request>\d+) # PR ID
|
||||
|(?<commit>[\h]{6,40}) # Commit ID
|
||||
)
|
||||
(?<suffix>\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
|
|
@ -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 <h1> tag for next case:
|
||||
#
|
||||
# # Title kinda h1
|
||||
#
|
||||
# ruby code here
|
||||
#
|
||||
<<-HTML
|
||||
|
||||
<div>#{Pygments.highlight(code, options)}</div>
|
||||
|
||||
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
|
Loading…
Reference in New Issue