Migrate a couple of components to new v-model and cleanup linting stuff

This commit is contained in:
Kasper Seweryn 2022-05-01 21:57:22 +02:00 committed by Georg Krause
parent 16d437be62
commit 2f80e0935f
27 changed files with 260 additions and 293 deletions

View File

@ -34,7 +34,8 @@ module.exports = {
// and gettext for vue 2
'@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-empty-function': 'off',
'@typescript-eslint/no-unused-vars': 'off',

View File

@ -11,6 +11,7 @@
"serve": "vite preview",
"test:unit": "jest",
"lint": "eslint --ext .ts,.js,.vue src",
"lint:tsc": "vue-tsc --noEmit",
"fix-fomantic-css": "scripts/fix-fomantic-css.sh",
"i18n-compile": "scripts/i18n-compile.sh",
"i18n-extract": "scripts/i18n-extract.sh",
@ -40,6 +41,7 @@
"vue-gettext": "2.1.12",
"vue-plyr": "7.0.0",
"vue-router": "4.0.14",
"vue-tsc": "0.34.7",
"vue-upload-component": "2.8.22",
"vue3-gettext": "2.2.0-alpha.1",
"vue3-lazyload": "0.2.5-beta",
@ -49,9 +51,6 @@
"vuex-router-sync": "5.0.0"
},
"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/jquery": "3.5.14",
"@types/lodash-es": "4.17.6",
@ -62,9 +61,7 @@
"@vue/eslint-config-standard": "6.1.0",
"@vue/eslint-config-typescript": "10.0.0",
"@vue/test-utils": "1.3.0",
"autoprefixer": "10.4.7",
"babel-core": "7.0.0-bridge.0",
"babel-jest": "27.5.1",
"autoprefixer": "10.4.4",
"chai": "4.3.6",
"easygettext": "2.17.0",
"eslint": "8.11.0",
@ -74,7 +71,6 @@
"eslint-plugin-node": "11.1.0",
"eslint-plugin-promise": "6.0.0",
"eslint-plugin-vue": "7.20.0",
"glob-all": "3.3.0",
"jest-cli": "27.5.1",
"moxios": "0.4.0",
"sinon": "13.0.2",
@ -91,27 +87,6 @@
"resolutions": {
"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": {
"moduleFileExtensions": [
"ts",

View File

@ -154,8 +154,8 @@
</template>
<user-modal
v-model:show="showUserModal"
@showThemeModalEvent="showThemeModal=true"
@showLanguageModalEvent="showLanguageModal=true"
@show-theme-modal-event="showThemeModal=true"
@show-language-modal-event="showLanguageModal=true"
/>
<modal
ref="languageModal"

View File

@ -56,9 +56,8 @@
/>
<signup-form-builder
v-else-if="setting.fieldType === 'formBuilder'"
:value="values[setting.identifier]"
v-model="values[setting.identifier]"
:signup-approval-enabled="values.moderation__signup_approval_enabled"
@input="set(setting.identifier, $event)"
/>
<input
v-else-if="setting.field.widget.class === 'PasswordInput'"
@ -238,12 +237,6 @@ export default {
self.isLoading = false
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)
}
}
}

View File

@ -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>
<div>
<div class="ui top attached tabular menu">
@ -23,7 +82,7 @@
class="ui bottom attached segment"
>
<signup-form
:customization="local"
:customization="value"
:signup-approval-enabled="signupApprovalEnabled"
:fetch-description-html="true"
/>
@ -43,10 +102,9 @@
</translate>
</p>
<content-form
v-model="value.help_text.text"
field-id="help-text"
:permissive="true"
:value="(local.help_text || {}).text"
@input="update('help_text.text', $event)"
/>
</div>
<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.
</translate>
</p>
<table v-if="local.fields.length > 0">
<table v-if="value.fields.length > 0">
<thead>
<tr>
<th>
@ -80,8 +138,9 @@
</tr>
</thead>
<tbody>
<!-- TODO (wvffle): Add random _id as :key -->
<tr
v-for="(field, idx) in local.fields"
v-for="(field, idx) in value.fields"
:key="idx"
>
<td>
@ -124,14 +183,14 @@
:disabled="idx === 0 || null"
role="button"
:title="labels.up"
:class="['up', 'arrow', {disabled: idx === 0}, 'icon']"
:class="['up', 'arrow', { disabled: idx === 0 }, 'icon']"
@click="move(idx, -1)"
/>
<i
:disabled="idx >= local.fields.length - 1 || null"
:disabled="idx >= value.fields.length - 1 || null"
role="button"
: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)"
/>
<i
@ -146,7 +205,7 @@
</table>
<div class="ui hidden divider" />
<button
v-if="local.fields.length < maxFields"
v-if="value.fields.length < maxFields"
class="ui basic button"
@click.stop.prevent="addField"
>
@ -159,90 +218,3 @@
<div class="ui hidden divider" />
</div>
</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>

