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
Léo-Paul Géneau
gitlab-ce
Commits
4edcb02f
Commit
4edcb02f
authored
Oct 04, 2018
by
Dennis Tang
Committed by
Phil Hughes
Oct 04, 2018
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Resolve "Add status message from within user menu"
parent
18777ec7
Changes
19
Hide whitespace changes
Inline
Side-by-side
Showing
19 changed files
with
730 additions
and
132 deletions
+730
-132
app/assets/javascripts/api.js
app/assets/javascripts/api.js
+10
-0
app/assets/javascripts/awards_handler.js
app/assets/javascripts/awards_handler.js
+18
-14
app/assets/javascripts/header.js
app/assets/javascripts/header.js
+55
-0
app/assets/javascripts/pages/profiles/show/index.js
app/assets/javascripts/pages/profiles/show/index.js
+2
-2
app/assets/javascripts/set_status_modal/emoji_menu_in_modal.js
...ssets/javascripts/set_status_modal/emoji_menu_in_modal.js
+21
-0
app/assets/javascripts/set_status_modal/event_hub.js
app/assets/javascripts/set_status_modal/event_hub.js
+3
-0
app/assets/javascripts/set_status_modal/set_status_modal_trigger.vue
...javascripts/set_status_modal/set_status_modal_trigger.vue
+33
-0
app/assets/javascripts/set_status_modal/set_status_modal_wrapper.vue
...javascripts/set_status_modal/set_status_modal_wrapper.vue
+241
-0
app/assets/stylesheets/framework/header.scss
app/assets/stylesheets/framework/header.scss
+35
-1
app/assets/stylesheets/framework/mixins.scss
app/assets/stylesheets/framework/mixins.scss
+56
-0
app/assets/stylesheets/framework/variables.scss
app/assets/stylesheets/framework/variables.scss
+2
-2
app/assets/stylesheets/pages/notes.scss
app/assets/stylesheets/pages/notes.scss
+1
-53
app/assets/stylesheets/pages/profile.scss
app/assets/stylesheets/pages/profile.scss
+4
-6
app/views/layouts/header/_current_user_dropdown.html.haml
app/views/layouts/header/_current_user_dropdown.html.haml
+7
-0
app/views/layouts/header/_default.html.haml
app/views/layouts/header/_default.html.haml
+3
-0
changelogs/unreleased/49075-add-status-message-from-within-user-menu.yml
...leased/49075-add-status-message-from-within-user-menu.yml
+5
-0
doc/user/profile/index.md
doc/user/profile/index.md
+7
-0
locale/gitlab.pot
locale/gitlab.pot
+27
-0
spec/features/profiles/user_edit_profile_spec.rb
spec/features/profiles/user_edit_profile_spec.rb
+200
-54
No files found.
app/assets/javascripts/api.js
View file @
4edcb02f
...
@@ -22,6 +22,7 @@ const Api = {
...
@@ -22,6 +22,7 @@ const Api = {
dockerfilePath
:
'
/api/:version/templates/dockerfiles/:key
'
,
dockerfilePath
:
'
/api/:version/templates/dockerfiles/:key
'
,
issuableTemplatePath
:
'
/:namespace_path/:project_path/templates/:type/:key
'
,
issuableTemplatePath
:
'
/:namespace_path/:project_path/templates/:type/:key
'
,
usersPath
:
'
/api/:version/users.json
'
,
usersPath
:
'
/api/:version/users.json
'
,
userStatusPath
:
'
/api/:version/user/status
'
,
commitPath
:
'
/api/:version/projects/:id/repository/commits
'
,
commitPath
:
'
/api/:version/projects/:id/repository/commits
'
,
commitPipelinesPath
:
'
/:project_id/commit/:sha/pipelines
'
,
commitPipelinesPath
:
'
/:project_id/commit/:sha/pipelines
'
,
branchSinglePath
:
'
/api/:version/projects/:id/repository/branches/:branch
'
,
branchSinglePath
:
'
/api/:version/projects/:id/repository/branches/:branch
'
,
...
@@ -266,6 +267,15 @@ const Api = {
...
@@ -266,6 +267,15 @@ const Api = {
});
});
},
},
postUserStatus
({
emoji
,
message
})
{
const
url
=
Api
.
buildUrl
(
this
.
userStatusPath
);
return
axios
.
put
(
url
,
{
emoji
,
message
,
});
},
templates
(
key
,
params
=
{})
{
templates
(
key
,
params
=
{})
{
const
url
=
Api
.
buildUrl
(
this
.
templatesPath
).
replace
(
'
:key
'
,
key
);
const
url
=
Api
.
buildUrl
(
this
.
templatesPath
).
replace
(
'
:key
'
,
key
);
...
...
app/assets/javascripts/awards_handler.js
View file @
4edcb02f
...
@@ -42,10 +42,11 @@ export class AwardsHandler {
...
@@ -42,10 +42,11 @@ export class AwardsHandler {
}
}
bindEvents
()
{
bindEvents
()
{
const
$parentEl
=
this
.
targetContainerEl
?
$
(
this
.
targetContainerEl
)
:
$
(
document
);
// If the user shows intent let's pre-build the menu
// If the user shows intent let's pre-build the menu
this
.
registerEventListener
(
this
.
registerEventListener
(
'
one
'
,
'
one
'
,
$
(
document
)
,
$
parentEl
,
'
mouseenter focus
'
,
'
mouseenter focus
'
,
this
.
toggleButtonSelector
,
this
.
toggleButtonSelector
,
'
mouseenter focus
'
,
'
mouseenter focus
'
,
...
@@ -58,7 +59,7 @@ export class AwardsHandler {
...
@@ -58,7 +59,7 @@ export class AwardsHandler {
}
}
},
},
);
);
this
.
registerEventListener
(
'
on
'
,
$
(
document
)
,
'
click
'
,
this
.
toggleButtonSelector
,
e
=>
{
this
.
registerEventListener
(
'
on
'
,
$
parentEl
,
'
click
'
,
this
.
toggleButtonSelector
,
e
=>
{
e
.
stopPropagation
();
e
.
stopPropagation
();
e
.
preventDefault
();
e
.
preventDefault
();
this
.
showEmojiMenu
(
$
(
e
.
currentTarget
));
this
.
showEmojiMenu
(
$
(
e
.
currentTarget
));
...
@@ -76,7 +77,7 @@ export class AwardsHandler {
...
@@ -76,7 +77,7 @@ export class AwardsHandler {
});
});
const
emojiButtonSelector
=
`.js-awards-block .js-emoji-btn, .
${
this
.
menuClass
}
.js-emoji-btn`
;
const
emojiButtonSelector
=
`.js-awards-block .js-emoji-btn, .
${
this
.
menuClass
}
.js-emoji-btn`
;
this
.
registerEventListener
(
'
on
'
,
$
(
document
)
,
'
click
'
,
emojiButtonSelector
,
e
=>
{
this
.
registerEventListener
(
'
on
'
,
$
parentEl
,
'
click
'
,
emojiButtonSelector
,
e
=>
{
e
.
preventDefault
();
e
.
preventDefault
();
const
$target
=
$
(
e
.
currentTarget
);
const
$target
=
$
(
e
.
currentTarget
);
const
$glEmojiElement
=
$target
.
find
(
'
gl-emoji
'
);
const
$glEmojiElement
=
$target
.
find
(
'
gl-emoji
'
);
...
@@ -168,7 +169,8 @@ export class AwardsHandler {
...
@@ -168,7 +169,8 @@ export class AwardsHandler {
</div>
</div>
`
;
`
;
document
.
body
.
insertAdjacentHTML
(
'
beforeend
'
,
emojiMenuMarkup
);
const
targetEl
=
this
.
targetContainerEl
?
this
.
targetContainerEl
:
document
.
body
;
targetEl
.
insertAdjacentHTML
(
'
beforeend
'
,
emojiMenuMarkup
);
this
.
addRemainingEmojiMenuCategories
();
this
.
addRemainingEmojiMenuCategories
();
this
.
setupSearch
();
this
.
setupSearch
();
...
@@ -250,6 +252,12 @@ export class AwardsHandler {
...
@@ -250,6 +252,12 @@ export class AwardsHandler {
}
}
positionMenu
(
$menu
,
$addBtn
)
{
positionMenu
(
$menu
,
$addBtn
)
{
if
(
this
.
targetContainerEl
)
{
return
$menu
.
css
({
top
:
`
${
$addBtn
.
outerHeight
()}
px`
,
});
}
const
position
=
$addBtn
.
data
(
'
position
'
);
const
position
=
$addBtn
.
data
(
'
position
'
);
// The menu could potentially be off-screen or in a hidden overflow element
// The menu could potentially be off-screen or in a hidden overflow element
// So we position the element absolute in the body
// So we position the element absolute in the body
...
@@ -424,9 +432,7 @@ export class AwardsHandler {
...
@@ -424,9 +432,7 @@ export class AwardsHandler {
users
=
origTitle
.
trim
().
split
(
FROM_SENTENCE_REGEX
);
users
=
origTitle
.
trim
().
split
(
FROM_SENTENCE_REGEX
);
}
}
users
.
unshift
(
'
You
'
);
users
.
unshift
(
'
You
'
);
return
awardBlock
return
awardBlock
.
attr
(
'
title
'
,
this
.
toSentence
(
users
)).
tooltip
(
'
_fixTitle
'
);
.
attr
(
'
title
'
,
this
.
toSentence
(
users
))
.
tooltip
(
'
_fixTitle
'
);
}
}
createAwardButtonForVotesBlock
(
votesBlock
,
emojiName
)
{
createAwardButtonForVotesBlock
(
votesBlock
,
emojiName
)
{
...
@@ -609,13 +615,11 @@ export class AwardsHandler {
...
@@ -609,13 +615,11 @@ export class AwardsHandler {
let
awardsHandlerPromise
=
null
;
let
awardsHandlerPromise
=
null
;
export
default
function
loadAwardsHandler
(
reload
=
false
)
{
export
default
function
loadAwardsHandler
(
reload
=
false
)
{
if
(
!
awardsHandlerPromise
||
reload
)
{
if
(
!
awardsHandlerPromise
||
reload
)
{
awardsHandlerPromise
=
import
(
/* webpackChunkName: 'emoji' */
'
./emoji
'
).
then
(
awardsHandlerPromise
=
import
(
/* webpackChunkName: 'emoji' */
'
./emoji
'
).
then
(
Emoji
=>
{
Emoji
=>
{
const
awardsHandler
=
new
AwardsHandler
(
Emoji
);
const
awardsHandler
=
new
AwardsHandler
(
Emoji
);
awardsHandler
.
bindEvents
();
awardsHandler
.
bindEvents
();
return
awardsHandler
;
return
awardsHandler
;
});
},
);
}
}
return
awardsHandlerPromise
;
return
awardsHandlerPromise
;
}
}
app/assets/javascripts/header.js
View file @
4edcb02f
import
$
from
'
jquery
'
;
import
$
from
'
jquery
'
;
import
Vue
from
'
vue
'
;
import
Translate
from
'
~/vue_shared/translate
'
;
import
{
highCountTrim
}
from
'
~/lib/utils/text_utility
'
;
import
{
highCountTrim
}
from
'
~/lib/utils/text_utility
'
;
import
SetStatusModalTrigger
from
'
./set_status_modal/set_status_modal_trigger.vue
'
;
import
SetStatusModalWrapper
from
'
./set_status_modal/set_status_modal_wrapper.vue
'
;
/**
/**
* Updates todo counter when todos are toggled.
* Updates todo counter when todos are toggled.
...
@@ -17,3 +21,54 @@ export default function initTodoToggle() {
...
@@ -17,3 +21,54 @@ export default function initTodoToggle() {
$todoPendingCount
.
toggleClass
(
'
hidden
'
,
parsedCount
===
0
);
$todoPendingCount
.
toggleClass
(
'
hidden
'
,
parsedCount
===
0
);
});
});
}
}
document
.
addEventListener
(
'
DOMContentLoaded
'
,
()
=>
{
const
setStatusModalTriggerEl
=
document
.
querySelector
(
'
.js-set-status-modal-trigger
'
);
const
setStatusModalWrapperEl
=
document
.
querySelector
(
'
.js-set-status-modal-wrapper
'
);
if
(
setStatusModalTriggerEl
||
setStatusModalWrapperEl
)
{
Vue
.
use
(
Translate
);
// eslint-disable-next-line no-new
new
Vue
({
el
:
setStatusModalTriggerEl
,
data
()
{
const
{
hasStatus
}
=
this
.
$options
.
el
.
dataset
;
return
{
hasStatus
:
hasStatus
===
'
true
'
,
};
},
render
(
createElement
)
{
return
createElement
(
SetStatusModalTrigger
,
{
props
:
{
hasStatus
:
this
.
hasStatus
,
},
});
},
});
// eslint-disable-next-line no-new
new
Vue
({
el
:
setStatusModalWrapperEl
,
data
()
{
const
{
currentEmoji
,
currentMessage
}
=
this
.
$options
.
el
.
dataset
;
return
{
currentEmoji
,
currentMessage
,
};
},
render
(
createElement
)
{
const
{
currentEmoji
,
currentMessage
}
=
this
;
return
createElement
(
SetStatusModalWrapper
,
{
props
:
{
currentEmoji
,
currentMessage
,
},
});
},
});
}
});
app/assets/javascripts/pages/profiles/show/index.js
View file @
4edcb02f
...
@@ -11,7 +11,7 @@ document.addEventListener('DOMContentLoaded', () => {
...
@@ -11,7 +11,7 @@ document.addEventListener('DOMContentLoaded', () => {
const
statusEmojiField
=
document
.
getElementById
(
'
js-status-emoji-field
'
);
const
statusEmojiField
=
document
.
getElementById
(
'
js-status-emoji-field
'
);
const
statusMessageField
=
document
.
getElementById
(
'
js-status-message-field
'
);
const
statusMessageField
=
document
.
getElementById
(
'
js-status-message-field
'
);
const
toggleNoEmojiPlaceholder
=
(
isVisible
)
=>
{
const
toggleNoEmojiPlaceholder
=
isVisible
=>
{
const
placeholderElement
=
document
.
getElementById
(
'
js-no-emoji-placeholder
'
);
const
placeholderElement
=
document
.
getElementById
(
'
js-no-emoji-placeholder
'
);
placeholderElement
.
classList
.
toggle
(
'
hidden
'
,
!
isVisible
);
placeholderElement
.
classList
.
toggle
(
'
hidden
'
,
!
isVisible
);
};
};
...
@@ -69,5 +69,5 @@ document.addEventListener('DOMContentLoaded', () => {
...
@@ -69,5 +69,5 @@ document.addEventListener('DOMContentLoaded', () => {
}
}
});
});
})
})
.
catch
(()
=>
createFlash
(
'
Failed to load emoji list
!
'
));
.
catch
(()
=>
createFlash
(
'
Failed to load emoji list
.
'
));
});
});
app/assets/javascripts/set_status_modal/emoji_menu_in_modal.js
0 → 100644
View file @
4edcb02f
import
{
AwardsHandler
}
from
'
~/awards_handler
'
;
class
EmojiMenuInModal
extends
AwardsHandler
{
constructor
(
emoji
,
toggleButtonSelector
,
menuClass
,
selectEmojiCallback
,
targetContainerEl
)
{
super
(
emoji
);
this
.
selectEmojiCallback
=
selectEmojiCallback
;
this
.
toggleButtonSelector
=
toggleButtonSelector
;
this
.
menuClass
=
menuClass
;
this
.
targetContainerEl
=
targetContainerEl
;
this
.
bindEvents
();
}
postEmoji
(
$emojiButton
,
awardUrl
,
selectedEmoji
,
callback
)
{
this
.
selectEmojiCallback
(
selectedEmoji
,
this
.
emoji
.
glEmojiTag
(
selectedEmoji
));
callback
();
}
}
export
default
EmojiMenuInModal
;
app/assets/javascripts/set_status_modal/event_hub.js
0 → 100644
View file @
4edcb02f
import
Vue
from
'
vue
'
;
export
default
new
Vue
();
app/assets/javascripts/set_status_modal/set_status_modal_trigger.vue
0 → 100644
View file @
4edcb02f
<
script
>
import
{
s__
}
from
'
~/locale
'
;
import
eventHub
from
'
./event_hub
'
;
export
default
{
props
:
{
hasStatus
:
{
type
:
Boolean
,
required
:
true
,
},
},
computed
:
{
buttonText
()
{
return
this
.
hasStatus
?
s__
(
'
SetStatusModal|Edit status
'
)
:
s__
(
'
SetStatusModal|Set status
'
);
},
},
methods
:
{
openModal
()
{
eventHub
.
$emit
(
'
openModal
'
);
},
},
};
</
script
>
<
template
>
<button
type=
"button"
class=
"btn menu-item"
@
click=
"openModal"
>
{{
buttonText
}}
</button>
</
template
>
app/assets/javascripts/set_status_modal/set_status_modal_wrapper.vue
0 → 100644
View file @
4edcb02f
<
script
>
import
$
from
'
jquery
'
;
import
createFlash
from
'
~/flash
'
;
import
Icon
from
'
~/vue_shared/components/icon.vue
'
;
import
GfmAutoComplete
from
'
~/gfm_auto_complete
'
;
import
{
__
,
s__
}
from
'
~/locale
'
;
import
Api
from
'
~/api
'
;
import
eventHub
from
'
./event_hub
'
;
import
EmojiMenuInModal
from
'
./emoji_menu_in_modal
'
;
const
emojiMenuClass
=
'
js-modal-status-emoji-menu
'
;
export
default
{
components
:
{
Icon
,
},
props
:
{
currentEmoji
:
{
type
:
String
,
required
:
true
,
},
currentMessage
:
{
type
:
String
,
required
:
true
,
},
},
data
()
{
return
{
defaultEmojiTag
:
''
,
emoji
:
this
.
currentEmoji
,
emojiMenu
:
null
,
emojiTag
:
''
,
isEmojiMenuVisible
:
false
,
message
:
this
.
currentMessage
,
modalId
:
'
set-user-status-modal
'
,
noEmoji
:
true
,
};
},
computed
:
{
isDirty
()
{
return
this
.
message
.
length
||
this
.
emoji
.
length
;
},
},
mounted
()
{
eventHub
.
$on
(
'
openModal
'
,
this
.
openModal
);
},
beforeDestroy
()
{
this
.
emojiMenu
.
destroy
();
},
methods
:
{
openModal
()
{
this
.
$root
.
$emit
(
'
bv::show::modal
'
,
this
.
modalId
);
},
closeModal
()
{
this
.
$root
.
$emit
(
'
bv::hide::modal
'
,
this
.
modalId
);
},
setupEmojiListAndAutocomplete
()
{
const
toggleEmojiMenuButtonSelector
=
'
#set-user-status-modal .js-toggle-emoji-menu
'
;
const
emojiAutocomplete
=
new
GfmAutoComplete
();
emojiAutocomplete
.
setup
(
$
(
this
.
$refs
.
statusMessageField
),
{
emojis
:
true
});
import
(
/* webpackChunkName: 'emoji' */
'
~/emoji
'
)
.
then
(
Emoji
=>
{
if
(
this
.
emoji
)
{
this
.
emojiTag
=
Emoji
.
glEmojiTag
(
this
.
emoji
);
}
this
.
noEmoji
=
this
.
emoji
===
''
;
this
.
defaultEmojiTag
=
Emoji
.
glEmojiTag
(
'
speech_balloon
'
);
this
.
emojiMenu
=
new
EmojiMenuInModal
(
Emoji
,
toggleEmojiMenuButtonSelector
,
emojiMenuClass
,
this
.
setEmoji
,
this
.
$refs
.
userStatusForm
,
);
})
.
catch
(()
=>
createFlash
(
__
(
'
Failed to load emoji list.
'
)));
},
showEmojiMenu
()
{
this
.
isEmojiMenuVisible
=
true
;
this
.
emojiMenu
.
showEmojiMenu
(
$
(
this
.
$refs
.
toggleEmojiMenuButton
));
},
hideEmojiMenu
()
{
if
(
!
this
.
isEmojiMenuVisible
)
{
return
;
}
this
.
isEmojiMenuVisible
=
false
;
this
.
emojiMenu
.
hideMenuElement
(
$
(
`.
${
emojiMenuClass
}
`
));
},
setDefaultEmoji
()
{
const
{
emojiTag
}
=
this
;
const
hasStatusMessage
=
this
.
message
;
if
(
hasStatusMessage
&&
emojiTag
)
{
return
;
}
if
(
hasStatusMessage
)
{
this
.
noEmoji
=
false
;
this
.
emojiTag
=
this
.
defaultEmojiTag
;
}
else
if
(
emojiTag
===
this
.
defaultEmojiTag
)
{
this
.
noEmoji
=
true
;
this
.
clearEmoji
();
}
},
setEmoji
(
emoji
,
emojiTag
)
{
this
.
emoji
=
emoji
;
this
.
noEmoji
=
false
;
this
.
clearEmoji
();
this
.
emojiTag
=
emojiTag
;
},
clearEmoji
()
{
if
(
this
.
emojiTag
)
{
this
.
emojiTag
=
''
;
}
},
clearStatusInputs
()
{
this
.
emoji
=
''
;
this
.
message
=
''
;
this
.
noEmoji
=
true
;
this
.
clearEmoji
();
this
.
hideEmojiMenu
();
},
removeStatus
()
{
this
.
clearStatusInputs
();
this
.
setStatus
();
},
setStatus
()
{
const
{
emoji
,
message
}
=
this
;
Api
.
postUserStatus
({
emoji
,
message
,
})
.
then
(
this
.
onUpdateSuccess
)
.
catch
(
this
.
onUpdateFail
);
},
onUpdateSuccess
()
{
this
.
closeModal
();
window
.
location
.
reload
();
},
onUpdateFail
()
{
createFlash
(
s__
(
"
SetStatusModal|Sorry, we weren't able to set your status. Please try again later.
"
),
);
this
.
closeModal
();
},
},
};
</
script
>
<
template
>
<gl-ui-modal
:title=
"s__('SetStatusModal|Set a status')"
:modal-id=
"modalId"
:ok-title=
"s__('SetStatusModal|Set status')"
:cancel-title=
"s__('SetStatusModal|Remove status')"
ok-variant=
"success"
class=
"set-user-status-modal"
@
shown=
"setupEmojiListAndAutocomplete"
@
hide=
"hideEmojiMenu"
@
ok=
"setStatus"
@
cancel=
"removeStatus"
>
<div>
<input
v-model=
"emoji"
class=
"js-status-emoji-field"
type=
"hidden"
name=
"user[status][emoji]"
/>
<div
ref=
"userStatusForm"
class=
"form-group position-relative m-0"
>
<div
class=
"input-group"
>
<span
class=
"input-group-btn"
>
<button
ref=
"toggleEmojiMenuButton"
v-gl-tooltip
.
bottom
:title=
"s__('SetStatusModal|Add status emoji')"
:aria-label=
"s__('SetStatusModal|Add status emoji')"
name=
"button"
type=
"button"
class=
"js-toggle-emoji-menu emoji-menu-toggle-button btn"
@
click=
"showEmojiMenu"
>
<span
v-html=
"emojiTag"
></span>
<span
v-show=
"noEmoji"
class=
"js-no-emoji-placeholder no-emoji-placeholder position-relative"
>
<icon
name=
"emoji_slightly_smiling_face"
css-classes=
"award-control-icon-neutral"
/>
<icon
name=
"emoji_smiley"
css-classes=
"award-control-icon-positive"
/>
<icon
name=
"emoji_smile"
css-classes=
"award-control-icon-super-positive"
/>
</span>
</button>
</span>
<input
ref=
"statusMessageField"
v-model=
"message"
:placeholder=
"s__('SetStatusModal|What\'s your status?')"
type=
"text"
class=
"form-control form-control input-lg js-status-message-field"
name=
"user[status][message]"
@
keyup=
"setDefaultEmoji"
@
keyup
.
enter
.
prevent
@
click=
"hideEmojiMenu"
/>
<span
v-show=
"isDirty"
class=
"input-group-btn"
>
<button
v-gl-tooltip
.
bottom
:title=
"s__('SetStatusModal|Clear status')"
:aria-label=
"s__('SetStatusModal|Clear status')"
name=
"button"
type=
"button"
class=
"js-clear-user-status-button clear-user-status btn"
@
click=
"clearStatusInputs()"
>
<icon
name=
"close"
/>
</button>
</span>
</div>
</div>
</div>
</gl-ui-modal>
</
template
>
app/assets/stylesheets/framework/header.scss
View file @
4edcb02f
...
@@ -529,9 +529,10 @@
...
@@ -529,9 +529,10 @@
}
}
.header-user
{
.header-user
{
.dropdown-menu
{
&
.show
.dropdown-menu
{
width
:
auto
;
width
:
auto
;
min-width
:
unset
;
min-width
:
unset
;
max-height
:
323px
;
margin-top
:
4px
;
margin-top
:
4px
;
color
:
$gl-text-color
;
color
:
$gl-text-color
;
left
:
auto
;
left
:
auto
;
...
@@ -542,6 +543,18 @@
...
@@ -542,6 +543,18 @@
.user-name
{
.user-name
{
display
:
block
;
display
:
block
;
}
}
.user-status-emoji
{
margin-right
:
0
;
display
:
block
;
vertical-align
:
text-top
;
max-width
:
148px
;
font-size
:
12px
;
gl-emoji
{
font-size
:
$gl-font-size
;
}
}
}
}
svg
{
svg
{
...
@@ -573,3 +586,24 @@
...
@@ -573,3 +586,24 @@
}
}
}
}
}
}
.set-user-status-modal
{
.modal-body
{
min-height
:
unset
;
}
.input-lg
{
max-width
:
unset
;
}
.no-emoji-placeholder
,
.clear-user-status
{
svg
{
fill
:
$gl-text-color-secondary
;
}
}
.emoji-menu-toggle-button
{
@include
emoji-menu-toggle-button
;
}
}
app/assets/stylesheets/framework/mixins.scss
View file @
4edcb02f
...
@@ -266,3 +266,59 @@
...
@@ -266,3 +266,59 @@
border-radius
:
50%
;
border-radius
:
50%
;
}
}
}
}
@mixin
emoji-menu-toggle-button
{
line-height
:
1
;
padding
:
0
;
min-width
:
16px
;
color
:
$gray-darkest
;
fill
:
$gray-darkest
;
.fa
{
position
:
relative
;
font-size
:
16px
;
}
svg
{
@include
btn-svg
;
margin
:
0
;
}
.award-control-icon-positive
,
.award-control-icon-super-positive
{
position
:
absolute
;
top
:
0
;
left
:
0
;
opacity
:
0
;
}
&
:hover
,
&
.is-active
{
.danger-highlight
{
color
:
$red-500
;
}
.link-highlight
{
color
:
$blue-600
;
fill
:
$blue-600
;
}
.award-control-icon-neutral
{
opacity
:
0
;
}
.award-control-icon-positive
{
opacity
:
1
;
}
}
&
.is-active
{
.award-control-icon-positive
{
opacity
:
0
;
}
.award-control-icon-super-positive
{
opacity
:
1
;
}
}
}
app/assets/stylesheets/framework/variables.scss
View file @
4edcb02f
...
@@ -314,7 +314,8 @@ $diff-jagged-border-gradient-color: darken($white-normal, 8%);
...
@@ -314,7 +314,8 @@ $diff-jagged-border-gradient-color: darken($white-normal, 8%);
$monospace-font
:
'Menlo'
,
'DejaVu Sans Mono'
,
'Liberation Mono'
,
'Consolas'
,
'Ubuntu Mono'
,
$monospace-font
:
'Menlo'
,
'DejaVu Sans Mono'
,
'Liberation Mono'
,
'Consolas'
,
'Ubuntu Mono'
,
'Courier New'
,
'andale mono'
,
'lucida console'
,
monospace
;
'Courier New'
,
'andale mono'
,
'lucida console'
,
monospace
;
$regular-font
:
-
apple-system
,
BlinkMacSystemFont
,
'Segoe UI'
,
Roboto
,
Oxygen-Sans
,
Ubuntu
,
Cantarell
,
$regular-font
:
-
apple-system
,
BlinkMacSystemFont
,
'Segoe UI'
,
Roboto
,
Oxygen-Sans
,
Ubuntu
,
Cantarell
,
'Helvetica Neue'
,
sans-serif
,
'Apple Color Emoji'
,
'Segoe UI Emoji'
,
'Segoe UI Symbol'
,
'Noto Color Emoji'
;
'Helvetica Neue'
,
sans-serif
,
'Apple Color Emoji'
,
'Segoe UI Emoji'
,
'Segoe UI Symbol'
,
'Noto Color Emoji'
;
/*
/*
* Dropdowns
* Dropdowns
...
@@ -634,5 +635,4 @@ Modals
...
@@ -634,5 +635,4 @@ Modals
*/
*/
$modal-body-height
:
134px
;
$modal-body-height
:
134px
;
$priority-label-empty-state-width
:
114px
;
$priority-label-empty-state-width
:
114px
;
app/assets/stylesheets/pages/notes.scss
View file @
4edcb02f
...
@@ -519,59 +519,7 @@ ul.notes {
...
@@ -519,59 +519,7 @@ ul.notes {
}
}
.note-action-button
{
.note-action-button
{
line-height
:
1
;
@include
emoji-menu-toggle-button
;
padding
:
0
;
min-width
:
16px
;
color
:
$gray-darkest
;
fill
:
$gray-darkest
;
.fa
{
position
:
relative
;
font-size
:
16px
;
}
svg
{
@include
btn-svg
;
margin
:
0
;
}
.award-control-icon-positive
,
.award-control-icon-super-positive
{
position
:
absolute
;
top
:
0
;
left
:
0
;
opacity
:
0
;
}
&
:hover
,
&
.is-active
{
.danger-highlight
{
color
:
$red-500
;
}
.link-highlight
{
color
:
$blue-600
;
fill
:
$blue-600
;
}
.award-control-icon-neutral
{
opacity
:
0
;
}
.award-control-icon-positive
{
opacity
:
1
;
}
}
&
.is-active
{
.award-control-icon-positive
{
opacity
:
0
;
}
.award-control-icon-super-positive
{
opacity
:
1
;
}
}
}
}
.discussion-toggle-button
{
.discussion-toggle-button
{
...
...
app/assets/stylesheets/pages/profile.scss
View file @
4edcb02f
...
@@ -81,14 +81,14 @@
...
@@ -81,14 +81,14 @@
// Middle dot divider between each element in a list of items.
// Middle dot divider between each element in a list of items.
.middle-dot-divider
{
.middle-dot-divider
{
&
:
:
after
{
&
:
:
after
{
content
:
"\00B7"
;
// Middle Dot
content
:
'\00B7'
;
// Middle Dot
padding
:
0
6px
;
padding
:
0
6px
;
font-weight
:
$gl-font-weight-bold
;
font-weight
:
$gl-font-weight-bold
;
}
}
&
:last-child
{
&
:last-child
{
&
:
:
after
{
&
:
:
after
{
content
:
""
;
content
:
''
;
padding
:
0
;
padding
:
0
;
}
}
}
}
...
@@ -191,7 +191,6 @@
...
@@ -191,7 +191,6 @@
@include
media-breakpoint-down
(
xs
)
{
@include
media-breakpoint-down
(
xs
)
{
width
:
auto
;
width
:
auto
;
}
}
}
}
.profile-crop-image-container
{
.profile-crop-image-container
{
...
@@ -215,7 +214,6 @@
...
@@ -215,7 +214,6 @@
}
}
}
}
.user-profile
{
.user-profile
{
.cover-controls
a
{
.cover-controls
a
{
margin-left
:
5px
;
margin-left
:
5px
;
...
@@ -418,7 +416,7 @@ table.u2f-registrations {
...
@@ -418,7 +416,7 @@ table.u2f-registrations {
}
}
&
.unverified
{
&
.unverified
{
@include
status-color
(
$gray-dark
,
color
(
"gray"
)
,
$common-gray-dark
);
@include
status-color
(
$gray-dark
,
color
(
'gray'
)
,
$common-gray-dark
);
}
}
}
}
}
}
...
@@ -431,7 +429,7 @@ table.u2f-registrations {
...
@@ -431,7 +429,7 @@ table.u2f-registrations {
}
}
.emoji-menu-toggle-button
{
.emoji-menu-toggle-button
{
@
extend
.note-action
-button
;
@
include
emoji-menu-toggle
-button
;
.no-emoji-placeholder
{
.no-emoji-placeholder
{
position
:
relative
;
position
:
relative
;
...
...
app/views/layouts/header/_current_user_dropdown.html.haml
View file @
4edcb02f
...
@@ -5,7 +5,14 @@
...
@@ -5,7 +5,14 @@
.user-name.bold
.user-name.bold
=
current_user
.
name
=
current_user
.
name
=
current_user
.
to_reference
=
current_user
.
to_reference
-
if
current_user
.
status
.user-status-emoji.str-truncated.has-tooltip
{
title:
current_user
.
status
.
message_html
,
data:
{
html:
'true'
,
placement:
'bottom'
}
}
=
emoji_icon
current_user
.
status
.
emoji
=
current_user
.
status
.
message_html
.
html_safe
%li
.divider
%li
.divider
-
if
can?
(
current_user
,
:update_user_status
,
current_user
)
%li
.js-set-status-modal-trigger
{
data:
{
has_status:
current_user
.
status
.
present?
?
'true'
:
'false'
}
}
-
if
current_user_menu?
(
:profile
)
-
if
current_user_menu?
(
:profile
)
%li
%li
=
link_to
s_
(
"CurrentUser|Profile"
),
current_user
,
class:
'profile-link'
,
data:
{
user:
current_user
.
username
}
=
link_to
s_
(
"CurrentUser|Profile"
),
current_user
,
class:
'profile-link'
,
data:
{
user:
current_user
.
username
}
...
...
app/views/layouts/header/_default.html.haml
View file @
4edcb02f
...
@@ -74,3 +74,6 @@
...
@@ -74,3 +74,6 @@
%span
.sr-only
=
_
(
'Toggle navigation'
)
%span
.sr-only
=
_
(
'Toggle navigation'
)
=
sprite_icon
(
'ellipsis_h'
,
size:
12
,
css_class:
'more-icon js-navbar-toggle-right'
)
=
sprite_icon
(
'ellipsis_h'
,
size:
12
,
css_class:
'more-icon js-navbar-toggle-right'
)
=
sprite_icon
(
'close'
,
size:
12
,
css_class:
'close-icon js-navbar-toggle-left'
)
=
sprite_icon
(
'close'
,
size:
12
,
css_class:
'close-icon js-navbar-toggle-left'
)
-
if
can?
(
current_user
,
:update_user_status
,
current_user
)
.js-set-status-modal-wrapper
{
data:
{
current_emoji:
current_user
.
status
.
present?
?
current_user
.
status
.
emoji
:
''
,
current_message:
current_user
.
status
.
present?
?
current_user
.
status
.
message
:
''
}
}
changelogs/unreleased/49075-add-status-message-from-within-user-menu.yml
0 → 100644
View file @
4edcb02f
---
title
:
Set user status from within user menu
merge_request
:
21643
author
:
type
:
added
doc/user/profile/index.md
View file @
4edcb02f
...
@@ -115,6 +115,13 @@ Please be aware that your status is publicly visible even if your [profile is pr
...
@@ -115,6 +115,13 @@ Please be aware that your status is publicly visible even if your [profile is pr
To set your current status:
To set your current status:
1.
Open the user menu in the top-right corner of the navigation bar.
1.
Hit
**Set status**
, or
**Edit status**
if you have already set a status.
1.
Set the emoji and/or status message to your liking.
1.
Hit
**Set status**
. Alternatively, you can also hit
**Remove status**
to remove your user status entirely.
or
1.
Navigate to your personal
[
profile settings
](
#profile-settings
)
.
1.
Navigate to your personal
[
profile settings
](
#profile-settings
)
.
1.
In the text field below
`Your status`
, enter your status message.
1.
In the text field below
`Your status`
, enter your status message.
1.
Select an emoji from the dropdown if you like.
1.
Select an emoji from the dropdown if you like.
...
...
locale/gitlab.pot
View file @
4edcb02f
...
@@ -2722,6 +2722,9 @@ msgstr ""
...
@@ -2722,6 +2722,9 @@ msgstr ""
msgid "Failed to check related branches."
msgid "Failed to check related branches."
msgstr ""
msgstr ""
msgid "Failed to load emoji list."
msgstr ""
msgid "Failed to remove issue from board, please try again."
msgid "Failed to remove issue from board, please try again."
msgstr ""
msgstr ""
...
@@ -5445,6 +5448,30 @@ msgstr ""
...
@@ -5445,6 +5448,30 @@ msgstr ""
msgid "SetPasswordToCloneLink|set a password"
msgid "SetPasswordToCloneLink|set a password"
msgstr ""
msgstr ""
msgid "SetStatusModal|Add status emoji"
msgstr ""
msgid "SetStatusModal|Clear status"
msgstr ""
msgid "SetStatusModal|Edit status"
msgstr ""
msgid "SetStatusModal|Remove status"
msgstr ""
msgid "SetStatusModal|Set a status"
msgstr ""
msgid "SetStatusModal|Set status"
msgstr ""
msgid "SetStatusModal|Sorry, we weren't able to set your status. Please try again later."
msgstr ""
msgid "SetStatusModal|What's your status?"
msgstr ""
msgid "Settings"
msgid "Settings"
msgstr ""
msgstr ""
...
...
spec/features/profiles/user_edit_profile_spec.rb
View file @
4edcb02f
...
@@ -61,83 +61,229 @@ describe 'User edit profile' do
...
@@ -61,83 +61,229 @@ describe 'User edit profile' do
end
end
context
'user status'
,
:js
do
context
'user status'
,
:js
do
def
select_emoji
(
emoji_name
)
def
select_emoji
(
emoji_name
,
is_modal
=
false
)
emoji_menu_class
=
is_modal
?
'.js-modal-status-emoji-menu'
:
'.js-status-emoji-menu'
toggle_button
=
find
(
'.js-toggle-emoji-menu'
)
toggle_button
=
find
(
'.js-toggle-emoji-menu'
)
toggle_button
.
click
toggle_button
.
click
emoji_button
=
find
(
%Q{
.js-status-emoji-menu
.js-emoji-btn gl-emoji[data-name="
#{
emoji_name
}
"]}
)
emoji_button
=
find
(
%Q{
#{
emoji_menu_class
}
.js-emoji-btn gl-emoji[data-name="
#{
emoji_name
}
"]}
)
emoji_button
.
click
emoji_button
.
click
end
end
it
'shows the user status form'
do
context
'profile edit form'
do
visit
(
profile_path
)
it
'shows the user status form'
do
visit
(
profile_path
)
expect
(
page
).
to
have_content
(
'Current status'
)
expect
(
page
).
to
have_content
(
'Current status'
)
end
end
it
'adds emoji to user status'
do
it
'adds emoji to user status'
do
emoji
=
'biohazard'
emoji
=
'biohazard'
visit
(
profile_path
)
visit
(
profile_path
)
select_emoji
(
emoji
)
select_emoji
(
emoji
)
submit_settings
submit_settings
visit
user_path
(
user
)
visit
user_path
(
user
)
within
(
'.cover-status'
)
do
within
(
'.cover-status'
)
do
expect
(
page
).
to
have_emoji
(
emoji
)
expect
(
page
).
to
have_emoji
(
emoji
)
end
end
end
end
it
'adds message to user status'
do
it
'adds message to user status'
do
message
=
'I have something to say'
message
=
'I have something to say'
visit
(
profile_path
)
visit
(
profile_path
)
fill_in
'js-status-message-field'
,
with:
message
fill_in
'js-status-message-field'
,
with:
message
submit_settings
submit_settings
visit
user_path
(
user
)
visit
user_path
(
user
)
within
(
'.cover-status'
)
do
within
(
'.cover-status'
)
do
expect
(
page
).
to
have_emoji
(
'speech_balloon'
)
expect
(
page
).
to
have_emoji
(
'speech_balloon'
)
expect
(
page
).
to
have_content
message
expect
(
page
).
to
have_content
message
end
end
end
end
it
'adds message and emoji to user status'
do
it
'adds message and emoji to user status'
do
emoji
=
'tanabata_tree'
emoji
=
'tanabata_tree'
message
=
'Playing outside'
message
=
'Playing outside'
visit
(
profile_path
)
visit
(
profile_path
)
select_emoji
(
emoji
)
select_emoji
(
emoji
)
fill_in
'js-status-message-field'
,
with:
message
fill_in
'js-status-message-field'
,
with:
message
submit_settings
submit_settings
visit
user_path
(
user
)
visit
user_path
(
user
)
within
(
'.cover-status'
)
do
within
(
'.cover-status'
)
do
expect
(
page
).
to
have_emoji
(
emoji
)
expect
(
page
).
to
have_emoji
(
emoji
)
expect
(
page
).
to
have_content
message
expect
(
page
).
to
have_content
message
end
end
end
end
it
'clears the user status'
do
it
'clears the user status'
do
user_status
=
create
(
:user_status
,
user:
user
,
message:
'Eating bread'
,
emoji:
'stuffed_flatbread'
)
user_status
=
create
(
:user_status
,
user:
user
,
message:
'Eating bread'
,
emoji:
'stuffed_flatbread'
)
visit
user_path
(
user
)
within
(
'.cover-status'
)
do
expect
(
page
).
to
have_emoji
(
user_status
.
emoji
)
expect
(
page
).
to
have_content
user_status
.
message
end
visit
(
profile_path
)
click_button
'js-clear-user-status-button'
submit_settings
visit
user_path
(
user
)
visit
user_path
(
user
)
within
(
'.cover-status'
)
do
expect
(
page
).
not_to
have_selector
'.cover-status'
expect
(
page
).
to
have_emoji
(
user_status
.
emoji
)
expect
(
page
).
to
have_content
user_status
.
message
end
end
visit
(
profile_path
)
it
'displays a default emoji if only message is entered'
do
click_button
'js-clear-user-status-button'
message
=
'a status without emoji'
submit_settings
visit
(
profile_path
)
fill_in
'js-status-message-field'
,
with:
message
visit
user_path
(
user
)
within
(
'.js-toggle-emoji-menu'
)
do
expect
(
page
).
not_to
have_selector
'.cover-status'
expect
(
page
).
to
have_emoji
(
'speech_balloon'
)
end
end
end
end
it
'displays a default emoji if only message is entered'
do
context
'user menu'
do
message
=
'a status without emoji'
def
open_user_status_modal
visit
(
profile_path
)
find
(
'.header-user-dropdown-toggle'
).
click
fill_in
'js-status-message-field'
,
with:
message
page
.
within
".header-user"
do
click_button
'Set status'
end
end
def
set_user_status_in_modal
page
.
within
"#set-user-status-modal"
do
click_button
'Set status'
end
end
before
do
visit
root_path
(
user
)
end
it
'shows the "Set status" menu item in the user menu'
do
find
(
'.header-user-dropdown-toggle'
).
click
page
.
within
".header-user"
do
expect
(
page
).
to
have_content
(
'Set status'
)
end
end
it
'shows the "Edit status" menu item in the user menu'
do
user_status
=
create
(
:user_status
,
user:
user
,
message:
'Eating bread'
,
emoji:
'stuffed_flatbread'
)
visit
root_path
(
user
)
find
(
'.header-user-dropdown-toggle'
).
click
page
.
within
".header-user"
do
expect
(
page
).
to
have_emoji
(
user_status
.
emoji
)
expect
(
page
).
to
have_content
user_status
.
message
expect
(
page
).
to
have_content
(
'Edit status'
)
end
end
it
'shows user status modal'
do
open_user_status_modal
expect
(
page
.
find
(
'#set-user-status-modal'
)).
to
be_visible
expect
(
page
).
to
have_content
(
'Set a status'
)
end
it
'adds emoji to user status'
do
emoji
=
'biohazard'
open_user_status_modal
select_emoji
(
emoji
,
true
)
set_user_status_in_modal
visit
user_path
(
user
)
within
(
'.cover-status'
)
do
expect
(
page
).
to
have_emoji
(
emoji
)
end
end
it
'adds message to user status'
do
message
=
'I have something to say'
open_user_status_modal
find
(
'.js-status-message-field'
).
native
.
send_keys
(
message
)
set_user_status_in_modal
visit
user_path
(
user
)
within
(
'.cover-status'
)
do
expect
(
page
).
to
have_emoji
(
'speech_balloon'
)
expect
(
page
).
to
have_content
message
end
end
it
'adds message and emoji to user status'
do
emoji
=
'tanabata_tree'
message
=
'Playing outside'
open_user_status_modal
select_emoji
(
emoji
,
true
)
find
(
'.js-status-message-field'
).
native
.
send_keys
(
message
)
set_user_status_in_modal
visit
user_path
(
user
)
within
(
'.cover-status'
)
do
expect
(
page
).
to
have_emoji
(
emoji
)
expect
(
page
).
to
have_content
message
end
end
it
'clears the user status with the "X" button'
do
user_status
=
create
(
:user_status
,
user:
user
,
message:
'Eating bread'
,
emoji:
'stuffed_flatbread'
)
visit
user_path
(
user
)
within
(
'.cover-status'
)
do
expect
(
page
).
to
have_emoji
(
user_status
.
emoji
)
expect
(
page
).
to
have_content
user_status
.
message
end
find
(
'.header-user-dropdown-toggle'
).
click
page
.
within
".header-user"
do
click_button
'Edit status'
end
find
(
'.js-clear-user-status-button'
).
click
set_user_status_in_modal
visit
user_path
(
user
)
expect
(
page
).
not_to
have_selector
'.cover-status'
end
it
'clears the user status with the "Remove status" button'
do
user_status
=
create
(
:user_status
,
user:
user
,
message:
'Eating bread'
,
emoji:
'stuffed_flatbread'
)
visit
user_path
(
user
)
within
(
'.cover-status'
)
do
expect
(
page
).
to
have_emoji
(
user_status
.
emoji
)
expect
(
page
).
to
have_content
user_status
.
message
end
find
(
'.header-user-dropdown-toggle'
).
click
page
.
within
".header-user"
do
click_button
'Edit status'
end
page
.
within
"#set-user-status-modal"
do
click_button
'Remove status'
end
visit
user_path
(
user
)
expect
(
page
).
not_to
have_selector
'.cover-status'
end
it
'displays a default emoji if only message is entered'
do
message
=
'a status without emoji'
open_user_status_modal
find
(
'.js-status-message-field'
).
native
.
send_keys
(
message
)
within
(
'.js-toggle-emoji-menu'
)
do
within
(
'.js-toggle-emoji-menu'
)
do
expect
(
page
).
to
have_emoji
(
'speech_balloon'
)
expect
(
page
).
to
have_emoji
(
'speech_balloon'
)
end
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