Merge remote-tracking branch 'origin/develop' into themes3

This commit is contained in:
Henry Jameson 2024-03-06 09:35:46 +02:00
commit 962bce5ee3
15 changed files with 996 additions and 15 deletions

View file

@ -0,0 +1,257 @@
import { clone, assign } from 'lodash'
import TabSwitcher from 'src/components/tab_switcher/tab_switcher.jsx'
import StringSetting from '../helpers/string_setting.vue'
import Checkbox from 'components/checkbox/checkbox.vue'
import StillImage from 'components/still-image/still-image.vue'
import Select from 'components/select/select.vue'
import Popover from 'components/popover/popover.vue'
import ConfirmModal from 'components/confirm_modal/confirm_modal.vue'
import ModifiedIndicator from '../helpers/modified_indicator.vue'
import EmojiEditingPopover from '../helpers/emoji_editing_popover.vue'
const EmojiTab = {
components: {
TabSwitcher,
StringSetting,
Checkbox,
StillImage,
Select,
Popover,
ConfirmModal,
ModifiedIndicator,
EmojiEditingPopover
},
data () {
return {
knownLocalPacks: { },
knownRemotePacks: { },
editedMetadata: { },
packName: '',
newPackName: '',
deleteModalVisible: false,
remotePackInstance: '',
remotePackDownloadAs: ''
}
},
provide () {
return { emojiAddr: this.emojiAddr }
},
computed: {
pack () {
return this.packName !== '' ? this.knownPacks[this.packName] : undefined
},
packMeta () {
if (this.editedMetadata[this.packName] === undefined) {
this.editedMetadata[this.packName] = clone(this.pack.pack)
}
return this.editedMetadata[this.packName]
},
knownPacks () {
// Copy the object itself but not the children, so they are still passed by reference and modified
const result = clone(this.knownLocalPacks)
for (const instName in this.knownRemotePacks) {
for (const instPack in this.knownRemotePacks[instName]) {
result[`${instPack}@${instName}`] = this.knownRemotePacks[instName][instPack]
}
}
return result
},
downloadWillReplaceLocal () {
return (this.remotePackDownloadAs.trim() === '' && this.pack.remote && this.pack.remote.baseName in this.knownLocalPacks) ||
(this.remotePackDownloadAs in this.knownLocalPacks)
}
},
methods: {
reloadEmoji () {
this.$store.state.api.backendInteractor.reloadEmoji()
},
importFromFS () {
this.$store.state.api.backendInteractor.importEmojiFromFS()
},
emojiAddr (name) {
if (this.pack.remote !== undefined) {
// Remote pack
return `${this.pack.remote.instance}/emoji/${encodeURIComponent(this.pack.remote.baseName)}/${name}`
} else {
return `${this.$store.state.instance.server}/emoji/${encodeURIComponent(this.packName)}/${name}`
}
},
createEmojiPack () {
this.$store.state.api.backendInteractor.createEmojiPack(
{ name: this.newPackName }
).then(resp => resp.json()).then(resp => {
if (resp === 'ok') {
return this.refreshPackList()
} else {
this.displayError(resp.error)
return Promise.reject(resp)
}
}).then(done => {
this.$refs.createPackPopover.hidePopover()
this.packName = this.newPackName
this.newPackName = ''
})
},
deleteEmojiPack () {
this.$store.state.api.backendInteractor.deleteEmojiPack(
{ name: this.packName }
).then(resp => resp.json()).then(resp => {
if (resp === 'ok') {
return this.refreshPackList()
} else {
this.displayError(resp.error)
return Promise.reject(resp)
}
}).then(done => {
delete this.editedMetadata[this.packName]
this.deleteModalVisible = false
this.packName = ''
})
},
metaEdited (prop) {
if (!this.pack) return
const def = this.pack.pack[prop] || ''
const edited = this.packMeta[prop] || ''
return edited !== def
},
savePackMetadata () {
this.$store.state.api.backendInteractor.saveEmojiPackMetadata({ name: this.packName, newData: this.packMeta }).then(
resp => resp.json()
).then(resp => {
if (resp.error !== undefined) {
this.displayError(resp.error)
return
}
// Update actual pack data
this.pack.pack = resp
// Delete edited pack data, should auto-update itself
delete this.editedMetadata[this.packName]
})
},
updatePackFiles (newFiles) {
this.pack.files = newFiles
this.sortPackFiles(this.packName)
},
loadPacksPaginated (listFunction) {
const pageSize = 25
const allPacks = {}
return listFunction({ instance: this.remotePackInstance, page: 1, pageSize: 0 })
.then(data => data.json())
.then(data => {
if (data.error !== undefined) { return Promise.reject(data.error) }
let resultingPromise = Promise.resolve({})
for (let i = 0; i < Math.ceil(data.count / pageSize); i++) {
resultingPromise = resultingPromise.then(() => listFunction({ instance: this.remotePackInstance, page: i, pageSize })
).then(data => data.json()).then(pageData => {
if (pageData.error !== undefined) { return Promise.reject(pageData.error) }
assign(allPacks, pageData.packs)
})
}
return resultingPromise
})
.then(finished => allPacks)
.catch(data => {
this.displayError(data)
})
},
refreshPackList () {
this.loadPacksPaginated(this.$store.state.api.backendInteractor.listEmojiPacks)
.then(allPacks => {
this.knownLocalPacks = allPacks
for (const name of Object.keys(this.knownLocalPacks)) {
this.sortPackFiles(name)
}
})
},
listRemotePacks () {
this.loadPacksPaginated(this.$store.state.api.backendInteractor.listRemoteEmojiPacks)
.then(allPacks => {
let inst = this.remotePackInstance
if (!inst.startsWith('http')) { inst = 'https://' + inst }
const instUrl = new URL(inst)
inst = instUrl.host
for (const packName in allPacks) {
allPacks[packName].remote = {
baseName: packName,
instance: instUrl.origin
}
}
this.knownRemotePacks[inst] = allPacks
for (const pack in this.knownRemotePacks[inst]) {
this.sortPackFiles(`${pack}@${inst}`)
}
this.$refs.remotePackPopover.hidePopover()
})
.catch(data => {
this.displayError(data)
})
},
downloadRemotePack () {
if (this.remotePackDownloadAs.trim() === '') {
this.remotePackDownloadAs = this.pack.remote.baseName
}
this.$store.state.api.backendInteractor.downloadRemoteEmojiPack({
instance: this.pack.remote.instance, packName: this.pack.remote.baseName, as: this.remotePackDownloadAs
})
.then(data => data.json())
.then(resp => {
if (resp === 'ok') {
this.$refs.dlPackPopover.hidePopover()
return this.refreshPackList()
} else {
this.displayError(resp.error)
return Promise.reject(resp)
}
}).then(done => {
this.packName = this.remotePackDownloadAs
this.remotePackDownloadAs = ''
})
},
displayError (msg) {
this.$store.dispatch('pushGlobalNotice', {
messageKey: 'admin_dash.emoji.error',
messageArgs: [msg],
level: 'error'
})
},
sortPackFiles (nameOfPack) {
// Sort by key
const sorted = Object.keys(this.knownPacks[nameOfPack].files).sort().reduce((acc, key) => {
if (key.length === 0) return acc
acc[key] = this.knownPacks[nameOfPack].files[key]
return acc
}, {})
this.knownPacks[nameOfPack].files = sorted
}
},
mounted () {
this.refreshPackList()
}
}
export default EmojiTab

View file

@ -0,0 +1,61 @@
@import "src/variables";
.emoji-tab {
.btn-group .btn:not(:first-child) {
margin-left: 0.5em;
}
.pack-info-wrapper {
margin-top: 1em;
}
.emoji-info-input {
width: 100%;
}
.emoji-data-input {
width: 40%;
margin-left: 0.5em;
margin-right: 0.5em;
}
.emoji {
width: 32px;
height: 32px;
}
.emoji-unsaved {
box-shadow: 0 3px 5px var(--cBlue, $fallback--cBlue);
}
.emoji-list {
display: flex;
flex-wrap: wrap;
gap: 1em 1em;
}
}
.emoji-tab-popover-button:not(:first-child) {
margin-left: 0.5em;
}
.emoji-tab-popover-input {
margin-bottom: 0.5em;
label {
display: block;
margin-bottom: 0.5em;
}
input {
width: 20em;
}
.emoji-tab-popover-file {
padding-top: 3px;
}
.warning {
color: var(--cOrange, $fallback--cOrange);
}
}

View file

@ -0,0 +1,278 @@
<template>
<div
class="emoji-tab"
:label="$t('admin_dash.tabs.emoji')"
>
<div class="setting-item">
<h2>{{ $t('admin_dash.tabs.emoji') }}</h2>
<ul class="setting-list">
<h3>{{ $t('admin_dash.emoji.global_actions') }}</h3>
<li class="btn-group setting-item">
<button
class="button button-default btn"
type="button"
@click="reloadEmoji">
{{ $t('admin_dash.emoji.reload') }}
</button>
<button
class="button button-default btn"
type="button"
@click="importFromFS">
{{ $t('admin_dash.emoji.importFS') }}
</button>
</li>
<li class="btn-group setting-item">
<button
class="button button-default btn"
type="button"
@click="$refs.remotePackPopover.showPopover">
{{ $t('admin_dash.emoji.remote_packs') }}
<Popover
ref="remotePackPopover"
popover-class="emoji-tab-edit-popover popover-default"
trigger="click"
placement="bottom"
bound-to-selector=".emoji-tab"
:bound-to="{ x: 'container' }"
:offset="{ y: 5 }"
>
<template #content>
<div class="emoji-tab-popover-input">
<h3>{{ $t('admin_dash.emoji.remote_pack_instance') }}</h3>
<input v-model="remotePackInstance" :placeholder="$t('admin_dash.emoji.remote_pack_instance')">
<button
class="button button-default btn emoji-tab-popover-button"
type="button"
@click="listRemotePacks">
{{ $t('admin_dash.emoji.do_list') }}
</button>
</div>
</template>
</Popover>
</button>
</li>
<h3>{{ $t('admin_dash.emoji.emoji_packs') }}</h3>
<li>
<h4>{{ $t('admin_dash.emoji.edit_pack') }}</h4>
<Select class="form-control" v-model="packName">
<option value="" disabled hidden>{{ $t('admin_dash.emoji.emoji_pack') }}</option>
<option v-for="(pack, listPackName) in knownPacks" :label="listPackName" :key="listPackName">
{{ listPackName }}
</option>
</Select>
<button
class="button button-default btn emoji-tab-popover-button"
type="button"
@click="$refs.createPackPopover.showPopover">
{{ $t('admin_dash.emoji.create_pack') }}
</button>
<Popover
ref="createPackPopover"
popover-class="emoji-tab-edit-popover popover-default"
trigger="click"
placement="bottom"
bound-to-selector=".emoji-tab"
:bound-to="{ x: 'container' }"
:offset="{ y: 5 }"
>
<template #content>
<div class="emoji-tab-popover-input">
<h3>{{ $t('admin_dash.emoji.new_pack_name') }}</h3>
<input v-model="newPackName" :placeholder="$t('admin_dash.emoji.new_pack_name')">
<button
class="button button-default btn emoji-tab-popover-button"
type="button"
@click="createEmojiPack">
{{ $t('admin_dash.emoji.create') }}
</button>
</div>
</template>
</Popover>
</li>
</ul>
<div v-if="pack">
<div class="pack-info-wrapper">
<ul class="setting-list">
<li>
<label>
{{ $t('admin_dash.emoji.description') }}
<ModifiedIndicator :changed="metaEdited('description')" message-key="admin_dash.emoji.metadata_changed" />
<textarea
v-model="packMeta.description"
:disabled="pack.remote !== undefined"
class="bio resize-height" />
</label>
</li>
<li>
<label>
{{ $t('admin_dash.emoji.homepage') }}
<ModifiedIndicator :changed="metaEdited('homepage')" message-key="admin_dash.emoji.metadata_changed" />
<input
class="emoji-info-input" v-model="packMeta.homepage"
:disabled="pack.remote !== undefined">
</label>
</li>
<li>
<label>
{{ $t('admin_dash.emoji.fallback_src') }}
<ModifiedIndicator :changed="metaEdited('fallback-src')" message-key="admin_dash.emoji.metadata_changed" />
<input class="emoji-info-input" v-model="packMeta['fallback-src']" :disabled="pack.remote !== undefined">
</label>
</li>
<li>
<label>
{{ $t('admin_dash.emoji.fallback_sha256') }}
<input :disabled="true" class="emoji-info-input" v-model="packMeta['fallback-src-sha256']">
</label>
</li>
<li>
<Checkbox :disabled="pack.remote !== undefined" v-model="packMeta['share-files']">
{{ $t('admin_dash.emoji.share') }}
</Checkbox>
<ModifiedIndicator :changed="metaEdited('share-files')" message-key="admin_dash.emoji.metadata_changed" />
</li>
<li class="btn-group">
<button
class="button button-default btn"
type="button"
v-if="pack.remote === undefined"
@click="savePackMetadata">
{{ $t('admin_dash.emoji.save_meta') }}
</button>
<button
class="button button-default btn"
type="button"
v-if="pack.remote === undefined"
@click="savePackMetadata">
{{ $t('admin_dash.emoji.revert_meta') }}
</button>
<button
class="button button-default btn"
v-if="pack.remote === undefined"
type="button"
@click="deleteModalVisible = true">
{{ $t('admin_dash.emoji.delete_pack') }}
<ConfirmModal
v-if="deleteModalVisible"
:title="$t('admin_dash.emoji.delete_title')"
:cancel-text="$t('status.delete_confirm_cancel_button')"
:confirm-text="$t('status.delete_confirm_accept_button')"
@cancelled="deleteModalVisible = false"
@accepted="deleteEmojiPack" >
{{ $t('admin_dash.emoji.delete_confirm', [packName]) }}
</ConfirmModal>
</button>
<button
class="button button-default btn"
type="button"
v-if="pack.remote !== undefined"
@click="$refs.dlPackPopover.showPopover">
{{ $t('admin_dash.emoji.download_pack') }}
<Popover
ref="dlPackPopover"
trigger="click"
placement="bottom"
bound-to-selector=".emoji-tab"
popover-class="emoji-tab-edit-popover popover-default"
:bound-to="{ x: 'container' }"
:offset="{ y: 5 }"
>
<template #content>
<h3>{{ $t('admin_dash.emoji.downloading_pack', [packName]) }}</h3>
<div>
<div>
<div class="emoji-tab-popover-input">
<label>
{{ $t('admin_dash.emoji.download_as_name') }}
<input class="emoji-data-input"
v-model="remotePackDownloadAs"
:placeholder="$t('admin_dash.emoji.download_as_name_full')">
</label>
<div v-if="downloadWillReplaceLocal" class="warning">
<em>{{ $t('admin_dash.emoji.replace_warning') }}</em>
</div>
</div>
<button
class="button button-default btn"
type="button"
@click="downloadRemotePack">
{{ $t('admin_dash.emoji.download') }}
</button>
</div>
</div>
</template>
</Popover>
</button>
</li>
</ul>
</div>
<ul class="setting-list">
<h4>
{{ $t('admin_dash.emoji.files') }}
<ModifiedIndicator v-if="pack"
:changed="$refs.emojiPopovers && $refs.emojiPopovers.some(p => p.isEdited)"
message-key="admin_dash.emoji.emoji_changed"/>
</h4>
<div class="emoji-list" v-if="pack">
<EmojiEditingPopover
v-if="pack.remote === undefined"
placement="bottom" new-upload
:title="$t('admin_dash.emoji.adding_new')"
:packName="packName"
@updatePackFiles="updatePackFiles" @displayError="displayError"
>
<template #trigger>
<FAIcon icon="plus" size="2x" :title="$t('admin_dash.emoji.add_file')" />
</template>
</EmojiEditingPopover>
<EmojiEditingPopover
placement="top" ref="emojiPopovers"
v-for="(file, shortcode) in pack.files" :key="shortcode"
:title="$t('admin_dash.emoji.editing', [shortcode])"
:disabled="pack.remote !== undefined"
:shortcode="shortcode" :file="file" :packName="packName"
@updatePackFiles="updatePackFiles" @displayError="displayError"
>
<template #trigger>
<StillImage
class="emoji"
:src="emojiAddr(file)"
:title="`:${shortcode}:`"
:alt="`:${shortcode}:`"
/>
</template>
</EmojiEditingPopover>
</div>
</ul>
</div>
</div>
</div>
</template>
<script src="./emoji_tab.js"></script>
<style lang="scss" src="./emoji_tab.scss"></style>

View file

@ -55,9 +55,13 @@ const FrontendsTab = {
return fe.refs.includes(frontend.ref)
},
getSuggestedRef (frontend) {
const defaultFe = this.adminDraft[':pleroma'][':frontends'][':primary']
if (defaultFe?.name === frontend.name && this.canInstall(defaultFe)) {
return defaultFe.ref
if (this.adminDraft) {
const defaultFe = this.adminDraft[':pleroma'][':frontends'][':primary']
if (defaultFe?.name === frontend.name && this.canInstall(defaultFe)) {
return defaultFe.ref
} else {
return frontend.refs[0]
}
} else {
return frontend.refs[0]
}

View file

@ -6,7 +6,7 @@
<div class="setting-item">
<h2>{{ $t('admin_dash.tabs.frontends') }}</h2>
<p>{{ $t('admin_dash.frontend.wip_notice') }}</p>
<ul class="setting-list">
<ul class="setting-list" v-if="adminDraft">
<li>
<h3>{{ $t('admin_dash.frontend.default_frontend') }}</h3>
<p>{{ $t('admin_dash.frontend.default_frontend_tip') }}</p>
@ -23,6 +23,10 @@
</ul>
</li>
</ul>
<div v-else class="setting-list">
{{ $t('admin_dash.frontend.default_frontend_unavail') }}
</div>
<div class="setting-list relative">
<PanelLoading
v-if="working"
@ -36,9 +40,9 @@
>
<strong>{{ frontend.name }}</strong>
{{ ' ' }}
<span v-if="adminDraft[':pleroma'][':frontends'][':primary']?.name === frontend.name">
<span v-if="adminDraft && adminDraft[':pleroma'][':frontends'][':primary']?.name === frontend.name">
<i18n-t
v-if="adminDraft[':pleroma'][':frontends'][':primary']?.ref === frontend.refs[0]"
v-if="adminDraft && adminDraft[':pleroma'][':frontends'][':primary']?.ref === frontend.refs[0]"
keypath="admin_dash.frontend.is_default"
/>
<i18n-t
@ -46,7 +50,7 @@
keypath="admin_dash.frontend.is_default_custom"
>
<template #version>
<code>{{ adminDraft[':pleroma'][':frontends'][':primary'].ref }}</code>
<code>{{ adminDraft && adminDraft[':pleroma'][':frontends'][':primary'].ref }}</code>
</template>
</i18n-t>
</span>
@ -137,7 +141,7 @@
class="button button-default btn"
type="button"
:disabled="
adminDraft[':pleroma'][':frontends'][':primary']?.name === frontend.name &&
!adminDraft || adminDraft[':pleroma'][':frontends'][':primary']?.name === frontend.name &&
adminDraft[':pleroma'][':frontends'][':primary']?.ref === frontend.refs[0]
"
@click="setDefault(frontend)"

View file

@ -0,0 +1,208 @@
<template>
<Popover
trigger="click"
:placement="placement"
bound-to-selector=".emoji-list"
popover-class="emoji-tab-edit-popover popover-default"
ref="emojiPopover"
:bound-to="{ x: 'container' }"
:offset="{ y: 5 }"
:disabled="disabled"
:class="{'emoji-unsaved': isEdited}"
>
<template #trigger>
<slot name="trigger" />
</template>
<template #content>
<h3>
{{ title }}
</h3>
<StillImage class="emoji" v-if="emojiPreview" :src="emojiPreview" />
<div v-else class="emoji"></div>
<div class="emoji-tab-popover-input" v-if="newUpload">
<input
type="file"
accept="image/*"
class="emoji-tab-popover-file"
@change="uploadFile = $event.target.files">
</div>
<div>
<div class="emoji-tab-popover-input">
<label>
{{ $t('admin_dash.emoji.shortcode') }}
<input class="emoji-data-input"
v-model="editedShortcode"
:placeholder="$t('admin_dash.emoji.new_shortcode')">
</label>
</div>
<div class="emoji-tab-popover-input">
<label>
{{ $t('admin_dash.emoji.filename') }}
<input class="emoji-data-input"
v-model="editedFile"
:placeholder="$t('admin_dash.emoji.new_filename')">
</label>
</div>
<button
class="button button-default btn"
type="button"
:disabled="newUpload ? uploadFile.length == 0 : !isEdited"
@click="newUpload ? uploadEmoji() : saveEditedEmoji()">
{{ $t('admin_dash.emoji.save') }}
</button>
<template v-if="!newUpload">
<button
class="button button-default btn emoji-tab-popover-button"
type="button"
@click="deleteModalVisible = true">
{{ $t('admin_dash.emoji.delete') }}
</button>
<button
class="button button-default btn emoji-tab-popover-button"
type="button"
@click="revertEmoji">
{{ $t('admin_dash.emoji.revert') }}
</button>
<ConfirmModal
v-if="deleteModalVisible"
:title="$t('admin_dash.emoji.delete_title')"
:cancel-text="$t('status.delete_confirm_cancel_button')"
:confirm-text="$t('status.delete_confirm_accept_button')"
@cancelled="deleteModalVisible = false"
@accepted="deleteEmoji" >
{{ $t('admin_dash.emoji.delete_confirm', [shortcode]) }}
</ConfirmModal>
</template>
</div>
</template>
</Popover>
</template>
<script>
import Popover from 'components/popover/popover.vue'
import ConfirmModal from 'components/confirm_modal/confirm_modal.vue'
import StillImage from 'components/still-image/still-image.vue'
export default {
components: { Popover, ConfirmModal, StillImage },
data () {
return {
uploadFile: [],
editedShortcode: this.shortcode,
editedFile: this.file,
deleteModalVisible: false
}
},
computed: {
emojiPreview () {
if (this.newUpload && this.uploadFile.length > 0) {
return URL.createObjectURL(this.uploadFile[0])
} else if (!this.newUpload) {
return this.emojiAddr(this.file)
}
return null
},
isEdited () {
return !this.newUpload && (this.editedShortcode !== this.shortcode || this.editedFile !== this.file)
}
},
inject: ['emojiAddr'],
methods: {
saveEditedEmoji () {
if (!this.isEdited) return
this.$store.state.api.backendInteractor.updateEmojiFile(
{ packName: this.packName, shortcode: this.shortcode, newShortcode: this.editedShortcode, newFilename: this.editedFile, force: false }
).then(resp => {
if (resp.error !== undefined) {
this.$emit('displayError', resp.error)
return Promise.reject(resp.error)
}
return resp.json()
}).then(resp => this.$emit('updatePackFiles', resp))
},
uploadEmoji () {
this.$store.state.api.backendInteractor.addNewEmojiFile({
packName: this.packName,
file: this.uploadFile[0],
shortcode: this.editedShortcode,
filename: this.editedFile
}).then(resp => resp.json()).then(resp => {
if (resp.error !== undefined) {
this.$emit('displayError', resp.error)
return
}
this.$emit('updatePackFiles', resp)
this.$refs.emojiPopover.hidePopover()
this.editedFile = ''
this.editedShortcode = ''
this.uploadFile = []
})
},
revertEmoji () {
this.editedFile = this.file
this.editedShortcode = this.shortcode
},
deleteEmoji () {
this.deleteModalVisible = false
this.$store.state.api.backendInteractor.deleteEmojiFile(
{ packName: this.packName, shortcode: this.shortcode }
).then(resp => resp.json()).then(resp => {
if (resp.error !== undefined) {
this.$emit('displayError', resp.error)
return
}
this.$emit('updatePackFiles', resp)
})
}
},
emits: ['updatePackFiles', 'displayError'],
props: {
placement: String,
disabled: {
type: Boolean,
default: false
},
newUpload: Boolean,
title: String,
packName: String,
shortcode: {
type: String,
// Only exists when this is not a new upload
default: ''
},
file: {
type: String,
// Only exists when this is not a new upload
default: ''
}
}
}
</script>
<style lang="scss">
.emoji-tab-edit-popover {
padding-left: 0.5em;
padding-right: 0.5em;
padding-bottom: 0.5em;
.emoji {
width: 32px;
height: 32px;
}
}
</style>

View file

@ -15,7 +15,7 @@
</template>
<template #content>
<div class="modified-tooltip">
{{ $t('settings.setting_changed') }}
{{ $t(messageKey) }}
</div>
</template>
</Popover>
@ -33,7 +33,13 @@ library.add(
export default {
components: { Popover },
props: ['changed']
props: {
changed: Boolean,
messageKey: {
type: String,
default: 'settings.setting_changed'
}
}
}
</script>

View file

@ -195,7 +195,8 @@ export default {
}
},
canHardReset () {
return this.realSource === 'admin' && this.$store.state.adminSettings.modifiedPaths.has(this.canonPath.join(' -> '))
return this.realSource === 'admin' && this.$store.state.adminSettings.modifiedPaths &&
this.$store.state.adminSettings.modifiedPaths.has(this.canonPath.join(' -> '))
},
matchesExpertLevel () {
return (this.expert || 0) <= this.$store.state.config.expertLevel > 0

View file

@ -3,6 +3,7 @@ import TabSwitcher from 'src/components/tab_switcher/tab_switcher.jsx'
import InstanceTab from './admin_tabs/instance_tab.vue'
import LimitsTab from './admin_tabs/limits_tab.vue'
import FrontendsTab from './admin_tabs/frontends_tab.vue'
import EmojiTab from './admin_tabs/emoji_tab.vue'
import { library } from '@fortawesome/fontawesome-svg-core'
import {
@ -33,7 +34,8 @@ const SettingsModalAdminContent = {
InstanceTab,
LimitsTab,
FrontendsTab
FrontendsTab,
EmojiTab
},
computed: {
user () {

View file

@ -60,6 +60,14 @@
>
<FrontendsTab />
</div>
<div
:label="$t('admin_dash.tabs.emoji')"
icon="face-smile-beam"
data-tab-name="emoji"
>
<EmojiTab />
</div>
</tab-switcher>
</template>

View file

@ -2,7 +2,7 @@
<video
class="video"
preload="metadata"
:src="attachment.url + '#t=0.5'"
:src="attachment.url + '#t=0.00000000000001'"
:loop="loopVideo"
:controls="controls"
:alt="attachment.description"