View File

@ -170,7 +170,7 @@ export default {
defaultInvitation: { type: String, required: false, default: null },
next: { type: String, default: '/' },
buttonClasses: { type: String, default: 'success' },
customization: { type: Object, default: null },
customization: { type: Object, default: null }, // ts type: Form
fetchDescriptionHtml: { type: Boolean, default: false },
signupApprovalEnabled: { type: Boolean, default: null, required: false }
},

View File

@ -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>
<a
role="button"
class="collapse link"
@click.prevent="$emit('input', !value)"
@click.prevent="value = !value"
>
<translate
v-if="isCollapsed"
key="1"
v-if="value"
translate-context="*/*/Button,Label"
>Expand</translate>
>
Expand
</translate>
<translate
v-else
key="2"
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>
</template>
<script>
export default {
props: {
value: { type: Boolean, required: true }
},
computed: {
isCollapsed () {
return this.value
}
}
}
</script>

View File

@ -124,9 +124,8 @@ export default {
await this.loadPreview()
}
if (!v) {
this.$nextTick(() => {
this.$refs.textarea.focus()
})
await this.$nextTick()
this.$refs.textarea.focus()
}
}
},

View File

@ -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>
<div class="ui fluid action input component-copy-input">
<p
@ -10,15 +30,15 @@
</p>
<input
:id="id"
ref="input"
v-model="value"
:name="id"
:value="value"
type="text"
readonly
>
<button
:class="['ui', buttonClasses, 'right', 'labeled', 'icon', 'button']"
@click="copy"
:disabled="!canCopy || undefined"
@click="copy()"
>
<i class="copy icon" />
<translate translate-context="*/*/Button.Label/Short, Verb">
@ -27,32 +47,3 @@
</button>
</div>
</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>

View File

@ -23,7 +23,7 @@ onMounted(() => {
...props.message
}
// @ts-ignore
// @ts-expect-error
$('body').toast(params)
$('.ui.toast.visible').last().attr('role', 'alert')
})

View File

@ -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>
<div class="ui fluid action input">
<input
:id="fieldId"
v-model="value"
required
name="password"
:type="passwordInputType"
:value="value"
@input="$emit('input', $event.target.value)"
>
<button
type="button"
@ -17,7 +58,7 @@
<i class="eye icon" />
</button>
<button
v-if="copyButton"
v-if="copyButton && canCopy"
type="button"
class="ui icon button"
:title="labels.copy"
@ -27,62 +68,3 @@
</button>
</div>
</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>

View File

@ -68,7 +68,7 @@
</a>
<modal
v-if="checkResult"
v-model::show="showCandidadesModal"
v-model:show="showCandidadesModal"
>
<h4 class="header">
<translate translate-context="Popup/Radio/Title/Noun">

View File

@ -47,8 +47,8 @@
</td>
<td>
<report-category-dropdown
:model-value="obj.type"
@update:modelValue="update({type: $event})"
v-model="obj.type"
@update:model-value="update({ type: $event })"
>
&#32;
<action-feedback :is-loading="updating.type" />

View File

@ -20,7 +20,7 @@
v-html="notificationData.message"
/>
</router-link>
<template
<div
v-else
v-html="notificationData.message"
/>

View File

