refactor: replace .eslintrc.cjs with eslint.config.js and restructure configuration

- Migrated from `.eslintrc.cjs` to `eslint.config.js` to leverage flat configuration format.
- Updated ESLint rules and integrated Vue 3, TypeScript, and global considerations.
- Adjusted project structure with improved component rendering patterns and prop validation logic.
- Introduced asynchronous imports for better performance.
- Cleaned up unused styles and refined templates/layouts, enhancing cohesion and readability.
- Updated `yarn.lock` to align with dependency changes and removed unused packages.
- Added `.nanocoder` commands for common tasks including `component`, `refactor`, `review`, and `test`.
- Improved tooling support (`agents.config.json`) with provider integrations and streamlined permissions.
This commit is contained in:
2025-09-26 20:31:22 +02:00
parent 08237f18e5
commit 698817693f
31 changed files with 6397 additions and 3716 deletions

View File

@@ -0,0 +1,15 @@
{
"permissions": {
"allow": [
"Bash(npm run typecheck:*)",
"Bash(npm run build:*)",
"Bash(timeout 10s npm run dev)",
"Bash(npm run format:*)",
"Bash(npm run analyze:*)",
"Bash(npx eslint:*)",
"Bash(git add:*)"
],
"deny": [],
"ask": []
}
}

View File

@@ -1,33 +0,0 @@
module.exports = {
root: true,
env: {
browser: true,
es2021: true,
node: true,
},
extends: [
'eslint:recommended',
'plugin:vue/vue3-recommended',
'@vue/eslint-config-typescript',
'eslint-config-vuetify',
],
parserOptions: {
ecmaVersion: 'latest',
sourceType: 'module',
},
rules: {
// Vue specific rules
'vue/multi-word-component-names': 'off',
'vue/no-undef-components': 'off', // Vuetify components are auto-imported
// TypeScript specific rules
'@typescript-eslint/no-unused-vars': ['error', { argsIgnorePattern: '^_' }],
'@typescript-eslint/no-explicit-any': 'warn',
// General code quality rules
'prefer-const': 'error',
'no-var': 'error',
'no-console': 'warn',
'no-debugger': 'error',
},
}

View File

@@ -0,0 +1,15 @@
---
description: Create a new UI component
aliases: [comp, ui]
parameters: [name, type]
---
Create a new {{type}} component named {{name}} that:
1. Follows project component patterns
2. Includes proper TypeScript types
3. Has responsive design considerations
4. Includes basic styling structure
5. Has proper prop validation
Make it reusable and well-documented.

View File

@@ -0,0 +1,15 @@
---
description: Refactor JavaScript/TypeScript code
aliases: [refactor-js, clean]
parameters: [target]
---
Refactor {{target}} to improve:
1. Code structure and organization
2. Modern ES6+ syntax usage
3. Performance optimizations
4. Type safety (for TypeScript)
5. Reusability and maintainability
Follow current project conventions and patterns.

View File

@@ -0,0 +1,15 @@
---
description: Review code and suggest improvements
aliases: [code-review, cr]
parameters: [files]
---
Review the code in {{files}} and provide detailed feedback on:
1. Code quality and best practices
2. Potential bugs or issues
3. Performance considerations
4. Readability and maintainability
5. Security concerns
Provide specific, actionable suggestions for improvement.

View File

@@ -0,0 +1,17 @@
---
description: Generate comprehensive unit tests
aliases: [unittest, test-gen]
parameters: [filename]
---
Generate comprehensive unit tests for {{filename}}.
Consider:
1. Test all public functions and methods
2. Include edge cases and error scenarios
3. Use appropriate mocking where needed
4. Follow existing test framework conventions
5. Ensure good test coverage
If no filename provided, suggest which files need tests.

View File

@@ -1,28 +0,0 @@
# AGENTS.md
## Setup & Commands
- Install dependencies: `npm install`
- Start the development server: `npm run dev`
- Lint and format: `npm run lint`
- Type-check & build: `npm run build`
## Current Architecture
- Auto-routed views live in `src/pages/` and use the layouts declared in `src/layouts/`.
- Shared UI elements are located in `src/components/`; global helpers live in `src/composables/` and `src/services/`.
- Navigation metadata and the component catalog are sourced from `src/services/componentCatalog.ts` and exposed through `useComponentCatalog`.
- Ambient type declarations (e.g. Prism.js module shims) are kept in `src/types/`.
## Refactor Highlights
- Deduplicated route and catalog metadata by consolidating on the `services/navigation` + `useComponentCatalog` flow.
- Converted the `Markup` code viewer to lazy-load Prism languages and share hyperlink handling through `services/prism`.
- Removed inline styles in favour of Vuetify utility classes or scoped styles.
- Added an ESLint configuration that enforces Vue 3 + TypeScript + Vuetify best practices under strict TypeScript settings.
## Maintenance Guidelines
- When adding catalog entries, update `src/services/componentCatalog.ts`; navigation drawers and pages consume the same source of truth.
- Prefer Vuetify utility classes; only introduce scoped CSS when utilities do not cover a use-case.
- Run `npm run lint` and `npm run build` before opening a PR to catch style or type regressions early.

View File

@@ -6,6 +6,8 @@ A Vue 3 + Vuetify playground for prototyping component ideas and documenting reu
- Install dependencies with `npm install`.
- Start the development server with `npm run dev` (Vite).
- Run the Vitest-powered unit suite with `npm run test:unit`.
- Trigger the placeholder end-to-end command with `npm run test:e2e` until the real harness lands.
- Lint the codebase with `npm run lint`.
- Type-check and build the production bundle with `npm run build`.

11
agents.config.json Normal file
View File

@@ -0,0 +1,11 @@
{
"nanocoder": {
"providers": [
{
"name": "Ollama",
"baseUrl": "http://localhost:11434/v1",
"models": ["qwen3-coder:latest", "gemma3n:e4b"]
}
]
}
}

55
eslint.config.js Normal file
View File

@@ -0,0 +1,55 @@
import globals from 'globals'
import js from '@eslint/js'
import eslintPluginVue from 'eslint-plugin-vue'
import typescriptEslintParser from '@typescript-eslint/parser'
import typescriptEslintPlugin from '@typescript-eslint/eslint-plugin'
import vueEslintParser from 'vue-eslint-parser'
export default [
{
ignores: ['dist/**'],
},
js.configs.recommended,
...eslintPluginVue.configs['flat/recommended'],
{
files: ['**/*.{js,ts,vue}'],
plugins: {
'@typescript-eslint': typescriptEslintPlugin,
},
languageOptions: {
globals: {
...globals.browser,
...globals.node,
ref: 'readonly',
computed: 'readonly',
defineAsyncComponent: 'readonly',
watchEffect: 'readonly',
onMounted: 'readonly',
onErrorCaptured: 'readonly',
nextTick: 'readonly',
},
parser: vueEslintParser,
parserOptions: {
parser: typescriptEslintParser,
sourceType: 'module',
},
},
rules: {
'no-unused-vars': 'off',
'no-undef': 'off',
'vue/multi-word-component-names': 'off',
'vue/no-undef-components': 'off',
'@typescript-eslint/no-unused-vars': [
'error',
{ argsIgnorePattern: '^_' },
],
'@typescript-eslint/no-explicit-any': 'warn',
'prefer-const': 'error',
'no-var': 'error',
'no-console': 'warn',
'no-debugger': 'error',
},
},
]

