Style: make code style consistent #186

Merged
jakob.scheid merged 29 commits from style/make-code-style-consistent into main 2026-08-09 13:09:54 +02:00
59 changed files with 9649 additions and 8795 deletions
Showing only changes of commit aecce32842 - Show all commits
+3 -4
View File
@@ -11,8 +11,8 @@ export default defineConfig([
languageOptions: {
globals: {
...globals.browser,
...globals.node
}
...globals.node,
},
},
rules: {
'eqeqeq': 'error',
@@ -44,8 +44,7 @@ export default defineConfig([
'object-shorthand': 'error',
'prefer-template': 'error',
}
},
},
pluginVue.configs['flat/essential'],
]);
+18 -8
View File
@@ -24,7 +24,19 @@ limitations under the License.
<noscript>
<style>
body {
font-family: 'Inter', system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif;
font-family:
'Inter',
system-ui,
-apple-system,
BlinkMacSystemFont,
'Segoe UI',
Roboto,
Oxygen,
Ubuntu,
Cantarell,
'Open Sans',
'Helvetica Neue',
sans-serif;
}
a {
@@ -40,17 +52,15 @@ limitations under the License.
</head>
<body>
<noscript>
<h1>
Seekra
</h1>
<p>
You need to enable JavaScript to use Seekra.
</p>
<h1>Seekra</h1>
<p>You need to enable JavaScript to use Seekra.</p>
<section>
<h2>Legal Content</h2>
<ul>
<li>
<a href="https://assets.seekra.jcloud-services.ddns.net/2026-08-04-76a067e/legal/legal_notice.html">
<a
href="https://assets.seekra.jcloud-services.ddns.net/2026-08-04-76a067e/legal/legal_notice.html"
>
Legal Notice
</a>
</li>
+4 -4
View File
@@ -1,7 +1,7 @@
{
"compilerOptions": {
"paths": {
"@/*": ["./src/*"]
"compilerOptions": {
"paths": {
"@/*": ["./src/*"]
}
}
}
}
+6957 -6957
View File
File diff suppressed because it is too large Load Diff
+38 -38
View File
@@ -1,40 +1,40 @@
{
"name": "frontend",
"private": true,
"version": "0.0.0",
"type": "module",
"scripts": {
"dev": "vite",
"build": "vite build",
"preview": "vite preview",
"test": "vitest",
"test:run": "vitest run",
"lint": "eslint .",
"lint:fix": "eslint . --fix",
"format": "prettier . --write",
"format:check": "prettier . --check"
},
"dependencies": {
"pinia": "^3.0.4",
"pinia-plugin-persistedstate": "^4.7.1",
"terser": "^5.47.1",
"vue": "^3.5.32",
"vue-i18n": "^11.4.4",
"vue-router": "^5.0.6"
},
"devDependencies": {
"@eslint/js": "^10.0.1",
"@pinia/testing": "^1.0.3",
"@vitejs/plugin-vue": "^6.0.6",
"@vue/test-utils": "^2.4.6",
"eslint": "^10.8.1",
"eslint-plugin-vue": "^10.10.0",
"generate-license-file": "^4.2.1",
"globals": "^17.9.0",
"jsdom": "^29.1.1",
"prettier": "^3.9.6",
"vite": "^8.0.10",
"vite-plugin-html": "^3.2.2",
"vitest": "^4.1.7"
}
"name": "frontend",
"private": true,
"version": "0.0.0",
"type": "module",
"scripts": {
"dev": "vite",
"build": "vite build",
"preview": "vite preview",
"test": "vitest",
"test:run": "vitest run",
"lint": "eslint .",
"lint:fix": "eslint . --fix",
"format": "prettier . --write",
"format:check": "prettier . --check"
},
"dependencies": {
"pinia": "^3.0.4",
"pinia-plugin-persistedstate": "^4.7.1",
"terser": "^5.47.1",
"vue": "^3.5.32",
"vue-i18n": "^11.4.4",
"vue-router": "^5.0.6"
},
"devDependencies": {
"@eslint/js": "^10.0.1",
"@pinia/testing": "^1.0.3",
"@vitejs/plugin-vue": "^6.0.6",
"@vue/test-utils": "^2.4.6",
"eslint": "^10.8.1",
"eslint-plugin-vue": "^10.10.0",
"generate-license-file": "^4.2.1",
"globals": "^17.9.0",
"jsdom": "^29.1.1",
"prettier": "^3.9.6",
"vite": "^8.0.10",
"vite-plugin-html": "^3.2.2",
"vitest": "^4.1.7"
}
}
+5 -16
View File
@@ -22,21 +22,16 @@ import { generateLicenseFile } from 'generate-license-file';
const packageJson = './package.json';
const packageJsonBackup = `${packageJson}~`;
await generateLicenseFile(
packageJson,
'THIRD_PARTY_LICENSES'
);
await generateLicenseFile(packageJson, 'THIRD_PARTY_LICENSES');
const pkg = JSON.parse(
await fs.readFile(packageJson, 'utf-8')
);
const pkg = JSON.parse(await fs.readFile(packageJson, 'utf-8'));
await fs.rename(packageJson, packageJsonBackup);
const devPackage = {
...pkg,
dependencies: pkg.devDependencies ?? {},
devDependencies: {}
devDependencies: {},
};
/*
@@ -45,14 +40,8 @@ const devPackage = {
https://github.com/TobyAndToby/generate-license-file/issues/752
*/
await fs.writeFile(
packageJson,
JSON.stringify(devPackage)
);
await fs.writeFile(packageJson, JSON.stringify(devPackage));
await generateLicenseFile(
packageJson,
'./THIRD_PARTY_LICENSES_DEV'
);
await generateLicenseFile(packageJson, './THIRD_PARTY_LICENSES_DEV');
await fs.rename(packageJsonBackup, packageJson);
+3 -2
View File
@@ -33,7 +33,7 @@ const updateColorScheme = function updateColorScheme(newColorScheme) {
document.body.style.setProperty(colorScheme, {
auto: 'normal',
dark: 'dark',
light: 'light'
light: 'light',
});
if (newColorScheme === 'dark') {
document.body.classList.add('dark');
@@ -72,7 +72,8 @@ onMounted(() => {
#app-wrapper {
--main-content-padding-x: 30px;
--main-content-padding-y: 40px;
--main-content-padding: var(--main-content-padding-y) var(--main-content-padding-x);
--main-content-padding: var(--main-content-padding-y)
var(--main-content-padding-x);
min-height: 100vh;
display: flex;
flex-direction: column;
+1 -1
View File
@@ -17,6 +17,6 @@ limitations under the License.
export const getAssetsUrl = function getAssetsUrl() {
return new URL(
`${import.meta.env.VITE_ASSETS_BUILD_ID}/`,
import.meta.env.VITE_ASSETS_BASE_URL
import.meta.env.VITE_ASSETS_BASE_URL,
).href;
};
@@ -26,19 +26,21 @@ const { getSetting, setSetting } = useSettings();
const colorScheme = computed(() => getSetting(['appearance', 'colorScheme']));
const colorSchemeNextMapper = {
'light': 'dark',
'dark': 'auto',
'auto': 'light'
light: 'dark',
dark: 'auto',
auto: 'light',
};
const colorSchemeIconMapper = {
'dark': 'crescent-moon',
'light': 'sun',
'auto': 'circle-black-white'
dark: 'crescent-moon',
light: 'sun',
auto: 'circle-black-white',
};
const getTooltipTranslation = function getTooltipTranslation(colorScheme) {
return t(`preferences.colorScheme.switch.${colorSchemeNextMapper[colorScheme]}`);
return t(
`preferences.colorScheme.switch.${colorSchemeNextMapper[colorScheme]}`,
);
};
const setColorScheme = function setColorScheme(newColorScheme) {
@@ -47,7 +49,8 @@ const setColorScheme = function setColorScheme(newColorScheme) {
</script>
<template>
<button class="color-scheme-button"
<button
class="color-scheme-button"
@click="setColorScheme(colorSchemeNextMapper[colorScheme])"
:aria-label="getTooltipTranslation(colorScheme)"
:title="getTooltipTranslation(colorScheme)"
@@ -44,8 +44,8 @@ const closeWrapperOnClickOutside = function closeWrapperOnClickOutside(e) {
if (languageDropdown.value) {
if (!languageDropdown.value.contains(e.target)) {
close();
};
};
}
}
};
const open = function open() {
@@ -54,7 +54,7 @@ const open = function open() {
setTimeout(() => {
document.addEventListener('click', closeWrapperOnClickOutside);
}, 0);
};
}
};
</script>
@@ -66,7 +66,9 @@ const open = function open() {
:aria-expanded="isOpen"
aria-haspopup="listbox"
>
<span class="lang-code">{{ t(`preferences.locale.languages.${locale}`) }}</span>
<span class="lang-code">{{
t(`preferences.locale.languages.${locale}`)
}}</span>
<span class="chevron" :class="{ open: isOpen }">
<UIIcon name="chevron-down" size="1em" />
</span>
@@ -78,7 +80,7 @@ const open = function open() {
class="language-dropdown"
role="listbox"
:style="{
bottom: 'calc(var(--offset) + 2 * var(--trigger-padding-y) + 1em)' // easier to add auto adaption to the available space
bottom: 'calc(var(--offset) + 2 * var(--trigger-padding-y) + 1em)', // easier to add auto adaption to the available space
}"
>
<li
@@ -89,7 +91,9 @@ const open = function open() {
:class="{ active: lang === locale }"
@click="selectLanguage(lang)"
>
<span class="lang-label">{{ t(`preferences.locale.languages.${lang}`) }}</span>
<span class="lang-label">{{
t(`preferences.locale.languages.${lang}`)
}}</span>
</li>
</ul>
</div>
@@ -30,7 +30,9 @@ describe('LanguageSwitchButton', () => {
expect(wrapper.find('.language-button').exists()).toBe(true);
expect(wrapper.find('.language-dropdown').exists()).toBe(false);
expect(wrapper.find('.language-button').attributes('aria-expanded')).toBe('false');
expect(
wrapper.find('.language-button').attributes('aria-expanded'),
).toBe('false');
});
test('opens the dropdown when language button is clicked', async () => {
@@ -52,12 +54,12 @@ describe('LanguageSwitchButton', () => {
const options = wrapper.findAll('.language-dropdown li');
const enOption = options.find(opt => opt.text().includes('en'));
const enOption = options.find((opt) => opt.text().includes('en'));
await enOption.trigger('click');
expect(getSetting(LANGUAGE_PATH)).toBe('en');
expect(wrapper.find('.language-dropdown').exists()).toBe(false);
const deOption = options.find(opt => opt.text().includes('de'));
const deOption = options.find((opt) => opt.text().includes('de'));
await deOption.trigger('click');
expect(getSetting(LANGUAGE_PATH)).toBe('de');
expect(wrapper.find('.language-dropdown').exists()).toBe(false);
@@ -69,7 +71,7 @@ describe('LanguageSwitchButton', () => {
await wrapper.find('.language-button').trigger('click');
expect(wrapper.find('.language-dropdown').exists()).toBe(true);
await new Promise(resolve => setTimeout(resolve, 0));
await new Promise((resolve) => setTimeout(resolve, 0));
const externalDiv = document.createElement('div');
document.body.appendChild(externalDiv);
@@ -19,33 +19,36 @@ import { describe, expect, test } from 'vitest';
describe('navigatorLanguage', () => {
test.for([
{ navigatorLanguage: 'en', expected: 'en'},
{ navigatorLanguage: 'de', expected: 'de'},
{ navigatorLanguage: 'fr', expected: 'fr'},
{ navigatorLanguage: 'en', expected: 'en' },
{ navigatorLanguage: 'de', expected: 'de' },
{ navigatorLanguage: 'fr', expected: 'fr' },
{ navigatorLanguage: 'en-US', expected: 'en'},
{ navigatorLanguage: 'en-AU', expected: 'en'},
{ navigatorLanguage: 'de-DE', expected: 'de'},
{ navigatorLanguage: 'fr-FR', expected: 'fr'},
{ navigatorLanguage: 'en-US', expected: 'en' },
{ navigatorLanguage: 'en-AU', expected: 'en' },
{ navigatorLanguage: 'de-DE', expected: 'de' },
{ navigatorLanguage: 'fr-FR', expected: 'fr' },
{ navigatorLanguage: 'en-us', expected: 'en'},
{ navigatorLanguage: 'en-au', expected: 'en'},
{ navigatorLanguage: 'de-de', expected: 'de'},
{ navigatorLanguage: 'fr-fr', expected: 'fr'},
{ navigatorLanguage: 'en-us', expected: 'en' },
{ navigatorLanguage: 'en-au', expected: 'en' },
{ navigatorLanguage: 'de-de', expected: 'de' },
{ navigatorLanguage: 'fr-fr', expected: 'fr' },
{ navigatorLanguage: 'zh-Hans-CN', expected: 'zh'},
{ navigatorLanguage: 'zh-Hant-TW', expected: 'zh'},
{ navigatorLanguage: 'uz-Latn-UZ', expected: 'uz'},
{ navigatorLanguage: 'zh-Hans-CN', expected: 'zh' },
{ navigatorLanguage: 'zh-Hant-TW', expected: 'zh' },
{ navigatorLanguage: 'uz-Latn-UZ', expected: 'uz' },
{ navigatorLanguage: 'en-US-u-ca-gregory', expected: 'en'},
{ navigatorLanguage: 'de-DE-u-co-phonebk', expected: 'de'},
{ navigatorLanguage: 'zh-Hant-TW-u-co-phonebk', expected: 'zh'}
])('returns language $expected with navigator language $navigatorLanguage', ({ navigatorLanguage, expected }) => {
Object.defineProperty(navigator, 'language', {
value: navigatorLanguage,
configurable: true
});
{ navigatorLanguage: 'en-US-u-ca-gregory', expected: 'en' },
{ navigatorLanguage: 'de-DE-u-co-phonebk', expected: 'de' },
{ navigatorLanguage: 'zh-Hant-TW-u-co-phonebk', expected: 'zh' },
])(
'returns language $expected with navigator language $navigatorLanguage',
({ navigatorLanguage, expected }) => {
Object.defineProperty(navigator, 'language', {
value: navigatorLanguage,
configurable: true,
});
expect(getNavigatorLanguage()).toBe(expected);
});
expect(getNavigatorLanguage()).toBe(expected);
},
);
});
+4 -4
View File
@@ -21,17 +21,17 @@ import { computed } from 'vue';
const props = defineProps({
name: {
required: true,
type: String
type: String,
},
size: {
type: [Number, String],
default: 24
}
default: 24,
},
});
const icons = import.meta.glob('@/assets/icons/*.svg', {
eager: true,
import: 'default'
import: 'default',
});
const icon = computed(() => icons[`/src/assets/icons/${props.name}.svg`]);
+2 -2
View File
@@ -15,8 +15,8 @@ limitations under the License.
*/
export class FetchError extends Error {
constructor (message) {
constructor(message) {
super(message);
this.name = 'FetchError';
}
};
}
@@ -60,29 +60,25 @@ describe('fetchLegalContent', () => {
describe('getLegalNotice', () => {
describe('success', () => {
test('returns HTML body', async () => {
fetchSpy.mockResolvedValue(
new Response(exampleHtmlDocument)
);
fetchSpy.mockResolvedValue(new Response(exampleHtmlDocument));
expect(
(await getLegalNotice()).trim()
).toBe(exampleHtmlWithLinkClass);
expect((await getLegalNotice()).trim()).toBe(
exampleHtmlWithLinkClass,
);
});
test('returns HTML <main>', async () => {
fetchSpy.mockResolvedValue(
new Response(exampleHtmlDocumentWithMain)
new Response(exampleHtmlDocumentWithMain),
);
expect(
(await getLegalNotice()).trim()
).toBe(exampleHtmlWithLinkClass);
expect((await getLegalNotice()).trim()).toBe(
exampleHtmlWithLinkClass,
);
});
test('does not fail on success status code', async () => {
fetchSpy.mockResolvedValue(
new Response(null, { status: 206 })
);
fetchSpy.mockResolvedValue(new Response(null, { status: 206 }));
await getLegalNotice();
});
@@ -95,9 +91,7 @@ describe('fetchLegalContent', () => {
});
test('throws FetchError on HTTP error', async () => {
fetchSpy.mockResolvedValue(
new Response(null, { status: 404 })
);
fetchSpy.mockResolvedValue(new Response(null, { status: 404 }));
await expect(getLegalNotice()).rejects.toThrow(FetchError);
});
@@ -30,15 +30,16 @@ const fetchHtml = async function fetchHtml(url, options) {
};
export const getLegalNotice = async function getLegalNotice() {
const legalNoticeUrl = new URL('legal/legal_notice.html', getAssetsUrl()).href;
const legalNoticeUrl = new URL('legal/legal_notice.html', getAssetsUrl())
.href;
const html = await fetchHtml(legalNoticeUrl);
const parser = new DOMParser();
const doc = parser.parseFromString(html, 'text/html');
const legalNoticeContainer = doc.querySelector('main') ?? doc.body;
legalNoticeContainer.querySelectorAll('a').forEach(
(link) => link.classList.add('link')
);
legalNoticeContainer
.querySelectorAll('a')
.forEach((link) => link.classList.add('link'));
return legalNoticeContainer.innerHTML;
};
@@ -20,12 +20,14 @@ import { mountComponent } from '@/test-utils/mountComponent';
import { flushPromises } from '@vue/test-utils';
import { afterEach, describe, expect, test, vi } from 'vitest';
const getLegalNotice = vi.hoisted(() => vi.fn().mockResolvedValue('legal notice'));
const getLegalNotice = vi.hoisted(() =>
vi.fn().mockResolvedValue('legal notice'),
);
vi.mock('../../services/fetchLegalContent', async (importOriginal) => {
const actual = await importOriginal();
return {
...actual,
getLegalNotice
getLegalNotice,
};
});
+4 -2
View File
@@ -33,7 +33,9 @@ import ColorSchemeButton from '@/features/colorScheme/components/ColorSchemeButt
</ul>
</nav>
<div class="navbar-search-bar-wrapper-small-screens-wrapper">
<NavbarSearchBarWrapper class="navbar-search-bar-wrapper-small-screens" />
<NavbarSearchBarWrapper
class="navbar-search-bar-wrapper-small-screens"
/>
</div>
</template>
@@ -60,7 +62,7 @@ import ColorSchemeButton from '@/features/colorScheme/components/ColorSchemeButt
color: var(--dark);
}
.global-nav .right-links a:hover{
.global-nav .right-links a:hover {
text-decoration: underline;
}
@@ -22,15 +22,22 @@ import { useRoute } from 'vue-router';
const route = useRoute();
const searchQueryModel = defineModel();
watch(() => route.name, name => {
searchQueryModel.value = name === 'searchResults' ? route.query.q || '' : '';
});
watch(
() => route.name,
(name) => {
searchQueryModel.value =
name === 'searchResults' ? route.query.q || '' : '';
},
);
watch(() => route.query.q, q => {
if (route.name === 'searchResults') {
searchQueryModel.value = q || '';
}
});
watch(
() => route.query.q,
(q) => {
if (route.name === 'searchResults') {
searchQueryModel.value = q || '';
}
},
);
</script>
<template>
+17 -8
View File
@@ -30,7 +30,7 @@ const submitSearch = function submitSearch() {
if (props.autoSubmit !== undefined) {
router.push({
name: 'searchResults',
query: { q: searchQuery.value }
query: { q: searchQuery.value },
});
}
};
@@ -41,10 +41,10 @@ const submitSearch = function submitSearch() {
<form @submit.prevent="submitSearch" class="search-form">
<div class="search-wrapper">
<input
v-model="searchQuery"
type="search"
:placeholder="t('search.searchBar.placeholder')"
required
v-model="searchQuery"
type="search"
:placeholder="t('search.searchBar.placeholder')"
required
/>
<button
type="submit"
@@ -52,7 +52,11 @@ const submitSearch = function submitSearch() {
:title="t('search.searchBar.submit')"
:aria-label="t('search.searchBar.submit')"
>
<UIIcon class="search-icon" name="magnifying-glass" size="1.1em" />
<UIIcon
class="search-icon"
name="magnifying-glass"
size="1.1em"
/>
</button>
</div>
</form>
@@ -69,7 +73,10 @@ const submitSearch = function submitSearch() {
align-items: center;
border: 1.5px solid var(--border);
box-shadow: 0 0px 32px var(--blue-box-shadow);
border-radius: calc(var(--content-height) * 0.5 + var(--submit-button-padding-y) + var(--padding));
border-radius: calc(
var(--content-height) * 0.5 + var(--submit-button-padding-y) +
var(--padding)
);
padding: var(--padding);
padding-left: var(--padding-left);
width: 100%;
@@ -90,7 +97,9 @@ const submitSearch = function submitSearch() {
font-size: 1rem;
height: calc(var(--content-height) + 2 * var(--submit-button-padding-y));
width: calc(var(--content-height) + 2 * var(--submit-button-padding-y));
border-radius: calc(var(--content-height) * 0.5 + var(--submit-button-padding-y));
border-radius: calc(
var(--content-height) * 0.5 + var(--submit-button-padding-y)
);
border: none;
padding: var(--submit-button-padding-y) 20px;
background: var(--primary-color);
@@ -28,8 +28,7 @@ const { t } = useI18n();
<p>{{ t('error.tryAgainToAnotherTime') }}</p>
</div>
</div>
<div class="search-results-container">
</div>
<div class="search-results-container"></div>
</div>
</template>
@@ -24,11 +24,11 @@ const { t } = useI18n();
const props = defineProps({
setting: {
required: true
required: true,
},
path: {
required: true
}
required: true,
},
});
const store = useSettingsStore();
@@ -41,22 +41,22 @@ const doneButtonInitial = ref(true);
const inputId = useId();
const inputLabelId = useId();
const inputType = computed(() => ({
number: 'number',
string: 'text'
}[props.setting.type]
));
const hasChanged = computed(() =>
inputModel.value !== store.get(props.path)
const inputType = computed(
() =>
({
number: 'number',
string: 'text',
})[props.setting.type],
);
const hasChanged = computed(() => inputModel.value !== store.get(props.path));
watch(store, (newValue) => {
const newInputValue = newValue.get(props.path);
if (inputModel.value === oldInputValue) {
inputModel.value = newInputValue;
oldInputValue = newInputValue;
};
}
});
watch(inputModel, () => {
@@ -129,7 +129,10 @@ const apply = function apply() {
}
.input-container.done-button-shown .input {
width: calc(100% - 1.5 * var(--input-padding) - 26px - 0.75 * var(--input-height) + 1.5px);
width: calc(
100% - 1.5 * var(--input-padding) - 26px - 0.75 * var(--input-height) +
1.5px
);
}
.done-button {
@@ -25,11 +25,11 @@ const { t } = useI18n();
const props = defineProps({
setting: {
required: true
required: true,
},
path: {
required: true
}
required: true,
},
});
</script>
@@ -78,15 +78,15 @@ const props = defineProps({
margin-bottom: calc(var(--size) - 0.2em);
}
.section-heading[aria-level="2"] {
.section-heading[aria-level='2'] {
--size: 1.8em;
}
.section-heading[aria-level="3"] {
.section-heading[aria-level='3'] {
--size: 1.6em;
}
.section-heading[aria-level="4"] {
.section-heading[aria-level='4'] {
--size: 1.4em;
}
</style>
@@ -25,12 +25,12 @@ const { goToSettingsPage } = useSettingsPage();
const props = defineProps({
target: {
required: true,
type: String
type: String,
},
displayName: {
required: true,
type: String
}
type: String,
},
});
const followLink = function followLink() {
@@ -42,7 +42,9 @@ const followLink = function followLink() {
<button
class="settings-button button settings-page-link"
:title="props.displayName"
:aria-label="t('settings.pages.link.ariaLabel', { page: props.displayName })"
:aria-label="
t('settings.pages.link.ariaLabel', { page: props.displayName })
"
@click="followLink"
>
{{ props.displayName }}
@@ -44,19 +44,22 @@ const normalizeSelectedValue = function normalizeSelectedValue(value) {
const props = defineProps({
setting: {
required: true
required: true,
},
path: {
required: true
}
required: true,
},
});
const store = useSettingsStore();
const optionType = computed(() => props.setting.allowMultiple ? 'checkbox' : 'radio');
const optionType = computed(() =>
props.setting.allowMultiple ? 'checkbox' : 'radio',
);
const selected = computed({
get: () => normalizeSelectedValue(store.get(props.path) ?? props.setting.default),
set: (value) => store.set(props.path, value)
get: () =>
normalizeSelectedValue(store.get(props.path) ?? props.setting.default),
set: (value) => store.set(props.path, value),
});
const labelId = useId();
@@ -72,7 +75,11 @@ const labelId = useId();
<label
class="settings-button"
:title="t(option.i18n)"
:aria-label="t('settings.selection.option.ariaLabel', { option: t(option.i18n) })"
:aria-label="
t('settings.selection.option.ariaLabel', {
option: t(option.i18n),
})
"
>
<input
:type="optionType"
@@ -80,9 +87,13 @@ const labelId = useId();
:value="option.name"
class="option-input"
v-model="selected"
>
/>
<div class="option-check-mark-wrapper">
<UIIcon class="option-check-mark" name="check-mark" size="1em" />
<UIIcon
class="option-check-mark"
name="check-mark"
size="1em"
/>
</div>
<div class="input-label">
{{ t(option.i18n) }}
@@ -23,11 +23,11 @@ const { t } = useI18n();
const props = defineProps({
setting: {
required: true
required: true,
},
path: {
required: true
}
required: true,
},
});
const labelId = useId();
@@ -20,18 +20,29 @@ import { mountComponent } from '@/test-utils/mountComponent';
import { describe, expect, test } from 'vitest';
import { nextTick } from 'vue';
const getWrapper = function getWrapper({ type = 'string', i18n = 'input', defaultValue = undefined, piniaOptions = {} } = {}) {
return mountComponent(SettingsInput, {
attrs: {
setting: {
name: 'input',
default: defaultValue,
i18n,
type
const getWrapper = function getWrapper({
type = 'string',
i18n = 'input',
defaultValue = undefined,
piniaOptions = {},
} = {}) {
return mountComponent(
SettingsInput,
{
attrs: {
setting: {
name: 'input',
default: defaultValue,
i18n,
type,
},
path: 'input',
},
path: 'input'
}
}, {}, [], piniaOptions);
},
{},
[],
piniaOptions,
);
};
describe('SettingsInput', () => {
@@ -63,8 +74,8 @@ describe('SettingsInput', () => {
setupStores: () => {
const store = useSettingsStore();
store.settings.input = value;
}
}
},
},
});
const inputElement = stringSettingWrapper.find('input');
@@ -76,7 +87,7 @@ describe('SettingsInput', () => {
const wrapper = getWrapper({
type: 'string',
i18n: 'input',
defaultValue: value
defaultValue: value,
});
const inputElement = wrapper.find('input');
@@ -94,8 +105,8 @@ describe('SettingsInput', () => {
setupStores: () => {
const store = useSettingsStore();
store.settings.input = storeValue;
}
}
},
},
});
const inputElement = wrapper.find('input');
@@ -191,18 +202,22 @@ describe('SettingsInput', () => {
test('shows correct translation', () => {
const translation = 'Text input';
const wrapper = mountComponent(SettingsInput, {
attrs: {
setting: {
name: 'input',
i18n: 'input',
type: 'string',
const wrapper = mountComponent(
SettingsInput,
{
attrs: {
setting: {
name: 'input',
i18n: 'input',
type: 'string',
},
path: 'input',
},
path: 'input'
}
}, {
input: translation
});
},
{
input: translation,
},
);
const label = wrapper.find('label');
expect(label.text()).toBe(translation);
@@ -22,36 +22,45 @@ import SettingsSwitch from '../SettingsSwitch.vue';
import { mountComponent } from '@/test-utils/mountComponent.js';
import { describe, expect, test } from 'vitest';
const getWrapper = function getWrapper({ content = [], translations = {}, piniaOptions = {} }) {
return mountComponent(SettingsPage, {
attrs: {
setting: {
name: 'section',
type: 'section',
i18n: 'section',
content
const getWrapper = function getWrapper({
content = [],
translations = {},
piniaOptions = {},
}) {
return mountComponent(
SettingsPage,
{
attrs: {
setting: {
name: 'section',
type: 'section',
i18n: 'section',
content,
},
path: 'section',
},
path: 'section'
}
}, translations, [], piniaOptions);
},
translations,
[],
piniaOptions,
);
};
const exampleSettings = [
{
type: 'bool',
name: 'b0',
i18n: 'settings.section.b0'
i18n: 'settings.section.b0',
},
{
type: 'number',
name: 'n0',
i18n: 'settings.section.n0'
i18n: 'settings.section.n0',
},
{
type: 'string',
name: 'st0',
i18n: 'settings.section1.st0'
i18n: 'settings.section1.st0',
},
{
type: 'selection',
@@ -61,14 +70,14 @@ const exampleSettings = [
{ name: 'o0', i18n: 'settings.section.se0.o0' },
{ name: 'o1', i18n: 'settings.section.se0.o1' },
{ name: 'o2', i18n: 'settings.section.se0.o2' },
{ name: 'o3', i18n: 'settings.section.se0.o3' }
]
{ name: 'o3', i18n: 'settings.section.se0.o3' },
],
},
{
type: 'section',
name: 's0',
i18n: 'settings.section.s0',
content: []
content: [],
},
{
type: 'section',
@@ -79,17 +88,17 @@ const exampleSettings = [
type: 'bool',
name: 'b0',
i18n: 'settings.section.s1.b0',
default: true
}
]
}
default: true,
},
],
},
];
describe('SettingsPage', () => {
test('shows correct heading', () => {
const translation = 'Example section';
const wrapper = getWrapper({
translations: { section: translation }
translations: { section: translation },
});
const heading = wrapper.find('.section-heading');
@@ -98,10 +107,9 @@ describe('SettingsPage', () => {
test('shows settings correctly', () => {
const wrapper = getWrapper({
content: exampleSettings
content: exampleSettings,
});
const settingsWrappers = wrapper.findAll('.setting');
expect(settingsWrappers.length).toBe(exampleSettings.length);
@@ -110,8 +118,10 @@ describe('SettingsPage', () => {
const switchSetting = settingWrapper.findComponent(SettingsSwitch);
const inputSetting = settingWrapper.findComponent(SettingsInput);
const selectionSetting = settingWrapper.findComponent(SettingsSelection);
const settingsPageLink = settingWrapper.findComponent(SettingsPageLink);
const selectionSetting =
settingWrapper.findComponent(SettingsSelection);
const settingsPageLink =
settingWrapper.findComponent(SettingsPageLink);
let setting;
if (expectedSetting.type === 'bool') {
@@ -121,7 +131,10 @@ describe('SettingsPage', () => {
expect(settingsPageLink.exists()).toBe(false);
setting = switchSetting;
} else if (expectedSetting.type === 'string' || expectedSetting.type === 'number') {
} else if (
expectedSetting.type === 'string' ||
expectedSetting.type === 'number'
) {
expect(switchSetting.exists()).toBe(false);
expect(inputSetting.exists()).toBe(true);
expect(selectionSetting.exists()).toBe(false);
@@ -150,10 +163,14 @@ describe('SettingsPage', () => {
expectedSetting.type === 'selection'
) {
expect(setting.props('setting')).toMatchObject(expectedSetting);
expect(setting.props('path')).toBe(`section.${expectedSetting.name}`);
expect(setting.props('path')).toBe(
`section.${expectedSetting.name}`,
);
} else {
expect(setting.props('displayName')).toBe(expectedSetting.i18n);
expect(setting.props('target')).toBe(`section.${expectedSetting.name}`);
expect(setting.props('target')).toBe(
`section.${expectedSetting.name}`,
);
}
});
});
@@ -22,21 +22,19 @@ import { createI18n } from 'vue-i18n';
const goToSettingsPage = vi.fn();
vi.mock('../../composables/useSettingsPage.js', () => ({
useSettingsPage: () => ({
goToSettingsPage
})
goToSettingsPage,
}),
}));
const getWrapper = function getWrapper({ target, displayName }) {
return mount(SettingsPageLink, {
attrs: {
target,
displayName
displayName,
},
global: {
plugins: [
createI18n()
]
}
plugins: [createI18n()],
},
});
};
@@ -26,21 +26,27 @@ const getWrapper = function getWrapper({
allowMultiple = false,
defaultValue = undefined,
translations = {},
piniaOptions = {}
piniaOptions = {},
} = {}) {
return mountComponent(SettingsSelection, {
attrs: {
setting: {
name: 'selection',
type: 'selection',
i18n,
options,
allowMultiple,
default: defaultValue
return mountComponent(
SettingsSelection,
{
attrs: {
setting: {
name: 'selection',
type: 'selection',
i18n,
options,
allowMultiple,
default: defaultValue,
},
path: 'selection',
},
path: 'selection'
}
}, translations, [], piniaOptions);
},
translations,
[],
piniaOptions,
);
};
const getChecked = function getChecked(inputs) {
@@ -50,7 +56,7 @@ const getChecked = function getChecked(inputs) {
const exampleOptions = [
{ name: 'o0', i18n: 'o0' },
{ name: 'o1', i18n: 'o1' },
{ name: 'o2', i18n: 'o2' }
{ name: 'o2', i18n: 'o2' },
];
describe('SettingsSelection', () => {
@@ -81,8 +87,8 @@ describe('SettingsSelection', () => {
setupStores: () => {
const store = useSettingsStore();
store.set('selection', 'o1');
}
}
},
},
});
const inputs = wrapper.findAll('input');
@@ -97,8 +103,8 @@ describe('SettingsSelection', () => {
setupStores: () => {
const store = useSettingsStore();
store.set('selection', 'o1');
}
}
},
},
});
const settings = useSettingsStore();
@@ -123,9 +129,9 @@ describe('SettingsSelection', () => {
setupStores: () => {
const store = useSettingsStore();
store.set('selection', ['o1', 'o2']);
}
},
},
allowMultiple: true
allowMultiple: true,
});
const inputs = wrapper.findAll('input');
@@ -136,7 +142,7 @@ describe('SettingsSelection', () => {
test('shows default value', () => {
const wrapper = getWrapper({
options: exampleOptions,
defaultValue: 'o2'
defaultValue: 'o2',
});
const inputs = wrapper.findAll('input');
@@ -148,7 +154,7 @@ describe('SettingsSelection', () => {
const wrapper = getWrapper({
options: exampleOptions,
defaultValue: ['o0', 'o1'],
allowMultiple: true
allowMultiple: true,
});
const inputs = wrapper.findAll('input');
@@ -164,8 +170,8 @@ describe('SettingsSelection', () => {
setupStores: () => {
const store = useSettingsStore();
store.set('selection', 'o1');
}
}
},
},
});
const inputs = wrapper.findAll('input');
@@ -212,20 +218,23 @@ describe('SettingsSelection', () => {
return setValue(input, false);
};
const expectSelectionValue = function expectSelectionValue(value, options = exampleOptions) {
const expectSelectionValue = function expectSelectionValue(
value,
options = exampleOptions,
) {
value.sort();
expect(store.get('selection').sort()).toStrictEqual(value);
const checkedInputs = getChecked(inputs);
expect(
options
.map((option) => option.name)
.filter((_, i) => checkedInputs[i])
.map((option) => option.name)
.filter((_, i) => checkedInputs[i]),
).toStrictEqual(value);
};
const wrapper = getWrapper({
options: exampleOptions,
allowMultiple: true
allowMultiple: true,
});
const store = useSettingsStore();
@@ -283,8 +292,8 @@ describe('SettingsSelection', () => {
const translation = 'Selection setting';
const wrapper = getWrapper({
translations: {
selection: translation
}
selection: translation,
},
});
const label = wrapper.find('.label');
@@ -24,19 +24,25 @@ const getWrapper = function getWrapper({
i18n = 'switch1',
defaultValue = undefined,
translations = {},
piniaOptions = {}
piniaOptions = {},
} = {}) {
return mountComponent(SettingsSwitch, {
attrs: {
setting: {
type: 'bool',
name: 'switch',
i18n,
default: defaultValue
return mountComponent(
SettingsSwitch,
{
attrs: {
setting: {
type: 'bool',
name: 'switch',
i18n,
default: defaultValue,
},
path: 'switch',
},
path: 'switch'
}
}, translations, [], piniaOptions);
},
translations,
[],
piniaOptions,
);
};
describe('SettingsSwitch', () => {
@@ -61,7 +67,7 @@ describe('SettingsSwitch', () => {
test('shows default value', () => {
const wrapper = getWrapper({
defaultValue: true
defaultValue: true,
});
const switchElement = wrapper.find('.switch-wrapper');
@@ -75,8 +81,8 @@ describe('SettingsSwitch', () => {
setupStores: () => {
const store = useSettingsStore();
store.set('switch', false);
}
}
},
},
});
const switchElement = wrapper.find('.switch-wrapper');
@@ -116,7 +122,7 @@ describe('SettingsSwitch', () => {
const translation = 'Switch';
const wrapper = getWrapper({
translations: { switch1: translation }
translations: { switch1: translation },
});
const label = wrapper.find('label');
@@ -33,17 +33,17 @@ vi.mock('../../config.js', () => ({
type: 'number',
i18n: '',
name: 'number',
default: settingDefaultValue
default: settingDefaultValue,
},
{
type: 'bool',
i18n: '',
name: 'bool'
}
]
}
]
}
name: 'bool',
},
],
},
],
},
}));
describe('useSettings', () => {
@@ -85,7 +85,7 @@ beforeEach(() => {
setActivePinia(
createTestingPinia({
createSpy: vi.fn,
stubActions: false
})
stubActions: false,
}),
);
});
@@ -20,8 +20,8 @@ import { describe, expect, test, vi } from 'vitest';
const pushRoute = vi.fn();
vi.mock('vue-router', () => ({
useRouter: () => ({
push: pushRoute
})
push: pushRoute,
}),
}));
describe('useSettingsPage', () => {
@@ -32,8 +32,8 @@ describe('useSettingsPage', () => {
expect(pushRoute).toHaveBeenCalledWith({
name: 'settings',
params: {
rest: ['a', 'b', 'c', 'd', 'e']
}
rest: ['a', 'b', 'c', 'd', 'e'],
},
});
});
});
@@ -32,9 +32,7 @@ export const useSettings = function useSettings() {
return (
settingsStore.get(key.join('.')) ??
getSettingRecursively(
key, settings.contents ?? []
)?.default
getSettingRecursively(key, settings.contents ?? [])?.default
);
};
@@ -24,8 +24,8 @@ export const useSettingsPage = function useSettingsPage() {
return router.push({
name: 'settings',
params: {
rest: getSettingsPagePathSegments(pagePath)
}
rest: getSettingsPagePathSegments(pagePath),
},
});
};
+13 -13
View File
@@ -40,10 +40,10 @@ export default {
default: defaultLanguage,
options: SUPPORTED_LANGUAGES.map((language) => ({
name: language,
i18n: `preferences.locale.languages.${language}`
}))
}
]
i18n: `preferences.locale.languages.${language}`,
})),
},
],
},
{
name: 'appearance',
@@ -57,19 +57,19 @@ export default {
options: [
{
name: 'auto',
i18n: 'settings.settings.appearance.contents.colorScheme.options.auto'
i18n: 'settings.settings.appearance.contents.colorScheme.options.auto',
},
{
name: 'light',
i18n: 'settings.settings.appearance.contents.colorScheme.options.light'
i18n: 'settings.settings.appearance.contents.colorScheme.options.light',
},
{
name: 'dark',
i18n: 'settings.settings.appearance.contents.colorScheme.options.dark'
}
]
}
]
}
]
i18n: 'settings.settings.appearance.contents.colorScheme.options.dark',
},
],
},
],
},
],
};
+22 -18
View File
@@ -17,26 +17,30 @@ limitations under the License.
import { defineStore } from 'pinia';
import { reactive } from 'vue';
export const useSettingsStore = defineStore('settings', () => {
const settings = reactive({});
export const useSettingsStore = defineStore(
'settings',
() => {
const settings = reactive({});
const set = function set(key, value) {
settings[key] = value;
};
const set = function set(key, value) {
settings[key] = value;
};
const get = function get(key) {
return settings[key];
};
const get = function get(key) {
return settings[key];
};
const remove = function remove(key) {
delete settings[key];
};
const remove = function remove(key) {
delete settings[key];
};
const clear = function clear() {
Object.keys(settings).forEach(key => delete settings[key]);
};
const clear = function clear() {
Object.keys(settings).forEach((key) => delete settings[key]);
};
return { set, get, remove, clear, settings };
}, {
persist: true
});
return { set, get, remove, clear, settings };
},
{
persist: true,
},
);
@@ -19,19 +19,23 @@ import { describe, test, expect } from 'vitest';
describe('getSetting', () => {
test.for([
({ settings: [], name: '', expected: undefined }),
({ settings: [], name: 'a', expected: undefined }),
({ settings: [
{
{ settings: [], name: '', expected: undefined },
{ settings: [], name: 'a', expected: undefined },
{
settings: [
{
type: 'bool',
name: 'a',
i18n: 'a',
},
],
name: 'a',
expected: {
type: 'bool',
name: 'a',
i18n: 'a'
}
], name: 'a', expected: {
type: 'bool',
name: 'a',
i18n: 'a'
} })
i18n: 'a',
},
},
])('returns $expected', ({ settings, name, expected }) => {
expect(getSetting(name, settings)).toStrictEqual(expected);
});
@@ -40,7 +44,7 @@ describe('getSetting', () => {
{
type: 'bool',
name: '1a',
i18n: '1a'
i18n: '1a',
},
{
type: 'section',
@@ -50,7 +54,7 @@ describe('getSetting', () => {
{
type: 'bool',
name: '2a',
i18n: '2a'
i18n: '2a',
},
{
type: 'section',
@@ -60,7 +64,7 @@ describe('getSetting', () => {
{
type: 'bool',
name: '3a',
i18n: '3a'
i18n: '3a',
},
{
type: 'section',
@@ -70,7 +74,7 @@ describe('getSetting', () => {
{
type: 'bool',
name: '4a',
i18n: '4a'
i18n: '4a',
},
{
type: 'section',
@@ -80,7 +84,7 @@ describe('getSetting', () => {
{
type: 'bool',
name: '5a',
i18n: '5a'
i18n: '5a',
},
{
type: 'section',
@@ -90,7 +94,7 @@ describe('getSetting', () => {
{
type: 'bool',
name: '6a',
i18n: '6a'
i18n: '6a',
},
{
type: 'section',
@@ -100,428 +104,488 @@ describe('getSetting', () => {
{
type: 'bool',
name: '7a',
i18n: '7a'
}
]
}
]
}
]
}
]
}
]
}
]
}
i18n: '7a',
},
],
},
],
},
],
},
],
},
],
},
],
},
];
test.for([
({ settings: [], path: ['a'], expected: undefined }),
({ settings: [
{
type: 'bool',
name: '1a',
i18n: '1a'
}
], path: ['1a'], expected: {
type: 'bool',
name: '1a',
i18n: '1a'
} }),
({ settings: [
{
type: 'bool',
name: '1a',
i18n: '1a'
},
{
type: 'section',
name: '1b',
i18n: '1b',
content: [
{
type: 'bool',
name: '2a',
i18n: '2a'
}
]
}
], path: ['1a'], expected: {
type: 'bool',
name: '1a',
i18n: '1a'
} }),
({ settings: [
{
type: 'bool',
name: '1a',
i18n: '1a'
},
{
type: 'section',
name: '1b',
i18n: '1b',
content: [
{
type: 'bool',
name: '2a',
i18n: '2a'
}
]
}
], path: ['1b', '2a'], expected: {
type: 'bool',
name: '2a',
i18n: '2a'
} }),
({ settings: [
{
type: 'bool',
name: '1a',
i18n: '1a'
},
{
type: 'section',
name: '1b',
i18n: '1b',
content: [
{
type: 'bool',
name: '2a',
i18n: '2a'
},
{
type: 'number',
name: '2b',
i18n: '2b',
default: 42
}
]
}
], path: ['1b', '2b'], expected: {
type: 'number',
name: '2b',
i18n: '2b',
default: 42
} }),
({ settings: [
{
type: 'bool',
name: '1a',
i18n: '1a'
},
{
type: 'section',
name: '1b',
i18n: '1b',
content: [
{
type: 'bool',
name: '2a',
i18n: '2a'
},
{
type: 'section',
name: '2b',
i18n: '2b',
content: [
{
type: 'string',
name: '3a',
i18n: '3a'
}
]
}
]
}
], path: ['1b', '2b', '3a'], expected: {
type: 'string',
name: '3a',
i18n: '3a'
} }),
({ settings: [
{
type: 'bool',
name: '1a',
i18n: '1a'
},
{
type: 'section',
name: '1b',
i18n: '1b',
content: [
{
type: 'bool',
name: '2a',
i18n: '2a'
},
{
type: 'section',
name: '2b',
i18n: '2b',
content: [
{
type: 'string',
name: '3a',
i18n: '3a'
}
]
}
]
}
], path: ['1b'], expected: {
type: 'section',
name: '1b',
i18n: '1b',
content: [
{ settings: [], path: ['a'], expected: undefined },
{
settings: [
{
type: 'bool',
name: '2a',
i18n: '2a'
name: '1a',
i18n: '1a',
},
],
path: ['1a'],
expected: {
type: 'bool',
name: '1a',
i18n: '1a',
},
},
{
settings: [
{
type: 'bool',
name: '1a',
i18n: '1a',
},
{
type: 'section',
name: '2b',
i18n: '2b',
content: [
{
type: 'string',
name: '3a',
i18n: '3a'
}
]
}
]
} }),
({ settings: [
{
type: 'bool',
name: '1a',
i18n: '1a'
},
{
type: 'section',
name: '1b',
i18n: '1b',
content: [
{
type: 'bool',
name: '2a',
i18n: '2a'
},
{
type: 'section',
name: '2b',
i18n: '2b',
content: [
{
type: 'string',
name: '3a',
i18n: '3a'
}
]
}
]
}
], path: ['1b', '2b'], expected: {
type: 'section',
name: '2b',
i18n: '2b',
content: [
{
type: 'string',
name: '3a',
i18n: '3a'
}
]
} }),
({ settings: [
{
type: 'bool',
name: '1a',
i18n: '1a'
},
{
type: 'section',
name: '1b',
i18n: '1b',
content: [
{
type: 'bool',
name: '2a',
i18n: '2a'
},
{
type: 'section',
name: '2b',
i18n: '2b',
content: [
{
type: 'string',
name: '3a',
i18n: '3a'
}
]
}
]
}
], path: ['1b', '2b', '3b'], expected: undefined }),
({ settings: [
{
type: 'bool',
name: '1a',
i18n: '1a'
},
{
type: 'section',
name: '1b',
i18n: '1b',
content: [
{
type: 'bool',
name: '2a',
i18n: '2a'
},
{
type: 'section',
name: '2b',
i18n: '2b',
content: [
{
type: 'string',
name: '3a',
i18n: '3a'
}
]
}
]
}
], path: ['1b', '2a', '3a'], expected: undefined }),
({ settings: [
{
type: 'bool',
name: '1a',
i18n: '1a'
},
{
type: 'section',
name: '1b',
i18n: '1b',
content: [
{
type: 'bool',
name: '2a',
i18n: '2a'
},
{
type: 'section',
name: '2b',
i18n: '2b',
content: [
{
type: 'string',
name: '3a',
i18n: '3a'
}
]
}
]
}
], path: ['1b', '2c', '3a'], expected: undefined }),
({ settings: [
{
type: 'bool',
name: '1a',
i18n: '1a'
},
{
type: 'section',
name: '1b',
i18n: '1b',
content: [
{
type: 'bool',
name: '2a',
i18n: '2a'
},
{
type: 'section',
name: '2b',
i18n: '2b',
content: [
{
type: 'string',
name: '3a',
i18n: '3a'
}
]
}
]
}
], path: ['1a', '2b', '3a'], expected: undefined }),
({ settings: [
{
type: 'bool',
name: '1a',
i18n: '1a'
},
{
type: 'section',
name: '1b',
i18n: '1b',
content: [
{
type: 'bool',
name: '2a',
i18n: '2a'
},
{
type: 'section',
name: '2b',
i18n: '2b',
content: [
{
type: 'string',
name: '3a',
i18n: '3a'
}
]
}
]
}
], path: ['1c', '2b', '3a'], expected: undefined }),
({ settings: veryNestedSettingsList, path: ['1b', '2b', '3b', '4b', '5b', '6b', '7a'], expected: {
type: 'bool',
name: '7a',
i18n: '7a'
} }),
({ settings: veryNestedSettingsList, path: ['1b', '2b', '3b', '4b', '5b'], expected: {
type: 'section',
name: '5b',
i18n: '5b',
content: [
{
type: 'bool',
name: '6a',
i18n: '6a'
},
{
type: 'section',
name: '6b',
i18n: '6b',
name: '1b',
i18n: '1b',
content: [
{
type: 'bool',
name: '7a',
i18n: '7a'
}
]
}
]
} }),
({ settings: veryNestedSettingsList, path: ['1b', '2b', '3b', '4ba', '5b', '6b', '7a'], expected: undefined })
name: '2a',
i18n: '2a',
},
],
},
],
path: ['1a'],
expected: {
type: 'bool',
name: '1a',
i18n: '1a',
},
},
{
settings: [
{
type: 'bool',
name: '1a',
i18n: '1a',
},
{
type: 'section',
name: '1b',
i18n: '1b',
content: [
{
type: 'bool',
name: '2a',
i18n: '2a',
},
],
},
],
path: ['1b', '2a'],
expected: {
type: 'bool',
name: '2a',
i18n: '2a',
},
},
{
settings: [
{
type: 'bool',
name: '1a',
i18n: '1a',
},
{
type: 'section',
name: '1b',
i18n: '1b',
content: [
{
type: 'bool',
name: '2a',
i18n: '2a',
},
{
type: 'number',
name: '2b',
i18n: '2b',
default: 42,
},
],
},
],
path: ['1b', '2b'],
expected: {
type: 'number',
name: '2b',
i18n: '2b',
default: 42,
},
},
{
settings: [
{
type: 'bool',
name: '1a',
i18n: '1a',
},
{
type: 'section',
name: '1b',
i18n: '1b',
content: [
{
type: 'bool',
name: '2a',
i18n: '2a',
},
{
type: 'section',
name: '2b',
i18n: '2b',
content: [
{
type: 'string',
name: '3a',
i18n: '3a',
},
],
},
],
},
],
path: ['1b', '2b', '3a'],
expected: {
type: 'string',
name: '3a',
i18n: '3a',
},
},
{
settings: [
{
type: 'bool',
name: '1a',
i18n: '1a',
},
{
type: 'section',
name: '1b',
i18n: '1b',
content: [
{
type: 'bool',
name: '2a',
i18n: '2a',
},
{
type: 'section',
name: '2b',
i18n: '2b',
content: [
{
type: 'string',
name: '3a',
i18n: '3a',
},
],
},
],
},
],
path: ['1b'],
expected: {
type: 'section',
name: '1b',
i18n: '1b',
content: [
{
type: 'bool',
name: '2a',
i18n: '2a',
},
{
type: 'section',
name: '2b',
i18n: '2b',
content: [
{
type: 'string',
name: '3a',
i18n: '3a',
},
],
},
],
},
},
{
settings: [
{
type: 'bool',
name: '1a',
i18n: '1a',
},
{
type: 'section',
name: '1b',
i18n: '1b',
content: [
{
type: 'bool',
name: '2a',
i18n: '2a',
},
{
type: 'section',
name: '2b',
i18n: '2b',
content: [
{
type: 'string',
name: '3a',
i18n: '3a',
},
],
},
],
},
],
path: ['1b', '2b'],
expected: {
type: 'section',
name: '2b',
i18n: '2b',
content: [
{
type: 'string',
name: '3a',
i18n: '3a',
},
],
},
},
{
settings: [
{
type: 'bool',
name: '1a',
i18n: '1a',
},
{
type: 'section',
name: '1b',
i18n: '1b',
content: [
{
type: 'bool',
name: '2a',
i18n: '2a',
},
{
type: 'section',
name: '2b',
i18n: '2b',
content: [
{
type: 'string',
name: '3a',
i18n: '3a',
},
],
},
],
},
],
path: ['1b', '2b', '3b'],
expected: undefined,
},
{
settings: [
{
type: 'bool',
name: '1a',
i18n: '1a',
},
{
type: 'section',
name: '1b',
i18n: '1b',
content: [
{
type: 'bool',
name: '2a',
i18n: '2a',
},
{
type: 'section',
name: '2b',
i18n: '2b',
content: [
{
type: 'string',
name: '3a',
i18n: '3a',
},
],
},
],
},
],
path: ['1b', '2a', '3a'],
expected: undefined,
},
{
settings: [
{
type: 'bool',
name: '1a',
i18n: '1a',
},
{
type: 'section',
name: '1b',
i18n: '1b',
content: [
{
type: 'bool',
name: '2a',
i18n: '2a',
},
{
type: 'section',
name: '2b',
i18n: '2b',
content: [
{
type: 'string',
name: '3a',
i18n: '3a',
},
],
},
],
},
],
path: ['1b', '2c', '3a'],
expected: undefined,
},
{
settings: [
{
type: 'bool',
name: '1a',
i18n: '1a',
},
{
type: 'section',
name: '1b',
i18n: '1b',
content: [
{
type: 'bool',
name: '2a',
i18n: '2a',
},
{
type: 'section',
name: '2b',
i18n: '2b',
content: [
{
type: 'string',
name: '3a',
i18n: '3a',
},
],
},
],
},
],
path: ['1a', '2b', '3a'],
expected: undefined,
},
{
settings: [
{
type: 'bool',
name: '1a',
i18n: '1a',
},
{
type: 'section',
name: '1b',
i18n: '1b',
content: [
{
type: 'bool',
name: '2a',
i18n: '2a',
},
{
type: 'section',
name: '2b',
i18n: '2b',
content: [
{
type: 'string',
name: '3a',
i18n: '3a',
},
],
},
],
},
],
path: ['1c', '2b', '3a'],
expected: undefined,
},
{
settings: veryNestedSettingsList,
path: ['1b', '2b', '3b', '4b', '5b', '6b', '7a'],
expected: {
type: 'bool',
name: '7a',
i18n: '7a',
},
},
{
settings: veryNestedSettingsList,
path: ['1b', '2b', '3b', '4b', '5b'],
expected: {
type: 'section',
name: '5b',
i18n: '5b',
content: [
{
type: 'bool',
name: '6a',
i18n: '6a',
},
{
type: 'section',
name: '6b',
i18n: '6b',
content: [
{
type: 'bool',
name: '7a',
i18n: '7a',
},
],
},
],
},
},
{
settings: veryNestedSettingsList,
path: ['1b', '2b', '3b', '4ba', '5b', '6b', '7a'],
expected: undefined,
},
])('returns $expected', ({ settings, path, expected }) => {
expect(getSettingRecursively(path, settings)).toStrictEqual(expected);
});
@@ -47,15 +47,24 @@ describe('settingsPage', () => {
{ path: 'a1.#b.c/', expected: ['a1', 'b', 'c'] },
{ path: 'a-1.#b.c/', expected: ['a-1', 'b', 'c'] },
{ path: 'a-1.b@.c', expected: ['a-1', 'b', 'c'] },
{ path: '....@a/#...)!&§[b.#§c..d....', expected: ['a', 'b', 'c', 'd'] },
{ path: '....@a/#...)!&§[b.#§c..dä....', expected: ['a', 'b', 'c', 'd'] },
{ path: '..,...~..@a/#.+..)!&§[b.#§c..dä..)..', expected: ['a', 'b', 'c', 'd'] },
{
path: '....@a/#...)!&§[b.#§c..d....',
expected: ['a', 'b', 'c', 'd'],
},
{
path: '....@a/#...)!&§[b.#§c..dä....',
expected: ['a', 'b', 'c', 'd'],
},
{
path: '..,...~..@a/#.+..)!&§[b.#§c..dä..)..',
expected: ['a', 'b', 'c', 'd'],
},
{ path: 'a.@.b', expected: ['a', 'b'] },
{ path: 1, expected: [] },
{ path: false, expected: [] },
{ path: true, expected: [] },
{ path: null, expected: [] },
{ path: undefined, expected: [] }
{ path: undefined, expected: [] },
])('returns $expected', ({ path, expected }) => {
expect(getSettingsPagePathSegments(path)).toStrictEqual(expected);
});
File diff suppressed because it is too large Load Diff
+8 -2
View File
@@ -18,7 +18,10 @@ export const getSetting = function getSetting(name, settings) {
return settings.filter((setting) => setting.name === name)[0];
};
export const getSettingRecursively = function getSettingRecursively(sectionPath, settings) {
export const getSettingRecursively = function getSettingRecursively(
sectionPath,
settings,
) {
const oneSettingsLevel = function oneSettingsLevel(cursor, acc) {
const setting = getSetting(cursor[0], acc.content);
if (!setting) return;
@@ -28,7 +31,10 @@ export const getSettingRecursively = function getSettingRecursively(sectionPath,
const newCursor = cursor.slice(1, cursor.length);
if (newCursor.length !== 0) {
return oneSettingsLevel(cursor.slice(1, cursor.length), setting);
return oneSettingsLevel(
cursor.slice(1, cursor.length),
setting,
);
} else {
return setting;
}
+4 -4
View File
@@ -19,13 +19,13 @@ limitations under the License.
* @param {string} path - The settings page path.
* @returns {Array} The settings page path segments.
*/
export const getSettingsPagePathSegments = function getSettingsPagePathSegments(path) {
export const getSettingsPagePathSegments = function getSettingsPagePathSegments(
path,
) {
if (typeof path !== 'string') return [];
return path
.replace(/^\.+|\.+$/g, '')
.split('.')
.map(
(segment) => segment.replace(/[^a-zA-Z0-9-]/g, '')
)
.map((segment) => segment.replace(/[^a-zA-Z0-9-]/g, ''))
.filter(Boolean);
};
@@ -25,14 +25,19 @@ export const assertString = function assertString(value, path) {
export const assertType = function assertType(value, path) {
if (!VALID_TYPES.includes(value)) {
throw new Error(
`[settings] "${path}" has invalid type "${value}". Must be one of: ${VALID_TYPES.join(', ')}`
`[settings] "${path}" has invalid type "${value}". Must be one of: ${VALID_TYPES.join(', ')}`,
);
}
};
export const validateSelectionOptions = function validateSelectionOptions(options, path) {
export const validateSelectionOptions = function validateSelectionOptions(
options,
path,
) {
if (!Array.isArray(options) || options.length === 0) {
throw new Error(`[settings] "${path}.options" must be a non-empty array`);
throw new Error(
`[settings] "${path}.options" must be a non-empty array`,
);
}
options.forEach((opt, i) => {
assertString(opt.name, `${path}.options[${i}].name`);
@@ -51,7 +56,7 @@ export const validateEntry = function validateEntry(entry, path) {
throw new Error(`[settings] "${path}.content" must be an array`);
}
entry.content.forEach((child, i) =>
validateEntry(child, `${path}.content[${i}]`)
validateEntry(child, `${path}.content[${i}]`),
);
return;
}
@@ -59,7 +64,9 @@ export const validateEntry = function validateEntry(entry, path) {
if (entry.type === 'selection') {
validateSelectionOptions(entry.options, path);
if (typeof entry.allowMultiple !== 'boolean' && entry.allowMultiple) {
throw new Error(`[settings] "${path}.allowMultiple" must be a boolean`);
throw new Error(
`[settings] "${path}.allowMultiple" must be a boolean`,
);
}
}
if (entry.default !== undefined) {
@@ -75,30 +82,47 @@ export const validateEntry = function validateEntry(entry, path) {
if (entry.type === 'selection') {
if (entry.allowMultiple) {
if (!Array.isArray(entry.default)) {
throw new Error(`[settings] "${path}.default" must be an array`);
throw new Error(
`[settings] "${path}.default" must be an array`,
);
}
const allOptions = entry.options.map((option) => option.name);
entry.default.forEach((defaultValue, index) => {
if (typeof defaultValue !== 'string') {
throw new Error(`[settings] "${path}.default[${index}]" must be a string`);
throw new Error(
`[settings] "${path}.default[${index}]" must be a string`,
);
}
if (!allOptions.includes(defaultValue)) {
throw new Error(`[settings] option "${path}.default[${index}]" does not exist`);
throw new Error(
`[settings] option "${path}.default[${index}]" does not exist`,
);
}
});
} else {
if (typeof entry.default !== 'string') {
throw new Error(`[settings] "${path}.default" must be a string`);
throw new Error(
`[settings] "${path}.default" must be a string`,
);
}
if (!entry.options.map((option) => option.name).includes(entry.default)) {
throw new Error(`[settings] option "${path}.default" does not exist`);
if (
!entry.options
.map((option) => option.name)
.includes(entry.default)
) {
throw new Error(
`[settings] option "${path}.default" does not exist`,
);
}
}
}
}
};
export const validateFirstLevelSection = function validateFirstLevelSection(section, path) {
export const validateFirstLevelSection = function validateFirstLevelSection(
section,
path,
) {
assertString(section.name);
assertString(section.i18n);
@@ -107,7 +131,7 @@ export const validateFirstLevelSection = function validateFirstLevelSection(sect
}
section.content.forEach((entry, i) =>
validateEntry(entry, `${path}.content[${i}]`)
validateEntry(entry, `${path}.content[${i}]`),
);
};
@@ -124,6 +148,6 @@ export const validateSettingsConfig = function validateSettingsConfig(raw) {
throw new Error('[settings] "contents" must be an array');
}
raw.contents.forEach((entry, i) =>
validateFirstLevelSection(entry, `contents[${i}]`)
validateFirstLevelSection(entry, `contents[${i}]`),
);
};
+9 -6
View File
@@ -15,7 +15,6 @@ limitations under the License.
-->
<script setup>
import SettingsPage from '../components/SettingsPage.vue';
import settingsConfiguration from '../config.js';
import { getSettingRecursively } from '../utils/getSetting.js';
@@ -31,9 +30,7 @@ const router = useRouter();
const settings = settingsConfiguration.contents;
const activePath = computed(() => {
const segments = route.path
.split('/')
.filter(Boolean);
const segments = route.path.split('/').filter(Boolean);
return segments.slice(1, segments.length);
});
@@ -50,7 +47,9 @@ const updateSettings = async function updateSettings() {
watch(route, (newRoute) => updateSettings(newRoute));
onMounted(updateSettings);
const toggleSidebarIfSmallScreen = function toggleSidebarIfSmallScreen(toggleSidebarFunction) {
const toggleSidebarIfSmallScreen = function toggleSidebarIfSmallScreen(
toggleSidebarFunction,
) {
if (matchMedia('(max-width: 48rem)').matches) toggleSidebarFunction();
};
</script>
@@ -69,7 +68,11 @@ const toggleSidebarIfSmallScreen = function toggleSidebarIfSmallScreen(toggleSid
<RouterLink
:to="`/settings/${section.name}`"
class="button button-link link sidebar-section"
:class="{ active: activeSection && activeSection.name === section.name }"
:class="{
active:
activeSection &&
activeSection.name === section.name,
}"
@click="toggleSidebarIfSmallScreen(toggleSidebar)"
>
{{ t(section.i18n) }}
@@ -23,7 +23,7 @@ const settingsHoisted = vi.hoisted(() => [
{
name: 's0',
i18n: 'settings.s0',
content: []
content: [],
},
{
name: 's1',
@@ -32,17 +32,17 @@ const settingsHoisted = vi.hoisted(() => [
{
type: 'bool',
name: 'b0',
i18n: 'settings.s1.b0'
i18n: 'settings.s1.b0',
},
{
type: 'number',
name: 'n0',
i18n: 'settings.s1.n0'
i18n: 'settings.s1.n0',
},
{
type: 'string',
name: 'st0',
i18n: 'settings.s1.st0'
i18n: 'settings.s1.st0',
},
{
type: 'selection',
@@ -52,14 +52,14 @@ const settingsHoisted = vi.hoisted(() => [
{ name: 'o0', i18n: 'settings.s1.se0.o0' },
{ name: 'o1', i18n: 'settings.s1.se0.o1' },
{ name: 'o2', i18n: 'settings.s1.se0.o2' },
{ name: 'o3', i18n: 'settings.s1.se0.o3' }
]
{ name: 'o3', i18n: 'settings.s1.se0.o3' },
],
},
{
type: 'section',
name: 's0',
i18n: 'settings.s1.s0',
content: []
content: [],
},
{
type: 'section',
@@ -70,11 +70,11 @@ const settingsHoisted = vi.hoisted(() => [
type: 'bool',
name: 'b0',
i18n: 'settings.s1.s1.b0',
default: true
}
]
}
]
default: true,
},
],
},
],
},
{
name: 's2',
@@ -89,11 +89,11 @@ const settingsHoisted = vi.hoisted(() => [
type: 'bool',
name: 'b0',
i18n: 'settings.s2.s0.b0',
default: true
}
]
}
]
default: true,
},
],
},
],
},
{
name: 's3',
@@ -103,9 +103,9 @@ const settingsHoisted = vi.hoisted(() => [
type: 'section',
name: 's0',
i18n: 'settings.s3.s0',
content: []
}
]
content: [],
},
],
},
{
name: 's4',
@@ -119,10 +119,10 @@ const settingsHoisted = vi.hoisted(() => [
{ name: 'o0', i18n: 'settings.s4.se0.o0' },
{ name: 'o1', i18n: 'settings.s4.se0.o1' },
{ name: 'o2', i18n: 'settings.s4.se0.o2' },
{ name: 'o3', i18n: 'settings.s4.se0.o3' }
]
}
]
{ name: 'o3', i18n: 'settings.s4.se0.o3' },
],
},
],
},
{
name: 's5',
@@ -131,9 +131,9 @@ const settingsHoisted = vi.hoisted(() => [
{
type: 'string',
name: 'st0',
i18n: 'settings.s5.st0'
}
]
i18n: 'settings.s5.st0',
},
],
},
{
name: 's6',
@@ -142,9 +142,9 @@ const settingsHoisted = vi.hoisted(() => [
{
type: 'number',
name: 'n0',
i18n: 'settings.s6.n0'
}
]
i18n: 'settings.s6.n0',
},
],
},
{
name: 's7',
@@ -153,9 +153,9 @@ const settingsHoisted = vi.hoisted(() => [
{
type: 'bool',
name: 'b0',
i18n: 'settings.s7.b0'
}
]
i18n: 'settings.s7.b0',
},
],
},
{
name: 's8',
@@ -181,16 +181,16 @@ const settingsHoisted = vi.hoisted(() => [
name: 'se0',
i18n: 'settings.s8.s0.s0.s0.se0',
allowMultiple: true,
options: []
}
]
}
]
}
]
}
]
}
options: [],
},
],
},
],
},
],
},
],
},
]);
const settings = settingsHoisted.values();
@@ -198,7 +198,7 @@ const originalCurrentSection = 's0';
let currentSection = originalCurrentSection;
vi.mock('../../config.js', () => ({
default: { contents: settingsHoisted }
default: { contents: settingsHoisted },
}));
vi.mock('vue-router', async (importOriginal) => {
@@ -206,8 +206,8 @@ vi.mock('vue-router', async (importOriginal) => {
return {
...actual,
useRoute: () => ({
path: `/settings/${currentSection}`
})
path: `/settings/${currentSection}`,
}),
};
});
@@ -221,8 +221,8 @@ describe('SettingsView', () => {
const translation = 'Settings';
const wrapper = getWrapper({
translations: {
'preferences.settings': translation
}
'preferences.settings': translation,
},
});
const header = wrapper.find('header');
@@ -242,8 +242,12 @@ describe('SettingsView', () => {
expect(sidebarItem.text()).toBe(setting.i18n);
const link = sidebarItem.find('*');
expect(link.attributes('href')).toBe(`/settings/${setting.name}`);
expect(link.classes('active')).toBe(setting.name === currentSection);
expect(link.attributes('href')).toBe(
`/settings/${setting.name}`,
);
expect(link.classes('active')).toBe(
setting.name === currentSection,
);
});
});
});
@@ -257,7 +261,9 @@ describe('SettingsView', () => {
await nextTick();
const mainContent = wrapper.find('.settings-main-content');
const mainContentNoSection = wrapper.find('.main-content--no-section');
const mainContentNoSection = wrapper.find(
'.main-content--no-section',
);
expect(mainContent.exists()).toBe(false);
expect(mainContentNoSection.exists()).toBe(true);
@@ -268,7 +274,9 @@ describe('SettingsView', () => {
await nextTick();
const mainContent = wrapper.find('.settings-main-content');
const mainContentNoSection = wrapper.find('.main-content--no-section');
const mainContentNoSection = wrapper.find(
'.main-content--no-section',
);
expect(mainContent.exists()).toBe(true);
expect(mainContentNoSection.exists()).toBe(false);
@@ -18,11 +18,11 @@ limitations under the License.
import SidebarExpandButton from './SidebarExpandButton.vue';
const props = defineProps({
'expanded': {
expanded: {
default: false,
required: false,
type: Boolean
}
type: Boolean,
},
});
const emit = defineEmits(['toggleExpanded']);
@@ -34,7 +34,10 @@ const toggleSidebar = function toggleSidebar() {
<template>
<nav class="sidebar" :class="{ expanded }">
<div class="sidebar-controls">
<SidebarExpandButton @click="toggleSidebar" class="sidebar-expand-button" />
<SidebarExpandButton
@click="toggleSidebar"
class="sidebar-expand-button"
/>
</div>
<div class="sidebar-content" v-show="props.expanded">
<slot />
+6 -14
View File
@@ -18,24 +18,14 @@ import { createI18n } from 'vue-i18n';
export const fallbackLocale = 'en';
export const LANGUAGES_RTL = [
'ar'
];
export const SUPPORTED_LANGUAGES = [
'en',
'de',
'fr',
'es',
'pt',
'it'
];
export const LANGUAGES_RTL = ['ar'];
export const SUPPORTED_LANGUAGES = ['en', 'de', 'fr', 'es', 'pt', 'it'];
export const i18n = createI18n({
legacy: false,
locale: fallbackLocale,
fallbackLocale,
messages: {}
messages: {},
});
const loadedLanguages = new Set();
@@ -61,5 +51,7 @@ export const loadLanguage = async function loadLanguage(locale) {
export const setLocale = async function setLocale(locale) {
await loadLanguage(locale);
document.documentElement.lang = locale;
document.documentElement.dir = LANGUAGES_RTL.includes(locale) ? 'rtl' : 'ltr';
document.documentElement.dir = LANGUAGES_RTL.includes(locale)
? 'rtl'
: 'ltr';
};
+14 -7
View File
@@ -20,11 +20,11 @@ import SidebarExpandButton from '@/features/sidebar/components/SidebarExpandButt
import { ref } from 'vue';
const props = defineProps({
'expanded': {
expanded: {
default: false,
required: false,
type: Boolean
}
type: Boolean,
},
});
const expanded = ref(props.expanded);
@@ -37,9 +37,16 @@ const toggleSidebar = function toggleSidebar() {
<template>
<div class="layout-container" :class="{ expanded }">
<div class="sidebar-expand-button-container">
<SidebarExpandButton class="sidebar-expand-button" @click="toggleSidebar" />
<SidebarExpandButton
class="sidebar-expand-button"
@click="toggleSidebar"
/>
</div>
<AppSidebar @toggle-expanded="toggleSidebar" :expanded="expanded" class="sidebar">
<AppSidebar
@toggle-expanded="toggleSidebar"
:expanded="expanded"
class="sidebar"
>
<slot name="sidebar" :toggleSidebar="toggleSidebar" />
</AppSidebar>
<main class="main-content">
@@ -54,8 +61,8 @@ const toggleSidebar = function toggleSidebar() {
grid-template-columns: calc(32px + 2em) 1fr;
grid-template-rows: auto 1fr;
grid-template-areas:
"sidebar-expand sidebar-expand"
"sidebar main-content";
'sidebar-expand sidebar-expand'
'sidebar main-content';
border-top: 1px solid var(--border);
}
+1 -1
View File
@@ -42,7 +42,7 @@
"settings": {
"input": {
"done": {
"title": "Salva valore",
"title": "Salva valore",
"ariaLabel": "Salva il valore inserito"
}
},
+1 -1
View File
@@ -42,7 +42,7 @@
"settings": {
"input": {
"done": {
"title": "Guardar valor",
"title": "Guardar valor",
"ariaLabel": "Guarda o valor introduzido"
}
},
+24 -20
View File
@@ -33,21 +33,29 @@ import { getAssetsUrl } from './config/env';
import { validateSettingsConfig } from './features/settings/utils/settingsValidator';
(async () => {
const fonts = rawFonts.map((font) => new FontFace(
font.family ?? '',
Array.isArray(font.source)
? font.source.map(
(source) => `url('${source.url
? new URL(source.url, getAssetsUrl()).href
: ''
}') format('${source.format ?? ''}')`
).join(',')
: '',
{
weight: font.weight ?? undefined,
style: font.style ?? undefined
}
));
const fonts = rawFonts.map(
(font) =>
new FontFace(
font.family ?? '',
Array.isArray(font.source)
? font.source
.map(
(source) =>
`url('${
source.url
? new URL(source.url, getAssetsUrl())
.href
: ''
}') format('${source.format ?? ''}')`,
)
.join(',')
: '',
{
weight: font.weight ?? undefined,
style: font.style ?? undefined,
},
),
);
await Promise.all(fonts.map((font) => font.load()));
fonts.forEach((font) => document.fonts.add(font));
@@ -58,9 +66,5 @@ import { validateSettingsConfig } from './features/settings/utils/settingsValida
validateSettingsConfig(settings);
createApp(App)
.use(router)
.use(i18n)
.use(pinia)
.mount('#app');
createApp(App).use(router).use(i18n).use(pinia).mount('#app');
})();
+14 -15
View File
@@ -23,57 +23,56 @@ import SearchView from '@/views/SearchView.vue';
import { i18n } from '@/i18n';
import { createRouter, createWebHistory } from 'vue-router';
const routes = [
{
path: '/',
name: 'startPage',
component: SearchView
component: SearchView,
},
{
path: '/search',
name: 'searchResults',
component: SearchResultsView,
props: route => ({
searchQuery: route.query.q
props: (route) => ({
searchQuery: route.query.q,
}),
meta: {
title: (route) => route.query.q
}
title: (route) => route.query.q,
},
},
{
path: '/legal/notice',
name: 'legalNotice',
component: LegalNoticeView,
meta: {
title: () => i18n.global.t('legal.notice.title')
}
title: () => i18n.global.t('legal.notice.title'),
},
},
{
path: '/settings/:rest(.*)*',
name: 'settings',
component: SettingsView,
meta: {
title: () => i18n.global.t('preferences.settings')
}
title: () => i18n.global.t('preferences.settings'),
},
},
{
path: '/:pathMatch(.*)*',
name: 'notFound',
component: NotFound
component: NotFound,
},
];
const router = createRouter({
history: createWebHistory(),
routes
routes,
});
export const updatePageTitle = function updatePageTitle(route) {
const title =
typeof route.meta.title === 'function'
? route.meta.title(route)
: route.meta.title;
? route.meta.title(route)
: route.meta.title;
if (title) {
document.title = `${title} - Seekra`;
@@ -89,7 +88,7 @@ router.beforeEach((route) => {
path: route.path.replace(/\/+$/, ''),
query: route.query,
hash: route.hash,
replace: true
replace: true,
};
}
});
+15 -2
View File
@@ -19,7 +19,19 @@ body {
color: var(--dark);
margin: 0;
font-size: 16px;
font-family: 'Inter', system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif;
font-family:
'Inter',
system-ui,
-apple-system,
BlinkMacSystemFont,
'Segoe UI',
Roboto,
Oxygen,
Ubuntu,
Cantarell,
'Open Sans',
'Helvetica Neue',
sans-serif;
}
.link {
@@ -27,7 +39,8 @@ body {
color: var(--dark);
}
.link:hover:not(.button-link), .link:focus-visible:not(.button-link) {
.link:hover:not(.button-link),
.link:focus-visible:not(.button-link) {
outline: none;
text-decoration: underline;
}
+106 -106
View File
@@ -15,144 +15,144 @@ limitations under the License.
*/
body {
--primary-color-l-8: oklch(0.897 0.202 260);
--primary-color-l-7: oklch(0.847 0.202 260);
--primary-color-l-6: oklch(0.797 0.202 260);
--primary-color-l-6: oklch(0.747 0.202 260);
--primary-color-l-4: oklch(0.697 0.202 260);
--primary-color-l-3: oklch(0.647 0.202 260);
--primary-color-l-2: oklch(0.597 0.202 260);
--primary-color-l-1: oklch(0.547 0.202 260);
--primary-color: oklch(0.497 0.202 260);
--primary-color-d-1: oklch(0.447 0.202 260);
--primary-color-d-2: oklch(0.397 0.202 260);
--primary-color-d-3: oklch(0.347 0.202 260);
--primary-color-d-4: oklch(0.297 0.202 260);
--primary-color-d-5: oklch(0.247 0.202 260);
--primary-color-d-6: oklch(0.197 0.202 260);
--primary-color-d-7: oklch(0.147 0.202 260);
--primary-color-d-8: oklch(0.097 0.202 260);
--primary-color-l-8: oklch(0.897 0.202 260);
--primary-color-l-7: oklch(0.847 0.202 260);
--primary-color-l-6: oklch(0.797 0.202 260);
--primary-color-l-6: oklch(0.747 0.202 260);
--primary-color-l-4: oklch(0.697 0.202 260);
--primary-color-l-3: oklch(0.647 0.202 260);
--primary-color-l-2: oklch(0.597 0.202 260);
--primary-color-l-1: oklch(0.547 0.202 260);
--primary-color: oklch(0.497 0.202 260);
--primary-color-d-1: oklch(0.447 0.202 260);
--primary-color-d-2: oklch(0.397 0.202 260);
--primary-color-d-3: oklch(0.347 0.202 260);
--primary-color-d-4: oklch(0.297 0.202 260);
--primary-color-d-5: oklch(0.247 0.202 260);
--primary-color-d-6: oklch(0.197 0.202 260);
--primary-color-d-7: oklch(0.147 0.202 260);
--primary-color-d-8: oklch(0.097 0.202 260);
--black-l-8: oklch(0.4 0 0);
--black-l-7: oklch(0.35 0 0);
--black-l-6: oklch(0.3 0 0);
--black-l-5: oklch(0.25 0 0);
--black-l-4: oklch(0.2 0 0);
--black-l-3: oklch(0.15 0 0);
--black-l-2: oklch(0.1 0 0);
--black-l-1: oklch(0.05 0 0);
--black: oklch(0 0 0);
--black-l-8: oklch(0.4 0 0);
--black-l-7: oklch(0.35 0 0);
--black-l-6: oklch(0.3 0 0);
--black-l-5: oklch(0.25 0 0);
--black-l-4: oklch(0.2 0 0);
--black-l-3: oklch(0.15 0 0);
--black-l-2: oklch(0.1 0 0);
--black-l-1: oklch(0.05 0 0);
--black: oklch(0 0 0);
--white: oklch(1 0 0);
--white-d-1: oklch(0.95 0 0);
--white-d-2: oklch(0.9 0 0);
--white-d-3: oklch(0.85 0 0);
--white-d-4: oklch(0.8 0 0);
--white-d-5: oklch(0.75 0 0);
--white-d-6: oklch(0.7 0 0);
--white-d-7: oklch(0.65 0 0);
--white-d-8: oklch(0.6 0 0);
--white: oklch(1 0 0);
--white-d-1: oklch(0.95 0 0);
--white-d-2: oklch(0.9 0 0);
--white-d-3: oklch(0.85 0 0);
--white-d-4: oklch(0.8 0 0);
--white-d-5: oklch(0.75 0 0);
--white-d-6: oklch(0.7 0 0);
--white-d-7: oklch(0.65 0 0);
--white-d-8: oklch(0.6 0 0);
--dark-l-8: var(--black-l-8);
--dark-l-7: var(--black-l-7);
--dark-l-6: var(--black-l-6);
--dark-l-5: var(--black-l-5);
--dark-l-4: var(--black-l-4);
--dark-l-3: var(--black-l-3);
--dark-l-2: var(--black-l-2);
--dark-l-1: var(--black-l-1);
--dark: var(--black);
--dark-l-8: var(--black-l-8);
--dark-l-7: var(--black-l-7);
--dark-l-6: var(--black-l-6);
--dark-l-5: var(--black-l-5);
--dark-l-4: var(--black-l-4);
--dark-l-3: var(--black-l-3);
--dark-l-2: var(--black-l-2);
--dark-l-1: var(--black-l-1);
--dark: var(--black);
--light: var(--white);
--light-d-1: var(--white-d-1);
--light-d-2: var(--white-d-2);
--light-d-3: var(--white-d-3);
--light-d-4: var(--white-d-4);
--light-d-5: var(--white-d-5);
--light-d-6: var(--white-d-6);
--light-d-7: var(--white-d-7);
--light-d-8: var(--white-d-8);
--light: var(--white);
--light-d-1: var(--white-d-1);
--light-d-2: var(--white-d-2);
--light-d-3: var(--white-d-3);
--light-d-4: var(--white-d-4);
--light-d-5: var(--white-d-5);
--light-d-6: var(--white-d-6);
--light-d-7: var(--white-d-7);
--light-d-8: var(--white-d-8);
--dark-bg: var(--black-l-2);
--light-bg: var(--white);
--dark-bg: var(--black-l-2);
--light-bg: var(--white);
--border: var(--white-d-3);
--border: var(--white-d-3);
--gray-box-shadow: oklch(0.8 0.0001 271 / 0.7);
--blue-box-shadow: oklch(0.52 0.15 268 / 0.25);
--gray-box-shadow: oklch(0.8 0.0001 271 / 0.7);
--blue-box-shadow: oklch(0.52 0.15 268 / 0.25);
--light-hover: var(--light-d-2);
--light-hover: var(--light-d-2);
--invert: invert(0);
--color-negative-bg: oklch(59% 0.22706 29.244);
--color-negative-bg: oklch(59% 0.22706 29.244);
--color-negative-border: oklch(51% 0.22706 29.244);
}
@media (prefers-color-scheme: dark) {
body.color-scheme-auto {
--dark-l-8: var(--white-d-8);
--dark-l-7: var(--white-d-7);
--dark-l-6: var(--white-d-6);
--dark-l-5: var(--white-d-5);
--dark-l-4: var(--white-d-4);
--dark-l-3: var(--white-d-3);
--dark-l-2: var(--white-d-2);
--dark-l-1: var(--white-d-1);
--dark: var(--white);
--dark-l-8: var(--white-d-8);
--dark-l-7: var(--white-d-7);
--dark-l-6: var(--white-d-6);
--dark-l-5: var(--white-d-5);
--dark-l-4: var(--white-d-4);
--dark-l-3: var(--white-d-3);
--dark-l-2: var(--white-d-2);
--dark-l-1: var(--white-d-1);
--dark: var(--white);
--light: var(--black);
--light-d-1: oklch(0.10 0 0);
--light-d-2: var(--black-l-2);
--light-d-3: var(--black-l-3);
--light-d-4: var(--black-l-4);
--light-d-5: var(--black-l-5);
--light-d-6: var(--black-l-6);
--light-d-7: var(--black-l-7);
--light-d-8: var(--black-l-8);
--light: var(--black);
--light-d-1: oklch(0.1 0 0);
--light-d-2: var(--black-l-2);
--light-d-3: var(--black-l-3);
--light-d-4: var(--black-l-4);
--light-d-5: var(--black-l-5);
--light-d-6: var(--black-l-6);
--light-d-7: var(--black-l-7);
--light-d-8: var(--black-l-8);
--dark-bg: var(--white);
--light-bg: oklch(0.18 0 0);
--dark-bg: var(--white);
--light-bg: oklch(0.18 0 0);
--border: var(--black-l-6);
--border: var(--black-l-6);
--gray-box-shadow: oklch(0.25 0.0001 271 / 0.7);
--gray-box-shadow: oklch(0.25 0.0001 271 / 0.7);
--light-hover: var(--light-d-5);
--light-hover: var(--light-d-5);
--invert: invert(1);
}
}
body.dark {
--dark-l-8: var(--white-d-8);
--dark-l-7: var(--white-d-7);
--dark-l-6: var(--white-d-6);
--dark-l-5: var(--white-d-5);
--dark-l-4: var(--white-d-4);
--dark-l-3: var(--white-d-3);
--dark-l-2: var(--white-d-2);
--dark-l-1: var(--white-d-1);
--dark: var(--white);
--dark-l-8: var(--white-d-8);
--dark-l-7: var(--white-d-7);
--dark-l-6: var(--white-d-6);
--dark-l-5: var(--white-d-5);
--dark-l-4: var(--white-d-4);
--dark-l-3: var(--white-d-3);
--dark-l-2: var(--white-d-2);
--dark-l-1: var(--white-d-1);
--dark: var(--white);
--light: var(--black);
--light-d-1: oklch(0.10 0 0);
--light-d-2: var(--black-l-2);
--light-d-3: var(--black-l-3);
--light-d-4: var(--black-l-4);
--light-d-5: var(--black-l-5);
--light-d-6: var(--black-l-6);
--light-d-7: var(--black-l-7);
--light-d-8: var(--black-l-8);
--light: var(--black);
--light-d-1: oklch(0.1 0 0);
--light-d-2: var(--black-l-2);
--light-d-3: var(--black-l-3);
--light-d-4: var(--black-l-4);
--light-d-5: var(--black-l-5);
--light-d-6: var(--black-l-6);
--light-d-7: var(--black-l-7);
--light-d-8: var(--black-l-8);
--dark-bg: var(--white);
--light-bg: oklch(0.18 0 0);
--dark-bg: var(--white);
--light-bg: oklch(0.18 0 0);
--border: var(--black-l-6);
--border: var(--black-l-6);
--gray-box-shadow: oklch(0.25 0.0001 271 / 0.7);
--gray-box-shadow: oklch(0.25 0.0001 271 / 0.7);
--light-hover: var(--light-d-5);
--light-hover: var(--light-d-5);
--invert: invert(1);
}
+14 -15
View File
@@ -20,29 +20,32 @@ import { vi } from 'vitest';
import { createI18n } from 'vue-i18n';
import { createMemoryHistory, createRouter } from 'vue-router';
export const mountComponent = function mountComponent(component, options = {}, i18nMessages = {}, routes = [], piniaOptions = {}) {
const {
setupStores,
...testingPiniaOptions
} = piniaOptions;
export const mountComponent = function mountComponent(
component,
options = {},
i18nMessages = {},
routes = [],
piniaOptions = {},
) {
const { setupStores, ...testingPiniaOptions } = piniaOptions;
const i18n = createI18n({
legacy: false,
locale: 'en',
messages: {
en: i18nMessages
}
en: i18nMessages,
},
});
const router = createRouter({
history: createMemoryHistory(),
routes
routes,
});
const testingPinia = createTestingPinia({
...testingPiniaOptions,
stubActions: false,
createSpy: vi.fn
createSpy: vi.fn,
});
if (setupStores) {
@@ -51,12 +54,8 @@ export const mountComponent = function mountComponent(component, options = {}, i
return mount(component, {
global: {
plugins: [
i18n,
router,
testingPinia
],
plugins: [i18n, router, testingPinia],
},
...options
...options,
});
};
+100 -88
View File
@@ -19,106 +19,118 @@ import { describe, expect, test } from 'vitest';
describe('cssDimensions', () => {
test.for([
{ dimension: null, expected: '0px' },
{ dimension: undefined, expected: '0px' },
{ dimension: null, expected: '0px' },
{ dimension: undefined, expected: '0px' },
{ dimension: 0, expected: '0px' },
{ dimension: -0, expected: '0px' },
{ dimension: -1, expected: '-1px' },
{ dimension: -42, expected: '-42px' },
{ dimension: 1, expected: '1px' },
{ dimension: 42, expected: '42px' },
{ dimension: 0, expected: '0px' },
{ dimension: -0, expected: '0px' },
{ dimension: -1, expected: '-1px' },
{ dimension: -42, expected: '-42px' },
{ dimension: 1, expected: '1px' },
{ dimension: 42, expected: '42px' },
{ dimension: 0.0, expected: '0px' },
{ dimension: -0.0, expected: '0px' },
{ dimension: -1.0, expected: '-1px' },
{ dimension: -42.0, expected: '-42px' },
{ dimension: 1.0, expected: '1px' },
{ dimension: 42.0, expected: '42px' },
{ dimension: 0.0, expected: '0px' },
{ dimension: -0.0, expected: '0px' },
{ dimension: -1.0, expected: '-1px' },
{ dimension: -42.0, expected: '-42px' },
{ dimension: 1.0, expected: '1px' },
{ dimension: 42.0, expected: '42px' },
{ dimension: 0.42, expected: '0.42px' },
{ dimension: -0.42, expected: '-0.42px' },
{ dimension: -1.42, expected: '-1.42px' },
{ dimension: -42.42, expected: '-42.42px' },
{ dimension: 1.42, expected: '1.42px' },
{ dimension: 42.42, expected: '42.42px' },
{ dimension: 0.42, expected: '0.42px' },
{ dimension: -0.42, expected: '-0.42px' },
{ dimension: -1.42, expected: '-1.42px' },
{ dimension: -42.42, expected: '-42.42px' },
{ dimension: 1.42, expected: '1.42px' },
{ dimension: 42.42, expected: '42.42px' },
{ dimension: '0', expected: '0px' },
{ dimension: '-0', expected: '-0px' },
{ dimension: '-1', expected: '-1px' },
{ dimension: '-42', expected: '-42px' },
{ dimension: '1', expected: '1px' },
{ dimension: '42', expected: '42px' },
{ dimension: '0', expected: '0px' },
{ dimension: '-0', expected: '-0px' },
{ dimension: '-1', expected: '-1px' },
{ dimension: '-42', expected: '-42px' },
{ dimension: '1', expected: '1px' },
{ dimension: '42', expected: '42px' },
{ dimension: '0.0', expected: '0.0px' },
{ dimension: '-0.0', expected: '-0.0px' },
{ dimension: '-1.0', expected: '-1.0px' },
{ dimension: '-42.0', expected: '-42.0px' },
{ dimension: '1.0', expected: '1.0px' },
{ dimension: '42.0', expected: '42.0px' },
{ dimension: '0.0', expected: '0.0px' },
{ dimension: '-0.0', expected: '-0.0px' },
{ dimension: '-1.0', expected: '-1.0px' },
{ dimension: '-42.0', expected: '-42.0px' },
{ dimension: '1.0', expected: '1.0px' },
{ dimension: '42.0', expected: '42.0px' },
{ dimension: '0.42', expected: '0.42px' },
{ dimension: '-0.42', expected: '-0.42px' },
{ dimension: '-1.42', expected: '-1.42px' },
{ dimension: '-42.42', expected: '-42.42px' },
{ dimension: '1.42', expected: '1.42px' },
{ dimension: '42.42', expected: '42.42px' },
{ dimension: '0.42', expected: '0.42px' },
{ dimension: '-0.42', expected: '-0.42px' },
{ dimension: '-1.42', expected: '-1.42px' },
{ dimension: '-42.42', expected: '-42.42px' },
{ dimension: '1.42', expected: '1.42px' },
{ dimension: '42.42', expected: '42.42px' },
{ dimension: '0px', expected: '0px' },
{ dimension: '-0px', expected: '-0px' },
{ dimension: '-1px', expected: '-1px' },
{ dimension: '-42px', expected: '-42px' },
{ dimension: '1px', expected: '1px' },
{ dimension: '42px', expected: '42px' },
{ dimension: '0px', expected: '0px' },
{ dimension: '-0px', expected: '-0px' },
{ dimension: '-1px', expected: '-1px' },
{ dimension: '-42px', expected: '-42px' },
{ dimension: '1px', expected: '1px' },
{ dimension: '42px', expected: '42px' },
{ dimension: '0.0px', expected: '0.0px' },
{ dimension: '-0.0px', expected: '-0.0px' },
{ dimension: '-1.0px', expected: '-1.0px' },
{ dimension: '-42.0px', expected: '-42.0px' },
{ dimension: '1.0px', expected: '1.0px' },
{ dimension: '42.0px', expected: '42.0px' },
{ dimension: '0.0px', expected: '0.0px' },
{ dimension: '-0.0px', expected: '-0.0px' },
{ dimension: '-1.0px', expected: '-1.0px' },
{ dimension: '-42.0px', expected: '-42.0px' },
{ dimension: '1.0px', expected: '1.0px' },
{ dimension: '42.0px', expected: '42.0px' },
{ dimension: '0.42px', expected: '0.42px' },
{ dimension: '-0.42px', expected: '-0.42px' },
{ dimension: '-1.42px', expected: '-1.42px' },
{ dimension: '-42.42px', expected: '-42.42px' },
{ dimension: '1.42px', expected: '1.42px' },
{ dimension: '42.42px', expected: '42.42px' },
{ dimension: '0.42px', expected: '0.42px' },
{ dimension: '-0.42px', expected: '-0.42px' },
{ dimension: '-1.42px', expected: '-1.42px' },
{ dimension: '-42.42px', expected: '-42.42px' },
{ dimension: '1.42px', expected: '1.42px' },
{ dimension: '42.42px', expected: '42.42px' },
{ dimension: '0em', expected: '0em' },
{ dimension: '-0em', expected: '-0em' },
{ dimension: '-1em', expected: '-1em' },
{ dimension: '-42em', expected: '-42em' },
{ dimension: '1em', expected: '1em' },
{ dimension: '42em', expected: '42em' },
{ dimension: '0em', expected: '0em' },
{ dimension: '-0em', expected: '-0em' },
{ dimension: '-1em', expected: '-1em' },
{ dimension: '-42em', expected: '-42em' },
{ dimension: '1em', expected: '1em' },
{ dimension: '42em', expected: '42em' },
{ dimension: '0.0em', expected: '0.0em' },
{ dimension: '-0.0em', expected: '-0.0em' },
{ dimension: '-1.0em', expected: '-1.0em' },
{ dimension: '-42.0em', expected: '-42.0em' },
{ dimension: '1.0em', expected: '1.0em' },
{ dimension: '42.0em', expected: '42.0em' },
{ dimension: '0.0em', expected: '0.0em' },
{ dimension: '-0.0em', expected: '-0.0em' },
{ dimension: '-1.0em', expected: '-1.0em' },
{ dimension: '-42.0em', expected: '-42.0em' },
{ dimension: '1.0em', expected: '1.0em' },
{ dimension: '42.0em', expected: '42.0em' },
{ dimension: '0.42em', expected: '0.42em' },
{ dimension: '-0.42em', expected: '-0.42em' },
{ dimension: '-1.42em', expected: '-1.42em' },
{ dimension: '-42.42em', expected: '-42.42em' },
{ dimension: '1.42em', expected: '1.42em' },
{ dimension: '42.42em', expected: '42.42em' },
{ dimension: '0.42em', expected: '0.42em' },
{ dimension: '-0.42em', expected: '-0.42em' },
{ dimension: '-1.42em', expected: '-1.42em' },
{ dimension: '-42.42em', expected: '-42.42em' },
{ dimension: '1.42em', expected: '1.42em' },
{ dimension: '42.42em', expected: '42.42em' },
{ dimension: 'calc(42px - 1em)', expected: 'calc(42px - 1em)' },
{ dimension: 'calc(42px)', expected: 'calc(42px)' },
{ dimension: 'calc(42vh)', expected: 'calc(42vh)' },
{ dimension: 'min(42px, 1em)', expected: 'min(42px, 1em)' },
{ dimension: 'max(42px, 5rem)', expected: 'max(42px, 5rem)' },
{ dimension: 'clamp(42vh, 23vw, 13cap)', expected: 'clamp(42vh, 23vw, 13cap)' },
{ dimension: 'calc(42px - 1em)', expected: 'calc(42px - 1em)' },
{ dimension: 'calc(42px)', expected: 'calc(42px)' },
{ dimension: 'calc(42vh)', expected: 'calc(42vh)' },
{ dimension: 'min(42px, 1em)', expected: 'min(42px, 1em)' },
{ dimension: 'max(42px, 5rem)', expected: 'max(42px, 5rem)' },
{
dimension: 'clamp(42vh, 23vw, 13cap)',
expected: 'clamp(42vh, 23vw, 13cap)',
},
{ dimension: 'calc(42px-1em)', expected: 'calc(42px-1em)' },
{ dimension: 'min(42px,1em)', expected: 'min(42px,1em)' },
{ dimension: 'max(42px,5rem)', expected: 'max(42px,5rem)' },
{ dimension: 'clamp(42vh,23vw,13cap)', expected: 'clamp(42vh,23vw,13cap)' },
{ dimension: 'clamp( 42vh,23vw,13cap )', expected: 'clamp( 42vh,23vw,13cap )' }
])('ensureUnit returns $expected with input $dimension', ({ dimension, expected }) => {
expect(ensureUnit(dimension)).toBe(expected);
});
{ dimension: 'calc(42px-1em)', expected: 'calc(42px-1em)' },
{ dimension: 'min(42px,1em)', expected: 'min(42px,1em)' },
{ dimension: 'max(42px,5rem)', expected: 'max(42px,5rem)' },
{
dimension: 'clamp(42vh,23vw,13cap)',
expected: 'clamp(42vh,23vw,13cap)',
},
{
dimension: 'clamp( 42vh,23vw,13cap )',
expected: 'clamp( 42vh,23vw,13cap )',
},
])(
'ensureUnit returns $expected with input $dimension',
({ dimension, expected }) => {
expect(ensureUnit(dimension)).toBe(expected);
},
);
});
+2 -2
View File
@@ -22,7 +22,7 @@ const { t } = useI18n();
<template>
<div class="not-found main-content-padding">
<span class="error-message">
<span class="error-message">
{{ t('error.pageNotFound') }}
</span>
<RouterLink to="/" id="link">
@@ -40,7 +40,7 @@ const { t } = useI18n();
align-items: center;
gap: 12px;
}
.error-message{
.error-message {
margin: 0;
font-weight: 600;
font-size: 1.75rem;
+12 -7
View File
@@ -27,15 +27,20 @@ const searchQuery = ref('');
<template>
<div class="search-content main-content-padding">
<header class="global-header">
<img :src="logo" alt="Seekra" class="header-logo" />
<span class="slogan">
{{ t('slogan') }}
</span>
</header>
<header class="global-header">
<img :src="logo" alt="Seekra" class="header-logo" />
<span class="slogan">
{{ t('slogan') }}
</span>
</header>
<div class="search-container">
<SearchBar v-model="searchQuery" ref="searchbar" class="search-bar" auto-submit />
<SearchBar
v-model="searchQuery"
ref="searchbar"
class="search-bar"
auto-submit
/>
</div>
</div>
</template>
+18 -18
View File
@@ -21,22 +21,22 @@ import { createHtmlPlugin } from 'vite-plugin-html';
// https://vite.dev/config/
export default defineConfig({
build: {
target: 'es2020'
},
plugins: [
vue(),
createHtmlPlugin({
minify: true
})
],
resolve: {
alias: {
'@': path.resolve(__dirname, './src')
}
},
test: {
globals: false,
environment: 'jsdom'
}
build: {
target: 'es2020',
},
plugins: [
vue(),
createHtmlPlugin({
minify: true,
}),
],
resolve: {
alias: {
'@': path.resolve(__dirname, './src'),
},
},
test: {
globals: false,
environment: 'jsdom',
},
});