Author SHA1 Message Date
jakob.scheid ffee1d0569 chore(utils): migrate CSS dimensions utility to TypeScript
Added type annotations to the CSS dimensions utility (cssDimensions) and
to its testing module. Both have been updated to .ts.
2026-10-25 02:37:18 +01:00
jakob.scheid 0140e5669e chore: migrate main module to TypeScript
Migrated main module (src/main.js) to TypeScript. Fonts configuration
has been moved from JSON to TypeScript module to make fonts type
annotations easier.
2026-10-25 02:24:14 +01:00
jakob.scheid a4a857062b chore(i18n): migrate i18n to TypeScript
Updated i18n module to .ts and added type annotations.
2026-10-25 02:36:17 +02:00
jakob.scheid 6bfef99cda chore: migrate router to TypeScript
Added type annotations to router module and updated it to .ts.
2026-10-25 02:36:16 +02:00
jakob.scheid 72b7d313ed chore: migrate config to TypeScript
Updated config utility modules to .ts, added types to them, and updated
imports depending on those.
2026-10-25 02:36:16 +02:00
jakob.scheid b1dc260d47 chore: add Vite environment types
Added src/vite-env.d.ts containing the Vite environment type interfaces.
2026-10-25 02:36:16 +02:00
jakob.scheid 3c9fd3052f chore: add TypeScript support
Added TypeScript configuration for Vite and Node (tsconfig.app.json and
tsconfig.node.json, referenced in tsconfig.json).
Vite configuration was migrated to TypeScript.
Added ESLint TypeScript rules and general ESLint TypeScript
configuration.
Npm scripts for type checking have been added and build script has been
updated to run type checking script before building.
2026-10-25 02:36:10 +02:00
jakob.scheid 7cb1c2a403 chore(deps): add @types/node dependency 2026-10-24 23:04:35 +02:00
jakob.scheid e0a29b011c chore(deps): fix security vulnerabilities 2026-10-24 20:31:53 +02:00
jakob.scheid 9886d1e61e chore(deps): add vue-tsc dependency 2026-10-24 20:31:48 +02:00
jakob.scheid f6d5ba28e0 chore(deps): add typescript-eslint dependency 2026-10-24 20:31:41 +02:00
jakob.scheid ac44b59a61 chore(deps): add typescript dependency 2026-10-24 20:31:35 +02:00
20 changed files with 2668 additions and 2323 deletions

No files matched your search

-1
View File
@@ -1,3 +1,2 @@
.gitea/
.github/
eslint.config.js
+69 -26
View File
@@ -1,11 +1,40 @@
import js from '@eslint/js';
import { defineConfig } from 'eslint/config';
import pluginVue from 'eslint-plugin-vue';
import tseslint from 'typescript-eslint';
import vueParser from 'vue-eslint-parser';
import globals from 'globals';
const commonRules = {
'eqeqeq': 'error', // prettier-ignore
'prefer-const': 'error',
'no-var': 'error',
'no-duplicate-imports': 'error',
'no-import-assign': 'error',
'prefer-arrow-callback': 'error',
'require-await': 'warn',
'no-async-promise-executor': 'error',
'no-unreachable': 'error',
'no-constant-condition': 'error',
'no-debugger': 'error',
'no-alert': 'error',
'no-console': 'warn',
'no-new-object': 'error',
'no-array-constructor': 'error',
'no-prototype-builtins': 'error',
'object-shorthand': 'error',
'prefer-template': 'error',
};
export default defineConfig([
{
files: ['**/*.{js,mjs,cjs,vue}'],
files: ['**/*.{js,mjs,cjs,ts,mts,cts,vue}'],
plugins: { js },
extends: ['js/recommended'],
languageOptions: {
@@ -14,37 +43,51 @@ export default defineConfig([
...globals.node,
},
},
rules: {
'eqeqeq': 'error',
rules: commonRules,
},
pluginVue.configs['flat/essential'],
{
files: ['**/*.{js,mjs,cjs}'],
rules: {
'no-unused-vars': 'error',
'no-undef': 'error',
'prefer-const': 'error',
'no-var': 'error',
'no-duplicate-imports': 'error',
'no-import-assign': 'error',
'no-empty-function': 'error',
'default-param-last': 'error',
'prefer-arrow-callback': 'error',
'require-await': 'warn',
'no-async-promise-executor': 'error',
'no-unreachable': 'error',
'no-constant-condition': 'error',
'no-debugger': 'error',
'no-alert': 'error',
'no-console': 'warn',
'no-new-object': 'error',
'no-array-constructor': 'error',
'no-prototype-builtins': 'error',
'object-shorthand': 'error',
'prefer-template': 'error',
},
},
pluginVue.configs['flat/essential'],
{
files: ['**/*.{ts,mts,cts,vue}'],
extends: [tseslint.configs.recommended],
languageOptions: {
globals: {
...globals.browser,
...globals.node,
},
},
rules: {
'no-undef': 'off',
'@typescript-eslint/no-unused-vars': 'error',
'@typescript-eslint/no-empty-function': 'error',
'@typescript-eslint/default-param-last': 'error',
'@typescript-eslint/consistent-type-imports': 'error',
'@typescript-eslint/no-explicit-any': 'warn',
},
},
{
files: ['**/*.vue'],
languageOptions: {
parser: vueParser,
parserOptions: {
parser: tseslint.parser,
extraFileExtensions: ['.vue'],
},
},
},
]);
+2387 -2244
View File
File diff suppressed because it is too large. Load diff
+8 -2
View File
@@ -5,8 +5,10 @@
"type": "module",
"scripts": {
"dev": "vite",
"build": "vite build",
"build": "npm run type-check && vite build",
"preview": "vite preview",
"type-check": "vue-tsc --noEmit -p tsconfig.app.json && tsc --noEmit -p tsconfig.node.json",
"type-check:watch": "vue-tsc --noEmit -p tsconfig.app.json --watch",
"test": "vitest",
"test:run": "vitest run",
"lint": "eslint .",
@@ -25,6 +27,7 @@
"devDependencies": {
"@eslint/js": "^10.0.1",
"@pinia/testing": "^1.0.3",
"@types/node": "^26.6.5",
"@vitejs/plugin-vue": "^6.0.6",
"@vue/test-utils": "^2.4.6",
"eslint": "^10.8.1",
@@ -33,8 +36,11 @@
"globals": "^17.9.0",
"jsdom": "^29.1.1",
"prettier": "^3.9.6",
"typescript": "^6.0.3",
"typescript-eslint": "^8.72.0",
"vite": "^8.0.10",
"vite-plugin-html": "^3.2.2",
"vitest": "^4.1.7"
"vitest": "^4.1.7",
"vue-tsc": "^3.3.13"
}
}
+1 -1
View File
@@ -15,7 +15,7 @@ limitations under the License.
-->
<script setup>
import { LANGUAGE_PATH } from './config/featureSettings.js';
import { LANGUAGE_PATH } from './config/featureSettings';
import AppFooter from './features/footer/components/AppFooter.vue';
import AppNavbar from './features/nav/components/AppNavbar.vue';
import { useSettings } from './features/settings/composables/useSettings.js';
+1 -1
View File
@@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
export const getAssetsUrl = function getAssetsUrl() {
export const getAssetsUrl = function getAssetsUrl(): string {
return new URL(
`${import.meta.env.VITE_ASSETS_BUILD_ID}/`,
import.meta.env.VITE_ASSETS_BASE_URL,
File renamed without changes.
@@ -15,7 +15,7 @@ limitations under the License.
*/
import LanguageSwitchButton from '../LanguageSwitchButton.vue';
import { LANGUAGE_PATH } from '@/config/featureSettings.js';
import { LANGUAGE_PATH } from '@/config/featureSettings';
import { useSettings } from '@/features/settings/composables/useSettings.js';
import { mountComponent } from '@/test-utils/mountComponent.js';
import { describe, expect, test } from 'vitest';
+7 -3
View File
@@ -28,9 +28,11 @@ export const i18n = createI18n({
messages: {},
});
const loadedLanguages = new Set();
const loadedLanguages = new Set<string>();
export const loadLanguage = async function loadLanguage(locale) {
export const loadLanguage = async function loadLanguage(
locale: string,
): Promise<void> {
if (!SUPPORTED_LANGUAGES.includes(locale)) {
locale = fallbackLocale;
}
@@ -48,7 +50,9 @@ export const loadLanguage = async function loadLanguage(locale) {
loadedLanguages.add(locale);
};
export const setLocale = async function setLocale(locale) {
export const setLocale = async function setLocale(
locale: string,
): Promise<void> {
await loadLanguage(locale);
document.documentElement.lang = locale;
document.documentElement.dir = LANGUAGES_RTL.includes(locale)
+1 -1
View File
@@ -23,7 +23,7 @@ import { createApp } from 'vue';
import App from './App.vue';
import './styles/common.css';
import rawFonts from './styles/fonts.json';
import rawFonts from './styles/fonts';
import './styles/variables/colors.css';
import './styles/variables/dimensions.css';
+12 -5
View File
@@ -21,9 +21,14 @@ import SettingsView from '@/features/settings/views/SettingsView.vue';
import NotFound from '@/views/NotFound.vue';
import { i18n } from '@/i18n';
import { createRouter, createWebHistory } from 'vue-router';
import {
createRouter,
createWebHistory,
type RouteLocationNormalizedLoaded,
type RouteRecordRaw,
} from 'vue-router';
const routes = [
const routes: RouteRecordRaw[] = [
{
path: '/',
name: 'startPage',
@@ -37,7 +42,7 @@ const routes = [
searchQuery: route.query.q,
}),
meta: {
title: (route) => route.query.q,
title: (route: RouteLocationNormalizedLoaded) => route.query.q,
},
},
{
@@ -68,7 +73,9 @@ const router = createRouter({
routes,
});
export const updatePageTitle = function updatePageTitle(route) {
export const updatePageTitle = function updatePageTitle(
route: RouteLocationNormalizedLoaded,
): void {
const title =
typeof route.meta.title === 'function'
? route.meta.title(route)
@@ -82,7 +89,7 @@ export const updatePageTitle = function updatePageTitle(route) {
};
// remove trailing slash(es)
router.beforeEach((route) => {
router.beforeEach((route: RouteLocationNormalizedLoaded) => {
if (route.path !== '/' && route.path.endsWith('/')) {
return {
path: route.path.replace(/\/+$/, ''),
-32
View File
@@ -1,32 +0,0 @@
[
{
"family": "Inter",
"weight": "100 900",
"style": "normal",
"source": [
{
"url": "fonts/Inter/Inter-Variable.woff2",
"format": "woff2"
},
{
"url": "fonts/Inter/Inter-Variable.ttf",
"format": "truetype"
}
]
},
{
"family": "Inter",
"weight": "100 900",
"style": "italic",
"source": [
{
"url": "fonts/Inter/Inter-Italic-Variable.woff2",
"format": "woff2"
},
{
"url": "fonts/Inter/Inter-Italic-Variable.ttf",
"format": "truetype"
}
]
}
]
+65
View File
@@ -0,0 +1,65 @@
/*
Copyright 2026 Seekra
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
type FontFormat =
'woff2' | 'woff' | 'truetype' | 'opentype' | 'embedded-opentype' | 'svg';
interface FontSource {
url: string;
format: FontFormat;
}
interface RawFont {
family: string;
weight: string;
style: string;
source: FontSource[];
}
const fonts: RawFont[] = [
{
family: 'Inter',
weight: '100 900',
style: 'normal',
source: [
{
url: 'fonts/Inter/Inter-Variable.woff2',
format: 'woff2',
},
{
url: 'fonts/Inter/Inter-Variable.ttf',
format: 'truetype',
},
],
},
{
family: 'Inter',
weight: '100 900',
style: 'italic',
source: [
{
url: 'fonts/Inter/Inter-Italic-Variable.woff2',
format: 'woff2',
},
{
url: 'fonts/Inter/Inter-Italic-Variable.ttf',
format: 'truetype',
},
],
},
];
export default fonts;
@@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import { ensureUnit } from '../cssDimensions';
import { ensureUnit, type RawDimension } from '../cssDimensions';
import { describe, expect, test } from 'vitest';
describe('cssDimensions', () => {
@@ -129,7 +129,13 @@ describe('cssDimensions', () => {
},
])(
'ensureUnit returns $expected with input $dimension',
({ dimension, expected }) => {
({
dimension,
expected,
}: {
dimension: RawDimension;
expected: string;
}) => {
expect(ensureUnit(dimension)).toBe(expected);
},
);
@@ -14,7 +14,13 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
export const ensureUnit = function ensureUnit(d) {
/*
* null and undefined because they are intended to represent empty input
* values
*/
export type RawDimension = null | number | string | undefined;
export const ensureUnit = function ensureUnit(d: RawDimension): string {
if (!d) d = 0;
// ensure that it is a string
+24
View File
@@ -0,0 +1,24 @@
/*
Copyright 2026 Seekra
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
interface ImportMetaEnv {
readonly VITE_ASSETS_BUILD_ID: string;
readonly VITE_ASSETS_BASE_URL: string;
}
interface ImportMeta {
readonly env: ImportMetaEnv;
}
+32
View File
@@ -0,0 +1,32 @@
{
"compilerOptions": {
"target": "ES2020",
"lib": ["ES2020", "DOM", "DOM.Iterable"],
"module": "esnext",
"moduleResolution": "bundler",
"strict": true,
"noEmit": true,
"isolatedModules": true,
"verbatimModuleSyntax": true,
"noImplicitAny": true,
"allowJs": true,
"checkJs": false,
"resolveJsonModule": true,
"skipLibCheck": true,
"types": ["vite/client"],
"paths": {
"@/*": ["./src/*"]
},
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo",
"useDefineForClassFields": true,
"jsx": "preserve",
"esModuleInterop": true
},
"include": ["src/**/*.ts", "src/**/*.js", "src/**/*.vue"]
}
+11
View File
@@ -0,0 +1,11 @@
{
"files": [],
"references": [
{
"path": "./tsconfig.app.json"
},
{
"path": "./tsconfig.node.json"
}
]
}
+31
View File
@@ -0,0 +1,31 @@
{
"compilerOptions": {
"target": "es2020",
"lib": ["ES2020"],
"module": "esnext",
"moduleResolution": "bundler",
"strict": true,
"noEmit": true,
"isolatedModules": true,
"verbatimModuleSyntax": true,
"noImplicitAny": true,
"allowJs": true,
"checkJs": false,
"skipLibCheck": true,
"types": ["node"],
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo"
},
"include": [
"vite.config.js",
"vite.config.ts",
"vitest.config.js",
"vitest.config.ts",
"eslint.config.js",
"eslint.config.ts"
]
}
+3 -3
View File
@@ -15,8 +15,8 @@ limitations under the License.
*/
import vue from '@vitejs/plugin-vue';
import path from 'path';
import { defineConfig } from 'vite';
import { fileURLToPath, URL } from 'node:url';
import { defineConfig } from 'vitest/config';
import { createHtmlPlugin } from 'vite-plugin-html';
// https://vite.dev/config/
@@ -32,7 +32,7 @@ export default defineConfig({
],
resolve: {
alias: {
'@': path.resolve(__dirname, './src'),
'@': fileURLToPath(new URL('./src', import.meta.url)),
},
},
test: {