Merge branch 'develop' into user_management
This commit is contained in:
commit
71cf3a52f2
115 changed files with 3080 additions and 2422 deletions
|
|
@ -32,7 +32,10 @@ const EmojiTab = {
|
|||
newPackName: '',
|
||||
deleteModalVisible: false,
|
||||
remotePackInstance: '',
|
||||
remotePackDownloadAs: ''
|
||||
remotePackDownloadAs: '',
|
||||
|
||||
remotePackURL: '',
|
||||
remotePackFile: null
|
||||
}
|
||||
},
|
||||
|
||||
|
|
@ -142,9 +145,9 @@ const EmojiTab = {
|
|||
})
|
||||
},
|
||||
|
||||
updatePackFiles (newFiles) {
|
||||
this.pack.files = newFiles
|
||||
this.sortPackFiles(this.packName)
|
||||
updatePackFiles (newFiles, packName) {
|
||||
this.knownPacks[packName].files = newFiles
|
||||
this.sortPackFiles(packName)
|
||||
},
|
||||
|
||||
loadPacksPaginated (listFunction) {
|
||||
|
|
@ -220,7 +223,7 @@ const EmojiTab = {
|
|||
.then(data => data.json())
|
||||
.then(resp => {
|
||||
if (resp === 'ok') {
|
||||
this.$refs.dlPackPopover.hidePopover()
|
||||
this.$refs.downloadPackPopover.hidePopover()
|
||||
|
||||
return this.refreshPackList()
|
||||
} else {
|
||||
|
|
@ -232,6 +235,47 @@ const EmojiTab = {
|
|||
this.remotePackDownloadAs = ''
|
||||
})
|
||||
},
|
||||
downloadRemoteURLPack () {
|
||||
this.$store.state.api.backendInteractor.downloadRemoteEmojiPackZIP({
|
||||
url: this.remotePackURL, packName: this.newPackName
|
||||
})
|
||||
.then(data => data.json())
|
||||
.then(resp => {
|
||||
if (resp === 'ok') {
|
||||
this.$refs.additionalRemotePopover.hidePopover()
|
||||
|
||||
return this.refreshPackList()
|
||||
} else {
|
||||
this.displayError(resp.error)
|
||||
return Promise.reject(resp)
|
||||
}
|
||||
}).then(() => {
|
||||
this.packName = this.newPackName
|
||||
this.newPackName = ''
|
||||
this.remotePackURL = ''
|
||||
})
|
||||
},
|
||||
downloadRemoteFilePack () {
|
||||
this.$store.state.api.backendInteractor.downloadRemoteEmojiPackZIP({
|
||||
file: this.remotePackFile[0], packName: this.newPackName
|
||||
})
|
||||
.then(data => data.json())
|
||||
.then(resp => {
|
||||
if (resp === 'ok') {
|
||||
this.$refs.additionalRemotePopover.hidePopover()
|
||||
|
||||
return this.refreshPackList()
|
||||
} else {
|
||||
this.displayError(resp.error)
|
||||
return Promise.reject(resp)
|
||||
}
|
||||
}).then(() => {
|
||||
this.packName = this.newPackName
|
||||
this.newPackName = ''
|
||||
this.remotePackURL = ''
|
||||
})
|
||||
},
|
||||
|
||||
displayError (msg) {
|
||||
useInterfaceStore().pushGlobalNotice({
|
||||
messageKey: 'admin_dash.emoji.error',
|
||||
|
|
|
|||
|
|
@ -62,6 +62,64 @@
|
|||
</template>
|
||||
</Popover>
|
||||
</button>
|
||||
<button
|
||||
class="button button-default emoji-panel-additional-actions"
|
||||
@click="$refs.additionalRemotePopover.showPopover"
|
||||
>
|
||||
<FAIcon
|
||||
icon="chevron-down"
|
||||
/>
|
||||
|
||||
<Popover
|
||||
ref="additionalRemotePopover"
|
||||
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')"
|
||||
class="input"
|
||||
>
|
||||
<h3>Import pack from URL</h3>
|
||||
<input
|
||||
v-model="remotePackURL"
|
||||
class="input"
|
||||
placeholder="Pack .zip URL"
|
||||
>
|
||||
<button
|
||||
class="button button-default btn emoji-tab-popover-button"
|
||||
type="button"
|
||||
:disabled="newPackName.trim() === '' || remotePackURL.trim() === ''"
|
||||
@click="downloadRemoteURLPack"
|
||||
>
|
||||
Import
|
||||
</button>
|
||||
<h3>Import pack from a file</h3>
|
||||
<input
|
||||
type="file"
|
||||
accept="application/zip"
|
||||
class="emoji-tab-popover-file input"
|
||||
@change="remotePackFile = $event.target.files"
|
||||
>
|
||||
<button
|
||||
class="button button-default btn emoji-tab-popover-button"
|
||||
type="button"
|
||||
:disabled="newPackName.trim() === '' || remotePackFile === null || remotePackFile.length === 0"
|
||||
@click="downloadRemoteFilePack"
|
||||
>
|
||||
Import
|
||||
</button>
|
||||
</div>
|
||||
</template>
|
||||
</Popover>
|
||||
</button>
|
||||
</li>
|
||||
|
||||
<h3>{{ $t('admin_dash.emoji.emoji_packs') }}</h3>
|
||||
|
|
@ -240,12 +298,12 @@
|
|||
v-if="pack.remote !== undefined"
|
||||
class="button button-default btn"
|
||||
type="button"
|
||||
@click="$refs.dlPackPopover.showPopover"
|
||||
@click="$refs.downloadPackPopover.showPopover"
|
||||
>
|
||||
{{ $t('admin_dash.emoji.download_pack') }}
|
||||
|
||||
<Popover
|
||||
ref="dlPackPopover"
|
||||
ref="downloadPackPopover"
|
||||
trigger="click"
|
||||
placement="bottom"
|
||||
bound-to-selector=".emoji-tab"
|
||||
|
|
@ -329,11 +387,12 @@
|
|||
ref="emojiPopovers"
|
||||
:key="shortcode"
|
||||
placement="top"
|
||||
:title="$t('admin_dash.emoji.editing', [shortcode])"
|
||||
:disabled="pack.remote !== undefined"
|
||||
:title="$t(`admin_dash.emoji.${pack.remote === undefined ? 'editing' : 'copying'}`, [shortcode])"
|
||||
:shortcode="shortcode"
|
||||
:file="file"
|
||||
:pack-name="packName"
|
||||
:remote="pack.remote"
|
||||
:known-local-packs="knownLocalPacks"
|
||||
@update-pack-files="updatePackFiles"
|
||||
@display-error="displayError"
|
||||
>
|
||||
|
|
|
|||
|
|
@ -7,7 +7,6 @@
|
|||
popover-class="emoji-tab-edit-popover popover-default"
|
||||
:bound-to="{ x: 'container' }"
|
||||
:offset="{ y: 5 }"
|
||||
:disabled="disabled"
|
||||
:class="{'emoji-unsaved': isEdited}"
|
||||
>
|
||||
<template #trigger>
|
||||
|
|
@ -30,15 +29,27 @@
|
|||
|
||||
<div
|
||||
v-if="newUpload"
|
||||
class="emoji-tab-popover-input"
|
||||
class="emoji-tab-popover-new-upload"
|
||||
>
|
||||
<input
|
||||
type="file"
|
||||
accept="image/*"
|
||||
class="emoji-tab-popover-file input"
|
||||
@change="uploadFile = $event.target.files"
|
||||
>
|
||||
<h4>{{ $t('admin_dash.emoji.emoji_source') }}</h4>
|
||||
|
||||
<div class="emoji-tab-popover-input">
|
||||
<input
|
||||
type="file"
|
||||
accept="image/*"
|
||||
class="emoji-tab-popover-file input"
|
||||
@change="uploadFile = $event.target.files"
|
||||
>
|
||||
</div>
|
||||
<div class="emoji-tab-popover-input ">
|
||||
<input
|
||||
v-model="uploadURL"
|
||||
:placeholder="$t('admin_dash.emoji.upload_url')"
|
||||
class="emoji-data-input input"
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<div class="emoji-tab-popover-input">
|
||||
<label>
|
||||
|
|
@ -63,16 +74,50 @@
|
|||
</label>
|
||||
</div>
|
||||
|
||||
<div
|
||||
v-if="remote !== undefined"
|
||||
class="emoji-tab-popover-input"
|
||||
>
|
||||
<label>
|
||||
{{ $t('admin_dash.emoji.copy_to') }}
|
||||
|
||||
<SelectComponent
|
||||
v-model="copyToPack"
|
||||
class="form-control"
|
||||
>
|
||||
<option
|
||||
value=""
|
||||
disabled
|
||||
hidden
|
||||
>
|
||||
{{ $t('admin_dash.emoji.emoji_pack') }}
|
||||
</option>
|
||||
<option
|
||||
v-for="(pack, listPackName) in knownLocalPacks"
|
||||
:key="listPackName"
|
||||
:label="listPackName"
|
||||
>
|
||||
{{ listPackName }}
|
||||
</option>
|
||||
</SelectComponent>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<!--
|
||||
For local emojis, disable the button if nothing was edited.
|
||||
For remote emojis, also disable it if a local pack is not selected.
|
||||
Remote emojis are processed by the same function that uploads new ones, as that is effectively what it does
|
||||
-->
|
||||
<button
|
||||
class="button button-default btn"
|
||||
type="button"
|
||||
:disabled="newUpload ? uploadFile.length == 0 : !isEdited"
|
||||
@click="newUpload ? uploadEmoji() : saveEditedEmoji()"
|
||||
:disabled="saveButtonDisabled"
|
||||
@click="(newUpload || remote !== undefined) ? uploadEmoji() : saveEditedEmoji()"
|
||||
>
|
||||
{{ $t('admin_dash.emoji.save') }}
|
||||
</button>
|
||||
|
||||
<template v-if="!newUpload">
|
||||
<template v-if="!newUpload && remote === undefined">
|
||||
<button
|
||||
class="button button-default btn emoji-tab-popover-button"
|
||||
type="button"
|
||||
|
|
@ -107,19 +152,16 @@
|
|||
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'
|
||||
import SelectComponent from 'components/select/select.vue'
|
||||
|
||||
export default {
|
||||
components: { Popover, ConfirmModal, StillImage },
|
||||
components: { Popover, ConfirmModal, StillImage, SelectComponent },
|
||||
inject: ['emojiAddr'],
|
||||
props: {
|
||||
placement: {
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
disabled: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
|
||||
newUpload: Boolean,
|
||||
|
||||
|
|
@ -140,21 +182,35 @@ export default {
|
|||
type: String,
|
||||
// Only exists when this is not a new upload
|
||||
default: ''
|
||||
},
|
||||
|
||||
// Only exists for emojis from remote packs
|
||||
remote: {
|
||||
type: Object,
|
||||
default: undefined
|
||||
},
|
||||
knownLocalPacks: {
|
||||
type: Object,
|
||||
default: undefined
|
||||
}
|
||||
},
|
||||
emits: ['updatePackFiles', 'displayError'],
|
||||
data () {
|
||||
return {
|
||||
uploadFile: [],
|
||||
uploadURL: '',
|
||||
editedShortcode: this.shortcode,
|
||||
editedFile: this.file,
|
||||
deleteModalVisible: false
|
||||
deleteModalVisible: false,
|
||||
copyToPack: ''
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
emojiPreview () {
|
||||
if (this.newUpload && this.uploadFile.length > 0) {
|
||||
return URL.createObjectURL(this.uploadFile[0])
|
||||
} else if (this.newUpload && this.uploadURL !== '') {
|
||||
return this.uploadURL
|
||||
} else if (!this.newUpload) {
|
||||
return this.emojiAddr(this.file)
|
||||
}
|
||||
|
|
@ -163,6 +219,12 @@ export default {
|
|||
},
|
||||
isEdited () {
|
||||
return !this.newUpload && (this.editedShortcode !== this.shortcode || this.editedFile !== this.file)
|
||||
},
|
||||
saveButtonDisabled() {
|
||||
if (this.remote === undefined)
|
||||
return this.newUpload ? (this.uploadURL === "" && this.uploadFile.length == 0) : !this.isEdited
|
||||
else
|
||||
return this.copyToPack === ""
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
|
|
@ -181,9 +243,12 @@ export default {
|
|||
}).then(resp => this.$emit('updatePackFiles', resp))
|
||||
},
|
||||
uploadEmoji () {
|
||||
let packName = this.remote === undefined ? this.packName : this.copyToPack
|
||||
this.$store.state.api.backendInteractor.addNewEmojiFile({
|
||||
packName: this.packName,
|
||||
file: this.uploadFile[0],
|
||||
packName: packName,
|
||||
file: this.remote === undefined
|
||||
? (this.uploadURL !== "" ? this.uploadURL : this.uploadFile[0])
|
||||
: this.emojiAddr(this.file),
|
||||
shortcode: this.editedShortcode,
|
||||
filename: this.editedFile
|
||||
}).then(resp => resp.json()).then(resp => {
|
||||
|
|
@ -192,7 +257,7 @@ export default {
|
|||
return
|
||||
}
|
||||
|
||||
this.$emit('updatePackFiles', resp)
|
||||
this.$emit('updatePackFiles', resp, packName)
|
||||
this.$refs.emojiPopover.hidePopover()
|
||||
|
||||
this.editedFile = ''
|
||||
|
|
@ -215,7 +280,7 @@ export default {
|
|||
return
|
||||
}
|
||||
|
||||
this.$emit('updatePackFiles', resp)
|
||||
this.$emit('updatePackFiles', resp, this.packName)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
@ -228,9 +293,22 @@ export default {
|
|||
padding-right: 0.5em;
|
||||
padding-bottom: 0.5em;
|
||||
|
||||
.emoji-tab-popover-new-upload {
|
||||
margin-bottom: 2em;
|
||||
}
|
||||
|
||||
.emoji {
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
width: 2.3em;
|
||||
height: 2.3em;
|
||||
}
|
||||
|
||||
.Select {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
h4 {
|
||||
margin-bottom: 1em;
|
||||
margin-top: 1em;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@ import {
|
|||
} from 'src/services/theme_data/css_utils.js'
|
||||
import { deserialize } from 'src/services/theme_data/iss_deserializer.js'
|
||||
import { createStyleSheet, adoptStyleSheets } from 'src/services/style_setter/style_setter.js'
|
||||
import fileSizeFormatService from 'src/components/../services/file_size_format/file_size_format.js'
|
||||
|
||||
import SharedComputedObject from '../helpers/shared_computed_object.js'
|
||||
import ProfileSettingIndicator from '../helpers/profile_setting_indicator.vue'
|
||||
|
|
@ -72,7 +73,10 @@ const AppearanceTab = {
|
|||
key: mode,
|
||||
value: mode,
|
||||
label: this.$t(`settings.style.themes3.hacks.underlay_override_mode_${mode}`)
|
||||
}))
|
||||
})),
|
||||
backgroundUploading: false,
|
||||
background: null,
|
||||
backgroundPreview: null,
|
||||
}
|
||||
},
|
||||
components: {
|
||||
|
|
@ -187,6 +191,9 @@ const AppearanceTab = {
|
|||
}
|
||||
},
|
||||
computed: {
|
||||
isDefaultBackground () {
|
||||
return !(this.$store.state.users.currentUser.background_image)
|
||||
},
|
||||
switchInProgress () {
|
||||
return useInterfaceStore().themeChangeInProgress
|
||||
},
|
||||
|
|
@ -420,7 +427,55 @@ const AppearanceTab = {
|
|||
].join(''))
|
||||
sheet.ready = true
|
||||
adoptStyleSheets()
|
||||
}
|
||||
},
|
||||
uploadFile (slot, e) {
|
||||
const file = e.target.files[0]
|
||||
if (!file) { return }
|
||||
if (file.size > this.$store.state.instance[slot + 'limit']) {
|
||||
const filesize = fileSizeFormatService.fileSizeFormat(file.size)
|
||||
const allowedsize = fileSizeFormatService.fileSizeFormat(this.$store.state.instance[slot + 'limit'])
|
||||
useInterfaceStore().pushGlobalNotice({
|
||||
messageKey: 'upload.error.message',
|
||||
messageArgs: [
|
||||
this.$t('upload.error.file_too_big', {
|
||||
filesize: filesize.num,
|
||||
filesizeunit: filesize.unit,
|
||||
allowedsize: allowedsize.num,
|
||||
allowedsizeunit: allowedsize.unit
|
||||
})
|
||||
],
|
||||
level: 'error'
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
const reader = new FileReader()
|
||||
reader.onload = ({ target }) => {
|
||||
const img = target.result
|
||||
this[slot + 'Preview'] = img
|
||||
this[slot] = file
|
||||
}
|
||||
reader.readAsDataURL(file)
|
||||
},
|
||||
resetBackground () {
|
||||
const confirmed = window.confirm(this.$t('settings.reset_background_confirm'))
|
||||
if (confirmed) {
|
||||
this.submitBackground('')
|
||||
}
|
||||
},
|
||||
submitBackground (background) {
|
||||
if (!this.backgroundPreview && background !== '') { return }
|
||||
|
||||
this.backgroundUploading = true
|
||||
this.$store.state.api.backendInteractor.updateProfileImages({ background })
|
||||
.then((data) => {
|
||||
this.$store.commit('addNewUsers', [data])
|
||||
this.$store.commit('setCurrentUser', data)
|
||||
this.backgroundPreview = null
|
||||
})
|
||||
.catch(this.displayUploadError)
|
||||
.finally(() => { this.backgroundUploading = false })
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -24,6 +24,46 @@
|
|||
margin: 0.5em 0;
|
||||
}
|
||||
|
||||
input[type="file"] {
|
||||
padding: 0.25em;
|
||||
height: auto;
|
||||
}
|
||||
|
||||
.banner-background-preview {
|
||||
max-width: 100%;
|
||||
width: 300px;
|
||||
position: relative;
|
||||
|
||||
img {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
.reset-button {
|
||||
position: absolute;
|
||||
top: 0.2em;
|
||||
right: 0.2em;
|
||||
border-radius: var(--roundness);
|
||||
background-color: rgb(0 0 0 / 60%);
|
||||
opacity: 0.7;
|
||||
width: 1.5em;
|
||||
height: 1.5em;
|
||||
text-align: center;
|
||||
line-height: 1.5em;
|
||||
font-size: 1.5em;
|
||||
cursor: pointer;
|
||||
|
||||
&:hover {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
svg {
|
||||
color: white;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
.palettes-container {
|
||||
height: 15em;
|
||||
overflow: hidden auto;
|
||||
|
|
|
|||
|
|
@ -151,6 +151,49 @@
|
|||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="setting-item">
|
||||
<h2>{{ $t('settings.background') }}</h2>
|
||||
<div class="banner-background-preview">
|
||||
<img :src="user.background_image">
|
||||
<button
|
||||
v-if="!isDefaultBackground"
|
||||
class="button-unstyled reset-button"
|
||||
:title="$t('settings.reset_profile_background')"
|
||||
@click="resetBackground"
|
||||
>
|
||||
<FAIcon
|
||||
icon="times"
|
||||
type="button"
|
||||
/>
|
||||
</button>
|
||||
</div>
|
||||
<p>{{ $t('settings.set_new_background') }}</p>
|
||||
<img
|
||||
v-if="backgroundPreview"
|
||||
class="banner-background-preview"
|
||||
:src="backgroundPreview"
|
||||
>
|
||||
<div>
|
||||
<input
|
||||
type="file"
|
||||
class="input"
|
||||
@change="uploadFile('background', $event)"
|
||||
>
|
||||
</div>
|
||||
<FAIcon
|
||||
v-if="backgroundUploading"
|
||||
class="uploading"
|
||||
spin
|
||||
icon="circle-notch"
|
||||
/>
|
||||
<button
|
||||
v-else-if="backgroundPreview"
|
||||
class="btn button-default"
|
||||
@click="submitBackground(background)"
|
||||
>
|
||||
{{ $t('settings.save') }}
|
||||
</button>
|
||||
</div>
|
||||
<div class="setting-item">
|
||||
<h2>{{ $t('settings.scale_and_layout') }}</h2>
|
||||
<div class="alert neutral theme-notice">
|
||||
|
|
|
|||
|
|
@ -1,3 +1,5 @@
|
|||
import { mapState } from 'vuex'
|
||||
|
||||
import BooleanSetting from '../helpers/boolean_setting.vue'
|
||||
import ChoiceSetting from '../helpers/choice_setting.vue'
|
||||
import ScopeSelector from 'src/components/scope_selector/scope_selector.vue'
|
||||
|
|
@ -6,10 +8,11 @@ import FloatSetting from '../helpers/float_setting.vue'
|
|||
import UnitSetting from '../helpers/unit_setting.vue'
|
||||
import InterfaceLanguageSwitcher from 'src/components/interface_language_switcher/interface_language_switcher.vue'
|
||||
import Select from 'src/components/select/select.vue'
|
||||
|
||||
import SharedComputedObject from '../helpers/shared_computed_object.js'
|
||||
import ProfileSettingIndicator from '../helpers/profile_setting_indicator.vue'
|
||||
|
||||
import SharedComputedObject from '../helpers/shared_computed_object.js'
|
||||
|
||||
import localeService from 'src/services/locale/locale.service.js'
|
||||
import { clearCache, cacheKey, emojiCacheKey } from 'src/services/sw/sw.js'
|
||||
import { library } from '@fortawesome/fontawesome-svg-core'
|
||||
import {
|
||||
|
|
@ -64,7 +67,8 @@ const GeneralTab = {
|
|||
// Chrome-likes
|
||||
Object.getOwnPropertyDescriptor(HTMLMediaElement.prototype, 'webkitAudioDecodedByteCount') ||
|
||||
// Future spec, still not supported in Nightly 63 as of 08/2018
|
||||
Object.getOwnPropertyDescriptor(HTMLMediaElement.prototype, 'audioTracks')
|
||||
Object.getOwnPropertyDescriptor(HTMLMediaElement.prototype, 'audioTracks'),
|
||||
emailLanguage: this.$store.state.users.currentUser.language || ['']
|
||||
}
|
||||
},
|
||||
components: {
|
||||
|
|
@ -74,8 +78,8 @@ const GeneralTab = {
|
|||
FloatSetting,
|
||||
UnitSetting,
|
||||
InterfaceLanguageSwitcher,
|
||||
ScopeSelector,
|
||||
ProfileSettingIndicator,
|
||||
ScopeSelector,
|
||||
Select
|
||||
},
|
||||
computed: {
|
||||
|
|
@ -97,7 +101,10 @@ const GeneralTab = {
|
|||
},
|
||||
instanceShoutboxPresent () { return this.$store.state.instance.shoutAvailable },
|
||||
instanceSpecificPanelPresent () { return this.$store.state.instance.showInstanceSpecificPanel },
|
||||
...SharedComputedObject()
|
||||
...SharedComputedObject(),
|
||||
...mapState({
|
||||
blockExpirationSupported: state => state.instance.blockExpiration,
|
||||
})
|
||||
},
|
||||
methods: {
|
||||
changeDefaultScope (value) {
|
||||
|
|
@ -117,7 +124,19 @@ const GeneralTab = {
|
|||
},
|
||||
clearEmojiCache () {
|
||||
this.clearCache(emojiCacheKey)
|
||||
}
|
||||
},
|
||||
updateProfile () {
|
||||
const params = {
|
||||
language: localeService.internalToBackendLocaleMulti(this.emailLanguage)
|
||||
}
|
||||
|
||||
this.$store.state.api.backendInteractor
|
||||
.updateProfile({ params })
|
||||
.then((user) => {
|
||||
this.$store.commit('addNewUsers', [user])
|
||||
this.$store.commit('setCurrentUser', user)
|
||||
})
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -5,10 +5,20 @@
|
|||
<ul class="setting-list">
|
||||
<li>
|
||||
<interface-language-switcher
|
||||
:prompt-text="$t('settings.interfaceLanguage')"
|
||||
:language="language"
|
||||
:set-language="val => language = val"
|
||||
/>
|
||||
v-model="language"
|
||||
@update="val => language = val"
|
||||
>
|
||||
{{ $t('settings.interfaceLanguage') }}
|
||||
</interface-language-switcher>
|
||||
</li>
|
||||
<li>
|
||||
<interface-language-switcher
|
||||
v-model="emailLanguage"
|
||||
:profile="true"
|
||||
@update:model-value="updateProfile()"
|
||||
>
|
||||
{{ $t('settings.email_language') }}
|
||||
</interface-language-switcher>
|
||||
</li>
|
||||
<li v-if="instanceSpecificPanelPresent">
|
||||
<BooleanSetting path="hideISP">
|
||||
|
|
|
|||
|
|
@ -1,19 +1,8 @@
|
|||
import unescape from 'lodash/unescape'
|
||||
import merge from 'lodash/merge'
|
||||
import ImageCropper from 'src/components/image_cropper/image_cropper.vue'
|
||||
import ScopeSelector from 'src/components/scope_selector/scope_selector.vue'
|
||||
import fileSizeFormatService from 'src/components/../services/file_size_format/file_size_format.js'
|
||||
import ProgressButton from 'src/components/progress_button/progress_button.vue'
|
||||
import EmojiInput from 'src/components/emoji_input/emoji_input.vue'
|
||||
import suggestor from 'src/components/emoji_input/suggestor.js'
|
||||
import Autosuggest from 'src/components/autosuggest/autosuggest.vue'
|
||||
import UserCard from 'src/components/user_card/user_card.vue'
|
||||
import Checkbox from 'src/components/checkbox/checkbox.vue'
|
||||
import InterfaceLanguageSwitcher from 'src/components/interface_language_switcher/interface_language_switcher.vue'
|
||||
import Select from 'src/components/select/select.vue'
|
||||
import BooleanSetting from '../helpers/boolean_setting.vue'
|
||||
import SharedComputedObject from '../helpers/shared_computed_object.js'
|
||||
import localeService from 'src/services/locale/locale.service.js'
|
||||
import { propsToNative } from 'src/services/attributes_helper/attributes_helper.service.js'
|
||||
import ProfileSettingIndicator from '../helpers/profile_setting_indicator.vue'
|
||||
|
||||
import { library } from '@fortawesome/fontawesome-svg-core'
|
||||
import {
|
||||
|
|
@ -21,7 +10,6 @@ import {
|
|||
faPlus,
|
||||
faCircleNotch
|
||||
} from '@fortawesome/free-solid-svg-icons'
|
||||
import { useInterfaceStore } from 'src/stores/interface'
|
||||
|
||||
library.add(
|
||||
faTimes,
|
||||
|
|
@ -32,248 +20,42 @@ library.add(
|
|||
const ProfileTab = {
|
||||
data () {
|
||||
return {
|
||||
newName: this.$store.state.users.currentUser.name_unescaped,
|
||||
newBio: unescape(this.$store.state.users.currentUser.description),
|
||||
newLocked: this.$store.state.users.currentUser.locked,
|
||||
newBirthday: this.$store.state.users.currentUser.birthday,
|
||||
showBirthday: this.$store.state.users.currentUser.show_birthday,
|
||||
newFields: this.$store.state.users.currentUser.fields.map(field => ({ name: field.name, value: field.value })),
|
||||
showRole: this.$store.state.users.currentUser.show_role,
|
||||
role: this.$store.state.users.currentUser.role,
|
||||
bot: this.$store.state.users.currentUser.bot,
|
||||
actorType: this.$store.state.users.currentUser.actor_type,
|
||||
pickAvatarBtnVisible: true,
|
||||
bannerUploading: false,
|
||||
backgroundUploading: false,
|
||||
banner: null,
|
||||
bannerPreview: null,
|
||||
background: null,
|
||||
backgroundPreview: null,
|
||||
emailLanguage: this.$store.state.users.currentUser.language || ['']
|
||||
// Whether user is locked or not
|
||||
locked: this.$store.state.users.currentUser.locked,
|
||||
}
|
||||
},
|
||||
components: {
|
||||
ScopeSelector,
|
||||
ImageCropper,
|
||||
EmojiInput,
|
||||
Autosuggest,
|
||||
ProgressButton,
|
||||
UserCard,
|
||||
Checkbox,
|
||||
BooleanSetting,
|
||||
InterfaceLanguageSwitcher,
|
||||
Select
|
||||
ProfileSettingIndicator
|
||||
},
|
||||
computed: {
|
||||
user () {
|
||||
return this.$store.state.users.currentUser
|
||||
},
|
||||
...SharedComputedObject(),
|
||||
emojiUserSuggestor () {
|
||||
return suggestor({
|
||||
emoji: [
|
||||
...this.$store.getters.standardEmojiList,
|
||||
...this.$store.state.instance.customEmoji
|
||||
],
|
||||
store: this.$store
|
||||
})
|
||||
},
|
||||
emojiSuggestor () {
|
||||
return suggestor({
|
||||
emoji: [
|
||||
...this.$store.getters.standardEmojiList,
|
||||
...this.$store.state.instance.customEmoji
|
||||
]
|
||||
})
|
||||
},
|
||||
userSuggestor () {
|
||||
return suggestor({ store: this.$store })
|
||||
},
|
||||
fieldsLimits () {
|
||||
return this.$store.state.instance.fieldsLimits
|
||||
},
|
||||
maxFields () {
|
||||
return this.fieldsLimits ? this.fieldsLimits.maxFields : 0
|
||||
},
|
||||
defaultAvatar () {
|
||||
return this.$store.state.instance.server + this.$store.state.instance.defaultAvatar
|
||||
},
|
||||
defaultBanner () {
|
||||
return this.$store.state.instance.server + this.$store.state.instance.defaultBanner
|
||||
},
|
||||
isDefaultAvatar () {
|
||||
const baseAvatar = this.$store.state.instance.defaultAvatar
|
||||
return !(this.$store.state.users.currentUser.profile_image_url) ||
|
||||
this.$store.state.users.currentUser.profile_image_url.includes(baseAvatar)
|
||||
},
|
||||
isDefaultBanner () {
|
||||
const baseBanner = this.$store.state.instance.defaultBanner
|
||||
return !(this.$store.state.users.currentUser.cover_photo) ||
|
||||
this.$store.state.users.currentUser.cover_photo.includes(baseBanner)
|
||||
},
|
||||
isDefaultBackground () {
|
||||
return !(this.$store.state.users.currentUser.background_image)
|
||||
},
|
||||
avatarImgSrc () {
|
||||
const src = this.$store.state.users.currentUser.profile_image_url_original
|
||||
return (!src) ? this.defaultAvatar : src
|
||||
},
|
||||
bannerImgSrc () {
|
||||
const src = this.$store.state.users.currentUser.cover_photo
|
||||
return (!src) ? this.defaultBanner : src
|
||||
},
|
||||
groupActorAvailable () {
|
||||
return this.$store.state.instance.groupActorAvailable
|
||||
},
|
||||
availableActorTypes () {
|
||||
return this.groupActorAvailable ? ['Person', 'Service', 'Group'] : ['Person', 'Service']
|
||||
}
|
||||
...SharedComputedObject()
|
||||
},
|
||||
methods: {
|
||||
updateProfile () {
|
||||
const params = {
|
||||
note: this.newBio,
|
||||
locked: this.newLocked,
|
||||
|
||||
// Backend notation.
|
||||
display_name: this.newName,
|
||||
fields_attributes: this.newFields.filter(el => el != null),
|
||||
actor_type: this.actorType,
|
||||
show_role: this.showRole,
|
||||
birthday: this.newBirthday || '',
|
||||
show_birthday: this.showBirthday
|
||||
}
|
||||
|
||||
if (this.emailLanguage) {
|
||||
params.language = localeService.internalToBackendLocaleMulti(this.emailLanguage)
|
||||
locked: this.locked
|
||||
}
|
||||
|
||||
this.$store.state.api.backendInteractor
|
||||
.updateProfile({ params })
|
||||
.then((user) => {
|
||||
this.newFields.splice(user.fields.length)
|
||||
merge(this.newFields, user.fields)
|
||||
this.$store.commit('addNewUsers', [user])
|
||||
this.$store.commit('setCurrentUser', user)
|
||||
})
|
||||
},
|
||||
changeVis (visibility) {
|
||||
this.newDefaultScope = visibility
|
||||
},
|
||||
addField () {
|
||||
if (this.newFields.length < this.maxFields) {
|
||||
this.newFields.push({ name: '', value: '' })
|
||||
return true
|
||||
}
|
||||
return false
|
||||
},
|
||||
deleteField (index) {
|
||||
this.newFields.splice(index, 1)
|
||||
},
|
||||
uploadFile (slot, e) {
|
||||
const file = e.target.files[0]
|
||||
if (!file) { return }
|
||||
if (file.size > this.$store.state.instance[slot + 'limit']) {
|
||||
const filesize = fileSizeFormatService.fileSizeFormat(file.size)
|
||||
const allowedsize = fileSizeFormatService.fileSizeFormat(this.$store.state.instance[slot + 'limit'])
|
||||
useInterfaceStore().pushGlobalNotice({
|
||||
messageKey: 'upload.error.message',
|
||||
messageArgs: [
|
||||
this.$t('upload.error.file_too_big', {
|
||||
filesize: filesize.num,
|
||||
filesizeunit: filesize.unit,
|
||||
allowedsize: allowedsize.num,
|
||||
allowedsizeunit: allowedsize.unit
|
||||
})
|
||||
],
|
||||
level: 'error'
|
||||
.catch((error) => {
|
||||
this.displayUploadError(error)
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
const reader = new FileReader()
|
||||
reader.onload = ({ target }) => {
|
||||
const img = target.result
|
||||
this[slot + 'Preview'] = img
|
||||
this[slot] = file
|
||||
}
|
||||
reader.readAsDataURL(file)
|
||||
},
|
||||
resetAvatar () {
|
||||
const confirmed = window.confirm(this.$t('settings.reset_avatar_confirm'))
|
||||
if (confirmed) {
|
||||
this.submitAvatar(undefined, '')
|
||||
}
|
||||
},
|
||||
resetBanner () {
|
||||
const confirmed = window.confirm(this.$t('settings.reset_banner_confirm'))
|
||||
if (confirmed) {
|
||||
this.submitBanner('')
|
||||
}
|
||||
},
|
||||
resetBackground () {
|
||||
const confirmed = window.confirm(this.$t('settings.reset_background_confirm'))
|
||||
if (confirmed) {
|
||||
this.submitBackground('')
|
||||
}
|
||||
},
|
||||
submitAvatar (canvas, file) {
|
||||
const that = this
|
||||
return new Promise((resolve, reject) => {
|
||||
function updateAvatar (avatar, avatarName) {
|
||||
that.$store.state.api.backendInteractor.updateProfileImages({ avatar, avatarName })
|
||||
.then((user) => {
|
||||
that.$store.commit('addNewUsers', [user])
|
||||
that.$store.commit('setCurrentUser', user)
|
||||
resolve()
|
||||
})
|
||||
.catch((error) => {
|
||||
that.displayUploadError(error)
|
||||
reject(error)
|
||||
})
|
||||
}
|
||||
|
||||
if (canvas) {
|
||||
canvas.toBlob((data) => updateAvatar(data, file.name), file.type)
|
||||
} else {
|
||||
updateAvatar(file, file.name)
|
||||
}
|
||||
})
|
||||
},
|
||||
submitBanner (banner) {
|
||||
if (!this.bannerPreview && banner !== '') { return }
|
||||
|
||||
this.bannerUploading = true
|
||||
this.$store.state.api.backendInteractor.updateProfileImages({ banner })
|
||||
.then((user) => {
|
||||
this.$store.commit('addNewUsers', [user])
|
||||
this.$store.commit('setCurrentUser', user)
|
||||
this.bannerPreview = null
|
||||
})
|
||||
.catch(this.displayUploadError)
|
||||
.finally(() => { this.bannerUploading = false })
|
||||
},
|
||||
submitBackground (background) {
|
||||
if (!this.backgroundPreview && background !== '') { return }
|
||||
|
||||
this.backgroundUploading = true
|
||||
this.$store.state.api.backendInteractor.updateProfileImages({ background })
|
||||
.then((data) => {
|
||||
this.$store.commit('addNewUsers', [data])
|
||||
this.$store.commit('setCurrentUser', data)
|
||||
this.backgroundPreview = null
|
||||
})
|
||||
.catch(this.displayUploadError)
|
||||
.finally(() => { this.backgroundUploading = false })
|
||||
},
|
||||
displayUploadError (error) {
|
||||
useInterfaceStore().pushGlobalNotice({
|
||||
messageKey: 'upload.error.message',
|
||||
messageArgs: [error.message],
|
||||
level: 'error'
|
||||
})
|
||||
},
|
||||
propsToNative (props) {
|
||||
return propsToNative(props)
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
locked () {
|
||||
this.updateProfile()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,133 +1,15 @@
|
|||
.profile-tab {
|
||||
.bio {
|
||||
// overriding global for better look
|
||||
.setting-item.profile-edit {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.visibility-tray {
|
||||
padding-top: 5px;
|
||||
}
|
||||
|
||||
input[type="file"] {
|
||||
padding: 5px;
|
||||
height: auto;
|
||||
}
|
||||
|
||||
.banner-background-preview {
|
||||
max-width: 100%;
|
||||
width: 300px;
|
||||
position: relative;
|
||||
|
||||
img {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
.uploading {
|
||||
font-size: 1.5em;
|
||||
margin: 0.25em;
|
||||
}
|
||||
|
||||
.name-changer {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.current-avatar-container {
|
||||
position: relative;
|
||||
width: 150px;
|
||||
height: 150px;
|
||||
}
|
||||
|
||||
.current-avatar {
|
||||
display: block;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border-radius: var(--roundness);
|
||||
}
|
||||
|
||||
.reset-button {
|
||||
position: absolute;
|
||||
top: 0.2em;
|
||||
right: 0.2em;
|
||||
border-radius: var(--roundness);
|
||||
background-color: rgb(0 0 0 / 60%);
|
||||
opacity: 0.7;
|
||||
width: 1.5em;
|
||||
height: 1.5em;
|
||||
text-align: center;
|
||||
line-height: 1.5em;
|
||||
font-size: 1.5em;
|
||||
cursor: pointer;
|
||||
|
||||
&:hover {
|
||||
opacity: 1;
|
||||
h2 {
|
||||
margin-left: 1rem;
|
||||
}
|
||||
|
||||
svg {
|
||||
color: white;
|
||||
.user-card {
|
||||
padding: 1.2em;
|
||||
overflow: hidden;
|
||||
}
|
||||
}
|
||||
|
||||
.oauth-tokens {
|
||||
width: 100%;
|
||||
|
||||
th {
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.actions {
|
||||
text-align: right;
|
||||
}
|
||||
}
|
||||
|
||||
&-usersearch-wrapper {
|
||||
padding: 1em;
|
||||
}
|
||||
|
||||
&-bulk-actions {
|
||||
text-align: right;
|
||||
padding: 0 1em;
|
||||
min-height: 2em;
|
||||
|
||||
button {
|
||||
width: 10em;
|
||||
}
|
||||
}
|
||||
|
||||
&-domain-mute-form {
|
||||
padding: 1em;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
button {
|
||||
align-self: flex-end;
|
||||
margin-top: 1em;
|
||||
width: 10em;
|
||||
}
|
||||
}
|
||||
|
||||
.setting-subitem {
|
||||
margin-left: 1.75em;
|
||||
}
|
||||
|
||||
.profile-fields {
|
||||
display: flex;
|
||||
|
||||
& > .emoji-input {
|
||||
flex: 1 1 auto;
|
||||
margin: 0 0.2em 0.5em;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.delete-field {
|
||||
width: 20px;
|
||||
align-self: center;
|
||||
margin: 0 0.2em 0.5em;
|
||||
padding: 0 0.5em;
|
||||
}
|
||||
}
|
||||
|
||||
.birthday-input {
|
||||
display: block;
|
||||
margin-bottom: 1em;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,283 +1,21 @@
|
|||
<template>
|
||||
<div class="profile-tab">
|
||||
<div class="setting-item">
|
||||
<h2>{{ $t('settings.name_bio') }}</h2>
|
||||
<p>{{ $t('settings.name') }}</p>
|
||||
<EmojiInput
|
||||
v-model="newName"
|
||||
enable-emoji-picker
|
||||
:suggest="emojiSuggestor"
|
||||
>
|
||||
<template #default="inputProps">
|
||||
<input
|
||||
id="username"
|
||||
v-model="newName"
|
||||
class="input name-changer"
|
||||
v-bind="propsToNative(inputProps)"
|
||||
>
|
||||
</template>
|
||||
</EmojiInput>
|
||||
<p>{{ $t('settings.bio') }}</p>
|
||||
<EmojiInput
|
||||
v-model="newBio"
|
||||
enable-emoji-picker
|
||||
:suggest="emojiUserSuggestor"
|
||||
>
|
||||
<template #default="inputProps">
|
||||
<textarea
|
||||
v-model="newBio"
|
||||
class="input bio resize-height"
|
||||
v-bind="propsToNative(inputProps)"
|
||||
/>
|
||||
</template>
|
||||
</EmojiInput>
|
||||
<p v-if="role === 'admin' || role === 'moderator'">
|
||||
<Checkbox v-model="showRole">
|
||||
<template v-if="role === 'admin'">
|
||||
{{ $t('settings.show_admin_badge') }}
|
||||
</template>
|
||||
<template v-if="role === 'moderator'">
|
||||
{{ $t('settings.show_moderator_badge') }}
|
||||
</template>
|
||||
</Checkbox>
|
||||
</p>
|
||||
<div>
|
||||
<p>{{ $t('settings.birthday.label') }}</p>
|
||||
<input
|
||||
id="birthday"
|
||||
v-model="newBirthday"
|
||||
type="date"
|
||||
class="input birthday-input"
|
||||
>
|
||||
<Checkbox v-model="showBirthday">
|
||||
{{ $t('settings.birthday.show_birthday') }}
|
||||
</Checkbox>
|
||||
</div>
|
||||
<div v-if="maxFields > 0">
|
||||
<p>{{ $t('settings.profile_fields.label') }}</p>
|
||||
<div
|
||||
v-for="(_, i) in newFields"
|
||||
:key="i"
|
||||
class="profile-fields"
|
||||
>
|
||||
<EmojiInput
|
||||
v-model="newFields[i].name"
|
||||
enable-emoji-picker
|
||||
hide-emoji-button
|
||||
:suggest="userSuggestor"
|
||||
>
|
||||
<template #default="inputProps">
|
||||
<input
|
||||
v-model="newFields[i].name"
|
||||
:placeholder="$t('settings.profile_fields.name')"
|
||||
v-bind="propsToNative(inputProps)"
|
||||
class="input"
|
||||
>
|
||||
</template>
|
||||
</EmojiInput>
|
||||
<EmojiInput
|
||||
v-model="newFields[i].value"
|
||||
enable-emoji-picker
|
||||
hide-emoji-button
|
||||
:suggest="userSuggestor"
|
||||
>
|
||||
<template #default="inputProps">
|
||||
<input
|
||||
v-model="newFields[i].value"
|
||||
:placeholder="$t('settings.profile_fields.value')"
|
||||
v-bind="propsToNative(inputProps)"
|
||||
class="input"
|
||||
>
|
||||
</template>
|
||||
</EmojiInput>
|
||||
<button
|
||||
class="delete-field button-unstyled -hover-highlight"
|
||||
@click="deleteField(i)"
|
||||
>
|
||||
<!-- TODO something is wrong with v-show here -->
|
||||
<FAIcon
|
||||
v-if="newFields.length > 1"
|
||||
icon="times"
|
||||
/>
|
||||
</button>
|
||||
</div>
|
||||
<button
|
||||
v-if="newFields.length < maxFields"
|
||||
class="add-field faint button-unstyled -hover-highlight"
|
||||
@click="addField"
|
||||
>
|
||||
<FAIcon icon="plus" />
|
||||
{{ $t("settings.profile_fields.add_field") }}
|
||||
</button>
|
||||
</div>
|
||||
<p>
|
||||
<label>
|
||||
{{ $t('settings.actor_type') }}
|
||||
<Select v-model="actorType">
|
||||
<option
|
||||
v-for="option in availableActorTypes"
|
||||
:key="option"
|
||||
:value="option"
|
||||
>
|
||||
{{ $t('settings.actor_type_' + option) }}
|
||||
</option>
|
||||
</Select>
|
||||
</label>
|
||||
</p>
|
||||
<div v-if="groupActorAvailable">
|
||||
<small>
|
||||
{{ $t('settings.actor_type_description') }}
|
||||
</small>
|
||||
</div>
|
||||
<p>
|
||||
<interface-language-switcher
|
||||
:prompt-text="$t('settings.email_language')"
|
||||
:language="emailLanguage"
|
||||
:set-language="val => emailLanguage = val"
|
||||
/>
|
||||
</p>
|
||||
<button
|
||||
:disabled="newName && newName.length === 0"
|
||||
class="btn button-default"
|
||||
@click="updateProfile"
|
||||
>
|
||||
{{ $t('settings.save') }}
|
||||
</button>
|
||||
</div>
|
||||
<div class="setting-item">
|
||||
<h2>{{ $t('settings.avatar') }}</h2>
|
||||
<p class="visibility-notice">
|
||||
{{ $t('settings.avatar_size_instruction') }}
|
||||
</p>
|
||||
<div class="current-avatar-container">
|
||||
<img
|
||||
:src="user.profile_image_url_original"
|
||||
class="current-avatar"
|
||||
>
|
||||
<button
|
||||
v-if="!isDefaultAvatar && pickAvatarBtnVisible"
|
||||
:title="$t('settings.reset_avatar')"
|
||||
class="button-unstyled reset-button"
|
||||
@click="resetAvatar"
|
||||
>
|
||||
<FAIcon
|
||||
icon="times"
|
||||
type="button"
|
||||
/>
|
||||
</button>
|
||||
</div>
|
||||
<p>{{ $t('settings.set_new_avatar') }}</p>
|
||||
<button
|
||||
v-show="pickAvatarBtnVisible"
|
||||
id="pick-avatar"
|
||||
class="button-default btn"
|
||||
type="button"
|
||||
>
|
||||
{{ $t('settings.upload_a_photo') }}
|
||||
</button>
|
||||
<image-cropper
|
||||
trigger="#pick-avatar"
|
||||
:submit-handler="submitAvatar"
|
||||
@open="pickAvatarBtnVisible=false"
|
||||
@close="pickAvatarBtnVisible=true"
|
||||
<div class="setting-item profile-edit">
|
||||
<h2>{{ $t('settings.account_profile_edit') }}</h2>
|
||||
<UserCard
|
||||
:user-id="user.id"
|
||||
:editable="true"
|
||||
:switcher="false"
|
||||
/>
|
||||
</div>
|
||||
<div class="setting-item">
|
||||
<h2>{{ $t('settings.profile_banner') }}</h2>
|
||||
<div class="banner-background-preview">
|
||||
<img :src="user.cover_photo">
|
||||
<button
|
||||
v-if="!isDefaultBanner"
|
||||
class="button-unstyled reset-button"
|
||||
:title="$t('settings.reset_profile_banner')"
|
||||
@click="resetBanner"
|
||||
>
|
||||
<FAIcon
|
||||
icon="times"
|
||||
type="button"
|
||||
/>
|
||||
</button>
|
||||
</div>
|
||||
<p>{{ $t('settings.set_new_profile_banner') }}</p>
|
||||
<img
|
||||
v-if="bannerPreview"
|
||||
class="banner-background-preview"
|
||||
:src="bannerPreview"
|
||||
>
|
||||
<div>
|
||||
<input
|
||||
type="file"
|
||||
class="input"
|
||||
@change="uploadFile('banner', $event)"
|
||||
>
|
||||
</div>
|
||||
<FAIcon
|
||||
v-if="bannerUploading"
|
||||
class="uploading"
|
||||
spin
|
||||
icon="circle-notch"
|
||||
/>
|
||||
<button
|
||||
v-else-if="bannerPreview"
|
||||
class="btn button-default"
|
||||
@click="submitBanner(banner)"
|
||||
>
|
||||
{{ $t('settings.save') }}
|
||||
</button>
|
||||
</div>
|
||||
<div class="setting-item">
|
||||
<h2>{{ $t('settings.profile_background') }}</h2>
|
||||
<div class="banner-background-preview">
|
||||
<img :src="user.background_image">
|
||||
<button
|
||||
v-if="!isDefaultBackground"
|
||||
class="button-unstyled reset-button"
|
||||
:title="$t('settings.reset_profile_background')"
|
||||
@click="resetBackground"
|
||||
>
|
||||
<FAIcon
|
||||
icon="times"
|
||||
type="button"
|
||||
/>
|
||||
</button>
|
||||
</div>
|
||||
<p>{{ $t('settings.set_new_profile_background') }}</p>
|
||||
<img
|
||||
v-if="backgroundPreview"
|
||||
class="banner-background-preview"
|
||||
:src="backgroundPreview"
|
||||
>
|
||||
<div>
|
||||
<input
|
||||
type="file"
|
||||
class="input"
|
||||
@change="uploadFile('background', $event)"
|
||||
>
|
||||
</div>
|
||||
<FAIcon
|
||||
v-if="backgroundUploading"
|
||||
class="uploading"
|
||||
spin
|
||||
icon="circle-notch"
|
||||
/>
|
||||
<button
|
||||
v-else-if="backgroundPreview"
|
||||
class="btn button-default"
|
||||
@click="submitBackground(background)"
|
||||
>
|
||||
{{ $t('settings.save') }}
|
||||
</button>
|
||||
</div>
|
||||
<div class="setting-item">
|
||||
<h2>{{ $t('settings.account_privacy') }}</h2>
|
||||
<ul class="setting-list">
|
||||
<li>
|
||||
<BooleanSetting
|
||||
source="profile"
|
||||
path="locked"
|
||||
>
|
||||
<Checkbox v-model="locked">
|
||||
{{ $t('settings.lock_account_description') }}
|
||||
</BooleanSetting>
|
||||
</Checkbox>
|
||||
<ProfileSettingIndicator :is-profile="true" />
|
||||
</li>
|
||||
<li>
|
||||
<BooleanSetting
|
||||
|
|
|
|||
|
|
@ -159,10 +159,16 @@
|
|||
|
||||
.qr-code {
|
||||
flex: 1;
|
||||
padding-right: 10px;
|
||||
padding-right: 0.7em;
|
||||
}
|
||||
|
||||
.verify {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.error {
|
||||
margin: 0.3em 0 0;
|
||||
}
|
||||
.verify { flex: 1; }
|
||||
.error { margin: 4px 0 0; }
|
||||
|
||||
.confirm-otp-actions {
|
||||
button {
|
||||
|
|
|
|||
|
|
@ -21,7 +21,10 @@
|
|||
</div>
|
||||
<div class="panel-body theme-preview-content">
|
||||
<div class="post">
|
||||
<div class="avatar still-image">
|
||||
<div
|
||||
class="avatar still-image"
|
||||
aria-hidden="true"
|
||||
>
|
||||
( ͡° ͜ʖ ͡°)
|
||||
</div>
|
||||
<div class="content">
|
||||
|
|
@ -71,7 +74,10 @@
|
|||
</div>
|
||||
|
||||
<div class="after-post">
|
||||
<div class="avatar-alt">
|
||||
<div
|
||||
class="avatar-alt"
|
||||
aria-hidden="true"
|
||||
>
|
||||
:^)
|
||||
</div>
|
||||
<div class="content">
|
||||
|
|
@ -149,7 +155,7 @@ export default {
|
|||
background-position: 50% 50%;
|
||||
|
||||
.theme-preview-content {
|
||||
padding: 20px;
|
||||
padding: 1.5em;
|
||||
}
|
||||
|
||||
.dummy {
|
||||
|
|
@ -203,19 +209,21 @@ export default {
|
|||
|
||||
.avatar-alt {
|
||||
flex: 0 auto;
|
||||
margin-left: 28px;
|
||||
font-size: 12px;
|
||||
min-width: 20px;
|
||||
min-height: 20px;
|
||||
line-height: 20px;
|
||||
margin-left: 2em;
|
||||
font-size: 0.85em;
|
||||
min-width: 2.2rem;
|
||||
min-height: 2.2rem;
|
||||
line-height: 1.5em;
|
||||
align-content: center;
|
||||
}
|
||||
|
||||
.avatar {
|
||||
flex: 0 auto;
|
||||
width: 48px;
|
||||
height: 48px;
|
||||
font-size: 14px;
|
||||
line-height: 48px;
|
||||
width: 3.5em;
|
||||
height: 3.5em;
|
||||
font-size: 1rem;
|
||||
line-height: 3.5em;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.actions {
|
||||
|
|
@ -241,7 +249,7 @@ export default {
|
|||
|
||||
.underlay-preview {
|
||||
position: absolute;
|
||||
inset: 0 10px;
|
||||
inset: 0 0.9em;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
|
|||
|
|
@ -107,7 +107,7 @@
|
|||
justify-content: space-between;
|
||||
align-items: baseline;
|
||||
width: 100%;
|
||||
min-height: 30px;
|
||||
min-height: 2.1em;
|
||||
margin-bottom: 1em;
|
||||
|
||||
p {
|
||||
|
|
@ -212,7 +212,7 @@
|
|||
.theme-color-cl,
|
||||
.theme-radius-in,
|
||||
.theme-color-in {
|
||||
margin-left: 4px;
|
||||
margin-left: 0.3em;
|
||||
}
|
||||
|
||||
.theme-radius-in {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue