Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Support
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in / Register
Toggle navigation
G
gitlab-ce
Project overview
Project overview
Details
Activity
Releases
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Issues
0
Issues
0
List
Boards
Labels
Milestones
Merge Requests
0
Merge Requests
0
Analytics
Analytics
Repository
Value Stream
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Create a new issue
Commits
Issue Boards
Open sidebar
Jérome Perrin
gitlab-ce
Commits
7bf0add0
Commit
7bf0add0
authored
Dec 26, 2013
by
Dmitriy Zaporozhets
Browse files
Options
Browse Files
Download
Plain Diff
Merge branch 'refactor/notes' of /home/git/repositories/gitlab/gitlabhq
parents
4cd7a563
5f7db3e9
Changes
17
Hide whitespace changes
Inline
Side-by-side
Showing
17 changed files
with
552 additions
and
720 deletions
+552
-720
app/assets/javascripts/notes.js
app/assets/javascripts/notes.js
+0
-587
app/assets/javascripts/notes.js.coffee
app/assets/javascripts/notes.js.coffee
+418
-0
app/assets/stylesheets/sections/notes.scss
app/assets/stylesheets/sections/notes.scss
+2
-2
app/controllers/projects/commit_controller.rb
app/controllers/projects/commit_controller.rb
+2
-2
app/controllers/projects/issues_controller.rb
app/controllers/projects/issues_controller.rb
+2
-2
app/controllers/projects/merge_requests_controller.rb
app/controllers/projects/merge_requests_controller.rb
+3
-3
app/controllers/projects/notes_controller.rb
app/controllers/projects/notes_controller.rb
+50
-62
app/controllers/projects/snippets_controller.rb
app/controllers/projects/snippets_controller.rb
+2
-2
app/helpers/notes_helper.rb
app/helpers/notes_helper.rb
+10
-9
app/models/note.rb
app/models/note.rb
+46
-23
app/views/projects/notes/_form.html.haml
app/views/projects/notes/_form.html.haml
+1
-2
app/views/projects/notes/_note.html.haml
app/views/projects/notes/_note.html.haml
+1
-1
app/views/projects/notes/_notes.html.haml
app/views/projects/notes/_notes.html.haml
+1
-1
app/views/projects/notes/_notes_with_form.html.haml
app/views/projects/notes/_notes_with_form.html.haml
+3
-2
app/views/projects/notes/create.js.haml
app/views/projects/notes/create.js.haml
+0
-18
features/steps/project/project_merge_requests.rb
features/steps/project/project_merge_requests.rb
+10
-3
spec/features/notes_on_merge_requests_spec.rb
spec/features/notes_on_merge_requests_spec.rb
+1
-1
No files found.
app/assets/javascripts/notes.js
deleted
100644 → 0
View file @
4cd7a563
var
NoteList
=
{
id
:
null
,
notes_path
:
null
,
target_params
:
null
,
target_id
:
0
,
target_type
:
null
,
init
:
function
(
tid
,
tt
,
path
)
{
NoteList
.
notes_path
=
path
+
"
.json
"
;
NoteList
.
target_id
=
tid
;
NoteList
.
target_type
=
tt
;
NoteList
.
target_params
=
"
target_type=
"
+
NoteList
.
target_type
+
"
&target_id=
"
+
NoteList
.
target_id
;
NoteList
.
setupMainTargetNoteForm
();
// get initial set of notes
NoteList
.
getContent
();
// Unbind events to prevent firing twice
$
(
document
).
off
(
"
click
"
,
"
.js-add-diff-note-button
"
);
$
(
document
).
off
(
"
click
"
,
"
.js-discussion-reply-button
"
);
$
(
document
).
off
(
"
click
"
,
"
.js-note-preview-button
"
);
$
(
document
).
off
(
"
click
"
,
"
.js-note-attachment-input
"
);
$
(
document
).
off
(
"
click
"
,
"
.js-close-discussion-note-form
"
);
$
(
document
).
off
(
"
click
"
,
"
.js-note-delete
"
);
$
(
document
).
off
(
"
click
"
,
"
.js-note-edit
"
);
$
(
document
).
off
(
"
click
"
,
"
.js-note-edit-cancel
"
);
$
(
document
).
off
(
"
click
"
,
"
.js-note-attachment-delete
"
);
$
(
document
).
off
(
"
click
"
,
"
.js-choose-note-attachment-button
"
);
$
(
document
).
off
(
"
click
"
,
"
.js-show-outdated-discussion
"
);
$
(
document
).
off
(
"
ajax:complete
"
,
"
.js-main-target-form
"
);
// add a new diff note
$
(
document
).
on
(
"
click
"
,
"
.js-add-diff-note-button
"
,
NoteList
.
addDiffNote
);
// reply to diff/discussion notes
$
(
document
).
on
(
"
click
"
,
"
.js-discussion-reply-button
"
,
NoteList
.
replyToDiscussionNote
);
// setup note preview
$
(
document
).
on
(
"
click
"
,
"
.js-note-preview-button
"
,
NoteList
.
previewNote
);
// update the file name when an attachment is selected
$
(
document
).
on
(
"
change
"
,
"
.js-note-attachment-input
"
,
NoteList
.
updateFormAttachment
);
// hide diff note form
$
(
document
).
on
(
"
click
"
,
"
.js-close-discussion-note-form
"
,
NoteList
.
removeDiscussionNoteForm
);
// remove a note (in general)
$
(
document
).
on
(
"
click
"
,
"
.js-note-delete
"
,
NoteList
.
removeNote
);
// show the edit note form
$
(
document
).
on
(
"
click
"
,
"
.js-note-edit
"
,
NoteList
.
showEditNoteForm
);
// cancel note editing
$
(
document
).
on
(
"
click
"
,
"
.note-edit-cancel
"
,
NoteList
.
cancelNoteEdit
);
// delete note attachment
$
(
document
).
on
(
"
click
"
,
"
.js-note-attachment-delete
"
,
NoteList
.
deleteNoteAttachment
);
// update the note after editing
$
(
document
).
on
(
"
ajax:complete
"
,
"
form.edit_note
"
,
NoteList
.
updateNote
);
// reset main target form after submit
$
(
document
).
on
(
"
ajax:complete
"
,
"
.js-main-target-form
"
,
NoteList
.
resetMainTargetForm
);
$
(
document
).
on
(
"
click
"
,
"
.js-choose-note-attachment-button
"
,
NoteList
.
chooseNoteAttachment
);
$
(
document
).
on
(
"
click
"
,
"
.js-show-outdated-discussion
"
,
function
(
e
)
{
$
(
this
).
next
(
'
.outdated-discussion
'
).
show
();
e
.
preventDefault
()
});
},
/**
* When clicking on buttons
*/
/**
* Called when clicking on the "add a comment" button on the side of a diff line.
*
* Inserts a temporary row for the form below the line.
* Sets up the form and shows it.
*/
addDiffNote
:
function
(
e
)
{
e
.
preventDefault
();
// find the form
var
form
=
$
(
"
.js-new-note-form
"
);
var
row
=
$
(
this
).
closest
(
"
tr
"
);
var
nextRow
=
row
.
next
();
// does it already have notes?
if
(
nextRow
.
is
(
"
.notes_holder
"
))
{
$
.
proxy
(
NoteList
.
replyToDiscussionNote
,
nextRow
.
find
(
"
.js-discussion-reply-button
"
)
).
call
();
}
else
{
// add a notes row and insert the form
row
.
after
(
'
<tr class="notes_holder js-temp-notes-holder"><td class="notes_line" colspan="2"></td><td class="notes_content"></td></tr>
'
);
form
.
clone
().
appendTo
(
row
.
next
().
find
(
"
.notes_content
"
));
// show the form
NoteList
.
setupDiscussionNoteForm
(
$
(
this
),
row
.
next
().
find
(
"
form
"
));
}
},
/**
* Called when clicking the "Choose File" button.
*
* Opens the file selection dialog.
*/
chooseNoteAttachment
:
function
()
{
var
form
=
$
(
this
).
closest
(
"
form
"
);
form
.
find
(
"
.js-note-attachment-input
"
).
click
();
},
/**
* Shows the note preview.
*
* Lets the server render GFM into Html and displays it.
*
* Note: uses the Toggler behavior to toggle preview/edit views/buttons
*/
previewNote
:
function
(
e
)
{
e
.
preventDefault
();
var
form
=
$
(
this
).
closest
(
"
form
"
);
var
preview
=
form
.
find
(
'
.js-note-preview
'
);
var
noteText
=
form
.
find
(
'
.js-note-text
'
).
val
();
if
(
noteText
.
trim
().
length
===
0
)
{
preview
.
text
(
'
Nothing to preview.
'
);
}
else
{
preview
.
text
(
'
Loading...
'
);
$
.
post
(
$
(
this
).
data
(
'
url
'
),
{
note
:
noteText
})
.
success
(
function
(
previewData
)
{
preview
.
html
(
previewData
);
});
}
},
/**
* Called in response to "cancel" on a diff note form.
*
* Shows the reply button again.
* Removes the form and if necessary it's temporary row.
*/
removeDiscussionNoteForm
:
function
()
{
var
form
=
$
(
this
).
closest
(
"
form
"
);
var
row
=
form
.
closest
(
"
tr
"
);
// show the reply button (will only work for replies)
form
.
prev
(
"
.js-discussion-reply-button
"
).
show
();
if
(
row
.
is
(
"
.js-temp-notes-holder
"
))
{
// remove temporary row for diff lines
row
.
remove
();
}
else
{
// only remove the form
form
.
remove
();
}
},
/**
* Called in response to deleting a note of any kind.
*
* Removes the actual note from view.
* Removes the whole discussion if the last note is being removed.
*/
removeNote
:
function
()
{
var
note
=
$
(
this
).
closest
(
"
.note
"
);
var
notes
=
note
.
closest
(
"
.notes
"
);
// check if this is the last note for this line
if
(
notes
.
find
(
"
.note
"
).
length
===
1
)
{
// for discussions
notes
.
closest
(
"
.discussion
"
).
remove
();
// for diff lines
notes
.
closest
(
"
tr
"
).
remove
();
}
note
.
remove
();
NoteList
.
updateVotes
();
},
/**
* Called in response to clicking the edit note link
*
* Replaces the note text with the note edit form
* Adds a hidden div with the original content of the note to fill the edit note form with
* if the user cancels
*/
showEditNoteForm
:
function
(
e
)
{
e
.
preventDefault
();
var
note
=
$
(
this
).
closest
(
"
.note
"
);
note
.
find
(
"
.note-text
"
).
hide
();
// Show the attachment delete link
note
.
find
(
"
.js-note-attachment-delete
"
).
show
();
GitLab
.
GfmAutoComplete
.
setup
();
var
form
=
note
.
find
(
"
.note-edit-form
"
);
form
.
show
();
var
textarea
=
form
.
find
(
"
textarea
"
);
if
(
form
.
find
(
"
.note-original-content
"
).
length
===
0
)
{
var
p
=
$
(
"
<p></p>
"
).
text
(
textarea
.
val
());
var
hidden_div
=
$
(
'
<div class="note-original-content"></div>
'
).
append
(
p
);
form
.
append
(
hidden_div
);
hidden_div
.
hide
();
}
textarea
.
focus
();
},
/**
* Called in response to clicking the cancel button when editing a note
*
* Resets and hides the note editing form
*/
cancelNoteEdit
:
function
(
e
)
{
e
.
preventDefault
();
var
note
=
$
(
this
).
closest
(
"
.note
"
);
NoteList
.
resetNoteEditing
(
note
);
},
/**
* Called in response to clicking the delete attachment link
*
* Removes the attachment wrapper view, including image tag if it exists
* Resets the note editing form
*/
deleteNoteAttachment
:
function
()
{
var
note
=
$
(
this
).
closest
(
"
.note
"
);
note
.
find
(
"
.note-attachment
"
).
remove
();
NoteList
.
resetNoteEditing
(
note
);
NoteList
.
rewriteTimestamp
(
note
.
find
(
"
.note-last-update
"
));
},
/**
* Called when clicking on the "reply" button for a diff line.
*
* Shows the note form below the notes.
*/
replyToDiscussionNote
:
function
()
{
// find the form
var
form
=
$
(
"
.js-new-note-form
"
);
// hide reply button
$
(
this
).
hide
();
// insert the form after the button
form
.
clone
().
insertAfter
(
$
(
this
));
// show the form
NoteList
.
setupDiscussionNoteForm
(
$
(
this
),
$
(
this
).
next
(
"
form
"
));
},
/**
* Helper for inserting and setting up note forms.
*/
/**
* Called in response to creating a note failing validation.
*
* Adds the rendered errors to the respective form.
* If "discussionId" is null or undefined, the main target form is assumed.
*/
errorsOnForm
:
function
(
errorsHtml
,
discussionId
)
{
// find the form
if
(
discussionId
)
{
var
form
=
$
(
"
form[rel='
"
+
discussionId
+
"
']
"
);
}
else
{
var
form
=
$
(
"
.js-main-target-form
"
);
}
form
.
find
(
"
.js-errors
"
).
remove
();
form
.
prepend
(
errorsHtml
);
form
.
find
(
"
.js-note-text
"
).
focus
();
},
/**
* Shows the diff/discussion form and does some setup on it.
*
* Sets some hidden fields in the form.
*
* Note: dataHolder must have the "discussionId", "lineCode", "noteableType"
* and "noteableId" data attributes set.
*/
setupDiscussionNoteForm
:
function
(
dataHolder
,
form
)
{
// setup note target
form
.
attr
(
"
rel
"
,
dataHolder
.
data
(
"
discussionId
"
));
form
.
find
(
"
#note_commit_id
"
).
val
(
dataHolder
.
data
(
"
commitId
"
));
form
.
find
(
"
#note_line_code
"
).
val
(
dataHolder
.
data
(
"
lineCode
"
));
form
.
find
(
"
#note_noteable_type
"
).
val
(
dataHolder
.
data
(
"
noteableType
"
));
form
.
find
(
"
#note_noteable_id
"
).
val
(
dataHolder
.
data
(
"
noteableId
"
));
NoteList
.
setupNoteForm
(
form
);
form
.
find
(
"
.js-note-text
"
).
focus
();
},
/**
* Shows the main form and does some setup on it.
*
* Sets some hidden fields in the form.
*/
setupMainTargetNoteForm
:
function
()
{
// find the form
var
form
=
$
(
"
.js-new-note-form
"
);
// insert the form after the button
form
.
clone
().
replaceAll
(
$
(
"
.js-main-target-form
"
));
form
=
form
.
prev
(
"
form
"
);
// show the form
NoteList
.
setupNoteForm
(
form
);
// fix classes
form
.
removeClass
(
"
js-new-note-form
"
);
form
.
addClass
(
"
js-main-target-form
"
);
// remove unnecessary fields and buttons
form
.
find
(
"
#note_line_code
"
).
remove
();
form
.
find
(
"
.js-close-discussion-note-form
"
).
remove
();
},
/**
* General note form setup.
*
* * deactivates the submit button when text is empty
* * hides the preview button when text is empty
* * setup GFM auto complete
* * show the form
*/
setupNoteForm
:
function
(
form
)
{
disableButtonIfEmptyField
(
form
.
find
(
"
.js-note-text
"
),
form
.
find
(
"
.js-comment-button
"
));
form
.
removeClass
(
"
js-new-note-form
"
);
// setup preview buttons
form
.
find
(
"
.js-note-edit-button, .js-note-preview-button
"
)
.
tooltip
({
placement
:
'
left
'
});
previewButton
=
form
.
find
(
"
.js-note-preview-button
"
);
form
.
find
(
"
.js-note-text
"
).
on
(
"
input
"
,
function
()
{
if
(
$
(
this
).
val
().
trim
()
!==
""
)
{
previewButton
.
removeClass
(
"
turn-off
"
).
addClass
(
"
turn-on
"
);
}
else
{
previewButton
.
removeClass
(
"
turn-on
"
).
addClass
(
"
turn-off
"
);
}
});
// remove notify commit author checkbox for non-commit notes
if
(
form
.
find
(
"
#note_noteable_type
"
).
val
()
!==
"
Commit
"
)
{
form
.
find
(
"
.js-notify-commit-author
"
).
remove
();
}
GitLab
.
GfmAutoComplete
.
setup
();
form
.
show
();
},
/**
* Handle loading the initial set of notes.
* And set up loading more notes when scrolling to the bottom of the page.
*/
/**
* Gets an initial set of notes.
*/
getContent
:
function
()
{
$
.
ajax
({
url
:
NoteList
.
notes_path
,
data
:
NoteList
.
target_params
,
complete
:
function
(){
$
(
'
.js-notes-busy
'
).
removeClass
(
"
loading
"
)},
beforeSend
:
function
()
{
$
(
'
.js-notes-busy
'
).
addClass
(
"
loading
"
)
},
success
:
function
(
data
)
{
NoteList
.
setContent
(
data
.
html
);
},
dataType
:
"
json
"
});
},
/**
* Called in response to getContent().
* Replaces the content of #notes-list with the given html.
*/
setContent
:
function
(
html
)
{
$
(
"
#notes-list
"
).
html
(
html
);
},
/**
* Adds a single common note to #notes-list.
*/
appendNewNote
:
function
(
id
,
html
)
{
$
(
"
#notes-list
"
).
append
(
html
);
NoteList
.
updateVotes
();
},
/**
* Adds a single discussion note to #notes-list.
*
* Also removes the corresponding form.
*/
appendNewDiscussionNote
:
function
(
discussionId
,
diffRowHtml
,
noteHtml
)
{
var
form
=
$
(
"
form[rel='
"
+
discussionId
+
"
']
"
);
var
row
=
form
.
closest
(
"
tr
"
);
// is this the first note of discussion?
if
(
row
.
is
(
"
.js-temp-notes-holder
"
))
{
// insert the note and the reply button after the temp row
row
.
after
(
diffRowHtml
);
// remove the note (will be added again below)
row
.
next
().
find
(
"
.note
"
).
remove
();
}
// append new note to all matching discussions
$
(
"
.notes[rel='
"
+
discussionId
+
"
']
"
).
append
(
noteHtml
);
// cleanup after successfully creating a diff/discussion note
$
.
proxy
(
NoteList
.
removeDiscussionNoteForm
,
form
).
call
();
},
/**
* Called in response the main target form has been successfully submitted.
*
* Removes any errors.
* Resets text and preview.
* Resets buttons.
*/
resetMainTargetForm
:
function
(){
var
form
=
$
(
this
);
// remove validation errors
form
.
find
(
"
.js-errors
"
).
remove
();
// reset text and preview
var
previewContainer
=
form
.
find
(
"
.js-toggler-container.note_text_and_preview
"
);
if
(
previewContainer
.
is
(
"
.on
"
))
{
previewContainer
.
removeClass
(
"
on
"
);
}
form
.
find
(
"
.js-note-text
"
).
val
(
""
).
trigger
(
"
input
"
);
},
/**
* Called after an attachment file has been selected.
*
* Updates the file name for the selected attachment.
*/
updateFormAttachment
:
function
()
{
var
form
=
$
(
this
).
closest
(
"
form
"
);
// get only the basename
var
filename
=
$
(
this
).
val
().
replace
(
/^.*
[\\\/]
/
,
''
);
form
.
find
(
"
.js-attachment-filename
"
).
text
(
filename
);
},
/**
* Recalculates the votes and updates them (if they are displayed at all).
*
* Assumes all relevant notes are displayed (i.e. there are no more notes to
* load via getMore()).
* Might produce inaccurate results when not all notes have been loaded and a
* recalculation is triggered (e.g. when deleting a note).
*/
updateVotes
:
function
()
{
var
votes
=
$
(
"
#votes .votes
"
);
var
notes
=
$
(
"
#notes-list .note .vote
"
);
// only update if there is a vote display
if
(
votes
.
size
())
{
var
upvotes
=
notes
.
filter
(
"
.upvote
"
).
size
();
var
downvotes
=
notes
.
filter
(
"
.downvote
"
).
size
();
var
votesCount
=
upvotes
+
downvotes
;
var
upvotesPercent
=
votesCount
?
(
100.0
/
votesCount
*
upvotes
)
:
0
;
var
downvotesPercent
=
votesCount
?
(
100.0
-
upvotesPercent
)
:
0
;
// change vote bar lengths
votes
.
find
(
"
.bar-success
"
).
css
(
"
width
"
,
upvotesPercent
+
"
%
"
);
votes
.
find
(
"
.bar-danger
"
).
css
(
"
width
"
,
downvotesPercent
+
"
%
"
);
// replace vote numbers
votes
.
find
(
"
.upvotes
"
).
text
(
votes
.
find
(
"
.upvotes
"
).
text
().
replace
(
/
\d
+/
,
upvotes
));
votes
.
find
(
"
.downvotes
"
).
text
(
votes
.
find
(
"
.downvotes
"
).
text
().
replace
(
/
\d
+/
,
downvotes
));
}
},
/**
* Called in response to the edit note form being submitted
*
* Updates the current note field.
* Hides the edit note form
*/
updateNote
:
function
(
e
,
xhr
,
settings
)
{
response
=
JSON
.
parse
(
xhr
.
responseText
);
if
(
response
.
success
)
{
var
note_li
=
$
(
"
#note_
"
+
response
.
id
);
var
note_text
=
note_li
.
find
(
"
.note-text
"
);
note_text
.
html
(
response
.
note
).
show
();
var
note_form
=
note_li
.
find
(
"
.note-edit-form
"
);
var
original_content
=
note_form
.
find
(
"
.note-original-content
"
);
original_content
.
remove
();
note_form
.
hide
();
note_form
.
find
(
"
.btn-save
"
).
enableButton
();
// Update the "Edited at xxx label" on the note to show it's just been updated
NoteList
.
rewriteTimestamp
(
note_li
.
find
(
"
.note-last-update
"
));
}
},
/**
* Called in response to the 'cancel note' link clicked, or after deleting a note attachment
*
* Hides the edit note form and shows the note
* Resets the edit note form textarea with the original content of the note
*/
resetNoteEditing
:
function
(
note
)
{
note
.
find
(
"
.note-text
"
).
show
();
// Hide the attachment delete link
note
.
find
(
"
.js-note-attachment-delete
"
).
hide
();
// Put the original content of the note back into the edit form textarea
var
form
=
note
.
find
(
"
.note-edit-form
"
);
var
original_content
=
form
.
find
(
"
.note-original-content
"
);
form
.
find
(
"
textarea
"
).
val
(
original_content
.
text
());
original_content
.
remove
();
note
.
find
(
"
.note-edit-form
"
).
hide
();
},
/**
* Utility function to generate new timestamp text for a note
*
*/
rewriteTimestamp
:
function
(
element
)
{
// Strip all newlines from the existing timestamp
var
ts
=
element
.
text
().
replace
(
/
\n
/g
,
'
'
).
trim
();
// If the timestamp already has '(Edited xxx ago)' text, remove it
ts
=
ts
.
replace
(
new
RegExp
(
"
\\
(Edited [A-Za-z0-9 ]+
\\
)$
"
,
"
gi
"
),
""
);
// Append "(Edited just now)"
ts
=
(
ts
+
"
<small>(Edited just now)</small>
"
);
element
.
html
(
ts
);
}
};
app/assets/javascripts/notes.js.coffee
0 → 100644
View file @
7bf0add0
class
Notes
constructor
:
(
notes_url
,
note_ids
)
->
@
notes_url
=
notes_url
@
notes_url
=
gon
.
relative_url_root
+
@
notes_url
if
gon
.
relative_url_root
?
@
note_ids
=
note_ids
@
initRefresh
()
@
setupMainTargetNoteForm
()
@
cleanBinding
()
@
addBinding
()
addBinding
:
->
# add note to UI after creation
$
(
document
).
on
"ajax:success"
,
".js-main-target-form"
,
@
addNote
$
(
document
).
on
"ajax:success"
,
".js-discussion-note-form"
,
@
addDiscussionNote
# change note in UI after update
$
(
document
).
on
"ajax:success"
,
"form.edit_note"
,
@
updateNote
# Edit note link
$
(
document
).
on
"click"
,
".js-note-edit"
,
@
showEditForm
$
(
document
).
on
"click"
,
".note-edit-cancel"
,
@
cancelEdit
# remove a note (in general)
$
(
document
).
on
"click"
,
".js-note-delete"
,
@
removeNote
# delete note attachment
$
(
document
).
on
"click"
,
".js-note-attachment-delete"
,
@
removeAttachment
# Preview button
$
(
document
).
on
"click"
,
".js-note-preview-button"
,
@
previewNote
# reset main target form after submit
$
(
document
).
on
"ajax:complete"
,
".js-main-target-form"
,
@
resetMainTargetForm
# attachment button
$
(
document
).
on
"click"
,
".js-choose-note-attachment-button"
,
@
chooseNoteAttachment
# reply to diff/discussion notes
$
(
document
).
on
"click"
,
".js-discussion-reply-button"
,
@
replyToDiscussionNote
# add diff note
$
(
document
).
on
"click"
,
".js-add-diff-note-button"
,
@
addDiffNote
# hide diff note form
$
(
document
).
on
"click"
,
".js-close-discussion-note-form"
,
@
cancelDiscussionForm
cleanBinding
:
->
$
(
document
).
off
"ajax:success"
,
".js-main-target-form"
$
(
document
).
off
"ajax:success"
,
".js-discussion-note-form"
$
(
document
).
off
"ajax:success"
,
"form.edit_note"
$
(
document
).
off
"click"
,
".js-note-edit"
$
(
document
).
off
"click"
,
".note-edit-cancel"
$
(
document
).
off
"click"
,
".js-note-delete"
$
(
document
).
off
"click"
,
".js-note-attachment-delete"
$
(
document
).
off
"click"
,
".js-note-preview-button"
$
(
document
).
off
"ajax:complete"
,
".js-main-target-form"
$
(
document
).
off
"click"
,
".js-choose-note-attachment-button"
$
(
document
).
off
"click"
,
".js-discussion-reply-button"
$
(
document
).
off
"click"
,
".js-add-diff-note-button"
initRefresh
:
->
setInterval
=>
@
refresh
()
,
15000
refresh
:
->
@
getContent
()
getContent
:
->
$
.
ajax
url
:
@
notes_url
dataType
:
"json"
success
:
(
data
)
=>
notes
=
data
.
notes
$
.
each
notes
,
(
i
,
note
)
=>
# render note if it not present in loaded list
# or skip if rendered
if
$
.
inArray
(
note
.
id
,
@
note_ids
)
==
-
1
@
note_ids
.
push
(
note
.
id
)
@
renderNote
(
note
)
###
Render note in main comments area.
Note: for rendering inline notes use renderDiscussionNote
###
renderNote
:
(
note
)
->
$
(
'ul.main-notes-list'
).
append
(
note
.
html
)
###
Render note in discussion area.
Note: for rendering inline notes use renderDiscussionNote
###
renderDiscussionNote
:
(
note
)
->
form
=
$
(
"form[rel='"
+
note
.
discussion_id
+
"']"
)
row
=
form
.
closest
(
"tr"
)
# is this the first note of discussion?
if
row
.
is
(
".js-temp-notes-holder"
)
# insert the note and the reply button after the temp row
row
.
after
note
.
discussion_html
# remove the note (will be added again below)
row
.
next
().
find
(
".note"
).
remove
()
# append new note to all matching discussions
$
(
".notes[rel='"
+
note
.
discussion_id
+
"']"
).
append
note
.
html
# cleanup after successfully creating a diff/discussion note
@
removeDiscussionNoteForm
(
form
)
###
Shows the note preview.
Lets the server render GFM into Html and displays it.
Note: uses the Toggler behavior to toggle preview/edit views/buttons
###
previewNote
:
(
e
)
->
e
.
preventDefault
()
form
=
$
(
this
).
closest
(
"form"
)
preview
=
form
.
find
(
".js-note-preview"
)
noteText
=
form
.
find
(
".js-note-text"
).
val
()
if
noteText
.
trim
().
length
is
0
preview
.
text
"Nothing to preview."
else
preview
.
text
"Loading..."
$
.
post
(
$
(
this
).
data
(
"url"
),
note
:
noteText
).
success
(
previewData
)
->
preview
.
html
previewData
###
Called in response the main target form has been successfully submitted.
Removes any errors.
Resets text and preview.
Resets buttons.
###
resetMainTargetForm
:
->
form
=
$
(
".js-main-target-form"
)
# remove validation errors
form
.
find
(
".js-errors"
).
remove
()
# reset text and preview
previewContainer
=
form
.
find
(
".js-toggler-container.note_text_and_preview"
)
previewContainer
.
removeClass
"on"
if
previewContainer
.
is
(
".on"
)
form
.
find
(
".js-note-text"
).
val
(
""
).
trigger
"input"
###
Called when clicking the "Choose File" button.
Opens the file selection dialog.
###
chooseNoteAttachment
:
->
form
=
$
(
this
).
closest
(
"form"
)
form
.
find
(
".js-note-attachment-input"
).
click
()
###
Shows the main form and does some setup on it.
Sets some hidden fields in the form.
###
setupMainTargetNoteForm
:
->
# find the form
form
=
$
(
".js-new-note-form"
)
# insert the form after the button
form
.
clone
().
replaceAll
$
(
".js-main-target-form"
)
form
=
form
.
prev
(
"form"
)
# show the form
@
setupNoteForm
(
form
)
# fix classes
form
.
removeClass
"js-new-note-form"
form
.
addClass
"js-main-target-form"
# remove unnecessary fields and buttons
form
.
find
(
"#note_line_code"
).
remove
()
form
.
find
(
".js-close-discussion-note-form"
).
remove
()
###
General note form setup.
deactivates the submit button when text is empty
hides the preview button when text is empty
setup GFM auto complete
show the form
###
setupNoteForm
:
(
form
)
->
disableButtonIfEmptyField
form
.
find
(
".js-note-text"
),
form
.
find
(
".js-comment-button"
)
form
.
removeClass
"js-new-note-form"
# setup preview buttons
form
.
find
(
".js-note-edit-button, .js-note-preview-button"
).
tooltip
placement
:
"left"
previewButton
=
form
.
find
(
".js-note-preview-button"
)
form
.
find
(
".js-note-text"
).
on
"input"
,
->
if
$
(
this
).
val
().
trim
()
isnt
""
previewButton
.
removeClass
(
"turn-off"
).
addClass
"turn-on"
else
previewButton
.
removeClass
(
"turn-on"
).
addClass
"turn-off"
# remove notify commit author checkbox for non-commit notes
form
.
find
(
".js-notify-commit-author"
).
remove
()
if
form
.
find
(
"#note_noteable_type"
).
val
()
isnt
"Commit"
GitLab
.
GfmAutoComplete
.
setup
()
form
.
show
()
###
Called in response to the new note form being submitted
Adds new note to list.
###
addNote
:
(
xhr
,
note
,
status
)
=>
@
note_ids
.
push
(
note
.
id
)
@
renderNote
(
note
)
###
Called in response to the new note form being submitted
Adds new note to list.
###
addDiscussionNote
:
(
xhr
,
note
,
status
)
=>
@
note_ids
.
push
(
note
.
id
)
@
renderDiscussionNote
(
note
)
###
Called in response to the edit note form being submitted
Updates the current note field.
###
updateNote
:
(
xhr
,
note
,
status
)
=>
note_li
=
$
(
"#note_"
+
note
.
id
)
note_li
.
replaceWith
(
note
.
html
)
###
Called in response to clicking the edit note link
Replaces the note text with the note edit form
Adds a hidden div with the original content of the note to fill the edit note form with
if the user cancels
###
showEditForm
:
(
e
)
->
e
.
preventDefault
()
note
=
$
(
this
).
closest
(
".note"
)
note
.
find
(
".note-text"
).
hide
()
# Show the attachment delete link
note
.
find
(
".js-note-attachment-delete"
).
show
()
GitLab
.
GfmAutoComplete
.
setup
()
form
=
note
.
find
(
".note-edit-form"
)
form
.
show
()
form
.
find
(
"textarea"
).
focus
()
###
Called in response to clicking the edit note link
Hides edit form
###
cancelEdit
:
(
e
)
->
e
.
preventDefault
()
note
=
$
(
this
).
closest
(
".note"
)
note
.
find
(
".note-text"
).
show
()
note
.
find
(
".js-note-attachment-delete"
).
hide
()
note
.
find
(
".note-edit-form"
).
hide
()
###
Called in response to deleting a note of any kind.
Removes the actual note from view.
Removes the whole discussion if the last note is being removed.
###
removeNote
:
->
note
=
$
(
this
).
closest
(
".note"
)
notes
=
note
.
closest
(
".notes"
)
# check if this is the last note for this line
if
notes
.
find
(
".note"
).
length
is
1
# for discussions
notes
.
closest
(
".discussion"
).
remove
()
# for diff lines
notes
.
closest
(
"tr"
).
remove
()
note
.
remove
()
###
Called in response to clicking the delete attachment link
Removes the attachment wrapper view, including image tag if it exists
Resets the note editing form
###
removeAttachment
:
->
note
=
$
(
this
).
closest
(
".note"
)
note
.
find
(
".note-attachment"
).
remove
()
note
.
find
(
".note-text"
).
show
()
note
.
find
(
".js-note-attachment-delete"
).
hide
()
note
.
find
(
".note-edit-form"
).
hide
()
###
Called when clicking on the "reply" button for a diff line.
Shows the note form below the notes.
###
replyToDiscussionNote
:
(
e
)
=>
form
=
$
(
".js-new-note-form"
)
replyLink
=
$
(
e
.
target
)
replyLink
.
hide
()
# insert the form after the button
form
.
clone
().
insertAfter
replyLink
# show the form
@
setupDiscussionNoteForm
(
replyLink
,
replyLink
.
next
(
"form"
))
###
Shows the diff or discussion form and does some setup on it.
Sets some hidden fields in the form.
Note: dataHolder must have the "discussionId", "lineCode", "noteableType"
and "noteableId" data attributes set.
###
setupDiscussionNoteForm
:
(
dataHolder
,
form
)
=>
# setup note target
form
.
attr
"rel"
,
dataHolder
.
data
(
"discussionId"
)
form
.
find
(
"#note_commit_id"
).
val
dataHolder
.
data
(
"commitId"
)
form
.
find
(
"#note_line_code"
).
val
dataHolder
.
data
(
"lineCode"
)
form
.
find
(
"#note_noteable_type"
).
val
dataHolder
.
data
(
"noteableType"
)
form
.
find
(
"#note_noteable_id"
).
val
dataHolder
.
data
(
"noteableId"
)
@
setupNoteForm
form
form
.
find
(
".js-note-text"
).
focus
()
form
.
addClass
"js-discussion-note-form"
###
General note form setup.
deactivates the submit button when text is empty
hides the preview button when text is empty
setup GFM auto complete
show the form
###
setupNoteForm
:
(
form
)
=>
disableButtonIfEmptyField
form
.
find
(
".js-note-text"
),
form
.
find
(
".js-comment-button"
)
form
.
removeClass
"js-new-note-form"
form
.
removeClass
"js-new-note-form"
GitLab
.
GfmAutoComplete
.
setup
()
# setup preview buttons
previewButton
=
form
.
find
(
".js-note-preview-button"
)
form
.
find
(
".js-note-text"
).
on
"input"
,
->
if
$
(
this
).
val
().
trim
()
isnt
""
previewButton
.
removeClass
(
"turn-off"
).
addClass
"turn-on"
else
previewButton
.
removeClass
(
"turn-on"
).
addClass
"turn-off"
form
.
show
()
###
Called when clicking on the "add a comment" button on the side of a diff line.
Inserts a temporary row for the form below the line.
Sets up the form and shows it.
###
addDiffNote
:
(
e
)
=>
e
.
preventDefault
()
link
=
e
.
target
form
=
$
(
".js-new-note-form"
)
row
=
$
(
link
).
closest
(
"tr"
)
nextRow
=
row
.
next
()
# does it already have notes?
if
nextRow
.
is
(
".notes_holder"
)
replyButton
=
nextRow
.
find
(
".js-discussion-reply-button"
)
if
replyButton
.
length
>
0
$
.
proxy
(
@
replyToDiscussionNote
,
replyButton
).
call
()
else
# add a notes row and insert the form
row
.
after
"<tr class=
\"
notes_holder js-temp-notes-holder
\"
><td class=
\"
notes_line
\"
colspan=
\"
2
\"
></td><td class=
\"
notes_content
\"
></td></tr>"
form
.
clone
().
appendTo
row
.
next
().
find
(
".notes_content"
)
# show the form
@
setupDiscussionNoteForm
$
(
link
),
row
.
next
().
find
(
"form"
)
###
Called in response to "cancel" on a diff note form.
Shows the reply button again.
Removes the form and if necessary it's temporary row.
###
removeDiscussionNoteForm
:
(
form
)
->
row
=
form
.
closest
(
"tr"
)
# show the reply button (will only work for replies)
form
.
prev
(
".js-discussion-reply-button"
).
show
()
if
row
.
is
(
".js-temp-notes-holder"
)
# remove temporary row for diff lines
row
.
remove
()
else
# only remove the form
form
.
remove
()
cancelDiscussionForm
:
(
e
)
=>
e
.
preventDefault
()
form
=
$
(
".js-new-note-form"
)
form
=
$
(
e
.
target
).
closest
(
".js-discussion-note-form"
)
@
removeDiscussionNoteForm
(
form
)
@
Notes
=
Notes
app/assets/stylesheets/sections/notes.scss
View file @
7bf0add0
...
@@ -257,12 +257,12 @@ ul.notes {
...
@@ -257,12 +257,12 @@ ul.notes {
.file
,
.file
,
.discussion
{
.discussion
{
.new_note
{
.new_note
{
margin
:
8px
5px
8px
0
;
margin
:
0
;
border
:
none
;
}
}
}
}
.new_note
{
.new_note
{
display
:
none
;
display
:
none
;
.buttons
{
.buttons
{
float
:
left
;
float
:
left
;
margin-top
:
8px
;
margin-top
:
8px
;
...
...
app/controllers/projects/commit_controller.rb
View file @
7bf0add0
...
@@ -24,8 +24,8 @@ class Projects::CommitController < Projects::ApplicationController
...
@@ -24,8 +24,8 @@ class Projects::CommitController < Projects::ApplicationController
@line_notes
=
result
[
:line_notes
]
@line_notes
=
result
[
:line_notes
]
@branches
=
result
[
:branches
]
@branches
=
result
[
:branches
]
@notes_count
=
result
[
:notes_count
]
@notes_count
=
result
[
:notes_count
]
@
target_type
=
:commit
@
notes
=
project
.
notes
.
for_commit_id
(
@commit
.
id
).
not_inline
.
fresh
@
target_id
=
@commit
.
id
@
noteable
=
@commit
@comments_allowed
=
@reply_allowed
=
true
@comments_allowed
=
@reply_allowed
=
true
@comments_target
=
{
noteable_type:
'Commit'
,
@comments_target
=
{
noteable_type:
'Commit'
,
...
...
app/controllers/projects/issues_controller.rb
View file @
7bf0add0
...
@@ -49,8 +49,8 @@ class Projects::IssuesController < Projects::ApplicationController
...
@@ -49,8 +49,8 @@ class Projects::IssuesController < Projects::ApplicationController
def
show
def
show
@note
=
@project
.
notes
.
new
(
noteable:
@issue
)
@note
=
@project
.
notes
.
new
(
noteable:
@issue
)
@
target_type
=
:issue
@
notes
=
@issue
.
notes
.
inc_author
.
fresh
@
target_id
=
@issue
.
id
@
noteable
=
@issue
respond_with
(
@issue
)
respond_with
(
@issue
)
end
end
...
...
app/controllers/projects/merge_requests_controller.rb
View file @
7bf0add0
...
@@ -198,6 +198,9 @@ class Projects::MergeRequestsController < Projects::ApplicationController
...
@@ -198,6 +198,9 @@ class Projects::MergeRequestsController < Projects::ApplicationController
def
define_show_vars
def
define_show_vars
# Build a note object for comment form
# Build a note object for comment form
@note
=
@project
.
notes
.
new
(
noteable:
@merge_request
)
@note
=
@project
.
notes
.
new
(
noteable:
@merge_request
)
@notes
=
@merge_request
.
mr_and_commit_notes
.
inc_author
.
fresh
@discussions
=
Note
.
discussions_from_notes
(
@notes
)
@noteable
=
@merge_request
# Get commits from repository
# Get commits from repository
# or from cache if already merged
# or from cache if already merged
...
@@ -205,9 +208,6 @@ class Projects::MergeRequestsController < Projects::ApplicationController
...
@@ -205,9 +208,6 @@ class Projects::MergeRequestsController < Projects::ApplicationController
@allowed_to_merge
=
allowed_to_merge?
@allowed_to_merge
=
allowed_to_merge?
@show_merge_controls
=
@merge_request
.
opened?
&&
@commits
.
any?
&&
@allowed_to_merge
@show_merge_controls
=
@merge_request
.
opened?
&&
@commits
.
any?
&&
@allowed_to_merge
@target_type
=
:merge_request
@target_id
=
@merge_request
.
id
end
end
def
allowed_to_merge?
def
allowed_to_merge?
...
...
app/controllers/projects/notes_controller.rb
View file @
7bf0add0
...
@@ -2,71 +2,54 @@ class Projects::NotesController < Projects::ApplicationController
...
@@ -2,71 +2,54 @@ class Projects::NotesController < Projects::ApplicationController
# Authorize
# Authorize
before_filter
:authorize_read_note!
before_filter
:authorize_read_note!
before_filter
:authorize_write_note!
,
only:
[
:create
]
before_filter
:authorize_write_note!
,
only:
[
:create
]
before_filter
:authorize_admin_note!
,
only:
[
:update
,
:destroy
]
respond_to
:js
def
index
def
index
@notes
=
Notes
::
LoadContext
.
new
(
project
,
current_user
,
params
).
execute
@notes
=
Notes
::
LoadContext
.
new
(
project
,
current_user
,
params
).
execute
@target_type
=
params
[
:target_type
].
camelize
@target_id
=
params
[
:target_id
]
if
params
[
:target_type
]
==
"merge_request"
notes_json
=
{
notes:
[]
}
@discussions
=
discussions_from_notes
end
respond_to
do
|
format
|
@notes
.
each
do
|
note
|
format
.
html
{
redirect_to
:back
}
notes_json
[
:notes
]
<<
{
format
.
json
do
id:
note
.
id
,
render
json:
{
html:
note_to_html
(
note
)
html:
view_to_html_string
(
"projects/notes/_notes"
)
}
}
end
end
end
render
json:
notes_json
end
end
def
create
def
create
@note
=
Notes
::
CreateContext
.
new
(
project
,
current_user
,
params
).
execute
@note
=
Notes
::
CreateContext
.
new
(
project
,
current_user
,
params
).
execute
@target_type
=
params
[
:target_type
].
camelize
@target_id
=
params
[
:target_id
]
respond_to
do
|
format
|
respond_to
do
|
format
|
format
.
html
{
redirect_to
:back
}
format
.
json
{
render_note_json
(
@note
)
}
format
.
js
format
.
html
{
redirect_to
:back
}
end
end
end
end
def
destroy
def
update
@note
=
@project
.
notes
.
find
(
params
[
:id
])
note
.
update_attributes
(
params
[
:note
])
return
access_denied!
unless
can?
(
current_user
,
:admin_note
,
@note
)
note
.
reset_events_cache
@note
.
destroy
@note
.
reset_events_cache
respond_to
do
|
format
|
respond_to
do
|
format
|
format
.
js
{
render
nothing:
true
}
format
.
json
{
render_note_json
(
note
)
}
format
.
html
{
redirect_to
:back
}
end
end
end
end
def
update
def
destroy
@note
=
@project
.
notes
.
find
(
params
[
:id
])
note
.
destroy
return
access_denied!
unless
can?
(
current_user
,
:admin_note
,
@note
)
note
.
reset_events_cache
@note
.
update_attributes
(
params
[
:note
])
@note
.
reset_events_cache
respond_to
do
|
format
|
respond_to
do
|
format
|
format
.
js
do
format
.
js
{
render
nothing:
true
}
render
js:
{
success:
@note
.
valid?
,
id:
@note
.
id
,
note:
view_context
.
markdown
(
@note
.
note
)
}.
to_json
end
format
.
html
do
redirect_to
:back
end
end
end
end
end
def
delete_attachment
def
delete_attachment
@note
=
@project
.
notes
.
find
(
params
[
:id
])
note
.
remove_attachment!
@note
.
remove_attachment!
note
.
update_attribute
(
:attachment
,
nil
)
@note
.
update_attribute
(
:attachment
,
nil
)
respond_to
do
|
format
|
respond_to
do
|
format
|
format
.
js
{
render
nothing:
true
}
format
.
js
{
render
nothing:
true
}
...
@@ -77,35 +60,40 @@ class Projects::NotesController < Projects::ApplicationController
...
@@ -77,35 +60,40 @@ class Projects::NotesController < Projects::ApplicationController
render
text:
view_context
.
markdown
(
params
[
:note
])
render
text:
view_context
.
markdown
(
params
[
:note
])
end
end
pr
otected
pr
ivate
def
discussion_notes_for
(
note
)
def
note
@notes
.
select
do
|
other_note
|
@note
||=
@project
.
notes
.
find
(
params
[
:id
])
note
.
discussion_id
==
other_note
.
discussion_id
end
end
end
def
discussions_from_notes
def
note_to_html
(
note
)
discussion_ids
=
[]
render_to_string
(
discussions
=
[]
"projects/notes/_note"
,
layout:
false
,
formats:
[
:html
],
locals:
{
note:
note
}
)
end
@notes
.
each
do
|
note
|
def
note_to_discussion_html
(
note
)
next
if
discussion_ids
.
include?
(
note
.
discussion_id
)
render_to_string
(
"projects/notes/_diff_notes_with_reply"
,
# don't group notes for the main target
layout:
false
,
if
note_for_main_target?
(
note
)
formats:
[
:html
],
discussions
<<
[
note
]
locals:
{
notes:
[
note
]
}
else
)
discussions
<<
discussion_notes_for
(
note
)
end
discussion_ids
<<
note
.
discussion_id
end
end
discussions
def
render_note_json
(
note
)
render
json:
{
id:
note
.
id
,
discussion_id:
note
.
discussion_id
,
html:
note_to_html
(
note
),
discussion_html:
note_to_discussion_html
(
note
)
}
end
end
# Helps to distinguish e.g. commit notes in mr notes list
def
authorize_admin_note!
def
note_for_main_target?
(
note
)
return
access_denied!
unless
can?
(
current_user
,
:admin_note
,
note
)
(
@target_type
.
camelize
==
note
.
noteable_type
&&
!
note
.
for_diff_line?
)
end
end
end
end
app/controllers/projects/snippets_controller.rb
View file @
7bf0add0
...
@@ -48,8 +48,8 @@ class Projects::SnippetsController < Projects::ApplicationController
...
@@ -48,8 +48,8 @@ class Projects::SnippetsController < Projects::ApplicationController
def
show
def
show
@note
=
@project
.
notes
.
new
(
noteable:
@snippet
)
@note
=
@project
.
notes
.
new
(
noteable:
@snippet
)
@
target_type
=
:snippet
@
notes
=
@snippet
.
notes
.
fresh
@
target_id
=
@snippet
.
id
@
noteable
=
@snippet
end
end
def
destroy
def
destroy
...
...
app/helpers/notes_helper.rb
View file @
7bf0add0
module
NotesHelper
module
NotesHelper
# Helps to distinguish e.g. commit notes in mr notes list
# Helps to distinguish e.g. commit notes in mr notes list
def
note_for_main_target?
(
note
)
def
note_for_main_target?
(
note
)
(
@
target_type
.
cameliz
e
==
note
.
noteable_type
&&
!
note
.
for_diff_line?
)
(
@
noteable
.
class
.
nam
e
==
note
.
noteable_type
&&
!
note
.
for_diff_line?
)
end
end
def
note_target_fields
def
note_target_fields
...
@@ -21,14 +21,6 @@ module NotesHelper
...
@@ -21,14 +21,6 @@ module NotesHelper
end
end
end
end
def
loading_more_notes?
params
[
:loading_more
].
present?
end
def
loading_new_notes?
params
[
:loading_new
].
present?
end
def
note_timestamp
(
note
)
def
note_timestamp
(
note
)
# Shows the created at time and the updated at time if different
# Shows the created at time and the updated at time if different
ts
=
"
#{
time_ago_with_tooltip
(
note
.
created_at
,
'bottom'
,
'note_created_ago'
)
}
ago"
ts
=
"
#{
time_ago_with_tooltip
(
note
.
created_at
,
'bottom'
,
'note_created_ago'
)
}
ago"
...
@@ -41,4 +33,13 @@ module NotesHelper
...
@@ -41,4 +33,13 @@ module NotesHelper
end
end
ts
.
html_safe
ts
.
html_safe
end
end
def
noteable_json
(
noteable
)
{
id:
noteable
.
id
,
class:
noteable
.
class
.
name
,
resources:
noteable
.
class
.
table_name
,
project_id:
noteable
.
project
.
id
,
}.
to_json
end
end
end
app/models/note.rb
View file @
7bf0add0
...
@@ -56,29 +56,52 @@ class Note < ActiveRecord::Base
...
@@ -56,29 +56,52 @@ class Note < ActiveRecord::Base
serialize
:st_diff
serialize
:st_diff
before_create
:set_diff
,
if:
->
(
n
)
{
n
.
line_code
.
present?
}
before_create
:set_diff
,
if:
->
(
n
)
{
n
.
line_code
.
present?
}
def
self
.
create_status_change_note
(
noteable
,
project
,
author
,
status
,
source
)
class
<<
self
body
=
"_Status changed to
#{
status
}#{
' by '
+
source
.
gfm_reference
if
source
}
_"
def
create_status_change_note
(
noteable
,
project
,
author
,
status
,
source
)
body
=
"_Status changed to
#{
status
}#{
' by '
+
source
.
gfm_reference
if
source
}
_"
create
({
noteable:
noteable
,
create
({
project:
project
,
noteable:
noteable
,
author:
author
,
project:
project
,
note:
body
,
author:
author
,
system:
true
note:
body
,
},
without_protection:
true
)
system:
true
end
},
without_protection:
true
)
end
# +noteable+ was referenced from +mentioner+, by including GFM in either +mentioner+'s description or an associated Note.
# Create a system Note associated with +noteable+ with a GFM back-reference to +mentioner+.
def
create_cross_reference_note
(
noteable
,
mentioner
,
author
,
project
)
create
({
noteable:
noteable
,
commit_id:
(
noteable
.
sha
if
noteable
.
respond_to?
:sha
),
project:
project
,
author:
author
,
note:
"_mentioned in
#{
mentioner
.
gfm_reference
}
_"
,
system:
true
},
without_protection:
true
)
end
# +noteable+ was referenced from +mentioner+, by including GFM in either +mentioner+'s description or an associated Note.
def
discussions_from_notes
(
notes
)
# Create a system Note associated with +noteable+ with a GFM back-reference to +mentioner+.
discussion_ids
=
[]
def
self
.
create_cross_reference_note
(
noteable
,
mentioner
,
author
,
project
)
discussions
=
[]
create
({
noteable:
noteable
,
notes
.
each
do
|
note
|
commit_id:
(
noteable
.
sha
if
noteable
.
respond_to?
:sha
),
next
if
discussion_ids
.
include?
(
note
.
discussion_id
)
project:
project
,
author:
author
,
# don't group notes for the main target
note:
"_mentioned in
#{
mentioner
.
gfm_reference
}
_"
,
if
!
note
.
for_diff_line?
&&
note
.
noteable_type
==
"MergeRequest"
system:
true
discussions
<<
[
note
]
},
without_protection:
true
)
else
discussions
<<
notes
.
select
do
|
other_note
|
note
.
discussion_id
==
other_note
.
discussion_id
end
discussion_ids
<<
note
.
discussion_id
end
end
discussions
end
end
end
# Determine whether or not a cross-reference note already exists.
# Determine whether or not a cross-reference note already exists.
...
@@ -89,7 +112,7 @@ class Note < ActiveRecord::Base
...
@@ -89,7 +112,7 @@ class Note < ActiveRecord::Base
def
commit_author
def
commit_author
@commit_author
||=
@commit_author
||=
project
.
users
.
find_by_email
(
noteable
.
author_email
)
||
project
.
users
.
find_by_email
(
noteable
.
author_email
)
||
project
.
users
.
find_by_name
(
noteable
.
author_name
)
project
.
users
.
find_by_name
(
noteable
.
author_name
)
rescue
rescue
nil
nil
end
end
...
...
app/views/projects/notes/_form.html.haml
View file @
7bf0add0
=
form_for
[
@project
,
@note
],
remote:
true
,
html:
{
multipart:
true
,
id:
nil
,
class:
"new_note js-new-note-form common-note-form"
},
authenticity_token:
true
do
|
f
|
=
form_for
[
@project
,
@note
],
remote:
true
,
html:
{
:'data-type'
=>
'json'
,
multipart:
true
,
id:
nil
,
class:
"new_note js-new-note-form common-note-form"
},
authenticity_token:
true
do
|
f
|
=
note_target_fields
=
note_target_fields
=
f
.
hidden_field
:commit_id
=
f
.
hidden_field
:commit_id
=
f
.
hidden_field
:line_code
=
f
.
hidden_field
:line_code
...
...
app/views/projects/notes/_note.html.haml
View file @
7bf0add0
...
@@ -34,7 +34,7 @@
...
@@ -34,7 +34,7 @@
=
markdown
(
note
.
note
)
=
markdown
(
note
.
note
)
.note-edit-form
.note-edit-form
=
form_for
note
,
url:
project_note_path
(
@project
,
note
),
method: :put
,
remote:
true
do
|
f
|
=
form_for
note
,
url:
project_note_path
(
@project
,
note
),
method: :put
,
remote:
true
,
authenticity_token:
true
do
|
f
|
=
f
.
text_area
:note
,
class:
'note_text js-note-text js-gfm-input turn-on'
=
f
.
text_area
:note
,
class:
'note_text js-note-text js-gfm-input turn-on'
.form-actions
.form-actions
...
...
app/views/projects/notes/_notes.html.haml
View file @
7bf0add0
...
@@ -4,7 +4,7 @@
...
@@ -4,7 +4,7 @@
-
if
note_for_main_target?
(
note
)
-
if
note_for_main_target?
(
note
)
=
render
discussion_notes
=
render
discussion_notes
-
else
-
else
=
render
'discussion'
,
discussion_notes:
discussion_notes
=
render
'
projects/notes/
discussion'
,
discussion_notes:
discussion_notes
-
else
-
else
-
@notes
.
each
do
|
note
|
-
@notes
.
each
do
|
note
|
-
next
unless
note
.
author
-
next
unless
note
.
author
...
...
app/views/projects/notes/_notes_with_form.html.haml
View file @
7bf0add0
%ul
#notes-list
.notes
%ul
#notes-list
.notes.main-notes-list
=
render
"projects/notes/notes"
.js-notes-busy
.js-notes-busy
.js-main-target-form
.js-main-target-form
...
@@ -6,4 +7,4 @@
...
@@ -6,4 +7,4 @@
=
render
"projects/notes/form"
=
render
"projects/notes/form"
:javascript
:javascript
NoteList
.
init
(
"
#{
@target_id
}
"
,
"
#{
@target_type
}
"
,
"
#{
project_notes_path
(
@project
)
}
"
);
new
Notes
(
"
#{
project_notes_path
(
target_id:
@noteable
.
id
,
target_type:
@noteable
.
class
.
name
.
underscore
)
}
"
,
#{
@notes
.
map
(
&
:id
).
to_json
}
)
app/views/projects/notes/create.js.haml
deleted
100644 → 0
View file @
4cd7a563
-
if
@note
.
valid?
var noteHtml = "
#{
escape_javascript
(
render
@note
)
}
";
-
if
note_for_main_target?
(
@note
)
NoteList.appendNewNote(
#{
@note
.
id
}
, noteHtml);
-
else
:plain
var firstDiscussionNoteHtml = "
#{
escape_javascript
(
render
"projects/notes/diff_notes_with_reply"
,
notes:
[
@note
])
}
";
NoteList.appendNewDiscussionNote("
#{
@note
.
discussion_id
}
",
firstDiscussionNoteHtml,
noteHtml);
-
else
var errorsHtml = "
#{
escape_javascript
(
render
'projects/notes/form_errors'
,
note:
@note
)
}
";
-
if
note_for_main_target?
(
@note
)
NoteList.errorsOnForm(errorsHtml);
-
else
NoteList.errorsOnForm(errorsHtml, "
#{
@note
.
discussion_id
}
");
features/steps/project/project_merge_requests.rb
View file @
7bf0add0
...
@@ -115,19 +115,26 @@ class ProjectMergeRequests < Spinach::FeatureSteps
...
@@ -115,19 +115,26 @@ class ProjectMergeRequests < Spinach::FeatureSteps
And
'I leave a comment on the diff page'
do
And
'I leave a comment on the diff page'
do
init_diff_note
init_diff_note
within
(
'.js-
temp-notes-holder
'
)
do
within
(
'.js-
discussion-note-form
'
)
do
fill_in
"note_note"
,
with:
"One comment to rule them all"
fill_in
"note_note"
,
with:
"One comment to rule them all"
click_button
"Add Comment"
click_button
"Add Comment"
end
end
within
".note-text"
do
page
.
should
have_content
"One comment to rule them all"
end
end
end
And
'I leave a comment like "Line is wrong" on line 185 of the first file'
do
And
'I leave a comment like "Line is wrong" on line 185 of the first file'
do
init_diff_note
init_diff_note
within
(
".js-
temp-notes-holder
"
)
do
within
(
".js-
discussion-note-form
"
)
do
fill_in
"note_note"
,
with:
"Line is wrong"
fill_in
"note_note"
,
with:
"Line is wrong"
click_button
"Add Comment"
click_button
"Add Comment"
sleep
0.05
end
within
".note-text"
do
page
.
should
have_content
"Line is wrong"
end
end
end
end
...
...
spec/features/notes_on_merge_requests_spec.rb
View file @
7bf0add0
...
@@ -108,7 +108,7 @@ describe "On a merge request", js: true do
...
@@ -108,7 +108,7 @@ describe "On a merge request", js: true do
within
(
"#note_
#{
note
.
id
}
"
)
do
within
(
"#note_
#{
note
.
id
}
"
)
do
should
have_css
(
".note-last-update small"
)
should
have_css
(
".note-last-update small"
)
find
(
".note-last-update small"
).
text
.
should
match
(
/Edited
just now
/
)
find
(
".note-last-update small"
).
text
.
should
match
(
/Edited
less than a minute ago
/
)
end
end
end
end
end
end
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment