--- /dev/null
+ /*
+ * This is a manifest file that'll automatically include all the stylesheets available in this directory
+ * and any sub-directories. You're free to add application-wide styles to this file and they'll appear at
+ * the top of the compiled file, but it's generally better to create a new file per style scope.
+ *= require jquery.ui.gitlab
+ *= require jquery.atwho
+ *= require chosen
+ *= require_self
+ */
+
+ /**
+ * GitLab bootstrap:
+ */
+ @import "gitlab_bootstrap.scss";
+
+ @import "common.scss";
+ @import "ref_select.scss";
+
+ @import "sections/header.scss";
+ @import "sections/nav.scss";
+ @import "sections/commits.scss";
+ @import "sections/issues.scss";
+ @import "sections/projects.scss";
+ @import "sections/snippets.scss";
+ @import "sections/votes.scss";
+ @import "sections/merge_requests.scss";
+ @import "sections/graph.scss";
+ @import "sections/events.scss";
+ @import "sections/themes.scss";
+ @import "sections/tree.scss";
+ @import "sections/notes.scss";
+ @import "sections/profile.scss";
+ @import "sections/login.scss";
+ @import "sections/editor.scss";
+
+ @import "highlight/white.scss";
+ @import "highlight/dark.scss";
+
+ /**
+ * UI themes:
+ */
+ @import "themes/ui_basic.scss";
+ @import "themes/ui_mars.scss";
+ @import "themes/ui_modern.scss";
+ @import "themes/ui_gray.scss";
+ @import "themes/ui_color.scss";
+
++/**
++ * Styles for JS behaviors.
++ */
++@import "behaviors.scss";
++
require 'file_size_validator'
class Note < ActiveRecord::Base
-
attr_accessible :note, :noteable, :noteable_id, :noteable_type, :project_id,
- :attachment, :line_code
+ :attachment, :line_code, :commit_id
attr_accessor :notify
attr_accessor :notify_author
delegate :name, :email, to: :author, prefix: true
validates :note, :project, presence: true
+ validates :line_code, format: { with: /\A\d+_\d+_\d+\Z/ }, allow_blank: true
validates :attachment, file_size: { maximum: 10.megabytes.to_i }
+ validates :noteable_id, presence: true, if: ->(n) { n.noteable_type.present? && n.noteable_type != 'Commit' }
+ validates :commit_id, presence: true, if: ->(n) { n.noteable_type == 'Commit' }
+
mount_uploader :attachment, AttachmentUploader
# Scopes
# Returns true if this is an upvote note,
# otherwise false is returned
def upvote?
- note.start_with?('+1') || note.start_with?(':+1:')
+ votable? && (note.start_with?('+1') ||
+ note.start_with?(':+1:')
+ )
end
- # Returns true if this is a downvote note,
- # otherwise false is returned
- def downvote?
- note.start_with?('-1') || note.start_with?(':-1:')
+ def votable?
+ for_issue? || (for_merge_request? && !for_diff_line?)
end
+
+ def noteable_type_name
+ if noteable_type.present?
+ noteable_type.downcase
+ else
+ "wall"
+ end
+ end
end
end
def commit_notes(commit)
- notes.where(noteable_id: commit.id, noteable_type: "Commit").where('line_code IS NULL OR line_code = ""')
- notes.where(commit_id: commit.id, noteable_type: "Commit", line_code: nil)
++ notes.where(commit_id: commit.id, noteable_type: "Commit").where('line_code IS NULL OR line_code = ""')
end
def commit_line_notes(commit)
--- /dev/null
+= form_for [@project, @note], remote: true, html: { multipart: true, id: nil, class: "new_note js-new-note-form" } do |f|
+
+ = note_target_fields
++ = f.hidden_field :commit_id
+ = f.hidden_field :line_code
+ = f.hidden_field :noteable_id
+ = f.hidden_field :noteable_type
+
+ .note_text_and_preview.js-toggler-container
+ %a.js-note-preview-button.js-toggler-target.turn-off{ href: "javascript:;", data: {title: "Preview", url: preview_project_notes_path(@project)} }
+ %i.icon-eye-open
+ %a.js-note-edit-button.js-toggler-target.turn-off{ href: "javascript:;", data: {title: "Edit"} }
+ %i.icon-edit
+
+ = f.text_area :note, size: 255, class: 'note_text js-note-text js-gfm-input turn-on'
+ .note_preview.js-note-preview.turn-off
+
+ .buttons
+ = f.submit 'Add Comment', class: "btn comment-btn grouped js-comment-button"
+ %a.btn.grouped.js-close-discussion-note-form Cancel
+ .hint
+ .right Comments are parsed with #{link_to "GitLab Flavored Markdown", help_markdown_path, target: '_blank'}.
+ .clearfix
+
+ .note_options
+ .attachment
+ %h6 Attachment:
+ .file_name.js-attachment-filename File name...
+ %a.choose-btn.btn.small.js-choose-note-attachment-button Choose File ...
+ .hint Any file up to 10 MB
+
+ = f.file_field :attachment, class: "js-note-attachment-input"
+
+ .notify_options
+ %h6 Notify via email:
+ = label_tag :notify do
+ = check_box_tag :notify, 1, !@note.for_commit?
+ Project team
+
+ .js-notify-commit-author
+ = label_tag :notify_author do
+ = check_box_tag :notify_author, 1 , !@note.for_commit?
+ Commit author
+ .clearfix
text
end
- (\W)? # Prefix (1)
- ( # Reference (2)
- @([\w\._]+) # User name (3)
- |[#!$](\d+) # Issue/MR/Snippet ID (4)
- |([\h]{6,40}) # Commit ID (5)
+ REFERENCE_PATTERN = %r{
- (\W)? # Suffix (6)
++ (?<prefix>\W)? # Prefix
++ ( # Reference
++ @(?<user>[a-zA-Z][a-zA-Z0-9_\-\.]*) # User name
++ |\#(?<issue>\d+) # Issue ID
++ |!(?<merge_request>\d+) # MR ID
++ |\$(?<snippet>\d+) # Snippet ID
++ |(?<commit>[\h]{6,40}) # Commit ID
+ )
++ (?<suffix>\W)? # Suffix
+ }x.freeze
+
++ TYPES = [:user, :issue, :merge_request, :snippet, :commit].freeze
++
def parse_references(text)
# parse reference links
text.gsub!(REFERENCE_PATTERN) do |match|
# parse emoji
text.gsub!(EMOJI_PATTERN) do |match|
if valid_emoji?($2)
- image_tag("emoji/#{$2}.png", class: 'emoji', title: $1, alt: $1)
- image_tag("emoji/#{$2}.png", size: "20x20", class: 'emoji', title: $1, alt: $1)
++ image_tag("emoji/#{$2}.png", class: 'emoji', title: $1, alt: $1, size: "20x20")
else
match
end
factory :note do
project
note "Note"
- noteable_id "bcf03b5de6c33f3869ef70d68cf06e679d1d7f9a"
+ author
+
+ factory :note_on_commit, traits: [:on_commit]
+ factory :note_on_commit_line, traits: [:on_commit, :on_line]
+ factory :note_on_issue, traits: [:on_issue], aliases: [:votable_note]
+ factory :note_on_merge_request, traits: [:on_merge_request]
+ factory :note_on_merge_request_line, traits: [:on_merge_request, :on_line]
+
+ trait :on_commit do
- noteable_id 1
++ commit_id "bcf03b5de6c33f3869ef70d68cf06e679d1d7f9a"
+ noteable_type "Commit"
+ end
+
+ trait :on_line do
+ line_code "0_184_184"
+ end
+
+ trait :on_merge_request do
+ noteable_id 1
+ noteable_type "MergeRequest"
+ end
+
+ trait :on_issue do
++ noteable_id 1
+ noteable_type "Issue"
+ end
end
factory :event do
end
end
- let(:project) { create(:project) }
- let(:commit) { project.commit }
-
describe "Commit notes" do
- before do
- @note = create(:note,
- commit_id: commit.id,
- noteable_type: "Commit")
- end
+ let!(:note) { create(:note_on_commit, note: "+1 from me") }
+ let!(:commit) { note.noteable }
it "should be accessible through #noteable" do
- note.noteable_id.should == commit.id
- @note.commit_id.should == commit.id
- @note.noteable.should be_a(Commit)
- @note.noteable.should == commit
++ note.commit_id.should == commit.id
+ note.noteable.should be_a(Commit)
+ note.noteable.should == commit
end
it "should save a valid note" do
- note.noteable_id.should == commit.id
- @note.commit_id.should == commit.id
- @note.noteable == commit
++ note.commit_id.should == commit.id
+ note.noteable == commit
end
it "should be recognized by #for_commit?" do
- @note.should be_for_commit
+ note.should be_for_commit
end
- end
- describe "Pre-line commit notes" do
- before do
- @note = create(:note,
- commit_id: commit.id,
- noteable_type: "Commit",
- line_code: "0_16_1")
+ it "should not be votable" do
+ note.should_not be_votable
end
+ end
+
+ describe "Commit diff line notes" do
+ let!(:note) { create(:note_on_commit_line, note: "+1 from me") }
+ let!(:commit) { note.noteable }
it "should save a valid note" do
- note.noteable_id.should == commit.id
- @note.commit_id.should == commit.id
- @note.noteable.id.should == commit.id
++ note.commit_id.should == commit.id
+ note.noteable.id.should == commit.id
end
it "should be recognized by #for_diff_line?" do