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
cf5cc6a9
Commit
cf5cc6a9
authored
Jul 25, 2017
by
Filipa Lacerda
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Follow vuex docs to divide store into: actions, getters and mutations
Use constants for mutation types as per vuex docs
parent
fbdc02ad
Changes
13
Hide whitespace changes
Inline
Side-by-side
Showing
13 changed files
with
446 additions
and
408 deletions
+446
-408
app/assets/javascripts/notes/components/issue_comment_form.vue
...ssets/javascripts/notes/components/issue_comment_form.vue
+17
-13
app/assets/javascripts/notes/components/issue_discussion.vue
app/assets/javascripts/notes/components/issue_discussion.vue
+10
-5
app/assets/javascripts/notes/components/issue_note_actions.vue
...ssets/javascripts/notes/components/issue_note_actions.vue
+4
-6
app/assets/javascripts/notes/components/issue_notes.vue
app/assets/javascripts/notes/components/issue_notes.vue
+1
-4
app/assets/javascripts/notes/index.js
app/assets/javascripts/notes/index.js
+7
-3
app/assets/javascripts/notes/stores/actions.js
app/assets/javascripts/notes/stores/actions.js
+205
-0
app/assets/javascripts/notes/stores/getters.js
app/assets/javascripts/notes/stores/getters.js
+15
-0
app/assets/javascripts/notes/stores/index.js
app/assets/javascripts/notes/stores/index.js
+18
-0
app/assets/javascripts/notes/stores/issue_notes_store.js
app/assets/javascripts/notes/stores/issue_notes_store.js
+0
-342
app/assets/javascripts/notes/stores/issue_notes_utils.js
app/assets/javascripts/notes/stores/issue_notes_utils.js
+0
-35
app/assets/javascripts/notes/stores/mutation_types.js
app/assets/javascripts/notes/stores/mutation_types.js
+11
-0
app/assets/javascripts/notes/stores/mutations.js
app/assets/javascripts/notes/stores/mutations.js
+127
-0
app/assets/javascripts/notes/stores/utils.js
app/assets/javascripts/notes/stores/utils.js
+31
-0
No files found.
app/assets/javascripts/notes/components/issue_comment_form.vue
View file @
cf5cc6a9
...
@@ -209,12 +209,13 @@ export default {
...
@@ -209,12 +209,13 @@ export default {
aria-hidden=
"true"
aria-hidden=
"true"
class=
"fa fa-caret-down toggle-icon"
></i>
class=
"fa fa-caret-down toggle-icon"
></i>
</button>
</button>
<ul
<ul
class=
"note-type-dropdown dropdown-open-top dropdown-menu"
>
class=
"note-type-dropdown dropdown-open-top dropdown-menu"
>
<li
<li
:class=
"
{ 'droplab-item-selected': noteType === 'comment' }"
:class=
"
{ 'droplab-item-selected': noteType === 'comment' }"
@click.prevent="setNoteType('comment')">
@click.prevent="setNoteType('comment')">
<button
class=
"btn btn-transparent"
>
<button
type=
"button"
class=
"btn btn-transparent"
>
<i
<i
aria-hidden=
"true"
aria-hidden=
"true"
class=
"fa fa-check icon"
></i>
class=
"fa fa-check icon"
></i>
...
@@ -230,10 +231,13 @@ export default {
...
@@ -230,10 +231,13 @@ export default {
<li
<li
:class=
"
{ 'droplab-item-selected': noteType === 'discussion' }"
:class=
"
{ 'droplab-item-selected': noteType === 'discussion' }"
@click.prevent="setNoteType('discussion')">
@click.prevent="setNoteType('discussion')">
<button
class=
"btn btn-transparent"
>
<button
type=
"button"
class=
"btn btn-transparent"
>
<i
<i
aria-hidden=
"true"
aria-hidden=
"true"
class=
"fa fa-check icon"
></i>
class=
"fa fa-check icon"
>
</i>
<div
class=
"description"
>
<div
class=
"description"
>
<strong>
Start discussion
</strong>
<strong>
Start discussion
</strong>
<p>
<p>
...
@@ -244,21 +248,21 @@ export default {
...
@@ -244,21 +248,21 @@ export default {
</li>
</li>
</ul>
</ul>
</div>
</div>
<a
<button
type=
"button"
@
click=
"handleSave(true)"
@
click=
"handleSave(true)"
v-if=
"canUpdateIssue"
v-if=
"canUpdateIssue"
:class=
"actionButtonClassNames"
:class=
"actionButtonClassNames"
class=
"btn btn-nr btn-comment btn-comment-and-close"
class=
"btn btn-nr btn-comment btn-comment-and-close"
>
role=
"button"
>
{{
issueActionButtonTitle
}}
{{
issueActionButtonTitle
}}
</a>
</button>
<a
<button
type=
"button"
v-if=
"note.length"
v-if=
"note.length"
@
click=
"discard"
@
click=
"discard"
class=
"btn btn-cancel js-note-discard"
class=
"btn btn-cancel js-note-discard"
>
role=
"button"
>
Discard draft
Discard draft
</
a
>
</
button
>
</div>
</div>
</div>
</div>
</div>
</div>
...
...
app/assets/javascripts/notes/components/issue_discussion.vue
View file @
cf5cc6a9
...
@@ -71,7 +71,8 @@ export default {
...
@@ -71,7 +71,8 @@ export default {
cancelReplyForm
(
shouldConfirm
)
{
cancelReplyForm
(
shouldConfirm
)
{
if
(
shouldConfirm
&&
this
.
$refs
.
noteForm
.
isDirty
)
{
if
(
shouldConfirm
&&
this
.
$refs
.
noteForm
.
isDirty
)
{
const
msg
=
'
Are you sure you want to cancel creating this comment?
'
;
const
msg
=
'
Are you sure you want to cancel creating this comment?
'
;
const
isConfirmed
=
confirm
(
msg
);
// eslint-disable-line
// eslint-disable-next-line no-alert
const
isConfirmed
=
confirm
(
msg
);
if
(
!
isConfirmed
)
{
if
(
!
isConfirmed
)
{
return
;
return
;
}
}
...
@@ -112,7 +113,8 @@ export default {
...
@@ -112,7 +113,8 @@ export default {
:link-href=
"author.path"
:link-href=
"author.path"
:img-src=
"author.avatar_url"
:img-src=
"author.avatar_url"
:img-alt=
"author.name"
:img-alt=
"author.name"
:img-size=
"40"
/>
:img-size=
"40"
/>
</div>
</div>
<div
class=
"timeline-content"
>
<div
class=
"timeline-content"
>
<div
class=
"discussion"
>
<div
class=
"discussion"
>
...
@@ -123,13 +125,15 @@ export default {
...
@@ -123,13 +125,15 @@ export default {
:note-id=
"discussion.id"
:note-id=
"discussion.id"
:include-toggle=
"true"
:include-toggle=
"true"
:toggle-handler=
"toggleDiscussion"
:toggle-handler=
"toggleDiscussion"
actionText=
"started a discussion"
/>
actionText=
"started a discussion"
/>
<issue-note-edited-text
<issue-note-edited-text
v-if=
"note.last_updated_by"
v-if=
"note.last_updated_by"
:edited-at=
"note.last_updated_at"
:edited-at=
"note.last_updated_at"
:edited-by=
"note.last_updated_by"
:edited-by=
"note.last_updated_by"
actionText=
"Last updated"
actionText=
"Last updated"
className=
"discussion-headline-light js-discussion-headline"
/>
className=
"discussion-headline-light js-discussion-headline"
/>
</div>
</div>
</div>
</div>
<div
<div
...
@@ -142,7 +146,8 @@ export default {
...
@@ -142,7 +146,8 @@ export default {
v-for=
"note in note.notes"
v-for=
"note in note.notes"
:is=
"componentName(note)"
:is=
"componentName(note)"
:note=
"componentData(note)"
:note=
"componentData(note)"
key=
"note.id"
/>
key=
"note.id"
/>
</ul>
</ul>
<div
class=
"flash-container"
></div>
<div
class=
"flash-container"
></div>
<div
class=
"discussion-reply-holder"
>
<div
class=
"discussion-reply-holder"
>
...
...
app/assets/javascripts/notes/components/issue_note_actions.vue
View file @
cf5cc6a9
...
@@ -2,6 +2,7 @@
...
@@ -2,6 +2,7 @@
import
emojiSmiling
from
'
icons/_emoji_slightly_smiling_face.svg
'
;
import
emojiSmiling
from
'
icons/_emoji_slightly_smiling_face.svg
'
;
import
emojiSmile
from
'
icons/_emoji_smile.svg
'
;
import
emojiSmile
from
'
icons/_emoji_smile.svg
'
;
import
emojiSmiley
from
'
icons/_emoji_smiley.svg
'
;
import
emojiSmiley
from
'
icons/_emoji_smiley.svg
'
;
import
loadingIcon
from
'
../../vue_shared/components/loadingIcon.vue
'
;
export
default
{
export
default
{
props
:
{
props
:
{
...
@@ -78,9 +79,7 @@ export default {
...
@@ -78,9 +79,7 @@ export default {
data-position="right"
data-position="right"
href="#"
href="#"
title="Add reaction">
title="Add reaction">
<i
<loading-icon
/>
aria-hidden=
"true"
class=
"fa fa-spinner fa-spin"
></i>
<span
<span
v-html=
"emojiSmiling"
v-html=
"emojiSmiling"
class=
"link-highlight award-control-icon-neutral"
></span>
class=
"link-highlight award-control-icon-neutral"
></span>
...
@@ -122,15 +121,14 @@ export default {
...
@@ -122,15 +121,14 @@ export default {
</a>
</a>
</li>
</li>
<li
v-if=
"canEdit"
>
<li
v-if=
"canEdit"
>
<
a
<
button
@
click.prevent=
"deleteHandler"
@
click.prevent=
"deleteHandler"
class=
"btn btn-transparent js-note-delete js-note-delete"
class=
"btn btn-transparent js-note-delete js-note-delete"
href=
"#"
type=
"button"
>
type=
"button"
>
<span
class=
"text-danger"
>
<span
class=
"text-danger"
>
Delete comment
Delete comment
</span>
</span>
</
a
>
</
button
>
</li>
</li>
</ul>
</ul>
</div>
</div>
...
...
app/assets/javascripts/notes/components/issue_notes.vue
View file @
cf5cc6a9
...
@@ -12,10 +12,7 @@ import issueSystemNote from './issue_system_note.vue';
...
@@ -12,10 +12,7 @@ import issueSystemNote from './issue_system_note.vue';
import
issueCommentForm
from
'
./issue_comment_form.vue
'
;
import
issueCommentForm
from
'
./issue_comment_form.vue
'
;
import
placeholderNote
from
'
./issue_placeholder_note.vue
'
;
import
placeholderNote
from
'
./issue_placeholder_note.vue
'
;
import
placeholderSystemNote
from
'
./issue_placeholder_system_note.vue
'
;
import
placeholderSystemNote
from
'
./issue_placeholder_system_note.vue
'
;
import
store
from
'
./store
'
;
Vue
.
use
(
Vuex
);
Vue
.
use
(
VueResource
);
const
store
=
new
Vuex
.
Store
(
storeOptions
);
export
default
{
export
default
{
name
:
'
IssueNotes
'
,
name
:
'
IssueNotes
'
,
...
...
app/assets/javascripts/notes/index.js
View file @
cf5cc6a9
...
@@ -8,9 +8,13 @@ document.addEventListener('DOMContentLoaded', () => {
...
@@ -8,9 +8,13 @@ document.addEventListener('DOMContentLoaded', () => {
components
:
{
components
:
{
issueNotes
,
issueNotes
,
},
},
template
:
`
render
(
createElement
)
{
<issue-notes ref="notes" />
return
createElement
(
'
issue-notes
'
,
{
`
,
attrs
:
{
ref
:
'
notes
'
,
},
});
},
});
});
window
.
issueNotes
=
{
window
.
issueNotes
=
{
...
...
app/assets/javascripts/notes/stores/actions.js
0 → 100644
View file @
cf5cc6a9
/* global Flash */
import
*
as
types
from
'
./mutation_types
'
;
import
*
as
utils
from
'
./issue_notes_utils
'
;
import
service
from
'
../services/issue_notes_service
'
;
import
loadAwardsHandler
from
'
../../awards_handler
'
;
import
sidebarTimeTrackingEventHub
from
'
../../sidebar/event_hub
'
;
export
const
fetchNotes
=
({
commit
},
path
)
=>
service
.
fetchNotes
(
path
)
.
then
(
res
=>
res
.
json
())
.
then
((
res
)
=>
{
commit
(
types
.
SET_INITAL_NOTES
,
res
);
});
export
const
deleteNote
=
({
commit
},
note
)
=>
service
.
deleteNote
(
note
.
path
)
.
then
(()
=>
{
commit
(
types
.
DELETE_NOTE
,
note
);
});
export
const
updateNote
=
({
commit
},
data
)
=>
{
const
{
endpoint
,
note
}
=
data
;
return
service
.
updateNote
(
endpoint
,
note
)
.
then
(
res
=>
res
.
json
())
.
then
((
res
)
=>
{
commit
(
types
.
UPDATE_NOTE
,
res
);
});
};
export
const
replyToDiscussion
=
({
commit
},
note
)
=>
{
const
{
endpoint
,
data
}
=
note
;
return
service
.
replyToDiscussion
(
endpoint
,
data
)
.
then
(
res
=>
res
.
json
())
.
then
((
res
)
=>
{
commit
(
types
.
ADD_NEW_REPLY_TO_DISCUSSION
,
res
);
return
res
;
});
};
export
const
createNewNote
=
({
commit
},
note
)
=>
{
const
{
endpoint
,
data
}
=
note
;
return
service
.
createNewNote
(
endpoint
,
data
)
.
then
(
res
=>
res
.
json
())
.
then
((
res
)
=>
{
if
(
!
res
.
errors
)
{
commit
(
types
.
ADD_NEW_NOTE
,
res
);
}
return
res
;
});
};
export
const
saveNote
=
({
commit
,
dispatch
},
noteData
)
=>
{
const
{
note
}
=
noteData
.
data
.
note
;
let
placeholderText
=
note
;
const
hasQuickActions
=
utils
.
hasQuickActions
(
placeholderText
);
const
replyId
=
noteData
.
data
.
in_reply_to_discussion_id
;
const
methodToDispatch
=
replyId
?
'
replyToDiscussion
'
:
'
createNewNote
'
;
if
(
hasQuickActions
)
{
placeholderText
=
utils
.
stripQuickActions
(
placeholderText
);
}
if
(
placeholderText
.
length
)
{
commit
(
types
.
SHOW_PLACEHOLDER_NOTE
,
{
noteBody
:
placeholderText
,
replyId
,
});
}
if
(
hasQuickActions
)
{
commit
(
types
.
SHOW_PLACEHOLDER_NOTE
,
{
isSystemNote
:
true
,
noteBody
:
utils
.
getQuickActionText
(
note
),
replyId
,
});
}
return
dispatch
(
methodToDispatch
,
noteData
)
.
then
((
res
)
=>
{
const
{
errors
}
=
res
;
const
commandsChanges
=
res
.
commands_changes
;
if
(
hasQuickActions
&&
Object
.
keys
(
errors
).
length
)
{
dispatch
(
'
poll
'
);
$
(
'
.js-gfm-input
'
).
trigger
(
'
clear-commands-cache.atwho
'
);
Flash
(
'
Commands applied
'
,
'
notice
'
,
$
(
noteData
.
flashContainer
));
}
if
(
commandsChanges
)
{
if
(
commandsChanges
.
emoji_award
)
{
const
votesBlock
=
$
(
'
.js-awards-block
'
).
eq
(
0
);
loadAwardsHandler
()
.
then
((
awardsHandler
)
=>
{
awardsHandler
.
addAwardToEmojiBar
(
votesBlock
,
commandsChanges
.
emoji_award
);
awardsHandler
.
scrollToAwards
();
})
.
catch
(()
=>
{
Flash
(
'
Something went wrong while adding your award. Please try again.
'
,
null
,
$
(
noteData
.
flashContainer
),
);
});
}
if
(
commandsChanges
.
spend_time
!=
null
||
commandsChanges
.
time_estimate
!=
null
)
{
sidebarTimeTrackingEventHub
.
$emit
(
'
timeTrackingUpdated
'
,
res
);
}
}
if
(
errors
&&
errors
.
commands_only
)
{
Flash
(
errors
.
commands_only
,
'
notice
'
,
$
(
noteData
.
flashContainer
));
}
commit
(
types
.
REMOVE_PLACEHOLDER_NOTES
);
return
res
;
})
.
catch
(()
=>
{
Flash
(
'
Your comment could not be submitted! Please check your network connection and try again.
'
,
'
alert
'
,
$
(
noteData
.
flashContainer
),
);
commit
(
types
.
REMOVE_PLACEHOLDER_NOTES
);
});
};
export
const
poll
=
({
commit
,
state
,
getters
})
=>
{
const
{
notesPath
}
=
$
(
'
.js-notes-wrapper
'
)[
0
].
dataset
;
return
service
.
poll
(
`
${
notesPath
}
?full_data=1`
,
state
.
lastFetchedAt
)
.
then
(
res
=>
res
.
json
())
.
then
((
res
)
=>
{
if
(
res
.
notes
.
length
)
{
const
{
notesById
}
=
getters
;
res
.
notes
.
forEach
((
note
)
=>
{
if
(
notesById
[
note
.
id
])
{
commit
(
types
.
UPDATE_NOTE
,
note
);
}
else
if
(
note
.
type
===
'
DiscussionNote
'
)
{
const
discussion
=
utils
.
findNoteObjectById
(
state
.
notes
,
note
.
discussion_id
);
if
(
discussion
)
{
commit
(
types
.
ADD_NEW_REPLY_TO_DISCUSSION
,
note
);
}
else
{
commit
(
types
.
ADD_NEW_NOTE
,
note
);
}
}
else
{
commit
(
types
.
ADD_NEW_NOTE
,
note
);
}
});
}
return
res
;
});
};
export
const
toggleAward
=
({
commit
,
getters
,
dispatch
},
data
)
=>
{
const
{
endpoint
,
awardName
,
noteId
,
skipMutalityCheck
}
=
data
;
const
note
=
getters
.
notesById
[
noteId
];
return
service
.
toggleAward
(
endpoint
,
{
name
:
awardName
})
.
then
(
res
=>
res
.
json
())
.
then
(()
=>
{
commit
(
types
.
TOGGLE_AWARD
,
{
awardName
,
note
});
if
(
!
skipMutalityCheck
&&
(
awardName
===
'
thumbsup
'
||
awardName
===
'
thumbsdown
'
))
{
const
counterAward
=
awardName
===
'
thumbsup
'
?
'
thumbsdown
'
:
'
thumbsup
'
;
const
targetNote
=
getters
.
notesById
[
noteId
];
let
amIAwarded
=
false
;
targetNote
.
award_emoji
.
forEach
((
a
)
=>
{
if
(
a
.
name
===
counterAward
&&
a
.
user
.
id
===
window
.
gon
.
current_user_id
)
{
amIAwarded
=
true
;
}
});
if
(
amIAwarded
)
{
Object
.
assign
(
data
,
{
awardName
:
counterAward
});
Object
.
assign
(
data
,
{
skipMutalityCheck
:
true
});
dispatch
(
types
.
TOGGLE_AWARD
,
data
);
}
}
});
};
export
const
scrollToNoteIfNeeded
=
(
context
,
el
)
=>
{
const
isInViewport
=
gl
.
utils
.
isInViewport
(
el
[
0
]);
if
(
!
isInViewport
)
{
gl
.
utils
.
scrollToElement
(
el
);
}
};
app/assets/javascripts/notes/stores/getters.js
0 → 100644
View file @
cf5cc6a9
export
const
notes
=
state
=>
state
.
notes
;
export
const
targetNoteHash
=
state
=>
state
.
targetNoteHash
;
export
const
notesById
=
(
state
)
=>
{
const
notesByIdObject
=
{};
state
.
notes
.
forEach
((
note
)
=>
{
note
.
notes
.
forEach
((
n
)
=>
{
notesByIdObject
[
n
.
id
]
=
n
;
});
});
return
notesByIdObject
;
};
app/assets/javascripts/notes/stores/index.js
0 → 100644
View file @
cf5cc6a9
import
Vue
from
'
vue
'
;
import
Vuex
from
'
vuex
'
;
import
*
as
actions
from
'
./actions
'
;
import
*
as
getters
from
'
./getters
'
;
import
mutations
from
'
./mutations
'
;
Vue
.
use
(
Vuex
);
export
default
new
Vuex
.
Store
({
state
:
{
notes
:
[],
targetNoteHash
:
null
,
lastFetchedAt
:
null
,
},
actions
,
getters
,
mutations
,
});
app/assets/javascripts/notes/stores/issue_notes_store.js
deleted
100644 → 0
View file @
fbdc02ad
/* eslint-disable no-param-reassign */
/* global Flash */
import
service
from
'
../services/issue_notes_service
'
;
import
utils
from
'
./issue_notes_utils
'
;
import
loadAwardsHandler
from
'
../../awards_handler
'
;
import
sidebarTimeTrackingEventHub
from
'
../../sidebar/event_hub
'
;
const
state
=
{
notes
:
[],
targetNoteHash
:
null
,
lastFetchedAt
:
null
,
};
const
getters
=
{
notes
(
storeState
)
{
return
storeState
.
notes
;
},
targetNoteHash
(
storeState
)
{
return
storeState
.
targetNoteHash
;
},
notesById
(
storeState
)
{
const
notesById
=
{};
storeState
.
notes
.
forEach
((
note
)
=>
{
note
.
notes
.
forEach
((
n
)
=>
{
notesById
[
n
.
id
]
=
n
;
});
});
return
notesById
;
},
};
const
mutations
=
{
setInitialNotes
(
storeState
,
notes
)
{
storeState
.
notes
=
notes
;
},
setTargetNoteHash
(
storeState
,
hash
)
{
storeState
.
targetNoteHash
=
hash
;
},
toggleDiscussion
(
storeState
,
{
discussionId
})
{
const
discussion
=
utils
.
findNoteObjectById
(
storeState
.
notes
,
discussionId
);
discussion
.
expanded
=
!
discussion
.
expanded
;
},
deleteNote
(
storeState
,
note
)
{
const
noteObj
=
utils
.
findNoteObjectById
(
storeState
.
notes
,
note
.
discussion_id
);
if
(
noteObj
.
individual_note
)
{
storeState
.
notes
.
splice
(
storeState
.
notes
.
indexOf
(
noteObj
),
1
);
}
else
{
const
comment
=
utils
.
findNoteObjectById
(
noteObj
.
notes
,
note
.
id
);
noteObj
.
notes
.
splice
(
noteObj
.
notes
.
indexOf
(
comment
),
1
);
if
(
!
noteObj
.
notes
.
length
)
{
storeState
.
notes
.
splice
(
storeState
.
notes
.
indexOf
(
noteObj
),
1
);
}
}
},
addNewReplyToDiscussion
(
storeState
,
note
)
{
const
noteObj
=
utils
.
findNoteObjectById
(
storeState
.
notes
,
note
.
discussion_id
);
if
(
noteObj
)
{
noteObj
.
notes
.
push
(
note
);
}
},
updateNote
(
storeState
,
note
)
{
const
noteObj
=
utils
.
findNoteObjectById
(
storeState
.
notes
,
note
.
discussion_id
);
if
(
noteObj
.
individual_note
)
{
noteObj
.
notes
.
splice
(
0
,
1
,
note
);
}
else
{
const
comment
=
utils
.
findNoteObjectById
(
noteObj
.
notes
,
note
.
id
);
noteObj
.
notes
.
splice
(
noteObj
.
notes
.
indexOf
(
comment
),
1
,
note
);
}
},
addNewNote
(
storeState
,
note
)
{
const
{
discussion_id
,
type
}
=
note
;
const
noteData
=
{
expanded
:
true
,
id
:
discussion_id
,
individual_note
:
!
(
type
===
'
DiscussionNote
'
),
notes
:
[
note
],
reply_id
:
discussion_id
,
};
storeState
.
notes
.
push
(
noteData
);
},
toggleAward
(
storeState
,
data
)
{
const
{
awardName
,
note
}
=
data
;
const
{
id
,
name
,
username
}
=
window
.
gl
.
currentUserData
;
let
index
=
-
1
;
note
.
award_emoji
.
forEach
((
a
,
i
)
=>
{
if
(
a
.
name
===
awardName
&&
a
.
user
.
id
===
id
)
{
index
=
i
;
}
});
if
(
index
>
-
1
)
{
// if I am awarded, remove my award
note
.
award_emoji
.
splice
(
index
,
1
);
}
else
{
note
.
award_emoji
.
push
({
name
:
awardName
,
user
:
{
id
,
name
,
username
},
});
}
},
setLastFetchedAt
(
storeState
,
fetchedAt
)
{
storeState
.
lastFetchedAt
=
fetchedAt
;
},
showPlaceholderNote
(
storeState
,
data
)
{
let
notesArr
=
storeState
.
notes
;
if
(
data
.
replyId
)
{
notesArr
=
utils
.
findNoteObjectById
(
notesArr
,
data
.
replyId
).
notes
;
}
notesArr
.
push
({
individual_note
:
true
,
isPlaceholderNote
:
true
,
placeholderType
:
data
.
isSystemNote
?
'
systemNote
'
:
'
note
'
,
notes
:
[
{
body
:
data
.
noteBody
,
},
],
});
},
removePlaceholderNotes
(
storeState
)
{
const
{
notes
}
=
storeState
;
for
(
let
i
=
notes
.
length
-
1
;
i
>=
0
;
i
-=
1
)
{
const
note
=
notes
[
i
];
const
children
=
note
.
notes
;
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
notes
.
splice
(
i
,
1
);
}
}
},
};
const
actions
=
{
fetchNotes
(
context
,
path
)
{
return
service
.
fetchNotes
(
path
)
.
then
(
res
=>
res
.
json
())
.
then
((
res
)
=>
{
context
.
commit
(
'
setInitialNotes
'
,
res
);
});
},
deleteNote
(
context
,
note
)
{
return
service
.
deleteNote
(
note
.
path
)
.
then
(()
=>
{
context
.
commit
(
'
deleteNote
'
,
note
);
});
},
updateNote
(
context
,
data
)
{
const
{
endpoint
,
note
}
=
data
;
return
service
.
updateNote
(
endpoint
,
note
)
.
then
(
res
=>
res
.
json
())
.
then
((
res
)
=>
{
context
.
commit
(
'
updateNote
'
,
res
);
});
},
replyToDiscussion
(
context
,
noteData
)
{
const
{
endpoint
,
data
}
=
noteData
;
return
service
.
replyToDiscussion
(
endpoint
,
data
)
.
then
(
res
=>
res
.
json
())
.
then
((
res
)
=>
{
context
.
commit
(
'
addNewReplyToDiscussion
'
,
res
);
return
res
;
});
},
createNewNote
(
context
,
noteData
)
{
const
{
endpoint
,
data
}
=
noteData
;
return
service
.
createNewNote
(
endpoint
,
data
)
.
then
(
res
=>
res
.
json
())
.
then
((
res
)
=>
{
if
(
!
res
.
errors
)
{
context
.
commit
(
'
addNewNote
'
,
res
);
}
return
res
;
});
},
saveNote
(
context
,
noteData
)
{
const
{
note
}
=
noteData
.
data
.
note
;
let
placeholderText
=
note
;
const
hasQuickActions
=
utils
.
hasQuickActions
(
placeholderText
);
const
replyId
=
noteData
.
data
.
in_reply_to_discussion_id
;
const
methodToDispatch
=
replyId
?
'
replyToDiscussion
'
:
'
createNewNote
'
;
if
(
hasQuickActions
)
{
placeholderText
=
utils
.
stripQuickActions
(
placeholderText
);
}
if
(
placeholderText
.
length
)
{
context
.
commit
(
'
showPlaceholderNote
'
,
{
noteBody
:
placeholderText
,
replyId
,
});
}
if
(
hasQuickActions
)
{
context
.
commit
(
'
showPlaceholderNote
'
,
{
isSystemNote
:
true
,
noteBody
:
utils
.
getQuickActionText
(
note
),
replyId
,
});
}
return
context
.
dispatch
(
methodToDispatch
,
noteData
)
.
then
((
res
)
=>
{
const
{
errors
}
=
res
;
const
commandsChanges
=
res
.
commands_changes
;
if
(
hasQuickActions
&&
Object
.
keys
(
errors
).
length
)
{
context
.
dispatch
(
'
poll
'
);
$
(
'
.js-gfm-input
'
).
trigger
(
'
clear-commands-cache.atwho
'
);
Flash
(
'
Commands applied
'
,
'
notice
'
,
$
(
noteData
.
flashContainer
));
}
if
(
commandsChanges
)
{
if
(
commandsChanges
.
emoji_award
)
{
const
votesBlock
=
$
(
'
.js-awards-block
'
).
eq
(
0
);
loadAwardsHandler
().
then
((
awardsHandler
)
=>
{
awardsHandler
.
addAwardToEmojiBar
(
votesBlock
,
commandsChanges
.
emoji_award
);
awardsHandler
.
scrollToAwards
();
}).
catch
(()
=>
{
const
msg
=
'
Something went wrong while adding your award. Please try again.
'
;
Flash
(
msg
,
$
(
noteData
.
flashContainer
));
});
}
if
(
commandsChanges
.
spend_time
!=
null
||
commandsChanges
.
time_estimate
!=
null
)
{
sidebarTimeTrackingEventHub
.
$emit
(
'
timeTrackingUpdated
'
,
res
);
}
}
if
(
errors
&&
errors
.
commands_only
)
{
Flash
(
errors
.
commands_only
,
'
notice
'
,
$
(
noteData
.
flashContainer
));
}
context
.
commit
(
'
removePlaceholderNotes
'
);
return
res
;
})
.
catch
(()
=>
{
const
msg
=
'
Your comment could not be submitted! Please check your network connection and try again.
'
;
Flash
(
msg
,
'
alert
'
,
$
(
noteData
.
flashContainer
));
context
.
commit
(
'
removePlaceholderNotes
'
);
});
},
poll
(
context
)
{
const
{
notesPath
}
=
$
(
'
.js-notes-wrapper
'
)[
0
].
dataset
;
return
service
.
poll
(
`
${
notesPath
}
?full_data=1`
,
context
.
state
.
lastFetchedAt
)
.
then
(
res
=>
res
.
json
())
.
then
((
res
)
=>
{
if
(
res
.
notes
.
length
)
{
const
{
notesById
}
=
context
.
getters
;
res
.
notes
.
forEach
((
note
)
=>
{
if
(
notesById
[
note
.
id
])
{
context
.
commit
(
'
updateNote
'
,
note
);
}
else
if
(
note
.
type
===
'
DiscussionNote
'
)
{
const
discussion
=
utils
.
findNoteObjectById
(
context
.
state
.
notes
,
note
.
discussion_id
);
if
(
discussion
)
{
context
.
commit
(
'
addNewReplyToDiscussion
'
,
note
);
}
else
{
context
.
commit
(
'
addNewNote
'
,
note
);
}
}
else
{
context
.
commit
(
'
addNewNote
'
,
note
);
}
});
}
return
res
;
});
},
toggleAward
(
context
,
data
)
{
const
{
endpoint
,
awardName
,
noteId
,
skipMutalityCheck
}
=
data
;
const
note
=
context
.
getters
.
notesById
[
noteId
];
return
service
.
toggleAward
(
endpoint
,
{
name
:
awardName
})
.
then
(
res
=>
res
.
json
())
.
then
(()
=>
{
context
.
commit
(
'
toggleAward
'
,
{
awardName
,
note
});
if
(
!
skipMutalityCheck
&&
(
awardName
===
'
thumbsup
'
||
awardName
===
'
thumbsdown
'
))
{
const
counterAward
=
awardName
===
'
thumbsup
'
?
'
thumbsdown
'
:
'
thumbsup
'
;
const
targetNote
=
context
.
getters
.
notesById
[
noteId
];
let
amIAwarded
=
false
;
targetNote
.
award_emoji
.
forEach
((
a
)
=>
{
if
(
a
.
name
===
counterAward
&&
a
.
user
.
id
===
window
.
gon
.
current_user_id
)
{
amIAwarded
=
true
;
}
});
if
(
amIAwarded
)
{
data
.
awardName
=
counterAward
;
data
.
skipMutalityCheck
=
true
;
context
.
dispatch
(
'
toggleAward
'
,
data
);
}
}
});
},
scrollToNoteIfNeeded
(
context
,
el
)
{
const
isInViewport
=
gl
.
utils
.
isInViewport
(
el
[
0
]);
if
(
!
isInViewport
)
{
gl
.
utils
.
scrollToElement
(
el
);
}
},
};
export
default
{
state
,
getters
,
mutations
,
actions
,
};
app/assets/javascripts/notes/stores/issue_notes_utils.js
deleted
100644 → 0
View file @
fbdc02ad
import
AjaxCache
from
'
~/lib/utils/ajax_cache
'
;
const
REGEX_QUICK_ACTIONS
=
/^
\/\w
+.*$/gm
;
export
default
{
findNoteObjectById
(
notes
,
id
)
{
return
notes
.
filter
(
n
=>
n
.
id
===
id
)[
0
];
},
getQuickActionText
(
note
)
{
let
text
=
'
Applying command
'
;
const
quickActions
=
AjaxCache
.
get
(
gl
.
GfmAutoComplete
.
dataSources
.
commands
)
||
[];
const
executedCommands
=
quickActions
.
filter
((
command
)
=>
{
const
commandRegex
=
new
RegExp
(
`/
${
command
.
name
}
`
);
return
commandRegex
.
test
(
note
);
});
if
(
executedCommands
&&
executedCommands
.
length
)
{
if
(
executedCommands
.
length
>
1
)
{
text
=
'
Applying multiple commands
'
;
}
else
{
const
commandDescription
=
executedCommands
[
0
].
description
.
toLowerCase
();
text
=
`Applying command to
${
commandDescription
}
`
;
}
}
return
text
;
},
hasQuickActions
(
note
)
{
return
REGEX_QUICK_ACTIONS
.
test
(
note
);
},
stripQuickActions
(
note
)
{
return
note
.
replace
(
REGEX_QUICK_ACTIONS
,
''
).
trim
();
},
};
app/assets/javascripts/notes/stores/mutation_types.js
0 → 100644
View file @
cf5cc6a9
export
const
ADD_NEW_NOTE
=
'
ADD_NEW_NOTE
'
;
export
const
ADD_NEW_REPLY_TO_DISCUSSION
=
'
ADD_NEW_REPLY_TO_DISCUSSION
'
;
export
const
DELETE_NOTE
=
'
DELETE_NOTE
'
;
export
const
REMOVE_PLACEHOLDER_NOTES
=
'
REMOVE_PLACEHOLDER_NOTES
'
;
export
const
SET_INITAL_NOTES
=
'
SET_INITIAL_NOTES
'
;
export
const
SET_LAST_FETCHED_AT
=
'
SET_LAST_FETCHED_AT
'
;
export
const
SET_TARGET_NOTE_HASH
=
'
SET_TARGET_NOTE_HASH
'
;
export
const
SHOW_PLACEHOLDER_NOTE
=
'
SHOW_PLACEHOLDER_NOTE
'
;
export
const
TOGGLE_AWARD
=
'
TOGGLE_AWARD
'
;
export
const
TOGGLE_DISCUSSION
=
'
TOGGLE_DISCUSSION
'
;
export
const
UPDATE_NOTE
=
'
UPDATE_NOTE
'
;
app/assets/javascripts/notes/stores/mutations.js
0 → 100644
View file @
cf5cc6a9
import
*
as
utils
from
'
./utils
'
;
import
*
as
types
from
'
./mutation_types
'
;
export
default
{
[
types
.
ADD_NEW_NOTE
](
state
,
note
)
{
const
{
discussion_id
,
type
}
=
note
;
const
noteData
=
{
expanded
:
true
,
id
:
discussion_id
,
individual_note
:
!
(
type
===
'
DiscussionNote
'
),
notes
:
[
note
],
reply_id
:
discussion_id
,
};
state
.
notes
.
push
(
noteData
);
},
[
types
.
ADD_NEW_REPLY_TO_DISCUSSION
](
state
,
note
)
{
const
noteObj
=
utils
.
findNoteObjectById
(
state
.
notes
,
note
.
discussion_id
);
if
(
noteObj
)
{
noteObj
.
notes
.
push
(
note
);
}
},
[
types
.
DELETE_NOTE
](
state
,
note
)
{
const
noteObj
=
utils
.
findNoteObjectById
(
state
.
notes
,
note
.
discussion_id
);
if
(
noteObj
.
individual_note
)
{
state
.
notes
.
splice
(
state
.
notes
.
indexOf
(
noteObj
),
1
);
}
else
{
const
comment
=
utils
.
findNoteObjectById
(
noteObj
.
notes
,
note
.
id
);
noteObj
.
notes
.
splice
(
noteObj
.
notes
.
indexOf
(
comment
),
1
);
if
(
!
noteObj
.
notes
.
length
)
{
state
.
notes
.
splice
(
state
.
notes
.
indexOf
(
noteObj
),
1
);
}
}
},
[
types
.
REMOVE_PLACEHOLDER_NOTES
](
state
)
{
const
{
notes
}
=
state
;
for
(
let
i
=
notes
.
length
-
1
;
i
>=
0
;
i
-=
1
)
{
const
note
=
notes
[
i
];
const
children
=
note
.
notes
;
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
notes
.
splice
(
i
,
1
);
}
}
},
[
types
.
SET_INITAL_NOTES
](
state
,
notes
)
{
state
.
notes
=
notes
;
},
[
types
.
SET_LAST_FETCHED_AT
](
state
,
fetchedAt
)
{
state
.
lastFetchedAt
=
fetchedAt
;
},
[
types
.
SET_TARGET_NOTE_HASH
](
state
,
hash
)
{
state
.
targetNoteHash
=
hash
;
},
[
types
.
SHOW_PLACEHOLDER_NOTE
](
state
,
data
)
{
let
notesArr
=
state
.
notes
;
if
(
data
.
replyId
)
{
notesArr
=
utils
.
findNoteObjectById
(
notesArr
,
data
.
replyId
).
notes
;
}
notesArr
.
push
({
individual_note
:
true
,
isPlaceholderNote
:
true
,
placeholderType
:
data
.
isSystemNote
?
'
systemNote
'
:
'
note
'
,
notes
:
[
{
body
:
data
.
noteBody
,
},
],
});
},
[
types
.
TOGGLE_AWARD
](
state
,
data
)
{
const
{
awardName
,
note
}
=
data
;
const
{
id
,
name
,
username
}
=
window
.
gl
.
currentUserData
;
let
index
=
-
1
;
note
.
award_emoji
.
forEach
((
a
,
i
)
=>
{
if
(
a
.
name
===
awardName
&&
a
.
user
.
id
===
id
)
{
index
=
i
;
}
});
if
(
index
>
-
1
)
{
// if I am awarded, remove my award
note
.
award_emoji
.
splice
(
index
,
1
);
}
else
{
note
.
award_emoji
.
push
({
name
:
awardName
,
user
:
{
id
,
name
,
username
},
});
}
},
[
types
.
TOGGLE_DISCUSSION
](
state
,
{
discussionId
})
{
const
discussion
=
utils
.
findNoteObjectById
(
state
.
notes
,
discussionId
);
discussion
.
expanded
=
!
discussion
.
expanded
;
},
[
types
.
UPDATE_NOTE
](
state
,
note
)
{
const
noteObj
=
utils
.
findNoteObjectById
(
state
.
notes
,
note
.
discussion_id
);
if
(
noteObj
.
individual_note
)
{
noteObj
.
notes
.
splice
(
0
,
1
,
note
);
}
else
{
const
comment
=
utils
.
findNoteObjectById
(
noteObj
.
notes
,
note
.
id
);
noteObj
.
notes
.
splice
(
noteObj
.
notes
.
indexOf
(
comment
),
1
,
note
);
}
},
};
app/assets/javascripts/notes/stores/utils.js
0 → 100644
View file @
cf5cc6a9
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
getQuickActionText
=
(
note
)
=>
{
let
text
=
'
Applying command
'
;
const
quickActions
=
AjaxCache
.
get
(
gl
.
GfmAutoComplete
.
dataSources
.
commands
)
||
[];
const
executedCommands
=
quickActions
.
filter
((
command
)
=>
{
const
commandRegex
=
new
RegExp
(
`/
${
command
.
name
}
`
);
return
commandRegex
.
test
(
note
);
});
if
(
executedCommands
&&
executedCommands
.
length
)
{
if
(
executedCommands
.
length
>
1
)
{
text
=
'
Applying multiple commands
'
;
}
else
{
const
commandDescription
=
executedCommands
[
0
].
description
.
toLowerCase
();
text
=
`Applying command to
${
commandDescription
}
`
;
}
}
return
text
;
};
export
const
hasQuickActions
=
note
=>
REGEX_QUICK_ACTIONS
.
test
(
note
);
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