biome format --write

This commit is contained in:
Henry Jameson 2026-01-06 16:22:52 +02:00
commit 9262e803ec
415 changed files with 54076 additions and 17419 deletions

View file

@ -11,10 +11,10 @@ import MapSetting from '../helpers/map_setting.vue'
import SharedComputedObject from '../helpers/shared_computed_object.js'
const AuthTab = {
provide () {
provide() {
return {
defaultDraftMode: true,
defaultSource: 'admin'
defaultSource: 'admin',
}
},
components: {
@ -26,14 +26,16 @@ const AuthTab = {
AttachmentSetting,
GroupSetting,
ListSetting,
MapSetting
MapSetting,
},
computed: {
...SharedComputedObject(),
LDAPEnabled () {
return this.$store.state.adminSettings.draft[':pleroma'][':ldap'][':enabled']
LDAPEnabled() {
return this.$store.state.adminSettings.draft[':pleroma'][':ldap'][
':enabled'
]
},
}
},
}
export default AuthTab

View file

@ -16,15 +16,10 @@ import {
faArrowsRotate,
faFolderOpen,
faServer,
faDownload
faDownload,
} from '@fortawesome/free-solid-svg-icons'
library.add(
faArrowsRotate,
faFolderOpen,
faDownload,
faServer
)
library.add(faArrowsRotate, faFolderOpen, faDownload, faServer)
const EmojiTab = {
components: {
@ -36,14 +31,14 @@ const EmojiTab = {
Popover,
ConfirmModal,
ModifiedIndicator,
EmojiEditingPopover
EmojiEditingPopover,
},
data () {
data() {
return {
knownLocalPacks: { },
knownRemotePacks: { },
editedMetadata: { },
knownLocalPacks: {},
knownRemotePacks: {},
editedMetadata: {},
packName: '',
newPackName: '',
deleteModalVisible: false,
@ -51,20 +46,20 @@ const EmojiTab = {
remotePackDownloadAs: '',
remotePackURL: '',
remotePackFile: null
remotePackFile: null,
}
},
provide () {
provide() {
return { emojiAddr: this.emojiAddr }
},
computed: {
...SharedComputedObject(),
pack () {
pack() {
return this.packName !== '' ? this.knownPacks[this.packName] : undefined
},
packMeta () {
packMeta() {
if (this.packName === '') return {}
if (this.editedMetadata[this.packName] === undefined) {
this.editedMetadata[this.packName] = clone(this.pack.pack)
@ -72,31 +67,36 @@ const EmojiTab = {
return this.editedMetadata[this.packName]
},
knownPacks () {
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]
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)
}
downloadWillReplaceLocal() {
return (
(this.remotePackDownloadAs.trim() === '' &&
this.pack.remote &&
this.pack.remote.baseName in this.knownLocalPacks) ||
this.remotePackDownloadAs in this.knownLocalPacks
)
},
},
methods: {
reloadEmoji () {
reloadEmoji() {
this.$store.state.api.backendInteractor.reloadEmoji()
},
importFromFS () {
importFromFS() {
this.$store.state.api.backendInteractor.importEmojiFromFS()
},
emojiAddr (name) {
emojiAddr(name) {
if (this.pack.remote !== undefined) {
// Remote pack
return `${this.pack.remote.instance}/emoji/${encodeURIComponent(this.pack.remote.baseName)}/${name}`
@ -105,115 +105,141 @@ const EmojiTab = {
}
},
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(() => {
this.packName = this.newPackName
this.newPackName = ''
})
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(() => {
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(() => {
delete this.editedMetadata[this.packName]
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(() => {
delete this.editedMetadata[this.packName]
this.deleteModalVisible = false
this.packName = ''
})
this.deleteModalVisible = false
this.packName = ''
})
},
metaEdited (prop) {
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
}
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]
})
// Update actual pack data
this.pack.pack = resp
// Delete edited pack data, should auto-update itself
delete this.editedMetadata[this.packName]
})
},
updatePackFiles (newFiles, packName) {
updatePackFiles(newFiles, packName) {
this.knownPacks[packName].files = newFiles
this.sortPackFiles(packName)
},
loadPacksPaginated (listFunction) {
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) }
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) }
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)
})
assign(allPacks, pageData.packs)
})
}
return resultingPromise
})
.then(() => allPacks)
.catch(data => {
.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)
}
})
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 => {
listRemotePacks() {
this.loadPacksPaginated(
this.$store.state.api.backendInteractor.listRemoteEmojiPacks,
)
.then((allPacks) => {
let inst = this.remotePackInstance
if (!inst.startsWith('http')) { inst = 'https://' + inst }
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
instance: instUrl.origin,
}
}
@ -222,89 +248,101 @@ const EmojiTab = {
this.sortPackFiles(`${pack}@${inst}`)
}
})
.catch(data => {
.catch((data) => {
this.displayError(data)
})
},
downloadRemotePack () {
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 => {
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') {
return this.refreshPackList()
} else {
this.displayError(resp.error)
return Promise.reject(resp)
}
}).then(() => {
})
.then(() => {
this.packName = this.remotePackDownloadAs
this.remotePackDownloadAs = ''
})
},
downloadRemoteURLPack () {
this.$store.state.api.backendInteractor.downloadRemoteEmojiPackZIP({
url: this.remotePackURL, packName: this.newPackName
})
.then(data => data.json())
.then(resp => {
downloadRemoteURLPack() {
this.$store.state.api.backendInteractor
.downloadRemoteEmojiPackZIP({
url: this.remotePackURL,
packName: this.newPackName,
})
.then((data) => data.json())
.then((resp) => {
if (resp === 'ok') {
return this.refreshPackList()
} else {
this.displayError(resp.error)
return Promise.reject(resp)
}
}).then(() => {
})
.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 => {
downloadRemoteFilePack() {
this.$store.state.api.backendInteractor
.downloadRemoteEmojiPackZIP({
file: this.remotePackFile[0],
packName: this.newPackName,
})
.then((data) => data.json())
.then((resp) => {
if (resp === 'ok') {
return this.refreshPackList()
} else {
this.displayError(resp.error)
return Promise.reject(resp)
}
}).then(() => {
})
.then(() => {
this.packName = this.newPackName
this.newPackName = ''
this.remotePackURL = ''
})
},
displayError (msg) {
displayError(msg) {
useInterfaceStore().pushGlobalNotice({
messageKey: 'admin_dash.emoji.error',
messageArgs: [msg],
level: 'error'
level: 'error',
})
},
sortPackFiles (nameOfPack) {
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
}, {})
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 () {
mounted() {
this.refreshPackList()
}
},
}
export default EmojiTab

View file

@ -10,10 +10,10 @@ import MapSetting from '../helpers/map_setting.vue'
import SharedComputedObject from '../helpers/shared_computed_object.js'
const FederationTab = {
provide () {
provide() {
return {
defaultDraftMode: true,
defaultSource: 'admin'
defaultSource: 'admin',
}
},
components: {
@ -24,11 +24,11 @@ const FederationTab = {
ListSetting,
ListTupleSetting,
GroupSetting,
MapSetting
MapSetting,
},
computed: {
...SharedComputedObject()
}
...SharedComputedObject(),
},
}
export default FederationTab

View file

@ -9,24 +9,20 @@ import { useInterfaceStore } from 'src/stores/interface'
import SharedComputedObject from '../helpers/shared_computed_object.js'
import { library } from '@fortawesome/fontawesome-svg-core'
import {
faGlobe
} from '@fortawesome/free-solid-svg-icons'
import { faGlobe } from '@fortawesome/free-solid-svg-icons'
library.add(
faGlobe
)
library.add(faGlobe)
const FrontendsTab = {
provide () {
provide() {
return {
defaultDraftMode: true,
defaultSource: 'admin'
defaultSource: 'admin',
}
},
data () {
data() {
return {
working: false
working: false,
}
},
components: {
@ -36,26 +32,26 @@ const FrontendsTab = {
StringSetting,
GroupSetting,
PanelLoading,
Popover
Popover,
},
created () {
created() {
if (this.user.rights.admin) {
this.$store.dispatch('loadFrontendsStuff')
}
},
computed: {
...SharedComputedObject(),
frontends () {
frontends() {
return this.$store.state.adminSettings.frontends
}
},
},
methods: {
canInstall (frontend) {
const fe = this.frontends.find(f => f.name === frontend.name)
canInstall(frontend) {
const fe = this.frontends.find((f) => f.name === frontend.name)
if (!fe) return false
return fe.refs.includes(frontend.ref)
},
getSuggestedRef (frontend) {
getSuggestedRef(frontend) {
if (this.adminDraft) {
const defaultFe = this.adminDraft[':pleroma'][':frontends'][':primary']
if (defaultFe?.name === frontend.name && this.canInstall(defaultFe)) {
@ -67,13 +63,14 @@ const FrontendsTab = {
return frontend.refs[0]
}
},
update (frontend, suggestRef) {
update(frontend, suggestRef) {
const ref = suggestRef || this.getSuggestedRef(frontend)
const { name } = frontend
const payload = { name, ref }
this.working = true
this.$store.state.api.backendInteractor.installFrontend({ payload })
this.$store.state.api.backendInteractor
.installFrontend({ payload })
.finally(() => {
this.working = false
})
@ -86,29 +83,32 @@ const FrontendsTab = {
messageKey: 'admin_dash.frontend.failure_installing_frontend',
messageArgs: {
version: name + '/' + ref,
reason: reason.error
reason: reason.error,
},
timeout: 5000
timeout: 5000,
})
} else {
useInterfaceStore().pushGlobalNotice({
level: 'success',
messageKey: 'admin_dash.frontend.success_installing_frontend',
messageArgs: {
version: name + '/' + ref
version: name + '/' + ref,
},
timeout: 2000
timeout: 2000,
})
}
})
},
setDefault (frontend, suggestRef) {
setDefault(frontend, suggestRef) {
const ref = suggestRef || this.getSuggestedRef(frontend)
const { name } = frontend
this.$store.commit('updateAdminDraft', { path: [':pleroma', ':frontends', ':primary'], value: { name, ref } })
}
}
this.$store.commit('updateAdminDraft', {
path: [':pleroma', ':frontends', ':primary'],
value: { name, ref },
})
},
},
}
export default FrontendsTab

View file

@ -13,10 +13,10 @@ import SharedComputedObject from '../helpers/shared_computed_object.js'
import { get } from 'lodash'
const HTTPTab = {
provide () {
provide() {
return {
defaultDraftMode: true,
defaultSource: 'admin'
defaultSource: 'admin',
}
},
components: {
@ -29,18 +29,23 @@ const HTTPTab = {
GroupSetting,
ListSetting,
TupleSetting,
ProxySetting
ProxySetting,
},
computed: {
...SharedComputedObject(),
sslOptions () {
const desc = get(this.$store.state.adminSettings.descriptions, ':pleroma.:http.:adapter.:ssl_options.:versions')
return new Set(desc.suggestions.map(option => ({
label: option.replace(':tlsv', 'TLS v'),
value: option
})))
sslOptions() {
const desc = get(
this.$store.state.adminSettings.descriptions,
':pleroma.:http.:adapter.:ssl_options.:versions',
)
return new Set(
desc.suggestions.map((option) => ({
label: option.replace(':tlsv', 'TLS v'),
value: option,
})),
)
},
}
},
}
export default HTTPTab

View file

@ -13,10 +13,10 @@ import SharedComputedObject from '../helpers/shared_computed_object.js'
import { get } from 'lodash'
const InstanceTab = {
provide () {
provide() {
return {
defaultDraftMode: true,
defaultSource: 'admin'
defaultSource: 'admin',
}
},
components: {
@ -29,25 +29,40 @@ const InstanceTab = {
ListSetting,
PWAManifestIconsSetting,
MapSetting,
GroupSetting
GroupSetting,
},
computed: {
...SharedComputedObject(),
providersOptions () {
const desc = get(this.$store.state.adminSettings.descriptions, [':pleroma', 'Pleroma.Web.Metadata', ':providers'])
return new Set(desc.suggestions.map(option => ({
label: option.replace('Pleroma.Web.Metadata.Providers.', ''),
value: option
})))
providersOptions() {
const desc = get(this.$store.state.adminSettings.descriptions, [
':pleroma',
'Pleroma.Web.Metadata',
':providers',
])
return new Set(
desc.suggestions.map((option) => ({
label: option.replace('Pleroma.Web.Metadata.Providers.', ''),
value: option,
})),
)
},
limitLocalContentOptions () {
const desc = get(this.$store.state.adminSettings.descriptions, [':pleroma', ':instance', ':limit_to_local_content'])
return new Set(desc.suggestions.map(option => ({
label: option !== 'false' ? this.$t('admin_dash.instance.' + option) : this.$t('general.no'),
value: option
})))
}
}
limitLocalContentOptions() {
const desc = get(this.$store.state.adminSettings.descriptions, [
':pleroma',
':instance',
':limit_to_local_content',
])
return new Set(
desc.suggestions.map((option) => ({
label:
option !== 'false'
? this.$t('admin_dash.instance.' + option)
: this.$t('general.no'),
value: option,
})),
)
},
},
}
export default InstanceTab

View file

@ -10,10 +10,10 @@ import ListSetting from '../helpers/list_setting.vue'
import SharedComputedObject from '../helpers/shared_computed_object.js'
const JobQueuesTab = {
provide () {
provide() {
return {
defaultDraftMode: true,
defaultSource: 'admin'
defaultSource: 'admin',
}
},
components: {
@ -24,11 +24,11 @@ const JobQueuesTab = {
TupleSetting,
AttachmentSetting,
GroupSetting,
ListSetting
ListSetting,
},
computed: {
...SharedComputedObject()
}
...SharedComputedObject(),
},
}
export default JobQueuesTab

View file

@ -10,11 +10,11 @@ const LimitsTab = {
BooleanSetting,
ChoiceSetting,
IntegerSetting,
StringSetting
StringSetting,
},
computed: {
...SharedComputedObject()
}
...SharedComputedObject(),
},
}
export default LimitsTab

View file

@ -12,10 +12,10 @@ import SharedComputedObject from '../helpers/shared_computed_object.js'
import { get } from 'lodash'
const LinksTab = {
provide () {
provide() {
return {
defaultDraftMode: true,
defaultSource: 'admin'
defaultSource: 'admin',
}
},
components: {
@ -26,84 +26,109 @@ const LinksTab = {
AttachmentSetting,
GroupSetting,
ListSetting,
Checkbox
Checkbox,
},
computed: {
classIsPresent () {
return this.$store.state.adminSettings.draft[':pleroma']['Pleroma.Formatter'][':class'] !== false
classIsPresent() {
return (
this.$store.state.adminSettings.draft[':pleroma']['Pleroma.Formatter'][
':class'
] !== false
)
},
relIsPresent () {
return this.$store.state.adminSettings.draft[':pleroma']['Pleroma.Formatter'][':rel'] !== false
relIsPresent() {
return (
this.$store.state.adminSettings.draft[':pleroma']['Pleroma.Formatter'][
':rel'
] !== false
)
},
truncateIsPresent () {
return this.$store.state.adminSettings.draft[':pleroma']['Pleroma.Formatter'][':truncate'] !== false
truncateIsPresent() {
return (
this.$store.state.adminSettings.draft[':pleroma']['Pleroma.Formatter'][
':truncate'
] !== false
)
},
truncateDescription () {
return get(this.$store.state.adminSettings.descriptions, [':pleroma', 'Pleroma.Formatter', ':truncate'])
truncateDescription() {
return get(this.$store.state.adminSettings.descriptions, [
':pleroma',
'Pleroma.Formatter',
':truncate',
])
},
ttlSettersOptions () {
const desc = get(this.$store.state.adminSettings.descriptions, ':pleroma.:rich_media.:ttl_setters')
return new Set(desc.suggestions.map(option => ({
label: option.replace('Pleroma.Web.RichMedia.Parser.TTL.', ''),
value: option
})))
ttlSettersOptions() {
const desc = get(
this.$store.state.adminSettings.descriptions,
':pleroma.:rich_media.:ttl_setters',
)
return new Set(
desc.suggestions.map((option) => ({
label: option.replace('Pleroma.Web.RichMedia.Parser.TTL.', ''),
value: option,
})),
)
},
parsersOptions () {
const desc = get(this.$store.state.adminSettings.descriptions, ':pleroma.:rich_media.:parsers')
return new Set(desc.suggestions.map(option => ({
label: option.replace('Pleroma.Web.RichMedia.Parsers.', ''),
value: option
})))
parsersOptions() {
const desc = get(
this.$store.state.adminSettings.descriptions,
':pleroma.:rich_media.:parsers',
)
return new Set(
desc.suggestions.map((option) => ({
label: option.replace('Pleroma.Web.RichMedia.Parsers.', ''),
value: option,
})),
)
},
validateTLDOptions () {
return [{
label: this.$t('general.yes'),
value: true
}, {
label: this.$t('general.no'),
value: false
}, {
label: this.$t('admin_dash.links.no_scheme'),
value: ':no_scheme'
}]
validateTLDOptions() {
return [
{
label: this.$t('general.yes'),
value: true,
},
{
label: this.$t('general.no'),
value: false,
},
{
label: this.$t('admin_dash.links.no_scheme'),
value: ':no_scheme',
},
]
},
mediaProxyEnabled () {
return this.$store.state.adminSettings.draft[':pleroma'][':media_proxy'][':enabled']
mediaProxyEnabled() {
return this.$store.state.adminSettings.draft[':pleroma'][':media_proxy'][
':enabled'
]
},
mediaInvalidationProvider () {
return this.$store.state.adminSettings.draft[':pleroma'][':media_proxy'][':invalidation'][':provider']
mediaInvalidationProvider() {
return this.$store.state.adminSettings.draft[':pleroma'][':media_proxy'][
':invalidation'
][':provider']
},
...SharedComputedObject()
...SharedComputedObject(),
},
methods: {
checkRel (e) {
this.$store.commit(
'updateAdminDraft',
{
path: [':pleroma','Pleroma.Formatter',':rel'],
value: e ? '' : false
}
)
checkRel(e) {
this.$store.commit('updateAdminDraft', {
path: [':pleroma', 'Pleroma.Formatter', ':rel'],
value: e ? '' : false,
})
},
checkClass (e) {
this.$store.commit(
'updateAdminDraft',
{
path: [':pleroma','Pleroma.Formatter',':class'],
value: e ? '' : false
}
)
checkClass(e) {
this.$store.commit('updateAdminDraft', {
path: [':pleroma', 'Pleroma.Formatter', ':class'],
value: e ? '' : false,
})
},
checkTruncate (e) {
this.$store.commit(
'updateAdminDraft',
{
path: [':pleroma','Pleroma.Formatter',':truncate'],
value: e ? 20 : false
}
)
}
}
checkTruncate(e) {
this.$store.commit('updateAdminDraft', {
path: [':pleroma', 'Pleroma.Formatter', ':truncate'],
value: e ? 20 : false,
})
},
},
}
export default LinksTab

View file

@ -9,10 +9,10 @@ import AttachmentSetting from '../helpers/attachment_setting.vue'
import SharedComputedObject from '../helpers/shared_computed_object.js'
const MailerTab = {
provide () {
provide() {
return {
defaultDraftMode: true,
defaultSource: 'admin'
defaultSource: 'admin',
}
},
components: {
@ -22,44 +22,50 @@ const MailerTab = {
StringSetting,
AttachmentSetting,
ColorSetting,
GroupSetting
GroupSetting,
},
computed: {
adaptersLabels () {
adaptersLabels() {
const prefix = 'Swoosh.Adapters.'
const descriptions = this.$store.state.adminSettings.descriptions
const options = descriptions[':pleroma']['Pleroma.Emails.Mailer'][':adapter'].suggestions
const options =
descriptions[':pleroma']['Pleroma.Emails.Mailer'][':adapter']
.suggestions
return Object.fromEntries(options.map(value => [
value, value.replace(prefix, '')
]))
return Object.fromEntries(
options.map((value) => [value, value.replace(prefix, '')]),
)
},
startTLSLabels () {
startTLSLabels() {
return {
':always': this.$t('admin_dash.generic_enforcement.always'),
':if_available': this.$t('admin_dash.generic_enforcement.if_available'),
':never': this.$t('admin_dash.generic_enforcement.never')
':never': this.$t('admin_dash.generic_enforcement.never'),
}
// return Object.fromEntries(options.map(value => [
// value, value.replace(prefix, '')
// ]))
},
adapter () {
return this.$store.state.adminSettings.draft[':pleroma']['Pleroma.Emails.Mailer'][':adapter']
adapter() {
return this.$store.state.adminSettings.draft[':pleroma'][
'Pleroma.Emails.Mailer'
][':adapter']
},
mailerEnabled () {
return this.$store.state.adminSettings.draft[':pleroma']['Pleroma.Emails.Mailer'][':enabled']
mailerEnabled() {
return this.$store.state.adminSettings.draft[':pleroma'][
'Pleroma.Emails.Mailer'
][':enabled']
},
...SharedComputedObject()
...SharedComputedObject(),
},
methods: {
adapterHasKey (key) {
adapterHasKey(key) {
const descriptions = this.$store.state.adminSettings.descriptions
const mailerStuff = descriptions[':pleroma']['Pleroma.Emails.Mailer']
const adapterStuff = mailerStuff[':subgroup,' + this.adapter]
return Object.hasOwn(adapterStuff, key)
}
}
},
},
}
export default MailerTab

View file

@ -9,10 +9,10 @@ import ListSetting from '../helpers/list_setting.vue'
import SharedComputedObject from '../helpers/shared_computed_object.js'
const MediaProxyTab = {
provide () {
provide() {
return {
defaultDraftMode: true,
defaultSource: 'admin'
defaultSource: 'admin',
}
},
components: {
@ -22,17 +22,21 @@ const MediaProxyTab = {
StringSetting,
AttachmentSetting,
GroupSetting,
ListSetting
ListSetting,
},
computed: {
mediaProxyEnabled () {
return this.$store.state.adminSettings.draft[':pleroma'][':media_proxy'][':enabled']
mediaProxyEnabled() {
return this.$store.state.adminSettings.draft[':pleroma'][':media_proxy'][
':enabled'
]
},
mediaInvalidationProvider () {
return this.$store.state.adminSettings.draft[':pleroma'][':media_proxy'][':invalidation'][':provider']
mediaInvalidationProvider() {
return this.$store.state.adminSettings.draft[':pleroma'][':media_proxy'][
':invalidation'
][':provider']
},
...SharedComputedObject()
}
...SharedComputedObject(),
},
}
export default MediaProxyTab

View file

@ -8,19 +8,15 @@ import ListSetting from '../helpers/list_setting.vue'
import SharedComputedObject from '../helpers/shared_computed_object.js'
import { library } from '@fortawesome/fontawesome-svg-core'
import {
faGlobe
} from '@fortawesome/free-solid-svg-icons'
import { faGlobe } from '@fortawesome/free-solid-svg-icons'
library.add(
faGlobe
)
library.add(faGlobe)
const MonitoringTab = {
provide () {
provide() {
return {
defaultDraftMode: true,
defaultSource: 'admin'
defaultSource: 'admin',
}
},
components: {
@ -30,13 +26,12 @@ const MonitoringTab = {
StringSetting,
AttachmentSetting,
GroupSetting,
ListSetting
ListSetting,
},
computed: {
...SharedComputedObject()
...SharedComputedObject(),
},
methods: {
}
methods: {},
}
export default MonitoringTab

View file

@ -12,10 +12,10 @@ import MapSetting from '../helpers/map_setting.vue'
import SharedComputedObject from '../helpers/shared_computed_object.js'
const OtherTab = {
provide () {
provide() {
return {
defaultDraftMode: true,
defaultSource: 'admin'
defaultSource: 'admin',
}
},
components: {
@ -28,11 +28,11 @@ const OtherTab = {
ListSetting,
PWAManifestIconsSetting,
MapSetting,
GroupSetting
GroupSetting,
},
computed: {
...SharedComputedObject()
}
...SharedComputedObject(),
},
}
export default OtherTab

View file

@ -12,10 +12,10 @@ import MapSetting from '../helpers/map_setting.vue'
import SharedComputedObject from '../helpers/shared_computed_object.js'
const PostsTab = {
provide () {
provide() {
return {
defaultDraftMode: true,
defaultSource: 'admin'
defaultSource: 'admin',
}
},
components: {
@ -28,11 +28,11 @@ const PostsTab = {
ListSetting,
PWAManifestIconsSetting,
MapSetting,
GroupSetting
GroupSetting,
},
computed: {
...SharedComputedObject()
}
...SharedComputedObject(),
},
}
export default PostsTab

View file

@ -3,18 +3,18 @@ import RateSetting from '../helpers/rate_setting.vue'
import SharedComputedObject from '../helpers/shared_computed_object.js'
const RatesTab = {
provide () {
provide() {
return {
defaultDraftMode: true,
defaultSource: 'admin'
defaultSource: 'admin',
}
},
components: {
RateSetting,
},
computed: {
...SharedComputedObject()
}
...SharedComputedObject(),
},
}
export default RatesTab

View file

@ -10,10 +10,10 @@ import ListSetting from '../helpers/list_setting.vue'
import SharedComputedObject from '../helpers/shared_computed_object.js'
const RegistrationsTab = {
provide () {
provide() {
return {
defaultDraftMode: true,
defaultSource: 'admin'
defaultSource: 'admin',
}
},
components: {
@ -24,11 +24,11 @@ const RegistrationsTab = {
TupleSetting,
AttachmentSetting,
GroupSetting,
ListSetting
ListSetting,
},
computed: {
...SharedComputedObject()
}
...SharedComputedObject(),
},
}
export default RegistrationsTab

View file

@ -6,41 +6,47 @@ import StringSetting from '../helpers/string_setting.vue'
import SharedComputedObject from '../helpers/shared_computed_object.js'
const UploadsTab = {
provide () {
provide() {
return {
defaultDraftMode: true,
defaultSource: 'admin'
defaultSource: 'admin',
}
},
data () {
data() {
return {
uploaders: [{
key: 'Pleroma.Uploaders.Local',
value: 'Pleroma.Uploaders.Local',
label: this.$t('admin_dash.uploads.local_uploader')
}, {
key: 'Pleroma.Uploaders.IPFS',
value: 'Pleroma.Uploaders.IPFS',
label: 'IPFS'
}, {
key: 'Pleroma.Uploaders.S3',
value: 'Pleroma.Uploaders.S3',
label: 'S3'
}]
uploaders: [
{
key: 'Pleroma.Uploaders.Local',
value: 'Pleroma.Uploaders.Local',
label: this.$t('admin_dash.uploads.local_uploader'),
},
{
key: 'Pleroma.Uploaders.IPFS',
value: 'Pleroma.Uploaders.IPFS',
label: 'IPFS',
},
{
key: 'Pleroma.Uploaders.S3',
value: 'Pleroma.Uploaders.S3',
label: 'S3',
},
],
}
},
components: {
BooleanSetting,
ChoiceSetting,
IntegerSetting,
StringSetting
StringSetting,
},
computed: {
uploader () {
return this.$store.state.adminSettings.draft[':pleroma']['Pleroma.Upload'][':uploader']
uploader() {
return this.$store.state.adminSettings.draft[':pleroma'][
'Pleroma.Upload'
][':uploader']
},
...SharedComputedObject()
}
...SharedComputedObject(),
},
}
export default UploadsTab

View file

@ -11,34 +11,36 @@ export default {
acceptTypes: {
type: String,
required: false,
default: 'image/*'
}
default: 'image/*',
},
},
components: {
...Setting.components,
MediaUpload,
Attachment
Attachment,
},
computed: {
...Setting.computed,
attachment () {
attachment() {
const path = this.realDraftMode ? this.draft : this.state
// The "server" part is primarily for local dev, but could be useful for alt-domain or multiuser usage.
const url = path.includes('://') ? path : this.$store.state.instance.server + path
const url = path.includes('://')
? path
: this.$store.state.instance.server + path
return {
mimetype: fileTypeExt(url),
url
url,
}
}
},
},
methods: {
...Setting.methods,
setMediaFile (fileInfo) {
setMediaFile(fileInfo) {
if (this.realDraftMode) {
this.draft = fileInfo.url
} else {
this.configSink(this.path, fileInfo.url)
}
}
}
},
},
}

View file

@ -5,27 +5,27 @@ export default {
...Setting,
props: {
...Setting.props,
indeterminateState: [String, Object]
indeterminateState: [String, Object],
},
components: {
...Setting.components,
Checkbox
Checkbox,
},
computed: {
...Setting.computed,
isIndeterminate () {
isIndeterminate() {
return this.visibleState === this.indeterminateState
}
},
},
methods: {
...Setting.methods,
getValue (e) {
getValue(e) {
// Basic tri-state toggle implementation
if (!!this.indeterminateState && !e && this.visibleState === true) {
// If we have indeterminate state, switching from true to false first goes through indeterminate
return this.indeterminateState
}
return e
}
}
},
},
}

View file

@ -5,50 +5,50 @@ export default {
...Setting,
components: {
...Setting.components,
Select
Select,
},
props: {
...Setting.props,
overrideOptions: {
type: Boolean,
required: false
required: false,
},
options: {
type: Array,
required: false
required: false,
},
optionLabelMap: {
type: Object,
required: false,
default: {}
}
default: {},
},
},
computed: {
...Setting.computed,
realOptions () {
realOptions() {
if (this.overrideOptions) {
return this.options
}
if (this.realSource === 'admin') {
if (
!this.backendDescriptionSuggestions?.length ||
this.backendDescriptionSuggestions?.length === 0
this.backendDescriptionSuggestions?.length === 0
) {
return this.options
}
return this.backendDescriptionSuggestions.map(x => ({
return this.backendDescriptionSuggestions.map((x) => ({
key: x,
value: x,
label: this.optionLabelMap[x] || x
label: this.optionLabelMap[x] || x,
}))
}
return this.options
}
},
},
methods: {
...Setting.methods,
getValue (e) {
getValue(e) {
return e
}
}
},
},
}

View file

@ -5,12 +5,12 @@ export default {
...Setting,
components: {
...Setting.components,
ColorInput
ColorInput,
},
methods: {
...Setting.methods,
getValue (e) {
getValue(e) {
return e
}
}
},
},
}

View file

@ -61,13 +61,11 @@ import Popover from 'src/components/popover/popover.vue'
import { library } from '@fortawesome/fontawesome-svg-core'
import { faWrench } from '@fortawesome/free-solid-svg-icons'
library.add(
faWrench
)
library.add(faWrench)
export default {
components: { Popover },
props: ['changed']
props: ['changed'],
}
</script>

View file

@ -160,53 +160,53 @@ export default {
props: {
placement: {
type: String,
required: true
required: true,
},
newUpload: Boolean,
title: {
type: String,
required: true
required: true,
},
packName: {
type: String,
required: true
required: true,
},
shortcode: {
type: String,
// Only exists when this is not a new upload
default: ''
default: '',
},
file: {
type: String,
// Only exists when this is not a new upload
default: ''
default: '',
},
// Only exists for emojis from remote packs
remote: {
type: Object,
default: undefined
default: undefined,
},
knownLocalPacks: {
type: Object,
default: undefined
}
default: undefined,
},
},
emits: ['updatePackFiles', 'displayError'],
data () {
data() {
return {
uploadFile: [],
uploadURL: '',
editedShortcode: this.shortcode,
editedFile: this.file,
deleteModalVisible: false,
copyToPack: ''
copyToPack: '',
}
},
computed: {
emojiPreview () {
emojiPreview() {
if (this.newUpload && this.uploadFile.length > 0) {
return URL.createObjectURL(this.uploadFile[0])
} else if (this.newUpload && this.uploadURL !== '') {
@ -217,73 +217,92 @@ export default {
return null
},
isEdited () {
return !this.newUpload && (this.editedShortcode !== this.shortcode || this.editedFile !== this.file)
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 === ""
}
return this.newUpload
? this.uploadURL === '' && this.uploadFile.length == 0
: !this.isEdited
else return this.copyToPack === ''
},
},
methods: {
saveEditedEmoji () {
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)
}
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))
return resp.json()
})
.then((resp) => this.$emit('updatePackFiles', resp))
},
uploadEmoji () {
uploadEmoji() {
let packName = this.remote === undefined ? this.packName : this.copyToPack
this.$store.state.api.backendInteractor.addNewEmojiFile({
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 => {
if (resp.error !== undefined) {
this.$emit('displayError', resp.error)
return
}
this.$store.state.api.backendInteractor
.addNewEmojiFile({
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) => {
if (resp.error !== undefined) {
this.$emit('displayError', resp.error)
return
}
this.$emit('updatePackFiles', resp, packName)
this.$refs.emojiPopover.hidePopover()
this.$emit('updatePackFiles', resp, packName)
this.$refs.emojiPopover.hidePopover()
this.editedFile = ''
this.editedShortcode = ''
this.uploadFile = []
})
this.editedFile = ''
this.editedShortcode = ''
this.uploadFile = []
})
},
revertEmoji () {
revertEmoji() {
this.editedFile = this.file
this.editedShortcode = this.shortcode
},
deleteEmoji () {
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.$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, this.packName)
})
}
}
this.$emit('updatePackFiles', resp, this.packName)
})
},
},
}
</script>

View file

@ -10,7 +10,7 @@
import NumberSetting from './number_setting.vue'
export default {
components: {
NumberSetting
}
NumberSetting,
},
}
</script>

View file

@ -22,12 +22,10 @@ import Popover from 'src/components/popover/popover.vue'
import { library } from '@fortawesome/fontawesome-svg-core'
import { faCircleQuestion } from '@fortawesome/free-solid-svg-icons'
library.add(
faCircleQuestion
)
library.add(faCircleQuestion)
export default {
components: { Popover }
components: { Popover },
}
</script>

View file

@ -11,7 +11,7 @@
import NumberSetting from './number_setting.vue'
export default {
components: {
NumberSetting
}
NumberSetting,
},
}
</script>

View file

@ -3,43 +3,43 @@ import Setting from './setting.js'
export default {
...Setting,
data () {
data() {
return {
newValue: '',
}
},
components: {
...Setting.components,
Checkbox
Checkbox,
},
props: {
...Setting.props,
ignoreSuggestions: {
required: false,
type: Boolean
type: Boolean,
},
overrideAvailableOptions: {
required: false,
type: Boolean
type: Boolean,
},
options: {
required: false,
type: Set
type: Set,
},
allowNew: {
required: false,
type: Boolean,
default: true
default: true,
},
forceNew: {
required: false,
type: Boolean,
default: false
}
default: false,
},
},
computed: {
...Setting.computed,
showNew () {
showNew() {
if (this.forceNew) return true
if (!this.allowNew) return false
@ -52,10 +52,10 @@ export default {
return true
}
},
valueSet () {
valueSet() {
return new Set(this.visibleState)
},
suggestionsSet () {
suggestionsSet() {
const suggestions = this.backendDescriptionSuggestions
if (suggestions) {
return new Set(suggestions)
@ -63,14 +63,14 @@ export default {
return new Set()
}
},
extraEntries () {
extraEntries() {
if (this.ignoreSuggestions) return [...this.valueSet.values()]
if (!this.suggestionsSet) return []
return [...this.valueSet.values()].filter((x) => {
return !this.builtinEntriesValueSet.has(x)
})
},
builtinEntries () {
builtinEntries() {
if (this.ignoreSuggestions) return []
if (this.overrideAvailableOptions) {
return [...this.options]
@ -80,19 +80,19 @@ export default {
const builtins = [...this.suggestionsSet.values()]
return builtins.map((option) => ({
label: option,
value: option
value: option,
}))
},
builtinEntriesValueSet () {
return new Set(this.builtinEntries.map(x => x.value))
}
builtinEntriesValueSet() {
return new Set(this.builtinEntries.map((x) => x.value))
},
},
methods: {
...Setting.methods,
optionPresent (option) {
optionPresent(option) {
return this.valueSet.has(option)
},
getValue ({ event, value, index, eventType }) {
getValue({ event, value, index, eventType }) {
switch (eventType) {
case 'toggle': {
this.newValue = ''
@ -128,6 +128,6 @@ export default {
return [...pre, string, ...post]
}
}
}
}
},
},
}

View file

@ -2,14 +2,14 @@ import ListSetting from './list_setting.js'
export default {
...ListSetting,
data () {
data() {
return {
newValue: ['','']
newValue: ['', ''],
}
},
methods: {
...ListSetting.methods,
getValue ({ event, index, eventType, tuple }) {
getValue({ event, index, eventType, tuple }) {
switch (eventType) {
case 'add': {
if (!this.newValue[0] || !this.newValue[1]) return this.visibleState
@ -39,6 +39,6 @@ export default {
}
}
}
}
}
},
},
}

View file

@ -7,28 +7,31 @@ export default {
allowNew: {
required: false,
type: Boolean,
default: true
}
default: true,
},
},
data () {
data() {
return {
newValue: ['',''] // avoiding extra complexity by just using an array instead of an object
newValue: ['', ''], // avoiding extra complexity by just using an array instead of an object
}
},
computed: {
...Setting.computed,
// state that we'll show in the UI, i.e. transforming map into list
displayState () {
displayState() {
return Object.entries(this.visibleState)
}
},
},
methods: {
...Setting.methods,
getValue ({ event, key, eventType, isKey }) {
getValue({ event, key, eventType, isKey }) {
switch (eventType) {
case 'add': {
if (key === '') return this.visibleState
const res = {...this.visibleState, ...Object.fromEntries([this.newValue])}
const res = {
...this.visibleState,
...Object.fromEntries([this.newValue]),
}
this.newValue = ['', '']
return res
}
@ -36,35 +39,35 @@ export default {
case 'remove': {
// initial state for this type is empty array
if (Array.isArray(this.visibleState)) return this.visibleState
const newEntries = Object.entries(this.visibleState).filter(([k]) => k !== key)
const newEntries = Object.entries(this.visibleState).filter(
([k]) => k !== key,
)
if (newEntries.length === 0 ) return []
if (newEntries.length === 0) return []
return Object.fromEntries(newEntries)
}
case 'edit': {
const string = event.target.value
const newEntries = Object
.entries(this.visibleState)
.map(([k, v]) => {
if (isKey) {
if (k === key) {
return [string, v]
} else {
return [k, v]
}
const newEntries = Object.entries(this.visibleState).map(([k, v]) => {
if (isKey) {
if (k === key) {
return [string, v]
} else {
if (k === key) {
return [k, string]
} else {
return [k, v]
}
return [k, v]
}
})
} else {
if (k === key) {
return [k, string]
} else {
return [k, v]
}
}
})
return Object.fromEntries(newEntries)
}
}
}
}
},
},
}

View file

@ -27,9 +27,7 @@ import Popover from 'src/components/popover/popover.vue'
import { library } from '@fortawesome/fontawesome-svg-core'
import { faWrench } from '@fortawesome/free-solid-svg-icons'
library.add(
faWrench
)
library.add(faWrench)
export default {
components: { Popover },
@ -37,9 +35,9 @@ export default {
changed: Boolean,
messageKey: {
type: String,
default: 'settings.setting_changed'
}
}
default: 'settings.setting_changed',
},
},
}
</script>

View file

@ -7,33 +7,33 @@ export default {
min: {
type: Number,
required: false,
default: 1
default: 1,
},
max: {
type: Number,
required: false,
default: 1
default: 1,
},
step: {
type: Number,
required: false,
default: 1
default: 1,
},
truncate: {
type: Number,
required: false,
default: 1
}
default: 1,
},
},
methods: {
...Setting.methods,
getValue (e) {
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)
}
}
},
},
}

View file

@ -27,13 +27,11 @@ import Popover from 'src/components/popover/popover.vue'
import { library } from '@fortawesome/fontawesome-svg-core'
import { faServer } from '@fortawesome/free-solid-svg-icons'
library.add(
faServer
)
library.add(faServer)
export default {
components: { Popover },
props: ['isProfile']
props: ['isProfile'],
}
</script>

View file

@ -1,18 +1,19 @@
import Checkbox from 'src/components/checkbox/checkbox.vue'
import Setting from './setting.js'
const getUrl = state => state?.tuple ? state.tuple[1] + ':' + state.tuple[2] : state
const getSocks = state => state?.tuple
const getUrl = (state) =>
state?.tuple ? state.tuple[1] + ':' + state.tuple[2] : state
const getSocks = (state) => state?.tuple
export default {
...Setting,
data () {
data() {
return {
urlField: '',
socksField: false
socksField: false,
}
},
created () {
created() {
Setting.created()
this.urlField = getUrl(this.realDraftMode ? this.draft : this.state)
this.socksField = getSocks(this.realDraftMode ? this.draft : this.state)
@ -20,23 +21,23 @@ export default {
computed: {
...Setting.computed,
// state that we'll show in the UI, i.e. transforming map into list
displayState () {
displayState() {
if (this.visibleState?.tuple) {
return this.visibleState.tuple[1] + ':' + this.visibleState.tuple[2]
}
return this.visibleState
},
socksState () {
socksState() {
return getSocks(this.visibleState)
}
},
},
components: {
...Setting.components,
Checkbox
Checkbox,
},
methods: {
...Setting.methods,
getValue ({ event, isProxy}) {
getValue({ event, isProxy }) {
if (isProxy) {
this.socksField = event
} else {
@ -44,10 +45,10 @@ export default {
}
if (this.socksField) {
return { tuple: [ ':socks5', ...this.urlField.split(':') ] }
return { tuple: [':socks5', ...this.urlField.split(':')] }
} else {
return this.urlField
}
}
}
},
},
}

View file

@ -12,60 +12,62 @@ export default {
...Setting.components,
Select,
Attachment,
MediaUpload
MediaUpload,
},
computed: {
...Setting.computed,
purposeOptions () {
return ['any','monochrome','maskable'].map(value => ({
purposeOptions() {
return ['any', 'monochrome', 'maskable'].map((value) => ({
value,
key: value,
label: this.$t('admin_dash.instance.pwa.icon.' + value)
label: this.$t('admin_dash.instance.pwa.icon.' + value),
}))
}
},
},
methods: {
...Setting.methods,
attachment (e) {
attachment(e) {
const path = e[':src']
if (!path) {
return {
mimetype: '',
url: ''
url: '',
}
}
const url = path.includes('://') ? path : this.$store.state.instance.server + path
const url = path.includes('://')
? path
: this.$store.state.instance.server + path
return {
mimetype: fileTypeExt(url),
url
url,
}
},
setMediaFile ({ event, index }) {
setMediaFile({ event, index }) {
this.update({
event: {
target: {
value: event.url
value: event.url,
},
},
index,
eventType: 'edit',
field: ':src'
field: ':src',
})
},
setPurpose ({ event, index }) {
setPurpose({ event, index }) {
this.update({
event: {
target: {
value: event
value: event,
},
},
index,
eventType: 'edit',
field: ':purpose'
field: ':purpose',
})
},
getValue ({ event, field, index, eventType }) {
getValue({ event, field, index, eventType }) {
switch (eventType) {
case 'add': {
const res = [...this.visibleState, {}]
@ -85,7 +87,7 @@ export default {
const item = clone(this.visibleState[index])
const string = event.target.value
if (!string) {
if (!string) {
delete item[field]
} else {
item[field] = string
@ -94,6 +96,6 @@ export default {
return [...pre, item, ...post]
}
}
}
}
},
},
}

View file

@ -3,40 +3,40 @@ import Setting from './setting.js'
export default {
...Setting,
data () {
data() {
return {
newValue: '',
}
},
components: {
...Setting.components,
Checkbox
Checkbox,
},
props: {
...Setting.props
...Setting.props,
},
computed: {
...Setting.computed,
isSeparate () {
isSeparate() {
// [[a1, b1], [a2, b2]] vs [a, b]
return Array.isArray(this.visibleState[0])
},
normalizedState () {
normalizedState() {
if (this.isSeparate) {
return this.visibleState.map(y => y.map(x => Number(x) || 0))
return this.visibleState.map((y) => y.map((x) => Number(x) || 0))
} else {
return [this.visibleState.map(x => Number(x) || 0)]
return [this.visibleState.map((x) => Number(x) || 0)]
}
}
},
},
methods: {
...Setting.methods,
getValue ({ event, side, index, eventType }) {
getValue({ event, side, index, eventType }) {
if (eventType === 'edit') {
const value = Number(event.target.value)
if (Number.isNaN(value)) return this.visibleState
const newVal = [...this.normalizedState.map(x => [...x])]
const newVal = [...this.normalizedState.map((x) => [...x])]
newVal[side][index] = value
return newVal
}
@ -48,6 +48,6 @@ export default {
return [this.normalizedState[0]]
}
}
}
}
},
},
}

View file

@ -7,119 +7,126 @@ export default {
components: {
ModifiedIndicator,
DraftButtons,
ProfileSettingIndicator
ProfileSettingIndicator,
},
props: {
modelValue: {
type: String,
default: null
default: null,
},
path: {
type: [String, Array],
required: false
required: false,
},
showDescription: {
type: Boolean,
required: false
required: false,
},
descriptionPathOverride: {
type: [String, Array],
required: false
required: false,
},
suggestions: {
type: [String, Array],
required: false
required: false,
},
subgroup: {
type: String,
required: false
required: false,
},
disabled: {
type: Boolean,
default: false
default: false,
},
parentPath: {
type: [String, Array]
type: [String, Array],
},
parentInvert: {
type: Boolean,
default: false
default: false,
},
expert: {
type: [Number, String],
default: 0
default: 0,
},
source: {
type: String,
default: undefined
default: undefined,
},
hideDraftButtons: { // this is for the weird backend hybrid (Boolean|String or Boolean|Number) settings
hideDraftButtons: {
// this is for the weird backend hybrid (Boolean|String or Boolean|Number) settings
required: false,
type: Boolean
type: Boolean,
},
hideLabel: {
type: Boolean
type: Boolean,
},
hideDescription: {
type: Boolean
type: Boolean,
},
swapDescriptionAndLabel: {
type: Boolean
type: Boolean,
},
backendDescriptionPath: {
type: [String, Array]
type: [String, Array],
},
overrideBackendDescription: {
type: Boolean
type: Boolean,
},
overrideBackendDescriptionLabel: {
type: [Boolean, String]
type: [Boolean, String],
},
draftMode: {
type: Boolean,
default: undefined
default: undefined,
},
timedApplyMode: {
type: Boolean,
default: false
}
default: false,
},
},
inject: {
defaultSource: {
default: 'default'
default: 'default',
},
defaultDraftMode: {
default: false
}
default: false,
},
},
data () {
data() {
return {
localDraft: null
localDraft: null,
}
},
created () {
if (this.realDraftMode && (this.realSource !== 'admin' || this.path == null)) {
created() {
if (
this.realDraftMode &&
(this.realSource !== 'admin' || this.path == null)
) {
this.draft = cloneDeep(this.state)
}
},
computed: {
draft: {
get () {
get() {
if (this.realSource === 'admin' || this.path == null) {
return get(this.$store.state.adminSettings.draft, this.canonPath)
} else {
return this.localDraft
}
},
set (value) {
set(value) {
if (this.realSource === 'admin' || this.path == null) {
this.$store.commit('updateAdminDraft', { path: this.canonPath, value })
this.$store.commit('updateAdminDraft', {
path: this.canonPath,
value,
})
} else {
this.localDraft = value
}
}
},
},
state () {
state() {
if (this.path == null) {
return this.modelValue
}
@ -130,71 +137,93 @@ export default {
return value
}
},
visibleState () {
visibleState() {
return this.realDraftMode ? this.draft : this.state
},
realSource () {
realSource() {
return this.source || this.defaultSource
},
realDraftMode () {
return typeof this.draftMode === 'undefined' ? this.defaultDraftMode : this.draftMode
realDraftMode() {
return typeof this.draftMode === 'undefined'
? this.defaultDraftMode
: this.draftMode
},
backendDescription () {
return get(this.$store.state.adminSettings.descriptions, this.descriptionPath)
backendDescription() {
return get(
this.$store.state.adminSettings.descriptions,
this.descriptionPath,
)
},
backendDescriptionLabel () {
backendDescriptionLabel() {
if (this.realSource !== 'admin') return ''
if (this.overrideBackendDescriptionLabel !== '' && typeof this.overrideBackendDescriptionLabel === 'string') {
if (
this.overrideBackendDescriptionLabel !== '' &&
typeof this.overrideBackendDescriptionLabel === 'string'
) {
return this.overrideBackendDescriptionLabel
}
if (!this.backendDescription || this.overrideBackendDescriptionLabel) {
return this.$t([
'admin_dash',
'temp_overrides',
...this.canonPath.map(p => p.replace(/\./g, '_DOT_')),
'label'
].join('.'))
return this.$t(
[
'admin_dash',
'temp_overrides',
...this.canonPath.map((p) => p.replace(/\./g, '_DOT_')),
'label',
].join('.'),
)
} else {
return this.swapDescriptionAndLabel
? this.backendDescription?.description
: this.backendDescription?.label
}
},
backendDescriptionDescription () {
backendDescriptionDescription() {
if (this.description) return this.description
if (this.realSource !== 'admin') return ''
if (this.hideDescription) return null
if (!this.backendDescription || this.overrideBackendDescription) {
return this.$t([
'admin_dash',
'temp_overrides',
...this.canonPath.map(p => p.replace(/\./g, '_DOT_')),
'description'
].join('.'))
return this.$t(
[
'admin_dash',
'temp_overrides',
...this.canonPath.map((p) => p.replace(/\./g, '_DOT_')),
'description',
].join('.'),
)
} else {
return this.swapDescriptionAndLabel
? this.backendDescription?.label
: this.backendDescription?.description
}
},
backendDescriptionSuggestions () {
backendDescriptionSuggestions() {
return this.backendDescription?.suggestions || this.suggestions
},
shouldBeDisabled () {
shouldBeDisabled() {
if (this.path == null) {
return this.disabled
}
let parentValue = null
if (this.parentPath !== undefined && this.realSource === 'admin') {
if (this.realDraftMode) {
parentValue = get(this.$store.state.adminSettings.draft, this.parentPath)
parentValue = get(
this.$store.state.adminSettings.draft,
this.parentPath,
)
} else {
parentValue = get(this.configSource, this.parentPath)
}
}
return this.disabled || (parentValue !== null ? (this.parentInvert ? parentValue : !parentValue) : false)
return (
this.disabled ||
(parentValue !== null
? this.parentInvert
? parentValue
: !parentValue
: false)
)
},
configSource () {
configSource() {
switch (this.realSource) {
case 'profile':
return this.$store.state.profileConfig
@ -204,24 +233,31 @@ export default {
return this.$store.getters.mergedConfig
}
},
configSink () {
configSink() {
if (this.path == null) {
return (k, v) => this.$emit('update:modelValue', v)
}
switch (this.realSource) {
case 'profile':
return (k, v) => this.$store.dispatch('setProfileOption', { name: k, value: v })
return (k, v) =>
this.$store.dispatch('setProfileOption', { name: k, value: v })
case 'admin':
return (k, v) => this.$store.dispatch('pushAdminSetting', { path: k, value: v })
return (k, v) =>
this.$store.dispatch('pushAdminSetting', { path: k, value: v })
default:
if (this.timedApplyMode) {
return (k, v) => this.$store.dispatch('setOptionTemporarily', { name: k, value: v })
return (k, v) =>
this.$store.dispatch('setOptionTemporarily', {
name: k,
value: v,
})
} else {
return (k, v) => this.$store.dispatch('setOption', { name: k, value: v })
return (k, v) =>
this.$store.dispatch('setOption', { name: k, value: v })
}
}
},
defaultState () {
defaultState() {
switch (this.realSource) {
case 'profile':
return {}
@ -229,10 +265,10 @@ export default {
return get(this.$store.getters.defaultConfig, this.path)
}
},
isProfileSetting () {
isProfileSetting() {
return this.realSource === 'profile'
},
isChanged () {
isChanged() {
if (this.path == null) return false
switch (this.realSource) {
case 'profile':
@ -242,24 +278,24 @@ export default {
return this.state !== this.defaultState
}
},
canonPath () {
canonPath() {
if (this.path == null) return null
return Array.isArray(this.path) ? this.path : this.path.split('.')
},
descriptionPath () {
descriptionPath() {
if (this.path == null) return null
if (this.descriptionPathOverride) return this.descriptionPathOverride
const path = Array.isArray(this.path) ? this.path : this.path.split('.')
if (this.subgroup) {
return [
...path.slice(0, path.length - 1),
':subgroup,' + this.subgroup,
...path.slice(path.length - 1)
...path.slice(0, path.length - 1),
':subgroup,' + this.subgroup,
...path.slice(path.length - 1),
]
}
return path
},
isDirty () {
isDirty() {
if (this.path == null) return false
if (this.realSource === 'admin' && this.canonPath.length > 3) {
return false // should not show draft buttons for "grouped" values
@ -267,47 +303,59 @@ export default {
return this.realDraftMode && !isEqual(this.draft, this.state)
}
},
canHardReset () {
return this.realSource === 'admin' && this.$store.state.adminSettings.modifiedPaths?.has(this.canonPath.join(' -> '))
canHardReset() {
return (
this.realSource === 'admin' &&
this.$store.state.adminSettings.modifiedPaths?.has(
this.canonPath.join(' -> '),
)
)
},
matchesExpertLevel () {
matchesExpertLevel() {
const settingExpertLevel = this.expert || 0
const userToggleExpert = this.$store.state.config.expertLevel || 0
return settingExpertLevel <= userToggleExpert
}
},
},
methods: {
getValue (e) {
getValue(e) {
return e.target.value
},
update (e) {
update(e) {
if (this.realDraftMode) {
this.draft = this.getValue(e)
} else {
this.configSink(this.path, this.getValue(e))
}
},
commitDraft () {
commitDraft() {
if (this.realDraftMode) {
this.configSink(this.path, this.draft)
}
},
reset () {
reset() {
if (this.realDraftMode) {
this.draft = cloneDeep(this.state)
} else {
set(this.$store.getters.mergedConfig, this.path, cloneDeep(this.defaultState))
set(
this.$store.getters.mergedConfig,
this.path,
cloneDeep(this.defaultState),
)
}
},
hardReset () {
hardReset() {
switch (this.realSource) {
case 'admin':
return this.$store.dispatch('resetAdminSetting', { path: this.path })
.then(() => { this.draft = this.state })
return this.$store
.dispatch('resetAdminSetting', { path: this.path })
.then(() => {
this.draft = this.state
})
default:
console.warn('Hard reset not implemented yet!')
}
}
}
},
},
}

View file

@ -1,19 +1,19 @@
const SharedComputedObject = () => ({
user () {
user() {
return this.$store.state.users.currentUser
},
expertLevel () {
expertLevel() {
return this.$store.getters.mergedConfig.expertLevel > 0
},
mergedConfig () {
mergedConfig() {
return this.$store.getters.mergedConfig
},
adminConfig () {
adminConfig() {
return this.$store.state.adminSettings.config
},
adminDraft () {
adminDraft() {
return this.$store.state.adminSettings.draft
}
},
})
export default SharedComputedObject

View file

@ -1,5 +1,5 @@
import Setting from './setting.js'
export default {
...Setting
...Setting,
}

View file

@ -4,13 +4,13 @@ export default {
...Setting,
methods: {
...Setting.methods,
getValue ({ e, side }) {
getValue({ e, side }) {
const [a, b] = this.visibleState || []
if (side === 0) {
return { tuple: [e.target.value, b]}
return { tuple: [e.target.value, b] }
} else {
return { tuple: [a, e.target.value]}
return { tuple: [a, e.target.value] }
}
}
}
},
},
}

View file

@ -1,7 +1,23 @@
import Select from 'src/components/select/select.vue'
import Setting from './setting.js'
export const allCssUnits = ['cm', 'mm', 'in', 'px', 'pt', 'pc', 'em', 'ex', 'ch', 'rem', 'vw', 'vh', 'vmin', 'vmax', '%']
export const allCssUnits = [
'cm',
'mm',
'in',
'px',
'pt',
'pc',
'em',
'ex',
'ch',
'rem',
'vw',
'vh',
'vmin',
'vmax',
'%',
]
export const defaultHorizontalUnits = ['px', 'rem', 'vw']
export const defaultVerticalUnits = ['px', 'rem', 'vh']
@ -9,47 +25,51 @@ export default {
...Setting,
components: {
...Setting.components,
Select
Select,
},
props: {
...Setting.props,
min: Number,
units: {
type: Array,
default: () => allCssUnits
default: () => allCssUnits,
},
unitSet: {
type: String,
default: 'none'
default: 'none',
},
step: {
type: Number,
default: 1
default: 1,
},
resetDefault: {
type: Object,
default: null
}
default: null,
},
},
computed: {
...Setting.computed,
stateUnit () {
return typeof this.state === 'string' ? this.state.replace(/[0-9,.]+/, '') : ''
stateUnit() {
return typeof this.state === 'string'
? this.state.replace(/[0-9,.]+/, '')
: ''
},
stateValue() {
return typeof this.state === 'string'
? this.state.replace(/[^0-9,.]+/, '')
: ''
},
stateValue () {
return typeof this.state === 'string' ? this.state.replace(/[^0-9,.]+/, '') : ''
}
},
methods: {
...Setting.methods,
getUnitString (value) {
getUnitString(value) {
if (this.unitSet === 'none') return value
return this.$t(['settings', 'units', this.unitSet, value].join('.'))
},
updateValue (e) {
updateValue(e) {
this.configSink(this.path, parseFloat(e.target.value) + this.stateUnit)
},
updateUnit (e) {
updateUnit(e) {
let value = this.stateValue
const newUnit = e.target.value
if (this.resetDefault) {
@ -59,6 +79,6 @@ export default {
}
}
this.configSink(this.path, value + newUnit)
}
}
},
},
}

View file

@ -8,7 +8,7 @@ import { FontAwesomeIcon as FAIcon } from '@fortawesome/vue-fontawesome'
import './vertical_tab_switcher.scss'
import { useInterfaceStore } from 'src/stores/interface'
const findFirstUsable = (slots) => slots.findIndex(_ => _.props)
const findFirstUsable = (slots) => slots.findIndex((_) => _.props)
export default {
name: 'VerticalTabSwitcher',
@ -16,30 +16,30 @@ export default {
renderOnlyFocused: {
required: false,
type: Boolean,
default: false
default: false,
},
onSwitch: {
required: false,
type: Function,
default: undefined
default: undefined,
},
activeTab: {
required: false,
type: String,
default: undefined
default: undefined,
},
bodyScrollLock: {
required: false,
type: Boolean,
default: false
default: false,
},
parentCollapsed: {
required: false,
type: Boolean,
default: null
}
default: null,
},
},
data () {
data() {
return {
active: findFirstUsable(this.slots()),
resizeHandler: null,
@ -47,82 +47,89 @@ export default {
}
},
computed: {
activeIndex () {
activeIndex() {
// In case of controlled component
if (this.activeTab) {
return this.slots().findIndex(slot => slot && slot.props && this.activeTab === slot.props.key)
return this.slots().findIndex(
(slot) => slot && slot.props && this.activeTab === slot.props.key,
)
} else {
return this.active
}
},
isActive () {
return tabName => {
const isWanted = slot => slot.props && slot.props['data-tab-name'] === tabName
isActive() {
return (tabName) => {
const isWanted = (slot) =>
slot.props && slot.props['data-tab-name'] === tabName
return this.$slots.default().findIndex(isWanted) === this.activeIndex
}
},
...mapPiniaState(useInterfaceStore, {
mobileLayout: store => store.layoutType === 'mobile'
mobileLayout: (store) => store.layoutType === 'mobile',
}),
},
beforeUpdate () {
beforeUpdate() {
const currentSlot = this.slots()[this.active]
if (!currentSlot.props) {
this.active = findFirstUsable(this.slots())
}
},
methods: {
clickTab (index) {
clickTab(index) {
return (e) => {
e.preventDefault()
this.setTab(index)
}
},
setTab (index) {
setTab(index) {
if (typeof this.onSwitch === 'function') {
this.onSwitch.call(null, this.slots()[index].key)
}
this.active = index
this.changeNavSide('content')
},
changeNavSide (side) {
changeNavSide(side) {
if (this.navSide !== side) {
this.navSide = side
}
},
// DO NOT put it to computed, it doesn't work (caching?)
slots () {
slots() {
if (this.$slots.default()[0].type === Fragment) {
return this.$slots.default()[0].children
}
return this.$slots.default()
}
},
},
render () {
const tabs = this.slots()
.map((slot, index) => {
const props = slot.props
if (!props) return
const classesTab = ['vertical-tab', 'menu-item']
if (this.activeIndex === index && useInterfaceStore().layoutType !== 'mobile') {
classesTab.push('-active')
}
return (
<button
disabled={props.disabled}
onClick={this.clickTab(index)}
class={classesTab.join(' ')}
type="button"
role="tab"
title={props.label}
>
{!props.icon ? '' : (<FAIcon class="tab-icon" size="1x" fixed-width icon={props.icon}/>)}
<span class="text">
{props.label}
</span>
</button>
)
})
render() {
const tabs = this.slots().map((slot, index) => {
const props = slot.props
if (!props) return
const classesTab = ['vertical-tab', 'menu-item']
if (
this.activeIndex === index &&
useInterfaceStore().layoutType !== 'mobile'
) {
classesTab.push('-active')
}
return (
<button
disabled={props.disabled}
onClick={this.clickTab(index)}
class={classesTab.join(' ')}
type="button"
role="tab"
title={props.label}
>
{!props.icon ? (
''
) : (
<FAIcon class="tab-icon" size="1x" fixed-width icon={props.icon} />
)}
<span class="text">{props.label}</span>
</button>
)
})
const contents = this.slots().map((slot, index) => {
const props = slot.props
@ -134,9 +141,8 @@ export default {
slot.props['delay-render'] = false
delayRender = false
}
const renderSlot = (!delayRender && (!this.renderOnlyFocused || active))
? slot
: ''
const renderSlot =
!delayRender && (!this.renderOnlyFocused || active) ? slot : ''
const headerClasses = ['tab-content-label']
const header = (
@ -147,17 +153,16 @@ export default {
title={this.$t('nav.back')}
class="button-unstyled"
>
<FAIcon
size="lg"
class="back-button-icon"
icon="chevron-left"
/>
<FAIcon size="lg" class="back-button-icon" icon="chevron-left" />
</button>
{props.label}
</h2>
)
const wrapperClasses = ['tab-content-wrapper', active ? '-active' : '-hidden' ]
const wrapperClasses = [
'tab-content-wrapper',
active ? '-active' : '-hidden',
]
const contentClasses = ['tab-content']
if (props['full-width'] || props['full-width'] === '') {
contentClasses.push('-full-width')
@ -168,14 +173,10 @@ export default {
wrapperClasses.push('-full-height')
}
return (
<div class={wrapperClasses} >
<div class="tab-mobile-header">
{header}
</div>
<div class={wrapperClasses}>
<div class="tab-mobile-header">{header}</div>
<div class="tab-slot-wrapper">
<div class={contentClasses} >
{renderSlot}
</div>
<div class={contentClasses}>{renderSlot}</div>
</div>
</div>
)
@ -193,12 +194,8 @@ export default {
}
return (
<div ref="root" class={ rootClasses.join(' ') }>
<div
class="tabs"
role="tablist"
ref="nav"
>
<div ref="root" class={rootClasses.join(' ')}>
<div class="tabs" role="tablist" ref="nav">
{tabs}
</div>
<div
@ -211,5 +208,5 @@ export default {
</div>
</div>
)
}
},
}

View file

@ -10,17 +10,15 @@ import { cloneDeep, isEqual } from 'lodash'
import { mapState, mapActions } from 'pinia'
import {
newImporter,
newExporter
newExporter,
} from 'src/services/export_import/export_import.js'
import {
faTimes,
faFileUpload,
faFileDownload,
faChevronDown
faChevronDown,
} from '@fortawesome/free-solid-svg-icons'
import {
faWindowMinimize
} from '@fortawesome/free-regular-svg-icons'
import { faWindowMinimize } from '@fortawesome/free-regular-svg-icons'
import { useInterfaceStore } from 'src/stores/interface'
const PLEROMAFE_SETTINGS_MAJOR_VERSION = 1
@ -31,25 +29,25 @@ library.add(
faWindowMinimize,
faFileUpload,
faFileDownload,
faChevronDown
faChevronDown,
)
const SettingsModal = {
data () {
data() {
return {
dataImporter: newImporter({
validator: this.importValidator,
onImport: this.onImport,
onImportFailure: this.onImportFailure
onImportFailure: this.onImportFailure,
}),
dataThemeExporter: newExporter({
filename: 'pleromafe_settings.full',
getExportedObject: () => this.generateExport(true)
getExportedObject: () => this.generateExport(true),
}),
dataExporter: newExporter({
filename: 'pleromafe_settings',
getExportedObject: () => this.generateExport()
})
getExportedObject: () => this.generateExport(),
}),
}
},
components: {
@ -62,29 +60,29 @@ const SettingsModal = {
{
loadingComponent: PanelLoading,
errorComponent: AsyncComponentError,
delay: 0
}
delay: 0,
},
),
SettingsModalAdminContent: getResettableAsyncComponent(
() => import('./settings_modal_admin_content.vue'),
{
loadingComponent: PanelLoading,
errorComponent: AsyncComponentError,
delay: 0
}
)
delay: 0,
},
),
},
methods: {
closeModal () {
closeModal() {
useInterfaceStore().closeSettingsModal()
},
peekModal () {
peekModal() {
useInterfaceStore().togglePeekSettingsModal()
},
importValidator (data) {
importValidator(data) {
if (!Array.isArray(data._pleroma_settings_version)) {
return {
messageKey: 'settings.file_import_export.invalid_file'
messageKey: 'settings.file_import_export.invalid_file',
}
}
@ -95,8 +93,8 @@ const SettingsModal = {
messageKey: 'settings.file_export_import.errors.file_too_new',
messageArgs: {
fileMajor: major,
feMajor: PLEROMAFE_SETTINGS_MAJOR_VERSION
}
feMajor: PLEROMAFE_SETTINGS_MAJOR_VERSION,
},
}
}
@ -105,94 +103,106 @@ const SettingsModal = {
messageKey: 'settings.file_export_import.errors.file_too_old',
messageArgs: {
fileMajor: major,
feMajor: PLEROMAFE_SETTINGS_MAJOR_VERSION
}
feMajor: PLEROMAFE_SETTINGS_MAJOR_VERSION,
},
}
}
if (minor > PLEROMAFE_SETTINGS_MINOR_VERSION) {
useInterfaceStore().pushGlobalNotice({
level: 'warning',
messageKey: 'settings.file_export_import.errors.file_slightly_new'
messageKey: 'settings.file_export_import.errors.file_slightly_new',
})
}
return true
},
onImportFailure (result) {
onImportFailure(result) {
if (result.error) {
useInterfaceStore().pushGlobalNotice({ messageKey: 'settings.invalid_settings_imported', level: 'error' })
useInterfaceStore().pushGlobalNotice({
messageKey: 'settings.invalid_settings_imported',
level: 'error',
})
} else {
useInterfaceStore().pushGlobalNotice({ ...result.validationResult, level: 'error' })
useInterfaceStore().pushGlobalNotice({
...result.validationResult,
level: 'error',
})
}
},
onImport (data) {
if (data) { this.$store.dispatch('loadSettings', data) }
onImport(data) {
if (data) {
this.$store.dispatch('loadSettings', data)
}
},
restore () {
restore() {
this.dataImporter.importData()
},
backup () {
backup() {
this.dataExporter.exportData()
},
backupWithTheme () {
backupWithTheme() {
this.dataThemeExporter.exportData()
},
generateExport (theme = false) {
generateExport(theme = false) {
const { config } = this.$store.state
let sample = config
if (!theme) {
const ignoreList = new Set([
'customTheme',
'customThemeSource',
'colors'
'colors',
])
sample = Object.fromEntries(
Object
.entries(sample)
.filter(([key]) => !ignoreList.has(key))
Object.entries(sample).filter(([key]) => !ignoreList.has(key)),
)
}
const clone = cloneDeep(sample)
clone._pleroma_settings_version = [
PLEROMAFE_SETTINGS_MAJOR_VERSION,
PLEROMAFE_SETTINGS_MINOR_VERSION
PLEROMAFE_SETTINGS_MINOR_VERSION,
]
return clone
},
resetAdminDraft () {
resetAdminDraft() {
this.$store.commit('resetAdminDraft')
},
pushAdminDraft () {
pushAdminDraft() {
this.$store.dispatch('pushAdminDraft')
},
...mapActions(useInterfaceStore, ['temporaryChangesRevert', 'temporaryChangesConfirm'])
...mapActions(useInterfaceStore, [
'temporaryChangesRevert',
'temporaryChangesConfirm',
]),
},
computed: {
...mapState(useInterfaceStore, {
temporaryChangesCountdown: store => store.temporaryChangesCountdown,
currentSaveStateNotice: store => store.settings.currentSaveStateNotice,
modalActivated: store => store.settingsModalState !== 'hidden',
modalMode: store => store.settingsModalMode,
modalOpenedOnceUser: store => store.settingsModalLoadedUser,
modalOpenedOnceAdmin: store => store.settingsModalLoadedAdmin,
modalPeeked: store => store.settingsModalState === 'minimized'
temporaryChangesCountdown: (store) => store.temporaryChangesCountdown,
currentSaveStateNotice: (store) => store.settings.currentSaveStateNotice,
modalActivated: (store) => store.settingsModalState !== 'hidden',
modalMode: (store) => store.settingsModalMode,
modalOpenedOnceUser: (store) => store.settingsModalLoadedUser,
modalOpenedOnceAdmin: (store) => store.settingsModalLoadedAdmin,
modalPeeked: (store) => store.settingsModalState === 'minimized',
}),
expertLevel: {
get () {
get() {
return this.$store.state.config.expertLevel > 0
},
set (value) {
this.$store.dispatch('setOption', { name: 'expertLevel', value: value ? 1 : 0 })
}
set(value) {
this.$store.dispatch('setOption', {
name: 'expertLevel',
value: value ? 1 : 0,
})
},
},
adminDraftAny () {
adminDraftAny() {
return !isEqual(
this.$store.state.adminSettings.config,
this.$store.state.adminSettings.draft
this.$store.state.adminSettings.draft,
)
}
}
},
},
}
export default SettingsModal

View file

@ -36,7 +36,7 @@ import {
faUpload,
faMessage,
faEllipsis,
faGauge
faGauge,
} from '@fortawesome/free-solid-svg-icons'
library.add(
@ -55,7 +55,7 @@ library.add(
faUpload,
faMessage,
faEllipsis,
faGauge
faGauge,
)
const SettingsModalAdminContent = {
@ -78,44 +78,46 @@ const SettingsModalAdminContent = {
MonitoringTab,
RatesTab,
OtherTab,
PostsTab
PostsTab,
},
computed: {
user () {
user() {
return this.$store.state.users.currentUser
},
isLoggedIn () {
isLoggedIn() {
return !!this.$store.state.users.currentUser
},
open () {
open() {
return useInterfaceStore().settingsModalState !== 'hidden'
},
bodyLock () {
bodyLock() {
return useInterfaceStore().settingsModalState === 'visible'
},
adminDbLoaded () {
adminDbLoaded() {
return this.$store.state.adminSettings.loaded
},
adminDescriptionsLoaded () {
adminDescriptionsLoaded() {
return this.$store.state.adminSettings.descriptions !== null
},
noDb () {
noDb() {
return this.$store.state.adminSettings.dbConfigEnabled === false
}
},
},
created () {
created() {
if (this.user.rights.admin) {
this.$store.dispatch('loadAdminStuff')
}
},
methods: {
onOpen () {
onOpen() {
const targetTab = useInterfaceStore().settingsModalTargetTab
// We're being told to open in specific tab
if (targetTab) {
const tabIndex = this.$refs.tabSwitcher.$slots.default().findIndex(elm => {
return elm.props && elm.props['data-tab-name'] === targetTab
})
const tabIndex = this.$refs.tabSwitcher.$slots
.default()
.findIndex((elm) => {
return elm.props && elm.props['data-tab-name'] === targetTab
})
if (tabIndex >= 0) {
this.$refs.tabSwitcher.setTab(tabIndex)
}
@ -123,16 +125,16 @@ const SettingsModalAdminContent = {
// Clear the state of target tab, so that next time settings is opened
// it doesn't force it.
useInterfaceStore().clearSettingsModalTargetTab()
}
},
},
mounted () {
mounted() {
this.onOpen()
},
watch: {
open: function (value) {
if (value) this.onOpen()
}
}
},
},
}
export default SettingsModalAdminContent

View file

@ -31,7 +31,7 @@ import {
faCode,
faBroom,
faLock,
faColumns
faColumns,
} from '@fortawesome/free-solid-svg-icons'
import { useInterfaceStore } from 'src/stores/interface'
@ -49,7 +49,7 @@ library.add(
faDownload,
faPalette,
faPaintBrush,
faCode
faCode,
)
const SettingsModalContent = {
@ -70,36 +70,38 @@ const SettingsModalContent = {
AppearanceTab,
StyleTab,
DeveloperTab,
OldThemeTab
OldThemeTab,
},
computed: {
isLoggedIn () {
isLoggedIn() {
return !!this.$store.state.users.currentUser
},
open () {
open() {
return useInterfaceStore().settingsModalState !== 'hidden'
},
bodyLock () {
bodyLock() {
return useInterfaceStore().settingsModalState === 'visible'
},
expertLevel () {
expertLevel() {
return this.$store.state.config.expertLevel
}
},
},
data () {
data() {
return {
navCollapsed: false,
navHideHeader: false
navHideHeader: false,
}
},
methods: {
onOpen () {
onOpen() {
const targetTab = useInterfaceStore().settingsModalTargetTab
// We're being told to open in specific tab
if (targetTab) {
const tabIndex = this.$refs.tabSwitcher.$slots.default().findIndex(elm => {
return elm.props && elm.props['data-tab-name'] === targetTab
})
const tabIndex = this.$refs.tabSwitcher.$slots
.default()
.findIndex((elm) => {
return elm.props && elm.props['data-tab-name'] === targetTab
})
if (tabIndex >= 0) {
this.$refs.tabSwitcher.setTab(tabIndex)
}
@ -107,16 +109,16 @@ const SettingsModalContent = {
// Clear the state of target tab, so that next time settings is opened
// it doesn't force it.
useInterfaceStore().clearSettingsModalTargetTab()
}
},
},
mounted () {
mounted() {
this.onOpen()
},
watch: {
open: function (value) {
if (value) this.onOpen()
}
}
},
},
}
export default SettingsModalContent

View file

@ -9,11 +9,12 @@ import Preview from './old_theme_tab/theme_preview.vue'
import { newImporter } from 'src/services/export_import/export_import.js'
import { convertTheme2To3 } from 'src/services/theme_data/theme2_to_theme3.js'
import { init } from 'src/services/theme_data/theme_data_3.service.js'
import {
getCssRules
} from 'src/services/theme_data/css_utils.js'
import { getCssRules } 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 {
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'
@ -23,7 +24,7 @@ import { mapActions } from 'pinia'
import { useInterfaceStore, normalizeThemeData } from 'src/stores/interface'
const AppearanceTab = {
data () {
data() {
return {
availableThemesV3: [],
availableThemesV2: [],
@ -34,7 +35,7 @@ const AppearanceTab = {
validator: this.importValidator,
onImport: this.onImport,
parser: this.importParser,
onImportFailure: this.onImportFailure
onImportFailure: this.onImportFailure,
}),
palettesKeys: [
'bg',
@ -44,19 +45,25 @@ const AppearanceTab = {
'cRed',
'cGreen',
'cBlue',
'cOrange'
'cOrange',
],
userPalette: {},
intersectionObserver: null,
forcedRoundnessOptions: ['disabled', 'sharp', 'nonsharp', 'round'].map((mode, i) => ({
key: mode,
value: i - 1,
label: this.$t(`settings.style.themes3.hacks.forced_roundness_mode_${mode}`)
})),
forcedRoundnessOptions: ['disabled', 'sharp', 'nonsharp', 'round'].map(
(mode, i) => ({
key: mode,
value: i - 1,
label: this.$t(
`settings.style.themes3.hacks.forced_roundness_mode_${mode}`,
),
}),
),
underlayOverrideModes: ['none', 'opaque', 'transparent'].map((mode) => ({
key: mode,
value: mode,
label: this.$t(`settings.style.themes3.hacks.underlay_override_mode_${mode}`)
label: this.$t(
`settings.style.themes3.hacks.underlay_override_mode_${mode}`,
),
})),
backgroundUploading: false,
background: null,
@ -71,9 +78,9 @@ const AppearanceTab = {
UnitSetting,
ProfileSettingIndicator,
Preview,
PaletteEditor
PaletteEditor,
},
mounted () {
mounted() {
useInterfaceStore().getThemeData()
const updateIndex = (resource) => {
@ -87,120 +94,151 @@ const AppearanceTab = {
promise = useInterfaceStore()[`fetch${capitalizedResource}sIndex`]()
}
return promise.then(index => {
return Object
.entries(index)
.map(([k, func]) => [k, func()])
return promise.then((index) => {
return Object.entries(index).map(([k, func]) => [k, func()])
})
}
updateIndex('style').then(styles => {
styles.forEach(([key, stylePromise]) => stylePromise.then(data => {
const meta = data.find(x => x.component === '@meta')
this.availableThemesV3.push({ key, data, name: meta.directives.name, version: 'v3' })
}))
updateIndex('style').then((styles) => {
styles.forEach(([key, stylePromise]) =>
stylePromise.then((data) => {
const meta = data.find((x) => x.component === '@meta')
this.availableThemesV3.push({
key,
data,
name: meta.directives.name,
version: 'v3',
})
}),
)
})
updateIndex('theme').then(themes => {
themes.forEach(([key, themePromise]) => themePromise.then(data => {
if (!data) {
console.warn(`Theme with key ${key} is empty or malformed`)
} else if (Array.isArray(data)) {
console.warn(`Theme with key ${key} is a v1 theme and should be moved to static/palettes/index.json`)
} else if (!data.source && !data.theme) {
console.warn(`Theme with key ${key} is malformed`)
} else {
this.availableThemesV2.push({ key, data, name: data.name, version: 'v2' })
}
}))
updateIndex('theme').then((themes) => {
themes.forEach(([key, themePromise]) =>
themePromise.then((data) => {
if (!data) {
console.warn(`Theme with key ${key} is empty or malformed`)
} else if (Array.isArray(data)) {
console.warn(
`Theme with key ${key} is a v1 theme and should be moved to static/palettes/index.json`,
)
} else if (!data.source && !data.theme) {
console.warn(`Theme with key ${key} is malformed`)
} else {
this.availableThemesV2.push({
key,
data,
name: data.name,
version: 'v2',
})
}
}),
)
})
this.userPalette = useInterfaceStore().paletteDataUsed || {}
updateIndex('palette').then(bundledPalettes => {
bundledPalettes.forEach(([key, palettePromise]) => palettePromise.then(v => {
let palette
if (Array.isArray(v)) {
const [
name,
bg,
fg,
text,
link,
cRed = '#FF0000',
cGreen = '#00FF00',
cBlue = '#0000FF',
cOrange = '#E3FF00'
] = v
palette = { key, name, bg, fg, text, link, cRed, cBlue, cGreen, cOrange }
} else {
palette = { key, ...v }
}
if (!palette.key.startsWith('style.')) {
this.bundledPalettes.push(palette)
}
}))
updateIndex('palette').then((bundledPalettes) => {
bundledPalettes.forEach(([key, palettePromise]) =>
palettePromise.then((v) => {
let palette
if (Array.isArray(v)) {
const [
name,
bg,
fg,
text,
link,
cRed = '#FF0000',
cGreen = '#00FF00',
cBlue = '#0000FF',
cOrange = '#E3FF00',
] = v
palette = {
key,
name,
bg,
fg,
text,
link,
cRed,
cBlue,
cGreen,
cOrange,
}
} else {
palette = { key, ...v }
}
if (!palette.key.startsWith('style.')) {
this.bundledPalettes.push(palette)
}
}),
)
})
this.previewTheme('stock', 'v3')
if (window.IntersectionObserver) {
this.intersectionObserver = new IntersectionObserver((entries, observer) => {
entries.forEach(({ target, isIntersecting }) => {
if (!isIntersecting) return
const theme = this.availableStyles.find(x => x.key === target.dataset.themeKey)
this.$nextTick(() => {
if (theme) this.previewTheme(theme.key, theme.version, theme.data)
this.intersectionObserver = new IntersectionObserver(
(entries, observer) => {
entries.forEach(({ target, isIntersecting }) => {
if (!isIntersecting) return
const theme = this.availableStyles.find(
(x) => x.key === target.dataset.themeKey,
)
this.$nextTick(() => {
if (theme) this.previewTheme(theme.key, theme.version, theme.data)
})
observer.unobserve(target)
})
observer.unobserve(target)
})
}, {
root: this.$refs.themeList
})
},
{
root: this.$refs.themeList,
},
)
} else {
this.availableStyles.forEach(theme => this.previewTheme(theme.key, theme.version, theme.data))
this.availableStyles.forEach((theme) =>
this.previewTheme(theme.key, theme.version, theme.data),
)
}
},
updated () {
updated() {
this.$nextTick(() => {
this.$refs.themeList.querySelectorAll('.theme-preview').forEach(node => {
this.intersectionObserver.observe(node)
})
this.$refs.themeList
.querySelectorAll('.theme-preview')
.forEach((node) => {
this.intersectionObserver.observe(node)
})
})
},
watch: {
paletteDataUsed () {
paletteDataUsed() {
this.userPalette = this.paletteDataUsed || {}
}
},
},
computed: {
isDefaultBackground () {
return !(this.$store.state.users.currentUser.background_image)
isDefaultBackground() {
return !this.$store.state.users.currentUser.background_image
},
switchInProgress () {
switchInProgress() {
return useInterfaceStore().themeChangeInProgress
},
paletteDataUsed () {
paletteDataUsed() {
return useInterfaceStore().paletteDataUsed
},
availableStyles () {
return [
...this.availableThemesV3,
...this.availableThemesV2
]
availableStyles() {
return [...this.availableThemesV3, ...this.availableThemesV2]
},
availablePalettes () {
return [
...this.bundledPalettes,
...this.stylePalettes
]
availablePalettes() {
return [...this.bundledPalettes, ...this.stylePalettes]
},
stylePalettes () {
stylePalettes() {
const ruleset = useInterfaceStore().styleDataUsed || []
if (!ruleset && ruleset.length === 0) return
const meta = ruleset.find(x => x.component === '@meta')
const result = ruleset.filter(x => x.component.startsWith('@palette'))
.map(x => {
const meta = ruleset.find((x) => x.component === '@meta')
const result = ruleset
.filter((x) => x.component.startsWith('@palette'))
.map((x) => {
const { variant, directives } = x
const {
bg,
@ -212,7 +250,7 @@ const AppearanceTab = {
cBlue,
cGreen,
cOrange,
wallpaper
wallpaper,
} = directives
const result = {
@ -227,94 +265,103 @@ const AppearanceTab = {
cBlue,
cGreen,
cOrange,
wallpaper
wallpaper,
}
return Object.fromEntries(Object.entries(result).filter(([, v]) => v))
})
return result
},
noIntersectionObserver () {
noIntersectionObserver() {
return !window.IntersectionObserver
},
instanceWallpaper () {
instanceWallpaper() {
this.$store.state.instance.background
},
instanceWallpaperUsed () {
return this.$store.state.instance.background &&
instanceWallpaperUsed() {
return (
this.$store.state.instance.background &&
!this.$store.state.users.currentUser.background_image
)
},
customThemeVersion () {
customThemeVersion() {
const { themeVersion } = useInterfaceStore()
return themeVersion
},
isCustomThemeUsed () {
isCustomThemeUsed() {
const { customTheme, customThemeSource } = this.mergedConfig
return customTheme != null || customThemeSource != null
},
isCustomStyleUsed () {
isCustomStyleUsed() {
const { styleCustomData } = this.mergedConfig
return styleCustomData != null
},
...SharedComputedObject()
...SharedComputedObject(),
},
methods: {
importFile () {
importFile() {
this.fileImporter.importData()
},
importParser (file, filename) {
importParser(file, filename) {
if (filename.endsWith('.json')) {
return JSON.parse(file)
} else if (filename.endsWith('.iss')) {
return deserialize(file)
}
},
onImport (parsed, filename) {
onImport(parsed, filename) {
if (filename.endsWith('.json')) {
useInterfaceStore().setThemeCustom(parsed.source || parsed.theme)
} else if (filename.endsWith('.iss')) {
useInterfaceStore().setStyleCustom(parsed)
}
},
onImportFailure (result) {
onImportFailure(result) {
console.error('Failure importing theme:', result)
useInterfaceStore().pushGlobalNotice({ messageKey: 'settings.invalid_theme_imported', level: 'error' })
useInterfaceStore().pushGlobalNotice({
messageKey: 'settings.invalid_theme_imported',
level: 'error',
})
},
importValidator (parsed, filename) {
importValidator(parsed, filename) {
if (filename.endsWith('.json')) {
const version = parsed._pleroma_theme_version
return version >= 1 || version <= 2
} else if (filename.endsWith('.iss')) {
if (!Array.isArray(parsed)) return false
if (parsed.length < 1) return false
if (parsed.find(x => x.component === '@meta') == null) return false
if (parsed.find((x) => x.component === '@meta') == null) return false
return true
}
},
isThemeActive (key) {
return key === (this.mergedConfig.theme || this.$store.state.instance.theme)
isThemeActive(key) {
return (
key === (this.mergedConfig.theme || this.$store.state.instance.theme)
)
},
isStyleActive (key) {
return key === (this.mergedConfig.style || this.$store.state.instance.style)
isStyleActive(key) {
return (
key === (this.mergedConfig.style || this.$store.state.instance.style)
)
},
isPaletteActive (key) {
return key === (this.mergedConfig.palette || this.$store.state.instance.palette)
isPaletteActive(key) {
return (
key ===
(this.mergedConfig.palette || this.$store.state.instance.palette)
)
},
...mapActions(useInterfaceStore, [
'setStyle',
'setTheme'
]),
setPalette (name, data) {
...mapActions(useInterfaceStore, ['setStyle', 'setTheme']),
setPalette(name, data) {
useInterfaceStore().setPalette(name)
this.userPalette = data
},
setPaletteCustom (data) {
setPaletteCustom(data) {
useInterfaceStore().setPaletteCustom(data)
this.userPalette = data
},
resetTheming () {
resetTheming() {
useInterfaceStore().setStyle('stock')
},
previewTheme (key, version, input) {
previewTheme(key, version, input) {
let theme3
if (this.compilationCache[key]) {
theme3 = this.compilationCache[key]
@ -327,10 +374,10 @@ const AppearanceTab = {
ultimateBackgroundColor: '#000000',
liteMode: true,
debug: true,
onlyNormalState: true
onlyNormalState: true,
})
} else if (version === 'v3') {
const palette = input.find(x => x.component === '@palette')
const palette = input.find((x) => x.component === '@palette')
let paletteRule
if (palette) {
const { directives } = palette
@ -339,21 +386,20 @@ const AppearanceTab = {
paletteRule = {
component: 'Root',
directives: Object.fromEntries(
Object
.entries(directives)
Object.entries(directives)
.filter(([k]) => k && k !== 'name')
.map(([k, v]) => ['--' + k, 'color | ' + v])
)
.map(([k, v]) => ['--' + k, 'color | ' + v]),
),
}
} else {
paletteRule = null
}
theme3 = init({
inputRuleset: [...input, paletteRule].filter(x => x),
inputRuleset: [...input, paletteRule].filter((x) => x),
ultimateBackgroundColor: '#000000',
liteMode: true,
onlyNormalState: true
onlyNormalState: true,
})
}
} else {
@ -361,7 +407,7 @@ const AppearanceTab = {
inputRuleset: [],
ultimateBackgroundColor: '#000000',
liteMode: true,
onlyNormalState: true
onlyNormalState: true,
})
}
@ -369,22 +415,29 @@ const AppearanceTab = {
this.compilationCache[key] = theme3
}
const sheet = createStyleSheet('appearance-tab-previews', 90)
sheet.addRule([
'#theme-preview-', key, ' {\n',
getCssRules(theme3.eager).join('\n'),
'\n}'
].join(''))
sheet.addRule(
[
'#theme-preview-',
key,
' {\n',
getCssRules(theme3.eager).join('\n'),
'\n}',
].join(''),
)
sheet.ready = true
adoptStyleSheets()
},
uploadFile (slot, e) {
uploadFile(slot, e) {
const file = e.target.files[0]
if (!file) { return }
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'])
const allowedsize = fileSizeFormatService.fileSizeFormat(
this.$store.state.instance[slot + 'limit'],
)
useInterfaceStore().pushGlobalNotice({
messageKey: 'upload.error.message',
messageArgs: [
@ -392,10 +445,10 @@ const AppearanceTab = {
filesize: filesize.num,
filesizeunit: filesize.unit,
allowedsize: allowedsize.num,
allowedsizeunit: allowedsize.unit
})
allowedsizeunit: allowedsize.unit,
}),
],
level: 'error'
level: 'error',
})
return
}
@ -408,29 +461,36 @@ const AppearanceTab = {
}
reader.readAsDataURL(file)
},
resetBackground () {
const confirmed = window.confirm(this.$t('settings.reset_background_confirm'))
resetBackground() {
const confirmed = window.confirm(
this.$t('settings.reset_background_confirm'),
)
if (confirmed) {
this.submitBackground('')
}
},
resetUploadedBackground () {
resetUploadedBackground() {
this.backgroundPreview = null
},
submitBackground (background) {
if (!this.backgroundPreview && background !== '') { return }
submitBackground(background) {
if (!this.backgroundPreview && background !== '') {
return
}
this.backgroundUploading = true
this.$store.state.api.backendInteractor.updateProfileImages({ background })
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 })
.finally(() => {
this.backgroundUploading = false
})
},
}
},
}
export default AppearanceTab

View file

@ -1,6 +1,6 @@
import { mapState, mapActions } from 'pinia'
import { mapState as mapVuexState } from 'vuex'
import { v4 as uuidv4 } from 'uuid';
import { v4 as uuidv4 } from 'uuid'
import { useServerSideStorageStore } from 'src/stores/serverSideStorage'
@ -14,7 +14,6 @@ import Select from 'src/components/select/select.vue'
import SharedComputedObject from '../helpers/shared_computed_object.js'
const ClutterTab = {
components: {
BooleanSetting,
@ -23,23 +22,23 @@ const ClutterTab = {
IntegerSetting,
Checkbox,
Select,
HelpIndicator
HelpIndicator,
},
computed: {
instanceSpecificPanelPresent () { return this.$store.state.instance.showInstanceSpecificPanel },
instanceSpecificPanelPresent() {
return this.$store.state.instance.showInstanceSpecificPanel
},
...SharedComputedObject(),
...mapState(
useServerSideStorageStore,
{
muteFilters: store => Object.entries(store.prefsStorage.simple.muteFilters),
muteFiltersObject: store => store.prefsStorage.simple.muteFilters
}
),
...mapState(useServerSideStorageStore, {
muteFilters: (store) =>
Object.entries(store.prefsStorage.simple.muteFilters),
muteFiltersObject: (store) => store.prefsStorage.simple.muteFilters,
}),
...mapVuexState({
blockExpirationSupported: state => state.instance.blockExpiration
blockExpirationSupported: (state) => state.instance.blockExpiration,
}),
onMuteDefaultActionLv1: {
get () {
get() {
const value = this.$store.state.config.onMuteDefaultAction
if (value === 'ask' || value === 'forever') {
return value
@ -47,16 +46,19 @@ const ClutterTab = {
return 'temporarily'
}
},
set (value) {
set(value) {
let realValue = value
if (value !== 'ask' && value !== 'forever') {
realValue = '14d'
}
this.$store.dispatch('setOption', { name: 'onMuteDefaultAction', value: realValue })
}
this.$store.dispatch('setOption', {
name: 'onMuteDefaultAction',
value: realValue,
})
},
},
onBlockDefaultActionLv1: {
get () {
get() {
const value = this.$store.state.config.onBlockDefaultAction
if (value === 'ask' || value === 'forever') {
return value
@ -64,29 +66,36 @@ const ClutterTab = {
return 'temporarily'
}
},
set (value) {
set(value) {
let realValue = value
if (value !== 'ask' && value !== 'forever') {
realValue = '14d'
}
this.$store.dispatch('setOption', { name: 'onBlockDefaultAction', value: realValue })
}
this.$store.dispatch('setOption', {
name: 'onBlockDefaultAction',
value: realValue,
})
},
},
muteFiltersDraft () {
muteFiltersDraft() {
return Object.entries(this.muteFiltersDraftObject)
},
muteFiltersExpired () {
muteFiltersExpired() {
const now = Date.now()
return Object
.entries(this.muteFiltersDraftObject)
.filter(([, { expires }]) => expires != null && expires <= now)
}
return Object.entries(this.muteFiltersDraftObject).filter(
([, { expires }]) => expires != null && expires <= now,
)
},
},
methods: {
...mapActions(useServerSideStorageStore, ['setPreference', 'unsetPreference', 'pushServerSideStorage']),
getDatetimeLocal (timestamp) {
...mapActions(useServerSideStorageStore, [
'setPreference',
'unsetPreference',
'pushServerSideStorage',
]),
getDatetimeLocal(timestamp) {
const date = new Date(timestamp)
const fmt = new Intl.NumberFormat("en-US", {minimumIntegerDigits: 2})
const fmt = new Intl.NumberFormat('en-US', { minimumIntegerDigits: 2 })
const datetime = [
date.getFullYear(),
'-',
@ -96,11 +105,11 @@ const ClutterTab = {
'T',
fmt.format(date.getHours()),
':',
fmt.format(date.getMinutes())
fmt.format(date.getMinutes()),
].join('')
return datetime
},
checkRegexValid (id) {
checkRegexValid(id) {
const filter = this.muteFiltersObject[id]
if (filter.type !== 'regexp') return true
if (filter.type !== 'user_regexp') return true
@ -114,19 +123,21 @@ const ClutterTab = {
}
return valid
},
createFilter (filter = {
type: 'word',
value: '',
name: 'New Filter',
enabled: true,
expires: null,
hide: false,
}) {
createFilter(
filter = {
type: 'word',
value: '',
name: 'New Filter',
enabled: true,
expires: null,
hide: false,
},
) {
const newId = uuidv4()
filter.order = this.muteFilters.length + 2
this.muteFiltersDraftObject[newId] = filter
this.setPreference({ path: 'simple.muteFilters.' + newId , value: filter })
this.setPreference({ path: 'simple.muteFilters.' + newId, value: filter })
this.pushServerSideStorage()
},
exportFilter(id) {
@ -137,23 +148,23 @@ const ClutterTab = {
importFilter() {
this.filterImporter.importData()
},
copyFilter (id) {
copyFilter(id) {
const filter = { ...this.muteFiltersDraftObject[id] }
const newId = uuidv4()
this.muteFiltersDraftObject[newId] = filter
this.setPreference({ path: 'simple.muteFilters.' + newId , value: filter })
this.setPreference({ path: 'simple.muteFilters.' + newId, value: filter })
this.pushServerSideStorage()
},
deleteFilter (id) {
deleteFilter(id) {
delete this.muteFiltersDraftObject[id]
this.unsetPreference({ path: 'simple.muteFilters.' + id , value: null })
this.unsetPreference({ path: 'simple.muteFilters.' + id, value: null })
this.pushServerSideStorage()
},
purgeExpiredFilters () {
purgeExpiredFilters() {
this.muteFiltersExpired.forEach(([id]) => {
delete this.muteFiltersDraftObject[id]
this.unsetPreference({ path: 'simple.muteFilters.' + id , value: null })
this.unsetPreference({ path: 'simple.muteFilters.' + id, value: null })
})
this.pushServerSideStorage()
},
@ -177,17 +188,20 @@ const ClutterTab = {
this.muteFiltersDraftDirty[id] = true
},
saveFilter(id) {
this.setPreference({ path: 'simple.muteFilters.' + id , value: this.muteFiltersDraftObject[id] })
this.setPreference({
path: 'simple.muteFilters.' + id,
value: this.muteFiltersDraftObject[id],
})
this.pushServerSideStorage()
this.muteFiltersDraftDirty[id] = false
},
},
// Updating nested properties
watch: {
replyVisibility () {
replyVisibility() {
this.$store.dispatch('queueFlushAll')
}
}
},
},
}
export default ClutterTab

View file

@ -21,69 +21,78 @@ import {
faMessage,
faPenAlt,
faDatabase,
faSliders
faSliders,
} from '@fortawesome/free-solid-svg-icons'
library.add(
faGlobe,
faMessage,
faPenAlt,
faDatabase,
faSliders
)
library.add(faGlobe, faMessage, faPenAlt, faDatabase, faSliders)
const ComposingTab = {
props: {
parentCollapsed: {
required: true,
type: Boolean
}
type: Boolean,
},
},
data () {
data() {
return {
subjectLineOptions: ['email', 'noop', 'masto'].map(mode => ({
subjectLineOptions: ['email', 'noop', 'masto'].map((mode) => ({
key: mode,
value: mode,
label: this.$t(`settings.subject_line_${mode === 'masto' ? 'mastodon' : mode}`)
label: this.$t(
`settings.subject_line_${mode === 'masto' ? 'mastodon' : mode}`,
),
})),
conversationDisplayOptions: ['tree', 'linear'].map(mode => ({
conversationDisplayOptions: ['tree', 'linear'].map((mode) => ({
key: mode,
value: mode,
label: this.$t(`settings.conversation_display_${mode}`)
label: this.$t(`settings.conversation_display_${mode}`),
})),
absoluteTime12hOptions: ['24h', '12h'].map(mode => ({
absoluteTime12hOptions: ['24h', '12h'].map((mode) => ({
key: mode,
value: mode,
label: this.$t(`settings.absolute_time_format_12h_${mode}`)
label: this.$t(`settings.absolute_time_format_12h_${mode}`),
})),
conversationOtherRepliesButtonOptions: ['below', 'inside'].map(mode => ({
conversationOtherRepliesButtonOptions: ['below', 'inside'].map(
(mode) => ({
key: mode,
value: mode,
label: this.$t(`settings.conversation_other_replies_button_${mode}`),
}),
),
mentionLinkDisplayOptions: ['short', 'full_for_remote', 'full'].map(
(mode) => ({
key: mode,
value: mode,
label: this.$t(`settings.mention_link_display_${mode}`),
}),
),
userPopoverAvatarActionOptions: ['close', 'zoom', 'open'].map((mode) => ({
key: mode,
value: mode,
label: this.$t(`settings.conversation_other_replies_button_${mode}`)
label: this.$t(`settings.user_popover_avatar_action_${mode}`),
})),
mentionLinkDisplayOptions: ['short', 'full_for_remote', 'full'].map(mode => ({
unsavedPostActionOptions: ['save', 'discard', 'confirm'].map((mode) => ({
key: mode,
value: mode,
label: this.$t(`settings.mention_link_display_${mode}`)
})),
userPopoverAvatarActionOptions: ['close', 'zoom', 'open'].map(mode => ({
key: mode,
value: mode,
label: this.$t(`settings.user_popover_avatar_action_${mode}`)
})),
unsavedPostActionOptions: ['save', 'discard', 'confirm'].map(mode => ({
key: mode,
value: mode,
label: this.$t(`settings.unsaved_post_action_${mode}`)
label: this.$t(`settings.unsaved_post_action_${mode}`),
})),
loopSilentAvailable:
// Firefox
Object.getOwnPropertyDescriptor(HTMLVideoElement.prototype, 'mozHasAudio') ||
// Chrome-likes
Object.getOwnPropertyDescriptor(HTMLMediaElement.prototype, 'webkitAudioDecodedByteCount') ||
// Future spec, still not supported in Nightly 63 as of 08/2018
Object.getOwnPropertyDescriptor(HTMLMediaElement.prototype, 'audioTracks'),
emailLanguage: this.$store.state.users.currentUser.language || ['']
// Firefox
Object.getOwnPropertyDescriptor(
HTMLVideoElement.prototype,
'mozHasAudio',
) ||
// Chrome-likes
Object.getOwnPropertyDescriptor(
HTMLMediaElement.prototype,
'webkitAudioDecodedByteCount',
) ||
// Future spec, still not supported in Nightly 63 as of 08/2018
Object.getOwnPropertyDescriptor(
HTMLMediaElement.prototype,
'audioTracks',
),
emailLanguage: this.$store.state.users.currentUser.language || [''],
}
},
components: {
@ -96,61 +105,68 @@ const ComposingTab = {
ProfileSettingIndicator,
ScopeSelector,
Select,
FontControl
FontControl,
},
computed: {
postFormats () {
postFormats() {
return this.$store.state.instance.postFormats || []
},
postContentOptions () {
return this.postFormats.map(format => ({
postContentOptions() {
return this.postFormats.map((format) => ({
key: format,
value: format,
label: this.$t(`post_status.content_type["${format}"]`)
label: this.$t(`post_status.content_type["${format}"]`),
}))
},
language: {
get: function () { return this.$store.getters.mergedConfig.interfaceLanguage },
get: function () {
return this.$store.getters.mergedConfig.interfaceLanguage
},
set: function (val) {
this.$store.dispatch('setOption', { name: 'interfaceLanguage', value: val })
}
this.$store.dispatch('setOption', {
name: 'interfaceLanguage',
value: val,
})
},
},
...SharedComputedObject(),
...mapState({
blockExpirationSupported: state => state.instance.blockExpiration,
})
blockExpirationSupported: (state) => state.instance.blockExpiration,
}),
},
methods: {
changeDefaultScope (value) {
changeDefaultScope(value) {
this.$store.dispatch('setProfileOption', { name: 'defaultScope', value })
},
clearCache (key) {
clearCache(key) {
clearCache(key)
.then(() => {
this.$store.dispatch('settingsSaved', { success: true })
})
.catch(error => {
.catch((error) => {
this.$store.dispatch('settingsSaved', { error })
})
},
tooSmall () {
tooSmall() {
this.$emit('tooSmall')
},
tooBig () {
tooBig() {
this.$emit('tooBig')
},
getNavMode () {
getNavMode() {
return this.$refs.tabSwitcher.getNavMode()
},
clearAssetCache () {
clearAssetCache() {
this.clearCache(cacheKey)
},
clearEmojiCache () {
clearEmojiCache() {
this.clearCache(emojiCacheKey)
},
updateProfile () {
updateProfile() {
const params = {
language: localeService.internalToBackendLocaleMulti(this.emailLanguage)
language: localeService.internalToBackendLocaleMulti(
this.emailLanguage,
),
}
this.$store.state.api.backendInteractor
@ -160,19 +176,19 @@ const ComposingTab = {
this.$store.commit('setCurrentUser', user)
})
},
updateFont (key, value) {
updateFont(key, value) {
this.$store.dispatch('setOption', {
name: 'theme3hacks',
value: {
...this.mergedConfig.theme3hacks,
fonts: {
...this.mergedConfig.theme3hacks.fonts,
[key]: value
}
}
[key]: value,
},
},
})
},
}
},
}
export default ComposingTab

View file

@ -5,81 +5,84 @@ import { mapState } from 'vuex'
import { useOAuthTokensStore } from 'src/stores/oauth_tokens'
const DataImportExportTab = {
data () {
data() {
return {
activeTab: 'profile',
newDomainToMute: '',
listBackupsError: false,
addBackupError: false,
addedBackup: false,
backups: []
backups: [],
}
},
created () {
created() {
useOAuthTokensStore().fetchTokens()
this.fetchBackups()
},
components: {
Importer,
Exporter,
Checkbox
Checkbox,
},
computed: {
...mapState({
backendInteractor: (state) => state.api.backendInteractor,
user: (state) => state.users.currentUser
})
user: (state) => state.users.currentUser,
}),
},
methods: {
getFollowsContent () {
return this.backendInteractor.exportFriends({ id: this.user.id })
getFollowsContent() {
return this.backendInteractor
.exportFriends({ id: this.user.id })
.then(this.generateExportableUsersContent)
},
getBlocksContent () {
return this.backendInteractor.fetchBlocks()
getBlocksContent() {
return this.backendInteractor
.fetchBlocks()
.then(this.generateExportableUsersContent)
},
getMutesContent () {
return this.backendInteractor.fetchMutes()
getMutesContent() {
return this.backendInteractor
.fetchMutes()
.then(this.generateExportableUsersContent)
},
importFollows (file) {
return this.backendInteractor.importFollows({ file })
.then((status) => {
if (!status) {
throw new Error('failed')
}
})
},
importBlocks (file) {
return this.backendInteractor.importBlocks({ file })
.then((status) => {
if (!status) {
throw new Error('failed')
}
})
},
importMutes (file) {
return this.backendInteractor.importMutes({ file })
.then((status) => {
if (!status) {
throw new Error('failed')
}
})
},
generateExportableUsersContent (users) {
// Get addresses
return users.map((user) => {
// check is it's a local user
if (user && user.is_local) {
// append the instance address
return user.screen_name + '@' + location.hostname
importFollows(file) {
return this.backendInteractor.importFollows({ file }).then((status) => {
if (!status) {
throw new Error('failed')
}
return user.screen_name
}).join('\n')
})
},
addBackup () {
this.$store.state.api.backendInteractor.addBackup()
importBlocks(file) {
return this.backendInteractor.importBlocks({ file }).then((status) => {
if (!status) {
throw new Error('failed')
}
})
},
importMutes(file) {
return this.backendInteractor.importMutes({ file }).then((status) => {
if (!status) {
throw new Error('failed')
}
})
},
generateExportableUsersContent(users) {
// Get addresses
return users
.map((user) => {
// check is it's a local user
if (user && user.is_local) {
// append the instance address
return user.screen_name + '@' + location.hostname
}
return user.screen_name
})
.join('\n')
},
addBackup() {
this.$store.state.api.backendInteractor
.addBackup()
.then(() => {
this.addedBackup = true
this.addBackupError = false
@ -90,8 +93,9 @@ const DataImportExportTab = {
})
.then(() => this.fetchBackups())
},
fetchBackups () {
this.$store.state.api.backendInteractor.listBackups()
fetchBackups() {
this.$store.state.api.backendInteractor
.listBackups()
.then((res) => {
this.backups = res
this.listBackupsError = false
@ -99,8 +103,8 @@ const DataImportExportTab = {
.catch((error) => {
this.listBackupsError = error.error
})
}
}
},
},
}
export default DataImportExportTab

View file

@ -4,43 +4,44 @@ import SharedComputedObject from '../helpers/shared_computed_object.js'
import { clearCache, cacheKey, emojiCacheKey } from 'src/services/sw/sw.js'
const pleromaFeCommitUrl = 'https://git.pleroma.social/pleroma/pleroma-fe/commit/'
const pleromaFeCommitUrl =
'https://git.pleroma.social/pleroma/pleroma-fe/commit/'
const VersionTab = {
data () {
data() {
const instance = this.$store.state.instance
return {
backendVersion: instance.backendVersion,
backendRepository: instance.backendRepository,
frontendVersion: instance.frontendVersion
frontendVersion: instance.frontendVersion,
}
},
components: {
BooleanSetting
BooleanSetting,
},
computed: {
frontendVersionLink () {
frontendVersionLink() {
return pleromaFeCommitUrl + this.frontendVersion
},
...SharedComputedObject(),
},
methods: {
clearAssetCache () {
clearAssetCache() {
this.clearCache(cacheKey)
},
clearEmojiCache () {
clearEmojiCache() {
this.clearCache(emojiCacheKey)
},
clearCache (key) {
clearCache(key) {
clearCache(key)
.then(() => {
this.$store.dispatch('settingsSaved', { success: true })
})
.catch(error => {
.catch((error) => {
this.$store.dispatch('settingsSaved', { error })
})
}
}
},
},
}
export default VersionTab

View file

@ -1,14 +1,14 @@
import { cloneDeep } from 'lodash'
import { mapState, mapActions } from 'pinia'
import { mapState as mapVuexState } from 'vuex'
import { v4 as uuidv4 } from 'uuid';
import { v4 as uuidv4 } from 'uuid'
import { useServerSideStorageStore } from 'src/stores/serverSideStorage'
import { useInterfaceStore } from 'src/stores/interface'
import {
newImporter,
newExporter
newExporter,
} from 'src/services/export_import/export_import.js'
import BooleanSetting from '../helpers/boolean_setting.vue'
@ -24,27 +24,29 @@ import SharedComputedObject from '../helpers/shared_computed_object.js'
const SUPPORTED_TYPES = new Set(['word', 'regexp', 'user', 'user_regexp'])
const FilteringTab = {
data () {
data() {
return {
replyVisibilityOptions: ['all', 'following', 'self'].map(mode => ({
replyVisibilityOptions: ['all', 'following', 'self'].map((mode) => ({
key: mode,
value: mode,
label: this.$t(`settings.reply_visibility_${mode}`)
label: this.$t(`settings.reply_visibility_${mode}`),
})),
muteBlockLv1Options: ['ask', 'forever', 'temporarily'].map(mode => ({
muteBlockLv1Options: ['ask', 'forever', 'temporarily'].map((mode) => ({
key: mode,
value: mode,
label: this.$t(`user_card.mute_block_${mode}`)
label: this.$t(`user_card.mute_block_${mode}`),
})),
muteFiltersDraftObject: cloneDeep(useServerSideStorageStore().prefsStorage.simple.muteFilters),
muteFiltersDraftObject: cloneDeep(
useServerSideStorageStore().prefsStorage.simple.muteFilters,
),
muteFiltersDraftDirty: Object.fromEntries(
Object.entries(
useServerSideStorageStore().prefsStorage.simple.muteFilters
).map(([k]) => [k, false])
useServerSideStorageStore().prefsStorage.simple.muteFilters,
).map(([k]) => [k, false]),
),
exportedFilter: null,
filterImporter: newImporter({
validator (parsed) {
validator(parsed) {
if (Array.isArray(parsed)) return false
if (!SUPPORTED_TYPES.has(parsed.type)) return false
return true
@ -55,7 +57,7 @@ const FilteringTab = {
expires = null,
hide = false,
name = '',
value = ''
value = '',
} = data
this.createFilter({
@ -63,22 +65,21 @@ const FilteringTab = {
expires,
hide,
name,
value
value,
})
},
onImportFailure (result) {
onImportFailure(result) {
console.error('Failure importing filter:', result)
useInterfaceStore()
.pushGlobalNotice({
messageKey: 'settings.filter.import_failure',
level: 'error'
})
}
useInterfaceStore().pushGlobalNotice({
messageKey: 'settings.filter.import_failure',
level: 'error',
})
},
}),
filterExporter: newExporter({
filename: 'pleromafe_mute-filter',
getExportedObject: () => this.exportedFilter
})
getExportedObject: () => this.exportedFilter,
}),
}
},
components: {
@ -88,23 +89,23 @@ const FilteringTab = {
IntegerSetting,
Checkbox,
Select,
HelpIndicator
HelpIndicator,
},
computed: {
instanceSpecificPanelPresent () { return this.$store.state.instance.showInstanceSpecificPanel },
instanceSpecificPanelPresent() {
return this.$store.state.instance.showInstanceSpecificPanel
},
...SharedComputedObject(),
...mapState(
useServerSideStorageStore,
{
muteFilters: store => Object.entries(store.prefsStorage.simple.muteFilters),
muteFiltersObject: store => store.prefsStorage.simple.muteFilters
}
),
...mapState(useServerSideStorageStore, {
muteFilters: (store) =>
Object.entries(store.prefsStorage.simple.muteFilters),
muteFiltersObject: (store) => store.prefsStorage.simple.muteFilters,
}),
...mapVuexState({
blockExpirationSupported: state => state.instance.blockExpiration
blockExpirationSupported: (state) => state.instance.blockExpiration,
}),
onMuteDefaultActionLv1: {
get () {
get() {
const value = this.$store.state.config.onMuteDefaultAction
if (value === 'ask' || value === 'forever') {
return value
@ -112,16 +113,19 @@ const FilteringTab = {
return 'temporarily'
}
},
set (value) {
set(value) {
let realValue = value
if (value !== 'ask' && value !== 'forever') {
realValue = '14d'
}
this.$store.dispatch('setOption', { name: 'onMuteDefaultAction', value: realValue })
}
this.$store.dispatch('setOption', {
name: 'onMuteDefaultAction',
value: realValue,
})
},
},
onBlockDefaultActionLv1: {
get () {
get() {
const value = this.$store.state.config.onBlockDefaultAction
if (value === 'ask' || value === 'forever') {
return value
@ -129,29 +133,36 @@ const FilteringTab = {
return 'temporarily'
}
},
set (value) {
set(value) {
let realValue = value
if (value !== 'ask' && value !== 'forever') {
realValue = '14d'
}
this.$store.dispatch('setOption', { name: 'onBlockDefaultAction', value: realValue })
}
this.$store.dispatch('setOption', {
name: 'onBlockDefaultAction',
value: realValue,
})
},
},
muteFiltersDraft () {
muteFiltersDraft() {
return Object.entries(this.muteFiltersDraftObject)
},
muteFiltersExpired () {
muteFiltersExpired() {
const now = Date.now()
return Object
.entries(this.muteFiltersDraftObject)
.filter(([, { expires }]) => expires != null && expires <= now)
}
return Object.entries(this.muteFiltersDraftObject).filter(
([, { expires }]) => expires != null && expires <= now,
)
},
},
methods: {
...mapActions(useServerSideStorageStore, ['setPreference', 'unsetPreference', 'pushServerSideStorage']),
getDatetimeLocal (timestamp) {
...mapActions(useServerSideStorageStore, [
'setPreference',
'unsetPreference',
'pushServerSideStorage',
]),
getDatetimeLocal(timestamp) {
const date = new Date(timestamp)
const fmt = new Intl.NumberFormat("en-US", {minimumIntegerDigits: 2})
const fmt = new Intl.NumberFormat('en-US', { minimumIntegerDigits: 2 })
const datetime = [
date.getFullYear(),
'-',
@ -161,11 +172,11 @@ const FilteringTab = {
'T',
fmt.format(date.getHours()),
':',
fmt.format(date.getMinutes())
fmt.format(date.getMinutes()),
].join('')
return datetime
},
checkRegexValid (id) {
checkRegexValid(id) {
const filter = this.muteFiltersObject[id]
if (filter.type !== 'regexp') return true
if (filter.type !== 'user_regexp') return true
@ -179,19 +190,21 @@ const FilteringTab = {
}
return valid
},
createFilter (filter = {
type: 'word',
value: '',
name: 'New Filter',
enabled: true,
expires: null,
hide: false,
}) {
createFilter(
filter = {
type: 'word',
value: '',
name: 'New Filter',
enabled: true,
expires: null,
hide: false,
},
) {
const newId = uuidv4()
filter.order = this.muteFilters.length + 2
this.muteFiltersDraftObject[newId] = filter
this.setPreference({ path: 'simple.muteFilters.' + newId , value: filter })
this.setPreference({ path: 'simple.muteFilters.' + newId, value: filter })
this.pushServerSideStorage()
},
exportFilter(id) {
@ -202,23 +215,23 @@ const FilteringTab = {
importFilter() {
this.filterImporter.importData()
},
copyFilter (id) {
copyFilter(id) {
const filter = { ...this.muteFiltersDraftObject[id] }
const newId = uuidv4()
this.muteFiltersDraftObject[newId] = filter
this.setPreference({ path: 'simple.muteFilters.' + newId , value: filter })
this.setPreference({ path: 'simple.muteFilters.' + newId, value: filter })
this.pushServerSideStorage()
},
deleteFilter (id) {
deleteFilter(id) {
delete this.muteFiltersDraftObject[id]
this.unsetPreference({ path: 'simple.muteFilters.' + id , value: null })
this.unsetPreference({ path: 'simple.muteFilters.' + id, value: null })
this.pushServerSideStorage()
},
purgeExpiredFilters () {
purgeExpiredFilters() {
this.muteFiltersExpired.forEach(([id]) => {
delete this.muteFiltersDraftObject[id]
this.unsetPreference({ path: 'simple.muteFilters.' + id , value: null })
this.unsetPreference({ path: 'simple.muteFilters.' + id, value: null })
})
this.pushServerSideStorage()
},
@ -242,17 +255,20 @@ const FilteringTab = {
this.muteFiltersDraftDirty[id] = true
},
saveFilter(id) {
this.setPreference({ path: 'simple.muteFilters.' + id , value: this.muteFiltersDraftObject[id] })
this.setPreference({
path: 'simple.muteFilters.' + id,
value: this.muteFiltersDraftObject[id],
})
this.pushServerSideStorage()
this.muteFiltersDraftDirty[id] = false
},
},
// Updating nested properties
watch: {
replyVisibility () {
replyVisibility() {
this.$store.dispatch('queueFlushAll')
}
}
},
},
}
export default FilteringTab

View file

@ -16,17 +16,17 @@ const GeneralTab = {
props: {
parentCollapsed: {
required: true,
type: Boolean
}
type: Boolean,
},
},
data () {
data() {
return {
absoluteTime12hOptions: ['24h', '12h'].map(mode => ({
absoluteTime12hOptions: ['24h', '12h'].map((mode) => ({
key: mode,
value: mode,
label: this.$t(`settings.absolute_time_format_12h_${mode}`)
label: this.$t(`settings.absolute_time_format_12h_${mode}`),
})),
emailLanguage: this.$store.state.users.currentUser.language || ['']
emailLanguage: this.$store.state.users.currentUser.language || [''],
}
},
components: {
@ -36,24 +36,31 @@ const GeneralTab = {
FloatSetting,
FontControl,
InterfaceLanguageSwitcher,
ProfileSettingIndicator
ProfileSettingIndicator,
},
computed: {
language: {
get: function () { return this.$store.getters.mergedConfig.interfaceLanguage },
get: function () {
return this.$store.getters.mergedConfig.interfaceLanguage
},
set: function (val) {
this.$store.dispatch('setOption', { name: 'interfaceLanguage', value: val })
}
this.$store.dispatch('setOption', {
name: 'interfaceLanguage',
value: val,
})
},
},
...SharedComputedObject(),
...mapState({
blockExpirationSupported: state => state.instance.blockExpiration,
})
blockExpirationSupported: (state) => state.instance.blockExpiration,
}),
},
methods: {
updateProfile () {
updateProfile() {
const params = {
language: localeService.internalToBackendLocaleMulti(this.emailLanguage)
language: localeService.internalToBackendLocaleMulti(
this.emailLanguage,
),
}
this.$store.state.api.backendInteractor
@ -63,19 +70,19 @@ const GeneralTab = {
this.$store.commit('setCurrentUser', user)
})
},
updateFont (key, value) {
updateFont(key, value) {
this.$store.dispatch('setOption', {
name: 'theme3hacks',
value: {
...this.mergedConfig.theme3hacks,
fonts: {
...this.mergedConfig.theme3hacks.fonts,
[key]: value
}
}
[key]: value,
},
},
})
},
}
},
}
export default GeneralTab

View file

@ -9,42 +9,49 @@ const GeneralTab = {
props: {
parentCollapsed: {
required: true,
type: Boolean
}
type: Boolean,
},
},
data () {
data() {
return {
thirdColumnModeOptions: ['none', 'notifications', 'postform'].map(mode => ({
key: mode,
value: mode,
label: this.$t(`settings.third_column_mode_${mode}`)
}))
thirdColumnModeOptions: ['none', 'notifications', 'postform'].map(
(mode) => ({
key: mode,
value: mode,
label: this.$t(`settings.third_column_mode_${mode}`),
}),
),
}
},
components: {
BooleanSetting,
ChoiceSetting,
UnitSetting,
ProfileSettingIndicator
ProfileSettingIndicator,
},
computed: {
postFormats () {
postFormats() {
return this.$store.state.instance.postFormats || []
},
instanceShoutboxPresent () { return this.$store.state.instance.shoutAvailable },
columns () {
instanceShoutboxPresent() {
return this.$store.state.instance.shoutAvailable
},
columns() {
const mode = this.$store.getters.mergedConfig.thirdColumnMode
const notif = mode === 'none' ? [] : ['notifs']
if (this.$store.getters.mergedConfig.sidebarRight || mode === 'postform') {
if (
this.$store.getters.mergedConfig.sidebarRight ||
mode === 'postform'
) {
return [...notif, 'content', 'sidebar']
} else {
return ['sidebar', 'content', ...notif]
}
},
...SharedComputedObject(),
}
},
}
export default GeneralTab

View file

@ -15,31 +15,33 @@ import { useOAuthTokensStore } from 'src/stores/oauth_tokens'
const BlockList = withLoadMore({
fetch: (props, $store) => $store.dispatch('fetchBlocks'),
select: (props, $store) => get($store.state.users.currentUser, 'blockIds', []),
select: (props, $store) =>
get($store.state.users.currentUser, 'blockIds', []),
destroy: () => {},
childPropName: 'items'
childPropName: 'items',
})(SelectableList)
const MuteList = withLoadMore({
fetch: (props, $store) => $store.dispatch('fetchMutes'),
select: (props, $store) => get($store.state.users.currentUser, 'muteIds', []),
destroy: () => {},
childPropName: 'items'
childPropName: 'items',
})(SelectableList)
const DomainMuteList = withSubscription({
fetch: (props, $store) => $store.dispatch('fetchDomainMutes'),
select: (props, $store) => get($store.state.users.currentUser, 'domainMutes', []),
childPropName: 'items'
select: (props, $store) =>
get($store.state.users.currentUser, 'domainMutes', []),
childPropName: 'items',
})(SelectableList)
const MutesAndBlocks = {
data () {
data() {
return {
activeTab: 'profile'
activeTab: 'profile',
}
},
created () {
created() {
useOAuthTokensStore().fetchTokens()
this.$store.dispatch('getKnownDomains')
},
@ -53,87 +55,94 @@ const MutesAndBlocks = {
DomainMuteCard,
ProgressButton,
Autosuggest,
Checkbox
Checkbox,
},
computed: {
knownDomains () {
knownDomains() {
return this.$store.state.instance.knownDomains
},
user () {
user() {
return this.$store.state.users.currentUser
}
},
},
methods: {
importFollows (file) {
return this.$store.state.api.backendInteractor.importFollows({ file })
importFollows(file) {
return this.$store.state.api.backendInteractor
.importFollows({ file })
.then((status) => {
if (!status) {
throw new Error('failed')
}
})
},
importBlocks (file) {
return this.$store.state.api.backendInteractor.importBlocks({ file })
importBlocks(file) {
return this.$store.state.api.backendInteractor
.importBlocks({ file })
.then((status) => {
if (!status) {
throw new Error('failed')
}
})
},
generateExportableUsersContent (users) {
generateExportableUsersContent(users) {
// Get addresses
return users.map((user) => {
// check is it's a local user
if (user && user.is_local) {
// append the instance address
return user.screen_name + '@' + location.hostname
}
return user.screen_name
}).join('\n')
return users
.map((user) => {
// check is it's a local user
if (user && user.is_local) {
// append the instance address
return user.screen_name + '@' + location.hostname
}
return user.screen_name
})
.join('\n')
},
activateTab (tabName) {
activateTab(tabName) {
this.activeTab = tabName
},
filterUnblockedUsers (userIds) {
filterUnblockedUsers(userIds) {
return reject(userIds, (userId) => {
const relationship = this.$store.getters.relationship(this.userId)
return relationship.blocking || userId === this.user.id
})
},
filterUnMutedUsers (userIds) {
filterUnMutedUsers(userIds) {
return reject(userIds, (userId) => {
const relationship = this.$store.getters.relationship(this.userId)
return relationship.muting || userId === this.user.id
})
},
queryUserIds (query) {
return this.$store.dispatch('searchUsers', { query })
queryUserIds(query) {
return this.$store
.dispatch('searchUsers', { query })
.then((users) => map(users, 'id'))
},
blockUsers (ids) {
blockUsers(ids) {
return this.$store.dispatch('blockUsers', ids)
},
unblockUsers (ids) {
unblockUsers(ids) {
return this.$store.dispatch('unblockUsers', ids)
},
muteUsers (ids) {
muteUsers(ids) {
return this.$store.dispatch('muteUsers', ids)
},
unmuteUsers (ids) {
unmuteUsers(ids) {
return this.$store.dispatch('unmuteUsers', ids)
},
filterUnMutedDomains (urls) {
return urls.filter(url => !this.user.domainMutes.includes(url))
filterUnMutedDomains(urls) {
return urls.filter((url) => !this.user.domainMutes.includes(url))
},
queryKnownDomains (query) {
queryKnownDomains(query) {
return new Promise((resolve) => {
resolve(this.knownDomains.filter(url => url.toLowerCase().includes(query)))
resolve(
this.knownDomains.filter((url) => url.toLowerCase().includes(query)),
)
})
},
unmuteDomains (domains) {
unmuteDomains(domains) {
return this.$store.dispatch('unmuteDomains', domains)
}
}
},
},
}
export default MutesAndBlocks

View file

@ -2,32 +2,36 @@ import BooleanSetting from '../helpers/boolean_setting.vue'
import SharedComputedObject from '../helpers/shared_computed_object.js'
const NotificationsTab = {
data () {
data() {
return {
activeTab: 'profile',
notificationSettings: this.$store.state.users.currentUser.notification_settings,
newDomainToMute: ''
notificationSettings:
this.$store.state.users.currentUser.notification_settings,
newDomainToMute: '',
}
},
components: {
BooleanSetting
BooleanSetting,
},
computed: {
user () {
user() {
return this.$store.state.users.currentUser
},
canReceiveReports () {
if (!this.user) { return false }
canReceiveReports() {
if (!this.user) {
return false
}
return this.user.privileges.includes('reports_manage_reports')
},
...SharedComputedObject()
...SharedComputedObject(),
},
methods: {
updateNotificationSettings () {
this.$store.state.api.backendInteractor
.updateNotificationSettings({ settings: this.notificationSettings })
}
}
updateNotificationSettings() {
this.$store.state.api.backendInteractor.updateNotificationSettings({
settings: this.notificationSettings,
})
},
},
}
export default NotificationsTab

View file

@ -2,15 +2,13 @@ import {
rgb2hex,
hex2rgb,
getContrastRatioLayers,
relativeLuminance
relativeLuminance,
} from 'src/services/color_convert/color_convert.js'
import {
newImporter,
newExporter
newExporter,
} from 'src/services/export_import/export_import.js'
import {
SLOT_INHERITANCE
} from 'src/services/theme_data/pleromafe.js'
import { SLOT_INHERITANCE } from 'src/services/theme_data/pleromafe.js'
import {
CURRENT_VERSION,
OPACITIES,
@ -22,16 +20,19 @@ import {
generateRadii,
generateFonts,
shadows2to3,
colors2to3
colors2to3,
} from 'src/services/theme_data/theme_data.service.js'
import { convertTheme2To3 } from 'src/services/theme_data/theme2_to_theme3.js'
import { init } from 'src/services/theme_data/theme_data_3.service.js'
import {
getCssRules,
getScopedVersion
getScopedVersion,
} from 'src/services/theme_data/css_utils.js'
import { createStyleSheet, adoptStyleSheets } from 'src/services/style_setter/style_setter.js'
import {
createStyleSheet,
adoptStyleSheets,
} from 'src/services/style_setter/style_setter.js'
import ColorInput from 'src/components/color_input/color_input.vue'
import RangeInput from 'src/components/range_input/range_input.vue'
@ -55,8 +56,8 @@ const v1OnlyNames = [
'cRed',
'cGreen',
'cBlue',
'cOrange'
].map(_ => _ + 'ColorLocal')
'cOrange',
].map((_) => _ + 'ColorLocal')
const colorConvert = (color) => {
if (color.startsWith('--') || color === 'transparent') {
@ -67,16 +68,16 @@ const colorConvert = (color) => {
}
export default {
data () {
data() {
return {
themeImporter: newImporter({
validator: this.importValidator,
onImport: this.onImport,
onImportFailure: this.onImportFailure
onImportFailure: this.onImportFailure,
}),
themeExporter: newExporter({
filename: 'pleroma_theme',
getExportedObject: () => this.exportedTheme
getExportedObject: () => this.exportedTheme,
}),
availableStyles: [],
selected: '',
@ -98,12 +99,18 @@ export default {
keepFonts: false,
...Object.keys(SLOT_INHERITANCE)
.map(key => [key, ''])
.reduce((acc, [key, val]) => ({ ...acc, [key + 'ColorLocal']: val }), {}),
.map((key) => [key, ''])
.reduce(
(acc, [key, val]) => ({ ...acc, [key + 'ColorLocal']: val }),
{},
),
...Object.keys(OPACITIES)
.map(key => [key, ''])
.reduce((acc, [key, val]) => ({ ...acc, [key + 'OpacityLocal']: val }), {}),
.map((key) => [key, ''])
.reduce(
(acc, [key, val]) => ({ ...acc, [key + 'OpacityLocal']: val }),
{},
),
shadowSelected: undefined,
shadowsLocal: {},
@ -117,10 +124,10 @@ export default {
avatarAltRadiusLocal: '',
attachmentRadiusLocal: '',
tooltipRadiusLocal: '',
chatMessageRadiusLocal: ''
chatMessageRadiusLocal: '',
}
},
created () {
created() {
const currentIndex = this.$store.state.instance.themesIndex
let promise
@ -130,50 +137,48 @@ export default {
promise = useInterfaceStore().fetchThemesIndex()
}
promise.then(themesIndex => {
Object
.values(themesIndex)
.forEach(themeFunc => {
themeFunc().then(themeData => themeData && this.availableStyles.push(themeData))
})
promise.then((themesIndex) => {
Object.values(themesIndex).forEach((themeFunc) => {
themeFunc().then(
(themeData) => themeData && this.availableStyles.push(themeData),
)
})
})
},
mounted () {
mounted() {
if (typeof this.shadowSelected === 'undefined') {
this.shadowSelected = this.shadowsAvailable[0]
}
},
computed: {
themeWarningHelp () {
themeWarningHelp() {
if (!this.themeWarning) return
const t = this.$t
const pre = 'settings.style.switcher.help.'
const {
origin,
themeEngineVersion,
type,
noActionsPossible
} = this.themeWarning
const { origin, themeEngineVersion, type, noActionsPossible } =
this.themeWarning
if (origin === 'file') {
// Loaded v2 theme from file
if (themeEngineVersion === 2 && type === 'wrong_version') {
return t(pre + 'v2_imported')
}
if (themeEngineVersion > CURRENT_VERSION) {
return t(pre + 'future_version_imported') + ' ' +
(
noActionsPossible
? t(pre + 'snapshot_missing')
: t(pre + 'snapshot_present')
)
return (
t(pre + 'future_version_imported') +
' ' +
(noActionsPossible
? t(pre + 'snapshot_missing')
: t(pre + 'snapshot_present'))
)
}
if (themeEngineVersion < CURRENT_VERSION) {
return t(pre + 'future_version_imported') + ' ' +
(
noActionsPossible
? t(pre + 'snapshot_missing')
: t(pre + 'snapshot_present')
)
return (
t(pre + 'future_version_imported') +
' ' +
(noActionsPossible
? t(pre + 'snapshot_missing')
: t(pre + 'snapshot_present'))
)
}
} else if (origin === 'localStorage') {
if (type === 'snapshot_source_mismatch') {
@ -185,38 +190,40 @@ export default {
}
// Admin downgraded FE
if (themeEngineVersion > CURRENT_VERSION) {
return t(pre + 'fe_downgraded') + ' ' +
(
noActionsPossible
? t(pre + 'migration_snapshot_ok')
: t(pre + 'migration_snapshot_gone')
)
return (
t(pre + 'fe_downgraded') +
' ' +
(noActionsPossible
? t(pre + 'migration_snapshot_ok')
: t(pre + 'migration_snapshot_gone'))
)
}
// Admin upgraded FE
if (themeEngineVersion < CURRENT_VERSION) {
return t(pre + 'fe_upgraded') + ' ' +
(
noActionsPossible
? t(pre + 'migration_snapshot_ok')
: t(pre + 'migration_snapshot_gone')
)
return (
t(pre + 'fe_upgraded') +
' ' +
(noActionsPossible
? t(pre + 'migration_snapshot_ok')
: t(pre + 'migration_snapshot_gone'))
)
}
}
},
selectedVersion () {
selectedVersion() {
return Array.isArray(this.selectedTheme) ? 1 : 2
},
currentColors () {
currentColors() {
return Object.keys(SLOT_INHERITANCE)
.map(key => [key, this[key + 'ColorLocal']])
.map((key) => [key, this[key + 'ColorLocal']])
.reduce((acc, [key, val]) => ({ ...acc, [key]: val }), {})
},
currentOpacity () {
currentOpacity() {
return Object.keys(OPACITIES)
.map(key => [key, this[key + 'OpacityLocal']])
.map((key) => [key, this[key + 'OpacityLocal']])
.reduce((acc, [key, val]) => ({ ...acc, [key]: val }), {})
},
currentRadii () {
currentRadii() {
return {
btn: this.btnRadiusLocal,
input: this.inputRadiusLocal,
@ -226,11 +233,11 @@ export default {
avatarAlt: this.avatarAltRadiusLocal,
tooltip: this.tooltipRadiusLocal,
attachment: this.attachmentRadiusLocal,
chatMessage: this.chatMessageRadiusLocal
chatMessage: this.chatMessageRadiusLocal,
}
},
// This needs optimization maybe
previewContrast () {
previewContrast() {
try {
if (!this.previewTheme.colors.bg) return {}
const colors = this.previewTheme.colors
@ -243,113 +250,124 @@ export default {
aaa: ratio >= 7,
// same but for 18pt+ texts
laa: ratio >= 3,
laaa: ratio >= 4.5
laaa: ratio >= 4.5,
})
const colorsConverted = Object.entries(colors).reduce((acc, [key, value]) => ({ ...acc, [key]: colorConvert(value) }), {})
const colorsConverted = Object.entries(colors).reduce(
(acc, [key, value]) => ({ ...acc, [key]: colorConvert(value) }),
{},
)
const ratios = Object.entries(SLOT_INHERITANCE).reduce((acc, [key, value]) => {
const slotIsBaseText = key === 'text' || key === 'link'
const slotIsText = slotIsBaseText || (
typeof value === 'object' && value !== null && value.textColor
)
if (!slotIsText) return acc
const { layer, variant } = slotIsBaseText ? { layer: 'bg' } : value
const background = variant || layer
const opacitySlot = getOpacitySlot(background)
const textColors = [
key,
...(background === 'bg' ? ['cRed', 'cGreen', 'cBlue', 'cOrange'] : [])
]
const ratios = Object.entries(SLOT_INHERITANCE).reduce(
(acc, [key, value]) => {
const slotIsBaseText = key === 'text' || key === 'link'
const slotIsText =
slotIsBaseText ||
(typeof value === 'object' && value !== null && value.textColor)
if (!slotIsText) return acc
const { layer, variant } = slotIsBaseText ? { layer: 'bg' } : value
const background = variant || layer
const opacitySlot = getOpacitySlot(background)
const textColors = [
key,
...(background === 'bg'
? ['cRed', 'cGreen', 'cBlue', 'cOrange']
: []),
]
const layers = getLayers(
layer,
variant || layer,
opacitySlot,
colorsConverted,
opacity
)
const layers = getLayers(
layer,
variant || layer,
opacitySlot,
colorsConverted,
opacity,
)
// Temporary patch for null-y value errors
if (layers.flat().some(v => v == null)) return acc
// Temporary patch for null-y value errors
if (layers.flat().some((v) => v == null)) return acc
return {
...acc,
...textColors.reduce((acc, textColorKey) => {
const newKey = slotIsBaseText
? 'bg' + textColorKey[0].toUpperCase() + textColorKey.slice(1)
: textColorKey
return {
...acc,
[newKey]: getContrastRatioLayers(
colorsConverted[textColorKey],
layers,
colorsConverted[textColorKey]
)
}
}, {})
}
return {
...acc,
...textColors.reduce((acc, textColorKey) => {
const newKey = slotIsBaseText
? 'bg' + textColorKey[0].toUpperCase() + textColorKey.slice(1)
: textColorKey
return {
...acc,
[newKey]: getContrastRatioLayers(
colorsConverted[textColorKey],
layers,
colorsConverted[textColorKey],
),
}
}, {}),
}
},
{},
)
return Object.entries(ratios).reduce((acc, [k, v]) => {
acc[k] = hints(v)
return acc
}, {})
return Object.entries(ratios).reduce((acc, [k, v]) => { acc[k] = hints(v); return acc }, {})
} catch (e) {
console.warn('Failure computing contrasts', e)
return {}
}
},
themeDataUsed () {
themeDataUsed() {
return useInterfaceStore().themeDataUsed
},
shadowsAvailable () {
shadowsAvailable() {
return Object.keys(DEFAULT_SHADOWS).sort()
},
currentShadowOverriden: {
get () {
get() {
return !!this.currentShadow
},
set (val) {
set(val) {
if (val) {
this.shadowsLocal[this.shadowSelected] = (this.currentShadowFallback || [])
.map(s => ({
name: null,
x: 0,
y: 0,
blur: 0,
spread: 0,
inset: false,
color: '#000000',
alpha: 1,
...s
}))
this.shadowsLocal[this.shadowSelected] = (
this.currentShadowFallback || []
).map((s) => ({
name: null,
x: 0,
y: 0,
blur: 0,
spread: 0,
inset: false,
color: '#000000',
alpha: 1,
...s,
}))
} else {
delete this.shadowsLocal[this.shadowSelected]
}
}
},
},
currentShadowFallback () {
currentShadowFallback() {
return (this.previewTheme.shadows || {})[this.shadowSelected]
},
currentShadow: {
get () {
get() {
return this.shadowsLocal[this.shadowSelected]
},
set (v) {
set(v) {
this.shadowsLocal[this.shadowSelected] = v
}
},
},
themeValid () {
themeValid() {
return !this.shadowsInvalid && !this.colorsInvalid && !this.radiiInvalid
},
exportedTheme () {
const saveEverything = (
exportedTheme() {
const saveEverything =
!this.keepFonts &&
!this.keepShadows &&
!this.keepOpacity &&
!this.keepRoundness &&
!this.keepColor
)
const source = {
themeEngineVersion: CURRENT_VERSION
themeEngineVersion: CURRENT_VERSION,
}
if (this.keepFonts || saveEverything) {
@ -370,18 +388,20 @@ export default {
const theme = {
themeEngineVersion: CURRENT_VERSION,
...this.previewTheme
...this.previewTheme,
}
return {
// To separate from other random JSON files and possible future source formats
_pleroma_theme_version: 2, theme, source
_pleroma_theme_version: 2,
theme,
source,
}
},
isActive () {
isActive() {
const tabSwitcher = this.$parent
return tabSwitcher ? tabSwitcher.isActive('theme') : false
}
},
},
components: {
ColorInput,
@ -393,70 +413,65 @@ export default {
TabSwitcher,
Preview,
Checkbox,
Select
Select,
},
methods: {
loadTheme (
{
theme,
source,
_pleroma_theme_version: fileVersion
},
loadTheme(
{ theme, source, _pleroma_theme_version: fileVersion },
origin,
forceUseSource = false
forceUseSource = false,
) {
this.dismissWarning()
const version = (origin === 'localStorage' && !theme.colors)
? 'l1'
: fileVersion
const version =
origin === 'localStorage' && !theme.colors ? 'l1' : fileVersion
const snapshotEngineVersion = (theme || {}).themeEngineVersion
const themeEngineVersion = (source || {}).themeEngineVersion || 2
const versionsMatch = themeEngineVersion === CURRENT_VERSION
const sourceSnapshotMismatch = (
const sourceSnapshotMismatch =
theme !== undefined &&
source !== undefined &&
themeEngineVersion !== snapshotEngineVersion
)
source !== undefined &&
themeEngineVersion !== snapshotEngineVersion
// Force loading of source if user requested it or if snapshot
// is unavailable
const forcedSourceLoad = (source && forceUseSource) || !theme
if (!(versionsMatch && !sourceSnapshotMismatch) &&
!forcedSourceLoad &&
version !== 'l1' &&
origin !== 'defaults'
if (
!(versionsMatch && !sourceSnapshotMismatch) &&
!forcedSourceLoad &&
version !== 'l1' &&
origin !== 'defaults'
) {
if (sourceSnapshotMismatch && origin === 'localStorage') {
this.themeWarning = {
origin,
themeEngineVersion,
type: 'snapshot_source_mismatch'
type: 'snapshot_source_mismatch',
}
} else if (!theme) {
this.themeWarning = {
origin,
noActionsPossible: true,
themeEngineVersion,
type: 'no_snapshot_old_version'
type: 'no_snapshot_old_version',
}
} else if (!versionsMatch) {
this.themeWarning = {
origin,
noActionsPossible: !source,
themeEngineVersion,
type: 'wrong_version'
type: 'wrong_version',
}
}
}
this.normalizeLocalState(theme, version, source, forcedSourceLoad)
},
forceLoadLocalStorage () {
forceLoadLocalStorage() {
this.loadThemeFromLocalStorage(true)
},
dismissWarning () {
dismissWarning() {
this.themeWarning = undefined
this.tempImportFile = undefined
},
forceLoad () {
forceLoad() {
const { origin } = this.themeWarning
switch (origin) {
case 'localStorage':
@ -468,7 +483,7 @@ export default {
}
this.dismissWarning()
},
forceSnapshot () {
forceSnapshot() {
const { origin } = this.themeWarning
switch (origin) {
case 'localStorage':
@ -480,25 +495,25 @@ export default {
}
this.dismissWarning()
},
loadThemeFromLocalStorage (confirmLoadSource = false) {
loadThemeFromLocalStorage(confirmLoadSource = false) {
const theme = this.themeDataUsed?.source
if (theme) {
this.loadTheme(
{
theme
theme,
},
'localStorage',
confirmLoadSource
confirmLoadSource,
)
}
},
setCustomTheme () {
setCustomTheme() {
useInterfaceStore().setThemeV2({
customTheme: {
ignore: true,
themeFileVersion: this.selectedVersion,
themeEngineVersion: CURRENT_VERSION,
...this.previewTheme
...this.previewTheme,
},
customThemeSource: {
themeFileVersion: this.selectedVersion,
@ -507,77 +522,84 @@ export default {
fonts: this.fontsLocal,
opacity: this.currentOpacity,
colors: this.currentColors,
radii: this.currentRadii
}
radii: this.currentRadii,
},
})
},
updatePreviewColors () {
updatePreviewColors() {
const result = generateColors({
opacity: this.currentOpacity,
colors: this.currentColors
colors: this.currentColors,
})
this.previewTheme.colors = result.theme.colors
this.previewTheme.opacity = result.theme.opacity
},
updatePreviewShadows () {
updatePreviewShadows() {
this.previewTheme.shadows = generateShadows(
{
shadows: this.shadowsLocal,
opacity: this.previewTheme.opacity,
themeEngineVersion: this.engineVersion
themeEngineVersion: this.engineVersion,
},
this.previewTheme.colors,
relativeLuminance(this.previewTheme.colors.bg) < 0.5 ? 1 : -1
relativeLuminance(this.previewTheme.colors.bg) < 0.5 ? 1 : -1,
).theme.shadows
},
importTheme () { this.themeImporter.importData() },
exportTheme () { this.themeExporter.exportData() },
onImport (parsed, forceSource = false) {
importTheme() {
this.themeImporter.importData()
},
exportTheme() {
this.themeExporter.exportData()
},
onImport(parsed, forceSource = false) {
this.tempImportFile = parsed
this.loadTheme(parsed, 'file', forceSource)
},
onImportFailure () {
useInterfaceStore().pushGlobalNotice({ messageKey: 'settings.invalid_theme_imported', level: 'error' })
onImportFailure() {
useInterfaceStore().pushGlobalNotice({
messageKey: 'settings.invalid_theme_imported',
level: 'error',
})
},
importValidator (parsed) {
importValidator(parsed) {
const version = parsed._pleroma_theme_version
return version >= 1 || version <= 2
},
clearAll () {
clearAll() {
this.loadThemeFromLocalStorage()
},
// Clears all the extra stuff when loading V1 theme
clearV1 () {
clearV1() {
Object.keys(this.$data)
.filter(_ => _.endsWith('ColorLocal') || _.endsWith('OpacityLocal'))
.filter(_ => !v1OnlyNames.includes(_))
.forEach(key => {
.filter((_) => _.endsWith('ColorLocal') || _.endsWith('OpacityLocal'))
.filter((_) => !v1OnlyNames.includes(_))
.forEach((key) => {
this.$data[key] = undefined
})
},
clearRoundness () {
clearRoundness() {
Object.keys(this.$data)
.filter(_ => _.endsWith('RadiusLocal'))
.forEach(key => {
.filter((_) => _.endsWith('RadiusLocal'))
.forEach((key) => {
this.$data[key] = undefined
})
},
clearOpacity () {
clearOpacity() {
Object.keys(this.$data)
.filter(_ => _.endsWith('OpacityLocal'))
.forEach(key => {
.filter((_) => _.endsWith('OpacityLocal'))
.forEach((key) => {
this.$data[key] = undefined
})
},
clearShadows () {
clearShadows() {
this.shadowsLocal = {}
},
clearFonts () {
clearFonts() {
this.fontsLocal = {}
},
@ -594,7 +616,7 @@ export default {
* @param {Boolean} source - by default source won't be used if version doesn't match since it might render differently
* this allows importing source anyway
*/
normalizeLocalState (theme, version = 0, source, forceSource = false) {
normalizeLocalState(theme, version = 0, source, forceSource = false) {
let input
if (typeof source !== 'undefined') {
if (forceSource || source?.themeEngineVersion === CURRENT_VERSION) {
@ -618,11 +640,17 @@ export default {
if (version === 0) {
if (input.version) version = input.version
// Old v1 naming: fg is text, btn is foreground
if (typeof colors.text === 'undefined' && typeof colors.fg !== 'undefined') {
if (
typeof colors.text === 'undefined' &&
typeof colors.fg !== 'undefined'
) {
version = 1
}
// New v2 naming: text is text, fg is foreground
if (typeof colors.text !== 'undefined' && typeof colors.fg !== 'undefined') {
if (
typeof colors.text !== 'undefined' &&
typeof colors.fg !== 'undefined'
) {
version = 2
}
}
@ -648,7 +676,7 @@ export default {
.add('cOrange')
}
keys.forEach(key => {
keys.forEach((key) => {
const color = colors[key]
const hex = rgb2hex(colors[key])
this[key + 'ColorLocal'] = hex === '#aN' ? color : hex
@ -689,33 +717,32 @@ export default {
this.fontsLocal = fonts
}
},
updateTheme3Preview () {
updateTheme3Preview() {
const theme2 = convertTheme2To3(this.previewTheme)
const theme3 = init({
inputRuleset: theme2,
ultimateBackgroundColor: '#000000',
liteMode: true
liteMode: true,
})
const sheet = createStyleSheet('theme-tab-overall-preview', 90)
const rule = getScopedVersion(
getCssRules(theme3.eager),
'&'
).join('\n')
const rule = getScopedVersion(getCssRules(theme3.eager), '&').join('\n')
sheet.clear()
sheet.addRule('#theme-preview {\n' + rule + '\n}')
sheet.ready = true
adoptStyleSheets()
}
},
},
watch: {
themeDataUsed () {
themeDataUsed() {
this.loadThemeFromLocalStorage()
},
currentRadii () {
currentRadii() {
try {
this.previewTheme.radii = generateRadii({ radii: this.currentRadii }).theme.radii
this.previewTheme.radii = generateRadii({
radii: this.currentRadii,
}).theme.radii
this.radiiInvalid = false
} catch (e) {
this.radiiInvalid = true
@ -723,7 +750,7 @@ export default {
}
},
shadowsLocal: {
handler () {
handler() {
try {
this.updatePreviewShadows()
this.shadowsInvalid = false
@ -732,21 +759,23 @@ export default {
console.warn(e)
}
},
deep: true
deep: true,
},
fontsLocal: {
handler () {
handler() {
try {
this.previewTheme.fonts = generateFonts({ fonts: this.fontsLocal }).theme.fonts
this.previewTheme.fonts = generateFonts({
fonts: this.fontsLocal,
}).theme.fonts
this.fontsInvalid = false
} catch (e) {
this.fontsInvalid = true
console.warn(e)
}
},
deep: true
deep: true,
},
currentColors () {
currentColors() {
try {
this.updatePreviewColors()
this.colorsInvalid = false
@ -755,23 +784,25 @@ export default {
console.warn(e)
}
},
currentOpacity () {
currentOpacity() {
try {
this.updatePreviewColors()
} catch (e) {
console.warn(e)
}
},
selected () {
this.selectedTheme = Object.entries(this.availableStyles).find(([, s]) => {
if (Array.isArray(s)) {
return s[0] === this.selected
} else {
return s.name === this.selected
}
})[1]
selected() {
this.selectedTheme = Object.entries(this.availableStyles).find(
([, s]) => {
if (Array.isArray(s)) {
return s[0] === this.selected
} else {
return s.name === this.selected
}
},
)[1]
},
selectedTheme () {
selectedTheme() {
this.dismissWarning()
if (this.selectedVersion === 1) {
if (!this.keepRoundness) {
@ -799,8 +830,12 @@ export default {
this.cOrangeColorLocal = this.selectedTheme[8]
}
} else if (this.selectedVersion >= 2) {
this.normalizeLocalState(this.selectedTheme.theme, 2, this.selectedTheme.source)
this.normalizeLocalState(
this.selectedTheme.theme,
2,
this.selectedTheme.source,
)
}
}
}
},
},
}

View file

@ -124,20 +124,15 @@ import {
faTimes,
faStar,
faRetweet,
faReply
faReply,
} from '@fortawesome/free-solid-svg-icons'
library.add(
faTimes,
faStar,
faRetweet,
faReply
)
library.add(faTimes, faStar, faRetweet, faReply)
export default {
components: {
Checkbox
}
Checkbox,
},
}
</script>

View file

@ -10,43 +10,56 @@ const GeneralTab = {
props: {
parentCollapsed: {
required: true,
type: Boolean
}
type: Boolean,
},
},
data () {
data() {
return {
conversationDisplayOptions: ['tree', 'linear'].map(mode => ({
conversationDisplayOptions: ['tree', 'linear'].map((mode) => ({
key: mode,
value: mode,
label: this.$t(`settings.conversation_display_${mode}`)
label: this.$t(`settings.conversation_display_${mode}`),
})),
conversationOtherRepliesButtonOptions: ['below', 'inside'].map(mode => ({
conversationOtherRepliesButtonOptions: ['below', 'inside'].map(
(mode) => ({
key: mode,
value: mode,
label: this.$t(`settings.conversation_other_replies_button_${mode}`),
}),
),
mentionLinkDisplayOptions: ['short', 'full_for_remote', 'full'].map(
(mode) => ({
key: mode,
value: mode,
label: this.$t(`settings.mention_link_display_${mode}`),
}),
),
userPopoverAvatarActionOptions: ['close', 'zoom', 'open'].map((mode) => ({
key: mode,
value: mode,
label: this.$t(`settings.conversation_other_replies_button_${mode}`)
label: this.$t(`settings.user_popover_avatar_action_${mode}`),
})),
mentionLinkDisplayOptions: ['short', 'full_for_remote', 'full'].map(mode => ({
unsavedPostActionOptions: ['save', 'discard', 'confirm'].map((mode) => ({
key: mode,
value: mode,
label: this.$t(`settings.mention_link_display_${mode}`)
})),
userPopoverAvatarActionOptions: ['close', 'zoom', 'open'].map(mode => ({
key: mode,
value: mode,
label: this.$t(`settings.user_popover_avatar_action_${mode}`)
})),
unsavedPostActionOptions: ['save', 'discard', 'confirm'].map(mode => ({
key: mode,
value: mode,
label: this.$t(`settings.unsaved_post_action_${mode}`)
label: this.$t(`settings.unsaved_post_action_${mode}`),
})),
loopSilentAvailable:
// Firefox
Object.getOwnPropertyDescriptor(HTMLVideoElement.prototype, 'mozHasAudio') ||
// Chrome-likes
Object.getOwnPropertyDescriptor(HTMLMediaElement.prototype, 'webkitAudioDecodedByteCount') ||
// Future spec, still not supported in Nightly 63 as of 08/2018
Object.getOwnPropertyDescriptor(HTMLMediaElement.prototype, 'audioTracks')
// Firefox
Object.getOwnPropertyDescriptor(
HTMLVideoElement.prototype,
'mozHasAudio',
) ||
// Chrome-likes
Object.getOwnPropertyDescriptor(
HTMLMediaElement.prototype,
'webkitAudioDecodedByteCount',
) ||
// Future spec, still not supported in Nightly 63 as of 08/2018
Object.getOwnPropertyDescriptor(
HTMLMediaElement.prototype,
'audioTracks',
),
}
},
components: {
@ -54,25 +67,25 @@ const GeneralTab = {
ChoiceSetting,
IntegerSetting,
FontControl,
ProfileSettingIndicator
ProfileSettingIndicator,
},
computed: {
...SharedComputedObject(),
},
methods: {
updateFont (key, value) {
updateFont(key, value) {
this.$store.dispatch('setOption', {
name: 'theme3hacks',
value: {
...this.mergedConfig.theme3hacks,
fonts: {
...this.mergedConfig.theme3hacks.fonts,
[key]: value
}
}
[key]: value,
},
},
})
},
}
},
}
export default GeneralTab

View file

@ -8,17 +8,13 @@ import { library } from '@fortawesome/fontawesome-svg-core'
import {
faTimes,
faPlus,
faCircleNotch
faCircleNotch,
} from '@fortawesome/free-solid-svg-icons'
library.add(
faTimes,
faPlus,
faCircleNotch
)
library.add(faTimes, faPlus, faCircleNotch)
const ProfileTab = {
data () {
data() {
return {
// Whether user is locked or not
locked: this.$store.state.users.currentUser.locked,
@ -28,18 +24,18 @@ const ProfileTab = {
UserCard,
Checkbox,
BooleanSetting,
ProfileSettingIndicator
ProfileSettingIndicator,
},
computed: {
user () {
user() {
return this.$store.state.users.currentUser
},
...SharedComputedObject()
...SharedComputedObject(),
},
methods: {
updateProfile () {
updateProfile() {
const params = {
locked: this.locked
locked: this.locked,
}
this.$store.state.api.backendInteractor
@ -51,13 +47,13 @@ const ProfileTab = {
.catch((error) => {
this.displayUploadError(error)
})
}
},
},
watch: {
locked () {
locked() {
this.updateProfile()
}
}
},
},
}
export default ProfileTab

View file

@ -2,8 +2,12 @@ const Confirm = {
props: ['disabled'],
data: () => ({}),
methods: {
confirm () { this.$emit('confirm') },
cancel () { this.$emit('cancel') }
}
confirm() {
this.$emit('confirm')
},
cancel() {
this.$emit('cancel')
},
},
}
export default Confirm

View file

@ -6,113 +6,124 @@ import { mapState } from 'vuex'
const Mfa = {
data: () => ({
settings: { // current settings of MFA
settings: {
// current settings of MFA
available: false,
enabled: false,
totp: false
totp: false,
},
setupState: { // setup mfa
setupState: {
// setup mfa
state: '', // state of setup. '' -> 'getBackupCodes' -> 'setupOTP' -> 'complete'
setupOTPState: '' // state of setup otp. '' -> 'prepare' -> 'confirm' -> 'complete'
setupOTPState: '', // state of setup otp. '' -> 'prepare' -> 'confirm' -> 'complete'
},
backupCodes: {
getNewCodes: false,
inProgress: false, // progress of fetch codes
codes: []
codes: [],
},
otpSettings: { // pre-setup setting of OTP. secret key, qrcode url.
otpSettings: {
// pre-setup setting of OTP. secret key, qrcode url.
provisioning_uri: '',
key: ''
key: '',
},
currentPassword: null,
otpConfirmToken: null,
error: null,
readyInit: false
readyInit: false,
}),
components: {
'recovery-codes': RecoveryCodes,
'totp-item': TOTP,
qrcode: VueQrcode,
confirm: Confirm
confirm: Confirm,
},
computed: {
canSetupOTP () {
canSetupOTP() {
return (
(this.setupInProgress && this.backupCodesPrepared) ||
this.settings.enabled
) && !this.settings.totp && !this.setupOTPInProgress
((this.setupInProgress && this.backupCodesPrepared) ||
this.settings.enabled) &&
!this.settings.totp &&
!this.setupOTPInProgress
)
},
setupInProgress () {
return this.setupState.state !== '' && this.setupState.state !== 'complete'
setupInProgress() {
return (
this.setupState.state !== '' && this.setupState.state !== 'complete'
)
},
setupOTPInProgress () {
setupOTPInProgress() {
return this.setupState.state === 'setupOTP' && !this.completedOTP
},
prepareOTP () {
prepareOTP() {
return this.setupState.setupOTPState === 'prepare'
},
confirmOTP () {
confirmOTP() {
return this.setupState.setupOTPState === 'confirm'
},
completedOTP () {
completedOTP() {
return this.setupState.setupOTPState === 'completed'
},
backupCodesPrepared () {
backupCodesPrepared() {
return !this.backupCodes.inProgress && this.backupCodes.codes.length > 0
},
confirmNewBackupCodes () {
confirmNewBackupCodes() {
return this.backupCodes.getNewCodes
},
...mapState({
backendInteractor: (state) => state.api.backendInteractor
})
backendInteractor: (state) => state.api.backendInteractor,
}),
},
methods: {
activateOTP () {
activateOTP() {
if (!this.settings.enabled) {
this.setupState.state = 'getBackupcodes'
this.fetchBackupCodes()
}
},
fetchBackupCodes () {
fetchBackupCodes() {
this.backupCodes.inProgress = true
this.backupCodes.codes = []
return this.backendInteractor.generateMfaBackupCodes()
.then((res) => {
this.backupCodes.codes = res.codes
this.backupCodes.inProgress = false
})
return this.backendInteractor.generateMfaBackupCodes().then((res) => {
this.backupCodes.codes = res.codes
this.backupCodes.inProgress = false
})
},
getBackupCodes () { // get a new backup codes
getBackupCodes() {
// get a new backup codes
this.backupCodes.getNewCodes = true
},
confirmBackupCodes () { // confirm getting new backup codes
confirmBackupCodes() {
// confirm getting new backup codes
this.fetchBackupCodes().then(() => {
this.backupCodes.getNewCodes = false
})
},
cancelBackupCodes () { // cancel confirm form of new backup codes
cancelBackupCodes() {
// cancel confirm form of new backup codes
this.backupCodes.getNewCodes = false
},
// Setup OTP
setupOTP () { // prepare setup OTP
setupOTP() {
// prepare setup OTP
this.setupState.state = 'setupOTP'
this.setupState.setupOTPState = 'prepare'
this.backendInteractor.mfaSetupOTP()
.then((res) => {
this.otpSettings = res
this.setupState.setupOTPState = 'confirm'
})
},
doConfirmOTP () { // handler confirm enable OTP
this.error = null
this.backendInteractor.mfaConfirmOTP({
token: this.otpConfirmToken,
password: this.currentPassword
this.backendInteractor.mfaSetupOTP().then((res) => {
this.otpSettings = res
this.setupState.setupOTPState = 'confirm'
})
},
doConfirmOTP() {
// handler confirm enable OTP
this.error = null
this.backendInteractor
.mfaConfirmOTP({
token: this.otpConfirmToken,
password: this.currentPassword,
})
.then((res) => {
if (res.error) {
this.error = res.error
@ -122,14 +133,15 @@ const Mfa = {
})
},
completeSetup () {
completeSetup() {
this.setupState.setupOTPState = 'complete'
this.setupState.state = 'complete'
this.currentPassword = null
this.error = null
this.fetchSettings()
},
cancelSetup () { // cancel setup
cancelSetup() {
// cancel setup
this.setupState.setupOTPState = ''
this.setupState.state = ''
this.currentPassword = null
@ -138,18 +150,18 @@ const Mfa = {
// end Setup OTP
// fetch settings from server
async fetchSettings () {
async fetchSettings() {
const result = await this.backendInteractor.settingsMFA()
if (result.error) return
this.settings = result.settings
this.settings.available = true
return result
}
},
},
mounted () {
mounted() {
this.fetchSettings().then(() => {
this.readyInit = true
})
}
},
}
export default Mfa

View file

@ -4,14 +4,20 @@ export default {
type: Object,
default: () => ({
inProgress: false,
codes: []
})
}
codes: [],
}),
},
},
data: () => ({}),
computed: {
inProgress () { return this.backupCodes.inProgress },
ready () { return this.backupCodes.codes.length > 0 },
displayTitle () { return this.inProgress || this.ready }
}
inProgress() {
return this.backupCodes.inProgress
},
ready() {
return this.backupCodes.codes.length > 0
},
displayTitle() {
return this.inProgress || this.ready
},
},
}

View file

@ -7,34 +7,38 @@ export default {
error: false,
currentPassword: '',
deactivate: false,
inProgress: false // progress peform request to disable otp method
inProgress: false, // progress peform request to disable otp method
}),
components: {
confirm: Confirm
confirm: Confirm,
},
computed: {
isActivated () {
isActivated() {
return this.settings.totp
},
...mapState({
backendInteractor: (state) => state.api.backendInteractor
})
backendInteractor: (state) => state.api.backendInteractor,
}),
},
methods: {
doActivate () {
doActivate() {
this.$emit('activate')
},
cancelDeactivate () { this.deactivate = false },
doDeactivate () {
cancelDeactivate() {
this.deactivate = false
},
doDeactivate() {
this.error = null
this.deactivate = true
},
confirmDeactivate () { // confirm deactivate TOTP method
confirmDeactivate() {
// confirm deactivate TOTP method
this.error = null
this.inProgress = true
this.backendInteractor.mfaDisableOTP({
password: this.currentPassword
})
this.backendInteractor
.mfaDisableOTP({
password: this.currentPassword,
})
.then((res) => {
this.inProgress = false
if (res.error) {
@ -44,6 +48,6 @@ export default {
this.deactivate = false
this.$emit('deactivate')
})
}
}
},
},
}

View file

@ -5,7 +5,7 @@ import localeService from 'src/services/locale/locale.service.js'
import { useOAuthTokensStore } from 'src/stores/oauth_tokens'
const SecurityTab = {
data () {
data() {
return {
newEmail: '',
changeEmailError: false,
@ -25,41 +25,44 @@ const SecurityTab = {
listAliasesError: false,
addAliasTarget: '',
addedAlias: false,
addAliasError: false
addAliasError: false,
}
},
created () {
created() {
useOAuthTokensStore().fetchTokens()
this.fetchAliases()
},
components: {
ProgressButton,
Mfa,
Checkbox
Checkbox,
},
computed: {
user () {
user() {
return this.$store.state.users.currentUser
},
pleromaExtensionsAvailable () {
pleromaExtensionsAvailable() {
return this.$store.state.instance.pleromaExtensionsAvailable
},
oauthTokens () {
return useOAuthTokensStore().tokens.map(oauthToken => {
oauthTokens() {
return useOAuthTokensStore().tokens.map((oauthToken) => {
return {
id: oauthToken.id,
appName: oauthToken.app_name,
validUntil: new Date(oauthToken.valid_until).toLocaleDateString(localeService.internalToBrowserLocale(this.$i18n.locale))
validUntil: new Date(oauthToken.valid_until).toLocaleDateString(
localeService.internalToBrowserLocale(this.$i18n.locale),
),
}
})
}
},
},
methods: {
confirmDelete () {
confirmDelete() {
this.deletingAccount = true
},
deleteAccount () {
this.$store.state.api.backendInteractor.deleteAccount({ password: this.deleteAccountConfirmPasswordInput })
deleteAccount() {
this.$store.state.api.backendInteractor
.deleteAccount({ password: this.deleteAccountConfirmPasswordInput })
.then((res) => {
if (res.status === 'success') {
this.$store.dispatch('logout')
@ -69,13 +72,14 @@ const SecurityTab = {
}
})
},
changePassword () {
changePassword() {
const params = {
password: this.changePasswordInputs[0],
newPassword: this.changePasswordInputs[1],
newPasswordConfirmation: this.changePasswordInputs[2]
newPasswordConfirmation: this.changePasswordInputs[2],
}
this.$store.state.api.backendInteractor.changePassword(params)
this.$store.state.api.backendInteractor
.changePassword(params)
.then((res) => {
if (res.status === 'success') {
this.changedPassword = true
@ -87,12 +91,13 @@ const SecurityTab = {
}
})
},
changeEmail () {
changeEmail() {
const params = {
email: this.newEmail,
password: this.changeEmailPassword
password: this.changeEmailPassword,
}
this.$store.state.api.backendInteractor.changeEmail(params)
this.$store.state.api.backendInteractor
.changeEmail(params)
.then((res) => {
if (res.status === 'success') {
this.changedEmail = true
@ -103,12 +108,13 @@ const SecurityTab = {
}
})
},
moveAccount () {
moveAccount() {
const params = {
targetAccount: this.moveAccountTarget,
password: this.moveAccountPassword
password: this.moveAccountPassword,
}
this.$store.state.api.backendInteractor.moveAccount(params)
this.$store.state.api.backendInteractor
.moveAccount(params)
.then((res) => {
if (res.status === 'success') {
this.movedAccount = true
@ -119,12 +125,14 @@ const SecurityTab = {
}
})
},
removeAlias (alias) {
this.$store.state.api.backendInteractor.deleteAlias({ alias })
removeAlias(alias) {
this.$store.state.api.backendInteractor
.deleteAlias({ alias })
.then(() => this.fetchAliases())
},
addAlias () {
this.$store.state.api.backendInteractor.addAlias({ alias: this.addAliasTarget })
addAlias() {
this.$store.state.api.backendInteractor
.addAlias({ alias: this.addAliasTarget })
.then(() => {
this.addedAlias = true
this.addAliasError = false
@ -136,8 +144,9 @@ const SecurityTab = {
})
.then(() => this.fetchAliases())
},
fetchAliases () {
this.$store.state.api.backendInteractor.listAliases()
fetchAliases() {
this.$store.state.api.backendInteractor
.listAliases()
.then((res) => {
this.aliases = res.aliases
this.listAliasesError = false
@ -146,16 +155,16 @@ const SecurityTab = {
this.listAliasesError = error.error
})
},
logout () {
logout() {
this.$store.dispatch('logout')
this.$router.replace('/')
},
revokeToken (id) {
revokeToken(id) {
if (window.confirm(`${this.$i18n.t('settings.revoke_token')}?`)) {
useOAuthTokensStore().revokeToken(id)
}
}
}
},
},
}
export default SecurityTab

View file

@ -1,4 +1,11 @@
import { ref, reactive, computed, watch, provide, getCurrentInstance } from 'vue'
import {
ref,
reactive,
computed,
watch,
provide,
getCurrentInstance,
} from 'vue'
import { useInterfaceStore } from 'src/stores/interface'
import { get, set, unset, throttle } from 'lodash'
@ -19,19 +26,28 @@ import Preview from '../old_theme_tab/theme_preview.vue'
import VirtualDirectivesTab from './virtual_directives_tab.vue'
import { createStyleSheet, adoptStyleSheets } from 'src/services/style_setter/style_setter.js'
import { init, findColor } from 'src/services/theme_data/theme_data_3.service.js'
import {
createStyleSheet,
adoptStyleSheets,
} from 'src/services/style_setter/style_setter.js'
import {
init,
findColor,
} from 'src/services/theme_data/theme_data_3.service.js'
import { getCssRules } from 'src/services/theme_data/css_utils.js'
import { serialize } from 'src/services/theme_data/iss_serializer.js'
import { deserializeShadow, deserialize } from 'src/services/theme_data/iss_deserializer.js'
import {
deserializeShadow,
deserialize,
} from 'src/services/theme_data/iss_deserializer.js'
import {
rgb2hex,
hex2rgb,
getContrastRatio
getContrastRatio,
} from 'src/services/color_convert/color_convert.js'
import {
newImporter,
newExporter
newExporter,
} from 'src/services/export_import/export_import.js'
import { library } from '@fortawesome/fontawesome-svg-core'
@ -40,7 +56,7 @@ import {
faFolderOpen,
faFile,
faArrowsRotate,
faCheck
faCheck,
} from '@fortawesome/free-solid-svg-icons'
// helper for debugging
@ -48,15 +64,10 @@ import {
const toValue = (x) => JSON.parse(JSON.stringify(x === undefined ? 'null' : x))
// helper to make states comparable
const normalizeStates = (states) => ['normal', ...(states?.filter(x => x !== 'normal') || [])].join(':')
const normalizeStates = (states) =>
['normal', ...(states?.filter((x) => x !== 'normal') || [])].join(':')
library.add(
faFile,
faFloppyDisk,
faFolderOpen,
faArrowsRotate,
faCheck
)
library.add(faFile, faFloppyDisk, faFolderOpen, faArrowsRotate, faCheck)
export default {
components: {
@ -74,18 +85,22 @@ export default {
RoundnessInput,
ContrastRatio,
Preview,
VirtualDirectivesTab
VirtualDirectivesTab,
},
setup () {
setup() {
const exports = {}
const interfaceStore = useInterfaceStore()
// All rules that are made by editor
const allEditedRules = ref(interfaceStore.styleDataUsed || {})
const styleDataUsed = computed(() => interfaceStore.styleDataUsed)
watch([styleDataUsed], () => {
onImport(interfaceStore.styleDataUsed)
}, { once: true })
watch(
[styleDataUsed],
() => {
onImport(interfaceStore.styleDataUsed)
},
{ once: true },
)
exports.isActive = computed(() => {
const tabSwitcher = getCurrentInstance().parent.ctx
@ -105,7 +120,7 @@ export default {
` author: ${exports.author.value};`,
` license: ${exports.license.value};`,
` website: ${exports.website.value};`,
'}'
'}',
].join('\n')
})
@ -115,8 +130,8 @@ export default {
name: exports.name.value,
author: exports.author.value,
license: exports.license.value,
website: exports.website.value
}
website: exports.website.value,
},
}))
// ## Palette stuff
@ -131,7 +146,7 @@ export default {
cRed: '#FF0000',
cBlue: '#0095ff',
cGreen: '#0fa00f',
cOrange: '#ffa500'
cOrange: '#ffa500',
},
{
name: 'light',
@ -144,8 +159,8 @@ export default {
cRed: '#d31014',
cGreen: '#0fa00f',
cOrange: '#ffa500',
border: '#d8e6f9'
}
border: '#d8e6f9',
},
])
exports.palettes = palettes
@ -163,12 +178,12 @@ export default {
const selectedPaletteId = ref(0)
const selectedPalette = computed({
get () {
get() {
return palettes[selectedPaletteId.value]
},
set (newPalette) {
set(newPalette) {
palettes[selectedPaletteId.value] = newPalette
}
},
})
exports.selectedPaletteId = selectedPaletteId
exports.selectedPalette = selectedPalette
@ -186,49 +201,50 @@ export default {
cRed: '#FF0000',
cBlue: '#0095ff',
cGreen: '#0fa00f',
cOrange: '#ffa500'
cOrange: '#ffa500',
})
// Raw format
const palettesRule = computed(() => {
return palettes.map(palette => {
return palettes.map((palette) => {
const { name, ...rest } = palette
return {
component: '@palette',
variant: name,
directives: Object
.entries(rest)
directives: Object.entries(rest)
.filter(([k, v]) => v && k)
.reduce((acc, [k, v]) => ({ ...acc, [k]: v }), {})
.reduce((acc, [k, v]) => ({ ...acc, [k]: v }), {}),
}
})
})
// Text format
const palettesOut = computed(() => {
return palettes.map(({ name, ...palette }) => {
const entries = Object
.entries(palette)
.filter(([k, v]) => v && k)
.map(([slot, data]) => ` ${slot}: ${data};`)
.join('\n')
return palettes
.map(({ name, ...palette }) => {
const entries = Object.entries(palette)
.filter(([k, v]) => v && k)
.map(([slot, data]) => ` ${slot}: ${data};`)
.join('\n')
return `@palette.${name} {\n${entries}\n}`
}).join('\n\n')
return `@palette.${name} {\n${entries}\n}`
})
.join('\n\n')
})
// ## Components stuff
// Getting existing components
const componentsContext = import.meta.glob(
['/src/**/*.style.js', '/src/**/*.style.json'],
{ eager: true }
{ eager: true },
)
const componentKeysAll = Object.keys(componentsContext)
const componentsMap = new Map(
componentKeysAll
.map(
key => [key, componentsContext[key].default]
).filter(([, component]) => !component.virtual && !component.notEditable)
.map((key) => [key, componentsContext[key].default])
.filter(
([, component]) => !component.virtual && !component.notEditable,
),
)
exports.componentsMap = componentsMap
const componentKeys = [...componentsMap.keys()]
@ -238,16 +254,24 @@ export default {
const selectedComponentKey = ref(componentsMap.keys().next().value)
exports.selectedComponentKey = selectedComponentKey
const selectedComponent = computed(() => componentsMap.get(selectedComponentKey.value))
const selectedComponent = computed(() =>
componentsMap.get(selectedComponentKey.value),
)
const selectedComponentName = computed(() => selectedComponent.value.name)
// Selection basis
exports.selectedComponentVariants = computed(() => {
return Object.keys({ normal: null, ...(selectedComponent.value.variants || {}) })
return Object.keys({
normal: null,
...(selectedComponent.value.variants || {}),
})
})
exports.selectedComponentStates = computed(() => {
const all = Object.keys({ normal: null, ...(selectedComponent.value.states || {}) })
return all.filter(x => x !== 'normal')
const all = Object.keys({
normal: null,
...(selectedComponent.value.states || {}),
})
return all.filter((x) => x !== 'normal')
})
// selection
@ -269,51 +293,49 @@ export default {
selectedState.clear()
}
watch(
selectedComponentName,
updateSelectedComponent
)
watch(selectedComponentName, updateSelectedComponent)
// ### Rules stuff aka meat and potatoes
// The native structure of separate rules and the child -> parent
// relation isn't very convenient for editor, we replace the array
// and child -> parent structure with map and parent -> child structure
const rulesToEditorFriendly = (rules, root = {}) => rules.reduce((acc, rule) => {
const { parent: rParent, component: rComponent } = rule
const parent = rParent ?? rule
const hasChildren = !!rParent
const child = hasChildren ? rule : null
const rulesToEditorFriendly = (rules, root = {}) =>
rules.reduce((acc, rule) => {
const { parent: rParent, component: rComponent } = rule
const parent = rParent ?? rule
const hasChildren = !!rParent
const child = hasChildren ? rule : null
const {
component: pComponent,
variant: pVariant = 'normal',
state: pState = [] // no relation to Intel CPUs whatsoever
} = parent
const pPath = `${hasChildren ? pComponent : rComponent}.${pVariant}.${normalizeStates(pState)}`
let output = get(acc, pPath)
if (!output) {
set(acc, pPath, {})
output = get(acc, pPath)
}
if (hasChildren) {
output._children = output._children ?? {}
const {
component: cComponent,
variant: cVariant = 'normal',
state: cState = [],
directives
} = child
component: pComponent,
variant: pVariant = 'normal',
state: pState = [], // no relation to Intel CPUs whatsoever
} = parent
const cPath = `${cComponent}.${cVariant}.${normalizeStates(cState)}`
set(output._children, cPath, { directives })
} else {
output.directives = parent.directives
}
return acc
}, root)
const pPath = `${hasChildren ? pComponent : rComponent}.${pVariant}.${normalizeStates(pState)}`
let output = get(acc, pPath)
if (!output) {
set(acc, pPath, {})
output = get(acc, pPath)
}
if (hasChildren) {
output._children = output._children ?? {}
const {
component: cComponent,
variant: cVariant = 'normal',
state: cState = [],
directives,
} = child
const cPath = `${cComponent}.${cVariant}.${normalizeStates(cState)}`
set(output._children, cPath, { directives })
} else {
output.directives = parent.directives
}
return acc
}, root)
const editorFriendlyFallbackStructure = computed(() => {
const root = {}
@ -323,7 +345,7 @@ export default {
const { defaultRules, name } = componentValue
rulesToEditorFriendly(
defaultRules.map((rule) => ({ ...rule, component: name })),
root
root,
)
})
@ -333,79 +355,111 @@ export default {
// Checking whether component can support some "directives" which
// are actually virtual subcomponents, i.e. Text, Link etc
exports.componentHas = (subComponent) => {
return !!selectedComponent.value.validInnerComponents?.find(x => x === subComponent)
return !!selectedComponent.value.validInnerComponents?.find(
(x) => x === subComponent,
)
}
// Path for lodash's get and set
const getPath = (component, directive) => {
const pathSuffix = component ? `._children.${component}.normal.normal` : ''
const pathSuffix = component
? `._children.${component}.normal.normal`
: ''
const path = `${selectedComponentName.value}.${selectedVariant.value}.${normalizeStates([...selectedState])}${pathSuffix}.directives.${directive}`
return path
}
// Templates for directives
const isElementPresent = (component, directive, defaultValue = '') => computed({
get () {
return get(allEditedRules.value, getPath(component, directive)) != null
},
set (value) {
if (value) {
const fallback = get(
editorFriendlyFallbackStructure.value,
getPath(component, directive)
const isElementPresent = (component, directive, defaultValue = '') =>
computed({
get() {
return (
get(allEditedRules.value, getPath(component, directive)) != null
)
set(allEditedRules.value, getPath(component, directive), fallback ?? defaultValue)
} else {
unset(allEditedRules.value, getPath(component, directive))
}
exports.updateOverallPreview()
}
})
},
set(value) {
if (value) {
const fallback = get(
editorFriendlyFallbackStructure.value,
getPath(component, directive),
)
set(
allEditedRules.value,
getPath(component, directive),
fallback ?? defaultValue,
)
} else {
unset(allEditedRules.value, getPath(component, directive))
}
exports.updateOverallPreview()
},
})
const getEditedElement = (component, directive, postProcess = x => x) => computed({
get () {
let usedRule
const fallback = editorFriendlyFallbackStructure.value
const real = allEditedRules.value
const path = getPath(component, directive)
const getEditedElement = (component, directive, postProcess = (x) => x) =>
computed({
get() {
let usedRule
const fallback = editorFriendlyFallbackStructure.value
const real = allEditedRules.value
const path = getPath(component, directive)
usedRule = get(real, path) // get real
if (usedRule === '') {
return usedRule
}
if (!usedRule) {
usedRule = get(fallback, path)
}
usedRule = get(real, path) // get real
if (usedRule === '') {
return usedRule
}
if (!usedRule) {
usedRule = get(fallback, path)
}
return postProcess(usedRule)
},
set (value) {
if (value != null) {
set(allEditedRules.value, getPath(component, directive), value)
} else {
unset(allEditedRules.value, getPath(component, directive))
}
exports.updateOverallPreview()
}
})
return postProcess(usedRule)
},
set(value) {
if (value != null) {
set(allEditedRules.value, getPath(component, directive), value)
} else {
unset(allEditedRules.value, getPath(component, directive))
}
exports.updateOverallPreview()
},
})
// All the editable stuff for the component
exports.editedBackgroundColor = getEditedElement(null, 'background')
exports.isBackgroundColorPresent = isElementPresent(null, 'background', '#FFFFFF')
exports.isBackgroundColorPresent = isElementPresent(
null,
'background',
'#FFFFFF',
)
exports.editedOpacity = getEditedElement(null, 'opacity')
exports.isOpacityPresent = isElementPresent(null, 'opacity', 1)
exports.editedRoundness = getEditedElement(null, 'roundness')
exports.isRoundnessPresent = isElementPresent(null, 'roundness', '0')
exports.editedTextColor = getEditedElement('Text', 'textColor')
exports.isTextColorPresent = isElementPresent('Text', 'textColor', '#000000')
exports.isTextColorPresent = isElementPresent(
'Text',
'textColor',
'#000000',
)
exports.editedTextAuto = getEditedElement('Text', 'textAuto')
exports.isTextAutoPresent = isElementPresent('Text', 'textAuto', '#000000')
exports.editedLinkColor = getEditedElement('Link', 'textColor')
exports.isLinkColorPresent = isElementPresent('Link', 'textColor', '#000080')
exports.isLinkColorPresent = isElementPresent(
'Link',
'textColor',
'#000080',
)
exports.editedIconColor = getEditedElement('Icon', 'textColor')
exports.isIconColorPresent = isElementPresent('Icon', 'textColor', '#909090')
exports.isIconColorPresent = isElementPresent(
'Icon',
'textColor',
'#909090',
)
exports.editedBorderColor = getEditedElement('Border', 'textColor')
exports.isBorderColorPresent = isElementPresent('Border', 'textColor', '#909090')
exports.isBorderColorPresent = isElementPresent(
'Border',
'textColor',
'#909090',
)
const getContrast = (bg, text) => {
try {
@ -422,7 +476,7 @@ export default {
aaa: ratio >= 7,
// same but for 18pt+ texts
laa: ratio >= 3,
laaa: ratio >= 4.5
laaa: ratio >= 4.5,
}
} catch (e) {
console.warn('Failure computing contrast', e)
@ -431,7 +485,7 @@ export default {
}
const normalizeShadows = (shadows) => {
return shadows?.map(shadow => {
return shadows?.map((shadow) => {
if (typeof shadow === 'object') {
return shadow
}
@ -455,7 +509,8 @@ export default {
const editedSubShadowId = ref(null)
exports.editedSubShadowId = editedSubShadowId
const editedSubShadow = computed(() => {
if (editedShadow.value == null || editedSubShadowId.value == null) return null
if (editedShadow.value == null || editedSubShadowId.value == null)
return null
return editedShadow.value[editedSubShadowId.value]
})
exports.editedSubShadow = editedSubShadow
@ -473,7 +528,7 @@ export default {
newEditedShadow[editedSubShadowId.value] = {
...newEditedShadow[editedSubShadowId.value],
[axis]: value
[axis]: value,
}
editedShadow.value = newEditedShadow
@ -513,12 +568,12 @@ export default {
component,
variant,
state,
directives: stateData.directives || {}
directives: stateData.directives || {},
}
if (parent) {
result.parent = {
component: parent
component: parent,
}
}
@ -526,13 +581,15 @@ export default {
// Currently we only support single depth for simplicity's sake
if (!parent) {
Object.entries(stateData._children || {}).forEach(([cName, child]) => convert(cName, child, component))
Object.entries(stateData._children || {}).forEach(
([cName, child]) => convert(cName, child, component),
)
}
})
})
}
[...componentsMap.values()].forEach(({ name }) => {
;[...componentsMap.values()].forEach(({ name }) => {
convert(name, allEditedRules.value[name])
})
@ -540,21 +597,20 @@ export default {
})
const allCustomVirtualDirectives = [...componentsMap.values()]
.map(c => {
return c
.defaultRules
.filter(c => c.component === 'Root')
.map(x => Object.entries(x.directives))
.map((c) => {
return c.defaultRules
.filter((c) => c.component === 'Root')
.map((x) => Object.entries(x.directives))
.flat()
})
.filter(x => x)
.filter((x) => x)
.flat()
.map(([name, value]) => {
const [valType, valVal] = value.split('|')
return {
name: name.substring(2),
valType: valType?.trim(),
value: valVal?.trim()
value: valVal?.trim(),
}
})
@ -568,8 +624,11 @@ export default {
const virtualDirectivesRule = computed(() => ({
component: 'Root',
directives: Object.fromEntries(
virtualDirectives.value.map(vd => [`--${vd.name}`, `${vd.valType} | ${vd.value}`])
)
virtualDirectives.value.map((vd) => [
`--${vd.name}`,
`${vd.valType} | ${vd.value}`,
]),
),
}))
// Text format
@ -577,16 +636,19 @@ export default {
return [
'Root {',
...virtualDirectives.value
.filter(vd => vd.name && vd.valType && vd.value)
.map(vd => ` --${vd.name}: ${vd.valType} | ${vd.value};`),
'}'
.filter((vd) => vd.name && vd.valType && vd.value)
.map((vd) => ` --${vd.name}: ${vd.valType} | ${vd.value};`),
'}',
].join('\n')
})
exports.computeColor = (color) => {
let computedColor
try {
computedColor = findColor(color, { dynamicVars: dynamicVars.value, staticVars: staticVars.value })
computedColor = findColor(color, {
dynamicVars: dynamicVars.value,
staticVars: staticVars.value,
})
if (computedColor) {
return rgb2hex(computedColor)
}
@ -600,7 +662,7 @@ export default {
exports.contrast = computed(() => {
return getContrast(
exports.computeColor(previewColors.value.background),
exports.computeColor(previewColors.value.text)
exports.computeColor(previewColors.value.text),
)
})
@ -609,30 +671,38 @@ export default {
filename: () => exports.name.value ?? 'pleroma_theme',
mime: 'text/plain',
extension: 'iss',
getExportedObject: () => exportStyleData.value
getExportedObject: () => exportStyleData.value,
})
const onImport = parsed => {
const editorComponents = parsed.filter(x => x.component.startsWith('@'))
const rootComponent = parsed.find(x => x.component === 'Root')
const rules = parsed.filter(x => !x.component.startsWith('@') && x.component !== 'Root')
const metaIn = editorComponents.find(x => x.component === '@meta').directives
const palettesIn = editorComponents.filter(x => x.component === '@palette')
const onImport = (parsed) => {
const editorComponents = parsed.filter((x) => x.component.startsWith('@'))
const rootComponent = parsed.find((x) => x.component === 'Root')
const rules = parsed.filter(
(x) => !x.component.startsWith('@') && x.component !== 'Root',
)
const metaIn = editorComponents.find(
(x) => x.component === '@meta',
).directives
const palettesIn = editorComponents.filter(
(x) => x.component === '@palette',
)
exports.name.value = metaIn.name
exports.license.value = metaIn.license
exports.author.value = metaIn.author
exports.website.value = metaIn.website
const newVirtualDirectives = Object
.entries(rootComponent.directives)
.map(([name, value]) => {
const [valType, valVal] = value.split('|').map(x => x.trim())
const newVirtualDirectives = Object.entries(rootComponent.directives).map(
([name, value]) => {
const [valType, valVal] = value.split('|').map((x) => x.trim())
return { name: name.substring(2), valType, value: valVal }
})
},
)
virtualDirectives.value = newVirtualDirectives
onPalettesUpdate(palettesIn.map(x => ({ name: x.variant, ...x.directives })))
onPalettesUpdate(
palettesIn.map((x) => ({ name: x.variant, ...x.directives })),
)
allEditedRules.value = rulesToEditorFriendly(rules)
@ -641,12 +711,17 @@ export default {
const styleImporter = newImporter({
accept: '.iss',
parser (string) { return deserialize(string) },
onImportFailure (result) {
console.error('Failure importing style:', result)
useInterfaceStore().pushGlobalNotice({ messageKey: 'settings.invalid_theme_imported', level: 'error' })
parser(string) {
return deserialize(string)
},
onImport
onImportFailure(result) {
console.error('Failure importing style:', result)
useInterfaceStore().pushGlobalNotice({
messageKey: 'settings.invalid_theme_imported',
level: 'error',
})
},
onImport,
})
// Raw format
@ -654,7 +729,7 @@ export default {
metaRule.value,
...palettesRule.value,
virtualDirectivesRule.value,
...editorFriendlyToOriginal.value
...editorFriendlyToOriginal.value,
])
// Text format
@ -663,7 +738,7 @@ export default {
metaOut.value,
palettesOut.value,
virtualDirectivesOut.value,
serialize(editorFriendlyToOriginal.value)
serialize(editorFriendlyToOriginal.value),
].join('\n\n')
})
@ -689,7 +764,9 @@ export default {
watch([overallPreviewRules], () => {
let css = null
try {
css = getCssRules(overallPreviewRules.value).map(r => r.replace('html', '&'))
css = getCssRules(overallPreviewRules.value).map((r) =>
r.replace('html', '&'),
)
} catch (e) {
console.error(e)
return
@ -698,11 +775,9 @@ export default {
const sheet = createStyleSheet('style-tab-overall-preview', 90)
sheet.clear()
sheet.addRule([
'#edited-style-preview {\n',
css.join('\n'),
'\n}'
].join(''))
sheet.addRule(
['#edited-style-preview {\n', css.join('\n'), '\n}'].join(''),
)
sheet.ready = true
adoptStyleSheets()
})
@ -715,15 +790,14 @@ export default {
{
component: 'Root',
directives: Object.fromEntries(
Object
.entries(selectedPalette.value)
Object.entries(selectedPalette.value)
.filter(([k, v]) => k && v && k !== 'name')
.map(([k, v]) => [`--${k}`, `color | ${v}`])
)
}
.map(([k, v]) => [`--${k}`, `color | ${v}`]),
),
},
],
ultimateBackgroundColor: '#000000',
debug: true
debug: true,
}).eager
} catch (e) {
console.error('Could not compile preview theme', e)
@ -733,30 +807,32 @@ export default {
//
// Apart from "hover" we can't really show how component looks like in
// certain states, so we have to fake them.
const simulatePseudoSelectors = (css, prefix) => css
.replace(prefix, '.preview-block')
.replace(':active', '.preview-active')
.replace(':hover', '.preview-hover')
.replace(':active', '.preview-active')
.replace(':focus', '.preview-focus')
.replace(':focus-within', '.preview-focus-within')
.replace(':disabled', '.preview-disabled')
const simulatePseudoSelectors = (css, prefix) =>
css
.replace(prefix, '.preview-block')
.replace(':active', '.preview-active')
.replace(':hover', '.preview-hover')
.replace(':active', '.preview-active')
.replace(':focus', '.preview-focus')
.replace(':focus-within', '.preview-focus-within')
.replace(':disabled', '.preview-disabled')
const previewRules = computed(() => {
const filtered = overallPreviewRules.value.filter(r => {
const filtered = overallPreviewRules.value.filter((r) => {
const componentMatch = r.component === selectedComponentName.value
const parentComponentMatch = r.parent?.component === selectedComponentName.value
const parentComponentMatch =
r.parent?.component === selectedComponentName.value
if (!componentMatch && !parentComponentMatch) return false
const rule = parentComponentMatch ? r.parent : r
if (rule.component !== selectedComponentName.value) return false
if (rule.variant !== selectedVariant.value) return false
const ruleState = new Set(rule.state.filter(x => x !== 'normal'))
const differenceA = [...ruleState].filter(x => !selectedState.has(x))
const differenceB = [...selectedState].filter(x => !ruleState.has(x))
return (differenceA.length + differenceB.length) === 0
const ruleState = new Set(rule.state.filter((x) => x !== 'normal'))
const differenceA = [...ruleState].filter((x) => !selectedState.has(x))
const differenceB = [...selectedState].filter((x) => !ruleState.has(x))
return differenceA.length + differenceB.length === 0
})
const sorted = [...filtered]
.filter(x => x.component === selectedComponentName.value)
.filter((x) => x.component === selectedComponentName.value)
.sort((a, b) => {
const aSelectorLength = a.selector.split(/ /g).length
const bSelectorLength = b.selector.split(/ /g).length
@ -765,27 +841,32 @@ export default {
const prefix = sorted[0].selector
return filtered.filter(x => x.selector.startsWith(prefix))
return filtered.filter((x) => x.selector.startsWith(prefix))
})
exports.previewClass = computed(() => {
const selectors = []
if (!!selectedComponent.value.variants?.normal || selectedVariant.value !== 'normal') {
if (
!!selectedComponent.value.variants?.normal ||
selectedVariant.value !== 'normal'
) {
selectors.push(selectedComponent.value.variants[selectedVariant.value])
}
if (selectedState.size > 0) {
selectedState.forEach(state => {
selectedState.forEach((state) => {
const original = selectedComponent.value.states[state]
selectors.push(simulatePseudoSelectors(original))
})
}
return selectors.map(x => x.substring(1)).join('')
return selectors.map((x) => x.substring(1)).join('')
})
exports.previewCss = computed(() => {
try {
const prefix = previewRules.value[0].selector
const scoped = getCssRules(previewRules.value).map(x => simulatePseudoSelectors(x, prefix))
const scoped = getCssRules(previewRules.value).map((x) =>
simulatePseudoSelectors(x, prefix),
)
return scoped.join('\n')
} catch (e) {
console.error('Invalid ruleset', e)
@ -798,7 +879,7 @@ export default {
})
const staticVars = computed(() => {
const rootComponent = overallPreviewRules.value.find(r => {
const rootComponent = overallPreviewRules.value.find((r) => {
return r.component === 'Root'
})
const rootDirectivesEntries = Object.entries(rootComponent.directives)
@ -807,7 +888,10 @@ export default {
.filter(([k, v]) => k.startsWith('--') && v.startsWith('color | '))
.map(([k, v]) => [k.substring(2), v.substring('color | '.length)])
.forEach(([k, v]) => {
directives[k] = findColor(v, { dynamicVars: {}, staticVars: directives })
directives[k] = findColor(v, {
dynamicVars: {},
staticVars: directives,
})
})
return directives
})
@ -816,13 +900,18 @@ export default {
const previewColors = computed(() => {
const stacked = dynamicVars.value.stacked
const background = typeof stacked === 'string' ? stacked : rgb2hex(stacked)
const background =
typeof stacked === 'string' ? stacked : rgb2hex(stacked)
return {
text: previewRules.value.find(r => r.component === 'Text')?.virtualDirectives['--text'],
link: previewRules.value.find(r => r.component === 'Link')?.virtualDirectives['--link'],
border: previewRules.value.find(r => r.component === 'Border')?.virtualDirectives['--border'],
icon: previewRules.value.find(r => r.component === 'Icon')?.virtualDirectives['--icon'],
background
text: previewRules.value.find((r) => r.component === 'Text')
?.virtualDirectives['--text'],
link: previewRules.value.find((r) => r.component === 'Link')
?.virtualDirectives['--link'],
border: previewRules.value.find((r) => r.component === 'Border')
?.virtualDirectives['--border'],
icon: previewRules.value.find((r) => r.component === 'Icon')
?.virtualDirectives['--icon'],
background,
}
})
exports.previewColors = previewColors
@ -836,11 +925,11 @@ export default {
palettes,
selectedPalette,
selectedState,
selectedVariant
selectedVariant,
],
updateOverallPreview
updateOverallPreview,
)
return exports
}
},
}

View file

@ -16,11 +16,11 @@ export default {
Select,
SelectMotion,
ShadowControl,
ColorInput
ColorInput,
},
props: ['modelValue'],
emits: ['update:modelValue'],
setup (props, context) {
setup(props, context) {
const exports = {}
const emit = context.emit
@ -32,23 +32,23 @@ export default {
exports.selectedVirtualDirectiveId = selectedVirtualDirectiveId
const selectedVirtualDirective = computed({
get () {
get() {
return props.modelValue[selectedVirtualDirectiveId.value]
},
set (value) {
set(value) {
const newVD = [...props.modelValue]
newVD[selectedVirtualDirectiveId.value] = value
emit('update:modelValue', newVD)
}
},
})
exports.selectedVirtualDirective = selectedVirtualDirective
exports.selectedVirtualDirectiveValType = computed({
get () {
get() {
return props.modelValue[selectedVirtualDirectiveId.value].valType
},
set (value) {
set(value) {
const newValType = value
let newValue
switch (value) {
@ -65,9 +65,9 @@ export default {
props.modelValue[selectedVirtualDirectiveId.value] = {
name: newName,
value: newValue,
valType: newValType
valType: newValType,
}
}
},
})
const draftVirtualDirectiveValid = ref(true)
@ -83,7 +83,9 @@ export default {
if (Array.isArray(directive.value)) {
draftVirtualDirective.value = normalizeShadows(directive.value)
} else {
const splitShadow = directive.value.split(/,/g).map(x => x.trim())
const splitShadow = directive.value
.split(/,/g)
.map((x) => x.trim())
draftVirtualDirective.value = normalizeShadows(splitShadow)
}
break
@ -96,7 +98,7 @@ export default {
break
}
},
{ immediate: true }
{ immediate: true },
)
watch(
@ -106,11 +108,12 @@ export default {
switch (selectedVirtualDirective.value.valType) {
case 'shadow': {
props.modelValue[selectedVirtualDirectiveId.value].value =
directive.map(x => serializeShadow(x)).join(', ')
directive.map((x) => serializeShadow(x)).join(', ')
break
}
default:
props.modelValue[selectedVirtualDirectiveId.value].value = directive
props.modelValue[selectedVirtualDirectiveId.value].value =
directive
}
draftVirtualDirectiveValid.value = true
} catch (e) {
@ -118,15 +121,15 @@ export default {
draftVirtualDirectiveValid.value = false
}
},
{ immediate: true }
{ immediate: true },
)
exports.getNewVirtualDirective = () => ({
name: 'newDirective',
valType: 'generic',
value: 'foobar'
value: 'foobar',
})
return exports
}
},
}