#73: merge master into branch
This commit is contained in:
commit
38ce6115c2
5
Gemfile
5
Gemfile
|
@ -23,12 +23,12 @@ 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 "redcarpet", '~> 2.2.2'
|
||||
gem 'creole'
|
||||
gem 'rdiscount'
|
||||
# gem 'org-ruby'
|
||||
|
@ -53,6 +53,7 @@ gem 'rack-throttle'
|
|||
gem 'rest-client', '~> 1.6.6'
|
||||
|
||||
gem 'attr_encrypted', '1.2.1'
|
||||
gem "gemoji", "~> 1.2.1", require: 'emoji/railtie'
|
||||
|
||||
group :assets do
|
||||
gem 'sass-rails', '~> 3.2.5'
|
||||
|
|
12
Gemfile.lock
12
Gemfile.lock
|
@ -121,10 +121,11 @@ 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)
|
||||
|
@ -257,7 +258,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)
|
||||
|
@ -391,7 +392,8 @@ 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)
|
||||
gollum (~> 2.1.3)
|
||||
grack!
|
||||
grit!
|
||||
|
@ -415,7 +417,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)
|
||||
|
|
|
@ -1909,6 +1909,15 @@ table#myTable thead tr.search th form.button_to div input {
|
|||
}
|
||||
}
|
||||
|
||||
.activity .state {
|
||||
float: right;
|
||||
padding: 3px 10px;
|
||||
margin: 3px 0;
|
||||
font-size: 12px;
|
||||
color: white;
|
||||
border-radius: 2px;
|
||||
}
|
||||
|
||||
article .activity .top {
|
||||
|
||||
.created {
|
||||
|
@ -1955,7 +1964,6 @@ article .activity .top {
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
#assigned-popup {
|
||||
z-index: 1001;
|
||||
position: absolute;
|
||||
|
@ -2007,3 +2015,7 @@ article .activity .top {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
.cm-s-default.md_and_cm p img {
|
||||
max-width: 800px;
|
||||
}
|
||||
|
|
|
@ -53,7 +53,7 @@ class Projects::CommentsController < Projects::BaseController
|
|||
end
|
||||
|
||||
def find_or_build_comment
|
||||
@comment = params[:id].present? && Comment.find(params[:id]) ||
|
||||
@comment = params[:id].present? && Comment.where(:automatic => false).find(params[:id]) ||
|
||||
current_user.comments.build(params[:comment]) {|c| c.commentable = @commentable; c.project = @project}
|
||||
end
|
||||
end
|
||||
|
|
|
@ -38,9 +38,12 @@ class Projects::IssuesController < Projects::BaseController
|
|||
end
|
||||
|
||||
def create
|
||||
@assignee_uname = params[:assignee_uname]
|
||||
@issue.user_id = current_user.id
|
||||
|
||||
unless can?(:write, @project)
|
||||
@issue.assignee_id = nil
|
||||
@issue.labelings = []
|
||||
end
|
||||
if @issue.save
|
||||
@issue.subscribe_creator(current_user.id)
|
||||
flash[:notice] = I18n.t("flash.issue.saved")
|
||||
|
@ -56,6 +59,12 @@ class Projects::IssuesController < Projects::BaseController
|
|||
end
|
||||
|
||||
def update
|
||||
unless can?(:write, @project)
|
||||
params.delete :update_labels
|
||||
[:assignee_id, :labelings, :labelings_attributes].each do |k|
|
||||
params[:issue].delete k
|
||||
end if params[:issue]
|
||||
end
|
||||
@issue.labelings.destroy_all if params[:update_labels]
|
||||
if params[:issue] && status = params[:issue][:status]
|
||||
@issue.set_close(current_user) if status == 'closed'
|
||||
|
|
|
@ -38,7 +38,7 @@ class Projects::PullRequestsController < Projects::BaseController
|
|||
authorize! :read, to_project
|
||||
|
||||
@pull = to_project.pull_requests.new pull_params
|
||||
@pull.issue.assignee_id = (params[:issue] || {})[:assignee_id]
|
||||
@pull.issue.assignee_id = (params[:issue] || {})[:assignee_id] if can?(:write, to_project)
|
||||
@pull.issue.user, @pull.issue.project, @pull.from_project = current_user, to_project, @project
|
||||
@pull.from_project_owner_uname = @pull.from_project.owner.uname
|
||||
@pull.from_project_name = @pull.from_project.name
|
||||
|
|
|
@ -38,15 +38,6 @@ 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 local_alert(text, type = 'error')
|
||||
html = "<div class='flash'><div class='alert #{type}'> #{text}"
|
||||
html << link_to('×', '#', :class => 'close close-alert', 'data-dismiss' => 'alert')
|
||||
|
|
|
@ -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
|
|
@ -1,7 +1,7 @@
|
|||
# -*- encoding : utf-8 -*-
|
||||
module PullRequestHelper
|
||||
def merge_activity comments, commits
|
||||
common_comments, pull_comments = comments.partition {|c| c.data.blank?}
|
||||
common_comments, pull_comments = comments.partition {|c| c.automatic || c.data.blank?}
|
||||
common_comments = common_comments.map{ |c| [c.created_at, c] }
|
||||
pull_comments = pull_comments.group_by(&:data).map{|data, c| [c.first.created_at, [data || {}, [c].flatten]]}
|
||||
commits = commits.map{ |c| [(c.committed_date || c.authored_date), c] }
|
||||
|
@ -23,7 +23,7 @@ module PullRequestHelper
|
|||
end
|
||||
|
||||
def pull_header pull
|
||||
str = "#{t '.header'} #{t 'from'} <span class='label-bootstrap label-info font14'> \
|
||||
str = "#{t '.header'} #{t 'from'} <span class='state label-bootstrap label-info font14'> \
|
||||
#{show_ref pull, 'from'}</span> \
|
||||
#{t 'into'} <span class='label-bootstrap label-info font14'> \
|
||||
#{show_ref pull, 'to'}</span>"
|
||||
|
|
|
@ -11,7 +11,7 @@ module UsersHelper
|
|||
elsif subject.kind_of? Group
|
||||
image_path('ava-big.png')
|
||||
else
|
||||
gravatar_url(subject.email, subject.avatar.styles[size].geometry.split('x').first)
|
||||
gravatar_url(subject.email, User::AVATAR_SIZES[size])
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -43,6 +43,7 @@ class ActivityFeedObserver < ActiveRecord::Observer
|
|||
end
|
||||
|
||||
when 'Comment'
|
||||
return if record.automatic
|
||||
if record.issue_comment?
|
||||
subscribes = record.commentable.subscribes
|
||||
subscribes.each do |subscribe|
|
||||
|
@ -67,15 +68,16 @@ class ActivityFeedObserver < ActiveRecord::Observer
|
|||
(subscribe.user.committer?(record.commentable) && subscribe.user.notifier.new_comment_commit_owner) )
|
||||
UserMailer.new_comment_notification(record, subscribe.user).deliver
|
||||
end
|
||||
ActivityFeed.create(
|
||||
:user => subscribe.user,
|
||||
:kind => 'new_comment_commit_notification',
|
||||
:data => {:user_name => record.user.name, :user_email => record.user.email, :user_id => record.user_id, :comment_body => record.body,
|
||||
:commit_message => record.commentable.message, :commit_id => record.commentable.id,
|
||||
:project_id => record.project.id, :comment_id => record.id, :project_name => record.project.name, :project_owner => record.project.owner.uname}
|
||||
)
|
||||
ActivityFeed.create(
|
||||
:user => subscribe.user,
|
||||
:kind => 'new_comment_commit_notification',
|
||||
:data => {:user_name => record.user.name, :user_email => record.user.email, :user_id => record.user_id, :comment_body => record.body,
|
||||
:commit_message => record.commentable.message, :commit_id => record.commentable.id,
|
||||
:project_id => record.project.id, :comment_id => record.id, :project_name => record.project.name, :project_owner => record.project.owner.uname}
|
||||
)
|
||||
end
|
||||
end
|
||||
Comment.create_link_on_issues_from_item(record)
|
||||
|
||||
when 'GitHook'
|
||||
return unless record.project
|
||||
|
@ -90,7 +92,6 @@ class ActivityFeedObserver < ActiveRecord::Observer
|
|||
:change_type => change_type, :project_owner => record.project.owner.uname}
|
||||
else
|
||||
if record.message # online update
|
||||
#FIXME using oldrev is a hack (only for online edit).
|
||||
last_commits, commits = [[record.newrev, record.message]], []
|
||||
else
|
||||
commits = record.project.repo.commits_between(record.oldrev, record.newrev)
|
||||
|
@ -104,6 +105,7 @@ class ActivityFeedObserver < ActiveRecord::Observer
|
|||
commits = commits[0...-3]
|
||||
options.merge!({:other_commits_count => commits.count, :other_commits => "#{commits[0].sha[0..9]}...#{commits[-1].sha[0..9]}"})
|
||||
end
|
||||
Comment.create_link_on_issues_from_item(record, last_commits) if last_commits.count > 0
|
||||
end
|
||||
options.merge!({:user_id => record.user.id, :user_name => record.user.name, :user_email => record.user.email}) if record.user
|
||||
|
||||
|
@ -170,7 +172,9 @@ class ActivityFeedObserver < ActiveRecord::Observer
|
|||
)
|
||||
end
|
||||
end
|
||||
when 'Comment'
|
||||
# dont remove outdated issues link
|
||||
Comment.create_link_on_issues_from_item(record)
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
|
|
@ -3,13 +3,14 @@ class Avatar < ActiveRecord::Base
|
|||
self.abstract_class = true
|
||||
|
||||
MAX_AVATAR_SIZE = 5.megabyte
|
||||
AVATAR_SIZES = {:micro => 16, :small => 30, :medium => 40, :big => 81}
|
||||
|
||||
has_attached_file :avatar, :styles =>
|
||||
{ :micro => { :geometry => "16x16#", :format => :jpg, :convert_options => '-strip -background white -flatten -quality 70'},
|
||||
:small => { :geometry => "30x30#", :format => :jpg, :convert_options => '-strip -background white -flatten -quality 70'},
|
||||
:medium => { :geometry => "40x40#", :format => :jpg, :convert_options => '-strip -background white -flatten -quality 70'},
|
||||
:big => { :geometry => "81x81#", :format => :jpg, :convert_options => '-strip -background white -flatten -quality 70'}
|
||||
}
|
||||
AVATAR_SIZES_HASH = {}.tap do |styles|
|
||||
AVATAR_SIZES.each do |name, size|
|
||||
styles[name] = { :geometry => "#{size}x#{size}#", :format => :jpg, :convert_options => '-strip -background white -flatten -quality 70'}
|
||||
end
|
||||
end
|
||||
has_attached_file :avatar, :styles => AVATAR_SIZES_HASH
|
||||
validates_inclusion_of :avatar_file_size, :in => (0..MAX_AVATAR_SIZE), :allow_nil => true
|
||||
|
||||
attr_accessible :avatar
|
||||
|
|
|
@ -1,5 +1,11 @@
|
|||
# -*- encoding : utf-8 -*-
|
||||
class Comment < ActiveRecord::Base
|
||||
# regexp take from http://code.google.com/p/concerto-platform/source/browse/v3/cms/lib/CodeMirror/mode/gfm/gfm.js?spec=svn861&r=861#71
|
||||
# User/Project#Num
|
||||
# User#Num
|
||||
# #Num
|
||||
ISSUES_REGEX = /(?:[a-zA-Z0-9\-_]*\/)?(?:[a-zA-Z0-9\-_]*)?[#!][0-9]+/
|
||||
|
||||
belongs_to :commentable, :polymorphic => true, :touch => true
|
||||
belongs_to :user
|
||||
belongs_to :project
|
||||
|
@ -85,7 +91,7 @@ class Comment < ActiveRecord::Base
|
|||
end
|
||||
|
||||
def pull_comment?
|
||||
return true if commentable.is_a?(Issue) && commentable.pull_request.present?
|
||||
commentable.is_a?(Issue) && commentable.pull_request.present?
|
||||
end
|
||||
|
||||
def set_additional_data params
|
||||
|
@ -129,6 +135,57 @@ class Comment < ActiveRecord::Base
|
|||
return true
|
||||
end
|
||||
|
||||
def self.create_link_on_issues_from_item item, commits = nil
|
||||
linker = item.user
|
||||
elements = if item.is_a? Comment
|
||||
[[item, item.body]]
|
||||
elsif item.is_a? GitHook
|
||||
commits
|
||||
end
|
||||
current_ability = Ability.new(linker)
|
||||
|
||||
elements.each do |element|
|
||||
element[1].scan(ISSUES_REGEX).each do |hash|
|
||||
delimiter = if hash.include? '!'
|
||||
'!'
|
||||
elsif hash.include? '#'
|
||||
'#'
|
||||
else
|
||||
raise 'Unknown delimiter for the hash tag!'
|
||||
end
|
||||
issue = Issue.find_by_hash_tag hash, current_ability, item.project, delimiter
|
||||
next unless issue
|
||||
# dont create link to the same issue
|
||||
next if item.respond_to?(:commentable) && issue == item.try(:commentable)
|
||||
find_dup = {:automatic => true, :commentable_type => issue.class.name, :commentable_id => issue.id}
|
||||
if item.is_a? GitHook
|
||||
find_dup.merge! :created_from_commit_hash => element[0].hex
|
||||
elsif item.commentable_type == 'Issue'
|
||||
find_dup.merge! :created_from_issue_id => item.commentable_id
|
||||
elsif item.commentable_type == 'Grit::Commit'
|
||||
find_dup.merge! :created_from_commit_hash => item.commentable_id
|
||||
end
|
||||
next if Comment.exists? find_dup # dont create duplicate link to issue
|
||||
|
||||
comment = linker.comments.new :body => 'automatic comment'
|
||||
comment.commentable, comment.project, comment.automatic = issue, issue.project, true
|
||||
comment.data = {:from_project_id => item.project.id}
|
||||
if item.is_a? GitHook
|
||||
next unless item.project.repo.commit element[0]
|
||||
comment.created_from_commit_hash = element[0].hex
|
||||
else
|
||||
comment.data.merge! :comment_id => item.id
|
||||
if item.commentable_type == 'Issue'
|
||||
comment.created_from_issue_id = item.commentable_id
|
||||
elsif item.commentable_type == 'Grit::Commit'
|
||||
comment.created_from_commit_hash = item.commentable_id
|
||||
end
|
||||
end
|
||||
comment.save
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
protected
|
||||
|
||||
def subscribe_on_reply
|
||||
|
|
|
@ -67,6 +67,17 @@ class Issue < ActiveRecord::Base
|
|||
recipients
|
||||
end
|
||||
|
||||
def self.find_by_hash_tag hash_tag, current_ability, project, delimiter = '#'
|
||||
hash_tag =~ /([a-zA-Z0-9\-_]*\/)?([a-zA-Z0-9\-_]*)?#{delimiter}([0-9]+)/
|
||||
owner_uname = Regexp.last_match[1].presence || Regexp.last_match[2].presence || project.owner.uname
|
||||
project_name = Regexp.last_match[1] ? Regexp.last_match[2] : project.name
|
||||
serial_id = Regexp.last_match[3]
|
||||
project = Project.find_by_owner_and_name(owner_uname.chomp('/'), project_name)
|
||||
return nil unless project
|
||||
return nil unless current_ability.can? :show, project
|
||||
project.issues.where(:serial_id => serial_id).first
|
||||
end
|
||||
|
||||
protected
|
||||
|
||||
def set_serial_id
|
||||
|
|
|
@ -181,7 +181,7 @@ class PullRequest < ActiveRecord::Base
|
|||
system 'git', 'remote', 'add', 'head', from_project.path
|
||||
end
|
||||
end
|
||||
clean # Need testing
|
||||
#clean # Need testing
|
||||
end
|
||||
|
||||
Dir.chdir(path) do
|
||||
|
|
|
@ -1,15 +1,34 @@
|
|||
# -*- encoding : utf-8 -*-
|
||||
class CommentPresenter < ApplicationPresenter
|
||||
include PullRequestHelper
|
||||
|
||||
attr_accessor :comment, :options
|
||||
attr_reader :header, :image, :date, :caption, :content, :buttons
|
||||
attr_reader :header, :image, :date, :caption, :content, :buttons, :is_reference_to_issue
|
||||
|
||||
def initialize(comment, opts = {})
|
||||
@comment = comment
|
||||
@user = comment.user
|
||||
@options = opts
|
||||
@is_reference_to_issue = !!(comment.automatic && comment.created_from_issue_id) # is it reference issue from another issue
|
||||
@comment, @user, @options = comment, comment.user, opts
|
||||
|
||||
@content = @comment.body
|
||||
unless @is_reference_to_issue
|
||||
@content = @comment.body
|
||||
else
|
||||
issue = Issue.where(:id => comment.created_from_issue_id).first
|
||||
@referenced_issue = issue.pull_request || issue
|
||||
if issue && Comment.exists?(comment.data[:comment_id])
|
||||
title = if issue == opts[:commentable]
|
||||
"#{issue.serial_id}"
|
||||
elsif issue.project.owner == opts[:commentable].project.owner
|
||||
"#{issue.project.name}##{issue.serial_id}"
|
||||
else
|
||||
"#{issue.project.name_with_owner}##{issue.serial_id}"
|
||||
end
|
||||
title = "<span style=\"color: #777;\">#{title}</span>:"
|
||||
issue_link = project_issue_path(issue.project, issue)
|
||||
@content = "<a href=\"#{issue_link}\">#{title} #{issue.title}</a>".html_safe
|
||||
else
|
||||
@content = t 'layout.comments.removed'
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def expandable?
|
||||
|
@ -17,7 +36,7 @@ class CommentPresenter < ApplicationPresenter
|
|||
end
|
||||
|
||||
def buttons?
|
||||
true
|
||||
!@is_reference_to_issue # dont show for automatic comment
|
||||
end
|
||||
|
||||
def content?
|
||||
|
@ -28,24 +47,33 @@ class CommentPresenter < ApplicationPresenter
|
|||
false
|
||||
end
|
||||
|
||||
def issue_referenced_state?
|
||||
@referenced_issue # show state of the existing referenced issue
|
||||
end
|
||||
|
||||
def buttons
|
||||
project, commentable = options[:project], options[:commentable]
|
||||
path = helpers.project_commentable_comment_path(project, commentable, comment)
|
||||
|
||||
res = [link_to(t("layout.link"), "#{helpers.project_commentable_path(project, commentable)}##{comment_anchor}", :class => "#{@options[:in_discussion].present? ? 'in_discussion_' : ''}link_to_comment").html_safe]
|
||||
res = [link_to(t('layout.link'), "#{helpers.project_commentable_path(project, commentable)}##{comment_anchor}", :class => "#{@options[:in_discussion].present? ? 'in_discussion_' : ''}link_to_comment").html_safe]
|
||||
if controller.can? :update, @comment
|
||||
res << link_to(t("layout.edit"), path, :id => "comment-#{comment.id}", :class => "edit_comment").html_safe
|
||||
res << link_to(t('layout.edit'), path, :id => "comment-#{comment.id}", :class => "edit_comment").html_safe
|
||||
end
|
||||
if controller.can? :destroy, @comment
|
||||
res << link_to(t("layout.delete"), path, :method => "delete",
|
||||
:confirm => t("layout.comments.confirm_delete")).html_safe
|
||||
res << link_to(t('layout.delete'), path, :method => "delete",
|
||||
:confirm => t('layout.comments.confirm_delete')).html_safe
|
||||
end
|
||||
res
|
||||
end
|
||||
|
||||
def header
|
||||
res = link_to "#{@user.uname} (#{@user.name})", user_path(@user.uname)
|
||||
res += ' ' + t("layout.comments.has_commented")
|
||||
user_link = link_to @user.fullname, user_path(@user.uname)
|
||||
res = unless @is_reference_to_issue
|
||||
"#{user_link} #{t 'layout.comments.has_commented'}"
|
||||
else
|
||||
t 'layout.comments.reference', :user => user_link
|
||||
end
|
||||
res.html_safe
|
||||
end
|
||||
|
||||
def image
|
||||
|
@ -74,4 +102,12 @@ class CommentPresenter < ApplicationPresenter
|
|||
"#{before}comment#{@comment.id}"
|
||||
end
|
||||
|
||||
def issue_referenced_state
|
||||
if @referenced_issue.is_a? Issue
|
||||
statuses = {'open' => 'success', 'closed' => 'important'}
|
||||
content_tag :span, t("layout.issues.status.#{@referenced_issue.status}"), :class => "state label-bootstrap label-#{statuses[@referenced_issue.status]}"
|
||||
else
|
||||
pull_status_label @referenced_issue
|
||||
end.html_safe
|
||||
end
|
||||
end
|
||||
|
|
|
@ -2,33 +2,54 @@
|
|||
class GitPresenters::CommitAsMessagePresenter < ApplicationPresenter
|
||||
include CommitHelper
|
||||
|
||||
attr_accessor :commit, :options
|
||||
attr_reader :header, :image, :date, :caption, :content, :expandable
|
||||
attr_accessor :commit
|
||||
attr_reader :header, :image, :date, :caption, :content, :expandable, :is_reference_to_issue, :committer
|
||||
|
||||
def initialize(commit, opts = {})
|
||||
@commit = commit
|
||||
@options = opts
|
||||
comment = opts[:comment]
|
||||
@is_reference_to_issue = !!comment # is it reference issue from commit
|
||||
@project = if comment
|
||||
Project.where(:id => opts[:comment].data[:from_project_id]).first
|
||||
else
|
||||
opts[:project]
|
||||
end
|
||||
if @project
|
||||
commit = commit || @project.repo.commit(comment.created_from_commit_hash.to_s(16))
|
||||
|
||||
@committer = User.where(:email => commit.committer.email).first || commit.committer
|
||||
@commit_hash = commit.id
|
||||
@committed_date, @authored_date = commit.committed_date, commit.authored_date
|
||||
@commit_message = commit.message
|
||||
else
|
||||
@committer = t('layout.commits.unknown_committer')
|
||||
@commit_hash = comment.created_from_commit_hash
|
||||
@committed_date = @authored_date = comment.created_at
|
||||
@commit_message = t('layout.commits.deleted')
|
||||
end
|
||||
prepare_message
|
||||
end
|
||||
|
||||
def header
|
||||
@header ||= if options[:project].present?
|
||||
I18n.t("layout.messages.commits.header",
|
||||
:committer => committer_link, :commit => commit_link, :project => options[:project].name)
|
||||
end.html_safe
|
||||
@header ||= if @is_reference_to_issue
|
||||
I18n.t('layout.commits.reference', :committer => committer_link, :commit => commit_link)
|
||||
elsif @project.present?
|
||||
I18n.t('layout.messages.commits.header',
|
||||
:committer => committer_link, :commit => commit_link, :project => @project.name)
|
||||
end.html_safe
|
||||
end
|
||||
|
||||
def image
|
||||
c = committer
|
||||
@image ||= if c.class == User
|
||||
helpers.avatar_url(c, :medium)
|
||||
@image ||= if committer.is_a? User
|
||||
helpers.avatar_url(committer, :medium)
|
||||
elsif committer.is_a? Grit::Actor
|
||||
helpers.avatar_url_by_email(committer.email, :medium)
|
||||
else
|
||||
helpers.avatar_url_by_email(c.email, :medium)
|
||||
helpers.gravatar_url('Unknown', User::AVATAR_SIZES[:medium])
|
||||
end
|
||||
end
|
||||
|
||||
def date
|
||||
@date ||= I18n.l(@commit.committed_date || @commit.authored_date, :format => :long)
|
||||
@date ||= I18n.l(@committed_date || @authored_date, :format => :long)
|
||||
end
|
||||
|
||||
def expandable?
|
||||
|
@ -51,33 +72,39 @@ class GitPresenters::CommitAsMessagePresenter < ApplicationPresenter
|
|||
false
|
||||
end
|
||||
|
||||
def issue_referenced_state?
|
||||
false
|
||||
end
|
||||
|
||||
protected
|
||||
|
||||
def committer
|
||||
@committer ||= User.where(:email => @commit.committer.email).first || @commit.committer
|
||||
def committer_link
|
||||
@committer_link ||= if committer.is_a? User
|
||||
link_to committer.uname, user_path(committer)
|
||||
elsif committer.is_a? Grit::Actor
|
||||
mail_to committer.email, committer.name
|
||||
else # unknown committer
|
||||
committer
|
||||
end
|
||||
end
|
||||
|
||||
def committer_link
|
||||
@committer_link ||= if committer.is_a? User
|
||||
link_to committer.uname, user_path(committer)
|
||||
else
|
||||
mail_to committer.email, committer.name
|
||||
end
|
||||
def commit_link
|
||||
if @project
|
||||
link_to shortest_hash_id(@commit_hash), commit_path(@project, @commit_hash)
|
||||
else
|
||||
shortest_hash_id(@commit_hash)
|
||||
end
|
||||
end
|
||||
|
||||
def commit_link
|
||||
link_to shortest_hash_id(@commit.id), commit_path(options[:project], @commit.id)
|
||||
def prepare_message
|
||||
(@caption, @content) = @commit_message.split("\n\n", 2)
|
||||
@caption = 'empty message' unless @caption.present?
|
||||
if @caption.length > 72
|
||||
tmp = '...' + @caption[69..-1]
|
||||
@content = (@content.present?) ? tmp + @content : tmp
|
||||
@caption = @caption[0..68] + '...'
|
||||
end
|
||||
|
||||
def prepare_message
|
||||
(@caption, @content) = @commit.message.split("\n\n", 2)
|
||||
@caption = 'empty message' unless @caption.present?
|
||||
if @caption.length > 72
|
||||
tmp = '...' + @caption[69..-1]
|
||||
@content = (@content.present?) ? tmp + @content : tmp
|
||||
@caption = @caption[0..68] + '...'
|
||||
end
|
||||
# @content = @content.gsub("\n", "<br />").html_safe if @content
|
||||
@content = simple_format(@content, {}, :sanitize => true).html_safe if @content
|
||||
end
|
||||
@content = simple_format(@content, {}, :sanitize => true).html_safe if @content
|
||||
end
|
||||
end
|
||||
|
|
|
@ -13,7 +13,7 @@
|
|||
- if can? :show, @platform
|
||||
%li{:class => (act == :index && contr == :maintainers) ? 'active' : nil}
|
||||
= link_to t("layout.platforms.maintainers"), platform_maintainers_path(@platform)
|
||||
- if can? :show, @platform
|
||||
- if @platform.main? && can?(:show, @platform)
|
||||
%li{:class => (contr == :mass_builds && [:index, :create].include?(act)) ? 'active' : ''}
|
||||
= link_to t("layout.platforms.mass_build"), platform_mass_builds_path(@platform)
|
||||
- if can? :read, @platform.products.build
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
= render 'platforms/base/submenu'
|
||||
= render 'platforms/base/sidebar'
|
||||
|
||||
= render 'form' if can? :edit, @platform
|
||||
= render 'form' if can? :create, @platform.mass_builds.new
|
||||
|
||||
%table.tablesorter.unbordered{:cellpadding => "0", :cellspacing => "0"}
|
||||
%thead
|
||||
|
|
|
@ -1,10 +1,11 @@
|
|||
- CommentPresenter.present(comment, data) do |presenter|
|
||||
= render 'shared/feed_message', :presenter => presenter
|
||||
#open-comment.comment.hidden{:class => "comment-#{comment.id}"}
|
||||
=render 'projects/comments/button_md_help'
|
||||
%h3.tmargin0= t("layout.comments.edit_header")
|
||||
= form_for comment, :url => project_commentable_comment_path(data[:project], data[:commentable], comment), :html => { :class => 'form edit_comment' } do |f|
|
||||
= render "projects/comments/form", :f => f, :id => "#{data[:add_id]}edit_#{comment.id}"
|
||||
.comment-left
|
||||
=link_to t('layout.cancel'), '#', :id => "comment-#{comment.id}", :class => 'cancel_edit_comment button'
|
||||
.both
|
||||
-unless comment.automatic
|
||||
#open-comment.comment.hidden{:class => "comment-#{comment.id}"}
|
||||
=render 'projects/comments/button_md_help'
|
||||
%h3.tmargin0= t("layout.comments.edit_header")
|
||||
= form_for comment, :url => project_commentable_comment_path(data[:project], data[:commentable], comment), :html => { :class => 'form edit_comment' } do |f|
|
||||
= render "projects/comments/form", :f => f, :id => "#{data[:add_id]}edit_#{comment.id}"
|
||||
.comment-left
|
||||
=link_to t('layout.cancel'), '#', :id => "comment-#{comment.id}", :class => 'cancel_edit_comment button'
|
||||
.both
|
||||
|
|
|
@ -2,5 +2,9 @@
|
|||
.hr
|
||||
%h3#block-list= t("layout.comments.comments_header")
|
||||
- list.each do |comment|
|
||||
= render 'projects/comments/comment', :comment => comment, :data => {:project => project, :commentable => commentable}
|
||||
-unless comment.created_from_commit_hash
|
||||
= render 'projects/comments/comment', :comment => comment, :data => {:project => project, :commentable => commentable}
|
||||
-else
|
||||
- GitPresenters::CommitAsMessagePresenter.present(nil, :comment => comment) do |presenter|
|
||||
= render 'shared/feed_message', :presenter => presenter, :item_no => "-#{comment.id}"
|
||||
= render "projects/comments/markdown_help"
|
||||
|
|
|
@ -17,23 +17,27 @@
|
|||
_This will also be italic_
|
||||
**This text will be bold**
|
||||
__This will also be bold__
|
||||
%p=link_to t('layout.comments.md_cheatsheet.emoji_header'), 'http://www.emoji-cheat-sheet.com/', :target => '_blank'
|
||||
%pre
|
||||
=":smile:"
|
||||
=image_tag(image_path('emoji/smile.png'), :class => 'emoji', :title => 'smile', :alt => 'smile', :size => "20x20")
|
||||
=" :+1:"
|
||||
=image_tag(image_path('emoji/+1.png'), :class => 'emoji', :title => '+1', :alt => '+1', :size => "20x20")
|
||||
.col
|
||||
%h3=t 'layout.comments.md_cheatsheet.lists'
|
||||
%p=t 'layout.comments.md_cheatsheet.unordered'
|
||||
%p{:style => 'float:left;margin-left:20px;'}=t 'layout.comments.md_cheatsheet.unordered'
|
||||
%p{:style => 'float:right;margin-right:40px;'}=t 'layout.comments.md_cheatsheet.ordered'
|
||||
.both
|
||||
%pre
|
||||
:preserve
|
||||
* Item 1
|
||||
* Item 2
|
||||
* Item 2a
|
||||
* Item 2b
|
||||
%p=t 'layout.comments.md_cheatsheet.ordered'
|
||||
* Item 1 1. Item 1
|
||||
* Item 2 2. Item 2
|
||||
* Item 2a 3. Item 3
|
||||
* Item 2b * Item 3a
|
||||
* Item 3b
|
||||
%p=t 'layout.comments.md_cheatsheet.reference_format'
|
||||
%pre
|
||||
:preserve
|
||||
1. Item 1
|
||||
2. Item 2
|
||||
3. Item 3
|
||||
* Item 3a
|
||||
* Item 3b
|
||||
=preserve t('layout.comments.md_cheatsheet.reference_format_example').html_safe
|
||||
.col
|
||||
%h3=t 'layout.comments.md_cheatsheet.miscellaneous'
|
||||
%p=t 'layout.comments.md_cheatsheet.images'
|
||||
|
@ -84,3 +88,4 @@
|
|||
:preserve
|
||||
I think you should use an
|
||||
`[addr]` element here instead.
|
||||
.both
|
||||
|
|
|
@ -1,13 +1,14 @@
|
|||
=render 'title_body', :f => f, :id => 'new'
|
||||
.leftlist= t('activerecord.attributes.issue.assignee') + ':'
|
||||
#assigned-container.rightlist
|
||||
=render 'user_container', :user => @issue.assignee
|
||||
.both
|
||||
.leftlist= t('layout.issues.labels') + ':'
|
||||
.rightlist
|
||||
%span#flag-span.small-text= t('layout.issues.choose_labels_on_left')
|
||||
#issue_labels
|
||||
.both
|
||||
- if can?(:write, @project)
|
||||
.leftlist= t('activerecord.attributes.issue.assignee') + ':'
|
||||
#assigned-container.rightlist
|
||||
=render 'user_container', :user => @issue.assignee
|
||||
.both
|
||||
.leftlist= t('layout.issues.labels') + ':'
|
||||
.rightlist
|
||||
%span#flag-span.small-text= t('layout.issues.choose_labels_on_left')
|
||||
#issue_labels
|
||||
.both
|
||||
.leftlist
|
||||
.rightlist
|
||||
%input{:type => "submit", :value => t(@issue.new_record? ? 'layout.create' : 'layout.update')}
|
||||
|
|
|
@ -22,4 +22,4 @@
|
|||
= link_to image_tag(avatar_url(issue.assignee), :alt => 'avatar', :class => 'avatar'), user_path(issue.assignee) if issue.assignee
|
||||
%a.answers{:href => "#{path}#block-list"}
|
||||
= image_tag 'answers.png', :class => 'pic'
|
||||
.count= issue.comments.count
|
||||
.count=issue.comments.where(:automatic => false).count
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
-content_for :sidebar do
|
||||
- can_manage = can?(:update, @issue) && @issue.persisted? || @issue.new_record?
|
||||
- if @issue.persisted?
|
||||
.bordered.nopadding
|
||||
%h3=t('activerecord.attributes.issue.status')
|
||||
- can_manage = can?(:update, @issue)
|
||||
#switcher.issue_status{:class => "#{@issue.closed? ? 'switcher-off' : 'switcher'} #{can_manage ? "switch_issue_status" : ''}"}
|
||||
.swleft=t('layout.issues.status.open')
|
||||
.swright=t('layout.issues.status.closed')
|
||||
|
@ -11,7 +11,7 @@
|
|||
=hidden_field_tag "issue_status", @issue.closed? ? 'closed' : 'open', :name => "issue[status]"
|
||||
.block
|
||||
%h3=t('layout.issues.labels')
|
||||
- if can_manage
|
||||
- if can?(:write, @project)
|
||||
.current_labels
|
||||
- (@project.labels || []).each do |label|
|
||||
- is_issue_label = @issue.labels.include? label
|
||||
|
@ -29,6 +29,6 @@
|
|||
.label.nopointer
|
||||
.labeltext.selected{:style => "background: ##{label.color};"}=label.name
|
||||
.both
|
||||
- if can_manage && @issue.persisted?
|
||||
- if can?(:write, @project) && @issue.persisted?
|
||||
=link_to(t('layout.issues.label_manage'), '#', :class => "button tmargin10 manage_labels")
|
||||
=link_to(t('layout.issues.done'), '#', :class => "button tmargin10 update_labels", :style => 'display:none')
|
||||
|
|
|
@ -6,5 +6,5 @@
|
|||
%span= t('layout.issues.is_assigned')
|
||||
- else
|
||||
%span= t('layout.issues.no_one_is_assigned')
|
||||
-if can?(:update, @issue) || @issue.new_record?
|
||||
-if can?(:write, @project)
|
||||
%span.icon-share
|
|
@ -5,7 +5,11 @@
|
|||
-if item.is_a? Comment
|
||||
=render 'projects/git/commits/commits_small', :commits => commits_queue if commits_queue.present?
|
||||
-commits_queue.clear
|
||||
=render 'projects/comments/comment', :comment => item, :data => {:project => @project, :commentable => @commentable}
|
||||
-unless item.created_from_commit_hash
|
||||
= render 'projects/comments/comment', :comment => item, :data => {:project => @project, :commentable => @commentable}
|
||||
-else
|
||||
- GitPresenters::CommitAsMessagePresenter.present(nil, :comment => item) do |presenter|
|
||||
= render 'shared/feed_message', :presenter => presenter, :item_no => "-#{item.id}"
|
||||
-elsif item.is_a? Grit::Commit
|
||||
-commits_queue << item
|
||||
-elsif item.is_a? Array
|
||||
|
@ -22,4 +26,3 @@
|
|||
=render 'projects/pull_requests/discussion_comments', :item => item, :diff_counter => diff_counter
|
||||
- diff_counter += 1
|
||||
=render 'projects/git/commits/commits_small', :commits => commits_queue if commits_queue.present?
|
||||
|
||||
|
|
|
@ -32,11 +32,11 @@
|
|||
%div{:class => @pull.ready? ? 'notice' : 'alert'}
|
||||
=pull_status @pull
|
||||
.both
|
||||
|
||||
.leftlist.big-list= t('activerecord.attributes.issue.assignee') + ':'
|
||||
#assigned-container.rightlist
|
||||
=render 'projects/issues/user_container', :user => @pull.assignee
|
||||
.both
|
||||
- if can?(:write, @pull.to_project)
|
||||
.leftlist.big-list= t('activerecord.attributes.issue.assignee') + ':'
|
||||
#assigned-container.rightlist
|
||||
=render 'projects/issues/user_container', :user => @pull.assignee
|
||||
.both
|
||||
.leftlist.big-list
|
||||
.rightlist
|
||||
=f.submit t('.submit'), :class => 'btn btn-primary disabled', 'data-loading-text' => t('layout.processing'), :id => 'create_pull' unless @pull.already?
|
||||
|
|
|
@ -17,7 +17,11 @@
|
|||
.both
|
||||
.both
|
||||
- if presenter.content?
|
||||
.fulltext{:class => "#{presenter.expandable? ? "hidden" : ''} #{presenter.caption? ? "" : "alone"}",
|
||||
:id => presenter.expandable? ? "content-expand#{item_no}" : ''}
|
||||
.cm-s-default.md_and_cm=markdown presenter.content
|
||||
.both
|
||||
%div
|
||||
=presenter.issue_referenced_state if presenter.issue_referenced_state?
|
||||
.fulltext{:class => "#{presenter.expandable? ? "hidden" : ''} #{presenter.caption? ? "" : "alone"}",
|
||||
:id => presenter.expandable? ? "content-expand#{item_no}" : ''}
|
||||
.md_and_cm{:class => presenter.is_reference_to_issue ? '' : 'cm-s-default'}
|
||||
=presenter.is_reference_to_issue ? presenter.content : markdown(presenter.content)
|
||||
.both
|
||||
|
||||
|
|
|
@ -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
|
|
@ -7,3 +7,6 @@ en:
|
|||
commit: commit
|
||||
commits: commits
|
||||
commits2: commits
|
||||
reference: "%{committer} referenced this issue from a commit %{commit}"
|
||||
deleted: Commit has since been removed from the git-repository and is no longer available.
|
||||
unknown_committer: Unknown
|
|
@ -7,3 +7,6 @@ ru:
|
|||
commit: коммит
|
||||
commits: коммита
|
||||
commits2: коммитов
|
||||
reference: "%{committer} ссылается на данную задачу в коммите %{commit}"
|
||||
deleted: Коммит был удален и более недоступен в git-репозитории.
|
||||
unknown_committer: Неизвестный
|
|
@ -25,6 +25,22 @@ en:
|
|||
syntax_highlighting: Syntax highlighting
|
||||
indent_code: indent your code 4 spaces
|
||||
inline_code: Inline code for comments
|
||||
emoji_header: Emoji Cheat Sheet
|
||||
reference_format: Reference Format
|
||||
reference_format_example: |
|
||||
for members: @abf
|
||||
for issues:
|
||||
#123 abf#123 abf/rosa-build#123
|
||||
for pull requests:
|
||||
!123 abf!123 abf/rosa-build!123
|
||||
for commits: 123456
|
||||
|
||||
issues: Issues
|
||||
pull_requests: Pull Requests
|
||||
members: Members
|
||||
commits: Commits
|
||||
reference: "%{user} referenced this issue"
|
||||
removed: Comment has since been removed and is no longer available.
|
||||
|
||||
flash:
|
||||
comment:
|
||||
|
|
|
@ -25,6 +25,21 @@ ru:
|
|||
syntax_highlighting: Подсветка синтаксиса
|
||||
indent_code: Отступ кода на 4 пробела
|
||||
inline_code: Встроенный код в строке
|
||||
emoji_header: Шпаргалка по Emoji
|
||||
reference_format: Формат ссылок
|
||||
reference_format_example: |
|
||||
для участников: @abf
|
||||
для задач:
|
||||
#123 abf#123 abf/rosa-build#123
|
||||
для пул реквестов:
|
||||
!123 abf!123 abf/rosa-build!123
|
||||
для коммитов: 123456
|
||||
issues: Задачи
|
||||
pull_requests: Пул реквесты
|
||||
members: Участники
|
||||
commits: Коммиты
|
||||
reference: "%{user} ссылается на данную задачу"
|
||||
removed: Комментарий был удален и более недоступен.
|
||||
|
||||
flash:
|
||||
comment:
|
||||
|
|
|
@ -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,9 @@
|
|||
class AddAutomaticToComments < ActiveRecord::Migration
|
||||
def change
|
||||
add_column :comments, :automatic, :boolean, :default => false
|
||||
|
||||
add_index :comments, :commentable_type
|
||||
add_index :comments, :automatic
|
||||
add_index :comments, :commentable_id
|
||||
end
|
||||
end
|
|
@ -0,0 +1,9 @@
|
|||
class AddAddInfoToComments < ActiveRecord::Migration
|
||||
def change
|
||||
add_column :comments, :created_from_commit_hash, :decimal, :precision => 50, :scale => 0
|
||||
add_column :comments, :created_from_issue_id, :integer
|
||||
|
||||
add_index :comments, :created_from_issue_id
|
||||
add_index :comments, :created_from_commit_hash
|
||||
end
|
||||
end
|
|
@ -12,18 +12,17 @@ class ReservedNameValidator < ActiveModel::EachValidator
|
|||
jobs
|
||||
login log-in log_in logout log-out log_out logs
|
||||
map maps
|
||||
new
|
||||
new none
|
||||
oauth oauth_clients openid
|
||||
privacy
|
||||
register remove replies rss root
|
||||
save search sessions settings
|
||||
signup sign-up sign_up signin sign-in sign_in signout sign-out sign_out
|
||||
sitemap ssl subscribe
|
||||
teams terms test trends tree
|
||||
teams terms test tour trends tree
|
||||
unfollow unsubscribe url user
|
||||
widget widgets wiki
|
||||
xfn xmpp
|
||||
tour
|
||||
}
|
||||
|
||||
def reserved_names
|
||||
|
|
|
@ -0,0 +1,199 @@
|
|||
# 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
|
||||
# * for issues:
|
||||
# * #123
|
||||
# * abf#123
|
||||
# * abf/rosa-build#123
|
||||
# * for pull requests:
|
||||
# * !123
|
||||
# * abf!123
|
||||
# * abf/rosa-build!123
|
||||
# * 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>(?:[a-zA-Z0-9\-_]*\/)?(?:[a-zA-Z0-9\-_]*)?\#[0-9]+) # Issue ID
|
||||
|(?<pull_request>(?:[a-zA-Z0-9\-_]*\/)?(?:[a-zA-Z0-9\-_]*)?\![0-9]+) # 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 = Issue.find_by_hash_tag(identifier, current_ability, @project)
|
||||
url = project_issue_path(issue.project.owner, issue.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)
|
||||
issue = Issue.find_by_hash_tag(identifier, current_ability, @project, '!')
|
||||
if pull_request = issue.pull_request
|
||||
title = "#{PullRequest.model_name.human}: #{pull_request.title}"
|
||||
link_to(identifier, project_pull_request_path(pull_request.to_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,37 @@
|
|||
# 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)
|
||||
# New lines are placed to fix an rendering issue
|
||||
# with code wrapped inside <h1> tag for next case:
|
||||
#
|
||||
# # Title kinda h1
|
||||
#
|
||||
# ruby code here
|
||||
#
|
||||
code_class = "class=\"#{language.downcase}\"" if language.present?
|
||||
<<-HTML
|
||||
|
||||
<pre><code #{code_class}">#{code}</code></pre>
|
||||
|
||||
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
|
|
@ -2,13 +2,14 @@
|
|||
require 'spec_helper'
|
||||
|
||||
shared_context "issues controller" do
|
||||
before(:each) do
|
||||
before do
|
||||
stub_symlink_methods
|
||||
|
||||
@project = FactoryGirl.create(:project)
|
||||
@issue_user = FactoryGirl.create(:user)
|
||||
|
||||
@issue = FactoryGirl.create(:issue, :project_id => @project.id, :assignee_id => @issue_user.id)
|
||||
@label = FactoryGirl.create(:label, :project_id => @project.id)
|
||||
|
||||
@project_with_turned_off_issues = FactoryGirl.create(:project, :has_issues => false)
|
||||
@turned_of_issue = FactoryGirl.create(:issue, :project_id => @project_with_turned_off_issues.id, :assignee_id => @issue_user.id)
|
||||
|
@ -20,10 +21,10 @@ shared_context "issues controller" do
|
|||
:owner_name => @project.owner.uname, :project_name => @project.name,
|
||||
:issue => {
|
||||
:title => "issue1",
|
||||
:body => "issue body"
|
||||
},
|
||||
:assignee_id => @issue_user.id,
|
||||
:assignee_uname => @issue_user.uname
|
||||
:body => "issue body",
|
||||
:labelings_attributes => { @label.id => {:label_id => @label.id}},
|
||||
:assignee_id => @issue_user.id
|
||||
}
|
||||
}
|
||||
|
||||
@update_params = {
|
||||
|
@ -56,9 +57,7 @@ shared_examples_for 'issue user with project reader rights' do
|
|||
get :index, :owner_name => @project.owner.uname, :project_name => @project.name
|
||||
response.should render_template(:index)
|
||||
end
|
||||
end
|
||||
|
||||
shared_examples_for 'issue user with project writer rights' do
|
||||
it 'should be able to perform create action' do
|
||||
post :create, @create_params
|
||||
response.should redirect_to(project_issues_path(@project))
|
||||
|
@ -69,6 +68,30 @@ shared_examples_for 'issue user with project writer rights' do
|
|||
end
|
||||
end
|
||||
|
||||
shared_examples_for 'issue user with project writer rights' do
|
||||
it 'should be able to perform index action on hidden project' do
|
||||
@project.update_attributes(:visibility => 'hidden')
|
||||
get :index, :owner_name => @project.owner.uname, :project_name => @project.name
|
||||
response.should render_template(:index)
|
||||
end
|
||||
|
||||
it 'should create issue object into db' do
|
||||
lambda{ post :create, @create_params }.should change{ Issue.count }.by(1)
|
||||
end
|
||||
|
||||
context 'perform create action' do
|
||||
before { post :create, @create_params }
|
||||
|
||||
it 'user should be assigned to issue' do
|
||||
@project.issues.last.assignee_id.should_not be_nil
|
||||
end
|
||||
|
||||
it 'label should be attached to issue' do
|
||||
@project.issues.last.labels.should have(1).item
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
shared_examples_for 'user with issue update rights' do
|
||||
it 'should be able to perform update action' do
|
||||
put :update, {:id => @issue.serial_id}.merge(@update_params)
|
||||
|
@ -167,11 +190,22 @@ describe Projects::IssuesController do
|
|||
|
||||
it_should_behave_like 'issue user with project guest rights'
|
||||
it_should_behave_like 'issue user with project reader rights'
|
||||
it_should_behave_like 'issue user with project writer rights'
|
||||
it_should_behave_like 'user without issue update rights'
|
||||
it_should_behave_like 'project with issues turned off'
|
||||
it_should_behave_like 'user without issue destroy rights'
|
||||
|
||||
context 'perform create action' do
|
||||
before { post :create, @create_params }
|
||||
|
||||
it 'user should not be assigned to issue' do
|
||||
@project.issues.last.assignee_id.should be_nil
|
||||
end
|
||||
|
||||
it 'label should not be attached to issue' do
|
||||
@project.issues.last.labels.should have(:no).items
|
||||
end
|
||||
end
|
||||
|
||||
# it 'should not be able to perform create action on project' do
|
||||
# post :create, @create_params
|
||||
# response.should redirect_to(forbidden_path)
|
||||
|
|
|
@ -0,0 +1,8 @@
|
|||
# -*- encoding : utf-8 -*-
|
||||
FactoryGirl.define do
|
||||
factory :label do
|
||||
name { FactoryGirl.generate(:string) }
|
||||
color 'FFF'
|
||||
association :project, :factory => :project
|
||||
end
|
||||
end
|
|
@ -0,0 +1,7 @@
|
|||
# -*- encoding : utf-8 -*-
|
||||
FactoryGirl.define do
|
||||
factory :labeling do
|
||||
association :project, :factory => :project
|
||||
association :label, :factory => :label
|
||||
end
|
||||
end
|
|
@ -6,6 +6,10 @@ def create_comment user
|
|||
FactoryGirl.create(:comment, :user => user, :commentable => @commit, :project => @project)
|
||||
end
|
||||
|
||||
def create_comment_in_commit commit, project, body
|
||||
FactoryGirl.create(:comment, :user => @user, :commentable => commit, :project => project, :body => body)
|
||||
end
|
||||
|
||||
def set_comments_data_for_commit
|
||||
@ability = Ability.new(@user)
|
||||
|
||||
|
@ -263,5 +267,63 @@ describe Comment do
|
|||
should_not_send_email(commentor: @user)
|
||||
end
|
||||
end
|
||||
|
||||
context 'automatic issue linking' do
|
||||
before(:each) do
|
||||
@same_name_project = FactoryGirl.create(:project, :name => @project.name)
|
||||
@issue_in_same_name_project = FactoryGirl.create(:issue, :project => @same_name_project, :user => @same_name_project.owner)
|
||||
@another_project = FactoryGirl.create(:project, :owner => @user)
|
||||
@other_user_project = FactoryGirl.create(:project)
|
||||
@issue = FactoryGirl.create(:issue, :project => @project, :user => @user)
|
||||
@second_issue = FactoryGirl.create(:issue, :project => @project, :user => @user)
|
||||
@issue_in_another_project = FactoryGirl.create(:issue, :project => @another_project, :user => @user)
|
||||
@issue_in_other_user_project = FactoryGirl.create(:issue, :project => @other_user_project, :user => @other_user_project.owner)
|
||||
end
|
||||
|
||||
it 'should create automatic comment' do
|
||||
create_comment_in_commit(@commit, @project, "test link to ##{@issue.serial_id}; [##{@second_issue.serial_id}]")
|
||||
Comment.where(:automatic => true, :commentable_type => 'Issue',
|
||||
:commentable_id => @second_issue.id,
|
||||
:created_from_commit_hash => @commit.id.hex).count.should == 1
|
||||
end
|
||||
|
||||
it 'should create automatic comment in the another project issue' do
|
||||
body = "[#{@another_project.name_with_owner}##{@issue_in_another_project.serial_id}]"
|
||||
create_comment_in_commit(@commit, @project, body)
|
||||
Comment.where(:automatic => true, :commentable_type => 'Issue',
|
||||
:commentable_id => @issue_in_another_project.id,
|
||||
:created_from_commit_hash => @commit.id.hex).count.should == 1
|
||||
end
|
||||
|
||||
it 'should create automatic comment in the same name project issue' do
|
||||
body = "[#{@same_name_project.owner.uname}##{@issue_in_same_name_project.serial_id}]"
|
||||
create_comment_in_commit(@commit, @project, body)
|
||||
Comment.where(:automatic => true, :commentable_type => 'Issue',
|
||||
:commentable_id => @issue_in_same_name_project.id,
|
||||
:created_from_commit_hash => @commit.id.hex).count.should == 1
|
||||
end
|
||||
|
||||
it 'should not create duplicate automatic comment' do
|
||||
create_comment_in_commit(@commit, @project, "test link to [##{@second_issue.serial_id}]")
|
||||
create_comment_in_commit(@commit, @project, "test duplicate link to [##{@second_issue.serial_id}]")
|
||||
Comment.where(:automatic => true, :commentable_type => 'Issue',
|
||||
:commentable_id => @second_issue.id,
|
||||
:created_from_commit_hash => @commit.id.hex).count.should == 1
|
||||
end
|
||||
|
||||
it 'should not create duplicate automatic comment from one' do
|
||||
create_comment_in_commit(@commit, @project, "test link to [##{@second_issue.serial_id}]; ##{@second_issue.serial_id}")
|
||||
Comment.where(:automatic => true, :commentable_type => 'Issue',
|
||||
:commentable_id => @second_issue.id,
|
||||
:created_from_commit_hash => @commit.id.hex).count.should == 1
|
||||
end
|
||||
it 'should create two automatic comment' do
|
||||
body = "test ##{@second_issue.serial_id}" +
|
||||
" && [#{@another_project.name_with_owner}##{@issue_in_another_project.serial_id}]"
|
||||
create_comment_in_commit(@commit, @project, body)
|
||||
Comment.where(:automatic => true,
|
||||
:created_from_commit_hash => @commit.id.hex).count.should == 2
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -14,6 +14,10 @@ def set_commentable_data
|
|||
any_instance_of(Project, :versions => ['v1.0', 'v2.0'])
|
||||
end
|
||||
|
||||
def create_comment_in_issue issue, body
|
||||
FactoryGirl.create(:comment, :user => issue.user, :commentable => issue, :project => issue.project, :body => body)
|
||||
end
|
||||
|
||||
describe Comment do
|
||||
before { stub_symlink_methods }
|
||||
context 'for global admin user' do
|
||||
|
@ -100,5 +104,69 @@ describe Comment do
|
|||
@comment.should_not allow_mass_assignment_of :project_id
|
||||
end
|
||||
end
|
||||
|
||||
context 'automatic issue linking' do
|
||||
before(:each) do
|
||||
@same_name_project = FactoryGirl.create(:project, :name => @project.name)
|
||||
@issue_in_same_name_project = FactoryGirl.create(:issue, :project => @same_name_project, :user => @same_name_project.owner)
|
||||
@another_project = FactoryGirl.create(:project, :owner => @user)
|
||||
@other_user_project = FactoryGirl.create(:project)
|
||||
@issue = FactoryGirl.create(:issue, :project => @project, :user => @user)
|
||||
@second_issue = FactoryGirl.create(:issue, :project => @project, :user => @user)
|
||||
@issue_in_another_project = FactoryGirl.create(:issue, :project => @another_project, :user => @user)
|
||||
@issue_in_other_user_project = FactoryGirl.create(:issue, :project => @other_user_project, :user => @other_user_project.owner)
|
||||
end
|
||||
|
||||
it 'should create automatic comment' do
|
||||
create_comment_in_issue(@issue, "test link to ##{@issue.serial_id}; [##{@second_issue.serial_id}]")
|
||||
Comment.where(:automatic => true, :commentable_type => 'Issue',
|
||||
:commentable_id => @second_issue.id,
|
||||
:created_from_issue_id => @issue.id).count.should == 1
|
||||
end
|
||||
|
||||
it 'should not create automatic comment to the same issue' do
|
||||
create_comment_in_issue(@issue, "test link to ##{@issue.serial_id}; [##{@second_issue.serial_id}]")
|
||||
Comment.where(:automatic => true,
|
||||
:created_from_issue_id => @issue.id).count.should == 1
|
||||
end
|
||||
|
||||
it 'should create automatic comment in the another project issue' do
|
||||
body = "[#{@another_project.name_with_owner}##{@issue_in_another_project.serial_id}]"
|
||||
create_comment_in_issue(@issue, body)
|
||||
Comment.where(:automatic => true, :commentable_type => 'Issue',
|
||||
:commentable_id => @issue_in_another_project.id,
|
||||
:created_from_issue_id => @issue.id).count.should == 1
|
||||
end
|
||||
|
||||
it 'should create automatic comment in the same name project issue' do
|
||||
body = "[#{@same_name_project.owner.uname}##{@issue_in_same_name_project.serial_id}]"
|
||||
create_comment_in_issue(@issue, body)
|
||||
Comment.where(:automatic => true, :commentable_type => 'Issue',
|
||||
:commentable_id => @issue_in_same_name_project.id,
|
||||
:created_from_issue_id => @issue.id).count.should == 1
|
||||
end
|
||||
|
||||
it 'should not create duplicate automatic comment' do
|
||||
create_comment_in_issue(@issue, "test link to [##{@second_issue.serial_id}]")
|
||||
create_comment_in_issue(@issue, "test duplicate link to [##{@second_issue.serial_id}]")
|
||||
Comment.where(:automatic => true, :commentable_type => 'Issue',
|
||||
:commentable_id => @second_issue.id,
|
||||
:created_from_issue_id => @issue.id).count.should == 1
|
||||
end
|
||||
|
||||
it 'should not create duplicate automatic comment from one' do
|
||||
create_comment_in_issue(@issue, "test link to [##{@second_issue.serial_id}]; ##{@second_issue.serial_id}")
|
||||
Comment.where(:automatic => true, :commentable_type => 'Issue',
|
||||
:commentable_id => @second_issue.id,
|
||||
:created_from_issue_id => @issue.id).count.should == 1
|
||||
end
|
||||
it 'should create two automatic comment' do
|
||||
body = "test ##{@second_issue.serial_id}" +
|
||||
" && [#{@another_project.name_with_owner}##{@issue_in_another_project.serial_id}]"
|
||||
create_comment_in_issue(@issue, body)
|
||||
Comment.where(:automatic => true,
|
||||
:created_from_issue_id => @issue.id).count.should == 2
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
Loading…
Reference in New Issue