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
fdc9ae2e
Commit
fdc9ae2e
authored
Mar 16, 2018
by
Fatih Acet
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Prettify notes.
parent
2c792c75
Changes
26
Expand all
Hide whitespace changes
Inline
Side-by-side
Showing
26 changed files
with
2149 additions
and
1792 deletions
+2149
-1792
app/assets/javascripts/mr_notes/index.js
app/assets/javascripts/mr_notes/index.js
+6
-3
app/assets/javascripts/notes.js
app/assets/javascripts/notes.js
+460
-204
app/assets/javascripts/notes/components/comment_form.vue
app/assets/javascripts/notes/components/comment_form.vue
+279
-273
app/assets/javascripts/notes/components/diff_file_header.vue
app/assets/javascripts/notes/components/diff_file_header.vue
+17
-17
app/assets/javascripts/notes/components/diff_with_note.vue
app/assets/javascripts/notes/components/diff_with_note.vue
+50
-46
app/assets/javascripts/notes/components/discussion_counter.vue
...ssets/javascripts/notes/components/discussion_counter.vue
+58
-56
app/assets/javascripts/notes/components/discussion_locked_widget.vue
...javascripts/notes/components/discussion_locked_widget.vue
+8
-10
app/assets/javascripts/notes/components/note_actions.vue
app/assets/javascripts/notes/components/note_actions.vue
+112
-114
app/assets/javascripts/notes/components/note_attachment.vue
app/assets/javascripts/notes/components/note_attachment.vue
+8
-8
app/assets/javascripts/notes/components/note_awards_list.vue
app/assets/javascripts/notes/components/note_awards_list.vue
+181
-168
app/assets/javascripts/notes/components/note_body.vue
app/assets/javascripts/notes/components/note_body.vue
+66
-68
app/assets/javascripts/notes/components/note_edited_text.vue
app/assets/javascripts/notes/components/note_edited_text.vue
+25
-25
app/assets/javascripts/notes/components/note_form.vue
app/assets/javascripts/notes/components/note_form.vue
+116
-108
app/assets/javascripts/notes/components/note_header.vue
app/assets/javascripts/notes/components/note_header.vue
+54
-56
app/assets/javascripts/notes/components/note_signed_out_widget.vue
...s/javascripts/notes/components/note_signed_out_widget.vue
+11
-13
app/assets/javascripts/notes/components/noteable_discussion.vue
...sets/javascripts/notes/components/noteable_discussion.vue
+180
-180
app/assets/javascripts/notes/components/noteable_note.vue
app/assets/javascripts/notes/components/noteable_note.vue
+130
-130
app/assets/javascripts/notes/components/notes_app.vue
app/assets/javascripts/notes/components/notes_app.vue
+142
-140
app/assets/javascripts/notes/index.js
app/assets/javascripts/notes/index.js
+38
-30
app/assets/javascripts/notes/mixins/autosave.js
app/assets/javascripts/notes/mixins/autosave.js
+5
-1
app/assets/javascripts/notes/mixins/resolvable.js
app/assets/javascripts/notes/mixins/resolvable.js
+8
-3
app/assets/javascripts/notes/services/notes_service.js
app/assets/javascripts/notes/services/notes_service.js
+3
-1
app/assets/javascripts/notes/stores/actions.js
app/assets/javascripts/notes/stores/actions.js
+147
-108
app/assets/javascripts/notes/stores/getters.js
app/assets/javascripts/notes/stores/getters.js
+19
-15
app/assets/javascripts/notes/stores/mutations.js
app/assets/javascripts/notes/stores/mutations.js
+18
-10
app/assets/javascripts/notes/stores/utils.js
app/assets/javascripts/notes/stores/utils.js
+8
-5
No files found.
app/assets/javascripts/mr_notes/index.js
View file @
fdc9ae2e
...
...
@@ -4,13 +4,15 @@ import discussionCounter from '../notes/components/discussion_counter.vue';
import
store
from
'
../notes/stores
'
;
export
default
function
initMrNotes
()
{
new
Vue
({
// eslint-disable-line
// eslint-disable-next-line no-new
new
Vue
({
el
:
'
#js-vue-mr-discussions
'
,
components
:
{
notesApp
,
},
data
()
{
const
notesDataset
=
document
.
getElementById
(
'
js-vue-mr-discussions
'
).
dataset
;
const
notesDataset
=
document
.
getElementById
(
'
js-vue-mr-discussions
'
)
.
dataset
;
return
{
noteableData
:
JSON
.
parse
(
notesDataset
.
noteableData
),
currentUserData
:
JSON
.
parse
(
notesDataset
.
currentUserData
),
...
...
@@ -28,7 +30,8 @@ export default function initMrNotes() {
},
});
new
Vue
({
// eslint-disable-line
// eslint-disable-next-line no-new
new
Vue
({
el
:
'
#js-vue-discussion-counter
'
,
components
:
{
discussionCounter
,
...
...
app/assets/javascripts/notes.js
View file @
fdc9ae2e
This diff is collapsed.
Click to expand it.
app/assets/javascripts/notes/components/comment_form.vue
View file @
fdc9ae2e
This diff is collapsed.
Click to expand it.
app/assets/javascripts/notes/components/diff_file_header.vue
View file @
fdc9ae2e
<
script
>
import
ClipboardButton
from
'
~/vue_shared/components/clipboard_button.vue
'
;
import
Icon
from
'
~/vue_shared/components/icon.vue
'
;
import
ClipboardButton
from
'
~/vue_shared/components/clipboard_button.vue
'
;
import
Icon
from
'
~/vue_shared/components/icon.vue
'
;
export
default
{
components
:
{
ClipboardButton
,
Icon
,
export
default
{
components
:
{
ClipboardButton
,
Icon
,
},
props
:
{
diffFile
:
{
type
:
Object
,
required
:
true
,
},
props
:
{
diffFile
:
{
type
:
Object
,
required
:
true
,
},
},
computed
:
{
titleTag
()
{
return
this
.
diffFile
.
discussionPath
?
'
a
'
:
'
span
'
;
},
computed
:
{
titleTag
()
{
return
this
.
diffFile
.
discussionPath
?
'
a
'
:
'
span
'
;
},
},
};
},
};
</
script
>
<
template
>
...
...
app/assets/javascripts/notes/components/diff_with_note.vue
View file @
fdc9ae2e
<
script
>
import
$
from
'
jquery
'
;
import
syntaxHighlight
from
'
~/syntax_highlight
'
;
import
imageDiffHelper
from
'
~/image_diff/helpers/index
'
;
import
{
convertObjectPropsToCamelCase
}
from
'
~/lib/utils/common_utils
'
;
import
DiffFileHeader
from
'
./diff_file_header.vue
'
;
import
$
from
'
jquery
'
;
import
syntaxHighlight
from
'
~/syntax_highlight
'
;
import
imageDiffHelper
from
'
~/image_diff/helpers/index
'
;
import
{
convertObjectPropsToCamelCase
}
from
'
~/lib/utils/common_utils
'
;
import
DiffFileHeader
from
'
./diff_file_header.vue
'
;
export
default
{
components
:
{
DiffFileHeader
,
export
default
{
components
:
{
DiffFileHeader
,
},
props
:
{
discussion
:
{
type
:
Object
,
required
:
true
,
},
props
:
{
discussion
:
{
type
:
Object
,
required
:
true
,
},
},
computed
:
{
isImageDiff
()
{
return
!
this
.
diffFile
.
text
;
},
computed
:
{
isImageDiff
()
{
return
!
this
.
diffFile
.
text
;
},
diffFileClass
()
{
const
{
text
}
=
this
.
diffFile
;
return
text
?
'
text-file
'
:
'
js-image-file
'
;
},
diffRows
()
{
return
$
(
this
.
discussion
.
truncatedDiffLines
);
},
diffFile
()
{
return
convertObjectPropsToCamelCase
(
this
.
discussion
.
diffFile
);
},
imageDiffHtml
()
{
return
this
.
discussion
.
imageDiffHtml
;
},
diffFileClass
()
{
const
{
text
}
=
this
.
diffFile
;
return
text
?
'
text-file
'
:
'
js-image-file
'
;
},
mounted
()
{
if
(
this
.
isImageDiff
)
{
const
canCreateNote
=
false
;
const
renderCommentBadge
=
true
;
imageDiffHelper
.
initImageDiff
(
this
.
$refs
.
fileHolder
,
canCreateNote
,
renderCommentBadge
);
}
else
{
const
fileHolder
=
$
(
this
.
$refs
.
fileHolder
);
this
.
$nextTick
(()
=>
{
syntaxHighlight
(
fileHolder
);
});
}
diffRows
()
{
return
$
(
this
.
discussion
.
truncatedDiffLines
);
},
methods
:
{
rowTag
(
html
)
{
return
html
.
outerHTML
?
'
tr
'
:
'
template
'
;
},
diffFile
()
{
return
convertObjectPropsToCamelCase
(
this
.
discussion
.
diffFile
);
},
};
imageDiffHtml
()
{
return
this
.
discussion
.
imageDiffHtml
;
},
},
mounted
()
{
if
(
this
.
isImageDiff
)
{
const
canCreateNote
=
false
;
const
renderCommentBadge
=
true
;
imageDiffHelper
.
initImageDiff
(
this
.
$refs
.
fileHolder
,
canCreateNote
,
renderCommentBadge
,
);
}
else
{
const
fileHolder
=
$
(
this
.
$refs
.
fileHolder
);
this
.
$nextTick
(()
=>
{
syntaxHighlight
(
fileHolder
);
});
}
},
methods
:
{
rowTag
(
html
)
{
return
html
.
outerHTML
?
'
tr
'
:
'
template
'
;
},
},
};
</
script
>
<
template
>
...
...
app/assets/javascripts/notes/components/discussion_counter.vue
View file @
fdc9ae2e
<
script
>
import
{
mapGetters
}
from
'
vuex
'
;
import
resolveSvg
from
'
icons/_icon_resolve_discussion.svg
'
;
import
resolvedSvg
from
'
icons/_icon_status_success_solid.svg
'
;
import
mrIssueSvg
from
'
icons/_icon_mr_issue.svg
'
;
import
nextDiscussionSvg
from
'
icons/_next_discussion.svg
'
;
import
{
pluralize
}
from
'
../../lib/utils/text_utility
'
;
import
{
scrollToElement
}
from
'
../../lib/utils/common_utils
'
;
import
tooltip
from
'
../../vue_shared/directives/tooltip
'
;
import
{
mapGetters
}
from
'
vuex
'
;
import
resolveSvg
from
'
icons/_icon_resolve_discussion.svg
'
;
import
resolvedSvg
from
'
icons/_icon_status_success_solid.svg
'
;
import
mrIssueSvg
from
'
icons/_icon_mr_issue.svg
'
;
import
nextDiscussionSvg
from
'
icons/_next_discussion.svg
'
;
import
{
pluralize
}
from
'
../../lib/utils/text_utility
'
;
import
{
scrollToElement
}
from
'
../../lib/utils/common_utils
'
;
import
tooltip
from
'
../../vue_shared/directives/tooltip
'
;
export
default
{
directives
:
{
tooltip
,
export
default
{
directives
:
{
tooltip
,
},
computed
:
{
...
mapGetters
([
'
getUserData
'
,
'
getNoteableData
'
,
'
discussionCount
'
,
'
unresolvedDiscussions
'
,
'
resolvedDiscussionCount
'
,
]),
isLoggedIn
()
{
return
this
.
getUserData
.
id
;
},
computed
:
{
...
mapGetters
([
'
getUserData
'
,
'
getNoteableData
'
,
'
discussionCount
'
,
'
unresolvedDiscussions
'
,
'
resolvedDiscussionCount
'
,
]),
isLoggedIn
()
{
return
this
.
getUserData
.
id
;
},
hasNextButton
()
{
return
this
.
isLoggedIn
&&
!
this
.
allResolved
;
},
countText
()
{
return
pluralize
(
'
discussion
'
,
this
.
discussionCount
);
},
allResolved
()
{
return
this
.
resolvedDiscussionCount
===
this
.
discussionCount
;
},
resolveAllDiscussionsIssuePath
()
{
return
this
.
getNoteableData
.
create_issue_to_resolve_discussions_path
;
},
firstUnresolvedDiscussionId
()
{
const
item
=
this
.
unresolvedDiscussions
[
0
]
||
{};
return
item
.
id
;
},
hasNextButton
()
{
return
this
.
isLoggedIn
&&
!
this
.
allResolved
;
},
countText
()
{
return
pluralize
(
'
discussion
'
,
this
.
discussionCount
);
},
allResolved
()
{
return
this
.
resolvedDiscussionCount
===
this
.
discussionCount
;
},
created
()
{
this
.
resolveSvg
=
resolveSvg
;
this
.
resolvedSvg
=
resolvedSvg
;
this
.
mrIssueSvg
=
mrIssueSvg
;
this
.
nextDiscussionSvg
=
nextDiscussionSvg
;
resolveAllDiscussionsIssuePath
()
{
return
this
.
getNoteableData
.
create_issue_to_resolve_discussions_path
;
},
firstUnresolvedDiscussionId
()
{
const
item
=
this
.
unresolvedDiscussions
[
0
]
||
{};
return
item
.
id
;
},
methods
:
{
jumpToFirstDiscussion
()
{
const
el
=
document
.
querySelector
(
`[data-discussion-id="
${
this
.
firstUnresolvedDiscussionId
}
"]`
);
const
activeTab
=
window
.
mrTabs
.
currentAction
;
},
created
()
{
this
.
resolveSvg
=
resolveSvg
;
this
.
resolvedSvg
=
resolvedSvg
;
this
.
mrIssueSvg
=
mrIssueSvg
;
this
.
nextDiscussionSvg
=
nextDiscussionSvg
;
},
methods
:
{
jumpToFirstDiscussion
()
{
const
el
=
document
.
querySelector
(
`[data-discussion-id="
${
this
.
firstUnresolvedDiscussionId
}
"]`
,
);
const
activeTab
=
window
.
mrTabs
.
currentAction
;
if
(
activeTab
===
'
commits
'
||
activeTab
===
'
pipelines
'
)
{
window
.
mrTabs
.
activateTab
(
'
show
'
);
}
if
(
activeTab
===
'
commits
'
||
activeTab
===
'
pipelines
'
)
{
window
.
mrTabs
.
activateTab
(
'
show
'
);
}
if
(
el
)
{
scrollToElement
(
el
);
}
},
if
(
el
)
{
scrollToElement
(
el
);
}
},
};
},
};
</
script
>
<
template
>
...
...
app/assets/javascripts/notes/components/discussion_locked_widget.vue
View file @
fdc9ae2e
<
script
>
import
Icon
from
'
~/vue_shared/components/icon.vue
'
;
import
Issuable
from
'
~/vue_shared/mixins/issuable
'
;
import
Icon
from
'
~/vue_shared/components/icon.vue
'
;
import
Issuable
from
'
~/vue_shared/mixins/issuable
'
;
export
default
{
components
:
{
Icon
,
},
mixins
:
[
Issuable
,
],
};
export
default
{
components
:
{
Icon
,
},
mixins
:
[
Issuable
],
};
</
script
>
<
template
>
...
...
app/assets/javascripts/notes/components/note_actions.vue
View file @
fdc9ae2e
<
script
>
import
{
mapGetters
}
from
'
vuex
'
;
import
emojiSmiling
from
'
icons/_emoji_slightly_smiling_face.svg
'
;
import
emojiSmile
from
'
icons/_emoji_smile.svg
'
;
import
emojiSmiley
from
'
icons/_emoji_smiley.svg
'
;
import
editSvg
from
'
icons/_icon_pencil.svg
'
;
import
resolveDiscussionSvg
from
'
icons/_icon_resolve_discussion.svg
'
;
import
resolvedDiscussionSvg
from
'
icons/_icon_status_success_solid.svg
'
;
import
ellipsisSvg
from
'
icons/_ellipsis_v.svg
'
;
import
loadingIcon
from
'
~/vue_shared/components/loading_icon.vue
'
;
import
tooltip
from
'
~/vue_shared/directives/tooltip
'
;
import
{
mapGetters
}
from
'
vuex
'
;
import
emojiSmiling
from
'
icons/_emoji_slightly_smiling_face.svg
'
;
import
emojiSmile
from
'
icons/_emoji_smile.svg
'
;
import
emojiSmiley
from
'
icons/_emoji_smiley.svg
'
;
import
editSvg
from
'
icons/_icon_pencil.svg
'
;
import
resolveDiscussionSvg
from
'
icons/_icon_resolve_discussion.svg
'
;
import
resolvedDiscussionSvg
from
'
icons/_icon_status_success_solid.svg
'
;
import
ellipsisSvg
from
'
icons/_ellipsis_v.svg
'
;
import
loadingIcon
from
'
~/vue_shared/components/loading_icon.vue
'
;
import
tooltip
from
'
~/vue_shared/directives/tooltip
'
;
export
default
{
name
:
'
NoteActions
'
,
directives
:
{
tooltip
,
},
components
:
{
loadingIcon
,
},
props
:
{
authorId
:
{
type
:
Number
,
required
:
true
,
},
noteId
:
{
type
:
Number
,
required
:
true
,
},
accessLevel
:
{
type
:
String
,
required
:
false
,
default
:
''
,
},
reportAbusePath
:
{
type
:
String
,
required
:
true
,
},
canEdit
:
{
type
:
Boolean
,
required
:
true
,
},
canDelete
:
{
type
:
Boolean
,
required
:
true
,
},
resolvable
:
{
type
:
Boolean
,
required
:
false
,
default
:
false
,
},
isResolved
:
{
type
:
Boolean
,
required
:
false
,
default
:
false
,
},
isResolving
:
{
type
:
Boolean
,
required
:
false
,
default
:
false
,
},
resolvedBy
:
{
type
:
Object
,
required
:
false
,
default
:
()
=>
({}),
},
canReportAsAbuse
:
{
type
:
Boolean
,
required
:
true
,
},
},
computed
:
{
...
mapGetters
([
'
getUserDataByProp
'
,
]),
shouldShowActionsDropdown
()
{
return
this
.
currentUserId
&&
(
this
.
canEdit
||
this
.
canReportAsAbuse
);
},
canAddAwardEmoji
()
{
return
this
.
currentUserId
;
},
isAuthoredByCurrentUser
()
{
return
this
.
authorId
===
this
.
currentUserId
;
},
currentUserId
()
{
return
this
.
getUserDataByProp
(
'
id
'
);
},
resolveButtonTitle
()
{
let
title
=
'
Mark as resolved
'
;
export
default
{
name
:
'
NoteActions
'
,
directives
:
{
tooltip
,
},
components
:
{
loadingIcon
,
},
props
:
{
authorId
:
{
type
:
Number
,
required
:
true
,
},
noteId
:
{
type
:
Number
,
required
:
true
,
},
accessLevel
:
{
type
:
String
,
required
:
false
,
default
:
''
,
},
reportAbusePath
:
{
type
:
String
,
required
:
true
,
},
canEdit
:
{
type
:
Boolean
,
required
:
true
,
},
canDelete
:
{
type
:
Boolean
,
required
:
true
,
},
resolvable
:
{
type
:
Boolean
,
required
:
false
,
default
:
false
,
},
isResolved
:
{
type
:
Boolean
,
required
:
false
,
default
:
false
,
},
isResolving
:
{
type
:
Boolean
,
required
:
false
,
default
:
false
,
},
resolvedBy
:
{
type
:
Object
,
required
:
false
,
default
:
()
=>
({}),
},
canReportAsAbuse
:
{
type
:
Boolean
,
required
:
true
,
},
},
computed
:
{
...
mapGetters
([
'
getUserDataByProp
'
]),
shouldShowActionsDropdown
()
{
return
this
.
currentUserId
&&
(
this
.
canEdit
||
this
.
canReportAsAbuse
);
},
canAddAwardEmoji
()
{
return
this
.
currentUserId
;
},
isAuthoredByCurrentUser
()
{
return
this
.
authorId
===
this
.
currentUserId
;
},
currentUserId
()
{
return
this
.
getUserDataByProp
(
'
id
'
);
},
resolveButtonTitle
()
{
let
title
=
'
Mark as resolved
'
;
if
(
this
.
resolvedBy
)
{
title
=
`Resolved by
${
this
.
resolvedBy
.
name
}
`
;
}
if
(
this
.
resolvedBy
)
{
title
=
`Resolved by
${
this
.
resolvedBy
.
name
}
`
;
}
return
title
;
},
},
created
()
{
this
.
emojiSmiling
=
emojiSmiling
;
this
.
emojiSmile
=
emojiSmile
;
this
.
emojiSmiley
=
emojiSmiley
;
this
.
editSvg
=
editSvg
;
this
.
ellipsisSvg
=
ellipsisSvg
;
this
.
resolveDiscussionSvg
=
resolveDiscussionSvg
;
this
.
resolvedDiscussionSvg
=
resolvedDiscussionSvg
;
},
methods
:
{
onEdit
()
{
this
.
$emit
(
'
handleEdit
'
);
},
onDelete
()
{
this
.
$emit
(
'
handleDelete
'
);
},
onResolve
()
{
this
.
$emit
(
'
handleResolve
'
);
},
},
};
return
title
;
},
},
created
()
{
this
.
emojiSmiling
=
emojiSmiling
;
this
.
emojiSmile
=
emojiSmile
;
this
.
emojiSmiley
=
emojiSmiley
;
this
.
editSvg
=
editSvg
;
this
.
ellipsisSvg
=
ellipsisSvg
;
this
.
resolveDiscussionSvg
=
resolveDiscussionSvg
;
this
.
resolvedDiscussionSvg
=
resolvedDiscussionSvg
;
},
methods
:
{
onEdit
()
{
this
.
$emit
(
'
handleEdit
'
);
},
onDelete
()
{
this
.
$emit
(
'
handleDelete
'
);
},
onResolve
()
{
this
.
$emit
(
'
handleResolve
'
);
},
},
};
</
script
>
<
template
>
...
...
app/assets/javascripts/notes/components/note_attachment.vue
View file @
fdc9ae2e
<
script
>
export
default
{
name
:
'
NoteAttachment
'
,
props
:
{
attachment
:
{
type
:
Object
,
required
:
true
,
},
export
default
{
name
:
'
NoteAttachment
'
,
props
:
{
attachment
:
{
type
:
Object
,
required
:
true
,
},
};
},
};
</
script
>
<
template
>
...
...
app/assets/javascripts/notes/components/note_awards_list.vue
View file @
fdc9ae2e
This diff is collapsed.
Click to expand it.
app/assets/javascripts/notes/components/note_body.vue
View file @
fdc9ae2e
<
script
>
import
$
from
'
jquery
'
;
import
noteEditedText
from
'
./note_edited_text.vue
'
;
import
noteAwardsList
from
'
./note_awards_list.vue
'
;
import
noteAttachment
from
'
./note_attachment.vue
'
;
import
noteForm
from
'
./note_form.vue
'
;
import
TaskList
from
'
../../task_list
'
;
import
autosave
from
'
../mixins/autosave
'
;
import
$
from
'
jquery
'
;
import
noteEditedText
from
'
./note_edited_text.vue
'
;
import
noteAwardsList
from
'
./note_awards_list.vue
'
;
import
noteAttachment
from
'
./note_attachment.vue
'
;
import
noteForm
from
'
./note_form.vue
'
;
import
TaskList
from
'
../../task_list
'
;
import
autosave
from
'
../mixins/autosave
'
;
export
default
{
components
:
{
noteEditedText
,
noteAwardsList
,
noteAttachment
,
noteForm
,
export
default
{
components
:
{
noteEditedText
,
noteAwardsList
,
noteAttachment
,
noteForm
,
},
mixins
:
[
autosave
],
props
:
{
note
:
{
type
:
Object
,
required
:
true
,
},
mixins
:
[
autosave
,
],
props
:
{
note
:
{
type
:
Object
,
required
:
true
,
},
canEdit
:
{
type
:
Boolean
,
required
:
true
,
},
isEditing
:
{
type
:
Boolean
,
required
:
false
,
default
:
false
,
},
canEdit
:
{
type
:
Boolean
,
required
:
true
,
},
computed
:
{
noteBody
()
{
return
this
.
note
.
note
;
}
,
isEditing
:
{
type
:
Boolean
,
required
:
false
,
default
:
false
,
},
mounted
()
{
this
.
renderGFM
();
this
.
initTaskList
();
},
computed
:
{
noteBody
()
{
return
this
.
note
.
note
;
},
},
mounted
()
{
this
.
renderGFM
();
this
.
initTaskList
();
if
(
this
.
isEditing
)
{
this
.
initAutoSave
(
this
.
note
.
noteable_type
);
}
},
updated
()
{
this
.
initTaskList
();
this
.
renderGFM
();
if
(
this
.
isEditing
)
{
if
(
this
.
isEditing
)
{
if
(
!
this
.
autosave
)
{
this
.
initAutoSave
(
this
.
note
.
noteable_type
);
}
else
{
this
.
setAutoSave
();
}
}
},
methods
:
{
renderGFM
()
{
$
(
this
.
$refs
[
'
note-body
'
]).
renderGFM
();
},
updated
()
{
this
.
initTaskList
();
this
.
renderGFM
();
if
(
this
.
isEditing
)
{
if
(
!
this
.
autosave
)
{
this
.
initAutoSave
(
this
.
note
.
noteable_type
);
}
else
{
this
.
setAutoSave
();
}
initTaskList
()
{
if
(
this
.
canEdit
)
{
this
.
taskList
=
new
TaskList
({
dataType
:
'
note
'
,
fieldName
:
'
note
'
,
selector
:
'
.notes
'
,
});
}
},
methods
:
{
renderGFM
()
{
$
(
this
.
$refs
[
'
note-body
'
]).
renderGFM
();
},
initTaskList
()
{
if
(
this
.
canEdit
)
{
this
.
taskList
=
new
TaskList
({
dataType
:
'
note
'
,
fieldName
:
'
note
'
,
selector
:
'
.notes
'
,
});
}
},
handleFormUpdate
(
note
,
parentElement
,
callback
)
{
this
.
$emit
(
'
handleFormUpdate
'
,
note
,
parentElement
,
callback
);
},
formCancelHandler
(
shouldConfirm
,
isDirty
)
{
this
.
$emit
(
'
cancelFormEdition
'
,
shouldConfirm
,
isDirty
);
},
handleFormUpdate
(
note
,
parentElement
,
callback
)
{
this
.
$emit
(
'
handleFormUpdate
'
,
note
,
parentElement
,
callback
);
},
formCancelHandler
(
shouldConfirm
,
isDirty
)
{
this
.
$emit
(
'
cancelFormEdition
'
,
shouldConfirm
,
isDirty
);
},
};
},
};
</
script
>
<
template
>
...
...
app/assets/javascripts/notes/components/note_edited_text.vue
View file @
fdc9ae2e
<
script
>
import
timeAgoTooltip
from
'
../../vue_shared/components/time_ago_tooltip.vue
'
;
import
timeAgoTooltip
from
'
../../vue_shared/components/time_ago_tooltip.vue
'
;
export
default
{
name
:
'
EditedNoteText
'
,
components
:
{
timeAgoTooltip
,
export
default
{
name
:
'
EditedNoteText
'
,
components
:
{
timeAgoTooltip
,
},
props
:
{
actionText
:
{
type
:
String
,
required
:
true
,
},
props
:
{
actionText
:
{
type
:
String
,
required
:
true
,
},
editedAt
:
{
type
:
String
,
required
:
true
,
},
editedBy
:
{
type
:
Object
,
required
:
false
,
default
:
()
=>
({}),
},
className
:
{
type
:
String
,
required
:
false
,
default
:
'
edited-text
'
,
},
editedAt
:
{
type
:
String
,
required
:
true
,
},
};
editedBy
:
{
type
:
Object
,
required
:
false
,
default
:
()
=>
({}),
},
className
:
{
type
:
String
,
required
:
false
,
default
:
'
edited-text
'
,
},
},
};
</
script
>
<
template
>
...
...
app/assets/javascripts/notes/components/note_form.vue
View file @
fdc9ae2e
<
script
>
import
{
mapGetters
,
mapActions
}
from
'
vuex
'
;
import
eventHub
from
'
../event_hub
'
;
import
issueWarning
from
'
../../vue_shared/components/issue/issue_warning.vue
'
;
import
markdownField
from
'
../../vue_shared/components/markdown/field.vue
'
;
import
issuableStateMixin
from
'
../mixins/issuable_state
'
;
import
resolvable
from
'
../mixins/resolvable
'
;
import
{
mapGetters
,
mapActions
}
from
'
vuex
'
;
import
eventHub
from
'
../event_hub
'
;
import
issueWarning
from
'
../../vue_shared/components/issue/issue_warning.vue
'
;
import
markdownField
from
'
../../vue_shared/components/markdown/field.vue
'
;
import
issuableStateMixin
from
'
../mixins/issuable_state
'
;
import
resolvable
from
'
../mixins/resolvable
'
;
export
default
{
name
:
'
IssueNoteForm
'
,
components
:
{
issueWarning
,
markdownField
,
export
default
{
name
:
'
IssueNoteForm
'
,
components
:
{
issueWarning
,
markdownField
,
},
mixins
:
[
issuableStateMixin
,
resolvable
],
props
:
{
noteBody
:
{
type
:
String
,
required
:
false
,
default
:
''
,
},
mixins
:
[
issuableStateMixin
,
resolvable
,
],
props
:
{
noteBody
:
{
type
:
String
,
required
:
false
,
default
:
''
,
},
noteId
:
{
type
:
Number
,
required
:
false
,
default
:
0
,
},
saveButtonTitle
:
{
type
:
String
,
required
:
false
,
default
:
'
Save comment
'
,
},
note
:
{
type
:
Object
,
required
:
false
,
default
:
()
=>
({}),
},
isEditing
:
{
type
:
Boolean
,
required
:
true
,
},
noteId
:
{
type
:
Number
,
required
:
false
,
default
:
0
,
},
data
()
{
return
{
updatedNoteBody
:
this
.
noteBody
,
conflictWhileEditing
:
false
,
isSubmitting
:
false
,
isResolving
:
false
,
resolveAsThread
:
true
,
};
saveButtonTitle
:
{
type
:
String
,
required
:
false
,
default
:
'
Save comment
'
,
},
computed
:
{
...
mapGetters
([
'
getDiscussionLastNote
'
,
'
getNoteableData
'
,
'
getNoteableDataByProp
'
,
'
getNotesDataByProp
'
,
'
getUserDataByProp
'
,
]),
noteHash
()
{
return
`#note_
${
this
.
noteId
}
`
;
},
markdownPreviewPath
()
{
return
this
.
getNoteableDataByProp
(
'
preview_note_path
'
);
},
markdownDocsPath
()
{
return
this
.
getNotesDataByProp
(
'
markdownDocsPath
'
);
},
quickActionsDocsPath
()
{
return
!
this
.
isEditing
?
this
.
getNotesDataByProp
(
'
quickActionsDocsPath
'
)
:
undefined
;
},
currentUserId
()
{
return
this
.
getUserDataByProp
(
'
id
'
);
},
isDisabled
()
{
return
!
this
.
updatedNoteBody
.
length
||
this
.
isSubmitting
;
},
note
:
{
type
:
Object
,
required
:
false
,
default
:
()
=>
({}),
},
watch
:
{
noteBody
()
{
if
(
this
.
updatedNoteBody
===
this
.
noteBody
)
{
this
.
updatedNoteBody
=
this
.
noteBody
;
}
else
{
this
.
conflictWhileEditing
=
true
;
}
},
isEditing
:
{
type
:
Boolean
,
required
:
true
,
},
},
data
()
{
return
{
updatedNoteBody
:
this
.
noteBody
,
conflictWhileEditing
:
false
,
isSubmitting
:
false
,
isResolving
:
false
,
resolveAsThread
:
true
,
};
},
computed
:
{
...
mapGetters
([
'
getDiscussionLastNote
'
,
'
getNoteableData
'
,
'
getNoteableDataByProp
'
,
'
getNotesDataByProp
'
,
'
getUserDataByProp
'
,
]),
noteHash
()
{
return
`#note_
${
this
.
noteId
}
`
;
},
markdownPreviewPath
()
{
return
this
.
getNoteableDataByProp
(
'
preview_note_path
'
);
},
markdownDocsPath
()
{
return
this
.
getNotesDataByProp
(
'
markdownDocsPath
'
);
},
quickActionsDocsPath
()
{
return
!
this
.
isEditing
?
this
.
getNotesDataByProp
(
'
quickActionsDocsPath
'
)
:
undefined
;
},
mounte
d
()
{
this
.
$refs
.
textarea
.
focus
(
);
currentUserI
d
()
{
return
this
.
getUserDataByProp
(
'
id
'
);
},
methods
:
{
...
mapActions
([
'
toggleResolveNote
'
,
]),
handleUpdate
(
shouldResolve
)
{
const
beforeSubmitDiscussionState
=
this
.
discussionResolved
;
this
.
isSubmitting
=
true
;
isDisabled
()
{
return
!
this
.
updatedNoteBody
.
length
||
this
.
isSubmitting
;
},
},
watch
:
{
noteBody
()
{
if
(
this
.
updatedNoteBody
===
this
.
noteBody
)
{
this
.
updatedNoteBody
=
this
.
noteBody
;
}
else
{
this
.
conflictWhileEditing
=
true
;
}
},
},
mounted
()
{
this
.
$refs
.
textarea
.
focus
();
},
methods
:
{
...
mapActions
([
'
toggleResolveNote
'
]),
handleUpdate
(
shouldResolve
)
{
const
beforeSubmitDiscussionState
=
this
.
discussionResolved
;
this
.
isSubmitting
=
true
;
this
.
$emit
(
'
handleFormUpdate
'
,
this
.
updatedNoteBody
,
this
.
$refs
.
editNoteForm
,
()
=>
{
this
.
$emit
(
'
handleFormUpdate
'
,
this
.
updatedNoteBody
,
this
.
$refs
.
editNoteForm
,
()
=>
{
this
.
isSubmitting
=
false
;
if
(
shouldResolve
)
{
this
.
resolveHandler
(
beforeSubmitDiscussionState
);
}
});
},
editMyLastNote
()
{
if
(
this
.
updatedNoteBody
===
''
)
{
const
lastNoteInDiscussion
=
this
.
getDiscussionLastNote
(
this
.
updatedNoteBody
);
},
);
},
editMyLastNote
()
{
if
(
this
.
updatedNoteBody
===
''
)
{
const
lastNoteInDiscussion
=
this
.
getDiscussionLastNote
(
this
.
updatedNoteBody
,
);
if
(
lastNoteInDiscussion
)
{
eventHub
.
$emit
(
'
enterEditMode
'
,
{
noteId
:
lastNoteInDiscussion
.
id
,
});
}
if
(
lastNoteInDiscussion
)
{
eventHub
.
$emit
(
'
enterEditMode
'
,
{
noteId
:
lastNoteInDiscussion
.
id
,
});
}
},
cancelHandler
(
shouldConfirm
=
false
)
{
// Sends information about confirm message and if the textarea has changed
this
.
$emit
(
'
cancelFormEdition
'
,
shouldConfirm
,
this
.
noteBody
!==
this
.
updatedNoteBody
);
},
}
},
cancelHandler
(
shouldConfirm
=
false
)
{
// Sends information about confirm message and if the textarea has changed
this
.
$emit
(
'
cancelFormEdition
'
,
shouldConfirm
,
this
.
noteBody
!==
this
.
updatedNoteBody
,
);
},
};
},
};
</
script
>
<
template
>
...
...
app/assets/javascripts/notes/components/note_header.vue
View file @
fdc9ae2e
<
script
>
import
{
mapActions
}
from
'
vuex
'
;
import
timeAgoTooltip
from
'
../../vue_shared/components/time_ago_tooltip.vue
'
;
import
{
mapActions
}
from
'
vuex
'
;
import
timeAgoTooltip
from
'
../../vue_shared/components/time_ago_tooltip.vue
'
;
export
default
{
components
:
{
timeAgoTooltip
,
export
default
{
components
:
{
timeAgoTooltip
,
},
props
:
{
author
:
{
type
:
Object
,
required
:
true
,
},
props
:
{
author
:
{
type
:
Object
,
required
:
true
,
},
createdAt
:
{
type
:
String
,
required
:
true
,
},
actionText
:
{
type
:
String
,
required
:
false
,
default
:
''
,
},
actionTextHtml
:
{
type
:
String
,
required
:
false
,
default
:
''
,
},
noteId
:
{
type
:
Number
,
required
:
true
,
},
includeToggle
:
{
type
:
Boolean
,
required
:
false
,
default
:
false
,
},
expanded
:
{
type
:
Boolean
,
required
:
false
,
default
:
true
,
},
createdAt
:
{
type
:
String
,
required
:
true
,
},
computed
:
{
toggleChevronClass
()
{
return
this
.
expanded
?
'
fa-chevron-up
'
:
'
fa-chevron-down
'
;
},
noteTimestampLink
()
{
return
`#note_
${
this
.
noteId
}
`
;
},
actionText
:
{
type
:
String
,
required
:
false
,
default
:
''
,
},
methods
:
{
...
mapActions
([
'
setTargetNoteHash
'
,
]),
handleToggle
()
{
this
.
$emit
(
'
toggleHandler
'
);
},
updateTargetNoteHash
()
{
this
.
setTargetNoteHash
(
this
.
noteTimestampLink
);
},
actionTextHtml
:
{
type
:
String
,
required
:
false
,
default
:
''
,
},
};
noteId
:
{
type
:
Number
,
required
:
true
,
},
includeToggle
:
{
type
:
Boolean
,
required
:
false
,
default
:
false
,
},
expanded
:
{
type
:
Boolean
,
required
:
false
,
default
:
true
,
},
},
computed
:
{
toggleChevronClass
()
{
return
this
.
expanded
?
'
fa-chevron-up
'
:
'
fa-chevron-down
'
;
},
noteTimestampLink
()
{
return
`#note_
${
this
.
noteId
}
`
;
},
},
methods
:
{
...
mapActions
([
'
setTargetNoteHash
'
]),
handleToggle
()
{
this
.
$emit
(
'
toggleHandler
'
);
},
updateTargetNoteHash
()
{
this
.
setTargetNoteHash
(
this
.
noteTimestampLink
);
},
},
};
</
script
>
<
template
>
...
...
app/assets/javascripts/notes/components/note_signed_out_widget.vue
View file @
fdc9ae2e
<
script
>
import
{
mapGetters
}
from
'
vuex
'
;
import
{
mapGetters
}
from
'
vuex
'
;
export
default
{
computed
:
{
...
mapGetters
([
'
getNotesDataByProp
'
,
]),
registerLink
()
{
return
this
.
getNotesDataByProp
(
'
registerPath
'
);
},
signInLink
()
{
return
this
.
getNotesDataByProp
(
'
newSessionPath
'
);
},
export
default
{
computed
:
{
...
mapGetters
([
'
getNotesDataByProp
'
]),
registerLink
()
{
return
this
.
getNotesDataByProp
(
'
registerPath
'
);
},
};
signInLink
()
{
return
this
.
getNotesDataByProp
(
'
newSessionPath
'
);
},
},
};
</
script
>
<
template
>
...
...
app/assets/javascripts/notes/components/noteable_discussion.vue
View file @
fdc9ae2e
This diff is collapsed.
Click to expand it.
app/assets/javascripts/notes/components/noteable_note.vue
View file @
fdc9ae2e
<
script
>
import
$
from
'
jquery
'
;
import
{
mapGetters
,
mapActions
}
from
'
vuex
'
;
import
{
escape
}
from
'
underscore
'
;
import
Flash
from
'
../../flash
'
;
import
userAvatarLink
from
'
../../vue_shared/components/user_avatar/user_avatar_link.vue
'
;
import
noteHeader
from
'
./note_header.vue
'
;
import
noteActions
from
'
./note_actions.vue
'
;
import
noteBody
from
'
./note_body.vue
'
;
import
eventHub
from
'
../event_hub
'
;
import
noteable
from
'
../mixins/noteable
'
;
import
resolvable
from
'
../mixins/resolvable
'
;
import
$
from
'
jquery
'
;
import
{
mapGetters
,
mapActions
}
from
'
vuex
'
;
import
{
escape
}
from
'
underscore
'
;
import
Flash
from
'
../../flash
'
;
import
userAvatarLink
from
'
../../vue_shared/components/user_avatar/user_avatar_link.vue
'
;
import
noteHeader
from
'
./note_header.vue
'
;
import
noteActions
from
'
./note_actions.vue
'
;
import
noteBody
from
'
./note_body.vue
'
;
import
eventHub
from
'
../event_hub
'
;
import
noteable
from
'
../mixins/noteable
'
;
import
resolvable
from
'
../mixins/resolvable
'
;
export
default
{
components
:
{
userAvatarLink
,
noteHeader
,
noteActions
,
noteBody
,
export
default
{
components
:
{
userAvatarLink
,
noteHeader
,
noteActions
,
noteBody
,
},
mixins
:
[
noteable
,
resolvable
],
props
:
{
note
:
{
type
:
Object
,
required
:
true
,
},
mixins
:
[
noteable
,
resolvable
,
],
props
:
{
note
:
{
type
:
Object
,
required
:
true
,
},
},
data
()
{
return
{
isEditing
:
false
,
isDeleting
:
false
,
isRequesting
:
false
,
isResolving
:
false
,
};
},
computed
:
{
...
mapGetters
([
'
targetNoteHash
'
,
'
getUserData
'
]),
author
()
{
return
this
.
note
.
author
;
},
data
()
{
classNameBindings
()
{
return
{
isEditing
:
false
,
isDeleting
:
false
,
isRequesting
:
false
,
isResolving
:
false
,
'
is-editing
'
:
this
.
isEditing
&&
!
this
.
isRequesting
,
'
is-requesting being-posted
'
:
this
.
isRequesting
,
'
disabled-content
'
:
this
.
isDeleting
,
target
:
this
.
targetNoteHash
===
this
.
noteAnchorId
,
};
},
computed
:
{
...
mapGetters
([
'
targetNoteHash
'
,
'
getUserData
'
,
]),
author
()
{
return
this
.
note
.
author
;
},
classNameBindings
()
{
return
{
'
is-editing
'
:
this
.
isEditing
&&
!
this
.
isRequesting
,
'
is-requesting being-posted
'
:
this
.
isRequesting
,
'
disabled-content
'
:
this
.
isDeleting
,
target
:
this
.
targetNoteHash
===
this
.
noteAnchorId
,
};
},
canReportAsAbuse
()
{
return
this
.
note
.
report_abuse_path
&&
this
.
author
.
id
!==
this
.
getUserData
.
id
;
},
noteAnchorId
()
{
return
`note_
${
this
.
note
.
id
}
`
;
},
canReportAsAbuse
()
{
return
(
this
.
note
.
report_abuse_path
&&
this
.
author
.
id
!==
this
.
getUserData
.
id
);
},
created
()
{
eventHub
.
$on
(
'
enterEditMode
'
,
({
noteId
})
=>
{
if
(
noteId
===
this
.
note
.
id
)
{
this
.
isEditing
=
true
;
this
.
scrollToNoteIfNeeded
(
$
(
this
.
$el
));
}
});
noteAnchorId
()
{
return
`note_
${
this
.
note
.
id
}
`
;
},
},
methods
:
{
...
mapActions
([
'
deleteNote
'
,
'
updateNote
'
,
'
toggleResolveNote
'
,
'
scrollToNoteIfNeeded
'
,
]),
editHandler
()
{
created
()
{
eventHub
.
$on
(
'
enterEditMode
'
,
({
noteId
})
=>
{
if
(
noteId
===
this
.
note
.
id
)
{
this
.
isEditing
=
true
;
},
deleteHandler
()
{
// eslint-disable-next-line no-alert
if
(
confirm
(
'
Are you sure you want to delete this comment?
'
))
{
this
.
isDeleting
=
true
;
this
.
scrollToNoteIfNeeded
(
$
(
this
.
$el
));
}
});
},
this
.
deleteNote
(
this
.
note
)
.
then
(()
=>
{
this
.
isDeleting
=
false
;
})
.
catch
(()
=>
{
Flash
(
'
Something went wrong while deleting your note. Please try again.
'
);
this
.
isDeleting
=
false
;
});
}
},
formUpdateHandler
(
noteText
,
parentElement
,
callback
)
{
const
data
=
{
endpoint
:
this
.
note
.
path
,
note
:
{
target_type
:
this
.
noteableType
,
target_id
:
this
.
note
.
noteable_id
,
note
:
{
note
:
noteText
},
},
};
this
.
isRequesting
=
true
;
this
.
oldContent
=
this
.
note
.
note_html
;
this
.
note
.
note_html
=
escape
(
noteText
);
methods
:
{
...
mapActions
([
'
deleteNote
'
,
'
updateNote
'
,
'
toggleResolveNote
'
,
'
scrollToNoteIfNeeded
'
,
]),
editHandler
()
{
this
.
isEditing
=
true
;
},
deleteHandler
()
{
// eslint-disable-next-line no-alert
if
(
confirm
(
'
Are you sure you want to delete this comment?
'
))
{
this
.
isDeleting
=
true
;
this
.
updateNote
(
data
)
this
.
deleteNote
(
this
.
note
)
.
then
(()
=>
{
this
.
isEditing
=
false
;
this
.
isRequesting
=
false
;
this
.
oldContent
=
null
;
$
(
this
.
$refs
.
noteBody
.
$el
).
renderGFM
();
this
.
$refs
.
noteBody
.
resetAutoSave
();
callback
();
this
.
isDeleting
=
false
;
})
.
catch
(()
=>
{
this
.
isRequesting
=
false
;
this
.
isEditing
=
true
;
this
.
$nextTick
(()
=>
{
const
msg
=
'
Something went wrong while editing your comment. Please try again.
'
;
Flash
(
msg
,
'
alert
'
,
this
.
$el
);
this
.
recoverNoteContent
(
noteText
);
callback
();
});
Flash
(
'
Something went wrong while deleting your note. Please try again.
'
,
);
this
.
isDeleting
=
false
;
});
},
formCancelHandler
(
shouldConfirm
,
isDirty
)
{
if
(
shouldConfirm
&&
isDirty
)
{
// eslint-disable-next-line no-alert
if
(
!
confirm
(
'
Are you sure you want to cancel editing this comment?
'
))
return
;
}
this
.
$refs
.
noteBody
.
resetAutoSave
();
if
(
this
.
oldContent
)
{
this
.
note
.
note_html
=
this
.
oldContent
;
}
},
formUpdateHandler
(
noteText
,
parentElement
,
callback
)
{
const
data
=
{
endpoint
:
this
.
note
.
path
,
note
:
{
target_type
:
this
.
noteableType
,
target_id
:
this
.
note
.
noteable_id
,
note
:
{
note
:
noteText
},
},
};
this
.
isRequesting
=
true
;
this
.
oldContent
=
this
.
note
.
note_html
;
this
.
note
.
note_html
=
escape
(
noteText
);
this
.
updateNote
(
data
)
.
then
(()
=>
{
this
.
isEditing
=
false
;
this
.
isRequesting
=
false
;
this
.
oldContent
=
null
;
}
this
.
isEditing
=
false
;
},
recoverNoteContent
(
noteText
)
{
// we need to do this to prevent noteForm inconsistent content warning
// this is something we intentionally do so we need to recover the content
this
.
note
.
note
=
noteText
;
this
.
$refs
.
noteBody
.
$refs
.
noteForm
.
note
.
note
=
noteText
;
},
$
(
this
.
$refs
.
noteBody
.
$el
).
renderGFM
();
this
.
$refs
.
noteBody
.
resetAutoSave
();
callback
();
})
.
catch
(()
=>
{
this
.
isRequesting
=
false
;
this
.
isEditing
=
true
;
this
.
$nextTick
(()
=>
{
const
msg
=
'
Something went wrong while editing your comment. Please try again.
'
;
Flash
(
msg
,
'
alert
'
,
this
.
$el
);
this
.
recoverNoteContent
(
noteText
);
callback
();
});
});
},
formCancelHandler
(
shouldConfirm
,
isDirty
)
{
if
(
shouldConfirm
&&
isDirty
)
{
// eslint-disable-next-line no-alert
if
(
!
confirm
(
'
Are you sure you want to cancel editing this comment?
'
))
return
;
}
this
.
$refs
.
noteBody
.
resetAutoSave
();
if
(
this
.
oldContent
)
{
this
.
note
.
note_html
=
this
.
oldContent
;
this
.
oldContent
=
null
;
}
this
.
isEditing
=
false
;
},
recoverNoteContent
(
noteText
)
{
// we need to do this to prevent noteForm inconsistent content warning
// this is something we intentionally do so we need to recover the content
this
.
note
.
note
=
noteText
;
this
.
$refs
.
noteBody
.
$refs
.
noteForm
.
note
.
note
=
noteText
;
},
};
},
};
</
script
>
<
template
>
...
...
app/assets/javascripts/notes/components/notes_app.vue
View file @
fdc9ae2e
<
script
>
import
$
from
'
jquery
'
;
import
{
mapGetters
,
mapActions
}
from
'
vuex
'
;
import
{
getLocationHash
}
from
'
../../lib/utils/url_utility
'
;
import
Flash
from
'
../../flash
'
;
import
store
from
'
../stores/
'
;
import
*
as
constants
from
'
../constants
'
;
import
noteableNote
from
'
./noteable_note.vue
'
;
import
noteableDiscussion
from
'
./noteable_discussion.vue
'
;
import
systemNote
from
'
../../vue_shared/components/notes/system_note.vue
'
;
import
commentForm
from
'
./comment_form.vue
'
;
import
placeholderNote
from
'
../../vue_shared/components/notes/placeholder_note.vue
'
;
import
placeholderSystemNote
from
'
../../vue_shared/components/notes/placeholder_system_note.vue
'
;
import
loadingIcon
from
'
../../vue_shared/components/loading_icon.vue
'
;
import
skeletonLoadingContainer
from
'
../../vue_shared/components/notes/skeleton_note.vue
'
;
import
$
from
'
jquery
'
;
import
{
mapGetters
,
mapActions
}
from
'
vuex
'
;
import
{
getLocationHash
}
from
'
../../lib/utils/url_utility
'
;
import
Flash
from
'
../../flash
'
;
import
store
from
'
../stores/
'
;
import
*
as
constants
from
'
../constants
'
;
import
noteableNote
from
'
./noteable_note.vue
'
;
import
noteableDiscussion
from
'
./noteable_discussion.vue
'
;
import
systemNote
from
'
../../vue_shared/components/notes/system_note.vue
'
;
import
commentForm
from
'
./comment_form.vue
'
;
import
placeholderNote
from
'
../../vue_shared/components/notes/placeholder_note.vue
'
;
import
placeholderSystemNote
from
'
../../vue_shared/components/notes/placeholder_system_note.vue
'
;
import
loadingIcon
from
'
../../vue_shared/components/loading_icon.vue
'
;
import
skeletonLoadingContainer
from
'
../../vue_shared/components/notes/skeleton_note.vue
'
;
export
default
{
name
:
'
NotesApp
'
,
components
:
{
noteableNote
,
noteableDiscussion
,
systemNote
,
commentForm
,
loadingIcon
,
placeholderNote
,
placeholderSystemNote
,
export
default
{
name
:
'
NotesApp
'
,
components
:
{
noteableNote
,
noteableDiscussion
,
systemNote
,
commentForm
,
loadingIcon
,
placeholderNote
,
placeholderSystemNote
,
},
props
:
{
noteableData
:
{
type
:
Object
,
required
:
true
,
},
props
:
{
noteableData
:
{
type
:
Object
,
required
:
true
,
},
notesData
:
{
type
:
Object
,
required
:
true
,
},
userData
:
{
type
:
Object
,
required
:
false
,
default
:
()
=>
({}),
},
notesData
:
{
type
:
Object
,
required
:
true
,
},
store
,
data
()
{
return
{
isLoading
:
true
,
};
userData
:
{
type
:
Object
,
required
:
false
,
default
:
()
=>
({}),
},
computed
:
{
...
mapGetters
([
'
notes
'
,
'
getNotesDataByProp
'
,
'
discussionCount
'
,
]),
noteableType
()
{
// FIXME -- @fatihacet Get this from JSON data.
const
{
ISSUE_NOTEABLE_TYPE
,
MERGE_REQUEST_NOTEABLE_TYPE
}
=
constants
;
},
store
,
data
()
{
return
{
isLoading
:
true
,
};
},
computed
:
{
...
mapGetters
([
'
notes
'
,
'
getNotesDataByProp
'
,
'
discussionCount
'
]),
noteableType
()
{
// FIXME -- @fatihacet Get this from JSON data.
const
{
ISSUE_NOTEABLE_TYPE
,
MERGE_REQUEST_NOTEABLE_TYPE
}
=
constants
;
return
this
.
noteableData
.
merge_params
?
MERGE_REQUEST_NOTEABLE_TYPE
:
ISSUE_NOTEABLE_TYPE
;
},
allNotes
()
{
if
(
this
.
isLoading
)
{
const
totalNotes
=
parseInt
(
this
.
notesData
.
totalNotes
,
10
)
||
0
;
return
new
Array
(
totalNotes
).
fill
({
isSkeletonNote
:
true
,
});
}
return
this
.
notes
;
},
},
created
()
{
this
.
setNotesData
(
this
.
notesData
);
this
.
setNoteableData
(
this
.
noteableData
);
this
.
setUserData
(
this
.
userData
);
return
this
.
noteableData
.
merge_params
?
MERGE_REQUEST_NOTEABLE_TYPE
:
ISSUE_NOTEABLE_TYPE
;
},
mounted
()
{
this
.
fetchNotes
();
allNotes
()
{
if
(
this
.
isLoading
)
{
const
totalNotes
=
parseInt
(
this
.
notesData
.
totalNotes
,
10
)
||
0
;
const
parentElement
=
this
.
$el
.
parentElement
;
if
(
parentElement
&&
parentElement
.
classList
.
contains
(
'
js-vue-notes-event
'
))
{
parentElement
.
addEventListener
(
'
toggleAward
'
,
(
event
)
=>
{
const
{
awardName
,
noteId
}
=
event
.
detail
;
this
.
actionToggleAward
({
awardName
,
noteId
});
return
new
Array
(
totalNotes
).
fill
({
isSkeletonNote
:
true
,
});
}
document
.
addEventListener
(
'
refreshVueNotes
'
,
this
.
fetchNotes
);
},
beforeDestroy
()
{
document
.
removeEventListener
(
'
refreshVueNotes
'
,
this
.
fetchNotes
);
return
this
.
notes
;
},
methods
:
{
...
mapActions
({
actionFetchNotes
:
'
fetchNotes
'
,
poll
:
'
poll
'
,
actionToggleAward
:
'
toggleAward
'
,
scrollToNoteIfNeeded
:
'
scrollToNoteIfNeeded
'
,
setNotesData
:
'
setNotesData
'
,
setNoteableData
:
'
setNoteableData
'
,
setUserData
:
'
setUserData
'
,
setLastFetchedAt
:
'
setLastFetchedAt
'
,
setTargetNoteHash
:
'
setTargetNoteHash
'
,
}),
getComponentName
(
note
)
{
if
(
note
.
isSkeletonNote
)
{
return
skeletonLoadingContainer
;
}
if
(
note
.
isPlaceholderNote
)
{
if
(
note
.
placeholderType
===
constants
.
SYSTEM_NOTE
)
{
return
placeholderSystemNote
;
}
return
placeholderNote
;
}
else
if
(
note
.
individual_note
)
{
return
note
.
notes
[
0
].
system
?
systemNote
:
noteableNote
;
}
},
created
()
{
this
.
setNotesData
(
this
.
notesData
);
this
.
setNoteableData
(
this
.
noteableData
);
this
.
setUserData
(
this
.
userData
);
},
mounted
()
{
this
.
fetchNotes
();
const
parentElement
=
this
.
$el
.
parentElement
;
return
noteableDiscussion
;
},
getComponentData
(
note
)
{
return
note
.
individual_note
?
note
.
notes
[
0
]
:
note
;
},
fetchNotes
()
{
return
this
.
actionFetchNotes
(
this
.
getNotesDataByProp
(
'
discussionsPath
'
))
.
then
(()
=>
this
.
initPolling
())
.
then
(()
=>
{
this
.
isLoading
=
false
;
})
.
then
(()
=>
this
.
$nextTick
())
.
then
(()
=>
this
.
checkLocationHash
())
.
catch
(()
=>
{
this
.
isLoading
=
false
;
Flash
(
'
Something went wrong while fetching comments. Please try again.
'
);
});
},
initPolling
()
{
if
(
this
.
isPollingInitialized
)
{
return
;
if
(
parentElement
&&
parentElement
.
classList
.
contains
(
'
js-vue-notes-event
'
)
)
{
parentElement
.
addEventListener
(
'
toggleAward
'
,
event
=>
{
const
{
awardName
,
noteId
}
=
event
.
detail
;
this
.
actionToggleAward
({
awardName
,
noteId
});
});
}
document
.
addEventListener
(
'
refreshVueNotes
'
,
this
.
fetchNotes
);
},
beforeDestroy
()
{
document
.
removeEventListener
(
'
refreshVueNotes
'
,
this
.
fetchNotes
);
},
methods
:
{
...
mapActions
({
actionFetchNotes
:
'
fetchNotes
'
,
poll
:
'
poll
'
,
actionToggleAward
:
'
toggleAward
'
,
scrollToNoteIfNeeded
:
'
scrollToNoteIfNeeded
'
,
setNotesData
:
'
setNotesData
'
,
setNoteableData
:
'
setNoteableData
'
,
setUserData
:
'
setUserData
'
,
setLastFetchedAt
:
'
setLastFetchedAt
'
,
setTargetNoteHash
:
'
setTargetNoteHash
'
,
}),
getComponentName
(
note
)
{
if
(
note
.
isSkeletonNote
)
{
return
skeletonLoadingContainer
;
}
if
(
note
.
isPlaceholderNote
)
{
if
(
note
.
placeholderType
===
constants
.
SYSTEM_NOTE
)
{
return
placeholderSystemNote
;
}
return
placeholderNote
;
}
else
if
(
note
.
individual_note
)
{
return
note
.
notes
[
0
].
system
?
systemNote
:
noteableNote
;
}
this
.
setLastFetchedAt
(
this
.
getNotesDataByProp
(
'
lastFetchedAt
'
));
return
noteableDiscussion
;
},
getComponentData
(
note
)
{
return
note
.
individual_note
?
note
.
notes
[
0
]
:
note
;
},
fetchNotes
()
{
return
this
.
actionFetchNotes
(
this
.
getNotesDataByProp
(
'
discussionsPath
'
))
.
then
(()
=>
this
.
initPolling
())
.
then
(()
=>
{
this
.
isLoading
=
false
;
})
.
then
(()
=>
this
.
$nextTick
())
.
then
(()
=>
this
.
checkLocationHash
())
.
catch
(()
=>
{
this
.
isLoading
=
false
;
Flash
(
'
Something went wrong while fetching comments. Please try again.
'
,
);
});
},
initPolling
()
{
if
(
this
.
isPollingInitialized
)
{
return
;
}
this
.
poll
();
this
.
isPollingInitialized
=
true
;
},
checkLocationHash
()
{
const
hash
=
getLocationHash
();
const
element
=
document
.
getElementById
(
hash
);
this
.
setLastFetchedAt
(
this
.
getNotesDataByProp
(
'
lastFetchedAt
'
));
if
(
hash
&&
element
)
{
this
.
setTargetNoteHash
(
hash
);
this
.
scrollToNoteIfNeeded
(
$
(
element
));
}
},
this
.
poll
();
this
.
isPollingInitialized
=
true
;
},
checkLocationHash
()
{
const
hash
=
getLocationHash
();
const
element
=
document
.
getElementById
(
hash
);
if
(
hash
&&
element
)
{
this
.
setTargetNoteHash
(
hash
);
this
.
scrollToNoteIfNeeded
(
$
(
element
));
}
},
};
},
};
</
script
>
<
template
>
...
...
app/assets/javascripts/notes/index.js
View file @
fdc9ae2e
import
Vue
from
'
vue
'
;
import
notesApp
from
'
./components/notes_app.vue
'
;
document
.
addEventListener
(
'
DOMContentLoaded
'
,
()
=>
new
Vue
({
el
:
'
#js-vue-notes
'
,
components
:
{
notesApp
,
},
data
()
{
const
notesDataset
=
document
.
getElementById
(
'
js-vue-notes
'
).
dataset
;
const
parsedUserData
=
JSON
.
parse
(
notesDataset
.
currentUserData
);
const
currentUserData
=
parsedUserData
?
{
id
:
parsedUserData
.
id
,
name
:
parsedUserData
.
name
,
username
:
parsedUserData
.
username
,
avatar_url
:
parsedUserData
.
avatar_path
||
parsedUserData
.
avatar_url
,
path
:
parsedUserData
.
path
,
}
:
{};
document
.
addEventListener
(
'
DOMContentLoaded
'
,
()
=>
new
Vue
({
el
:
'
#js-vue-notes
'
,
components
:
{
notesApp
,
},
data
()
{
const
notesDataset
=
document
.
getElementById
(
'
js-vue-notes
'
).
dataset
;
const
parsedUserData
=
JSON
.
parse
(
notesDataset
.
currentUserData
);
let
currentUserData
=
{};
if
(
parsedUserData
)
{
currentUserData
=
{
id
:
parsedUserData
.
id
,
name
:
parsedUserData
.
name
,
username
:
parsedUserData
.
username
,
avatar_url
:
parsedUserData
.
avatar_path
||
parsedUserData
.
avatar_url
,
path
:
parsedUserData
.
path
,
};
}
return
{
noteableData
:
JSON
.
parse
(
notesDataset
.
noteableData
),
currentUserData
,
notesData
:
JSON
.
parse
(
notesDataset
.
notesData
),
};
},
render
(
createElement
)
{
return
createElement
(
'
notes-app
'
,
{
props
:
{
noteableData
:
this
.
noteableData
,
notesData
:
this
.
notesData
,
userData
:
this
.
currentUserData
,
return
{
noteableData
:
JSON
.
parse
(
notesDataset
.
noteableData
),
currentUserData
,
notesData
:
JSON
.
parse
(
notesDataset
.
notesData
),
};
},
render
(
createElement
)
{
return
createElement
(
'
notes-app
'
,
{
props
:
{
noteableData
:
this
.
noteableData
,
notesData
:
this
.
notesData
,
userData
:
this
.
currentUserData
,
},
});
},
});
},
}));
}),
);
app/assets/javascripts/notes/mixins/autosave.js
View file @
fdc9ae2e
...
...
@@ -5,7 +5,11 @@ import { capitalizeFirstCharacter } from '../../lib/utils/text_utility';
export
default
{
methods
:
{
initAutoSave
(
noteableType
)
{
this
.
autosave
=
new
Autosave
(
$
(
this
.
$refs
.
noteForm
.
$refs
.
textarea
),
[
'
Note
'
,
capitalizeFirstCharacter
(
noteableType
),
this
.
note
.
id
]);
this
.
autosave
=
new
Autosave
(
$
(
this
.
$refs
.
noteForm
.
$refs
.
textarea
),
[
'
Note
'
,
capitalizeFirstCharacter
(
noteableType
),
this
.
note
.
id
,
]);
},
resetAutoSave
()
{
this
.
autosave
.
reset
();
...
...
app/assets/javascripts/notes/mixins/resolvable.js
View file @
fdc9ae2e
...
...
@@ -12,7 +12,8 @@ export default {
discussionResolved
()
{
const
{
notes
,
resolved
}
=
this
.
note
;
if
(
notes
)
{
// Decide resolved state using store. Only valid for discussions.
if
(
notes
)
{
// Decide resolved state using store. Only valid for discussions.
return
notes
.
every
(
note
=>
note
.
resolved
&&
!
note
.
system
);
}
...
...
@@ -26,7 +27,9 @@ export default {
return
__
(
'
Comment and resolve discussion
'
);
}
return
this
.
discussionResolved
?
__
(
'
Unresolve discussion
'
)
:
__
(
'
Resolve discussion
'
);
return
this
.
discussionResolved
?
__
(
'
Unresolve discussion
'
)
:
__
(
'
Resolve discussion
'
);
},
},
methods
:
{
...
...
@@ -42,7 +45,9 @@ export default {
})
.
catch
(()
=>
{
this
.
isResolving
=
false
;
const
msg
=
__
(
'
Something went wrong while resolving this discussion. Please try again.
'
);
const
msg
=
__
(
'
Something went wrong while resolving this discussion. Please try again.
'
,
);
Flash
(
msg
,
'
alert
'
,
this
.
$el
);
});
},
...
...
app/assets/javascripts/notes/services/notes_service.js
View file @
fdc9ae2e
...
...
@@ -22,7 +22,9 @@ export default {
},
toggleResolveNote
(
endpoint
,
isResolved
)
{
const
{
RESOLVE_NOTE_METHOD_NAME
,
UNRESOLVE_NOTE_METHOD_NAME
}
=
constants
;
const
method
=
isResolved
?
UNRESOLVE_NOTE_METHOD_NAME
:
RESOLVE_NOTE_METHOD_NAME
;
const
method
=
isResolved
?
UNRESOLVE_NOTE_METHOD_NAME
:
RESOLVE_NOTE_METHOD_NAME
;
return
Vue
.
http
[
method
](
endpoint
);
},
...
...
app/assets/javascripts/notes/stores/actions.js
View file @
fdc9ae2e
This diff is collapsed.
Click to expand it.
app/assets/javascripts/notes/stores/getters.js
View file @
fdc9ae2e
...
...
@@ -11,27 +11,31 @@ export const getNoteableDataByProp = state => prop => state.noteableData[prop];
export
const
openState
=
state
=>
state
.
noteableData
.
state
;
export
const
getUserData
=
state
=>
state
.
userData
||
{};
export
const
getUserDataByProp
=
state
=>
prop
=>
state
.
userData
&&
state
.
userData
[
prop
];
export
const
getUserDataByProp
=
state
=>
prop
=>
state
.
userData
&&
state
.
userData
[
prop
];
export
const
notesById
=
state
=>
state
.
notes
.
reduce
((
acc
,
note
)
=>
{
note
.
notes
.
every
(
n
=>
Object
.
assign
(
acc
,
{
[
n
.
id
]:
n
}));
return
acc
;
},
{});
export
const
notesById
=
state
=>
state
.
notes
.
reduce
((
acc
,
note
)
=>
{
note
.
notes
.
every
(
n
=>
Object
.
assign
(
acc
,
{
[
n
.
id
]:
n
}));
return
acc
;
},
{});
const
reverseNotes
=
array
=>
array
.
slice
(
0
).
reverse
();
const
isLastNote
=
(
note
,
state
)
=>
!
note
.
system
&&
state
.
userData
&&
note
.
author
&&
const
isLastNote
=
(
note
,
state
)
=>
!
note
.
system
&&
state
.
userData
&&
note
.
author
&&
note
.
author
.
id
===
state
.
userData
.
id
;
export
const
getCurrentUserLastNote
=
state
=>
_
.
flatten
(
reverseNotes
(
state
.
notes
)
.
map
(
note
=>
reverseNotes
(
note
.
notes
)),
export
const
getCurrentUserLastNote
=
state
=>
_
.
flatten
(
reverseNotes
(
state
.
notes
)
.
map
(
note
=>
reverseNotes
(
note
.
notes
)),
).
find
(
el
=>
isLastNote
(
el
,
state
));
export
const
getDiscussionLastNote
=
state
=>
discussion
=>
reverseNotes
(
discussion
.
notes
)
.
find
(
el
=>
isLastNote
(
el
,
state
));
export
const
getDiscussionLastNote
=
state
=>
discussion
=>
reverseNotes
(
discussion
.
notes
)
.
find
(
el
=>
isLastNote
(
el
,
state
));
export
const
discussionCount
=
(
state
)
=>
{
export
const
discussionCount
=
state
=>
{
const
discussions
=
state
.
notes
.
filter
(
n
=>
!
n
.
individual_note
);
return
discussions
.
length
;
...
...
@@ -43,10 +47,10 @@ export const unresolvedDiscussions = (state, getters) => {
return
state
.
notes
.
filter
(
n
=>
!
n
.
individual_note
&&
!
resolvedMap
[
n
.
id
]);
};
export
const
resolvedDiscussionsById
=
(
state
)
=>
{
export
const
resolvedDiscussionsById
=
state
=>
{
const
map
=
{};
state
.
notes
.
forEach
(
(
n
)
=>
{
state
.
notes
.
forEach
(
n
=>
{
if
(
n
.
notes
)
{
const
resolved
=
n
.
notes
.
every
(
note
=>
note
.
resolved
&&
!
note
.
system
);
...
...
app/assets/javascripts/notes/stores/mutations.js
View file @
fdc9ae2e
...
...
@@ -7,7 +7,7 @@ export default {
[
types
.
ADD_NEW_NOTE
](
state
,
note
)
{
const
{
discussion_id
,
type
}
=
note
;
const
[
exists
]
=
state
.
notes
.
filter
(
n
=>
n
.
id
===
note
.
discussion_id
);
const
isDiscussion
=
(
type
===
constants
.
DISCUSSION_NOTE
)
;
const
isDiscussion
=
type
===
constants
.
DISCUSSION_NOTE
;
if
(
!
exists
)
{
const
noteData
=
{
...
...
@@ -63,13 +63,15 @@ export default {
const
note
=
notes
[
i
];
const
children
=
note
.
notes
;
if
(
children
.
length
&&
!
note
.
individual_note
)
{
// remove placeholder from discussions
if
(
children
.
length
&&
!
note
.
individual_note
)
{
// remove placeholder from discussions
for
(
let
j
=
children
.
length
-
1
;
j
>=
0
;
j
-=
1
)
{
if
(
children
[
j
].
isPlaceholderNote
)
{
children
.
splice
(
j
,
1
);
}
}
}
else
if
(
note
.
isPlaceholderNote
)
{
// remove placeholders from state root
}
else
if
(
note
.
isPlaceholderNote
)
{
// remove placeholders from state root
notes
.
splice
(
i
,
1
);
}
}
...
...
@@ -89,10 +91,10 @@ export default {
[
types
.
SET_INITIAL_NOTES
](
state
,
notesData
)
{
const
notes
=
[];
notesData
.
forEach
(
(
note
)
=>
{
notesData
.
forEach
(
note
=>
{
// To support legacy notes, should be very rare case.
if
(
note
.
individual_note
&&
note
.
notes
.
length
>
1
)
{
note
.
notes
.
forEach
(
(
n
)
=>
{
note
.
notes
.
forEach
(
n
=>
{
notes
.
push
({
...
note
,
notes
:
[
n
],
// override notes array to only have one item to mimick individual_note
...
...
@@ -103,7 +105,7 @@ export default {
notes
.
push
({
...
note
,
expanded
:
(
oldNote
?
oldNote
.
expanded
:
note
.
expanded
)
,
expanded
:
oldNote
?
oldNote
.
expanded
:
note
.
expanded
,
});
}
});
...
...
@@ -128,7 +130,9 @@ export default {
notesArr
.
push
({
individual_note
:
true
,
isPlaceholderNote
:
true
,
placeholderType
:
data
.
isSystemNote
?
constants
.
SYSTEM_NOTE
:
constants
.
NOTE
,
placeholderType
:
data
.
isSystemNote
?
constants
.
SYSTEM_NOTE
:
constants
.
NOTE
,
notes
:
[
{
body
:
data
.
noteBody
,
...
...
@@ -141,12 +145,16 @@ export default {
const
{
awardName
,
note
}
=
data
;
const
{
id
,
name
,
username
}
=
state
.
userData
;
const
hasEmojiAwardedByCurrentUser
=
note
.
award_emoji
.
filter
(
emoji
=>
emoji
.
name
===
data
.
awardName
&&
emoji
.
user
.
id
===
id
);
const
hasEmojiAwardedByCurrentUser
=
note
.
award_emoji
.
filter
(
emoji
=>
emoji
.
name
===
data
.
awardName
&&
emoji
.
user
.
id
===
id
,
);
if
(
hasEmojiAwardedByCurrentUser
.
length
)
{
// If current user has awarded this emoji, remove it.
note
.
award_emoji
.
splice
(
note
.
award_emoji
.
indexOf
(
hasEmojiAwardedByCurrentUser
[
0
]),
1
);
note
.
award_emoji
.
splice
(
note
.
award_emoji
.
indexOf
(
hasEmojiAwardedByCurrentUser
[
0
]),
1
,
);
}
else
{
note
.
award_emoji
.
push
({
name
:
awardName
,
...
...
app/assets/javascripts/notes/stores/utils.js
View file @
fdc9ae2e
...
...
@@ -2,13 +2,15 @@ import AjaxCache from '~/lib/utils/ajax_cache';
const
REGEX_QUICK_ACTIONS
=
/^
\/\w
+.*$/gm
;
export
const
findNoteObjectById
=
(
notes
,
id
)
=>
notes
.
filter
(
n
=>
n
.
id
===
id
)[
0
];
export
const
findNoteObjectById
=
(
notes
,
id
)
=>
notes
.
filter
(
n
=>
n
.
id
===
id
)[
0
];
export
const
getQuickActionText
=
(
note
)
=>
{
export
const
getQuickActionText
=
note
=>
{
let
text
=
'
Applying command
'
;
const
quickActions
=
AjaxCache
.
get
(
gl
.
GfmAutoComplete
.
dataSources
.
commands
)
||
[];
const
quickActions
=
AjaxCache
.
get
(
gl
.
GfmAutoComplete
.
dataSources
.
commands
)
||
[];
const
executedCommands
=
quickActions
.
filter
(
(
command
)
=>
{
const
executedCommands
=
quickActions
.
filter
(
command
=>
{
const
commandRegex
=
new
RegExp
(
`/
${
command
.
name
}
`
);
return
commandRegex
.
test
(
note
);
});
...
...
@@ -27,4 +29,5 @@ export const getQuickActionText = (note) => {
export
const
hasQuickActions
=
note
=>
REGEX_QUICK_ACTIONS
.
test
(
note
);
export
const
stripQuickActions
=
note
=>
note
.
replace
(
REGEX_QUICK_ACTIONS
,
''
).
trim
();
export
const
stripQuickActions
=
note
=>
note
.
replace
(
REGEX_QUICK_ACTIONS
,
''
).
trim
();
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