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:
15
.claude/settings.local.json
Normal file
15
.claude/settings.local.json
Normal 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": []
|
||||
}
|
||||
}
|
||||
@@ -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',
|
||||
},
|
||||
}
|
||||
15
.nanocoder/commands/component.md
Normal file
15
.nanocoder/commands/component.md
Normal 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.
|
||||
15
.nanocoder/commands/refactor.md
Normal file
15
.nanocoder/commands/refactor.md
Normal 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.
|
||||
15
.nanocoder/commands/review.md
Normal file
15
.nanocoder/commands/review.md
Normal 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.
|
||||
17
.nanocoder/commands/test.md
Normal file
17
.nanocoder/commands/test.md
Normal 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.
|
||||
28
AGENTS.md
28
AGENTS.md
@@ -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.
|
||||
@@ -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
11
agents.config.json
Normal 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
55
eslint.config.js
Normal 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
5712
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
65
package.json
65
package.json
@@ -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
12
src/auto-imports.d.ts
vendored
@@ -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
1
src/components.d.ts
vendored
@@ -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 */
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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,
|
||||
})
|
||||
|
||||
|
||||
@@ -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]>[]
|
||||
>(() => {
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -9,6 +9,7 @@ import { createApp } from 'vue'
|
||||
|
||||
import App from './App.vue'
|
||||
|
||||
import '@/styles/main.scss'
|
||||
import { registerPlugins } from '@/plugins'
|
||||
|
||||
// Components
|
||||
|
||||
@@ -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 }">
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -27,8 +27,3 @@
|
||||
color: #609926;
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
.footer-meta {
|
||||
gap: 5px;
|
||||
font-size: 0.8rem;
|
||||
}
|
||||
|
||||
36
src/typed-router.d.ts
vendored
36
src/typed-router.d.ts
vendored
@@ -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
|
||||
}
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
/// <reference lib="dom" />
|
||||
|
||||
export interface Item {
|
||||
readonly name: string
|
||||
readonly source: string
|
||||
|
||||
5
src/vite-env.d.ts
vendored
5
src/vite-env.d.ts
vendored
@@ -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
|
||||
}
|
||||
|
||||
@@ -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'],
|
||||
|
||||
Reference in New Issue
Block a user