5712
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,13 +1,16 @@
{
"name": "nachtigall.dev",
"version": "0.0.0",
"type": "module",
"scripts": {
"dev": "vite",
"build": "vue-tsc --noEmit && vite build",
"preview": "vite preview",
"lint": "eslint . --fix --ignore-path .gitignore",
"lint": "eslint . --fix",
"format": "prettier --write .",
"test": "vitest",
"test": "vitest run",
"test:unit": "vitest run",
"test:e2e": "echo \"No E2E tests are currently defined\"",
"test:ui": "vitest --ui",
"test:coverage": "vitest --coverage",
"typecheck": "vue-tsc --noEmit",
@@ -25,46 +28,50 @@
},
"dependencies": {
"@mdi/font": "^7.4.47",
"core-js": "^3.37.1",
"prettier": "^3.3.3",
"core-js": "^3.45.1",
"prettier": "^3.6.2",
"prism-theme-vars": "^0.2.5",
"prismjs": "^1.29.0",
"prismjs": "^1.30.0",
"roboto-fontface": "*",
"vue": "^3.4.31",
"vuetify": "^3.6.11"
"vue": "^3.5.22",
"vuetify": "^3.10.3"
},
"devDependencies": {
"@babel/types": "^7.24.7",
"@types/node": "^20.14.10",
"@babel/types": "^7.28.4",
"@eslint/eslintrc": "^3.3.1",
"@eslint/js": "^9.36.0",
"@types/node": "^24.5.2",
"@types/prismjs": "^1.26.5",
"@vitejs/plugin-vue": "^5.0.5",
"@vitejs/plugin-vue": "^6.0.1",
"@vitest/ui": "^3.2.4",
"@vue/eslint-config-typescript": "^13.0.0",
"@vue/eslint-config-typescript": "^14.6.0",
"@vue/test-utils": "^2.4.6",
"eslint": "^8.57.0",
"eslint": "^9.36.0",
"eslint-config-standard": "^17.1.0",
"eslint-config-vuetify": "^1.0.0",
"eslint-plugin-import": "^2.29.1",
"eslint-plugin-n": "^16.6.2",
"eslint-config-vuetify": "^4.2.0",
"eslint-plugin-import": "^2.32.0",
"eslint-plugin-n": "^17.23.1",
"eslint-plugin-node": "^11.1.0",
"eslint-plugin-promise": "^6.4.0",
"eslint-plugin-vue": "^9.27.0",
"eslint-plugin-promise": "^7.2.1",
"eslint-plugin-vue": "^10.5.0",
"globals": "^16.4.0",
"husky": "^9.1.7",
"jsdom": "^27.0.0",
"lint-staged": "^16.1.6",
"pinia": "^2.1.7",
"lint-staged": "^16.2.1",
"npm-check-updates": "^18.3.0",
"pinia": "^3.0.3",
"rollup-plugin-visualizer": "^6.0.3",
"sass": "1.77.6",
"typescript": "^5.4.2",
"unplugin-auto-import": "^0.17.6",
"unplugin-fonts": "^1.1.1",
"unplugin-vue-components": "^0.27.2",
"unplugin-vue-router": "^0.10.0",
"vite": "^5.3.3",
"sass": "1.93.2",
"typescript": "^5.9.2",
"unplugin-auto-import": "^20.2.0",
"unplugin-fonts": "^1.4.0",
"unplugin-vue-components": "^29.1.0",
"unplugin-vue-router": "^0.15.0",
"vite": "^7.1.7",
"vite-plugin-vue-layouts": "^0.11.0",
"vite-plugin-vuetify": "^2.0.3",
"vite-plugin-vuetify": "^2.1.2",
"vitest": "^3.2.4",
"vue-router": "^4.4.0",
"vue-tsc": "^2.0.26"
"vue-router": "^4.5.1",
"vue-tsc": "^3.0.8"
}
}

12
src/auto-imports.d.ts vendored
View File

