Page MenuHomePhorge

No OneTemporary

Authored By
Unknown
Size
19 KB
Referenced Files
None
Subscribers
None
diff --git a/src/components/emoji_picker/emoji_picker.scss b/src/components/emoji_picker/emoji_picker.scss
index 12c09388f9..45290a89b4 100644
--- a/src/components/emoji_picker/emoji_picker.scss
+++ b/src/components/emoji_picker/emoji_picker.scss
@@ -1,214 +1,225 @@
.emoji-picker {
--__emoji-picker-header: 2.2em;
width: 25em;
max-width: calc(100vw - 20px); // popover gives 10px margin from window edge
display: flex;
flex-direction: column;
&-header-image {
display: inline-flex;
justify-content: center;
align-items: center;
width: var(--__emoji-picker-header);
max-width: var(--__emoji-picker-header);
height: var(--__emoji-picker-header);
max-height: var(--__emoji-picker-header);
.still-image {
width: var(--__emoji-picker-header);
max-width: var(--__emoji-picker-header);
height: var(--__emoji-picker-header);
max-height: var(--__emoji-picker-header);
object-fit: contain;
--_still_image-label-scale: 0.5;
}
}
.keep-open,
.too-many-emoji,
.hide-custom-emoji {
padding: 0.5em;
line-height: normal;
}
.hide-custom-emoji {
padding-top: 0;
}
.too-many-emoji {
display: flex;
flex-direction: column;
}
.keep-open-label {
padding: 0 0.5em;
display: flex;
}
.heading {
display: flex;
padding: 0.7em 0.5em 0;
}
.content {
display: flex;
flex-direction: column;
flex: 1 1 auto;
min-height: 0;
}
.emoji-tabs {
flex-grow: 1;
display: flex;
flex-flow: row nowrap;
overflow-x: auto;
overflow-y: hidden;
}
.additional-tabs {
display: flex;
border-left: 1px solid;
border-left-color: var(--border);
padding-left: 0.5em;
flex: 0 0 auto;
}
.additional-tabs,
.emoji-tabs {
flex-basis: auto;
display: flex;
align-content: center;
scrollbar-width: thin;
&-item {
padding: 0 0.5em;
cursor: pointer;
width: var(--__emoji-picker-header);
max-width: var(--__emoji-picker-header);
height: var(--__emoji-picker-header);
max-height: var(--__emoji-picker-header);
display: flex;
align-items: center;
.svg-inline--fa {
font-size: 1.85em;
}
&.disabled {
opacity: 0.5;
pointer-events: none;
}
&.toggled {
border-bottom: 0.2em solid;
}
}
}
.sticker-picker {
flex: 1 1 auto;
}
.stickers,
.emoji {
&-content {
display: flex;
flex-direction: column;
flex: 1 1 auto;
min-height: 0;
&.hidden {
opacity: 0;
pointer-events: none;
position: absolute;
}
}
}
.emoji {
&-search {
padding: 0.3em;
flex: 0 0 auto;
input {
width: 100%;
}
}
&-groups {
height: 100%;
min-height: 200px;
flex: 1 1 1px;
position: relative;
overflow: auto;
scrollbar-gutter: stable both-edges;
user-select: none;
mask:
linear-gradient(to top, white 0, transparent 100%) bottom no-repeat,
linear-gradient(to bottom, white 0, transparent 100%) top no-repeat,
linear-gradient(to top, white, white);
transition: mask-size 150ms;
mask-size: 100% 20px, 100% 20px, auto;
// Autoprefixed seem to ignore this one, and also syntax is different
mask-composite: xor;
mask-composite: exclude;
&.scrolled {
&-top {
mask-size: 100% 20px, 100% 0, auto;
}
&-bottom {
mask-size: 100% 0, 100% 20px, auto;
}
}
}
- &-group {
- display: flex;
- align-items: center;
- flex-wrap: wrap;
- justify-content: left;
-
- &-title {
- font-size: 0.85em;
- width: 100%;
- margin: 0;
- padding-left: 0.3em;
-
- &.disabled {
- display: none;
- }
- }
- }
-
&-item {
width: var(--emoji-size);
height: var(--emoji-size);
box-sizing: border-box;
display: flex;
line-height: var(--emoji-size);
align-items: center;
justify-content: center;
margin: 0.2em;
cursor: pointer;
.emoji-picker-emoji.-custom {
object-fit: contain;
width: var(--emoji-size);
max-width: var(--emoji-size);
height: var(--emoji-size);
max-height: var(--emoji-size);
--_still_image-label-scale: 0.5;
}
.emoji-picker-emoji.-unicode {
- font-size: 1.6em;
+ font-size: calc(var(--emoji-size) * 0.8);
overflow: hidden;
}
}
+
+ &-group {
+ display: grid;
+ grid-template-columns: repeat(var(--__amount), 1fr);
+ justify-items: center;
+ align-items: center;
+ justify-content: left;
+ grid-template-rows: repeat(1, auto);
+
+ &.first-row {
+ grid-template-rows: repeat(2, auto);
+
+ .emoji-item {
+ grid-row: 2;
+ }
+ }
+
+ &-title {
+ font-size: 0.85em;
+ grid-column: span var(--__amount);
+ width: 100%;
+ margin: 0;
+ padding-left: 0.3em;
+
+ &.disabled {
+ display: none;
+ }
+ }
+ }
}
}
diff --git a/src/components/emoji_picker/emoji_picker.vue b/src/components/emoji_picker/emoji_picker.vue
index a3dc8f9eed..0079c369b3 100644
--- a/src/components/emoji_picker/emoji_picker.vue
+++ b/src/components/emoji_picker/emoji_picker.vue
@@ -1,173 +1,175 @@
<template>
<Popover
ref="popover"
trigger="click"
popover-class="emoji-picker popover-default"
:trigger-attrs="{ 'aria-hidden': true, tabindex: -1 }"
@show="onPopoverShown"
@close="onPopoverClosed"
>
<template #content>
<div class="heading">
<!--
Body scroll lock needs to be on every scrollable element on safari iOS.
Here we tell it to enable scrolling for this element.
See https://github.com/willmcpo/body-scroll-lock#vanilla-js
-->
<span
ref="header"
v-body-scroll-lock="isInModal"
class="emoji-tabs"
>
<span
v-for="group in filteredEmojiGroups"
:ref="setGroupRef('group-header-' + group.id)"
:key="group.id"
class="button-unstyled emoji-tabs-item"
:class="{
toggled: activeGroupView === group.id
}"
:title="group.text"
role="button"
@click.prevent="highlight(group.id)"
>
<span
v-if="group.image"
class="emoji-picker-header-image"
>
<still-image
:alt="group.text"
:src="group.image"
/>
</span>
<FAIcon
v-else
:icon="group.icon"
fixed-width
/>
</span>
</span>
<span
v-if="stickerPickerEnabled"
class="additional-tabs"
>
<span
class="button-unstyled stickers-tab-icon additional-tabs-item"
:class="{toggled: showingStickers}"
:title="$t('emoji.stickers')"
@click.prevent="toggleStickers"
>
<FAIcon
icon="sticky-note"
fixed-width
/>
</span>
</span>
</div>
<div
v-if="contentLoaded"
class="content"
>
<div
class="emoji-content"
:class="{hidden: showingStickers}"
>
<div class="emoji-search">
<input
ref="search"
v-model="keyword"
type="text"
class="input form-control"
:placeholder="$t('emoji.search_emoji')"
@input="$event.target.composing = false"
>
</div>
<!-- Enables scrolling for this element on safari iOS. See comments for header. -->
<DynamicScroller
ref="emoji-groups"
v-body-scroll-lock="isInModal"
class="emoji-groups"
:class="groupsScrolledClass"
:min-item-size="minItemSize"
:buffer="minItemSize"
:items="emojiItems"
:emit-update="true"
@update="onScroll"
@visible="recalculateItemPerRow"
@resize="recalculateItemPerRow"
>
<template #default="{ item: group, index, active }">
<DynamicScrollerItem
:ref="setGroupRef('group-' + group.id)"
:item="group"
:active="active"
:data-index="index"
:size-dependencies="[group.emojis.length]"
>
<div
class="emoji-group"
+ :class="{ ['first-row']: group.isFirstRow }"
+ :style="{ '--__amount': itemPerRow }"
>
<h6
v-if="group.isFirstRow"
class="emoji-group-title"
>
{{ group.text }}
</h6>
<span
v-for="emoji in group.emojis"
:key="group.id + emoji.displayText"
:title="maybeLocalizedEmojiName(emoji)"
class="emoji-item"
role="button"
@click.stop.prevent="onEmoji(emoji)"
>
<span
v-if="!emoji.imageUrl"
class="emoji-picker-emoji -unicode"
>{{ emoji.replacement }}</span>
<still-image
v-else
class="emoji-picker-emoji -custom"
loading="lazy"
:alt="maybeLocalizedEmojiName(emoji)"
:src="emoji.imageUrl"
:data-emoji-name="group.id + emoji.displayText"
/>
</span>
</div>
</DynamicScrollerItem>
</template>
</DynamicScroller>
<div class="keep-open">
<Checkbox v-model="keepOpen">
{{ $t('emoji.keep_open') }}
</Checkbox>
</div>
<div
v-if="!hideCustomEmoji"
class="hide-custom-emoji"
>
<Checkbox
v-model="hideCustomEmojiInPicker"
@change="onShowing"
>
{{ $t('emoji.hide_custom_emoji') }}
</Checkbox>
</div>
</div>
<div
v-if="showingStickers"
class="stickers-content"
>
<sticker-picker
@uploaded="onStickerUploaded"
@upload-failed="onStickerUploadFailed"
/>
</div>
</div>
</template>
</Popover>
</template>
<script src="./emoji_picker.js"></script>
<style lang="scss" src="./emoji_picker.scss"></style>
diff --git a/src/components/emoji_reactions/emoji_reactions.js b/src/components/emoji_reactions/emoji_reactions.js
index 4d5c6c5a38..dd5577bda6 100644
--- a/src/components/emoji_reactions/emoji_reactions.js
+++ b/src/components/emoji_reactions/emoji_reactions.js
@@ -1,96 +1,98 @@
import UserAvatar from '../user_avatar/user_avatar.vue'
import UserListPopover from '../user_list_popover/user_list_popover.vue'
+import StillImage from 'src/components/still-image/still-image.vue'
import { library } from '@fortawesome/fontawesome-svg-core'
import {
faPlus,
faMinus,
faCheck
} from '@fortawesome/free-solid-svg-icons'
library.add(
faPlus,
faMinus,
faCheck
)
const EMOJI_REACTION_COUNT_CUTOFF = 12
const EmojiReactions = {
name: 'EmojiReactions',
components: {
UserAvatar,
- UserListPopover
+ UserListPopover,
+ StillImage
},
props: ['status'],
data: () => ({
showAll: false
}),
computed: {
tooManyReactions () {
return this.status.emoji_reactions.length > EMOJI_REACTION_COUNT_CUTOFF
},
emojiReactions () {
return this.showAll
? this.status.emoji_reactions
: this.status.emoji_reactions.slice(0, EMOJI_REACTION_COUNT_CUTOFF)
},
showMoreString () {
return `+${this.status.emoji_reactions.length - EMOJI_REACTION_COUNT_CUTOFF}`
},
accountsForEmoji () {
return this.status.emoji_reactions.reduce((acc, reaction) => {
acc[reaction.name] = reaction.accounts || []
return acc
}, {})
},
loggedIn () {
return !!this.$store.state.users.currentUser
},
remoteInteractionLink () {
return this.$store.getters.remoteInteractionLink({ statusId: this.status.id })
}
},
methods: {
toggleShowAll () {
this.showAll = !this.showAll
},
reactedWith (emoji) {
return this.status.emoji_reactions.find(r => r.name === emoji).me
},
async fetchEmojiReactionsByIfMissing () {
const hasNoAccounts = this.status.emoji_reactions.find(r => !r.accounts)
if (hasNoAccounts) {
return await this.$store.dispatch('fetchEmojiReactionsBy', this.status.id)
}
},
reactWith (emoji) {
this.$store.dispatch('reactWithEmoji', { id: this.status.id, emoji })
},
unreact (emoji) {
this.$store.dispatch('unreactWithEmoji', { id: this.status.id, emoji })
},
async emojiOnClick (emoji, event) {
if (!this.loggedIn) return
await this.fetchEmojiReactionsByIfMissing()
if (this.reactedWith(emoji)) {
this.unreact(emoji)
} else {
this.reactWith(emoji)
}
},
counterTriggerAttrs (reaction) {
return {
class: [
'btn',
'button-default',
'emoji-reaction-count-button',
{ '-picked-reaction': this.reactedWith(reaction.name) }
],
'aria-label': this.$tc('status.reaction_count_label', reaction.count, { num: reaction.count })
}
}
}
}
export default EmojiReactions
diff --git a/src/components/emoji_reactions/emoji_reactions.vue b/src/components/emoji_reactions/emoji_reactions.vue
index 3ab4c125ef..4c768905e2 100644
--- a/src/components/emoji_reactions/emoji_reactions.vue
+++ b/src/components/emoji_reactions/emoji_reactions.vue
@@ -1,199 +1,202 @@
<template>
<div class="EmojiReactions">
<span
v-for="(reaction) in emojiReactions"
:key="reaction.url || reaction.name"
class="emoji-reaction-container btn-group"
>
<component
:is="loggedIn ? 'button' : 'a'"
v-bind="!loggedIn ? { href: remoteInteractionLink } : {}"
role="button"
class="emoji-reaction btn button-default"
:class="{ '-picked-reaction': reactedWith(reaction.name) }"
:title="reaction.url ? reaction.name : undefined"
:aria-pressed="reactedWith(reaction.name)"
@click="emojiOnClick(reaction.name, $event)"
>
<span
class="reaction-emoji"
>
- <img
+ <StillImage
v-if="reaction.url"
:src="reaction.url"
class="reaction-emoji-content"
- width="1em"
- >
+ />
<span
v-else
class="reaction-emoji reaction-emoji-content"
>{{ reaction.name }}</span>
</span>
<FALayers>
<FAIcon
v-if="reactedWith(reaction.name)"
class="active-marker"
transform="shrink-6 up-9"
icon="check"
/>
<FAIcon
v-if="!reactedWith(reaction.name)"
class="focus-marker"
transform="shrink-6 up-9"
icon="plus"
/>
<FAIcon
v-else
class="focus-marker"
transform="shrink-6 up-9"
icon="minus"
/>
</FALayers>
</component>
<UserListPopover
:users="accountsForEmoji[reaction.name]"
class="emoji-reaction-popover"
:trigger-attrs="counterTriggerAttrs(reaction)"
@show="fetchEmojiReactionsByIfMissing()"
>
<span class="emoji-reaction-counts">{{ reaction.count }}</span>
</UserListPopover>
</span>
<a
v-if="tooManyReactions"
class="emoji-reaction-expand faint"
href="javascript:void(0)"
@click="toggleShowAll"
>
{{ showAll ? $t('general.show_less') : showMoreString }}
</a>
</div>
</template>
<script src="./emoji_reactions.js"></script>
<style lang="scss">
@import "../../mixins";
.EmojiReactions {
display: flex;
margin-top: 0.25em;
flex-wrap: wrap;
--emoji-size: calc(var(--emojiSize, 1.25em) * var(--emojiReactionsScale, 1));
.emoji-reaction-container {
display: flex;
align-items: stretch;
margin-top: 0.5em;
margin-right: 0.5em;
.emoji-reaction-popover {
padding: 0;
.emoji-reaction-count-button {
margin: 0;
height: 100%;
border-top-left-radius: 0;
border-bottom-left-radius: 0;
box-sizing: border-box;
min-width: 2em;
display: inline-flex;
justify-content: center;
align-items: center;
&.-picked-reaction {
border: 1px solid var(--accent);
margin-right: -1px;
}
}
}
}
.emoji-reaction {
padding-left: 0.5em;
display: flex;
align-items: center;
justify-content: center;
box-sizing: border-box;
border-top-right-radius: 0;
border-bottom-right-radius: 0;
margin: 0;
.reaction-emoji {
width: var(--emoji-size);
height: var(--emoji-size);
margin-right: 0.25em;
line-height: var(--emoji-size);
display: flex;
justify-content: center;
align-items: center;
}
.reaction-emoji-content {
max-width: 100%;
max-height: 100%;
- width: auto;
- height: auto;
+ width: var(--emoji-size);
+ height: var(--emoji-size);
line-height: inherit;
overflow: hidden;
font-size: calc(var(--emoji-size) * 0.8);
margin: 0;
+
+ img {
+ object-fit: contain;
+ }
}
&:focus {
outline: none;
}
.svg-inline--fa {
color: var(--text);
}
&.-picked-reaction {
border: 1px solid var(--accent);
margin-left: -1px; // offset the border, can't use inset shadows either
margin-right: -1px;
.svg-inline--fa {
color: var(--accent);
}
}
@include unfocused-style {
.focus-marker {
visibility: hidden;
}
.active-marker {
visibility: visible;
}
}
@include focused-style {
.svg-inline--fa {
color: var(--accent);
}
.focus-marker {
visibility: visible;
}
.active-marker {
visibility: hidden;
}
}
}
.emoji-reaction-expand {
padding: 0 0.5em;
margin-right: 0.5em;
margin-top: 0.5em;
display: flex;
align-items: center;
justify-content: center;
&:hover {
text-decoration: underline;
}
}
}
</style>

File Metadata

Mime Type
text/x-diff
Expires
Sat, Oct 31, 5:19 AM (21 h, 28 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
1801069
Default Alt Text
(19 KB)

Event Timeline