Migrate a couple of components to new v-model and cleanup linting stuff
This commit is contained in:
parent
16d437be62
commit
2f80e0935f
|
@ -34,7 +34,8 @@ module.exports = {
|
||||||
// and gettext for vue 2
|
// and gettext for vue 2
|
||||||
'@typescript-eslint/ban-ts-comment': 'off',
|
'@typescript-eslint/ban-ts-comment': 'off',
|
||||||
|
|
||||||
// TODO (wvffle): Enable typescript rules later
|
// TODO (wvffle): Enable these rules later
|
||||||
|
'vue/multi-word-component-names': 'off',
|
||||||
'@typescript-eslint/no-this-alias': 'off',
|
'@typescript-eslint/no-this-alias': 'off',
|
||||||
'@typescript-eslint/no-empty-function': 'off',
|
'@typescript-eslint/no-empty-function': 'off',
|
||||||
'@typescript-eslint/no-unused-vars': 'off',
|
'@typescript-eslint/no-unused-vars': 'off',
|
||||||
|
|
|
@ -11,6 +11,7 @@
|
||||||
"serve": "vite preview",
|
"serve": "vite preview",
|
||||||
"test:unit": "jest",
|
"test:unit": "jest",
|
||||||
"lint": "eslint --ext .ts,.js,.vue src",
|
"lint": "eslint --ext .ts,.js,.vue src",
|
||||||
|
"lint:tsc": "vue-tsc --noEmit",
|
||||||
"fix-fomantic-css": "scripts/fix-fomantic-css.sh",
|
"fix-fomantic-css": "scripts/fix-fomantic-css.sh",
|
||||||
"i18n-compile": "scripts/i18n-compile.sh",
|
"i18n-compile": "scripts/i18n-compile.sh",
|
||||||
"i18n-extract": "scripts/i18n-extract.sh",
|
"i18n-extract": "scripts/i18n-extract.sh",
|
||||||
|
@ -40,6 +41,7 @@
|
||||||
"vue-gettext": "2.1.12",
|
"vue-gettext": "2.1.12",
|
||||||
"vue-plyr": "7.0.0",
|
"vue-plyr": "7.0.0",
|
||||||
"vue-router": "4.0.14",
|
"vue-router": "4.0.14",
|
||||||
|
"vue-tsc": "0.34.7",
|
||||||
"vue-upload-component": "2.8.22",
|
"vue-upload-component": "2.8.22",
|
||||||
"vue3-gettext": "2.2.0-alpha.1",
|
"vue3-gettext": "2.2.0-alpha.1",
|
||||||
"vue3-lazyload": "0.2.5-beta",
|
"vue3-lazyload": "0.2.5-beta",
|
||||||
|
@ -49,9 +51,6 @@
|
||||||
"vuex-router-sync": "5.0.0"
|
"vuex-router-sync": "5.0.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@babel/core": "7.17.12",
|
|
||||||
"@babel/plugin-transform-runtime": "7.17.12",
|
|
||||||
"@babel/preset-env": "7.16.11",
|
|
||||||
"@types/jest": "27.4.1",
|
"@types/jest": "27.4.1",
|
||||||
"@types/jquery": "3.5.14",
|
"@types/jquery": "3.5.14",
|
||||||
"@types/lodash-es": "4.17.6",
|
"@types/lodash-es": "4.17.6",
|
||||||
|
@ -62,9 +61,7 @@
|
||||||
"@vue/eslint-config-standard": "6.1.0",
|
"@vue/eslint-config-standard": "6.1.0",
|
||||||
"@vue/eslint-config-typescript": "10.0.0",
|
"@vue/eslint-config-typescript": "10.0.0",
|
||||||
"@vue/test-utils": "1.3.0",
|
"@vue/test-utils": "1.3.0",
|
||||||
"autoprefixer": "10.4.7",
|
"autoprefixer": "10.4.4",
|
||||||
"babel-core": "7.0.0-bridge.0",
|
|
||||||
"babel-jest": "27.5.1",
|
|
||||||
"chai": "4.3.6",
|
"chai": "4.3.6",
|
||||||
"easygettext": "2.17.0",
|
"easygettext": "2.17.0",
|
||||||
"eslint": "8.11.0",
|
"eslint": "8.11.0",
|
||||||
|
@ -74,7 +71,6 @@
|
||||||
"eslint-plugin-node": "11.1.0",
|
"eslint-plugin-node": "11.1.0",
|
||||||
"eslint-plugin-promise": "6.0.0",
|
"eslint-plugin-promise": "6.0.0",
|
||||||
"eslint-plugin-vue": "7.20.0",
|
"eslint-plugin-vue": "7.20.0",
|
||||||
"glob-all": "3.3.0",
|
|
||||||
"jest-cli": "27.5.1",
|
"jest-cli": "27.5.1",
|
||||||
"moxios": "0.4.0",
|
"moxios": "0.4.0",
|
||||||
"sinon": "13.0.2",
|
"sinon": "13.0.2",
|
||||||
|
@ -91,27 +87,6 @@
|
||||||
"resolutions": {
|
"resolutions": {
|
||||||
"vue-plyr/plyr": "3.6.12"
|
"vue-plyr/plyr": "3.6.12"
|
||||||
},
|
},
|
||||||
"postcss": {
|
|
||||||
"plugins": {
|
|
||||||
"autoprefixer": {}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"browserslist": [
|
|
||||||
"IE >= 11",
|
|
||||||
"Firefox >= 52",
|
|
||||||
"ChromeAndroid >= 70",
|
|
||||||
"Chrome >= 49",
|
|
||||||
"Safari >= 9",
|
|
||||||
"Edge >= 16",
|
|
||||||
"Opera >= 57",
|
|
||||||
"OperaMini >= 57",
|
|
||||||
"Samsung >= 7",
|
|
||||||
"FirefoxAndroid >= 63",
|
|
||||||
"UCAndroid >= 11",
|
|
||||||
"iOS >= 9",
|
|
||||||
"Android >= 4",
|
|
||||||
"not dead"
|
|
||||||
],
|
|
||||||
"jest": {
|
"jest": {
|
||||||
"moduleFileExtensions": [
|
"moduleFileExtensions": [
|
||||||
"ts",
|
"ts",
|
||||||
|
|
|
@ -154,8 +154,8 @@
|
||||||
</template>
|
</template>
|
||||||
<user-modal
|
<user-modal
|
||||||
v-model:show="showUserModal"
|
v-model:show="showUserModal"
|
||||||
@showThemeModalEvent="showThemeModal=true"
|
@show-theme-modal-event="showThemeModal=true"
|
||||||
@showLanguageModalEvent="showLanguageModal=true"
|
@show-language-modal-event="showLanguageModal=true"
|
||||||
/>
|
/>
|
||||||
<modal
|
<modal
|
||||||
ref="languageModal"
|
ref="languageModal"
|
||||||
|
|
|
@ -56,9 +56,8 @@
|
||||||
/>
|
/>
|
||||||
<signup-form-builder
|
<signup-form-builder
|
||||||
v-else-if="setting.fieldType === 'formBuilder'"
|
v-else-if="setting.fieldType === 'formBuilder'"
|
||||||
:value="values[setting.identifier]"
|
v-model="values[setting.identifier]"
|
||||||
:signup-approval-enabled="values.moderation__signup_approval_enabled"
|
:signup-approval-enabled="values.moderation__signup_approval_enabled"
|
||||||
@input="set(setting.identifier, $event)"
|
|
||||||
/>
|
/>
|
||||||
<input
|
<input
|
||||||
v-else-if="setting.field.widget.class === 'PasswordInput'"
|
v-else-if="setting.field.widget.class === 'PasswordInput'"
|
||||||
|
@ -238,12 +237,6 @@ export default {
|
||||||
self.isLoading = false
|
self.isLoading = false
|
||||||
self.errors = error.backendErrors
|
self.errors = error.backendErrors
|
||||||
})
|
})
|
||||||
},
|
|
||||||
set (key, value) {
|
|
||||||
// otherwise reactivity doesn't trigger :/
|
|
||||||
this.values = cloneDeep(this.values)
|
|
||||||
// TODO (wvffle): Replace $set and $delete with reactive()
|
|
||||||
this.$set(this.values, key, value)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,3 +1,62 @@
|
||||||
|
<script setup lang="ts">
|
||||||
|
import SignupForm from '~/components/auth/SignupForm.vue'
|
||||||
|
import { useVModel } from '@vueuse/core'
|
||||||
|
import { computed, ref } from 'vue'
|
||||||
|
import { useGettext } from 'vue3-gettext'
|
||||||
|
import { Form } from '~/types'
|
||||||
|
import { arrayMove } from '~/utils'
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
modelValue: Form
|
||||||
|
signupApprovalEnabled?: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
const props = withDefaults(defineProps<Props>(), {
|
||||||
|
signupApprovalEnabled: false
|
||||||
|
})
|
||||||
|
|
||||||
|
const emit = defineEmits(['update:modelValue'])
|
||||||
|
const value = useVModel(props, 'modelValue', emit, { deep: true })
|
||||||
|
|
||||||
|
const maxFields = ref(10)
|
||||||
|
const isPreviewing = ref(false)
|
||||||
|
|
||||||
|
const { $pgettext } = useGettext()
|
||||||
|
const labels = computed(() => ({
|
||||||
|
delete: $pgettext('*/*/*', 'Delete'),
|
||||||
|
up: $pgettext('*/*/*', 'Move up'),
|
||||||
|
down: $pgettext('*/*/*', 'Move down')
|
||||||
|
}))
|
||||||
|
|
||||||
|
if (!value.value?.fields) {
|
||||||
|
value.value = {
|
||||||
|
help_text: {
|
||||||
|
text: '',
|
||||||
|
content_type: 'text/markdown'
|
||||||
|
},
|
||||||
|
fields: []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const addField = () => {
|
||||||
|
value.value.fields.push({
|
||||||
|
label: $pgettext('*/*/Form-builder', 'Additional field') + ' ' + (value.value.fields.length + 1),
|
||||||
|
required: true,
|
||||||
|
input_type: 'short_text'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const remove = (idx: number) => {
|
||||||
|
value.value.fields.splice(idx, 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
const move = (idx: number, increment: number) => {
|
||||||
|
if (idx + increment >= value.value.fields.length) return
|
||||||
|
if (idx === 0 && increment < 0) return
|
||||||
|
arrayMove(value.value.fields, idx, idx + increment)
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div>
|
<div>
|
||||||
<div class="ui top attached tabular menu">
|
<div class="ui top attached tabular menu">
|
||||||
|
@ -23,7 +82,7 @@
|
||||||
class="ui bottom attached segment"
|
class="ui bottom attached segment"
|
||||||
>
|
>
|
||||||
<signup-form
|
<signup-form
|
||||||
:customization="local"
|
:customization="value"
|
||||||
:signup-approval-enabled="signupApprovalEnabled"
|
:signup-approval-enabled="signupApprovalEnabled"
|
||||||
:fetch-description-html="true"
|
:fetch-description-html="true"
|
||||||
/>
|
/>
|
||||||
|
@ -43,10 +102,9 @@
|
||||||
</translate>
|
</translate>
|
||||||
</p>
|
</p>
|
||||||
<content-form
|
<content-form
|
||||||
|
v-model="value.help_text.text"
|
||||||
field-id="help-text"
|
field-id="help-text"
|
||||||
:permissive="true"
|
:permissive="true"
|
||||||
:value="(local.help_text || {}).text"
|
|
||||||
@input="update('help_text.text', $event)"
|
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div class="field">
|
<div class="field">
|
||||||
|
@ -58,7 +116,7 @@
|
||||||
Additional form fields to be displayed in the form. Only shown if manual sign-up validation is enabled.
|
Additional form fields to be displayed in the form. Only shown if manual sign-up validation is enabled.
|
||||||
</translate>
|
</translate>
|
||||||
</p>
|
</p>
|
||||||
<table v-if="local.fields.length > 0">
|
<table v-if="value.fields.length > 0">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th>
|
<th>
|
||||||
|
@ -80,8 +138,9 @@
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
|
<!-- TODO (wvffle): Add random _id as :key -->
|
||||||
<tr
|
<tr
|
||||||
v-for="(field, idx) in local.fields"
|
v-for="(field, idx) in value.fields"
|
||||||
:key="idx"
|
:key="idx"
|
||||||
>
|
>
|
||||||
<td>
|
<td>
|
||||||
|
@ -124,14 +183,14 @@
|
||||||
:disabled="idx === 0 || null"
|
:disabled="idx === 0 || null"
|
||||||
role="button"
|
role="button"
|
||||||
:title="labels.up"
|
:title="labels.up"
|
||||||
:class="['up', 'arrow', {disabled: idx === 0}, 'icon']"
|
:class="['up', 'arrow', { disabled: idx === 0 }, 'icon']"
|
||||||
@click="move(idx, -1)"
|
@click="move(idx, -1)"
|
||||||
/>
|
/>
|
||||||
<i
|
<i
|
||||||
:disabled="idx >= local.fields.length - 1 || null"
|
:disabled="idx >= value.fields.length - 1 || null"
|
||||||
role="button"
|
role="button"
|
||||||
:title="labels.down"
|
:title="labels.down"
|
||||||
:class="['down', 'arrow', {disabled: idx >= local.fields.length - 1}, 'icon']"
|
:class="['down', 'arrow', { disabled: idx >= value.fields.length - 1 }, 'icon']"
|
||||||
@click="move(idx, 1)"
|
@click="move(idx, 1)"
|
||||||
/>
|
/>
|
||||||
<i
|
<i
|
||||||
|
@ -146,7 +205,7 @@
|
||||||
</table>
|
</table>
|
||||||
<div class="ui hidden divider" />
|
<div class="ui hidden divider" />
|
||||||
<button
|
<button
|
||||||
v-if="local.fields.length < maxFields"
|
v-if="value.fields.length < maxFields"
|
||||||
class="ui basic button"
|
class="ui basic button"
|
||||||
@click.stop.prevent="addField"
|
@click.stop.prevent="addField"
|
||||||
>
|
>
|
||||||
|
@ -159,90 +218,3 @@
|
||||||
<div class="ui hidden divider" />
|
<div class="ui hidden divider" />
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
|
||||||
import { cloneDeep, tap, set } from 'lodash-es'
|
|
||||||
|
|
||||||
import SignupForm from '~/components/auth/SignupForm.vue'
|
|
||||||
|
|
||||||
function arrayMove (arr, oldIndex, newIndex) {
|
|
||||||
if (newIndex >= arr.length) {
|
|
||||||
let k = newIndex - arr.length + 1
|
|
||||||
while (k--) {
|
|
||||||
arr.push(undefined)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
arr.splice(newIndex, 0, arr.splice(oldIndex, 1)[0])
|
|
||||||
return arr
|
|
||||||
}
|
|
||||||
|
|
||||||
// v-model with objects is complex, cf
|
|
||||||
// https://simonkollross.de/posts/vuejs-using-v-model-with-objects-for-custom-components
|
|
||||||
export default {
|
|
||||||
components: {
|
|
||||||
SignupForm
|
|
||||||
},
|
|
||||||
props: {
|
|
||||||
value: { type: Object, required: true },
|
|
||||||
signupApprovalEnabled: { type: Boolean }
|
|
||||||
},
|
|
||||||
data () {
|
|
||||||
return {
|
|
||||||
maxFields: 10,
|
|
||||||
isPreviewing: false
|
|
||||||
}
|
|
||||||
},
|
|
||||||
computed: {
|
|
||||||
labels () {
|
|
||||||
return {
|
|
||||||
delete: this.$pgettext('*/*/*', 'Delete'),
|
|
||||||
up: this.$pgettext('*/*/*', 'Move up'),
|
|
||||||
down: this.$pgettext('*/*/*', 'Move down')
|
|
||||||
}
|
|
||||||
},
|
|
||||||
local () {
|
|
||||||
return (this.value && this.value.fields) ? this.value : { help_text: { text: null, content_type: 'text/markdown' }, fields: [] }
|
|
||||||
}
|
|
||||||
},
|
|
||||||
created () {
|
|
||||||
this.$emit('input', this.local)
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
addField () {
|
|
||||||
const newValue = tap(cloneDeep(this.local), v => v.fields.push({
|
|
||||||
label: this.$pgettext('*/*/Form-builder', 'Additional field') + ' ' + (this.local.fields.length + 1),
|
|
||||||
required: true,
|
|
||||||
input_type: 'short_text'
|
|
||||||
}))
|
|
||||||
this.$emit('input', newValue)
|
|
||||||
},
|
|
||||||
remove (idx) {
|
|
||||||
this.$emit('input', tap(cloneDeep(this.local), v => v.fields.splice(idx, 1)))
|
|
||||||
},
|
|
||||||
move (idx, incr) {
|
|
||||||
if (idx === 0 && incr < 0) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if (idx + incr >= this.local.fields.length) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
const newFields = arrayMove(cloneDeep(this.local).fields, idx, idx + incr)
|
|
||||||
this.update('fields', newFields)
|
|
||||||
},
|
|
||||||
update (key, value) {
|
|
||||||
if (key === 'help_text.text') {
|
|
||||||
key = 'help_text'
|
|
||||||
if (!value || value.length === 0) {
|
|
||||||
value = null
|
|
||||||
} else {
|
|
||||||
value = {
|
|
||||||
text: value,
|
|
||||||
content_type: 'text/markdown'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
this.$emit('input', tap(cloneDeep(this.local), v => set(v, key, value)))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
|
@ -170,7 +170,7 @@ export default {
|
||||||
defaultInvitation: { type: String, required: false, default: null },
|
defaultInvitation: { type: String, required: false, default: null },
|
||||||
next: { type: String, default: '/' },
|
next: { type: String, default: '/' },
|
||||||
buttonClasses: { type: String, default: 'success' },
|
buttonClasses: { type: String, default: 'success' },
|
||||||
customization: { type: Object, default: null },
|
customization: { type: Object, default: null }, // ts type: Form
|
||||||
fetchDescriptionHtml: { type: Boolean, default: false },
|
fetchDescriptionHtml: { type: Boolean, default: false },
|
||||||
signupApprovalEnabled: { type: Boolean, default: null, required: false }
|
signupApprovalEnabled: { type: Boolean, default: null, required: false }
|
||||||
},
|
},
|
||||||
|
|
|
@ -1,32 +1,33 @@
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { useVModel } from '@vueuse/core'
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
modelValue: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
const props = defineProps<Props>()
|
||||||
|
const emit = defineEmits(['update:modelValue'])
|
||||||
|
const value = useVModel(props, 'modelValue', emit)
|
||||||
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<a
|
<a
|
||||||
role="button"
|
role="button"
|
||||||
class="collapse link"
|
class="collapse link"
|
||||||
@click.prevent="$emit('input', !value)"
|
@click.prevent="value = !value"
|
||||||
>
|
>
|
||||||
<translate
|
<translate
|
||||||
v-if="isCollapsed"
|
v-if="value"
|
||||||
key="1"
|
|
||||||
translate-context="*/*/Button,Label"
|
translate-context="*/*/Button,Label"
|
||||||
>Expand</translate>
|
>
|
||||||
|
Expand
|
||||||
|
</translate>
|
||||||
<translate
|
<translate
|
||||||
v-else
|
v-else
|
||||||
key="2"
|
|
||||||
translate-context="*/*/Button,Label"
|
translate-context="*/*/Button,Label"
|
||||||
>Collapse</translate>
|
>
|
||||||
<i :class="[{down: !isCollapsed}, {right: isCollapsed}, 'angle', 'icon']" />
|
Collapse
|
||||||
|
</translate>
|
||||||
|
<i :class="[{ down: !value, right: value }, 'angle', 'icon']" />
|
||||||
</a>
|
</a>
|
||||||
</template>
|
</template>
|
||||||
<script>
|
|
||||||
|
|
||||||
export default {
|
|
||||||
props: {
|
|
||||||
value: { type: Boolean, required: true }
|
|
||||||
},
|
|
||||||
computed: {
|
|
||||||
isCollapsed () {
|
|
||||||
return this.value
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
|
@ -124,9 +124,8 @@ export default {
|
||||||
await this.loadPreview()
|
await this.loadPreview()
|
||||||
}
|
}
|
||||||
if (!v) {
|
if (!v) {
|
||||||
this.$nextTick(() => {
|
await this.$nextTick()
|
||||||
this.$refs.textarea.focus()
|
this.$refs.textarea.focus()
|
||||||
})
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
|
@ -1,3 +1,23 @@
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { useClipboard, useVModel } from '@vueuse/core'
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
modelValue: string
|
||||||
|
buttonClasses?: string
|
||||||
|
id?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
const props = withDefaults(defineProps<Props>(), {
|
||||||
|
buttonClasses: 'accent',
|
||||||
|
id: 'copy-input'
|
||||||
|
})
|
||||||
|
|
||||||
|
const emit = defineEmits(['update:modelValue'])
|
||||||
|
const value = useVModel(props, 'modelValue', emit)
|
||||||
|
|
||||||
|
const { copy, isSupported: canCopy, copied } = useClipboard({ source: value, copiedDuring: 5000 })
|
||||||
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="ui fluid action input component-copy-input">
|
<div class="ui fluid action input component-copy-input">
|
||||||
<p
|
<p
|
||||||
|
@ -10,15 +30,15 @@
|
||||||
</p>
|
</p>
|
||||||
<input
|
<input
|
||||||
:id="id"
|
:id="id"
|
||||||
ref="input"
|
v-model="value"
|
||||||
:name="id"
|
:name="id"
|
||||||
:value="value"
|
|
||||||
type="text"
|
type="text"
|
||||||
readonly
|
readonly
|
||||||
>
|
>
|
||||||
<button
|
<button
|
||||||
:class="['ui', buttonClasses, 'right', 'labeled', 'icon', 'button']"
|
:class="['ui', buttonClasses, 'right', 'labeled', 'icon', 'button']"
|
||||||
@click="copy"
|
:disabled="!canCopy || undefined"
|
||||||
|
@click="copy()"
|
||||||
>
|
>
|
||||||
<i class="copy icon" />
|
<i class="copy icon" />
|
||||||
<translate translate-context="*/*/Button.Label/Short, Verb">
|
<translate translate-context="*/*/Button.Label/Short, Verb">
|
||||||
|
@ -27,32 +47,3 @@
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<script>
|
|
||||||
export default {
|
|
||||||
props: {
|
|
||||||
value: { type: String, required: true },
|
|
||||||
buttonClasses: { type: String, default: 'accent' },
|
|
||||||
id: { type: String, default: 'copy-input' }
|
|
||||||
},
|
|
||||||
data () {
|
|
||||||
return {
|
|
||||||
copied: false,
|
|
||||||
timeout: null
|
|
||||||
}
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
copy () {
|
|
||||||
if (this.timeout) {
|
|
||||||
clearTimeout(this.timeout)
|
|
||||||
}
|
|
||||||
this.$refs.input.select()
|
|
||||||
document.execCommand('Copy')
|
|
||||||
const self = this
|
|
||||||
self.copied = true
|
|
||||||
this.timeout = setTimeout(() => {
|
|
||||||
self.copied = false
|
|
||||||
}, 5000)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
|
@ -23,7 +23,7 @@ onMounted(() => {
|
||||||
...props.message
|
...props.message
|
||||||
}
|
}
|
||||||
|
|
||||||
// @ts-ignore
|
// @ts-expect-error
|
||||||
$('body').toast(params)
|
$('body').toast(params)
|
||||||
$('.ui.toast.visible').last().attr('role', 'alert')
|
$('.ui.toast.visible').last().attr('role', 'alert')
|
||||||
})
|
})
|
||||||
|
|
|
@ -1,12 +1,53 @@
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { computed, ref } from 'vue'
|
||||||
|
import { useGettext } from 'vue3-gettext'
|
||||||
|
import { useClipboard, useVModel } from '@vueuse/core'
|
||||||
|
import { useStore } from 'vuex'
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
modelValue: string
|
||||||
|
defaultShow?: boolean
|
||||||
|
copyButton?: boolean
|
||||||
|
fieldId: string
|
||||||
|
}
|
||||||
|
|
||||||
|
const props = withDefaults(defineProps<Props>(), {
|
||||||
|
defaultShow: false,
|
||||||
|
copyButton: false
|
||||||
|
})
|
||||||
|
|
||||||
|
const emit = defineEmits(['update:modelValue'])
|
||||||
|
const value = useVModel(props, 'modelValue', emit)
|
||||||
|
|
||||||
|
const showPassword = ref(props.defaultShow)
|
||||||
|
|
||||||
|
const { $pgettext } = useGettext()
|
||||||
|
const labels = computed(() => ({
|
||||||
|
title: $pgettext('Content/Settings/Button.Tooltip/Verb', 'Show/hide password'),
|
||||||
|
copy: $pgettext('*/*/Button.Label/Short, Verb', 'Copy')
|
||||||
|
}))
|
||||||
|
|
||||||
|
const passwordInputType = computed(() => showPassword.value ? 'text' : 'password')
|
||||||
|
|
||||||
|
const store = useStore()
|
||||||
|
const { isSupported: canCopy, copy } = useClipboard({ source: value })
|
||||||
|
const copyPassword = () => {
|
||||||
|
copy()
|
||||||
|
store.commit('ui/addMessage', {
|
||||||
|
content: $pgettext('Content/*/Paragraph', 'Text copied to clipboard!'),
|
||||||
|
date: new Date()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="ui fluid action input">
|
<div class="ui fluid action input">
|
||||||
<input
|
<input
|
||||||
:id="fieldId"
|
:id="fieldId"
|
||||||
|
v-model="value"
|
||||||
required
|
required
|
||||||
name="password"
|
name="password"
|
||||||
:type="passwordInputType"
|
:type="passwordInputType"
|
||||||
:value="value"
|
|
||||||
@input="$emit('input', $event.target.value)"
|
|
||||||
>
|
>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
|
@ -17,7 +58,7 @@
|
||||||
<i class="eye icon" />
|
<i class="eye icon" />
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
v-if="copyButton"
|
v-if="copyButton && canCopy"
|
||||||
type="button"
|
type="button"
|
||||||
class="ui icon button"
|
class="ui icon button"
|
||||||
:title="labels.copy"
|
:title="labels.copy"
|
||||||
|
@ -27,62 +68,3 @@
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<script>
|
|
||||||
export default {
|
|
||||||
props: {
|
|
||||||
value: { type: String, required: true },
|
|
||||||
defaultShow: { type: Boolean, default: false },
|
|
||||||
copyButton: { type: Boolean, default: false },
|
|
||||||
fieldId: { type: String, required: true }
|
|
||||||
},
|
|
||||||
data () {
|
|
||||||
return {
|
|
||||||
showPassword: this.defaultShow || false
|
|
||||||
}
|
|
||||||
},
|
|
||||||
computed: {
|
|
||||||
labels () {
|
|
||||||
return {
|
|
||||||
title: this.$pgettext(
|
|
||||||
'Content/Settings/Button.Tooltip/Verb',
|
|
||||||
'Show/hide password'
|
|
||||||
),
|
|
||||||
copy: this.$pgettext('*/*/Button.Label/Short, Verb', 'Copy')
|
|
||||||
}
|
|
||||||
},
|
|
||||||
passwordInputType () {
|
|
||||||
if (this.showPassword) {
|
|
||||||
return 'text'
|
|
||||||
}
|
|
||||||
return 'password'
|
|
||||||
}
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
copyPassword () {
|
|
||||||
try {
|
|
||||||
this._copyStringToClipboard(this.value)
|
|
||||||
this.$store.commit('ui/addMessage', {
|
|
||||||
content: this.$pgettext(
|
|
||||||
'Content/*/Paragraph',
|
|
||||||
'Text copied to clipboard!'
|
|
||||||
),
|
|
||||||
date: new Date()
|
|
||||||
})
|
|
||||||
} catch ($e) {
|
|
||||||
console.error('Cannot copy', $e)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
_copyStringToClipboard (str) {
|
|
||||||
// cf https://techoverflow.net/2018/03/30/copying-strings-to-the-clipboard-using-pure-javascript/
|
|
||||||
const el = document.createElement('textarea')
|
|
||||||
el.value = str
|
|
||||||
el.setAttribute('readonly', '')
|
|
||||||
el.style = { position: 'absolute', left: '-9999px' }
|
|
||||||
document.body.appendChild(el)
|
|
||||||
el.select()
|
|
||||||
document.execCommand('copy')
|
|
||||||
document.body.removeChild(el)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
|
@ -68,7 +68,7 @@
|
||||||
</a>
|
</a>
|
||||||
<modal
|
<modal
|
||||||
v-if="checkResult"
|
v-if="checkResult"
|
||||||
v-model::show="showCandidadesModal"
|
v-model:show="showCandidadesModal"
|
||||||
>
|
>
|
||||||
<h4 class="header">
|
<h4 class="header">
|
||||||
<translate translate-context="Popup/Radio/Title/Noun">
|
<translate translate-context="Popup/Radio/Title/Noun">
|
||||||
|
|
|
@ -47,8 +47,8 @@
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
<report-category-dropdown
|
<report-category-dropdown
|
||||||
:model-value="obj.type"
|
v-model="obj.type"
|
||||||
@update:modelValue="update({type: $event})"
|
@update:model-value="update({ type: $event })"
|
||||||
>
|
>
|
||||||
 
|
 
|
||||||
<action-feedback :is-loading="updating.type" />
|
<action-feedback :is-loading="updating.type" />
|
||||||
|
|
|
@ -20,7 +20,7 @@
|
||||||
v-html="notificationData.message"
|
v-html="notificationData.message"
|
||||||
/>
|
/>
|
||||||
</router-link>
|
</router-link>
|
||||||
<template
|
<div
|
||||||
v-else
|
v-else
|
||||||
v-html="notificationData.message"
|
v-html="notificationData.message"
|
||||||
/>
|
/>
|
||||||
|
|
|
@ -27,6 +27,7 @@ const show = useVModel(props, 'show', emit)
|
||||||
|
|
||||||
const control = ref()
|
const control = ref()
|
||||||
const initModal = () => {
|
const initModal = () => {
|
||||||
|
// @ts-expect-error
|
||||||
control.value = $(modal.value).modal({
|
control.value = $(modal.value).modal({
|
||||||
duration: 100,
|
duration: 100,
|
||||||
onApprove: () => emit('approved'),
|
onApprove: () => emit('approved'),
|
||||||
|
|
|
@ -1,7 +1,9 @@
|
||||||
import EmbedFrame from './EmbedFrame.vue'
|
import EmbedFrame from './EmbedFrame.vue'
|
||||||
import VuePlyr from 'vue-plyr'
|
|
||||||
import { createApp } from 'vue'
|
import { createApp } from 'vue'
|
||||||
|
|
||||||
|
// @ts-expect-error
|
||||||
|
import VuePlyr from 'vue-plyr'
|
||||||
|
|
||||||
const app = createApp(EmbedFrame)
|
const app = createApp(EmbedFrame)
|
||||||
app.use(VuePlyr)
|
app.use(VuePlyr)
|
||||||
app.mount('#app')
|
app.mount('#app')
|
||||||
|
|
|
@ -7,7 +7,7 @@ export const install: InitModule = ({ app, store }) => {
|
||||||
})
|
})
|
||||||
|
|
||||||
app.directive('dropdown', function (el, binding) {
|
app.directive('dropdown', function (el, binding) {
|
||||||
// @ts-ignore
|
// @ts-expect-error
|
||||||
jQuery(el).dropdown({
|
jQuery(el).dropdown({
|
||||||
selectOnKeydown: false,
|
selectOnKeydown: false,
|
||||||
action (text: string, value: string, $el: JQuery<HTMLElement>) {
|
action (text: string, value: string, $el: JQuery<HTMLElement>) {
|
||||||
|
@ -15,7 +15,7 @@ export const install: InitModule = ({ app, store }) => {
|
||||||
// works as expected
|
// works as expected
|
||||||
const button = $el[0]
|
const button = $el[0]
|
||||||
button.click()
|
button.click()
|
||||||
// @ts-ignore
|
// @ts-expect-error
|
||||||
jQuery(el).find('.ui.dropdown').dropdown('hide')
|
jQuery(el).find('.ui.dropdown').dropdown('hide')
|
||||||
},
|
},
|
||||||
...(binding.value || {})
|
...(binding.value || {})
|
||||||
|
|
|
@ -3,10 +3,12 @@ import store from '~/store'
|
||||||
import { configureCompat, createApp, defineAsyncComponent, h } from 'vue'
|
import { configureCompat, createApp, defineAsyncComponent, h } from 'vue'
|
||||||
import useLogger from '~/composables/useLogger'
|
import useLogger from '~/composables/useLogger'
|
||||||
import useTheme from '~/composables/useTheme'
|
import useTheme from '~/composables/useTheme'
|
||||||
|
|
||||||
|
// NOTE: Set the theme as fast as possible
|
||||||
useTheme()
|
useTheme()
|
||||||
|
|
||||||
configureCompat({
|
configureCompat({
|
||||||
RENDER_FUNCTION: false,
|
RENDER_FUNCTION: false
|
||||||
// COMPONENT_V_MODEL: false
|
// COMPONENT_V_MODEL: false
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -32,7 +34,7 @@ const app = createApp({
|
||||||
app.use(router)
|
app.use(router)
|
||||||
app.use(store)
|
app.use(store)
|
||||||
|
|
||||||
const modules: Promise<unknown>[] = []
|
const modules: Array<Promise<unknown>> = []
|
||||||
for (const module of Object.values(import.meta.globEager('./init/*.ts'))) {
|
for (const module of Object.values(import.meta.globEager('./init/*.ts'))) {
|
||||||
modules.push(module.install?.({
|
modules.push(module.install?.({
|
||||||
app,
|
app,
|
||||||
|
@ -50,8 +52,10 @@ Promise.all(modules).finally(() => {
|
||||||
// TODO (wvffle): Migrate to pinia
|
// TODO (wvffle): Migrate to pinia
|
||||||
// TODO (wvffle): Remove global Vue (Only vuex files affected)
|
// TODO (wvffle): Remove global Vue (Only vuex files affected)
|
||||||
// TODO (wvffle): Remove shims-vue2.d.ts
|
// TODO (wvffle): Remove shims-vue2.d.ts
|
||||||
|
// TODO (wvffle): Replace $set and $delete with reactive()
|
||||||
// TODO (wvffle): Check for mixin merging: https://v3-migration.vuejs.org/breaking-changes/data-option.html#mixin-merge-behavior-change=
|
// TODO (wvffle): Check for mixin merging: https://v3-migration.vuejs.org/breaking-changes/data-option.html#mixin-merge-behavior-change=
|
||||||
// TODO (wvffle): Use emits options: https://v3-migration.vuejs.org/breaking-changes/emits-option.html
|
// TODO (wvffle): Use emits options: https://v3-migration.vuejs.org/breaking-changes/emits-option.html
|
||||||
// TODO (wvffle): Migrate to new v-model: https://v3-migration.vuejs.org/breaking-changes/v-model.html
|
// TODO (wvffle): Migrate to new v-model: https://v3-migration.vuejs.org/breaking-changes/v-model.html
|
||||||
// TODO (wvffle): Migrate to <script setup>
|
// TODO (wvffle): Migrate to <script setup>
|
||||||
// TODO (wvffle): Replace `from '(../)+` with `from '~/`
|
// TODO (wvffle): Replace `from '(../)+` with `from '~/`
|
||||||
|
// TODO (wvffle): Remove `allowJs` from tsconfig.json
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
import { createRouter, createWebHistory } from 'vue-router'
|
import { createRouter, createWebHistory, NavigationGuardNext, RouteLocationNormalized } from 'vue-router'
|
||||||
import store from '~/store'
|
import store from '~/store'
|
||||||
|
|
||||||
function adminPermissions (to, from, next) {
|
function adminPermissions (to: RouteLocationNormalized, from: RouteLocationNormalized, next: NavigationGuardNext) {
|
||||||
if (store.state.auth.authenticated === true && store.state.auth.availablePermissions.settings === true) {
|
if (store.state.auth.authenticated && store.state.auth.availablePermissions.settings) {
|
||||||
next()
|
next()
|
||||||
} else {
|
} else {
|
||||||
console.log('Not authenticated. Redirecting to library.')
|
console.log('Not authenticated. Redirecting to library.')
|
||||||
|
@ -10,8 +10,8 @@ function adminPermissions (to, from, next) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function moderatorPermissions (to, from, next) {
|
function moderatorPermissions (to: RouteLocationNormalized, from: RouteLocationNormalized, next: NavigationGuardNext) {
|
||||||
if (store.state.auth.authenticated === true && store.state.auth.availablePermissions.moderation === true) {
|
if (store.state.auth.authenticated && store.state.auth.availablePermissions.moderation) {
|
||||||
next()
|
next()
|
||||||
} else {
|
} else {
|
||||||
console.log('Not authenticated. Redirecting to library.')
|
console.log('Not authenticated. Redirecting to library.')
|
||||||
|
@ -19,8 +19,8 @@ function moderatorPermissions (to, from, next) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function libraryPermissions (to, from, next) {
|
function libraryPermissions (to: RouteLocationNormalized, from: RouteLocationNormalized, next: NavigationGuardNext) {
|
||||||
if (store.state.auth.authenticated === true && store.state.auth.availablePermissions.library === true) {
|
if (store.state.auth.authenticated && store.state.auth.availablePermissions.library) {
|
||||||
next()
|
next()
|
||||||
} else {
|
} else {
|
||||||
console.log('Not authenticated. Redirecting to library.')
|
console.log('Not authenticated. Redirecting to library.')
|
||||||
|
@ -126,7 +126,7 @@ export default createRouter({
|
||||||
initialId: route.query.id,
|
initialId: route.query.id,
|
||||||
initialType: route.query.type || 'artists',
|
initialType: route.query.type || 'artists',
|
||||||
initialQuery: route.query.q,
|
initialQuery: route.query.q,
|
||||||
initialPage: parseInt(route.query.page) || 1
|
initialPage: parseInt(route.query.page as string) || 1
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
|
@ -0,0 +1,6 @@
|
||||||
|
/* eslint-disable */
|
||||||
|
declare module '*.vue' {
|
||||||
|
import type { DefineComponent } from 'vue'
|
||||||
|
const component: DefineComponent<{}, {}, any>
|
||||||
|
export default component
|
||||||
|
}
|
|
@ -1,4 +1,4 @@
|
||||||
import { createStore, Store } from 'vuex'
|
import { createStore } from 'vuex'
|
||||||
import createPersistedState from 'vuex-persistedstate'
|
import createPersistedState from 'vuex-persistedstate'
|
||||||
|
|
||||||
import favorites from './favorites'
|
import favorites from './favorites'
|
||||||
|
@ -13,7 +13,7 @@ import player from './player'
|
||||||
import playlists from './playlists'
|
import playlists from './playlists'
|
||||||
import ui from './ui'
|
import ui from './ui'
|
||||||
|
|
||||||
export default <Store<any>> createStore({
|
export default createStore({
|
||||||
modules: {
|
modules: {
|
||||||
ui,
|
ui,
|
||||||
auth,
|
auth,
|
||||||
|
@ -69,7 +69,7 @@ export default <Store<any>> createStore({
|
||||||
return {
|
return {
|
||||||
queue: {
|
queue: {
|
||||||
currentIndex: state.queue.currentIndex,
|
currentIndex: state.queue.currentIndex,
|
||||||
tracks: state.queue.tracks.map(track => {
|
tracks: state.queue.tracks.map((track: any) => {
|
||||||
// we keep only valuable fields to make the cache lighter and avoid
|
// we keep only valuable fields to make the cache lighter and avoid
|
||||||
// cyclic value serialization errors
|
// cyclic value serialization errors
|
||||||
const artist = {
|
const artist = {
|
||||||
|
@ -83,7 +83,8 @@ export default <Store<any>> createStore({
|
||||||
mbid: track.mbid,
|
mbid: track.mbid,
|
||||||
uploads: track.uploads,
|
uploads: track.uploads,
|
||||||
listen_url: track.listen_url,
|
listen_url: track.listen_url,
|
||||||
artist: artist
|
artist: artist,
|
||||||
|
album: {}
|
||||||
}
|
}
|
||||||
if (track.album) {
|
if (track.album) {
|
||||||
data.album = {
|
data.album = {
|
||||||
|
|
|
@ -82,6 +82,23 @@ export interface FileSystem {
|
||||||
content: FSEntry[]
|
content: FSEntry[]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Form stuff
|
||||||
|
export interface FormHelpText {
|
||||||
|
content_type: string
|
||||||
|
text?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface FormField {
|
||||||
|
label: string
|
||||||
|
input_type: 'short_text' | 'long_text'
|
||||||
|
required: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Form {
|
||||||
|
fields: FormField[]
|
||||||
|
help_text: FormHelpText
|
||||||
|
}
|
||||||
|
|
||||||
// Yet uncategorized stuff
|
// Yet uncategorized stuff
|
||||||
export interface Actor {
|
export interface Actor {
|
||||||
preferred_username: string
|
preferred_username: string
|
||||||
|
|
|
@ -20,15 +20,15 @@ export function parseAPIErrors (responseData: APIErrorResponse, parentField?: st
|
||||||
|
|
||||||
const value = responseData[field]
|
const value = responseData[field]
|
||||||
if (Array.isArray(value)) {
|
if (Array.isArray(value)) {
|
||||||
const values = value as string[]
|
const values = value
|
||||||
errors.push(...values.map(err => {
|
errors.push(...values.map(err => {
|
||||||
return err.toLocaleLowerCase().includes('this field ')
|
return err.toLocaleLowerCase().includes('this field ')
|
||||||
? `${fieldName}: ${err}`
|
? `${fieldName}: ${err}`
|
||||||
: err
|
: err
|
||||||
}))
|
}))
|
||||||
} else if (value as APIErrorResponse) {
|
} else if (value) {
|
||||||
// nested errors
|
// nested errors
|
||||||
const nestedErrors = parseAPIErrors(value as APIErrorResponse, fieldName)
|
const nestedErrors = parseAPIErrors(value, fieldName)
|
||||||
errors.push(...nestedErrors)
|
errors.push(...nestedErrors)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -63,3 +63,12 @@ export function getDomain (url: string) {
|
||||||
parser.href = url
|
parser.href = url
|
||||||
return parser.hostname
|
return parser.hostname
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function arrayMove (arr: unknown[], oldIndex: number, newIndex: number) {
|
||||||
|
if (newIndex >= arr.length) {
|
||||||
|
arr.push(...Array(newIndex - arr.length + 1))
|
||||||
|
}
|
||||||
|
|
||||||
|
arr.splice(newIndex, 0, arr.splice(oldIndex, 1)[0])
|
||||||
|
return arr
|
||||||
|
}
|
||||||
|
|
|
@ -52,7 +52,7 @@
|
||||||
:all="true"
|
:all="true"
|
||||||
:label="true"
|
:label="true"
|
||||||
:model-value="getTokenValue('category', '')"
|
:model-value="getTokenValue('category', '')"
|
||||||
@update:modelValue="addSearchToken('category', $event)"
|
@update:model-value="addSearchToken('category', $event)"
|
||||||
/>
|
/>
|
||||||
<div class="field">
|
<div class="field">
|
||||||
<label for="reports-ordering"><translate translate-context="Content/Search/Dropdown.Label/Noun">Ordering</translate></label>
|
<label for="reports-ordering"><translate translate-context="Content/Search/Dropdown.Label/Noun">Ordering</translate></label>
|
||||||
|
|
|
@ -38,7 +38,7 @@ if (store.state.auth.authenticated) {
|
||||||
Log in to your Funkwhale account
|
Log in to your Funkwhale account
|
||||||
</translate>
|
</translate>
|
||||||
</h2>
|
</h2>
|
||||||
<login-form :next="redirectTo" />
|
<login-form :next="next" />
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
</main>
|
</main>
|
||||||
|
|
|
@ -1,26 +1,30 @@
|
||||||
{
|
{
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"baseUrl": ".",
|
"baseUrl": ".",
|
||||||
"module": "ESNext",
|
"target": "esnext",
|
||||||
"target": "ESNext",
|
"useDefineForClassFields": true,
|
||||||
"lib": ["DOM", "ESNext", "WebWorker"],
|
"module": "esnext",
|
||||||
"strict": true,
|
|
||||||
"esModuleInterop": true,
|
|
||||||
"jsx": "preserve",
|
|
||||||
"skipLibCheck": true,
|
|
||||||
"moduleResolution": "node",
|
"moduleResolution": "node",
|
||||||
|
"strict": true,
|
||||||
|
"jsx": "preserve",
|
||||||
|
"sourceMap": true,
|
||||||
"resolveJsonModule": true,
|
"resolveJsonModule": true,
|
||||||
|
"isolatedModules": true,
|
||||||
|
"esModuleInterop": true,
|
||||||
|
"skipLibCheck": true,
|
||||||
|
"lib": ["dom", "esnext", "webworker"],
|
||||||
|
|
||||||
|
"allowJs": true,
|
||||||
|
|
||||||
"noUnusedLocals": true,
|
"noUnusedLocals": true,
|
||||||
"strictNullChecks": true,
|
"strictNullChecks": true,
|
||||||
"forceConsistentCasingInFileNames": true,
|
"forceConsistentCasingInFileNames": true,
|
||||||
"typeRoots": [
|
"typeRoots": ["node_modules/@types"],
|
||||||
"node_modules/@types"
|
|
||||||
],
|
|
||||||
"types": [
|
"types": [
|
||||||
"vite/client",
|
"vite/client",
|
||||||
"vue/ref-macros",
|
"vue/ref-macros",
|
||||||
"unplugin-vue2-script-setup/types",
|
"vue-gettext/types",
|
||||||
"vue-gettext/types"
|
"vite-plugin-pwa/client"
|
||||||
],
|
],
|
||||||
"paths": {
|
"paths": {
|
||||||
"~/*": ["src/*"]
|
"~/*": ["src/*"]
|
||||||
|
@ -29,5 +33,6 @@
|
||||||
"vueCompilerOptions": {
|
"vueCompilerOptions": {
|
||||||
"experimentalCompatMode": 2
|
"experimentalCompatMode": 2
|
||||||
},
|
},
|
||||||
"include": ["src/*.d.ts", "src/**/*.ts", "src/**/*.vue"]
|
"include": ["src/**/*.d.ts", "src/**/*.ts", "src/**/*.vue"],
|
||||||
|
"references": [{ "path": "./tsconfig.node.json" }]
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,8 @@
|
||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"composite": true,
|
||||||
|
"module": "esnext",
|
||||||
|
"moduleResolution": "node"
|
||||||
|
},
|
||||||
|
"include": ["vite.config.ts"]
|
||||||
|
}
|
Loading…
Reference in New Issue