├── packages ├── playground │ ├── README.md │ ├── src │ │ ├── postcss.config.js │ │ ├── vite-env.d.ts │ │ ├── App.vue │ │ ├── pages │ │ │ ├── register.vue │ │ │ ├── about.vue │ │ │ ├── contact.vue │ │ │ ├── index.vue │ │ │ ├── dashboard │ │ │ │ ├── profile.vue │ │ │ │ ├── posts.vue │ │ │ │ ├── users.vue │ │ │ │ └── index.vue │ │ │ └── login.vue │ │ ├── layouts │ │ │ ├── auth.vue │ │ │ ├── dashboard.vue │ │ │ └── default.vue │ │ ├── middleware │ │ │ ├── logger.ts │ │ │ ├── index.ts │ │ │ └── dashboard.ts │ │ ├── router │ │ │ ├── index.ts │ │ │ └── routes.ts │ │ ├── style.css │ │ ├── composables │ │ │ └── index.ts │ │ ├── components │ │ │ └── Navbar.vue │ │ └── main.ts │ ├── vercel.json │ ├── postcss.config.js │ ├── tailwind.config.js │ ├── tsconfig.node.json │ ├── vite.config.ts │ ├── index.html │ ├── package.json │ └── tsconfig.json └── vue-middleware │ ├── src │ ├── drivers │ │ ├── index.ts │ │ ├── driver.ts │ │ └── laravel.ts │ ├── globalDeclarations.ts │ ├── composables │ │ └── index.ts │ ├── index.ts │ └── handler.ts │ ├── tsconfig.json │ ├── vite.config.ts │ ├── package.json │ ├── LICENSE │ └── README.md ├── pnpm-workspace.yaml ├── .vscode └── extensions.json ├── docs ├── installation.md ├── package.json ├── public │ └── vue-logo.svg ├── introduction.md ├── api-examples.md ├── index.md ├── page-title.md ├── can-and-is.md ├── .vitepress │ └── config.mts ├── custom-drivers.md ├── quick-usage.md ├── roles-and-permissions.md ├── basics.md └── pnpm-lock.yaml ├── .gitignore ├── tsconfig.json ├── package.json ├── LICENSE ├── README.md └── scripts └── build.js /packages/playground/README.md: -------------------------------------------------------------------------------- 1 | # Vue middleware playground -------------------------------------------------------------------------------- /pnpm-workspace.yaml: -------------------------------------------------------------------------------- 1 | packages: 2 | - docs 3 | - packages/* -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": ["Vue.volar"] 3 | } 4 | -------------------------------------------------------------------------------- /packages/playground/src/postcss.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | // 3 | } -------------------------------------------------------------------------------- /packages/playground/src/vite-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | -------------------------------------------------------------------------------- /packages/playground/src/App.vue: -------------------------------------------------------------------------------- 1 | 4 | -------------------------------------------------------------------------------- /packages/playground/src/pages/register.vue: -------------------------------------------------------------------------------- 1 | 4 | -------------------------------------------------------------------------------- /packages/playground/src/layouts/auth.vue: -------------------------------------------------------------------------------- 1 | 4 | -------------------------------------------------------------------------------- /packages/playground/src/pages/about.vue: -------------------------------------------------------------------------------- 1 | 4 | -------------------------------------------------------------------------------- /packages/playground/src/pages/contact.vue: -------------------------------------------------------------------------------- 1 | 4 | -------------------------------------------------------------------------------- /packages/vue-middleware/src/drivers/index.ts: -------------------------------------------------------------------------------- 1 | export { LaravelPermissionsDriver } from './laravel' 2 | -------------------------------------------------------------------------------- /packages/playground/vercel.json: -------------------------------------------------------------------------------- 1 | { 2 | "rewrites": [{ "source": "/(.*)", "destination": "/" }] 3 | } 4 | -------------------------------------------------------------------------------- /packages/playground/src/middleware/logger.ts: -------------------------------------------------------------------------------- 1 | export default () => { 2 | console.log('> 🔥 The `logger` middleware..') 3 | } 4 | -------------------------------------------------------------------------------- /packages/playground/postcss.config.js: -------------------------------------------------------------------------------- 1 | export default { 2 | plugins: { 3 | tailwindcss: {}, 4 | autoprefixer: {}, 5 | }, 6 | } 7 | -------------------------------------------------------------------------------- /docs/installation.md: -------------------------------------------------------------------------------- 1 | # Installation 2 | 3 | Quickly install `vue-middleware` into your project, in this example we're using npm. 4 | 5 | ``` 6 | npm i vue-middleware 7 | ``` 8 | -------------------------------------------------------------------------------- /packages/playground/src/middleware/index.ts: -------------------------------------------------------------------------------- 1 | import dashboard from './dashboard' 2 | import logger from './logger' 3 | 4 | export default { 5 | dashboard, 6 | logger, 7 | } 8 | -------------------------------------------------------------------------------- /packages/playground/src/layouts/dashboard.vue: -------------------------------------------------------------------------------- 1 | 5 | 6 | 9 | -------------------------------------------------------------------------------- /packages/playground/src/layouts/default.vue: -------------------------------------------------------------------------------- 1 | 5 | 6 | 9 | -------------------------------------------------------------------------------- /packages/playground/src/router/index.ts: -------------------------------------------------------------------------------- 1 | import { createRouter, createWebHistory } from 'vue-router' 2 | import routes from './routes' 3 | 4 | export default createRouter({ 5 | history: createWebHistory(), 6 | routes, 7 | }) 8 | -------------------------------------------------------------------------------- /packages/vue-middleware/src/globalDeclarations.ts: -------------------------------------------------------------------------------- 1 | export {} 2 | 3 | declare global { 4 | interface Window { 5 | Laravel: undefined | { 6 | permissions: string[] 7 | roles: string[] 8 | } 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /docs/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "docs", 3 | "private": true, 4 | "scripts": { 5 | "docs:dev": "vitepress dev", 6 | "docs:build": "vitepress build", 7 | "docs:preview": "vitepress preview" 8 | }, 9 | "dependencies": { 10 | "vitepress": "^1.1.3" 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /packages/playground/tailwind.config.js: -------------------------------------------------------------------------------- 1 | /** @type {import('tailwindcss').Config} */ 2 | export default { 3 | content: [ 4 | './index.html', 5 | './src/**/*.{js,ts,jsx,tsx,vue}', 6 | ], 7 | theme: { 8 | fontFamily: { 9 | sans: ['Inter'], 10 | }, 11 | }, 12 | plugins: [], 13 | } 14 | -------------------------------------------------------------------------------- /packages/playground/tsconfig.node.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "composite": true, 4 | "skipLibCheck": true, 5 | "module": "esnext", 6 | "moduleResolution": "bundler", 7 | "allowSyntheticDefaultImports": true, 8 | "strict": true 9 | }, 10 | "include": ["vite.config.ts"] 11 | } 12 | -------------------------------------------------------------------------------- /packages/vue-middleware/src/composables/index.ts: -------------------------------------------------------------------------------- 1 | import { getCurrentInstance } from 'vue' 2 | 3 | export function usePermissions() { 4 | const globalprops = getCurrentInstance()?.appContext.config.globalProperties 5 | 6 | return { 7 | is: globalprops?.is, 8 | can: globalprops?.can, 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /packages/playground/src/pages/index.vue: -------------------------------------------------------------------------------- 1 | 7 | -------------------------------------------------------------------------------- /packages/playground/vite.config.ts: -------------------------------------------------------------------------------- 1 | import { fileURLToPath, URL } from 'node:url' 2 | import { defineConfig } from 'vite' 3 | import vue from '@vitejs/plugin-vue' 4 | 5 | export default defineConfig({ 6 | plugins: [vue()], 7 | resolve: { 8 | alias: { 9 | '@': fileURLToPath(new URL('./src', import.meta.url)), 10 | }, 11 | }, 12 | }) 13 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | pnpm-debug.log* 8 | lerna-debug.log* 9 | 10 | node_modules 11 | dist 12 | dist-ssr 13 | *.local 14 | 15 | # Editor directories and files 16 | .vscode/* 17 | !.vscode/extensions.json 18 | .idea 19 | .DS_Store 20 | *.suo 21 | *.ntvs* 22 | *.njsproj 23 | *.sln 24 | *.sw? 25 | 26 | docs/.vitepress/cache -------------------------------------------------------------------------------- /packages/vue-middleware/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "include": ["src", "src/globalDeclarations.d.ts"], 4 | "exclude": ["node_modules", "dist"], 5 | "compilerOptions": { 6 | "noEmit": false, 7 | "declaration": true, 8 | "emitDeclarationOnly": true, 9 | "outDir": "dist/types", 10 | "baseUrl": "./", 11 | "paths": { 12 | "@/*": ["./src/*"] 13 | }, 14 | "sourceMap": true 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /packages/playground/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Vue middleware dplayground 8 | 9 | 10 |
11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /docs/public/vue-logo.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es2020", 4 | "useDefineForClassFields": true, 5 | "module": "esnext", 6 | "lib": ["es2020", "dom", "dom.iterable"], 7 | "skipLibCheck": true, 8 | "moduleResolution": "bundler", 9 | "allowImportingTsExtensions": true, 10 | "resolveJsonModule": true, 11 | "isolatedModules": true, 12 | "noEmit": true, 13 | "strict": true, 14 | "noUnusedLocals": true, 15 | "noUnusedParameters": true, 16 | "noFallthroughCasesInSwitch": true, 17 | "types": ["node"], 18 | }, 19 | } 20 | -------------------------------------------------------------------------------- /packages/playground/src/middleware/dashboard.ts: -------------------------------------------------------------------------------- 1 | import { type MiddlewareContext } from 'vue-middleware' 2 | import { useAuth } from '@/composables' 3 | 4 | export default ({ app, router, from, to, redirect, abort, guard }: MiddlewareContext) => { 5 | const { loggedIn } = useAuth() 6 | 7 | console.log('> 🔥 The `dashboard` middleware..', guard) 8 | 9 | if (loggedIn.value && guard === 'guest') { 10 | return redirect({ name: 'dashboard' }) 11 | } 12 | 13 | if (!loggedIn.value && !guard) { 14 | return redirect({ path: '/auth/login' }) 15 | } 16 | 17 | // return abort() 18 | } 19 | -------------------------------------------------------------------------------- /packages/playground/src/pages/dashboard/profile.vue: -------------------------------------------------------------------------------- 1 | 11 | 12 | 17 | -------------------------------------------------------------------------------- /packages/vue-middleware/vite.config.ts: -------------------------------------------------------------------------------- 1 | import { resolve } from 'path' 2 | import { defineConfig } from 'vite' 3 | 4 | export default defineConfig({ 5 | build: { 6 | emptyOutDir: false, 7 | lib: { 8 | entry: resolve(__dirname, 'src/index.ts'), 9 | name: 'vue-middleware', 10 | fileName: 'vue-middleware', 11 | formats: ['umd', 'es', 'cjs'], 12 | }, 13 | rollupOptions: { 14 | external: ['vue', 'vue-router'], 15 | output: { 16 | exports: 'named', 17 | globals: { 18 | vue: 'Vue', 19 | 'vue-router': 'vue-router', 20 | }, 21 | }, 22 | }, 23 | }, 24 | }) 25 | -------------------------------------------------------------------------------- /packages/playground/src/style.css: -------------------------------------------------------------------------------- 1 | @tailwind base; 2 | @tailwind components; 3 | @tailwind utilities; 4 | 5 | /* 6 | * { 7 | padding: 0; 8 | margin: 0; 9 | box-sizing: border-box; 10 | } 11 | body { 12 | font-family: system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif; 13 | } 14 | nav { 15 | padding: 1rem 5rem; 16 | display: flex; 17 | justify-content: space-between; 18 | } 19 | ul { 20 | list-style: none; 21 | } 22 | ul li { 23 | display: inline-block; 24 | margin: 0 10px; 25 | } 26 | a { 27 | text-decoration: none; 28 | } */ -------------------------------------------------------------------------------- /packages/playground/src/pages/dashboard/posts.vue: -------------------------------------------------------------------------------- 1 | 14 | -------------------------------------------------------------------------------- /packages/playground/src/composables/index.ts: -------------------------------------------------------------------------------- 1 | import { ref, computed, Ref } from 'vue' 2 | 3 | interface User { 4 | id: number 5 | name: string 6 | } 7 | 8 | export function useAuth() { 9 | const user: Ref = ref(null) 10 | const permissions: Ref<{ permissions: []; roles: [] } | undefined> = ref(undefined) 11 | const loggedIn = computed(() => { 12 | return !!user.value 13 | }) 14 | 15 | if (localStorage.getItem('user')) { 16 | const data = JSON.parse(localStorage.getItem('user') as string) 17 | 18 | user.value = data.user 19 | permissions.value = data.permissions 20 | } 21 | 22 | const reset = () => { 23 | user.value = null 24 | permissions.value = undefined 25 | } 26 | 27 | return { loggedIn, user, permissions, reset } 28 | } 29 | -------------------------------------------------------------------------------- /packages/playground/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "playground", 3 | "private": true, 4 | "type": "module", 5 | "scripts": { 6 | "plugin:dev": "pnpm run --filter=vue-middleware watch", 7 | "playground:dev": "vite", 8 | "dev": "npm-run-all -p 'plugin:dev' 'playground:dev'", 9 | "build": "vite build", 10 | "preview": "vite preview" 11 | }, 12 | "dependencies": { 13 | "vue": "^3.4.21", 14 | "vue-router": "^4.3.2" 15 | }, 16 | "devDependencies": { 17 | "@vitejs/plugin-vue": "^5.0.4", 18 | "autoprefixer": "^10.4.19", 19 | "npm-run-all": "^4.1.5", 20 | "postcss": "^8.4.38", 21 | "tailwindcss": "^3.4.3", 22 | "typescript": "^5.2.2", 23 | "vite": "^5.2.0", 24 | "vue-tsc": "^2.0.6" 25 | }, 26 | "peerDependencies": { 27 | "vue-middleware": "*" 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "private": true, 3 | "type": "module", 4 | "scripts": { 5 | "plugin:build": "pnpm run --filter=vue-middleware build", 6 | "plugin:build-dts": "pnpm run --filter=vue-middleware build:dts", 7 | "docs:dev": "pnpm run --filter=docs docs:dev", 8 | "playground:dev": "pnpm run --filter=playground dev" 9 | }, 10 | "devDependencies": { 11 | "@rollup/plugin-typescript": "^11.1.6", 12 | "@types/node": "^20.14.2", 13 | "chalk": "^5.3.0", 14 | "fs-extra": "^11.2.0", 15 | "rimraf": "^5.0.7", 16 | "rollup": "^4.18.0", 17 | "rollup-plugin-dts": "^6.1.1", 18 | "tslib": "^2.6.3", 19 | "typescript": "^5.4.5", 20 | "vite": "^5.3.1", 21 | "vue-tsc": "^2.0.21" 22 | }, 23 | "dependencies": { 24 | "vue": "^3.4.29", 25 | "vue-router": "^4.3.3" 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /docs/introduction.md: -------------------------------------------------------------------------------- 1 | # Introduction 2 | 3 | Vue middleware is a powerful Vuejs plugin for creating custom middleware, similar to what you find in Nuxt apps but with extra features for roles and permissions. 4 | 5 | It helps you manage who can access which parts of your app especially when it comes to different route-based roles or permissions using a nice driver-approach making it great with integrating other backend frameworks. 6 | 7 | For live demo please visit [https://vue-middleware-demo.vercel.app](https://vue-middleware-demo.vercel.app). 8 | 9 | For demo source code please visit [our playground](https://github.com/themustafaomar/vue-middleware/tree/main/packages/playground). 10 | 11 | Out of the box it makes it super easy to handle Laravel roles and permissions without needing to set up a bunch of stuff using zero-config driver. 12 | -------------------------------------------------------------------------------- /packages/playground/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES2020", 4 | "useDefineForClassFields": true, 5 | "module": "ESNext", 6 | "lib": ["ES2020", "DOM", "DOM.Iterable"], 7 | "skipLibCheck": true, 8 | 9 | /* Bundler mode */ 10 | "moduleResolution": "bundler", 11 | "allowImportingTsExtensions": true, 12 | "resolveJsonModule": true, 13 | "isolatedModules": true, 14 | "noEmit": true, 15 | "jsx": "preserve", 16 | 17 | /* Linting */ 18 | "strict": false, 19 | "noUnusedLocals": true, 20 | "noUnusedParameters": true, 21 | "noFallthroughCasesInSwitch": true, 22 | 23 | "baseUrl": ".", 24 | "paths": { 25 | "@/*": ["./src/*"] 26 | } 27 | }, 28 | "include": ["src/**/*.ts", "src/**/*.tsx", "src/**/*.vue"], 29 | "references": [{ "path": "./tsconfig.node.json" }] 30 | } -------------------------------------------------------------------------------- /packages/playground/src/pages/dashboard/users.vue: -------------------------------------------------------------------------------- 1 | 14 | 15 | 22 | -------------------------------------------------------------------------------- /packages/vue-middleware/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "vue-middleware", 3 | "version": "1.0.0-alpha.5", 4 | "description": "Vue middleware is a powerful Vuejs plugin for creating custom middleware and manage roles and permissions easily.", 5 | "main": "dist/vue-middleware.js", 6 | "type": "module", 7 | "license": "MIT", 8 | "files": [ 9 | "src", 10 | "dist", 11 | "README.md" 12 | ], 13 | "scripts": { 14 | "watch": "vue-tsc && vite build --watch --minify false" 15 | }, 16 | "dependencies": { 17 | "vue": "^3.4.26", 18 | "vue-router": "^4.3.2" 19 | }, 20 | "module": "./dist/vue-middleware.js", 21 | "types": "./dist/vue-middleware.d.ts", 22 | "exports": { 23 | "default": "./dist/vue-middleware.esm.js", 24 | "import": "./dist/vue-middleware.esm.js", 25 | "require": "./dist/vue-middleware.cjs", 26 | "types": { 27 | "import": "./dist/vue-middleware.d.ts" 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /packages/vue-middleware/src/index.ts: -------------------------------------------------------------------------------- 1 | import type { App } from 'vue' 2 | import type { Router } from 'vue-router' 3 | import { type Options, handler } from './handler' 4 | import './globalDeclarations' 5 | 6 | export type { Options, MiddlewareContext } from './handler' 7 | 8 | export { Driver } from './drivers/driver' 9 | export * from './drivers' 10 | export * from './composables' 11 | 12 | declare module 'vue' { 13 | interface ComponentCustomProperties { 14 | is: (value: string) => boolean 15 | can: (value: string) => boolean 16 | } 17 | } 18 | 19 | const plugin = { 20 | install(app: App, options: Partial = {}) { 21 | const router: Router = app.config.globalProperties.$router 22 | 23 | if (!app.config.globalProperties.$router) { 24 | throw new Error( 25 | 'The vue-router is required in order to work with vue-middleware.' 26 | ) 27 | } 28 | 29 | handler(app, router, options) 30 | }, 31 | } 32 | 33 | export default plugin 34 | -------------------------------------------------------------------------------- /docs/api-examples.md: -------------------------------------------------------------------------------- 1 | --- 2 | outline: deep 3 | --- 4 | 5 | # Runtime API Examples 6 | 7 | This page demonstrates usage of some of the runtime APIs provided by VitePress. 8 | 9 | The main `useData()` API can be used to access site, theme, and page data for the current page. It works in both `.md` and `.vue` files: 10 | 11 | ```md 12 | 17 | 18 | ## Results 19 | 20 | ### Theme Data 21 |
{{ theme }}
22 | 23 | ### Page Data 24 |
{{ page }}
25 | 26 | ### Page Frontmatter 27 |
{{ frontmatter }}
28 | ``` 29 | 30 | 35 | 36 | ## Results 37 | 38 | ### Theme Data 39 |
{{ theme }}
40 | 41 | ### Page Data 42 |
{{ page }}
43 | 44 | ### Page Frontmatter 45 |
{{ frontmatter }}
46 | 47 | ## More 48 | 49 | Check out the documentation for the [full list of runtime APIs](https://vitepress.dev/reference/runtime-api#usedata). 50 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 Mustafa Omar 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /packages/vue-middleware/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 Mustafa Omar 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /docs/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | # https://vitepress.dev/reference/default-theme-home-page 3 | layout: home 4 | 5 | hero: 6 | name: "Vue Middleware" 7 | text: "Middleware has never been easier" 8 | tagline: Vue middleware is a powerful Vue.js plugin for creating middleware, similar to what you find in Nuxt apps, with extra features for roles and permissions. 9 | image: 10 | src: /vue-logo.svg 11 | alt: Vue Middleware Logo 12 | actions: 13 | - theme: brand 14 | text: Documenation 15 | link: /introduction 16 | - theme: alt 17 | text: Demo 18 | link: https://vue-middleware-demo.vercel.app 19 | 20 | features: 21 | - icon: 🛠️ 22 | title: Middleware functions 23 | details: Vue middleware provides a way to register middleware functions and can be attached to routes using its name. 24 | - icon: ✈️ 25 | title: Route-based roles & permissions 26 | details: It also provides route-based roles and permissions functionality for protecting your application routes. 27 | - icon: 🚀 28 | title: Interoperability 29 | details: While it supports Laravel roles and permissions out of the box, it also provides a way to support other backend frameworks using custom driver. 30 | --- 31 | 32 | -------------------------------------------------------------------------------- /packages/vue-middleware/src/drivers/driver.ts: -------------------------------------------------------------------------------- 1 | import { type App } from 'vue' 2 | import { type RouteMeta } from 'vue-router' 3 | 4 | export abstract class Driver { 5 | _app: App 6 | 7 | constructor(app: App) { 8 | this._app = app 9 | } 10 | 11 | abstract can(): (value: string) => boolean 12 | 13 | abstract is(): (value: string) => boolean 14 | 15 | _hasntRole({ roles }: RouteMeta) { 16 | roles = this._normalize(roles as string | []) 17 | 18 | return !roles || this.is()(roles as string) ? false : true 19 | } 20 | 21 | _hasntPermissions({ permissions }: RouteMeta) { 22 | if (!permissions) { 23 | return false 24 | } 25 | 26 | permissions = this._normalize(permissions as string | []) 27 | 28 | return !permissions || this.can()(permissions as string) ? false : true 29 | } 30 | 31 | _normalize(value: string[] | string) { 32 | if (Array.isArray(value)) { 33 | return value.join('&') 34 | } 35 | 36 | if (typeof value === 'string') { 37 | return value 38 | } 39 | 40 | return '' 41 | } 42 | 43 | _lookup() { 44 | const globalProperties = this._app.config.globalProperties 45 | 46 | globalProperties.can = this.can() 47 | globalProperties.is = this.is() 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /docs/page-title.md: -------------------------------------------------------------------------------- 1 | # Page Title 2 | 3 | While navigating each route vue-middleware generates a page title depending on the route name if presented, with the ability to customize the title format perfectly. 4 | 5 | ## Enabling Page Title 6 | 7 | Actually, the page title is enabled by default and you can disable it with the following: 8 | 9 | ```ts 10 | app.use(vueMiddleware, { 11 | pageTitle: false, 12 | } 13 | ``` 14 | 15 | ## Customization 16 | 17 | By default the page title is enabled and replacing each `-` or `_` with a space, that means if you have a route named `dashboard_users` it becomes Dashboard - users, to customize this behavior take a look at the following snippet. 18 | 19 | ```ts 20 | import { type RouteMeta } from 'vue-router' 21 | 22 | app.use(vueMiddleware, { 23 | pageTitle: { 24 | template: (name: string, meta: RouteMeta) => { 25 | // Transform you name, you're also have access to 26 | // meta property if you need to define your own title from route meta. 27 | 28 | let title: string[] | string = name.replace(/_|-/g, ' ').split(' ') 29 | 30 | title = title[title.length - 1] 31 | 32 | // dashboard_users -> Users 33 | // dashboard-posts -> Posts etc.. 34 | 35 | return `${title.charAt(0).toUpperCase()}${title.slice(1)}` 36 | } 37 | }, 38 | }) 39 | ``` 40 | -------------------------------------------------------------------------------- /packages/playground/src/pages/dashboard/index.vue: -------------------------------------------------------------------------------- 1 | 32 | -------------------------------------------------------------------------------- /packages/vue-middleware/src/drivers/laravel.ts: -------------------------------------------------------------------------------- 1 | import { type App } from 'vue' 2 | import { Driver } from './driver' 3 | 4 | export class LaravelPermissionsDriver extends Driver { 5 | constructor(app: App) { 6 | super(app) 7 | } 8 | 9 | can(): (value: string) => boolean { 10 | return (value: string) => { 11 | const permissions = window.Laravel?.permissions 12 | 13 | if (!permissions || !Array.isArray(permissions)) { 14 | return false 15 | } 16 | 17 | if (value.includes('|')) { 18 | return value 19 | .split('|') 20 | .some((permission) => permissions.includes(permission.trim())) 21 | } else if (value.includes('&')) { 22 | return value 23 | .split('&') 24 | .every((permission) => !permissions.includes(permission.trim())) 25 | } else { 26 | return permissions.includes(value.trim()) 27 | } 28 | } 29 | } 30 | 31 | is(): (value: string) => boolean { 32 | return (value: string) => { 33 | const roles = window.Laravel?.roles 34 | 35 | if (!roles || !Array.isArray(roles)) { 36 | return false 37 | } 38 | 39 | if (value.includes('|')) { 40 | return value.split('|').some((item) => roles.includes(item.trim())) 41 | } else if (value.includes('&')) { 42 | return value.split('&').every((role) => !roles.includes(role.trim())) 43 | } else { 44 | return roles.includes(value.trim()) 45 | } 46 | } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /docs/can-and-is.md: -------------------------------------------------------------------------------- 1 | # can() And is() 2 | 3 | In this section we're gonna be discussing how to use can and is utilities in your application. 4 | 5 | ## is() 6 | 7 | The is() utility used to check for the roles of the current authenticated user. 8 | 9 | ```vue 10 | 21 | ``` 22 | 23 | ## can() 24 | 25 | The can() utility used to check for the current authenticated user's permissions. 26 | 27 | ```vue 28 | 39 | ``` 40 | 41 | ## Using In Script Setup 42 | 43 | The is() and can() utilities are available in script setup as well using usePermissions() composable. 44 | 45 | ```vue 46 | 59 | ``` 60 | -------------------------------------------------------------------------------- /packages/playground/src/pages/login.vue: -------------------------------------------------------------------------------- 1 | 18 | 19 | 46 | -------------------------------------------------------------------------------- /packages/playground/src/components/Navbar.vue: -------------------------------------------------------------------------------- 1 | 35 | 36 | 50 | -------------------------------------------------------------------------------- /packages/playground/src/main.ts: -------------------------------------------------------------------------------- 1 | import { createApp, h, toRaw, unref } from 'vue' 2 | import vueMiddleware, { LaravelPermissionsDriver } from 'vue-middleware' 3 | import { useAuth } from './composables' 4 | import middleware from './middleware' 5 | import './style.css' 6 | import router from './router' 7 | import App from './App.vue' 8 | import { RouteLocationNormalized, RouteMeta } from 'vue-router' 9 | 10 | const app = createApp({ 11 | render: () => h(App), 12 | beforeCreate: () => { 13 | window.Laravel = toRaw(unref(useAuth().permissions)) 14 | }, 15 | }) 16 | 17 | app.use(router) 18 | app.use(vueMiddleware, { 19 | middleware, 20 | permissions: { 21 | driver: LaravelPermissionsDriver, 22 | }, 23 | hooks: { 24 | onBeforeEach: ( 25 | to: RouteLocationNormalized, 26 | from: RouteLocationNormalized 27 | ): void => { 28 | // 29 | }, 30 | onAfterEach: ( 31 | to: RouteLocationNormalized, 32 | from: RouteLocationNormalized 33 | ): void => { 34 | // 35 | }, 36 | }, 37 | pageTitle: { 38 | template: (name: string, meta: RouteMeta) => { 39 | // Transform you name, you're also have access to 40 | // meta property if you need to define your own title from route meta. 41 | // console.log(name, meta) 42 | 43 | let title: string[] | string = name.replace(/_|-/g, ' ').split(' ') 44 | 45 | title = title[title.length - 1] 46 | 47 | // dashboard_users -> Users, dashboard-posts -> Posts etc.. 48 | 49 | return `${title.charAt(0).toUpperCase()}${title.slice(1)}` 50 | } 51 | }, 52 | }) 53 | app.mount('#app') 54 | -------------------------------------------------------------------------------- /docs/.vitepress/config.mts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vitepress' 2 | 3 | // https://vitepress.dev/reference/site-config 4 | export default defineConfig({ 5 | title: "Vue Middleware", 6 | description: "Vue middleware is a powerful Vuejs plugin for creating custom middleware, similar to what you find in Nuxt apps but with extra features for roles and permissions.", 7 | themeConfig: { 8 | siteTitle: 'Vue Middleware', 9 | // https://vitepress.dev/reference/default-theme-config 10 | nav: [ 11 | { text: 'Home', link: '/' }, 12 | { text: 'Demo', link: 'https://vue-middleware-demo.vercel.app' } 13 | ], 14 | 15 | sidebar: [ 16 | { 17 | text: 'Getting Started', 18 | items: [ 19 | { text: 'Introduction', link: '/introduction' }, 20 | { text: 'Quick usage', link: '/quick-usage' }, 21 | { text: 'Installation', link: '/installation' }, 22 | ] 23 | }, 24 | { 25 | text: 'Middleware', 26 | items: [ 27 | { text: 'Basic middleware', link: '/basics' }, 28 | { text: 'Page title', link: '/page-title' }, 29 | ] 30 | }, 31 | { 32 | text: 'Roles & Permissions', 33 | items: [ 34 | { text: 'Basics', link: '/roles-and-permissions' }, 35 | { text: 'can() and is() Utilities', link: '/can-and-is' }, 36 | { text: 'Creating Drivers', link: '/custom-drivers' }, 37 | ] 38 | }, 39 | { 40 | text: 'Links', 41 | items: [ 42 | { text: 'Troubleshooting', link: 'https://github.com/themustafaomar/vue-middleware/issues/new', target: '_blank' }, 43 | ] 44 | } 45 | ], 46 | 47 | socialLinks: [ 48 | { icon: 'github', link: 'https://github.com/themustafaomar/vue-middleware' } 49 | ] 50 | } 51 | }) 52 | -------------------------------------------------------------------------------- /docs/custom-drivers.md: -------------------------------------------------------------------------------- 1 | # Creating Custom Drivers 2 | 3 | For some reasons you may want to write your own is and can, vue middleware makes it easy to create your custom driver. 4 | 5 | ## Getting Started 6 | 7 | Let's create our custom driver that handles roles and permissions for us, feel free to add your can and is logic inside the can and is methods. 8 | 9 | In the following example that's the default Laravel driver. 10 | 11 | The can and is become our global utilties you're using everywhere. 12 | 13 | ```ts 14 | import type { App } from 'vue' 15 | import { Driver } from 'vue-middleware' 16 | 17 | export class MyPermissionsDriver extends Driver { 18 | constructor(app: App) { 19 | super(app) 20 | } 21 | 22 | can(): (value: string) => boolean { 23 | return (value: string) => { 24 | const permissions = window.Laravel?.permissions 25 | 26 | if (!permissions || !Array.isArray(permissions)) { 27 | return false 28 | } 29 | 30 | if (value.includes('|')) { 31 | return value 32 | .split('|') 33 | .some((permission) => permissions.includes(permission.trim())) 34 | } else if (value.includes('&')) { 35 | return value 36 | .split('&') 37 | .every((permission) => !permissions.includes(permission.trim())) 38 | } else { 39 | return permissions.includes(value.trim()) 40 | } 41 | } 42 | } 43 | 44 | is(): (value: string) => boolean { 45 | return (value: string) => { 46 | const roles = window.Laravel?.roles 47 | 48 | if (!roles || !Array.isArray(roles)) { 49 | return false 50 | } 51 | 52 | if (value.includes('|')) { 53 | return value.split('|').some((item) => roles.includes(item.trim())) 54 | } else if (value.includes('&')) { 55 | return value.split('&').every((role) => !roles.includes(role.trim())) 56 | } else { 57 | return roles.includes(value.trim()) 58 | } 59 | } 60 | } 61 | } 62 | 63 | ``` -------------------------------------------------------------------------------- /packages/playground/src/router/routes.ts: -------------------------------------------------------------------------------- 1 | export default [ 2 | // Public routes 3 | { 4 | name: 'home', 5 | path: '/', 6 | component: () => import('@/layouts/default.vue'), 7 | children: [ 8 | { 9 | name: 'home', 10 | path: '', 11 | component: () => import('@/pages/index.vue'), 12 | }, 13 | { 14 | name: 'about', 15 | path: 'about', 16 | component: () => import('@/pages/about.vue'), 17 | }, 18 | { 19 | name: 'contact', 20 | path: 'contact', 21 | component: () => import('@/pages/contact.vue'), 22 | }, 23 | ] 24 | }, 25 | // Authentication routes 26 | { 27 | name: 'auth', 28 | path: '/auth', 29 | component: () => import('@/layouts/auth.vue'), 30 | redirect: '/auth/login', 31 | meta: { 32 | middleware: 'dashboard:guest', 33 | }, 34 | children: [ 35 | { 36 | name: 'login', 37 | path: 'login', 38 | component: () => import('@/pages/login.vue'), 39 | }, 40 | { 41 | name: 'register', 42 | path: 'register', 43 | component: () => import('@/pages/register.vue'), 44 | }, 45 | ], 46 | }, 47 | // Dashboard routes 48 | { 49 | name: 'dashboard', 50 | path: '/dashboard', 51 | component: () => import('@/layouts/dashboard.vue'), 52 | meta: { 53 | middleware: ['dashboard'], 54 | fallbackTo: '/auth/login', 55 | }, 56 | children: [ 57 | { 58 | name: 'dashboard_home', 59 | path: '', 60 | component: () => import('@/pages/dashboard/index.vue'), 61 | }, 62 | { 63 | name: 'dashboard_users', 64 | path: 'users', 65 | component: () => import('@/pages/dashboard/users.vue'), 66 | meta: { 67 | middleware: 'logger', 68 | }, 69 | }, 70 | { 71 | name: 'dashboard_posts', 72 | path: 'posts', 73 | component: () => import('@/pages/dashboard/posts.vue'), 74 | }, 75 | { 76 | name: 'dashboard_profile', 77 | path: 'profile', 78 | component: () => import('@/pages/dashboard/profile.vue'), 79 | }, 80 | ], 81 | }, 82 | ] 83 | -------------------------------------------------------------------------------- /packages/vue-middleware/README.md: -------------------------------------------------------------------------------- 1 | # vue-middleware 2 | 3 | vue-middleware is a powerful Vuejs plugin for creating custom middleware, similar to what you find in Nuxt apps but with extra features for roles and permissions. 4 | 5 | It helps you manage who can access which parts of your app especially when it comes to different route-based roles or permissions using a nice driver-approach making it great with integrating other backend frameworks, for more information please visit the offical [documentation](https://vue-middleware-docs.vercel.app). 6 | 7 | Out of the box it makes it super easy to handle Laravel roles and permissions without needing to set up a bunch of stuff using zero-config driver. 8 | 9 | ## Installation 10 | 11 | Follow these steps to quickly install `vue-middleware` into your project, in this example we're using npm. 12 | 13 | ``` 14 | npm i vue-middleware 15 | ``` 16 | 17 | ## Quick Usage 18 | 19 | Let's create a simple middleware for protecting our dashboard. 20 | 21 | In `main.ts` we're goning to register our first `dashboard` middleware the function receives all the parameters you might think of in the authentication process. 22 | 23 | ```ts 24 | import { createApp, App } from 'vue' 25 | import { vueMiddleware, MiddlewareContext } from 'vue-middleware' 26 | import App from './App.vue' 27 | 28 | const app: App = createApp(App) 29 | 30 | app.use(vueMiddleware, { 31 | middleware: { 32 | dashboard: ({ app, router, from, to, redirect, abort, guard }: MiddlewareContext) => { 33 | // 34 | }, 35 | }, 36 | }) 37 | 38 | app.mount('#app') 39 | ``` 40 | 41 | In vue-router routes we need to attach this middleware name in a route. 42 | 43 | ```ts 44 | export const routes = [ 45 | { 46 | name: 'dashboard', 47 | path: '/dashboard', 48 | component: () => import('@/layouts/dashboard.vue'), 49 | meta: { 50 | middleware: 'dashboard', // This dashboard and its children are now guarded using the dashboard middleware 51 | }, 52 | children: [ 53 | { 54 | name: 'dashboard_home', 55 | path: '', 56 | component: () => import('@/pages/dashboard/index.vue'), 57 | }, 58 | { 59 | name: 'dashboard_users', 60 | path: 'users', 61 | component: () => import('@/pages/dashboard/users.vue'), 62 | }, 63 | ] 64 | } 65 | ] 66 | ``` -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # vue-middleware 2 | 3 | vue-middleware is a powerful Vuejs plugin for creating custom middleware, similar to what you find in Nuxt apps but with extra features for roles and permissions.. 4 | 5 | It helps you manage who can access which parts of your app especially when it comes to different route-based roles or permissions using a nice driver-approach making it great with integrating other backend frameworks, for more information please visit the offical [documentation](https://vue-middleware-docs.vercel.app). 6 | 7 | Out of the box it makes it super easy to handle Laravel roles and permissions without needing to set up a bunch of stuff using zero-config driver. 8 | 9 | ## Demo 10 | 11 | For live demo please visit [https://vue-middleware-demo.vercel.app](https://vue-middleware-demo.vercel.app). 12 | 13 | For demo source code please visit the [playground](https://github.com/themustafaomar/vue-middleware/tree/main/packages/playground). 14 | 15 | ## Installation 16 | 17 | Follow these steps to quickly install `vue-middleware` into your project, in this example we're using npm. 18 | 19 | ``` 20 | npm i vue-middleware 21 | ``` 22 | 23 | ## Quick Usage 24 | 25 | Let's create a simple middleware for protecting our dashboard. 26 | 27 | In `main.ts` we're goning to register our first `dashboard` middleware the function receives all the parameters you might think of in the authentication process. 28 | 29 | ```ts 30 | import { createApp, App } from 'vue' 31 | import vueMiddleware, { type MiddlewareContext } from 'vue-middleware' 32 | import App from './App.vue' 33 | 34 | const app: App = createApp(App) 35 | 36 | app.use(vueMiddleware, { 37 | middleware: { 38 | dashboard: ({ app, router, from, to, redirect, abort, guard }: MiddlewareContext) => { 39 | // 40 | }, 41 | }, 42 | }) 43 | 44 | app.mount('#app') 45 | ``` 46 | 47 | In vue-router routes we need to attach this middleware name in a route. 48 | 49 | ```ts 50 | export const routes = [ 51 | { 52 | name: 'dashboard', 53 | path: '/dashboard', 54 | component: () => import('@/layouts/dashboard.vue'), 55 | meta: { 56 | middleware: 'dashboard', // This dashboard and its children are now guarded using the dashboard middleware 57 | }, 58 | children: [ 59 | { 60 | name: 'dashboard_home', 61 | path: '', 62 | component: () => import('@/pages/dashboard/index.vue'), 63 | }, 64 | { 65 | name: 'dashboard_users', 66 | path: 'users', 67 | component: () => import('@/pages/dashboard/users.vue'), 68 | }, 69 | ] 70 | } 71 | ] 72 | ``` 73 | 74 | ## License 75 | 76 | vue-middleware is released under the MIT License. -------------------------------------------------------------------------------- /scripts/build.js: -------------------------------------------------------------------------------- 1 | import { execSync } from 'child_process' 2 | import { rimraf } from 'rimraf' 3 | import { rollup } from 'rollup' 4 | import fs from 'fs-extra' 5 | import chalk from 'chalk' 6 | import path from 'path' 7 | import typescript from '@rollup/plugin-typescript' 8 | import dts from 'rollup-plugin-dts' 9 | 10 | const packageDir = path.resolve('packages/vue-middleware') 11 | const entryFilePath = path.resolve(packageDir, 'src/index.ts') 12 | const outputDir = path.resolve(packageDir, 'dist') 13 | const tsconfigPath = path.resolve(packageDir, 'tsconfig.json') 14 | 15 | function generateConfig(format) { 16 | const extension = format === 'es' 17 | ? 'esm.js' 18 | : format === 'umd' 19 | ? 'js' 20 | : 'cjs' 21 | 22 | return { 23 | input: entryFilePath, 24 | external: ['vue', 'vue-router'], 25 | plugins: [ 26 | typescript({ 27 | tsconfig: tsconfigPath, 28 | declarationDir: outputDir, 29 | declaration: true, 30 | allowImportingTsExtensions: false, 31 | sourceMap: false, 32 | }), 33 | ], 34 | output: { 35 | format, 36 | name: format === 'umd' ? 'vue-middleware' : undefined, 37 | exports: format === 'umd' || format === 'cjs' ? 'named' : undefined, 38 | globals: { 39 | vue: 'Vue', 40 | 'vue-router': 'VueRouter', 41 | }, 42 | }, 43 | fileName: `vue-middleware.${extension}`, 44 | } 45 | } 46 | 47 | async function build() { 48 | rimraf.sync(outputDir) 49 | 50 | console.log(chalk.blackBright('⏳ Building library ./dist\n')) 51 | 52 | const formats = ['umd', 'es', 'cjs'] 53 | 54 | for (const format of formats) { 55 | const { output, fileName, ...rest } = await generateConfig(format) 56 | const bundle = await rollup(rest) 57 | const { output: [{ code }] } = await bundle.generate(output) 58 | 59 | fs.outputFileSync( 60 | path.join(outputDir, fileName), code 61 | ) 62 | console.log(chalk.cyan(`-> Generated library file ./dist/${fileName}`)) 63 | } 64 | 65 | console.log(chalk.blackBright('\n⏳ Generating declarations...')) 66 | 67 | execSync(`tsc --project ${tsconfigPath}`, { 68 | stdio: 'inherit', 69 | }) 70 | 71 | const dtsBundle = await rollup({ 72 | input: path.resolve(outputDir, 'types/index.d.ts'), 73 | plugins: [dts()], 74 | }) 75 | 76 | const { output: [{ code }] } = await dtsBundle.generate({ 77 | format: 'es', 78 | }) 79 | 80 | fs.outputFileSync(path.join(outputDir, 'vue-middleware.d.ts'), code) 81 | 82 | console.log(chalk.cyan('\n-> Declarations generated: vue-middleware.d.ts')) 83 | 84 | rimraf.sync(path.resolve(outputDir, 'types')) 85 | } 86 | 87 | build().catch((error) => { 88 | console.error(error) 89 | process.exit(1) 90 | }) 91 | -------------------------------------------------------------------------------- /docs/quick-usage.md: -------------------------------------------------------------------------------- 1 | # Quick usage 2 | 3 | Let's create a simple middleware for protecting our dashboard. 4 | 5 | In `main.ts` we're goning to register our first `dashboard` middleware the function receives all the parameters you might think of in the authentication process. 6 | 7 | ::: warning 8 | Notice that vue-middleware requires vue-router in order to work. 9 | ::: 10 | 11 | ```ts 12 | import { createApp, type App } from 'vue' 13 | import vueMiddleware, { type MiddlewareContext } from 'vue-middleware' 14 | import App from './App.vue' 15 | 16 | const app: App = createApp(App) 17 | 18 | app.use(vueMiddleware, { 19 | middleware: { 20 | dashboard: ({ app, router, from, to, redirect, abort, guard }: MiddlewareContext) => { 21 | // 22 | }, 23 | }, 24 | }) 25 | 26 | app.mount('#app') 27 | ``` 28 | 29 | In routes file we need to attach this middleware name in a route or most likely a route with its children, e.g dashboard. 30 | 31 | ```ts 32 | export const routes = [ 33 | { 34 | name: 'dashboard', 35 | path: '/dashboard', 36 | component: () => import('@/layouts/dashboard.vue'), 37 | meta: { 38 | // This dashboard and its children are now protected using the dashboard middleware 39 | middleware: 'dashboard', 40 | }, 41 | children: [ 42 | { 43 | name: 'dashboard_home', 44 | path: '', 45 | component: () => import('@/pages/dashboard/index.vue'), 46 | }, 47 | { 48 | name: 'dashboard_users', 49 | path: 'users', 50 | component: () => import('@/pages/dashboard/users.vue'), 51 | }, 52 | ] 53 | } 54 | ] 55 | ``` 56 | 57 | 142 | -------------------------------------------------------------------------------- /docs/roles-and-permissions.md: -------------------------------------------------------------------------------- 1 | # Roles And Permissions 2 | 3 | In this section we're gonna be discussing how to add roles and permissions into your application. 4 | 5 | ## Getting Started 6 | 7 | Roles and permissions as we've already mentioned is a route-based, in order to work we have to store the roles and permissions somehow when a user logged in. 8 | 9 | Typically, when a user logged in we add the user, roles and permissions in the localStorage, vue-middleware requires the roles and permissions to be added to the window object as well, when using `LaravelPermissionsDriver` driver, the example below we'll assume we're using Laravel. 10 | 11 | ::: info 12 | But refreshing the page would waste these values, right? that's why you need to populate the window each time a refresh is made. 13 | ::: 14 | 15 | ```ts 16 | ... 17 | 18 | const login = () => { 19 | const { data } = axios.post('/login', ...) 20 | 21 | localStorage.set('user', JSON.stringify({ 22 | // The user.. 23 | user: data.user, 24 | 25 | // An array of permissions ['view posts', 'create posts', 'edit posts', ...] 26 | permissions: data.permissions, 27 | 28 | // An array of roles ['admin'] 29 | roles: data.roles, 30 | })) 31 | 32 | window.Laravel = data // A copy for the window. 33 | } 34 | ``` 35 | 36 | **Tip**: in order to populate the roles and permissions in the window, beforeCreate hook is a perfect place to do so, for complete use-case senario please visit the [playground](https://github.com/themustafaomar/vue-middleware/tree/main/packages/playground) project. 37 | 38 | ```ts 39 | const app = createApp({ 40 | ... 41 | beforeCreate: () => { 42 | const { roles, permissions } = JSON.parse(localStorage.get('user')) 43 | 44 | window.Laravel = { 45 | roles, // ['admin', ...] 46 | permissions, // ['view users', 'create users', 'edit users', 'delete users', ...] 47 | } 48 | }, 49 | ... 50 | }) 51 | ``` 52 | 53 | ## Registering A Driver 54 | 55 | In this example we're gonna be using the `LaravelPermissionsDriver` driver. 56 | 57 | ```diff 58 | + import { LaravelPermissionsDriver } from 'vue-middleware' 59 | 60 | app.use(vueMiddleware, { 61 | middlewares, 62 | + permissions: { 63 | + driver: LaravelPermissionsDriver, 64 | + }, 65 | }) 66 | ``` 67 | 68 | ## Attaching Permissions 69 | 70 | Once the driver is installed now we we're ready to add roles and permissions per route. 71 | 72 | ```ts 73 | { 74 | name: 'dashboard_users', 75 | path: 'users', 76 | component: () => import('@/pages/dashboard/users.vue'), 77 | meta: { 78 | // All permissions in the array must be granted in order to enter. 79 | permissions: ['view users'], 80 | 81 | // Using the string syntax. 82 | permissions: 'view users', 83 | 84 | // Any permissions 85 | permissions: 'view any | view users', 86 | 87 | // All permissions have to be granted, 88 | permissions: 'view dashboard & view users', 89 | }, 90 | }, 91 | ``` 92 | 93 | ## Attaching Roles 94 | 95 | Attaching roles is is the same as attaching permissions. 96 | 97 | ```ts 98 | { 99 | name: 'dashboard_users', 100 | path: 'users', 101 | component: () => import('@/pages/dashboard/users.vue'), 102 | meta: { 103 | // All permissions in the array must be granted in order to enter. 104 | roles: ['admin'], 105 | 106 | // Using the string syntax. 107 | roles: 'admin', 108 | 109 | // Any roles 110 | roles: 'admin | moderator', 111 | 112 | // All roles have to be granted, 113 | roles: 'moderator & editor', 114 | }, 115 | }, 116 | ``` -------------------------------------------------------------------------------- /packages/vue-middleware/src/handler.ts: -------------------------------------------------------------------------------- 1 | import type { App } from 'vue' 2 | import type { 3 | Router, 4 | RouteLocationNormalized, 5 | RouteMeta, 6 | RouteLocationRaw, 7 | } from 'vue-router' 8 | import { Driver } from './drivers/driver' 9 | 10 | export interface MiddlewareContext { 11 | app: App 12 | router: Router 13 | from: RouteLocationNormalized 14 | to: RouteLocationNormalized 15 | redirect: (to: RouteLocationRaw) => void 16 | abort: () => Symbol | boolean 17 | guard?: string | undefined 18 | } 19 | 20 | export interface Middleware { 21 | [key: string]: (ctx: MiddlewareContext) => void 22 | } 23 | 24 | type MiddlewareName = [string, string | undefined] 25 | 26 | export interface Options { 27 | middleware: Middleware 28 | pageTitle: { 29 | template: (name: string, meta: RouteMeta) => string 30 | } | boolean 31 | permissions: { 32 | driver: new (app: App) => Driver 33 | } 34 | hooks: { 35 | onBeforeEach?: (to: RouteLocationNormalized, from: RouteLocationNormalized) => void 36 | onAfterEach?: (to: RouteLocationNormalized, from: RouteLocationNormalized) => void 37 | } 38 | } 39 | 40 | const ABORT_KEY = Symbol('VMAbort') 41 | const abort = () => ABORT_KEY 42 | 43 | export function handler( 44 | app: App, 45 | router: Router, 46 | options: Partial 47 | ) { 48 | const { 49 | pageTitle, 50 | middleware = {}, 51 | permissions, 52 | hooks, 53 | } = options 54 | 55 | let permissionsDriver = null 56 | if (permissions?.driver) { 57 | permissionsDriver = new permissions.driver(app) 58 | if (!(permissionsDriver instanceof Driver)) { 59 | throw new Error( 60 | "The driver is not compatible with our base driver are you sure your're extending the base driver." 61 | ) 62 | } 63 | permissionsDriver._lookup() 64 | } 65 | 66 | router.beforeEach((to, from, next) => { 67 | if (hooks?.onBeforeEach) { 68 | hooks.onBeforeEach(to, from) 69 | } 70 | 71 | const { name, matched, meta } = to 72 | 73 | // Add a title and id to the current page 74 | if (pageTitle !== false && name) { 75 | document.title = typeof pageTitle === 'object' 76 | ? pageTitle.template(String(name), meta) 77 | : createTitle(String(name)) 78 | } 79 | 80 | // Handle the role and permissions if any 81 | if (permissionsDriver) { 82 | const fallbackTo = meta.fallbackTo as string | undefined || '' 83 | if (permissionsDriver._hasntRole(meta)) { 84 | return next({ 85 | path: fallbackTo, 86 | }) 87 | } else if (permissionsDriver._hasntPermissions(meta)) { 88 | return next({ 89 | path: fallbackTo, 90 | }) 91 | } 92 | } 93 | 94 | const redirect = (to: RouteLocationRaw) => { 95 | router.push(to) 96 | } 97 | 98 | const ctx: MiddlewareContext = { 99 | app, 100 | router, 101 | from, 102 | to, 103 | redirect, 104 | abort, 105 | } 106 | 107 | // Well, looks like we don't have any middleware to run, so we can call 108 | // next now, otherwise we will ensure that each middleware doesn't return 109 | // failure by returning explicit `false` or `ABORT_KEY`.. 110 | const middlewaresToRun = getMiddlewares( 111 | matched.map(match => match.meta) 112 | ) 113 | if (!middlewaresToRun.length) { 114 | next() 115 | } else { 116 | const result: boolean[] | Symbol[] | unknown[] = middlewaresToRun.map((middlewareName) => { 117 | return runMiddleware(middleware, middlewareName, ctx) 118 | }) 119 | if (result.some( 120 | (value: unknown) => value === false || value == ABORT_KEY 121 | )) { 122 | return next(false) 123 | } 124 | next() 125 | } 126 | }) 127 | 128 | router.afterEach((to, from) => { 129 | if (hooks?.onAfterEach) { 130 | hooks.onAfterEach(to, from) 131 | } 132 | }) 133 | } 134 | 135 | // Create a title from a given name 136 | function createTitle(name: string) { 137 | return (name.charAt(0).toUpperCase() + name.slice(1)).replace(/_|-/gi, ' - ') 138 | } 139 | 140 | // Let's run the middleware and give the 141 | // middleware a bunch of parameters to play around with. 142 | function runMiddleware( 143 | middlewares: Middleware, 144 | name: string, 145 | ctx: MiddlewareContext 146 | ) { 147 | const [middleware, guard]: MiddlewareName = name.split(':') as MiddlewareName 148 | 149 | if (!Array.prototype.hasOwnProperty.call(middlewares, middleware)) { 150 | throw new Error( 151 | `Unknown [${middleware}] middleware, did you register this middleware?` 152 | ) 153 | } 154 | 155 | return middlewares[middleware]({ 156 | ...ctx, 157 | guard, 158 | }) 159 | } 160 | 161 | function getMiddlewares(middlewares: RouteMeta[]) { 162 | return middlewares.reduce((middlewares: string[], meta: RouteMeta) => { 163 | if (meta.excludeMiddleware) { 164 | const excludes = Array.isArray(meta.excludeMiddleware) 165 | ? meta.excludeMiddleware 166 | : [meta.excludeMiddleware] 167 | middlewares = middlewares.filter(name => !excludes.includes(name)) 168 | } 169 | 170 | if (Array.isArray(meta.middleware)) { 171 | middlewares.push(...meta.middleware as []) 172 | } else if (typeof meta.middleware === 'string') { 173 | middlewares.push(meta.middleware) 174 | } 175 | 176 | return middlewares 177 | }, []) 178 | } 179 | -------------------------------------------------------------------------------- /docs/basics.md: -------------------------------------------------------------------------------- 1 | # Basic Middleware 2 | 3 | As we mentioned earlier that we can create middlewares inlined so to say, each middleware function is receiving a context parameters you may need it. 4 | 5 | ```ts 6 | app.use(vueMiddleware, { 7 | middleware: { 8 | dashboard({ 9 | app, 10 | router, 11 | from, 12 | to, 13 | redirect, 14 | abort, 15 | guard 16 | }) { 17 | // 18 | }, 19 | }, 20 | }) 21 | ``` 22 | 23 | ## Putting It Where It Belongs 24 | 25 | It's prefered to create a middleware directory at the root of your project `@/middleware`, and create an index.ts file that exports all your middlewares, in `index.ts`. 26 | 27 | ```ts 28 | import dashboard from './dashboard' 29 | 30 | export { 31 | dashboard, 32 | // More middlewares.. 33 | } 34 | ``` 35 | 36 | ### Creating Middleware File 37 | 38 | In the middleware module file we will define our middleware, at the end it's probably will be something like that for protecting a dashboard. 39 | 40 | ```ts 41 | import { type MiddlewareContext } from 'vue-middleware' 42 | 43 | export default ({ redirect, guard }: MiddlewareContext) => { 44 | // Assuming this is a Pinia store.. 45 | const { loggedIn } = storeToRefs(useAuthStore()) 46 | 47 | // The user is logged in and trying to 48 | // access auth page e.g: login and register. 49 | if (loggedIn.value && guard) { 50 | redirect('/dashboard') 51 | } 52 | 53 | // The user is not logged in and it's not entering 54 | // an auth page such as login or register. 55 | if (!loggedIn.value && !guard) { 56 | redirect('/auth/login') 57 | } 58 | 59 | // 60 | } 61 | ``` 62 | 63 | ## Attaching Middleware 64 | 65 | Now we've done registering our first middleware, it's time to attach this middleware somewehre on a bunch of routes or a particular route. 66 | 67 | In routes file we can attach the middleware in the meta property with middleware property with string or array syntax, notice that we can run multiple middlewares together by order. 68 | 69 | ```ts 70 | { 71 | name: 'dashboard', 72 | path: '/dashboard', 73 | component: () => import('@/layouts/dashboard.vue'), 74 | meta: { 75 | middleware: 'dashboard', 76 | // or using array syntax.. 77 | middleware: ['dashboard', 'logger'], // use extra middleware in sub routes.. 78 | }, 79 | } 80 | ``` 81 | 82 | ## Guard 83 | 84 | When you create your authentication system, likely you will need to check if the user is trying to access login, register pages.. when it's already logged in 85 | 86 | In this senario the guard comes in to play, notice how we're going to use the same middleware but this time with guest guard to identity the guest pages, take a look the [earlier example](#creating-middleware-file) to get it into your mind. 87 | 88 | Feel free to add new value as a guard and you will receive this value in the middleware, by default if you don't pass a guard value it's be undefined. 89 | 90 | ```ts 91 | { 92 | name: 'login', 93 | path: 'login', 94 | component: () => import('@/pages/login.vue'), 95 | meta: { 96 | middleware: 'dashboard:guest', 97 | }, 98 | }, 99 | ``` 100 | 101 | ## Redirect 102 | 103 | When a middleware runs you may use router.push function to redirect an unauthenticated user or for other reasons somewhere, or using the redirect utility to do the same. 104 | 105 | Notice that you don't need to call the next navigation guard to move the next route, for aborting the navigation take a look at the next section. 106 | 107 | ```ts 108 | export default ({ redirect }) => { 109 | const store = useAuthStore() 110 | 111 | if (!store.loggedIn) { 112 | redirect('/login') 113 | } 114 | 115 | // ... 116 | } 117 | ``` 118 | 119 | ## Aborting Navigation 120 | 121 | There are two ways to abort a navigation by whether using the utility function abort or returning false explicitly. 122 | 123 | ```ts 124 | export default ({ abort })) => { 125 | if (forSomeReasonWillAbort()) { 126 | return abort() 127 | } 128 | 129 | // Using `false` explicitly. 130 | if (forSomeReasonWillAbort()) { 131 | return false 132 | } 133 | } 134 | ``` 135 | 136 | ## Advanced 137 | 138 | Sometimes you may want to apply multiple middlewares in the parent layout but some reasons you want to exclude this middleware from a child page, you may achieve this with excludeMiddleware property, actaully you can apply another middleware children. 139 | 140 | ```ts 141 | { 142 | name: 'dashboard', 143 | path: '/dashboard', 144 | component: () => import('@/layouts/dashboard.vue'), 145 | meta: { 146 | middleware: ['dashboard', 'reporter'], 147 | }, 148 | children: [ 149 | { 150 | name: 'dashboard_home', 151 | path: '', 152 | component: () => import('@/pages/dashboard/index.vue'), 153 | }, 154 | { 155 | // Final middleware list of this route would be: ['dashboard'] 156 | name: 'dashboard_users', 157 | path: 'users', 158 | component: () => import('@/pages/dashboard/users.vue'), 159 | meta: { 160 | excludeMiddleware: 'reporter', 161 | }, 162 | }, 163 | { 164 | // Final middleware list of this route would be: ['dashboard', 'reporter', 'logger'] 165 | name: 'dashboard_posts', 166 | path: 'posts', 167 | component: () => import('@/pages/dashboard/posts.vue'), 168 | meta: { 169 | middleware: 'logger', 170 | }, 171 | }, 172 | ] 173 | } 174 | ``` 175 | 176 | ## Context 177 | 178 | | Function | Description | Type | 179 | | ------------| ------------- | --------- | 180 | | `app` | The app instance | `App` | 181 | | `router` | The router instance | `Router` | 182 | | `from` | The current route location | `RouteLocationNormalized` | 183 | | `to` | The target route location | `RouteLocationNormalized` | 184 | | `redirect` | Redirect to location | `(to: RouteLocationRaw) => void` | 185 | | `abort` | Abort the navigation | `() => Symbol \| boolean` | 186 | | `guard` | The guard identifier | `boolean` | -------------------------------------------------------------------------------- /docs/pnpm-lock.yaml: -------------------------------------------------------------------------------- 1 | lockfileVersion: '6.0' 2 | 3 | settings: 4 | autoInstallPeers: true 5 | excludeLinksFromLockfile: false 6 | 7 | dependencies: 8 | vitepress: 9 | specifier: ^1.1.3 10 | version: 1.1.3(@algolia/client-search@4.23.3)(search-insights@2.13.0) 11 | 12 | packages: 13 | 14 | /@algolia/autocomplete-core@1.9.3(@algolia/client-search@4.23.3)(algoliasearch@4.23.3)(search-insights@2.13.0): 15 | resolution: {integrity: sha512-009HdfugtGCdC4JdXUbVJClA0q0zh24yyePn+KUGk3rP7j8FEe/m5Yo/z65gn6nP/cM39PxpzqKrL7A6fP6PPw==} 16 | dependencies: 17 | '@algolia/autocomplete-plugin-algolia-insights': 1.9.3(@algolia/client-search@4.23.3)(algoliasearch@4.23.3)(search-insights@2.13.0) 18 | '@algolia/autocomplete-shared': 1.9.3(@algolia/client-search@4.23.3)(algoliasearch@4.23.3) 19 | transitivePeerDependencies: 20 | - '@algolia/client-search' 21 | - algoliasearch 22 | - search-insights 23 | dev: false 24 | 25 | /@algolia/autocomplete-plugin-algolia-insights@1.9.3(@algolia/client-search@4.23.3)(algoliasearch@4.23.3)(search-insights@2.13.0): 26 | resolution: {integrity: sha512-a/yTUkcO/Vyy+JffmAnTWbr4/90cLzw+CC3bRbhnULr/EM0fGNvM13oQQ14f2moLMcVDyAx/leczLlAOovhSZg==} 27 | peerDependencies: 28 | search-insights: '>= 1 < 3' 29 | dependencies: 30 | '@algolia/autocomplete-shared': 1.9.3(@algolia/client-search@4.23.3)(algoliasearch@4.23.3) 31 | search-insights: 2.13.0 32 | transitivePeerDependencies: 33 | - '@algolia/client-search' 34 | - algoliasearch 35 | dev: false 36 | 37 | /@algolia/autocomplete-preset-algolia@1.9.3(@algolia/client-search@4.23.3)(algoliasearch@4.23.3): 38 | resolution: {integrity: sha512-d4qlt6YmrLMYy95n5TB52wtNDr6EgAIPH81dvvvW8UmuWRgxEtY0NJiPwl/h95JtG2vmRM804M0DSwMCNZlzRA==} 39 | peerDependencies: 40 | '@algolia/client-search': '>= 4.9.1 < 6' 41 | algoliasearch: '>= 4.9.1 < 6' 42 | dependencies: 43 | '@algolia/autocomplete-shared': 1.9.3(@algolia/client-search@4.23.3)(algoliasearch@4.23.3) 44 | '@algolia/client-search': 4.23.3 45 | algoliasearch: 4.23.3 46 | dev: false 47 | 48 | /@algolia/autocomplete-shared@1.9.3(@algolia/client-search@4.23.3)(algoliasearch@4.23.3): 49 | resolution: {integrity: sha512-Wnm9E4Ye6Rl6sTTqjoymD+l8DjSTHsHboVRYrKgEt8Q7UHm9nYbqhN/i0fhUYA3OAEH7WA8x3jfpnmJm3rKvaQ==} 50 | peerDependencies: 51 | '@algolia/client-search': '>= 4.9.1 < 6' 52 | algoliasearch: '>= 4.9.1 < 6' 53 | dependencies: 54 | '@algolia/client-search': 4.23.3 55 | algoliasearch: 4.23.3 56 | dev: false 57 | 58 | /@algolia/cache-browser-local-storage@4.23.3: 59 | resolution: {integrity: sha512-vRHXYCpPlTDE7i6UOy2xE03zHF2C8MEFjPN2v7fRbqVpcOvAUQK81x3Kc21xyb5aSIpYCjWCZbYZuz8Glyzyyg==} 60 | dependencies: 61 | '@algolia/cache-common': 4.23.3 62 | dev: false 63 | 64 | /@algolia/cache-common@4.23.3: 65 | resolution: {integrity: sha512-h9XcNI6lxYStaw32pHpB1TMm0RuxphF+Ik4o7tcQiodEdpKK+wKufY6QXtba7t3k8eseirEMVB83uFFF3Nu54A==} 66 | dev: false 67 | 68 | /@algolia/cache-in-memory@4.23.3: 69 | resolution: {integrity: sha512-yvpbuUXg/+0rbcagxNT7un0eo3czx2Uf0y4eiR4z4SD7SiptwYTpbuS0IHxcLHG3lq22ukx1T6Kjtk/rT+mqNg==} 70 | dependencies: 71 | '@algolia/cache-common': 4.23.3 72 | dev: false 73 | 74 | /@algolia/client-account@4.23.3: 75 | resolution: {integrity: sha512-hpa6S5d7iQmretHHF40QGq6hz0anWEHGlULcTIT9tbUssWUriN9AUXIFQ8Ei4w9azD0hc1rUok9/DeQQobhQMA==} 76 | dependencies: 77 | '@algolia/client-common': 4.23.3 78 | '@algolia/client-search': 4.23.3 79 | '@algolia/transporter': 4.23.3 80 | dev: false 81 | 82 | /@algolia/client-analytics@4.23.3: 83 | resolution: {integrity: sha512-LBsEARGS9cj8VkTAVEZphjxTjMVCci+zIIiRhpFun9jGDUlS1XmhCW7CTrnaWeIuCQS/2iPyRqSy1nXPjcBLRA==} 84 | dependencies: 85 | '@algolia/client-common': 4.23.3 86 | '@algolia/client-search': 4.23.3 87 | '@algolia/requester-common': 4.23.3 88 | '@algolia/transporter': 4.23.3 89 | dev: false 90 | 91 | /@algolia/client-common@4.23.3: 92 | resolution: {integrity: sha512-l6EiPxdAlg8CYhroqS5ybfIczsGUIAC47slLPOMDeKSVXYG1n0qGiz4RjAHLw2aD0xzh2EXZ7aRguPfz7UKDKw==} 93 | dependencies: 94 | '@algolia/requester-common': 4.23.3 95 | '@algolia/transporter': 4.23.3 96 | dev: false 97 | 98 | /@algolia/client-personalization@4.23.3: 99 | resolution: {integrity: sha512-3E3yF3Ocr1tB/xOZiuC3doHQBQ2zu2MPTYZ0d4lpfWads2WTKG7ZzmGnsHmm63RflvDeLK/UVx7j2b3QuwKQ2g==} 100 | dependencies: 101 | '@algolia/client-common': 4.23.3 102 | '@algolia/requester-common': 4.23.3 103 | '@algolia/transporter': 4.23.3 104 | dev: false 105 | 106 | /@algolia/client-search@4.23.3: 107 | resolution: {integrity: sha512-P4VAKFHqU0wx9O+q29Q8YVuaowaZ5EM77rxfmGnkHUJggh28useXQdopokgwMeYw2XUht49WX5RcTQ40rZIabw==} 108 | dependencies: 109 | '@algolia/client-common': 4.23.3 110 | '@algolia/requester-common': 4.23.3 111 | '@algolia/transporter': 4.23.3 112 | dev: false 113 | 114 | /@algolia/logger-common@4.23.3: 115 | resolution: {integrity: sha512-y9kBtmJwiZ9ZZ+1Ek66P0M68mHQzKRxkW5kAAXYN/rdzgDN0d2COsViEFufxJ0pb45K4FRcfC7+33YB4BLrZ+g==} 116 | dev: false 117 | 118 | /@algolia/logger-console@4.23.3: 119 | resolution: {integrity: sha512-8xoiseoWDKuCVnWP8jHthgaeobDLolh00KJAdMe9XPrWPuf1by732jSpgy2BlsLTaT9m32pHI8CRfrOqQzHv3A==} 120 | dependencies: 121 | '@algolia/logger-common': 4.23.3 122 | dev: false 123 | 124 | /@algolia/recommend@4.23.3: 125 | resolution: {integrity: sha512-9fK4nXZF0bFkdcLBRDexsnGzVmu4TSYZqxdpgBW2tEyfuSSY54D4qSRkLmNkrrz4YFvdh2GM1gA8vSsnZPR73w==} 126 | dependencies: 127 | '@algolia/cache-browser-local-storage': 4.23.3 128 | '@algolia/cache-common': 4.23.3 129 | '@algolia/cache-in-memory': 4.23.3 130 | '@algolia/client-common': 4.23.3 131 | '@algolia/client-search': 4.23.3 132 | '@algolia/logger-common': 4.23.3 133 | '@algolia/logger-console': 4.23.3 134 | '@algolia/requester-browser-xhr': 4.23.3 135 | '@algolia/requester-common': 4.23.3 136 | '@algolia/requester-node-http': 4.23.3 137 | '@algolia/transporter': 4.23.3 138 | dev: false 139 | 140 | /@algolia/requester-browser-xhr@4.23.3: 141 | resolution: {integrity: sha512-jDWGIQ96BhXbmONAQsasIpTYWslyjkiGu0Quydjlowe+ciqySpiDUrJHERIRfELE5+wFc7hc1Q5hqjGoV7yghw==} 142 | dependencies: 143 | '@algolia/requester-common': 4.23.3 144 | dev: false 145 | 146 | /@algolia/requester-common@4.23.3: 147 | resolution: {integrity: sha512-xloIdr/bedtYEGcXCiF2muajyvRhwop4cMZo+K2qzNht0CMzlRkm8YsDdj5IaBhshqfgmBb3rTg4sL4/PpvLYw==} 148 | dev: false 149 | 150 | /@algolia/requester-node-http@4.23.3: 151 | resolution: {integrity: sha512-zgu++8Uj03IWDEJM3fuNl34s746JnZOWn1Uz5taV1dFyJhVM/kTNw9Ik7YJWiUNHJQXcaD8IXD1eCb0nq/aByA==} 152 | dependencies: 153 | '@algolia/requester-common': 4.23.3 154 | dev: false 155 | 156 | /@algolia/transporter@4.23.3: 157 | resolution: {integrity: sha512-Wjl5gttqnf/gQKJA+dafnD0Y6Yw97yvfY8R9h0dQltX1GXTgNs1zWgvtWW0tHl1EgMdhAyw189uWiZMnL3QebQ==} 158 | dependencies: 159 | '@algolia/cache-common': 4.23.3 160 | '@algolia/logger-common': 4.23.3 161 | '@algolia/requester-common': 4.23.3 162 | dev: false 163 | 164 | /@babel/helper-string-parser@7.24.1: 165 | resolution: {integrity: sha512-2ofRCjnnA9y+wk8b9IAREroeUP02KHp431N2mhKniy2yKIDKpbrHv9eXwm8cBeWQYcJmzv5qKCu65P47eCF7CQ==} 166 | engines: {node: '>=6.9.0'} 167 | dev: false 168 | 169 | /@babel/helper-validator-identifier@7.22.20: 170 | resolution: {integrity: sha512-Y4OZ+ytlatR8AI+8KZfKuL5urKp7qey08ha31L8b3BwewJAoJamTzyvxPR/5D+KkdJCGPq/+8TukHBlY10FX9A==} 171 | engines: {node: '>=6.9.0'} 172 | dev: false 173 | 174 | /@babel/parser@7.24.4: 175 | resolution: {integrity: sha512-zTvEBcghmeBma9QIGunWevvBAp4/Qu9Bdq+2k0Ot4fVMD6v3dsC9WOcRSKk7tRRyBM/53yKMJko9xOatGQAwSg==} 176 | engines: {node: '>=6.0.0'} 177 | hasBin: true 178 | dependencies: 179 | '@babel/types': 7.24.0 180 | dev: false 181 | 182 | /@babel/types@7.24.0: 183 | resolution: {integrity: sha512-+j7a5c253RfKh8iABBhywc8NSfP5LURe7Uh4qpsh6jc+aLJguvmIUBdjSdEMQv2bENrCR5MfRdjGo7vzS/ob7w==} 184 | engines: {node: '>=6.9.0'} 185 | dependencies: 186 | '@babel/helper-string-parser': 7.24.1 187 | '@babel/helper-validator-identifier': 7.22.20 188 | to-fast-properties: 2.0.0 189 | dev: false 190 | 191 | /@docsearch/css@3.6.0: 192 | resolution: {integrity: sha512-+sbxb71sWre+PwDK7X2T8+bhS6clcVMLwBPznX45Qu6opJcgRjAp7gYSDzVFp187J+feSj5dNBN1mJoi6ckkUQ==} 193 | dev: false 194 | 195 | /@docsearch/js@3.6.0(@algolia/client-search@4.23.3)(search-insights@2.13.0): 196 | resolution: {integrity: sha512-QujhqINEElrkIfKwyyyTfbsfMAYCkylInLYMRqHy7PHc8xTBQCow73tlo/Kc7oIwBrCLf0P3YhjlOeV4v8hevQ==} 197 | dependencies: 198 | '@docsearch/react': 3.6.0(@algolia/client-search@4.23.3)(search-insights@2.13.0) 199 | preact: 10.20.2 200 | transitivePeerDependencies: 201 | - '@algolia/client-search' 202 | - '@types/react' 203 | - react 204 | - react-dom 205 | - search-insights 206 | dev: false 207 | 208 | /@docsearch/react@3.6.0(@algolia/client-search@4.23.3)(search-insights@2.13.0): 209 | resolution: {integrity: sha512-HUFut4ztcVNmqy9gp/wxNbC7pTOHhgVVkHVGCACTuLhUKUhKAF9KYHJtMiLUJxEqiFLQiuri1fWF8zqwM/cu1w==} 210 | peerDependencies: 211 | '@types/react': '>= 16.8.0 < 19.0.0' 212 | react: '>= 16.8.0 < 19.0.0' 213 | react-dom: '>= 16.8.0 < 19.0.0' 214 | search-insights: '>= 1 < 3' 215 | peerDependenciesMeta: 216 | '@types/react': 217 | optional: true 218 | react: 219 | optional: true 220 | react-dom: 221 | optional: true 222 | search-insights: 223 | optional: true 224 | dependencies: 225 | '@algolia/autocomplete-core': 1.9.3(@algolia/client-search@4.23.3)(algoliasearch@4.23.3)(search-insights@2.13.0) 226 | '@algolia/autocomplete-preset-algolia': 1.9.3(@algolia/client-search@4.23.3)(algoliasearch@4.23.3) 227 | '@docsearch/css': 3.6.0 228 | algoliasearch: 4.23.3 229 | search-insights: 2.13.0 230 | transitivePeerDependencies: 231 | - '@algolia/client-search' 232 | dev: false 233 | 234 | /@esbuild/aix-ppc64@0.20.2: 235 | resolution: {integrity: sha512-D+EBOJHXdNZcLJRBkhENNG8Wji2kgc9AZ9KiPr1JuZjsNtyHzrsfLRrY0tk2H2aoFu6RANO1y1iPPUCDYWkb5g==} 236 | engines: {node: '>=12'} 237 | cpu: [ppc64] 238 | os: [aix] 239 | requiresBuild: true 240 | dev: false 241 | optional: true 242 | 243 | /@esbuild/android-arm64@0.20.2: 244 | resolution: {integrity: sha512-mRzjLacRtl/tWU0SvD8lUEwb61yP9cqQo6noDZP/O8VkwafSYwZ4yWy24kan8jE/IMERpYncRt2dw438LP3Xmg==} 245 | engines: {node: '>=12'} 246 | cpu: [arm64] 247 | os: [android] 248 | requiresBuild: true 249 | dev: false 250 | optional: true 251 | 252 | /@esbuild/android-arm@0.20.2: 253 | resolution: {integrity: sha512-t98Ra6pw2VaDhqNWO2Oph2LXbz/EJcnLmKLGBJwEwXX/JAN83Fym1rU8l0JUWK6HkIbWONCSSatf4sf2NBRx/w==} 254 | engines: {node: '>=12'} 255 | cpu: [arm] 256 | os: [android] 257 | requiresBuild: true 258 | dev: false 259 | optional: true 260 | 261 | /@esbuild/android-x64@0.20.2: 262 | resolution: {integrity: sha512-btzExgV+/lMGDDa194CcUQm53ncxzeBrWJcncOBxuC6ndBkKxnHdFJn86mCIgTELsooUmwUm9FkhSp5HYu00Rg==} 263 | engines: {node: '>=12'} 264 | cpu: [x64] 265 | os: [android] 266 | requiresBuild: true 267 | dev: false 268 | optional: true 269 | 270 | /@esbuild/darwin-arm64@0.20.2: 271 | resolution: {integrity: sha512-4J6IRT+10J3aJH3l1yzEg9y3wkTDgDk7TSDFX+wKFiWjqWp/iCfLIYzGyasx9l0SAFPT1HwSCR+0w/h1ES/MjA==} 272 | engines: {node: '>=12'} 273 | cpu: [arm64] 274 | os: [darwin] 275 | requiresBuild: true 276 | dev: false 277 | optional: true 278 | 279 | /@esbuild/darwin-x64@0.20.2: 280 | resolution: {integrity: sha512-tBcXp9KNphnNH0dfhv8KYkZhjc+H3XBkF5DKtswJblV7KlT9EI2+jeA8DgBjp908WEuYll6pF+UStUCfEpdysA==} 281 | engines: {node: '>=12'} 282 | cpu: [x64] 283 | os: [darwin] 284 | requiresBuild: true 285 | dev: false 286 | optional: true 287 | 288 | /@esbuild/freebsd-arm64@0.20.2: 289 | resolution: {integrity: sha512-d3qI41G4SuLiCGCFGUrKsSeTXyWG6yem1KcGZVS+3FYlYhtNoNgYrWcvkOoaqMhwXSMrZRl69ArHsGJ9mYdbbw==} 290 | engines: {node: '>=12'} 291 | cpu: [arm64] 292 | os: [freebsd] 293 | requiresBuild: true 294 | dev: false 295 | optional: true 296 | 297 | /@esbuild/freebsd-x64@0.20.2: 298 | resolution: {integrity: sha512-d+DipyvHRuqEeM5zDivKV1KuXn9WeRX6vqSqIDgwIfPQtwMP4jaDsQsDncjTDDsExT4lR/91OLjRo8bmC1e+Cw==} 299 | engines: {node: '>=12'} 300 | cpu: [x64] 301 | os: [freebsd] 302 | requiresBuild: true 303 | dev: false 304 | optional: true 305 | 306 | /@esbuild/linux-arm64@0.20.2: 307 | resolution: {integrity: sha512-9pb6rBjGvTFNira2FLIWqDk/uaf42sSyLE8j1rnUpuzsODBq7FvpwHYZxQ/It/8b+QOS1RYfqgGFNLRI+qlq2A==} 308 | engines: {node: '>=12'} 309 | cpu: [arm64] 310 | os: [linux] 311 | requiresBuild: true 312 | dev: false 313 | optional: true 314 | 315 | /@esbuild/linux-arm@0.20.2: 316 | resolution: {integrity: sha512-VhLPeR8HTMPccbuWWcEUD1Az68TqaTYyj6nfE4QByZIQEQVWBB8vup8PpR7y1QHL3CpcF6xd5WVBU/+SBEvGTg==} 317 | engines: {node: '>=12'} 318 | cpu: [arm] 319 | os: [linux] 320 | requiresBuild: true 321 | dev: false 322 | optional: true 323 | 324 | /@esbuild/linux-ia32@0.20.2: 325 | resolution: {integrity: sha512-o10utieEkNPFDZFQm9CoP7Tvb33UutoJqg3qKf1PWVeeJhJw0Q347PxMvBgVVFgouYLGIhFYG0UGdBumROyiig==} 326 | engines: {node: '>=12'} 327 | cpu: [ia32] 328 | os: [linux] 329 | requiresBuild: true 330 | dev: false 331 | optional: true 332 | 333 | /@esbuild/linux-loong64@0.20.2: 334 | resolution: {integrity: sha512-PR7sp6R/UC4CFVomVINKJ80pMFlfDfMQMYynX7t1tNTeivQ6XdX5r2XovMmha/VjR1YN/HgHWsVcTRIMkymrgQ==} 335 | engines: {node: '>=12'} 336 | cpu: [loong64] 337 | os: [linux] 338 | requiresBuild: true 339 | dev: false 340 | optional: true 341 | 342 | /@esbuild/linux-mips64el@0.20.2: 343 | resolution: {integrity: sha512-4BlTqeutE/KnOiTG5Y6Sb/Hw6hsBOZapOVF6njAESHInhlQAghVVZL1ZpIctBOoTFbQyGW+LsVYZ8lSSB3wkjA==} 344 | engines: {node: '>=12'} 345 | cpu: [mips64el] 346 | os: [linux] 347 | requiresBuild: true 348 | dev: false 349 | optional: true 350 | 351 | /@esbuild/linux-ppc64@0.20.2: 352 | resolution: {integrity: sha512-rD3KsaDprDcfajSKdn25ooz5J5/fWBylaaXkuotBDGnMnDP1Uv5DLAN/45qfnf3JDYyJv/ytGHQaziHUdyzaAg==} 353 | engines: {node: '>=12'} 354 | cpu: [ppc64] 355 | os: [linux] 356 | requiresBuild: true 357 | dev: false 358 | optional: true 359 | 360 | /@esbuild/linux-riscv64@0.20.2: 361 | resolution: {integrity: sha512-snwmBKacKmwTMmhLlz/3aH1Q9T8v45bKYGE3j26TsaOVtjIag4wLfWSiZykXzXuE1kbCE+zJRmwp+ZbIHinnVg==} 362 | engines: {node: '>=12'} 363 | cpu: [riscv64] 364 | os: [linux] 365 | requiresBuild: true 366 | dev: false 367 | optional: true 368 | 369 | /@esbuild/linux-s390x@0.20.2: 370 | resolution: {integrity: sha512-wcWISOobRWNm3cezm5HOZcYz1sKoHLd8VL1dl309DiixxVFoFe/o8HnwuIwn6sXre88Nwj+VwZUvJf4AFxkyrQ==} 371 | engines: {node: '>=12'} 372 | cpu: [s390x] 373 | os: [linux] 374 | requiresBuild: true 375 | dev: false 376 | optional: true 377 | 378 | /@esbuild/linux-x64@0.20.2: 379 | resolution: {integrity: sha512-1MdwI6OOTsfQfek8sLwgyjOXAu+wKhLEoaOLTjbijk6E2WONYpH9ZU2mNtR+lZ2B4uwr+usqGuVfFT9tMtGvGw==} 380 | engines: {node: '>=12'} 381 | cpu: [x64] 382 | os: [linux] 383 | requiresBuild: true 384 | dev: false 385 | optional: true 386 | 387 | /@esbuild/netbsd-x64@0.20.2: 388 | resolution: {integrity: sha512-K8/DhBxcVQkzYc43yJXDSyjlFeHQJBiowJ0uVL6Tor3jGQfSGHNNJcWxNbOI8v5k82prYqzPuwkzHt3J1T1iZQ==} 389 | engines: {node: '>=12'} 390 | cpu: [x64] 391 | os: [netbsd] 392 | requiresBuild: true 393 | dev: false 394 | optional: true 395 | 396 | /@esbuild/openbsd-x64@0.20.2: 397 | resolution: {integrity: sha512-eMpKlV0SThJmmJgiVyN9jTPJ2VBPquf6Kt/nAoo6DgHAoN57K15ZghiHaMvqjCye/uU4X5u3YSMgVBI1h3vKrQ==} 398 | engines: {node: '>=12'} 399 | cpu: [x64] 400 | os: [openbsd] 401 | requiresBuild: true 402 | dev: false 403 | optional: true 404 | 405 | /@esbuild/sunos-x64@0.20.2: 406 | resolution: {integrity: sha512-2UyFtRC6cXLyejf/YEld4Hajo7UHILetzE1vsRcGL3earZEW77JxrFjH4Ez2qaTiEfMgAXxfAZCm1fvM/G/o8w==} 407 | engines: {node: '>=12'} 408 | cpu: [x64] 409 | os: [sunos] 410 | requiresBuild: true 411 | dev: false 412 | optional: true 413 | 414 | /@esbuild/win32-arm64@0.20.2: 415 | resolution: {integrity: sha512-GRibxoawM9ZCnDxnP3usoUDO9vUkpAxIIZ6GQI+IlVmr5kP3zUq+l17xELTHMWTWzjxa2guPNyrpq1GWmPvcGQ==} 416 | engines: {node: '>=12'} 417 | cpu: [arm64] 418 | os: [win32] 419 | requiresBuild: true 420 | dev: false 421 | optional: true 422 | 423 | /@esbuild/win32-ia32@0.20.2: 424 | resolution: {integrity: sha512-HfLOfn9YWmkSKRQqovpnITazdtquEW8/SoHW7pWpuEeguaZI4QnCRW6b+oZTztdBnZOS2hqJ6im/D5cPzBTTlQ==} 425 | engines: {node: '>=12'} 426 | cpu: [ia32] 427 | os: [win32] 428 | requiresBuild: true 429 | dev: false 430 | optional: true 431 | 432 | /@esbuild/win32-x64@0.20.2: 433 | resolution: {integrity: sha512-N49X4lJX27+l9jbLKSqZ6bKNjzQvHaT8IIFUy+YIqmXQdjYCToGWwOItDrfby14c78aDd5NHQl29xingXfCdLQ==} 434 | engines: {node: '>=12'} 435 | cpu: [x64] 436 | os: [win32] 437 | requiresBuild: true 438 | dev: false 439 | optional: true 440 | 441 | /@jridgewell/sourcemap-codec@1.4.15: 442 | resolution: {integrity: sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==} 443 | dev: false 444 | 445 | /@rollup/rollup-android-arm-eabi@4.16.4: 446 | resolution: {integrity: sha512-GkhjAaQ8oUTOKE4g4gsZ0u8K/IHU1+2WQSgS1TwTcYvL+sjbaQjNHFXbOJ6kgqGHIO1DfUhI/Sphi9GkRT9K+Q==} 447 | cpu: [arm] 448 | os: [android] 449 | requiresBuild: true 450 | dev: false 451 | optional: true 452 | 453 | /@rollup/rollup-android-arm64@4.16.4: 454 | resolution: {integrity: sha512-Bvm6D+NPbGMQOcxvS1zUl8H7DWlywSXsphAeOnVeiZLQ+0J6Is8T7SrjGTH29KtYkiY9vld8ZnpV3G2EPbom+w==} 455 | cpu: [arm64] 456 | os: [android] 457 | requiresBuild: true 458 | dev: false 459 | optional: true 460 | 461 | /@rollup/rollup-darwin-arm64@4.16.4: 462 | resolution: {integrity: sha512-i5d64MlnYBO9EkCOGe5vPR/EeDwjnKOGGdd7zKFhU5y8haKhQZTN2DgVtpODDMxUr4t2K90wTUJg7ilgND6bXw==} 463 | cpu: [arm64] 464 | os: [darwin] 465 | requiresBuild: true 466 | dev: false 467 | optional: true 468 | 469 | /@rollup/rollup-darwin-x64@4.16.4: 470 | resolution: {integrity: sha512-WZupV1+CdUYehaZqjaFTClJI72fjJEgTXdf4NbW69I9XyvdmztUExBtcI2yIIU6hJtYvtwS6pkTkHJz+k08mAQ==} 471 | cpu: [x64] 472 | os: [darwin] 473 | requiresBuild: true 474 | dev: false 475 | optional: true 476 | 477 | /@rollup/rollup-linux-arm-gnueabihf@4.16.4: 478 | resolution: {integrity: sha512-ADm/xt86JUnmAfA9mBqFcRp//RVRt1ohGOYF6yL+IFCYqOBNwy5lbEK05xTsEoJq+/tJzg8ICUtS82WinJRuIw==} 479 | cpu: [arm] 480 | os: [linux] 481 | requiresBuild: true 482 | dev: false 483 | optional: true 484 | 485 | /@rollup/rollup-linux-arm-musleabihf@4.16.4: 486 | resolution: {integrity: sha512-tJfJaXPiFAG+Jn3cutp7mCs1ePltuAgRqdDZrzb1aeE3TktWWJ+g7xK9SNlaSUFw6IU4QgOxAY4rA+wZUT5Wfg==} 487 | cpu: [arm] 488 | os: [linux] 489 | requiresBuild: true 490 | dev: false 491 | optional: true 492 | 493 | /@rollup/rollup-linux-arm64-gnu@4.16.4: 494 | resolution: {integrity: sha512-7dy1BzQkgYlUTapDTvK997cgi0Orh5Iu7JlZVBy1MBURk7/HSbHkzRnXZa19ozy+wwD8/SlpJnOOckuNZtJR9w==} 495 | cpu: [arm64] 496 | os: [linux] 497 | requiresBuild: true 498 | dev: false 499 | optional: true 500 | 501 | /@rollup/rollup-linux-arm64-musl@4.16.4: 502 | resolution: {integrity: sha512-zsFwdUw5XLD1gQe0aoU2HVceI6NEW7q7m05wA46eUAyrkeNYExObfRFQcvA6zw8lfRc5BHtan3tBpo+kqEOxmg==} 503 | cpu: [arm64] 504 | os: [linux] 505 | requiresBuild: true 506 | dev: false 507 | optional: true 508 | 509 | /@rollup/rollup-linux-powerpc64le-gnu@4.16.4: 510 | resolution: {integrity: sha512-p8C3NnxXooRdNrdv6dBmRTddEapfESEUflpICDNKXpHvTjRRq1J82CbU5G3XfebIZyI3B0s074JHMWD36qOW6w==} 511 | cpu: [ppc64] 512 | os: [linux] 513 | requiresBuild: true 514 | dev: false 515 | optional: true 516 | 517 | /@rollup/rollup-linux-riscv64-gnu@4.16.4: 518 | resolution: {integrity: sha512-Lh/8ckoar4s4Id2foY7jNgitTOUQczwMWNYi+Mjt0eQ9LKhr6sK477REqQkmy8YHY3Ca3A2JJVdXnfb3Rrwkng==} 519 | cpu: [riscv64] 520 | os: [linux] 521 | requiresBuild: true 522 | dev: false 523 | optional: true 524 | 525 | /@rollup/rollup-linux-s390x-gnu@4.16.4: 526 | resolution: {integrity: sha512-1xwwn9ZCQYuqGmulGsTZoKrrn0z2fAur2ujE60QgyDpHmBbXbxLaQiEvzJWDrscRq43c8DnuHx3QorhMTZgisQ==} 527 | cpu: [s390x] 528 | os: [linux] 529 | requiresBuild: true 530 | dev: false 531 | optional: true 532 | 533 | /@rollup/rollup-linux-x64-gnu@4.16.4: 534 | resolution: {integrity: sha512-LuOGGKAJ7dfRtxVnO1i3qWc6N9sh0Em/8aZ3CezixSTM+E9Oq3OvTsvC4sm6wWjzpsIlOCnZjdluINKESflJLA==} 535 | cpu: [x64] 536 | os: [linux] 537 | requiresBuild: true 538 | dev: false 539 | optional: true 540 | 541 | /@rollup/rollup-linux-x64-musl@4.16.4: 542 | resolution: {integrity: sha512-ch86i7KkJKkLybDP2AtySFTRi5fM3KXp0PnHocHuJMdZwu7BuyIKi35BE9guMlmTpwwBTB3ljHj9IQXnTCD0vA==} 543 | cpu: [x64] 544 | os: [linux] 545 | requiresBuild: true 546 | dev: false 547 | optional: true 548 | 549 | /@rollup/rollup-win32-arm64-msvc@4.16.4: 550 | resolution: {integrity: sha512-Ma4PwyLfOWZWayfEsNQzTDBVW8PZ6TUUN1uFTBQbF2Chv/+sjenE86lpiEwj2FiviSmSZ4Ap4MaAfl1ciF4aSA==} 551 | cpu: [arm64] 552 | os: [win32] 553 | requiresBuild: true 554 | dev: false 555 | optional: true 556 | 557 | /@rollup/rollup-win32-ia32-msvc@4.16.4: 558 | resolution: {integrity: sha512-9m/ZDrQsdo/c06uOlP3W9G2ENRVzgzbSXmXHT4hwVaDQhYcRpi9bgBT0FTG9OhESxwK0WjQxYOSfv40cU+T69w==} 559 | cpu: [ia32] 560 | os: [win32] 561 | requiresBuild: true 562 | dev: false 563 | optional: true 564 | 565 | /@rollup/rollup-win32-x64-msvc@4.16.4: 566 | resolution: {integrity: sha512-YunpoOAyGLDseanENHmbFvQSfVL5BxW3k7hhy0eN4rb3gS/ct75dVD0EXOWIqFT/nE8XYW6LP6vz6ctKRi0k9A==} 567 | cpu: [x64] 568 | os: [win32] 569 | requiresBuild: true 570 | dev: false 571 | optional: true 572 | 573 | /@shikijs/core@1.3.0: 574 | resolution: {integrity: sha512-7fedsBfuILDTBmrYZNFI8B6ATTxhQAasUHllHmjvSZPnoq4bULWoTpHwmuQvZ8Aq03/tAa2IGo6RXqWtHdWaCA==} 575 | dev: false 576 | 577 | /@shikijs/transformers@1.3.0: 578 | resolution: {integrity: sha512-3mlpg2I9CjhjE96dEWQOGeCWoPcyTov3s4aAsHmgvnTHa8MBknEnCQy8/xivJPSpD+olqOqIEoHnLfbNJK29AA==} 579 | dependencies: 580 | shiki: 1.3.0 581 | dev: false 582 | 583 | /@types/estree@1.0.5: 584 | resolution: {integrity: sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==} 585 | dev: false 586 | 587 | /@types/linkify-it@3.0.5: 588 | resolution: {integrity: sha512-yg6E+u0/+Zjva+buc3EIb+29XEg4wltq7cSmd4Uc2EE/1nUVmxyzpX6gUXD0V8jIrG0r7YeOGVIbYRkxeooCtw==} 589 | dev: false 590 | 591 | /@types/markdown-it@14.0.1: 592 | resolution: {integrity: sha512-6WfOG3jXR78DW8L5cTYCVVGAsIFZskRHCDo5tbqa+qtKVt4oDRVH7hyIWu1SpDQJlmIoEivNQZ5h+AGAOrgOtQ==} 593 | dependencies: 594 | '@types/linkify-it': 3.0.5 595 | '@types/mdurl': 1.0.5 596 | dev: false 597 | 598 | /@types/mdurl@1.0.5: 599 | resolution: {integrity: sha512-6L6VymKTzYSrEf4Nev4Xa1LCHKrlTlYCBMTlQKFuddo1CvQcE52I0mwfOJayueUC7MJuXOeHTcIU683lzd0cUA==} 600 | dev: false 601 | 602 | /@types/web-bluetooth@0.0.20: 603 | resolution: {integrity: sha512-g9gZnnXVq7gM7v3tJCWV/qw7w+KeOlSHAhgF9RytFyifW6AF61hdT2ucrYhPq9hLs5JIryeupHV3qGk95dH9ow==} 604 | dev: false 605 | 606 | /@vitejs/plugin-vue@5.0.4(vite@5.2.10)(vue@3.4.25): 607 | resolution: {integrity: sha512-WS3hevEszI6CEVEx28F8RjTX97k3KsrcY6kvTg7+Whm5y3oYvcqzVeGCU3hxSAn4uY2CLCkeokkGKpoctccilQ==} 608 | engines: {node: ^18.0.0 || >=20.0.0} 609 | peerDependencies: 610 | vite: ^5.0.0 611 | vue: ^3.2.25 612 | dependencies: 613 | vite: 5.2.10 614 | vue: 3.4.25 615 | dev: false 616 | 617 | /@vue/compiler-core@3.4.25: 618 | resolution: {integrity: sha512-Y2pLLopaElgWnMNolgG8w3C5nNUVev80L7hdQ5iIKPtMJvhVpG0zhnBG/g3UajJmZdvW0fktyZTotEHD1Srhbg==} 619 | dependencies: 620 | '@babel/parser': 7.24.4 621 | '@vue/shared': 3.4.25 622 | entities: 4.5.0 623 | estree-walker: 2.0.2 624 | source-map-js: 1.2.0 625 | dev: false 626 | 627 | /@vue/compiler-dom@3.4.25: 628 | resolution: {integrity: sha512-Ugz5DusW57+HjllAugLci19NsDK+VyjGvmbB2TXaTcSlQxwL++2PETHx/+Qv6qFwNLzSt7HKepPe4DcTE3pBWg==} 629 | dependencies: 630 | '@vue/compiler-core': 3.4.25 631 | '@vue/shared': 3.4.25 632 | dev: false 633 | 634 | /@vue/compiler-sfc@3.4.25: 635 | resolution: {integrity: sha512-m7rryuqzIoQpOBZ18wKyq05IwL6qEpZxFZfRxlNYuIPDqywrXQxgUwLXIvoU72gs6cRdY6wHD0WVZIFE4OEaAQ==} 636 | dependencies: 637 | '@babel/parser': 7.24.4 638 | '@vue/compiler-core': 3.4.25 639 | '@vue/compiler-dom': 3.4.25 640 | '@vue/compiler-ssr': 3.4.25 641 | '@vue/shared': 3.4.25 642 | estree-walker: 2.0.2 643 | magic-string: 0.30.10 644 | postcss: 8.4.38 645 | source-map-js: 1.2.0 646 | dev: false 647 | 648 | /@vue/compiler-ssr@3.4.25: 649 | resolution: {integrity: sha512-H2ohvM/Pf6LelGxDBnfbbXFPyM4NE3hrw0e/EpwuSiYu8c819wx+SVGdJ65p/sFrYDd6OnSDxN1MB2mN07hRSQ==} 650 | dependencies: 651 | '@vue/compiler-dom': 3.4.25 652 | '@vue/shared': 3.4.25 653 | dev: false 654 | 655 | /@vue/devtools-api@7.1.3(vue@3.4.25): 656 | resolution: {integrity: sha512-W8IwFJ/o5iUk78jpqhvScbgCsPiOp2uileDVC0NDtW38gCWhsnu9SeBTjcdu3lbwLdsjc+H1c5Msd/x9ApbcFA==} 657 | dependencies: 658 | '@vue/devtools-kit': 7.1.3(vue@3.4.25) 659 | transitivePeerDependencies: 660 | - vue 661 | dev: false 662 | 663 | /@vue/devtools-kit@7.1.3(vue@3.4.25): 664 | resolution: {integrity: sha512-NFskFSJMVCBXTkByuk2llzI3KD3Blcm7WqiRorWjD6nClHPgkH5BobDH08rfulqq5ocRt5xV+3qOT1Q9FXJrwQ==} 665 | peerDependencies: 666 | vue: ^3.0.0 667 | dependencies: 668 | '@vue/devtools-shared': 7.1.3 669 | hookable: 5.5.3 670 | mitt: 3.0.1 671 | perfect-debounce: 1.0.0 672 | speakingurl: 14.0.1 673 | vue: 3.4.25 674 | dev: false 675 | 676 | /@vue/devtools-shared@7.1.3: 677 | resolution: {integrity: sha512-KJ3AfgjTn3tJz/XKF+BlVShNPecim3G21oHRue+YQOsooW+0s+qXvm09U09aO7yBza5SivL1QgxSrzAbiKWjhQ==} 678 | dependencies: 679 | rfdc: 1.3.1 680 | dev: false 681 | 682 | /@vue/reactivity@3.4.25: 683 | resolution: {integrity: sha512-mKbEtKr1iTxZkAG3vm3BtKHAOhuI4zzsVcN0epDldU/THsrvfXRKzq+lZnjczZGnTdh3ojd86/WrP+u9M51pWQ==} 684 | dependencies: 685 | '@vue/shared': 3.4.25 686 | dev: false 687 | 688 | /@vue/runtime-core@3.4.25: 689 | resolution: {integrity: sha512-3qhsTqbEh8BMH3pXf009epCI5E7bKu28fJLi9O6W+ZGt/6xgSfMuGPqa5HRbUxLoehTNp5uWvzCr60KuiRIL0Q==} 690 | dependencies: 691 | '@vue/reactivity': 3.4.25 692 | '@vue/shared': 3.4.25 693 | dev: false 694 | 695 | /@vue/runtime-dom@3.4.25: 696 | resolution: {integrity: sha512-ode0sj77kuwXwSc+2Yhk8JMHZh1sZp9F/51wdBiz3KGaWltbKtdihlJFhQG4H6AY+A06zzeMLkq6qu8uDSsaoA==} 697 | dependencies: 698 | '@vue/runtime-core': 3.4.25 699 | '@vue/shared': 3.4.25 700 | csstype: 3.1.3 701 | dev: false 702 | 703 | /@vue/server-renderer@3.4.25(vue@3.4.25): 704 | resolution: {integrity: sha512-8VTwq0Zcu3K4dWV0jOwIVINESE/gha3ifYCOKEhxOj6MEl5K5y8J8clQncTcDhKF+9U765nRw4UdUEXvrGhyVQ==} 705 | peerDependencies: 706 | vue: 3.4.25 707 | dependencies: 708 | '@vue/compiler-ssr': 3.4.25 709 | '@vue/shared': 3.4.25 710 | vue: 3.4.25 711 | dev: false 712 | 713 | /@vue/shared@3.4.25: 714 | resolution: {integrity: sha512-k0yappJ77g2+KNrIaF0FFnzwLvUBLUYr8VOwz+/6vLsmItFp51AcxLL7Ey3iPd7BIRyWPOcqUjMnm7OkahXllA==} 715 | dev: false 716 | 717 | /@vueuse/core@10.9.0(vue@3.4.25): 718 | resolution: {integrity: sha512-/1vjTol8SXnx6xewDEKfS0Ra//ncg4Hb0DaZiwKf7drgfMsKFExQ+FnnENcN6efPen+1kIzhLQoGSy0eDUVOMg==} 719 | dependencies: 720 | '@types/web-bluetooth': 0.0.20 721 | '@vueuse/metadata': 10.9.0 722 | '@vueuse/shared': 10.9.0(vue@3.4.25) 723 | vue-demi: 0.14.7(vue@3.4.25) 724 | transitivePeerDependencies: 725 | - '@vue/composition-api' 726 | - vue 727 | dev: false 728 | 729 | /@vueuse/integrations@10.9.0(focus-trap@7.5.4)(vue@3.4.25): 730 | resolution: {integrity: sha512-acK+A01AYdWSvL4BZmCoJAcyHJ6EqhmkQEXbQLwev1MY7NBnS+hcEMx/BzVoR9zKI+UqEPMD9u6PsyAuiTRT4Q==} 731 | peerDependencies: 732 | async-validator: '*' 733 | axios: '*' 734 | change-case: '*' 735 | drauu: '*' 736 | focus-trap: '*' 737 | fuse.js: '*' 738 | idb-keyval: '*' 739 | jwt-decode: '*' 740 | nprogress: '*' 741 | qrcode: '*' 742 | sortablejs: '*' 743 | universal-cookie: '*' 744 | peerDependenciesMeta: 745 | async-validator: 746 | optional: true 747 | axios: 748 | optional: true 749 | change-case: 750 | optional: true 751 | drauu: 752 | optional: true 753 | focus-trap: 754 | optional: true 755 | fuse.js: 756 | optional: true 757 | idb-keyval: 758 | optional: true 759 | jwt-decode: 760 | optional: true 761 | nprogress: 762 | optional: true 763 | qrcode: 764 | optional: true 765 | sortablejs: 766 | optional: true 767 | universal-cookie: 768 | optional: true 769 | dependencies: 770 | '@vueuse/core': 10.9.0(vue@3.4.25) 771 | '@vueuse/shared': 10.9.0(vue@3.4.25) 772 | focus-trap: 7.5.4 773 | vue-demi: 0.14.7(vue@3.4.25) 774 | transitivePeerDependencies: 775 | - '@vue/composition-api' 776 | - vue 777 | dev: false 778 | 779 | /@vueuse/metadata@10.9.0: 780 | resolution: {integrity: sha512-iddNbg3yZM0X7qFY2sAotomgdHK7YJ6sKUvQqbvwnf7TmaVPxS4EJydcNsVejNdS8iWCtDk+fYXr7E32nyTnGA==} 781 | dev: false 782 | 783 | /@vueuse/shared@10.9.0(vue@3.4.25): 784 | resolution: {integrity: sha512-Uud2IWncmAfJvRaFYzv5OHDli+FbOzxiVEQdLCKQKLyhz94PIyFC3CHcH7EDMwIn8NPtD06+PNbC/PiO0LGLtw==} 785 | dependencies: 786 | vue-demi: 0.14.7(vue@3.4.25) 787 | transitivePeerDependencies: 788 | - '@vue/composition-api' 789 | - vue 790 | dev: false 791 | 792 | /algoliasearch@4.23.3: 793 | resolution: {integrity: sha512-Le/3YgNvjW9zxIQMRhUHuhiUjAlKY/zsdZpfq4dlLqg6mEm0nL6yk+7f2hDOtLpxsgE4jSzDmvHL7nXdBp5feg==} 794 | dependencies: 795 | '@algolia/cache-browser-local-storage': 4.23.3 796 | '@algolia/cache-common': 4.23.3 797 | '@algolia/cache-in-memory': 4.23.3 798 | '@algolia/client-account': 4.23.3 799 | '@algolia/client-analytics': 4.23.3 800 | '@algolia/client-common': 4.23.3 801 | '@algolia/client-personalization': 4.23.3 802 | '@algolia/client-search': 4.23.3 803 | '@algolia/logger-common': 4.23.3 804 | '@algolia/logger-console': 4.23.3 805 | '@algolia/recommend': 4.23.3 806 | '@algolia/requester-browser-xhr': 4.23.3 807 | '@algolia/requester-common': 4.23.3 808 | '@algolia/requester-node-http': 4.23.3 809 | '@algolia/transporter': 4.23.3 810 | dev: false 811 | 812 | /csstype@3.1.3: 813 | resolution: {integrity: sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==} 814 | dev: false 815 | 816 | /entities@4.5.0: 817 | resolution: {integrity: sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==} 818 | engines: {node: '>=0.12'} 819 | dev: false 820 | 821 | /esbuild@0.20.2: 822 | resolution: {integrity: sha512-WdOOppmUNU+IbZ0PaDiTst80zjnrOkyJNHoKupIcVyU8Lvla3Ugx94VzkQ32Ijqd7UhHJy75gNWDMUekcrSJ6g==} 823 | engines: {node: '>=12'} 824 | hasBin: true 825 | requiresBuild: true 826 | optionalDependencies: 827 | '@esbuild/aix-ppc64': 0.20.2 828 | '@esbuild/android-arm': 0.20.2 829 | '@esbuild/android-arm64': 0.20.2 830 | '@esbuild/android-x64': 0.20.2 831 | '@esbuild/darwin-arm64': 0.20.2 832 | '@esbuild/darwin-x64': 0.20.2 833 | '@esbuild/freebsd-arm64': 0.20.2 834 | '@esbuild/freebsd-x64': 0.20.2 835 | '@esbuild/linux-arm': 0.20.2 836 | '@esbuild/linux-arm64': 0.20.2 837 | '@esbuild/linux-ia32': 0.20.2 838 | '@esbuild/linux-loong64': 0.20.2 839 | '@esbuild/linux-mips64el': 0.20.2 840 | '@esbuild/linux-ppc64': 0.20.2 841 | '@esbuild/linux-riscv64': 0.20.2 842 | '@esbuild/linux-s390x': 0.20.2 843 | '@esbuild/linux-x64': 0.20.2 844 | '@esbuild/netbsd-x64': 0.20.2 845 | '@esbuild/openbsd-x64': 0.20.2 846 | '@esbuild/sunos-x64': 0.20.2 847 | '@esbuild/win32-arm64': 0.20.2 848 | '@esbuild/win32-ia32': 0.20.2 849 | '@esbuild/win32-x64': 0.20.2 850 | dev: false 851 | 852 | /estree-walker@2.0.2: 853 | resolution: {integrity: sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==} 854 | dev: false 855 | 856 | /focus-trap@7.5.4: 857 | resolution: {integrity: sha512-N7kHdlgsO/v+iD/dMoJKtsSqs5Dz/dXZVebRgJw23LDk+jMi/974zyiOYDziY2JPp8xivq9BmUGwIJMiuSBi7w==} 858 | dependencies: 859 | tabbable: 6.2.0 860 | dev: false 861 | 862 | /fsevents@2.3.3: 863 | resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} 864 | engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} 865 | os: [darwin] 866 | requiresBuild: true 867 | dev: false 868 | optional: true 869 | 870 | /hookable@5.5.3: 871 | resolution: {integrity: sha512-Yc+BQe8SvoXH1643Qez1zqLRmbA5rCL+sSmk6TVos0LWVfNIB7PGncdlId77WzLGSIB5KaWgTaNTs2lNVEI6VQ==} 872 | dev: false 873 | 874 | /magic-string@0.30.10: 875 | resolution: {integrity: sha512-iIRwTIf0QKV3UAnYK4PU8uiEc4SRh5jX0mwpIwETPpHdhVM4f53RSwS/vXvN1JhGX+Cs7B8qIq3d6AH49O5fAQ==} 876 | dependencies: 877 | '@jridgewell/sourcemap-codec': 1.4.15 878 | dev: false 879 | 880 | /mark.js@8.11.1: 881 | resolution: {integrity: sha512-1I+1qpDt4idfgLQG+BNWmrqku+7/2bi5nLf4YwF8y8zXvmfiTBY3PV3ZibfrjBueCByROpuBjLLFCajqkgYoLQ==} 882 | dev: false 883 | 884 | /minisearch@6.3.0: 885 | resolution: {integrity: sha512-ihFnidEeU8iXzcVHy74dhkxh/dn8Dc08ERl0xwoMMGqp4+LvRSCgicb+zGqWthVokQKvCSxITlh3P08OzdTYCQ==} 886 | dev: false 887 | 888 | /mitt@3.0.1: 889 | resolution: {integrity: sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw==} 890 | dev: false 891 | 892 | /nanoid@3.3.7: 893 | resolution: {integrity: sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==} 894 | engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} 895 | hasBin: true 896 | dev: false 897 | 898 | /perfect-debounce@1.0.0: 899 | resolution: {integrity: sha512-xCy9V055GLEqoFaHoC1SoLIaLmWctgCUaBaWxDZ7/Zx4CTyX7cJQLJOok/orfjZAh9kEYpjJa4d0KcJmCbctZA==} 900 | dev: false 901 | 902 | /picocolors@1.0.0: 903 | resolution: {integrity: sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==} 904 | dev: false 905 | 906 | /postcss@8.4.38: 907 | resolution: {integrity: sha512-Wglpdk03BSfXkHoQa3b/oulrotAkwrlLDRSOb9D0bN86FdRyE9lppSp33aHNPgBa0JKCoB+drFLZkQoRRYae5A==} 908 | engines: {node: ^10 || ^12 || >=14} 909 | dependencies: 910 | nanoid: 3.3.7 911 | picocolors: 1.0.0 912 | source-map-js: 1.2.0 913 | dev: false 914 | 915 | /preact@10.20.2: 916 | resolution: {integrity: sha512-S1d1ernz3KQ+Y2awUxKakpfOg2CEmJmwOP+6igPx6dgr6pgDvenqYviyokWso2rhHvGtTlWWnJDa7RaPbQerTg==} 917 | dev: false 918 | 919 | /rfdc@1.3.1: 920 | resolution: {integrity: sha512-r5a3l5HzYlIC68TpmYKlxWjmOP6wiPJ1vWv2HeLhNsRZMrCkxeqxiHlQ21oXmQ4F3SiryXBHhAD7JZqvOJjFmg==} 921 | dev: false 922 | 923 | /rollup@4.16.4: 924 | resolution: {integrity: sha512-kuaTJSUbz+Wsb2ATGvEknkI12XV40vIiHmLuFlejoo7HtDok/O5eDDD0UpCVY5bBX5U5RYo8wWP83H7ZsqVEnA==} 925 | engines: {node: '>=18.0.0', npm: '>=8.0.0'} 926 | hasBin: true 927 | dependencies: 928 | '@types/estree': 1.0.5 929 | optionalDependencies: 930 | '@rollup/rollup-android-arm-eabi': 4.16.4 931 | '@rollup/rollup-android-arm64': 4.16.4 932 | '@rollup/rollup-darwin-arm64': 4.16.4 933 | '@rollup/rollup-darwin-x64': 4.16.4 934 | '@rollup/rollup-linux-arm-gnueabihf': 4.16.4 935 | '@rollup/rollup-linux-arm-musleabihf': 4.16.4 936 | '@rollup/rollup-linux-arm64-gnu': 4.16.4 937 | '@rollup/rollup-linux-arm64-musl': 4.16.4 938 | '@rollup/rollup-linux-powerpc64le-gnu': 4.16.4 939 | '@rollup/rollup-linux-riscv64-gnu': 4.16.4 940 | '@rollup/rollup-linux-s390x-gnu': 4.16.4 941 | '@rollup/rollup-linux-x64-gnu': 4.16.4 942 | '@rollup/rollup-linux-x64-musl': 4.16.4 943 | '@rollup/rollup-win32-arm64-msvc': 4.16.4 944 | '@rollup/rollup-win32-ia32-msvc': 4.16.4 945 | '@rollup/rollup-win32-x64-msvc': 4.16.4 946 | fsevents: 2.3.3 947 | dev: false 948 | 949 | /search-insights@2.13.0: 950 | resolution: {integrity: sha512-Orrsjf9trHHxFRuo9/rzm0KIWmgzE8RMlZMzuhZOJ01Rnz3D0YBAe+V6473t6/H6c7irs6Lt48brULAiRWb3Vw==} 951 | dev: false 952 | 953 | /shiki@1.3.0: 954 | resolution: {integrity: sha512-9aNdQy/etMXctnPzsje1h1XIGm9YfRcSksKOGqZWXA/qP9G18/8fpz5Bjpma8bOgz3tqIpjERAd6/lLjFyzoww==} 955 | dependencies: 956 | '@shikijs/core': 1.3.0 957 | dev: false 958 | 959 | /source-map-js@1.2.0: 960 | resolution: {integrity: sha512-itJW8lvSA0TXEphiRoawsCksnlf8SyvmFzIhltqAHluXd88pkCd+cXJVHTDwdCr0IzwptSm035IHQktUu1QUMg==} 961 | engines: {node: '>=0.10.0'} 962 | dev: false 963 | 964 | /speakingurl@14.0.1: 965 | resolution: {integrity: sha512-1POYv7uv2gXoyGFpBCmpDVSNV74IfsWlDW216UPjbWufNf+bSU6GdbDsxdcxtfwb4xlI3yxzOTKClUosxARYrQ==} 966 | engines: {node: '>=0.10.0'} 967 | dev: false 968 | 969 | /tabbable@6.2.0: 970 | resolution: {integrity: sha512-Cat63mxsVJlzYvN51JmVXIgNoUokrIaT2zLclCXjRd8boZ0004U4KCs/sToJ75C6sdlByWxpYnb5Boif1VSFew==} 971 | dev: false 972 | 973 | /to-fast-properties@2.0.0: 974 | resolution: {integrity: sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==} 975 | engines: {node: '>=4'} 976 | dev: false 977 | 978 | /vite@5.2.10: 979 | resolution: {integrity: sha512-PAzgUZbP7msvQvqdSD+ErD5qGnSFiGOoWmV5yAKUEI0kdhjbH6nMWVyZQC/hSc4aXwc0oJ9aEdIiF9Oje0JFCw==} 980 | engines: {node: ^18.0.0 || >=20.0.0} 981 | hasBin: true 982 | peerDependencies: 983 | '@types/node': ^18.0.0 || >=20.0.0 984 | less: '*' 985 | lightningcss: ^1.21.0 986 | sass: '*' 987 | stylus: '*' 988 | sugarss: '*' 989 | terser: ^5.4.0 990 | peerDependenciesMeta: 991 | '@types/node': 992 | optional: true 993 | less: 994 | optional: true 995 | lightningcss: 996 | optional: true 997 | sass: 998 | optional: true 999 | stylus: 1000 | optional: true 1001 | sugarss: 1002 | optional: true 1003 | terser: 1004 | optional: true 1005 | dependencies: 1006 | esbuild: 0.20.2 1007 | postcss: 8.4.38 1008 | rollup: 4.16.4 1009 | optionalDependencies: 1010 | fsevents: 2.3.3 1011 | dev: false 1012 | 1013 | /vitepress@1.1.3(@algolia/client-search@4.23.3)(search-insights@2.13.0): 1014 | resolution: {integrity: sha512-hGrIYN0w9IHWs0NQSnlMjKV/v/HLfD+Ywv5QdvCSkiT32mpNOOwUrZjnqZv/JL/WBPpUc94eghTUvmipxw0xrA==} 1015 | hasBin: true 1016 | peerDependencies: 1017 | markdown-it-mathjax3: ^4 1018 | postcss: ^8 1019 | peerDependenciesMeta: 1020 | markdown-it-mathjax3: 1021 | optional: true 1022 | postcss: 1023 | optional: true 1024 | dependencies: 1025 | '@docsearch/css': 3.6.0 1026 | '@docsearch/js': 3.6.0(@algolia/client-search@4.23.3)(search-insights@2.13.0) 1027 | '@shikijs/core': 1.3.0 1028 | '@shikijs/transformers': 1.3.0 1029 | '@types/markdown-it': 14.0.1 1030 | '@vitejs/plugin-vue': 5.0.4(vite@5.2.10)(vue@3.4.25) 1031 | '@vue/devtools-api': 7.1.3(vue@3.4.25) 1032 | '@vueuse/core': 10.9.0(vue@3.4.25) 1033 | '@vueuse/integrations': 10.9.0(focus-trap@7.5.4)(vue@3.4.25) 1034 | focus-trap: 7.5.4 1035 | mark.js: 8.11.1 1036 | minisearch: 6.3.0 1037 | shiki: 1.3.0 1038 | vite: 5.2.10 1039 | vue: 3.4.25 1040 | transitivePeerDependencies: 1041 | - '@algolia/client-search' 1042 | - '@types/node' 1043 | - '@types/react' 1044 | - '@vue/composition-api' 1045 | - async-validator 1046 | - axios 1047 | - change-case 1048 | - drauu 1049 | - fuse.js 1050 | - idb-keyval 1051 | - jwt-decode 1052 | - less 1053 | - lightningcss 1054 | - nprogress 1055 | - qrcode 1056 | - react 1057 | - react-dom 1058 | - sass 1059 | - search-insights 1060 | - sortablejs 1061 | - stylus 1062 | - sugarss 1063 | - terser 1064 | - typescript 1065 | - universal-cookie 1066 | dev: false 1067 | 1068 | /vue-demi@0.14.7(vue@3.4.25): 1069 | resolution: {integrity: sha512-EOG8KXDQNwkJILkx/gPcoL/7vH+hORoBaKgGe+6W7VFMvCYJfmF2dGbvgDroVnI8LU7/kTu8mbjRZGBU1z9NTA==} 1070 | engines: {node: '>=12'} 1071 | hasBin: true 1072 | requiresBuild: true 1073 | peerDependencies: 1074 | '@vue/composition-api': ^1.0.0-rc.1 1075 | vue: ^3.0.0-0 || ^2.6.0 1076 | peerDependenciesMeta: 1077 | '@vue/composition-api': 1078 | optional: true 1079 | dependencies: 1080 | vue: 3.4.25 1081 | dev: false 1082 | 1083 | /vue@3.4.25: 1084 | resolution: {integrity: sha512-HWyDqoBHMgav/OKiYA2ZQg+kjfMgLt/T0vg4cbIF7JbXAjDexRf5JRg+PWAfrAkSmTd2I8aPSXtooBFWHB98cg==} 1085 | peerDependencies: 1086 | typescript: '*' 1087 | peerDependenciesMeta: 1088 | typescript: 1089 | optional: true 1090 | dependencies: 1091 | '@vue/compiler-dom': 3.4.25 1092 | '@vue/compiler-sfc': 3.4.25 1093 | '@vue/runtime-dom': 3.4.25 1094 | '@vue/server-renderer': 3.4.25(vue@3.4.25) 1095 | '@vue/shared': 3.4.25 1096 | dev: false 1097 | --------------------------------------------------------------------------------