Migrate Settings and SettingsGroup
This commit is contained in:
parent
58eec54952
commit
b597dc4a71
|
@ -18,6 +18,7 @@
|
|||
"postinstall": "yarn run fix-fomantic-css"
|
||||
},
|
||||
"dependencies": {
|
||||
"@vue/runtime-core": "^3.2.37",
|
||||
"@vueuse/core": "8.7.5",
|
||||
"@vueuse/integrations": "8.7.5",
|
||||
"axios": "0.27.2",
|
||||
|
|
|
@ -1,3 +1,99 @@
|
|||
<script setup lang="ts">
|
||||
import type { BackendError, SettingsGroup, SettingsDataEntry, FunctionRef, Form } from '~/types'
|
||||
import axios from 'axios'
|
||||
import SignupFormBuilder from '~/components/admin/SignupFormBuilder.vue'
|
||||
import useFormData from '~/composables/useFormData'
|
||||
import { ref, computed, reactive } from 'vue'
|
||||
import { useStore } from '~/store'
|
||||
import useLogger from '~/composables/useLogger'
|
||||
|
||||
interface Props {
|
||||
group: SettingsGroup
|
||||
settingsData: SettingsDataEntry[]
|
||||
}
|
||||
|
||||
const props = defineProps<Props>()
|
||||
|
||||
const values = reactive({} as Record<string, unknown | Form | string>)
|
||||
const result = ref<boolean | null>(null)
|
||||
const errors = ref([] as string[])
|
||||
|
||||
const fileRefs = reactive({} as Record<string, HTMLInputElement>)
|
||||
const setFileRef = (identifier: string) => (el: FunctionRef) => {
|
||||
console.log(el)
|
||||
fileRefs[identifier] = el as HTMLInputElement
|
||||
}
|
||||
|
||||
const logger = useLogger()
|
||||
const store = useStore()
|
||||
|
||||
const settings = computed(() => {
|
||||
const byIdentifier = props.settingsData.reduce((acc, entry) => {
|
||||
acc[entry.identifier] = entry
|
||||
return acc
|
||||
}, {} as Record<string, SettingsDataEntry>)
|
||||
|
||||
return props.group.settings.map(entry => {
|
||||
return { ...byIdentifier[entry.name], fieldType: entry.fieldType, fieldParams: entry.fieldParams || {} }
|
||||
})
|
||||
})
|
||||
|
||||
const fileSettings = computed(() => settings.value.filter(setting => setting.field.widget.class === 'ImageWidget'))
|
||||
|
||||
for (const setting of settings.value) {
|
||||
values[setting.identifier] = setting.value
|
||||
}
|
||||
|
||||
const isLoading = ref(false)
|
||||
const save = async () => {
|
||||
errors.value = []
|
||||
result.value = null
|
||||
|
||||
let postData: unknown = values
|
||||
let contentType = 'application/json'
|
||||
|
||||
if (fileSettings.value.length > 0) {
|
||||
const fileSettingsIDs = fileSettings.value.map((setting) => setting.identifier)
|
||||
const data = settings.value.reduce((data, setting) => {
|
||||
if (fileSettingsIDs.includes(setting.identifier)) {
|
||||
const input = fileRefs[setting.identifier]
|
||||
const { files } = input
|
||||
|
||||
logger.debug('ref', input, files)
|
||||
|
||||
if (files && files.length > 0) {
|
||||
data[setting.identifier] = files[0]
|
||||
}
|
||||
} else {
|
||||
data[setting.identifier] = values[setting.identifier] as string
|
||||
}
|
||||
|
||||
return data
|
||||
}, {} as Record<string, string | File>)
|
||||
|
||||
contentType = 'multipart/form-data'
|
||||
postData = useFormData(data)
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await axios.post('instance/admin/settings/bulk/', postData, {
|
||||
headers: { 'Content-Type': contentType }
|
||||
})
|
||||
|
||||
result.value = true
|
||||
for (const setting of response.data) {
|
||||
values[setting.identifier] = setting.value
|
||||
}
|
||||
|
||||
store.dispatch('instance/fetchSettings')
|
||||
} catch (error) {
|
||||
errors.value = (error as BackendError).backendErrors
|
||||
}
|
||||
|
||||
isLoading.value = false
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<form
|
||||
:id="group.id"
|
||||
|
@ -35,9 +131,6 @@
|
|||
Settings updated successfully.
|
||||
</translate>
|
||||
</div>
|
||||
<p v-if="group.help">
|
||||
{{ group.help }}
|
||||
</p>
|
||||
<div
|
||||
v-for="(setting, key) in settings"
|
||||
:key="key"
|
||||
|
@ -56,8 +149,8 @@
|
|||
/>
|
||||
<signup-form-builder
|
||||
v-else-if="setting.fieldType === 'formBuilder'"
|
||||
v-model="values[setting.identifier]"
|
||||
:signup-approval-enabled="values.moderation__signup_approval_enabled"
|
||||
v-model="values[setting.identifier] as Form"
|
||||
:signup-approval-enabled="!!values.moderation__signup_approval_enabled"
|
||||
/>
|
||||
<input
|
||||
v-else-if="setting.field.widget.class === 'PasswordInput'"
|
||||
|
@ -86,7 +179,7 @@
|
|||
<textarea
|
||||
v-else-if="setting.field.widget.class === 'Textarea'"
|
||||
:id="setting.identifier"
|
||||
v-model="values[setting.identifier]"
|
||||
v-model="values[setting.identifier] as string"
|
||||
:name="setting.identifier"
|
||||
type="text"
|
||||
class="ui input"
|
||||
|
@ -97,7 +190,7 @@
|
|||
>
|
||||
<input
|
||||
:id="setting.identifier"
|
||||
v-model="values[setting.identifier]"
|
||||
v-model="values[setting.identifier] as boolean"
|
||||
:name="setting.identifier"
|
||||
type="checkbox"
|
||||
>
|
||||
|
@ -114,8 +207,8 @@
|
|||
class="ui search selection dropdown"
|
||||
>
|
||||
<option
|
||||
v-for="(v, index) in setting.additional_data.choices"
|
||||
:key="index"
|
||||
v-for="v in setting.additional_data.choices"
|
||||
:key="v[0]"
|
||||
:value="v[0]"
|
||||
>
|
||||
{{ v[1] }}
|
||||
|
@ -124,7 +217,7 @@
|
|||
<div v-else-if="setting.field.widget.class === 'ImageWidget'">
|
||||
<input
|
||||
:id="setting.identifier"
|
||||
:ref="setting.identifier"
|
||||
:ref="setFileRef(setting.identifier)"
|
||||
type="file"
|
||||
>
|
||||
<div v-if="values[setting.identifier]">
|
||||
|
@ -153,99 +246,3 @@
|
|||
</button>
|
||||
</form>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import axios from 'axios'
|
||||
import { cloneDeep } from 'lodash-es'
|
||||
import SignupFormBuilder from '~/components/admin/SignupFormBuilder.vue'
|
||||
import useFormData from '~/composables/useFormData'
|
||||
|
||||
export default {
|
||||
components: {
|
||||
SignupFormBuilder
|
||||
},
|
||||
props: {
|
||||
group: { type: Object, required: true },
|
||||
settingsData: { type: Array, required: true }
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
values: {},
|
||||
result: null,
|
||||
errors: [],
|
||||
isLoading: false
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
settings () {
|
||||
const byIdentifier = {}
|
||||
this.settingsData.forEach(e => {
|
||||
byIdentifier[e.identifier] = e
|
||||
})
|
||||
return this.group.settings.map(e => {
|
||||
return { ...byIdentifier[e.name], fieldType: e.fieldType, fieldParams: e.fieldParams || {} }
|
||||
})
|
||||
},
|
||||
fileSettings () {
|
||||
return this.settings.filter((s) => {
|
||||
return s.field.widget.class === 'ImageWidget'
|
||||
})
|
||||
}
|
||||
},
|
||||
created () {
|
||||
const self = this
|
||||
this.settings.forEach(e => {
|
||||
self.values[e.identifier] = e.value
|
||||
})
|
||||
},
|
||||
methods: {
|
||||
save () {
|
||||
const self = this
|
||||
this.isLoading = true
|
||||
self.errors = []
|
||||
self.result = null
|
||||
let postData = self.values
|
||||
let contentType = 'application/json'
|
||||
const fileSettingsIDs = this.fileSettings.map((s) => { return s.identifier })
|
||||
if (fileSettingsIDs.length > 0) {
|
||||
const data = this.settings.reduce((data, setting) => {
|
||||
if (fileSettingsIDs.includes(setting.identifier)) {
|
||||
const [input] = this.$refs[setting.identifier]
|
||||
const { files } = input
|
||||
|
||||
// TODO (wvffle): Move to the top of setup
|
||||
const logger = useLogger()
|
||||
logger.debug('ref', input, files)
|
||||
|
||||
if (files?.length > 0) {
|
||||
data[s.identifier] = files[0]
|
||||
}
|
||||
} else {
|
||||
postData.append(s.identifier, this.values[s.identifier])
|
||||
}
|
||||
|
||||
return data
|
||||
}, {})
|
||||
|
||||
contentType = 'multipart/form-data'
|
||||
postData = useFormData(data)
|
||||
}
|
||||
axios.post('instance/admin/settings/bulk/', postData, {
|
||||
headers: {
|
||||
'Content-Type': contentType
|
||||
}
|
||||
}).then((response) => {
|
||||
self.result = true
|
||||
response.data.forEach((s) => {
|
||||
self.values[s.identifier] = s.value
|
||||
})
|
||||
self.isLoading = false
|
||||
self.$store.dispatch('instance/fetchSettings')
|
||||
}, error => {
|
||||
self.isLoading = false
|
||||
self.errors = error.backendErrors
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
|
|
@ -1,8 +1,11 @@
|
|||
import type { App } from 'vue'
|
||||
import type { Store } from 'vuex'
|
||||
import { Router } from 'vue-router'
|
||||
import { AxiosError } from 'axios'
|
||||
import { RootState } from '~/store'
|
||||
import type { Router } from 'vue-router'
|
||||
import type { AxiosError } from 'axios'
|
||||
import type { RootState } from '~/store'
|
||||
import type { ComponentPublicInstance } from '@vue/runtime-core'
|
||||
|
||||
export type FunctionRef = Element | ComponentPublicInstance | null
|
||||
|
||||
declare global {
|
||||
interface Window {
|
||||
|
@ -259,3 +262,39 @@ export interface Actor {
|
|||
is_local: boolean
|
||||
domain: string
|
||||
}
|
||||
|
||||
// Settings stuff
|
||||
export type SettingsId = 'instance'
|
||||
export interface SettingsGroup {
|
||||
label: string
|
||||
id: SettingsId
|
||||
settings: SettingsField[]
|
||||
}
|
||||
|
||||
export interface SettingsField {
|
||||
name: string
|
||||
fieldType?: 'markdown'
|
||||
fieldParams?: {
|
||||
charLimit: number | null
|
||||
permissive: boolean
|
||||
}
|
||||
}
|
||||
|
||||
export interface SettingsDataEntry {
|
||||
identifier: string
|
||||
fieldType: string
|
||||
fieldParams: object
|
||||
help_text: string
|
||||
verbose_name: string
|
||||
value: unknown
|
||||
field: {
|
||||
class: string
|
||||
widget: {
|
||||
class: string
|
||||
}
|
||||
}
|
||||
|
||||
additional_data: {
|
||||
choices: [string, string]
|
||||
}
|
||||
}
|
|
@ -1,3 +1,163 @@
|
|||
<script setup lang="ts">
|
||||
import type { SettingsGroup } from '~/types'
|
||||
import axios from 'axios'
|
||||
import $ from 'jquery'
|
||||
|
||||
import { useCurrentElement } from '@vueuse/core'
|
||||
import { ref, nextTick, onMounted, computed, watch } from 'vue'
|
||||
import { useRoute } from 'vue-router'
|
||||
import { useGettext } from 'vue3-gettext'
|
||||
|
||||
const current = ref()
|
||||
const settingsData = ref()
|
||||
|
||||
const { $pgettext } = useGettext()
|
||||
|
||||
const groups = computed(() => [
|
||||
{
|
||||
label: $pgettext('Content/Admin/Menu', 'Instance information'),
|
||||
id: 'instance',
|
||||
settings: [
|
||||
{ name: 'instance__name' },
|
||||
{ name: 'instance__short_description' },
|
||||
{ name: 'instance__long_description', fieldType: 'markdown', fieldParams: { charLimit: null, permissive: true } },
|
||||
{ name: 'instance__contact_email' },
|
||||
{ name: 'instance__rules', fieldType: 'markdown', fieldParams: { charLimit: null, permissive: true } },
|
||||
{ name: 'instance__terms', fieldType: 'markdown', fieldParams: { charLimit: null, permissive: true } },
|
||||
{ name: 'instance__banner' },
|
||||
{ name: 'instance__support_message', fieldType: 'markdown', fieldParams: { charLimit: null, permissive: true } }
|
||||
]
|
||||
},
|
||||
{
|
||||
label: $pgettext('*/*/*/Noun', 'Sign-ups'),
|
||||
id: 'signup',
|
||||
settings: [
|
||||
{ name: 'users__registration_enabled' },
|
||||
{ name: 'moderation__signup_approval_enabled' },
|
||||
{ name: 'moderation__signup_form_customization', fieldType: 'formBuilder' }
|
||||
]
|
||||
},
|
||||
{
|
||||
label: $pgettext('*/*/*/Noun', 'Security'),
|
||||
id: 'security',
|
||||
settings: [
|
||||
{ name: 'common__api_authentication_required' },
|
||||
{ name: 'users__default_permissions' },
|
||||
{ name: 'users__upload_quota' }
|
||||
]
|
||||
},
|
||||
{
|
||||
label: $pgettext('*/*/*/Noun', 'Music'),
|
||||
id: 'music',
|
||||
settings: [
|
||||
{ name: 'music__transcoding_enabled' },
|
||||
{ name: 'music__transcoding_cache_duration' }
|
||||
]
|
||||
},
|
||||
{
|
||||
label: $pgettext('*/*/*', 'Channels'),
|
||||
id: 'channels',
|
||||
settings: [
|
||||
{ name: 'audio__channels_enabled' },
|
||||
{ name: 'audio__max_channels' }
|
||||
]
|
||||
},
|
||||
{
|
||||
label: $pgettext('*/*/*', 'Playlists'),
|
||||
id: 'playlists',
|
||||
settings: [
|
||||
{ name: 'playlists__max_tracks' }
|
||||
]
|
||||
},
|
||||
{
|
||||
label: $pgettext('*/Moderation/*', 'Moderation'),
|
||||
id: 'moderation',
|
||||
settings: [
|
||||
{ name: 'moderation__allow_list_enabled' },
|
||||
{ name: 'moderation__allow_list_public' },
|
||||
{ name: 'moderation__unauthenticated_report_types' }
|
||||
]
|
||||
},
|
||||
{
|
||||
label: $pgettext('*/*/*', 'Federation'),
|
||||
id: 'federation',
|
||||
settings: [
|
||||
{ name: 'federation__enabled' },
|
||||
{ name: 'federation__public_index' },
|
||||
{ name: 'federation__collection_page_size' },
|
||||
{ name: 'federation__music_cache_duration' },
|
||||
{ name: 'federation__actor_fetch_delay' }
|
||||
]
|
||||
},
|
||||
{
|
||||
label: $pgettext('Content/Admin/Menu', 'Subsonic'),
|
||||
id: 'subsonic',
|
||||
settings: [
|
||||
{ name: 'subsonic__enabled' }
|
||||
]
|
||||
},
|
||||
{
|
||||
label: $pgettext('Content/Home/Header', 'Statistics'),
|
||||
id: 'ui',
|
||||
settings: [
|
||||
{ name: 'ui__custom_css' },
|
||||
{ name: 'instance__funkwhale_support_message_enabled' }
|
||||
]
|
||||
},
|
||||
{
|
||||
label: $pgettext('Content/Admin/Menu', 'User Interface'),
|
||||
id: 'statistics',
|
||||
settings: [
|
||||
{ name: 'instance__nodeinfo_stats_enabled' },
|
||||
{ name: 'instance__nodeinfo_private' }
|
||||
]
|
||||
}
|
||||
] as SettingsGroup[])
|
||||
|
||||
const labels = computed(() => ({
|
||||
settings: $pgettext('Head/Admin/Title', 'Instance settings')
|
||||
}))
|
||||
|
||||
const scrollTo = (id: string) => {
|
||||
current.value = id
|
||||
document.getElementById(id)?.scrollIntoView()
|
||||
}
|
||||
|
||||
const route = useRoute()
|
||||
if (route.hash) {
|
||||
scrollTo(route.hash.slice(1))
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
// @ts-expect-error dropdown is from semantic ui
|
||||
$('select.dropdown').dropdown()
|
||||
})
|
||||
|
||||
const el = useCurrentElement()
|
||||
watch(settingsData, async () => {
|
||||
await nextTick()
|
||||
// @ts-expect-error sticky is from semantic ui
|
||||
$(el.value).find('.sticky').sticky({ context: '#settings-grid' })
|
||||
})
|
||||
|
||||
const isLoading = ref(false)
|
||||
const fetchSettings = async () => {
|
||||
isLoading.value = true
|
||||
|
||||
try {
|
||||
const response = await axios.get('instance/admin/settings/')
|
||||
settingsData.value = response.data
|
||||
} catch (error) {
|
||||
// TODO (wvffle): Handle error
|
||||
}
|
||||
|
||||
isLoading.value = false
|
||||
}
|
||||
|
||||
await fetchSettings()
|
||||
await nextTick()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<main
|
||||
v-title="labels.settings"
|
||||
|
@ -14,7 +174,7 @@
|
|||
<div class="twelve wide stretched column">
|
||||
<settings-group
|
||||
v-for="group in groups"
|
||||
:key="group.title"
|
||||
:key="group.id"
|
||||
:settings-data="settingsData"
|
||||
:group="group"
|
||||
/>
|
||||
|
@ -40,181 +200,3 @@
|
|||
</div>
|
||||
</main>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import axios from 'axios'
|
||||
import $ from 'jquery'
|
||||
|
||||
import SettingsGroup from '~/components/admin/SettingsGroup.vue'
|
||||
import { nextTick } from 'vue'
|
||||
import { useRoute } from 'vue-router'
|
||||
|
||||
export default {
|
||||
components: {
|
||||
SettingsGroup
|
||||
},
|
||||
async setup () {
|
||||
await this.fetchSettings()
|
||||
await nextTick()
|
||||
|
||||
const route = useRoute()
|
||||
if (route.hash) {
|
||||
this.scrollTo(route.hash.slice(1))
|
||||
}
|
||||
|
||||
$('select.dropdown').dropdown()
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
isLoading: false,
|
||||
settingsData: null,
|
||||
current: null
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
labels () {
|
||||
return {
|
||||
settings: this.$pgettext('Head/Admin/Title', 'Instance settings')
|
||||
}
|
||||
},
|
||||
groups () {
|
||||
// somehow, extraction fails if in the return block directly
|
||||
const instanceLabel = this.$pgettext('Content/Admin/Menu', 'Instance information')
|
||||
const signupsLabel = this.$pgettext('*/*/*/Noun', 'Sign-ups')
|
||||
const securityLabel = this.$pgettext('*/*/*/Noun', 'Security')
|
||||
const musicLabel = this.$pgettext('*/*/*/Noun', 'Music')
|
||||
const channelsLabel = this.$pgettext('*/*/*', 'Channels')
|
||||
const playlistsLabel = this.$pgettext('*/*/*', 'Playlists')
|
||||
const federationLabel = this.$pgettext('*/*/*', 'Federation')
|
||||
const moderationLabel = this.$pgettext('*/Moderation/*', 'Moderation')
|
||||
const subsonicLabel = this.$pgettext('Content/Admin/Menu', 'Subsonic')
|
||||
const statisticsLabel = this.$pgettext('Content/Home/Header', 'Statistics')
|
||||
const uiLabel = this.$pgettext('Content/Admin/Menu', 'User Interface')
|
||||
return [
|
||||
{
|
||||
label: instanceLabel,
|
||||
id: 'instance',
|
||||
settings: [
|
||||
{ name: 'instance__name' },
|
||||
{ name: 'instance__short_description' },
|
||||
{ name: 'instance__long_description', fieldType: 'markdown', fieldParams: { charLimit: null, permissive: true } },
|
||||
{ name: 'instance__contact_email' },
|
||||
{ name: 'instance__rules', fieldType: 'markdown', fieldParams: { charLimit: null, permissive: true } },
|
||||
{ name: 'instance__terms', fieldType: 'markdown', fieldParams: { charLimit: null, permissive: true } },
|
||||
{ name: 'instance__banner' },
|
||||
{ name: 'instance__support_message', fieldType: 'markdown', fieldParams: { charLimit: null, permissive: true } }
|
||||
]
|
||||
},
|
||||
{
|
||||
label: signupsLabel,
|
||||
id: 'signup',
|
||||
settings: [
|
||||
{ name: 'users__registration_enabled' },
|
||||
{ name: 'moderation__signup_approval_enabled' },
|
||||
{ name: 'moderation__signup_form_customization', fieldType: 'formBuilder' }
|
||||
]
|
||||
},
|
||||
{
|
||||
label: securityLabel,
|
||||
id: 'security',
|
||||
settings: [
|
||||
{ name: 'common__api_authentication_required' },
|
||||
{ name: 'users__default_permissions' },
|
||||
{ name: 'users__upload_quota' }
|
||||
]
|
||||
},
|
||||
{
|
||||
label: musicLabel,
|
||||
id: 'music',
|
||||
settings: [
|
||||
{ name: 'music__transcoding_enabled' },
|
||||
{ name: 'music__transcoding_cache_duration' }
|
||||
]
|
||||
},
|
||||
{
|
||||
label: channelsLabel,
|
||||
id: 'channels',
|
||||
settings: [
|
||||
{ name: 'audio__channels_enabled' },
|
||||
{ name: 'audio__max_channels' }
|
||||
]
|
||||
},
|
||||
{
|
||||
label: playlistsLabel,
|
||||
id: 'playlists',
|
||||
settings: [
|
||||
{ name: 'playlists__max_tracks' }
|
||||
]
|
||||
},
|
||||
{
|
||||
label: moderationLabel,
|
||||
id: 'moderation',
|
||||
settings: [
|
||||
{ name: 'moderation__allow_list_enabled' },
|
||||
{ name: 'moderation__allow_list_public' },
|
||||
{ name: 'moderation__unauthenticated_report_types' }
|
||||
]
|
||||
},
|
||||
{
|
||||
label: federationLabel,
|
||||
id: 'federation',
|
||||
settings: [
|
||||
{ name: 'federation__enabled' },
|
||||
{ name: 'federation__public_index' },
|
||||
{ name: 'federation__collection_page_size' },
|
||||
{ name: 'federation__music_cache_duration' },
|
||||
{ name: 'federation__actor_fetch_delay' }
|
||||
]
|
||||
},
|
||||
{
|
||||
label: subsonicLabel,
|
||||
id: 'subsonic',
|
||||
settings: [
|
||||
{ name: 'subsonic__enabled' }
|
||||
]
|
||||
},
|
||||
{
|
||||
label: uiLabel,
|
||||
id: 'ui',
|
||||
settings: [
|
||||
{ name: 'ui__custom_css' },
|
||||
{ name: 'instance__funkwhale_support_message_enabled' }
|
||||
]
|
||||
},
|
||||
{
|
||||
label: statisticsLabel,
|
||||
id: 'statistics',
|
||||
settings: [
|
||||
{ name: 'instance__nodeinfo_stats_enabled' },
|
||||
{ name: 'instance__nodeinfo_private' }
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
settingsData () {
|
||||
const self = this
|
||||
this.$nextTick(() => {
|
||||
$(self.$el)
|
||||
.find('.sticky')
|
||||
.sticky({ context: '#settings-grid' })
|
||||
})
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
scrollTo (id) {
|
||||
this.current = id
|
||||
document.getElementById(id).scrollIntoView()
|
||||
},
|
||||
async fetchSettings () {
|
||||
const self = this
|
||||
self.isLoading = true
|
||||
return axios.get('instance/admin/settings/').then(response => {
|
||||
self.settingsData = response.data
|
||||
self.isLoading = false
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
|
|
@ -1364,9 +1364,9 @@
|
|||
"@babel/types" "^7.3.0"
|
||||
|
||||
"@types/dompurify@^2.3.3":
|
||||
version "2.3.3"
|
||||
resolved "https://registry.yarnpkg.com/@types/dompurify/-/dompurify-2.3.3.tgz#c24c92f698f77ed9cc9d9fa7888f90cf2bfaa23f"
|
||||
integrity sha512-nnVQSgRVuZ/843oAfhA25eRSNzUFcBPk/LOiw5gm8mD9/X7CNcbRkQu/OsjCewO8+VIYfPxUnXvPEVGenw14+w==
|
||||
version "2.3.4"
|
||||
resolved "https://registry.yarnpkg.com/@types/dompurify/-/dompurify-2.3.4.tgz#94e997e30338ea24d4c8d08beca91ce4dd17a1b4"
|
||||
integrity sha512-EXzDatIb5EspL2eb/xPGmaC8pePcTHrkDCONjeisusLFrVfl38Pjea/R0YJGu3k9ZQadSvMqW0WXPI2hEo2Ajg==
|
||||
dependencies:
|
||||
"@types/trusted-types" "*"
|
||||
|
||||
|
@ -1870,7 +1870,7 @@
|
|||
dependencies:
|
||||
"@vue/shared" "3.2.37"
|
||||
|
||||
"@vue/reactivity@^3.2.37":
|
||||
"@vue/reactivity@3.2.38", "@vue/reactivity@^3.2.37":
|
||||
version "3.2.38"
|
||||
resolved "https://registry.yarnpkg.com/@vue/reactivity/-/reactivity-3.2.38.tgz#d576fdcea98eefb96a1f1ad456e289263e87292e"
|
||||
integrity sha512-6L4myYcH9HG2M25co7/BSo0skKFHpAN8PhkNPM4xRVkyGl1K5M3Jx4rp5bsYhvYze2K4+l+pioN4e6ZwFLUVtw==
|
||||
|
@ -1885,6 +1885,14 @@
|
|||
"@vue/reactivity" "3.2.37"
|
||||
"@vue/shared" "3.2.37"
|
||||
|
||||
"@vue/runtime-core@^3.2.37":
|
||||
version "3.2.38"
|
||||
resolved "https://registry.yarnpkg.com/@vue/runtime-core/-/runtime-core-3.2.38.tgz#d19cf591c210713f80e6a94ffbfef307c27aea06"
|
||||
integrity sha512-kk0qiSiXUU/IKxZw31824rxmFzrLr3TL6ZcbrxWTKivadoKupdlzbQM4SlGo4MU6Zzrqv4fzyUasTU1jDoEnzg==
|
||||
dependencies:
|
||||
"@vue/reactivity" "3.2.38"
|
||||
"@vue/shared" "3.2.38"
|
||||
|
||||
"@vue/runtime-dom@3.2.37":
|
||||
version "3.2.37"
|
||||
resolved "https://registry.yarnpkg.com/@vue/runtime-dom/-/runtime-dom-3.2.37.tgz#002bdc8228fa63949317756fb1e92cdd3f9f4bbd"
|
||||
|
@ -2928,9 +2936,9 @@ domhandler@^5.0.1, domhandler@^5.0.2, domhandler@^5.0.3:
|
|||
domelementtype "^2.3.0"
|
||||
|
||||
dompurify@^2.3.8:
|
||||
version "2.3.8"
|
||||
resolved "https://registry.yarnpkg.com/dompurify/-/dompurify-2.3.8.tgz#224fe9ae57d7ebd9a1ae1ac18c1c1ca3f532226f"
|
||||
integrity sha512-eVhaWoVibIzqdGYjwsBWodIQIaXFSB+cKDf4cfxLMsK0xiud6SE+/WCVx/Xw/UwQsa4cS3T2eITcdtmTg2UKcw==
|
||||
version "2.4.0"
|
||||
resolved "https://registry.yarnpkg.com/dompurify/-/dompurify-2.4.0.tgz#c9c88390f024c2823332615c9e20a453cf3825dd"
|
||||
integrity sha512-Be9tbQMZds4a3C6xTmz68NlMfeONA//4dOavl/1rNw50E+/QO0KVpbcU0PcaW0nsQxurXls9ZocqFxk8R2mWEA==
|
||||
|
||||
domutils@^2.5.2, domutils@^2.8.0:
|
||||
version "2.8.0"
|
||||
|
|
Loading…
Reference in New Issue