@@ -1,8 +1,9 @@
/* eslint-disable */
/* prettier-ignore */
// @ts-nocheck
// noinspection JSUnusedGlobalSymbols
// Generated by unplugin-auto-import
// biome-ignore lint: disable
export {}
declare global {
const EffectScope: typeof import('vue')['EffectScope']
@@ -14,12 +15,14 @@ declare global {
const effectScope: typeof import('vue')['effectScope']
const getCurrentInstance: typeof import('vue')['getCurrentInstance']
const getCurrentScope: typeof import('vue')['getCurrentScope']
const getCurrentWatcher: typeof import('vue')['getCurrentWatcher']
const h: typeof import('vue')['h']
const inject: typeof import('vue')['inject']
const isProxy: typeof import('vue')['isProxy']
const isReactive: typeof import('vue')['isReactive']
const isReadonly: typeof import('vue')['isReadonly']
const isRef: typeof import('vue')['isRef']
const isShallow: typeof import('vue')['isShallow']
const markRaw: typeof import('vue')['markRaw']
const nextTick: typeof import('vue')['nextTick']
const onActivated: typeof import('vue')['onActivated']
@@ -73,9 +76,10 @@ declare global {
// for type re-export
declare global {
// @ts-ignore
export type { Component, ComponentPublicInstance, ComputedRef, DirectiveBinding, ExtractDefaultPropTypes, ExtractPropTypes, ExtractPublicPropTypes, InjectionKey, PropType, Ref, MaybeRef, MaybeRefOrGetter, VNode, WritableComputedRef } from 'vue'
export type { Component, Slot, Slots, ComponentPublicInstance, ComputedRef, DirectiveBinding, ExtractDefaultPropTypes, ExtractPropTypes, ExtractPublicPropTypes, InjectionKey, PropType, Ref, ShallowRef, MaybeRef, MaybeRefOrGetter, VNode, WritableComputedRef } from 'vue'
import('vue')
}
// for vue template auto import
import { UnwrapRef } from 'vue'
declare module 'vue' {
@@ -90,12 +94,14 @@ declare module 'vue' {
readonly effectScope: UnwrapRef<typeof import('vue')['effectScope']>
readonly getCurrentInstance: UnwrapRef<typeof import('vue')['getCurrentInstance']>
readonly getCurrentScope: UnwrapRef<typeof import('vue')['getCurrentScope']>
readonly getCurrentWatcher: UnwrapRef<typeof import('vue')['getCurrentWatcher']>
readonly h: UnwrapRef<typeof import('vue')['h']>
readonly inject: UnwrapRef<typeof import('vue')['inject']>
readonly isProxy: UnwrapRef<typeof import('vue')['isProxy']>
readonly isReactive: UnwrapRef<typeof import('vue')['isReactive']>
readonly isReadonly: UnwrapRef<typeof import('vue')['isReadonly']>
readonly isRef: UnwrapRef<typeof import('vue')['isRef']>
readonly isShallow: UnwrapRef<typeof import('vue')['isShallow']>
readonly markRaw: UnwrapRef<typeof import('vue')['markRaw']>
readonly nextTick: UnwrapRef<typeof import('vue')['nextTick']>
readonly onActivated: UnwrapRef<typeof import('vue')['onActivated']>
@@ -140,4 +146,4 @@ declare module 'vue' {
readonly watchPostEffect: UnwrapRef<typeof import('vue')['watchPostEffect']>
readonly watchSyncEffect: UnwrapRef<typeof import('vue')['watchSyncEffect']>
}
}
}

1
src/components.d.ts vendored
View File

@@ -2,6 +2,7 @@
// @ts-nocheck
// Generated by unplugin-vue-components
// Read more: https://github.com/vuejs/core/pull/3399
// biome-ignore lint: disable
export {}
/* prettier-ignore */

View File

@@ -5,7 +5,7 @@
<v-col cols="12" lg="6" md="8">
<v-card class="pa-6">
<v-card-title class="text-h5 mb-4">
<v-icon class="me-2" color="error">mdi-alert-circle</v-icon>
<v-icon class="me-2" color="error"> mdi-alert-circle </v-icon>
Something went wrong
</v-card-title>
@@ -18,7 +18,7 @@
<v-expansion-panels v-if="errorDetails" variant="accordion">
<v-expansion-panel>
<v-expansion-panel-title>
<v-icon class="me-2">mdi-bug</v-icon>
<v-icon class="me-2"> mdi-bug </v-icon>
Error Details
</v-expansion-panel-title>
<v-expansion-panel-text>
@@ -30,12 +30,12 @@
<v-card-actions>
<v-btn color="primary" @click="retry">
<v-icon start>mdi-refresh</v-icon>
<v-icon start> mdi-refresh </v-icon>
Try Again
</v-btn>
<v-btn variant="text" @click="goHome">
<v-icon start>mdi-home</v-icon>
<v-icon start> mdi-home </v-icon>
Go Home
</v-btn>
</v-card-actions>

View File

@@ -11,7 +11,7 @@
rel="noopener noreferrer"
target="_blank"
>
<i aria-hidden="true" class="fa fa-gitea footer-icon" />
<i aria-hidden="true" class="fa fa-gitea app-icon" />
</a>
</div>
</v-footer>
@@ -23,8 +23,4 @@
.footer-content
gap: 5px
font-size: 0.8rem
.footer-icon
color: #609926
font-size: 1rem
</style>

View File

@@ -11,7 +11,7 @@
class="mx-n1"
>
<RouterLink
class="text-white text-decoration-none font-weight-black text-uppercase nav-link"
class="text-white text-decoration-none font-weight-black text-uppercase app-link"
:to="{ path: route.path }"
>
{{ route.displayLabel }}
@@ -35,51 +35,3 @@ const navigationLinks = computed(() =>
}))
)
</script>
<style lang="sass" scoped>
.nav-link
letter-spacing: 1px
.router-link-active
background: linear-gradient(270deg, #ff14e1, #1443ff, #ff1465)
-webkit-background-clip: text
-webkit-text-fill-color: transparent
background-size: 200% 200%
-webkit-animation: AnimationName 4s ease infinite
-moz-animation: AnimationName 4s ease infinite
-o-animation: AnimationName 4s ease infinite
animation: AnimationName 4s ease infinite
@-webkit-keyframes AnimationName
0%
background-position: 0% 50%
50%
background-position: 100% 50%
100%
background-position: 0% 50%
@-moz-keyframes AnimationName
0%
background-position: 0% 50%
50%
background-position: 100% 50%
100%
background-position: 0% 50%
@-o-keyframes AnimationName
0%
background-position: 0% 50%
50%
background-position: 100% 50%
100%
background-position: 0% 50%
@keyframes AnimationName
0%
background-position: 0% 50%
50%
background-position: 100% 50%
100%
background-position: 0% 50%
</style>

View File

@@ -1,4 +1,5 @@
<template>
<!-- eslint-disable vue/no-v-html -->
<v-sheet
ref="root"
class="app-markup overflow-hidden"
@@ -42,13 +43,19 @@
<div class="pa-4 pe-12">
<slot>
<pre v-if="inline" :class="className">
<code :class="className" v-html="highlighted" />
<!-- eslint-disable-next-line vue/no-v-html -->
<code
:class="className"
v-html="highlighted"
/>
</pre>
<!-- eslint-disable-next-line vue/no-v-html -->
<code v-else :class="className" v-html="highlighted" />
</slot>
</div>
</v-sheet>
<!-- eslint-enable vue/no-v-html -->
</template>
<script lang="ts" setup>
@@ -58,6 +65,7 @@ import 'prismjs/themes/prism.css'
// Services
import { highlightCode } from '@/services/prism'
import { wait } from '@/utils/helpers'
import { logger } from '@/utils/logger'
// Types
@@ -74,6 +82,8 @@ interface MarkupProps {
const props = withDefaults(defineProps<MarkupProps>(), {
inline: false,
language: 'markup',
resource: undefined,
code: null,
rounded: true,
})

View File

@@ -27,7 +27,6 @@ const toTitleCase = (value: string) =>
export function useComponentCatalog() {
const topics = computed(() => getComponentTopics())
// eslint-disable-next-line no-extra-parens
const groupedComponents = computed<
ComponentSection<(typeof componentCatalog)[number]>[]
>(() => {

View File

@@ -1,4 +1,4 @@
import { ref } from 'vue'
import { ref, Ref } from 'vue'
import { logger } from '@/utils/logger'
export interface ErrorState {
@@ -20,27 +20,27 @@ export interface UseErrorHandlerReturn {
* Composable for handling errors and loading states
*/
export function useErrorHandler(): UseErrorHandlerReturn {
const error = ref<ErrorState | null>(null)
const errorState = ref<ErrorState | null>(null)
const isLoading = ref(false)
const handleError = (err: Error, context?: string) => {
logger.error('Error occurred:', err, context ? `Context: ${context}` : '')
const handleError = (cause: Error, context?: string) => {
logger.error('Error occurred:', cause, context ? `Context: ${context}` : '')
error.value = {
errorState.value = {
hasError: true,
message: err.message || 'An unexpected error occurred',
details: err.stack,
message: cause.message || 'An unexpected error occurred',
details: cause.stack,
timestamp: new Date(),
}
isLoading.value = false
// You could integrate with error reporting service here
// reportError(err, context)
// reportError(cause, context)
}
const clearError = () => {
error.value = null
errorState.value = null
}
const setLoading = (loading: boolean) => {
@@ -51,7 +51,7 @@ export function useErrorHandler(): UseErrorHandlerReturn {
}
return {
error,
error: errorState,
handleError,
clearError,
isLoading,
@@ -68,8 +68,11 @@ export async function withErrorHandling<T>(
): Promise<T | null> {
try {
return await operation()
} catch (error) {
const err = error instanceof Error ? error : new Error(String(error))
} catch (unknownError) {
const err =
unknownError instanceof Error
? unknownError
: new Error(String(unknownError))
if (errorHandler) {
errorHandler(err)

View File

@@ -16,7 +16,7 @@
:key="route.path"
>
<RouterLink
class="text-white text-decoration-none font-weight-black components-nav-link"
class="text-white text-decoration-none font-weight-black components-nav-link app-link"
:to="{ path: route.path }"
>
{{ route.label }}
@@ -30,7 +30,7 @@
</h4>
<v-list-item v-for="route in section.items" :key="route.path">
<RouterLink
class="text-white text-decoration-none font-weight-black components-nav-link"
class="text-white text-decoration-none font-weight-black components-nav-link app-link"
:to="{ path: route.path }"
>
{{ route.label }}
@@ -72,47 +72,4 @@ const componentSections = computed(() =>
.components-nav-heading
font-size: 0.8rem
.router-link-active
background: linear-gradient(270deg, #ff14e1, #1443ff, #ff1465)
-webkit-background-clip: text
-webkit-text-fill-color: transparent
background-size: 200% 200%
-webkit-animation: AnimationName 4s ease infinite
-moz-animation: AnimationName 4s ease infinite
-o-animation: AnimationName 4s ease infinite
animation: AnimationName 4s ease infinite
@-webkit-keyframes AnimationName
0%
background-position: 0% 50%
50%
background-position: 100% 50%
100%
background-position: 0% 50%
@-moz-keyframes AnimationName
0%
background-position: 0% 50%
50%
background-position: 100% 50%
100%
background-position: 0% 50%
@-o-keyframes AnimationName
0%
background-position: 0% 50%
50%
background-position: 100% 50%
100%
background-position: 0% 50%
@keyframes AnimationName
0%
background-position: 0% 50%
50%
background-position: 100% 50%
100%
background-position: 0% 50%
</style>

View File

@@ -9,6 +9,7 @@ import { createApp } from 'vue'
import App from './App.vue'
import '@/styles/main.scss'
import { registerPlugins } from '@/plugins'
// Components

View File

@@ -13,7 +13,9 @@
:key="section.topic"
class="d-flex flex-column"
>
<h2 class="mb-4 text-uppercase">{{ section.label }}</h2>
<h2 class="mb-4 text-uppercase">
{{ section.label }}
</h2>
<v-col class="d-flex flex-wrap components-grid">
<v-hover v-for="component in section.items" :key="component.path">
<template #default="{ isHovering, props }">

View File

@@ -17,62 +17,65 @@
<v-row>
<v-col>
<h5 class="text-h5">Beispiel</h5>
<!-- eslint-disable vue/no-v-html -->
<div class="text-error" v-html="htmlCode" />
<!-- eslint-enable vue/no-v-html -->
</v-col>
</v-row>
<v-row>
<v-row v-for="sample in codeSamples" :key="sample.language">
<v-col>
<h6 class="text-h6">HTML Code</h6>
<Markup :code="htmlCode" language="html" />
</v-col>
</v-row>
<v-row>
<v-col>
<h6 class="text-h6">TypeScript Code</h6>
<Markup :code="typeScriptCode" language="ts" />
</v-col>
</v-row>
<v-row>
<v-col>
<h4 class="text-h6">CSS Code</h4>
<Markup :code="cssCode" language="css" />
</v-col>
</v-row>
<v-row>
<v-col>
<h4 class="text-h6">SCSS Code</h4>
<Markup :code="scssCode" language="scss" />
</v-col>
</v-row>
<v-row>
<v-col>
<h4 class="text-h6">SASS Code</h4>
<Markup :code="sassCode" language="sass" />
<h6 class="text-h6">
{{ sample.heading }}
</h6>
<Markup :code="sample.code" :language="sample.language" />
</v-col>
</v-row>
</v-container>
</template>
<script lang="ts" setup>
import Markup from '@/components/app/Markup.vue'
const Markup = defineAsyncComponent(() => import('@/components/app/Markup.vue'))
// htmlCode is rendered directly for demo purposes and remains static
const htmlCode = `<h2 class="header-1">
Hello world!
</h2>`
const typeScriptCode = `console.log('hello world!')`
const cssCode = `.header-1 {
interface CodeSample {
readonly heading: string
readonly language: string
readonly code: string
}
const codeSamples: readonly CodeSample[] = [
{ heading: 'HTML Code', language: 'html', code: htmlCode },
{
heading: 'TypeScript Code',
language: 'ts',
code: `console.log('hello world!')`,
},
{
heading: 'CSS Code',
language: 'css',
code: `.header-1 {
color: red;
}`
const scssCode = `.header-1 {
}`,
},
{
heading: 'SCSS Code',
language: 'scss',
code: `.header-1 {
color: red;
}`
const sassCode = `.header-1
color: red`
}`,
},
{
heading: 'SASS Code',
language: 'sass',
code: `.header-1
color: red`,
},
]
</script>
<style lang="sass" scoped></style>

View File

@@ -1,6 +1,7 @@
import type PrismType from 'prismjs'
import { stripLinks } from '@/utils/api'
import { logger } from '@/utils/logger'
let prismPromise: Promise<typeof PrismType> | null = null
let hyperlinkHookRegistered = false
@@ -17,19 +18,34 @@ const languageLoaders: ReadonlyArray<() => Promise<unknown>> = [
async function loadPrism(): Promise<typeof PrismType> {
if (!prismPromise) {
prismPromise = import('prismjs').then(async module => {
await Promise.all(languageLoaders.map(loader => loader()))
prismPromise = import('prismjs')
.then(async module => {
await Promise.all(
languageLoaders.map(async loader => {
try {
await loader()
} catch (error) {
logger.error('Failed to load Prism language', error)
throw error
}
})
)
const prism: typeof PrismType =
module.default ?? (module as unknown as typeof PrismType)
const prism: typeof PrismType =
module.default ?? (module as unknown as typeof PrismType)
if (!hyperlinkHookRegistered) {
registerHyperlinkHook(prism)
hyperlinkHookRegistered = true
}
if (!hyperlinkHookRegistered) {
registerHyperlinkHook(prism)
hyperlinkHookRegistered = true
}
return prism
})
return prism
})
.catch(error => {
prismPromise = null
logger.error('Failed to initialise Prism', error)
throw error
})
}
return prismPromise

View File

@@ -27,8 +27,3 @@
color: #609926;
font-size: 1rem;
}
.footer-meta {
gap: 5px;
font-size: 0.8rem;
}

36
src/typed-router.d.ts vendored
View File

@@ -22,4 +22,40 @@ declare module 'vue-router/auto-routes' {
'/components/': RouteRecordInfo<'/components/', '/components', Record<never, never>, Record<never, never>>,
'/components/tester': RouteRecordInfo<'/components/tester', '/components/tester', Record<never, never>, Record<never, never>>,
}
/**
* Route file to route info map by unplugin-vue-router.
* Used by the volar plugin to automatically type useRoute()
*
* Each key is a file path relative to the project root with 2 properties:
* - routes: union of route names of the possible routes when in this page (passed to useRoute<...>())
* - views: names of nested views (can be passed to <RouterView name="...">)
*
* @internal
*/
export interface _RouteFileInfoMap {
'src/pages/index.vue': {
routes: '/'
views: never
}
'src/pages/components/index.vue': {
routes: '/components/'
views: never
}
'src/pages/components/tester.vue': {
routes: '/components/tester'
views: never
}
}
/**
* Get a union of possible route names in a certain route component file.
* Used by the volar plugin to automatically type useRoute()
*
* @internal
*/
export type _RouteNamesForFilePath<FilePath extends string> =
_RouteFileInfoMap extends Record<FilePath, infer Info>
? Info['routes']
: keyof RouteNamedMap
}

View File

@@ -1,3 +1,5 @@
/// <reference lib="dom" />
export interface Item {
readonly name: string
readonly source: string

5
src/vite-env.d.ts vendored
View File

@@ -2,6 +2,9 @@
declare module '*.vue' {
import type { DefineComponent } from 'vue'
const component: DefineComponent<{}, {}, any>
const component: DefineComponent<
Record<string, unknown>,
Record<string, unknown>
>
export default component
}

View File

@@ -10,6 +10,7 @@ export default defineConfig({
globals: true,
environment: 'jsdom',
setupFiles: ['./src/test/setup.ts'],
include: ['src/**/*.spec.ts', 'src/**/*.test.ts'],
coverage: {
provider: 'v8',
reporter: ['text', 'json', 'html'],

3795
yarn.lock

File diff suppressed because it is too large Load Diff