@ -27,6 +27,7 @@ const show = useVModel(props, 'show', emit)
const control = ref()
const initModal = () => {
// @ts-expect-error
control.value = $(modal.value).modal({
duration: 100,
onApprove: () => emit('approved'),

View File

@ -1,7 +1,9 @@
import EmbedFrame from './EmbedFrame.vue'
import VuePlyr from 'vue-plyr'
import { createApp } from 'vue'
// @ts-expect-error
import VuePlyr from 'vue-plyr'
const app = createApp(EmbedFrame)
app.use(VuePlyr)
app.mount('#app')

View File

@ -7,7 +7,7 @@ export const install: InitModule = ({ app, store }) => {
})
app.directive('dropdown', function (el, binding) {
// @ts-ignore
// @ts-expect-error
jQuery(el).dropdown({
selectOnKeydown: false,
action (text: string, value: string, $el: JQuery<HTMLElement>) {
@ -15,7 +15,7 @@ export const install: InitModule = ({ app, store }) => {
// works as expected
const button = $el[0]
button.click()
// @ts-ignore
// @ts-expect-error
jQuery(el).find('.ui.dropdown').dropdown('hide')
},
...(binding.value || {})

View File

@ -3,10 +3,12 @@ import store from '~/store'
import { configureCompat, createApp, defineAsyncComponent, h } from 'vue'
import useLogger from '~/composables/useLogger'
import useTheme from '~/composables/useTheme'
// NOTE: Set the theme as fast as possible
useTheme()
configureCompat({
RENDER_FUNCTION: false,
RENDER_FUNCTION: false
// COMPONENT_V_MODEL: false
})
@ -32,7 +34,7 @@ const app = createApp({
app.use(router)
app.use(store)
const modules: Promise<unknown>[] = []
const modules: Array<Promise<unknown>> = []
for (const module of Object.values(import.meta.globEager('./init/*.ts'))) {
modules.push(module.install?.({
app,
@ -50,8 +52,10 @@ Promise.all(modules).finally(() => {
// TODO (wvffle): Migrate to pinia
// TODO (wvffle): Remove global Vue (Only vuex files affected)
// 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): 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 <script setup>
// TODO (wvffle): Replace `from '(../)+` with `from '~/`
// TODO (wvffle): Remove `allowJs` from tsconfig.json

View File

@ -1,8 +1,8 @@
import { createRouter, createWebHistory } from 'vue-router'
import { createRouter, createWebHistory, NavigationGuardNext, RouteLocationNormalized } from 'vue-router'
import store from '~/store'
function adminPermissions (to, from, next) {
if (store.state.auth.authenticated === true && store.state.auth.availablePermissions.settings === true) {
function adminPermissions (to: RouteLocationNormalized, from: RouteLocationNormalized, next: NavigationGuardNext) {
if (store.state.auth.authenticated && store.state.auth.availablePermissions.settings) {
next()
} else {
console.log('Not authenticated. Redirecting to library.')
@ -10,8 +10,8 @@ function adminPermissions (to, from, next) {
}
}
function moderatorPermissions (to, from, next) {
if (store.state.auth.authenticated === true && store.state.auth.availablePermissions.moderation === true) {
function moderatorPermissions (to: RouteLocationNormalized, from: RouteLocationNormalized, next: NavigationGuardNext) {
if (store.state.auth.authenticated && store.state.auth.availablePermissions.moderation) {
next()
} else {
console.log('Not authenticated. Redirecting to library.')
@ -19,8 +19,8 @@ function moderatorPermissions (to, from, next) {
}
}
function libraryPermissions (to, from, next) {
if (store.state.auth.authenticated === true && store.state.auth.availablePermissions.library === true) {
function libraryPermissions (to: RouteLocationNormalized, from: RouteLocationNormalized, next: NavigationGuardNext) {
if (store.state.auth.authenticated && store.state.auth.availablePermissions.library) {
next()
} else {
console.log('Not authenticated. Redirecting to library.')
@ -126,7 +126,7 @@ export default createRouter({
initialId: route.query.id,
initialType: route.query.type || 'artists',
initialQuery: route.query.q,
initialPage: parseInt(route.query.page) || 1
initialPage: parseInt(route.query.page as string) || 1
})
},
{

6
front/src/shims-vue.d.ts vendored Normal file
View File

@ -0,0 +1,6 @@
/* eslint-disable */
declare module '*.vue' {
import type { DefineComponent } from 'vue'
const component: DefineComponent<{}, {}, any>
export default component
}

View File

@ -1,4 +1,4 @@
import { createStore, Store } from 'vuex'
import { createStore } from 'vuex'
import createPersistedState from 'vuex-persistedstate'
import favorites from './favorites'
@ -13,7 +13,7 @@ import player from './player'
import playlists from './playlists'
import ui from './ui'
export default <Store<any>> createStore({
export default createStore({
modules: {
ui,
auth,
@ -69,7 +69,7 @@ export default <Store<any>> createStore({
return {
queue: {
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
// cyclic value serialization errors
const artist = {
@ -83,7 +83,8 @@ export default <Store<any>> createStore({
mbid: track.mbid,
uploads: track.uploads,
listen_url: track.listen_url,
artist: artist
artist: artist,
album: {}
}
if (track.album) {
data.album = {

View File

@ -82,6 +82,23 @@ export interface FileSystem {
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
export interface Actor {
preferred_username: string

View File

@ -20,15 +20,15 @@ export function parseAPIErrors (responseData: APIErrorResponse, parentField?: st
const value = responseData[field]
if (Array.isArray(value)) {
const values = value as string[]
const values = value
errors.push(...values.map(err => {
return err.toLocaleLowerCase().includes('this field ')
? `${fieldName}: ${err}`
: err
}))
} else if (value as APIErrorResponse) {
} else if (value) {
// nested errors
const nestedErrors = parseAPIErrors(value as APIErrorResponse, fieldName)
const nestedErrors = parseAPIErrors(value, fieldName)
errors.push(...nestedErrors)
}
}
@ -63,3 +63,12 @@ export function getDomain (url: string) {
parser.href = url
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
}

View File

@ -52,7 +52,7 @@
:all="true"
:label="true"
:model-value="getTokenValue('category', '')"
@update:modelValue="addSearchToken('category', $event)"
@update:model-value="addSearchToken('category', $event)"
/>
<div class="field">
<label for="reports-ordering"><translate translate-context="Content/Search/Dropdown.Label/Noun">Ordering</translate></label>

View File

@ -38,7 +38,7 @@ if (store.state.auth.authenticated) {
Log in to your Funkwhale account
</translate>
</h2>
<login-form :next="redirectTo" />
<login-form :next="next" />
</div>
</section>
</main>

View File

@ -1,26 +1,30 @@
{
"compilerOptions": {
"baseUrl": ".",
"module": "ESNext",
"target": "ESNext",
"lib": ["DOM", "ESNext", "WebWorker"],
"strict": true,
"esModuleInterop": true,
"jsx": "preserve",
"skipLibCheck": true,
"target": "esnext",
"useDefineForClassFields": true,
"module": "esnext",
"moduleResolution": "node",
"strict": true,
"jsx": "preserve",
"sourceMap": true,
"resolveJsonModule": true,
"isolatedModules": true,
"esModuleInterop": true,
"skipLibCheck": true,
"lib": ["dom", "esnext", "webworker"],
"allowJs": true,
"noUnusedLocals": true,
"strictNullChecks": true,
"forceConsistentCasingInFileNames": true,
"typeRoots": [
"node_modules/@types"
],
"typeRoots": ["node_modules/@types"],
"types": [
"vite/client",
"vue/ref-macros",
"unplugin-vue2-script-setup/types",
"vue-gettext/types"
"vue-gettext/types",
"vite-plugin-pwa/client"
],
"paths": {
"~/*": ["src/*"]
@ -29,5 +33,6 @@
"vueCompilerOptions": {
"experimentalCompatMode": 2
},
"include": ["src/*.d.ts", "src/**/*.ts", "src/**/*.vue"]
"include": ["src/**/*.d.ts", "src/**/*.ts", "src/**/*.vue"],
"references": [{ "path": "./tsconfig.node.json" }]
}

8
front/tsconfig.node.json Normal file
View File

@ -0,0 +1,8 @@
{
"compilerOptions": {
"composite": true,
"module": "esnext",
"moduleResolution": "node"
},
"include": ["vite.config.ts"]
}