Merge remote-tracking branch 'origin/develop' into improve_settings_reusability
* origin/develop: Translated using Weblate (Chinese (Simplified)) Generalize IntegerSetting into NumberSetting, add Integer/Float wrappers Allow custom emoji reactions: add option to scale reaction buttons Fix user-profile route crash on pinned favorites route Hide custom emoji in reaction picker when BE does not advertise pleroma_custom_emoji_reactions Allow custom emoji reactions
This commit is contained in:
commit
819cd41cf0
|
@ -253,6 +253,7 @@ const getNodeInfo = async ({ store }) => {
|
||||||
store.dispatch('setInstanceOption', { name: 'safeDM', value: features.includes('safe_dm_mentions') })
|
store.dispatch('setInstanceOption', { name: 'safeDM', value: features.includes('safe_dm_mentions') })
|
||||||
store.dispatch('setInstanceOption', { name: 'shoutAvailable', value: features.includes('chat') })
|
store.dispatch('setInstanceOption', { name: 'shoutAvailable', value: features.includes('chat') })
|
||||||
store.dispatch('setInstanceOption', { name: 'pleromaChatMessagesAvailable', value: features.includes('pleroma_chat_messages') })
|
store.dispatch('setInstanceOption', { name: 'pleromaChatMessagesAvailable', value: features.includes('pleroma_chat_messages') })
|
||||||
|
store.dispatch('setInstanceOption', { name: 'pleromaCustomEmojiReactionsAvailable', value: features.includes('pleroma_custom_emoji_reactions') })
|
||||||
store.dispatch('setInstanceOption', { name: 'gopherAvailable', value: features.includes('gopher') })
|
store.dispatch('setInstanceOption', { name: 'gopherAvailable', value: features.includes('gopher') })
|
||||||
store.dispatch('setInstanceOption', { name: 'pollsAvailable', value: features.includes('polls') })
|
store.dispatch('setInstanceOption', { name: 'pollsAvailable', value: features.includes('polls') })
|
||||||
store.dispatch('setInstanceOption', { name: 'editingAvailable', value: features.includes('editing') })
|
store.dispatch('setInstanceOption', { name: 'editingAvailable', value: features.includes('editing') })
|
||||||
|
|
|
@ -98,6 +98,11 @@ const EmojiPicker = {
|
||||||
required: false,
|
required: false,
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
default: false
|
default: false
|
||||||
|
},
|
||||||
|
hideCustomEmoji: {
|
||||||
|
required: false,
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
data () {
|
data () {
|
||||||
|
@ -280,6 +285,9 @@ const EmojiPicker = {
|
||||||
return 0
|
return 0
|
||||||
},
|
},
|
||||||
allCustomGroups () {
|
allCustomGroups () {
|
||||||
|
if (this.hideCustomEmoji) {
|
||||||
|
return {}
|
||||||
|
}
|
||||||
const emojis = this.$store.getters.groupedCustomEmojis
|
const emojis = this.$store.getters.groupedCustomEmojis
|
||||||
if (emojis.unpacked) {
|
if (emojis.unpacked) {
|
||||||
emojis.unpacked.text = this.$t('emoji.unpacked')
|
emojis.unpacked.text = this.$t('emoji.unpacked')
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
<div class="EmojiReactions">
|
<div class="EmojiReactions">
|
||||||
<UserListPopover
|
<UserListPopover
|
||||||
v-for="(reaction) in emojiReactions"
|
v-for="(reaction) in emojiReactions"
|
||||||
:key="reaction.name"
|
:key="reaction.url || reaction.name"
|
||||||
:users="accountsForEmoji[reaction.name]"
|
:users="accountsForEmoji[reaction.name]"
|
||||||
>
|
>
|
||||||
<button
|
<button
|
||||||
|
@ -11,7 +11,21 @@
|
||||||
@click="emojiOnClick(reaction.name, $event)"
|
@click="emojiOnClick(reaction.name, $event)"
|
||||||
@mouseenter="fetchEmojiReactionsByIfMissing()"
|
@mouseenter="fetchEmojiReactionsByIfMissing()"
|
||||||
>
|
>
|
||||||
<span class="reaction-emoji">{{ reaction.name }}</span>
|
<span
|
||||||
|
class="reaction-emoji"
|
||||||
|
>
|
||||||
|
<img
|
||||||
|
v-if="reaction.url"
|
||||||
|
:src="reaction.url"
|
||||||
|
:title="reaction.name"
|
||||||
|
class="reaction-emoji-content"
|
||||||
|
width="1em"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
v-else
|
||||||
|
class="reaction-emoji reaction-emoji-content"
|
||||||
|
>{{ reaction.name }}</span>
|
||||||
|
</span>
|
||||||
<span>{{ reaction.count }}</span>
|
<span>{{ reaction.count }}</span>
|
||||||
</button>
|
</button>
|
||||||
</UserListPopover>
|
</UserListPopover>
|
||||||
|
@ -35,6 +49,8 @@
|
||||||
margin-top: 0.25em;
|
margin-top: 0.25em;
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
|
|
||||||
|
--emoji-size: calc(1.25em * var(--emojiReactionsScale, 1));
|
||||||
|
|
||||||
.emoji-reaction {
|
.emoji-reaction {
|
||||||
padding: 0 0.5em;
|
padding: 0 0.5em;
|
||||||
margin-right: 0.5em;
|
margin-right: 0.5em;
|
||||||
|
@ -45,8 +61,24 @@
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
|
|
||||||
.reaction-emoji {
|
.reaction-emoji {
|
||||||
width: 1.25em;
|
width: var(--emoji-size);
|
||||||
|
height: var(--emoji-size);
|
||||||
margin-right: 0.25em;
|
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;
|
||||||
|
line-height: inherit;
|
||||||
|
overflow: hidden;
|
||||||
|
font-size: calc(var(--emoji-size) * 0.8);
|
||||||
|
margin: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
&:focus {
|
&:focus {
|
||||||
|
|
|
@ -80,3 +80,21 @@ export const ROOT_ITEMS = {
|
||||||
criteria: ['announcements']
|
criteria: ['announcements']
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function routeTo (item, currentUser) {
|
||||||
|
if (!item.route && !item.routeObject) return null
|
||||||
|
|
||||||
|
let route
|
||||||
|
|
||||||
|
if (item.routeObject) {
|
||||||
|
route = item.routeObject
|
||||||
|
} else {
|
||||||
|
route = { name: (item.anon || currentUser) ? item.route : item.anonRoute }
|
||||||
|
}
|
||||||
|
|
||||||
|
if (USERNAME_ROUTES.has(route.name)) {
|
||||||
|
route.params = { username: currentUser.screen_name, name: currentUser.screen_name }
|
||||||
|
}
|
||||||
|
|
||||||
|
return route
|
||||||
|
}
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import { mapState } from 'vuex'
|
import { mapState } from 'vuex'
|
||||||
import { USERNAME_ROUTES } from 'src/components/navigation/navigation.js'
|
import { routeTo } from 'src/components/navigation/navigation.js'
|
||||||
import OptionalRouterLink from 'src/components/optional_router_link/optional_router_link.vue'
|
import OptionalRouterLink from 'src/components/optional_router_link/optional_router_link.vue'
|
||||||
import { library } from '@fortawesome/fontawesome-svg-core'
|
import { library } from '@fortawesome/fontawesome-svg-core'
|
||||||
import { faThumbtack } from '@fortawesome/free-solid-svg-icons'
|
import { faThumbtack } from '@fortawesome/free-solid-svg-icons'
|
||||||
|
@ -26,17 +26,7 @@ const NavigationEntry = {
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
routeTo () {
|
routeTo () {
|
||||||
if (!this.item.route && !this.item.routeObject) return null
|
return routeTo(this.item, this.currentUser)
|
||||||
let route
|
|
||||||
if (this.item.routeObject) {
|
|
||||||
route = this.item.routeObject
|
|
||||||
} else {
|
|
||||||
route = { name: (this.item.anon || this.currentUser) ? this.item.route : this.item.anonRoute }
|
|
||||||
}
|
|
||||||
if (USERNAME_ROUTES.has(route.name)) {
|
|
||||||
route.params = { username: this.currentUser.screen_name, name: this.currentUser.screen_name }
|
|
||||||
}
|
|
||||||
return route
|
|
||||||
},
|
},
|
||||||
getters () {
|
getters () {
|
||||||
return this.$store.getters
|
return this.$store.getters
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import { mapState } from 'vuex'
|
import { mapState } from 'vuex'
|
||||||
import { TIMELINES, ROOT_ITEMS, USERNAME_ROUTES } from 'src/components/navigation/navigation.js'
|
import { TIMELINES, ROOT_ITEMS, routeTo } from 'src/components/navigation/navigation.js'
|
||||||
import { getListEntries, filterNavigation } from 'src/components/navigation/filter.js'
|
import { getListEntries, filterNavigation } from 'src/components/navigation/filter.js'
|
||||||
|
|
||||||
import { library } from '@fortawesome/fontawesome-svg-core'
|
import { library } from '@fortawesome/fontawesome-svg-core'
|
||||||
|
@ -31,14 +31,7 @@ const NavPanel = {
|
||||||
props: ['limit'],
|
props: ['limit'],
|
||||||
methods: {
|
methods: {
|
||||||
getRouteTo (item) {
|
getRouteTo (item) {
|
||||||
if (item.routeObject) {
|
return routeTo(item, this.currentUser)
|
||||||
return item.routeObject
|
|
||||||
}
|
|
||||||
const route = { name: (item.anon || this.currentUser) ? item.route : item.anonRoute }
|
|
||||||
if (USERNAME_ROUTES.has(route.name)) {
|
|
||||||
route.params = { username: this.currentUser.screen_name }
|
|
||||||
}
|
|
||||||
return route
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
|
|
|
@ -121,7 +121,16 @@
|
||||||
scope="global"
|
scope="global"
|
||||||
keypath="notifications.reacted_with"
|
keypath="notifications.reacted_with"
|
||||||
>
|
>
|
||||||
<span class="emoji-reaction-emoji">{{ notification.emoji }}</span>
|
<img
|
||||||
|
v-if="notification.emoji_url"
|
||||||
|
class="emoji-reaction-emoji emoji-reaction-emoji-image"
|
||||||
|
:src="notification.emoji_url"
|
||||||
|
:name="notification.emoji"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
v-else
|
||||||
|
class="emoji-reaction-emoji"
|
||||||
|
>{{ notification.emoji }}</span>
|
||||||
</i18n-t>
|
</i18n-t>
|
||||||
</small>
|
</small>
|
||||||
</span>
|
</span>
|
||||||
|
|
|
@ -129,6 +129,13 @@
|
||||||
|
|
||||||
.emoji-reaction-emoji {
|
.emoji-reaction-emoji {
|
||||||
font-size: 1.3em;
|
font-size: 1.3em;
|
||||||
|
max-width: 1.25em;
|
||||||
|
height: 1.25em;
|
||||||
|
width: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.emoji-reaction-emoji-image {
|
||||||
|
vertical-align: middle;
|
||||||
}
|
}
|
||||||
|
|
||||||
.notification-details {
|
.notification-details {
|
||||||
|
|
|
@ -1,9 +1,8 @@
|
||||||
import Popover from '../popover/popover.vue'
|
import Popover from '../popover/popover.vue'
|
||||||
import { ensureFinalFallback } from '../../i18n/languages.js'
|
import EmojiPicker from '../emoji_picker/emoji_picker.vue'
|
||||||
import { library } from '@fortawesome/fontawesome-svg-core'
|
import { library } from '@fortawesome/fontawesome-svg-core'
|
||||||
import { faPlus, faTimes } from '@fortawesome/free-solid-svg-icons'
|
import { faPlus, faTimes } from '@fortawesome/free-solid-svg-icons'
|
||||||
import { faSmileBeam } from '@fortawesome/free-regular-svg-icons'
|
import { faSmileBeam } from '@fortawesome/free-regular-svg-icons'
|
||||||
import { trim } from 'lodash'
|
|
||||||
|
|
||||||
library.add(
|
library.add(
|
||||||
faPlus,
|
faPlus,
|
||||||
|
@ -20,105 +19,34 @@ const ReactButton = {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
components: {
|
components: {
|
||||||
Popover
|
Popover,
|
||||||
|
EmojiPicker
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
addReaction (event, emoji, close) {
|
addReaction (event) {
|
||||||
|
const emoji = event.insertion
|
||||||
const existingReaction = this.status.emoji_reactions.find(r => r.name === emoji)
|
const existingReaction = this.status.emoji_reactions.find(r => r.name === emoji)
|
||||||
if (existingReaction && existingReaction.me) {
|
if (existingReaction && existingReaction.me) {
|
||||||
this.$store.dispatch('unreactWithEmoji', { id: this.status.id, emoji })
|
this.$store.dispatch('unreactWithEmoji', { id: this.status.id, emoji })
|
||||||
} else {
|
} else {
|
||||||
this.$store.dispatch('reactWithEmoji', { id: this.status.id, emoji })
|
this.$store.dispatch('reactWithEmoji', { id: this.status.id, emoji })
|
||||||
}
|
}
|
||||||
close()
|
},
|
||||||
|
show () {
|
||||||
|
if (!this.expanded) {
|
||||||
|
this.$refs.picker.showPicker()
|
||||||
|
}
|
||||||
},
|
},
|
||||||
onShow () {
|
onShow () {
|
||||||
this.expanded = true
|
this.expanded = true
|
||||||
this.focusInput()
|
|
||||||
},
|
},
|
||||||
onClose () {
|
onClose () {
|
||||||
this.expanded = false
|
this.expanded = false
|
||||||
},
|
|
||||||
focusInput () {
|
|
||||||
this.$nextTick(() => {
|
|
||||||
const input = document.querySelector('.reaction-picker-filter > input')
|
|
||||||
if (input) input.focus()
|
|
||||||
})
|
|
||||||
},
|
|
||||||
// Vaguely adjusted copypaste from emoji_input and emoji_picker!
|
|
||||||
maybeLocalizedEmojiNamesAndKeywords (emoji) {
|
|
||||||
const names = [emoji.displayText]
|
|
||||||
const keywords = []
|
|
||||||
|
|
||||||
if (emoji.displayTextI18n) {
|
|
||||||
names.push(this.$t(emoji.displayTextI18n.key, emoji.displayTextI18n.args))
|
|
||||||
}
|
|
||||||
|
|
||||||
if (emoji.annotations) {
|
|
||||||
this.languages.forEach(lang => {
|
|
||||||
names.push(emoji.annotations[lang]?.name)
|
|
||||||
|
|
||||||
keywords.push(...(emoji.annotations[lang]?.keywords || []))
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
names: names.filter(k => k),
|
|
||||||
keywords: keywords.filter(k => k)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
maybeLocalizedEmojiName (emoji) {
|
|
||||||
if (!emoji.annotations) {
|
|
||||||
return emoji.displayText
|
|
||||||
}
|
|
||||||
|
|
||||||
if (emoji.displayTextI18n) {
|
|
||||||
return this.$t(emoji.displayTextI18n.key, emoji.displayTextI18n.args)
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const lang of this.languages) {
|
|
||||||
if (emoji.annotations[lang]?.name) {
|
|
||||||
return emoji.annotations[lang].name
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return emoji.displayText
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
commonEmojis () {
|
hideCustomEmoji () {
|
||||||
const hardcodedSet = new Set(['👍', '😠', '👀', '😂', '🔥'])
|
return !this.$store.state.instance.pleromaChatMessagesAvailable
|
||||||
return this.$store.getters.standardEmojiList.filter(emoji => hardcodedSet.has(emoji.replacement))
|
|
||||||
},
|
|
||||||
languages () {
|
|
||||||
return ensureFinalFallback(this.$store.getters.mergedConfig.interfaceLanguage)
|
|
||||||
},
|
|
||||||
emojis () {
|
|
||||||
if (this.filterWord !== '') {
|
|
||||||
const keywordLowercase = trim(this.filterWord.toLowerCase())
|
|
||||||
|
|
||||||
const orderedEmojiList = []
|
|
||||||
for (const emoji of this.$store.getters.standardEmojiList) {
|
|
||||||
const indices = this.maybeLocalizedEmojiNamesAndKeywords(emoji)
|
|
||||||
.keywords
|
|
||||||
.map(k => k.toLowerCase().indexOf(keywordLowercase))
|
|
||||||
.filter(k => k > -1)
|
|
||||||
|
|
||||||
const indexOfKeyword = indices.length ? Math.min(...indices) : -1
|
|
||||||
|
|
||||||
if (indexOfKeyword > -1) {
|
|
||||||
if (!Array.isArray(orderedEmojiList[indexOfKeyword])) {
|
|
||||||
orderedEmojiList[indexOfKeyword] = []
|
|
||||||
}
|
|
||||||
orderedEmojiList[indexOfKeyword].push(emoji)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return orderedEmojiList.flat()
|
|
||||||
}
|
|
||||||
return this.$store.getters.standardEmojiList || []
|
|
||||||
},
|
|
||||||
mergedConfig () {
|
|
||||||
return this.$store.getters.mergedConfig
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,73 +1,39 @@
|
||||||
<template>
|
<template>
|
||||||
<Popover
|
<span class="ReactButton">
|
||||||
trigger="click"
|
<EmojiPicker
|
||||||
class="ReactButton"
|
ref="picker"
|
||||||
placement="top"
|
:enable-sticker-picker="enableStickerPicker"
|
||||||
:offset="{ y: 5 }"
|
:hide-custom-emoji="hideCustomEmoji"
|
||||||
:bound-to="{ x: 'container' }"
|
class="emoji-picker-panel"
|
||||||
remove-padding
|
@emoji="addReaction"
|
||||||
popover-class="ReactButton popover-default"
|
@show="onShow"
|
||||||
@show="onShow"
|
@close="onClose"
|
||||||
@close="onClose"
|
/>
|
||||||
>
|
<span
|
||||||
<template #content="{close}">
|
class="button-unstyled popover-trigger"
|
||||||
<div class="reaction-picker-filter">
|
:title="$t('tool_tip.add_reaction')"
|
||||||
<input
|
@click.stop.prevent="show"
|
||||||
v-model="filterWord"
|
>
|
||||||
size="1"
|
<FALayers>
|
||||||
:placeholder="$t('emoji.search_emoji')"
|
<FAIcon
|
||||||
@input="$event.target.composing = false"
|
class="fa-scale-110 fa-old-padding"
|
||||||
>
|
:icon="['far', 'smile-beam']"
|
||||||
</div>
|
/>
|
||||||
<div class="reaction-picker">
|
<FAIcon
|
||||||
<span
|
v-show="!expanded"
|
||||||
v-for="emoji in commonEmojis"
|
class="focus-marker"
|
||||||
:key="emoji.replacement"
|
transform="shrink-6 up-9 right-17"
|
||||||
class="emoji-button"
|
icon="plus"
|
||||||
:title="maybeLocalizedEmojiName(emoji)"
|
/>
|
||||||
@click="addReaction($event, emoji.replacement, close)"
|
<FAIcon
|
||||||
>
|
v-show="expanded"
|
||||||
{{ emoji.replacement }}
|
class="focus-marker"
|
||||||
</span>
|
transform="shrink-6 up-9 right-17"
|
||||||
<div class="reaction-picker-divider" />
|
icon="times"
|
||||||
<span
|
/>
|
||||||
v-for="(emoji, key) in emojis"
|
</FALayers>
|
||||||
:key="key"
|
</span>
|
||||||
class="emoji-button"
|
</span>
|
||||||
:title="maybeLocalizedEmojiName(emoji)"
|
|
||||||
@click="addReaction($event, emoji.replacement, close)"
|
|
||||||
>
|
|
||||||
{{ emoji.replacement }}
|
|
||||||
</span>
|
|
||||||
<div class="reaction-bottom-fader" />
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
<template #trigger>
|
|
||||||
<span
|
|
||||||
class="button-unstyled popover-trigger"
|
|
||||||
:title="$t('tool_tip.add_reaction')"
|
|
||||||
>
|
|
||||||
<FALayers>
|
|
||||||
<FAIcon
|
|
||||||
class="fa-scale-110 fa-old-padding"
|
|
||||||
:icon="['far', 'smile-beam']"
|
|
||||||
/>
|
|
||||||
<FAIcon
|
|
||||||
v-show="!expanded"
|
|
||||||
class="focus-marker"
|
|
||||||
transform="shrink-6 up-9 right-17"
|
|
||||||
icon="plus"
|
|
||||||
/>
|
|
||||||
<FAIcon
|
|
||||||
v-show="expanded"
|
|
||||||
class="focus-marker"
|
|
||||||
transform="shrink-6 up-9 right-17"
|
|
||||||
icon="times"
|
|
||||||
/>
|
|
||||||
</FALayers>
|
|
||||||
</span>
|
|
||||||
</template>
|
|
||||||
</Popover>
|
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script src="./react_button.js"></script>
|
<script src="./react_button.js"></script>
|
||||||
|
@ -135,11 +101,6 @@
|
||||||
color: $fallback--text;
|
color: $fallback--text;
|
||||||
color: var(--text, $fallback--text);
|
color: var(--text, $fallback--text);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
.popover-trigger-button {
|
|
||||||
/* override of popover internal stuff */
|
|
||||||
width: auto;
|
|
||||||
|
|
||||||
@include unfocused-style {
|
@include unfocused-style {
|
||||||
.focus-marker {
|
.focus-marker {
|
||||||
|
|
|
@ -0,0 +1,16 @@
|
||||||
|
<template>
|
||||||
|
<NumberSetting
|
||||||
|
v-bind="$attrs"
|
||||||
|
>
|
||||||
|
<slot />
|
||||||
|
</NumberSetting>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import NumberSetting from './number_setting.vue'
|
||||||
|
export default {
|
||||||
|
components: {
|
||||||
|
NumberSetting
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
|
@ -1,11 +0,0 @@
|
||||||
import Setting from './setting.js'
|
|
||||||
|
|
||||||
export default {
|
|
||||||
...Setting,
|
|
||||||
methods: {
|
|
||||||
...Setting.methods,
|
|
||||||
getValue (e) {
|
|
||||||
return parseInt(e.target.value)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,40 +1,17 @@
|
||||||
<template>
|
<template>
|
||||||
<span
|
<NumberSetting
|
||||||
v-if="matchesExpertLevel"
|
v-bind="$attrs"
|
||||||
class="IntegerSetting"
|
truncate="1"
|
||||||
>
|
>
|
||||||
<label :for="path">
|
<slot />
|
||||||
<template v-if="backendDescription">
|
</NumberSetting>
|
||||||
{{ backendDescriptionLabel + ' ' }}
|
|
||||||
</template>
|
|
||||||
<template v-else>
|
|
||||||
<slot />
|
|
||||||
</template>
|
|
||||||
</label>
|
|
||||||
<input
|
|
||||||
:id="path"
|
|
||||||
class="number-input"
|
|
||||||
type="number"
|
|
||||||
step="1"
|
|
||||||
:disabled="disabled"
|
|
||||||
:min="min || 0"
|
|
||||||
:value="draftMode ? draft :state"
|
|
||||||
@change="update"
|
|
||||||
>
|
|
||||||
{{ ' ' }}
|
|
||||||
<ModifiedIndicator
|
|
||||||
:changed="isChanged"
|
|
||||||
:onclick="reset"
|
|
||||||
/>
|
|
||||||
<ProfileSettingIndicator :is-profile="isProfileSetting" />
|
|
||||||
<DraftButtons />
|
|
||||||
<p
|
|
||||||
v-if="backendDescriptionDescription"
|
|
||||||
class="setting-description"
|
|
||||||
>
|
|
||||||
{{ backendDescriptionDescription + ' ' }}
|
|
||||||
</p>
|
|
||||||
</span>
|
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script src="./integer_setting.js"></script>
|
<script>
|
||||||
|
import NumberSetting from './number_setting.vue'
|
||||||
|
export default {
|
||||||
|
components: {
|
||||||
|
NumberSetting
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
|
@ -0,0 +1,24 @@
|
||||||
|
import Setting from './setting.js'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
...Setting,
|
||||||
|
props: {
|
||||||
|
...Setting.props,
|
||||||
|
truncate: {
|
||||||
|
type: Number,
|
||||||
|
required: false,
|
||||||
|
default: 1
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
...Setting.methods,
|
||||||
|
getValue (e) {
|
||||||
|
if (!this.truncate === 1) {
|
||||||
|
return parseInt(e.target.value)
|
||||||
|
} else if (this.truncate > 1) {
|
||||||
|
return Math.trunc(e.target.value / this.truncate) * this.truncate
|
||||||
|
}
|
||||||
|
return parseFloat(e.target.value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,27 @@
|
||||||
|
<template>
|
||||||
|
<span
|
||||||
|
v-if="matchesExpertLevel"
|
||||||
|
class="NumberSetting"
|
||||||
|
>
|
||||||
|
<label :for="path">
|
||||||
|
<slot />
|
||||||
|
</label>
|
||||||
|
<input
|
||||||
|
:id="path"
|
||||||
|
class="number-input"
|
||||||
|
type="number"
|
||||||
|
:step="step || 1"
|
||||||
|
:disabled="disabled"
|
||||||
|
:min="min || 0"
|
||||||
|
:value="draftMode ? draft :state"
|
||||||
|
@change="update"
|
||||||
|
>
|
||||||
|
{{ ' ' }}
|
||||||
|
<ModifiedIndicator
|
||||||
|
:changed="isChanged"
|
||||||
|
:onclick="reset"
|
||||||
|
/>
|
||||||
|
</span>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script src="./number_setting.js"></script>
|
|
@ -60,14 +60,13 @@ export default {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
backendDescription () {
|
backendDescription () {
|
||||||
console.log(get(this.$store.state.adminSettings.descriptions, this.path))
|
|
||||||
return get(this.$store.state.adminSettings.descriptions, this.path)
|
return get(this.$store.state.adminSettings.descriptions, this.path)
|
||||||
},
|
},
|
||||||
backendDescriptionLabel () {
|
backendDescriptionLabel () {
|
||||||
return this.backendDescription.label
|
return this.backendDescription?.label
|
||||||
},
|
},
|
||||||
backendDescriptionDescription () {
|
backendDescriptionDescription () {
|
||||||
return this.backendDescription.description
|
return this.backendDescription?.description
|
||||||
},
|
},
|
||||||
shouldBeDisabled () {
|
shouldBeDisabled () {
|
||||||
const parentValue = this.parentPath !== undefined ? get(this.configSource, this.parentPath) : null
|
const parentValue = this.parentPath !== undefined ? get(this.configSource, this.parentPath) : null
|
||||||
|
|
|
@ -2,6 +2,7 @@ import BooleanSetting from '../helpers/boolean_setting.vue'
|
||||||
import ChoiceSetting from '../helpers/choice_setting.vue'
|
import ChoiceSetting from '../helpers/choice_setting.vue'
|
||||||
import ScopeSelector from 'src/components/scope_selector/scope_selector.vue'
|
import ScopeSelector from 'src/components/scope_selector/scope_selector.vue'
|
||||||
import IntegerSetting from '../helpers/integer_setting.vue'
|
import IntegerSetting from '../helpers/integer_setting.vue'
|
||||||
|
import FloatSetting from '../helpers/float_setting.vue'
|
||||||
import SizeSetting, { defaultHorizontalUnits } from '../helpers/size_setting.vue'
|
import SizeSetting, { defaultHorizontalUnits } from '../helpers/size_setting.vue'
|
||||||
import InterfaceLanguageSwitcher from 'src/components/interface_language_switcher/interface_language_switcher.vue'
|
import InterfaceLanguageSwitcher from 'src/components/interface_language_switcher/interface_language_switcher.vue'
|
||||||
|
|
||||||
|
@ -62,6 +63,7 @@ const GeneralTab = {
|
||||||
BooleanSetting,
|
BooleanSetting,
|
||||||
ChoiceSetting,
|
ChoiceSetting,
|
||||||
IntegerSetting,
|
IntegerSetting,
|
||||||
|
FloatSetting,
|
||||||
SizeSetting,
|
SizeSetting,
|
||||||
InterfaceLanguageSwitcher,
|
InterfaceLanguageSwitcher,
|
||||||
ScopeSelector,
|
ScopeSelector,
|
||||||
|
|
|
@ -269,6 +269,15 @@
|
||||||
{{ $t('settings.no_rich_text_description') }}
|
{{ $t('settings.no_rich_text_description') }}
|
||||||
</BooleanSetting>
|
</BooleanSetting>
|
||||||
</li>
|
</li>
|
||||||
|
<li>
|
||||||
|
<FloatSetting
|
||||||
|
v-if="user"
|
||||||
|
path="emojiReactionsScale"
|
||||||
|
expert="1"
|
||||||
|
>
|
||||||
|
{{ $t('settings.emoji_reactions_scale') }}
|
||||||
|
</FloatSetting>
|
||||||
|
</li>
|
||||||
<h3>{{ $t('settings.attachments') }}</h3>
|
<h3>{{ $t('settings.attachments') }}</h3>
|
||||||
<li>
|
<li>
|
||||||
<BooleanSetting
|
<BooleanSetting
|
||||||
|
|
|
@ -467,6 +467,7 @@
|
||||||
"pad_emoji": "Pad emoji with spaces when adding from picker",
|
"pad_emoji": "Pad emoji with spaces when adding from picker",
|
||||||
"autocomplete_select_first": "Automatically select the first candidate when autocomplete results are available",
|
"autocomplete_select_first": "Automatically select the first candidate when autocomplete results are available",
|
||||||
"emoji_reactions_on_timeline": "Show emoji reactions on timeline",
|
"emoji_reactions_on_timeline": "Show emoji reactions on timeline",
|
||||||
|
"emoji_reactions_scale": "Reactions scale factor",
|
||||||
"export_theme": "Save preset",
|
"export_theme": "Save preset",
|
||||||
"filtering": "Filtering",
|
"filtering": "Filtering",
|
||||||
"wordfilter": "Wordfilter",
|
"wordfilter": "Wordfilter",
|
||||||
|
|
|
@ -295,7 +295,7 @@
|
||||||
"change_password_error": "修改密码的时候出了点问题。",
|
"change_password_error": "修改密码的时候出了点问题。",
|
||||||
"changed_password": "成功修改了密码!",
|
"changed_password": "成功修改了密码!",
|
||||||
"collapse_subject": "折叠带主题的内容",
|
"collapse_subject": "折叠带主题的内容",
|
||||||
"composing": "写作",
|
"composing": "撰写",
|
||||||
"confirm_new_password": "确认新密码",
|
"confirm_new_password": "确认新密码",
|
||||||
"current_avatar": "当前头像",
|
"current_avatar": "当前头像",
|
||||||
"current_password": "当前密码",
|
"current_password": "当前密码",
|
||||||
|
@ -737,7 +737,8 @@
|
||||||
"mention_link_use_tooltip": "点击提及链接时显示用户卡片",
|
"mention_link_use_tooltip": "点击提及链接时显示用户卡片",
|
||||||
"mention_link_show_avatar": "在链接旁边显示用户头像",
|
"mention_link_show_avatar": "在链接旁边显示用户头像",
|
||||||
"mention_link_show_avatar_quick": "在提及内容旁边显示用户头像",
|
"mention_link_show_avatar_quick": "在提及内容旁边显示用户头像",
|
||||||
"user_popover_avatar_action_open": "打开个人资料"
|
"user_popover_avatar_action_open": "打开个人资料",
|
||||||
|
"autocomplete_select_first": "当有自动完成的结果时,自动选择第一个候选项"
|
||||||
},
|
},
|
||||||
"time": {
|
"time": {
|
||||||
"day": "{0} 天",
|
"day": "{0} 天",
|
||||||
|
|
|
@ -98,6 +98,7 @@ export const defaultState = {
|
||||||
sidebarColumnWidth: '25rem',
|
sidebarColumnWidth: '25rem',
|
||||||
contentColumnWidth: '45rem',
|
contentColumnWidth: '45rem',
|
||||||
notifsColumnWidth: '25rem',
|
notifsColumnWidth: '25rem',
|
||||||
|
emojiReactionsScale: 1.0,
|
||||||
navbarColumnStretch: false,
|
navbarColumnStretch: false,
|
||||||
greentext: undefined, // instance default
|
greentext: undefined, // instance default
|
||||||
useAtIcon: undefined, // instance default
|
useAtIcon: undefined, // instance default
|
||||||
|
@ -205,6 +206,7 @@ const config = {
|
||||||
case 'sidebarColumnWidth':
|
case 'sidebarColumnWidth':
|
||||||
case 'contentColumnWidth':
|
case 'contentColumnWidth':
|
||||||
case 'notifsColumnWidth':
|
case 'notifsColumnWidth':
|
||||||
|
case 'emojiReactionsScale':
|
||||||
applyConfig(state)
|
applyConfig(state)
|
||||||
break
|
break
|
||||||
case 'customTheme':
|
case 'customTheme':
|
||||||
|
|
|
@ -123,6 +123,7 @@ const defaultState = {
|
||||||
// Feature-set, apparently, not everything here is reported...
|
// Feature-set, apparently, not everything here is reported...
|
||||||
shoutAvailable: false,
|
shoutAvailable: false,
|
||||||
pleromaChatMessagesAvailable: false,
|
pleromaChatMessagesAvailable: false,
|
||||||
|
pleromaCustomEmojiReactionsAvailable: false,
|
||||||
gopherAvailable: false,
|
gopherAvailable: false,
|
||||||
mediaProxyAvailable: false,
|
mediaProxyAvailable: false,
|
||||||
suggestionsEnabled: false,
|
suggestionsEnabled: false,
|
||||||
|
|
|
@ -441,6 +441,7 @@ export const parseNotification = (data) => {
|
||||||
: parseUser(data.target)
|
: parseUser(data.target)
|
||||||
output.from_profile = parseUser(data.account)
|
output.from_profile = parseUser(data.account)
|
||||||
output.emoji = data.emoji
|
output.emoji = data.emoji
|
||||||
|
output.emoji_url = data.emoji_url
|
||||||
if (data.report) {
|
if (data.report) {
|
||||||
output.report = data.report
|
output.report = data.report
|
||||||
output.report.content = data.report.content
|
output.report.content = data.report.content
|
||||||
|
|
|
@ -21,8 +21,8 @@ export const applyTheme = (input) => {
|
||||||
body.classList.remove('hidden')
|
body.classList.remove('hidden')
|
||||||
}
|
}
|
||||||
|
|
||||||
const configColumns = ({ sidebarColumnWidth, contentColumnWidth, notifsColumnWidth }) =>
|
const configColumns = ({ sidebarColumnWidth, contentColumnWidth, notifsColumnWidth, emojiReactionsScale }) =>
|
||||||
({ sidebarColumnWidth, contentColumnWidth, notifsColumnWidth })
|
({ sidebarColumnWidth, contentColumnWidth, notifsColumnWidth, emojiReactionsScale })
|
||||||
|
|
||||||
const defaultConfigColumns = configColumns(defaultState)
|
const defaultConfigColumns = configColumns(defaultState)
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue