📝 docs: Add documentation

This commit is contained in:
web@ppanel
2025-12-11 03:29:07 +00:00
parent 50e695a1bb
commit 99e7f6062d
135 changed files with 79115 additions and 8 deletions
+40
View File
@@ -0,0 +1,40 @@
{
"hash": "7e859c1e",
"configHash": "1f757845",
"lockfileHash": "e3b0c442",
"browserHash": "1e554b7a",
"optimized": {
"vue": {
"src": "../../../../node_modules/vue/dist/vue.runtime.esm-bundler.js",
"file": "vue.js",
"fileHash": "3b2af542",
"needsInterop": false
},
"vitepress > @vue/devtools-api": {
"src": "../../../../node_modules/@vue/devtools-api/dist/index.js",
"file": "vitepress___@vue_devtools-api.js",
"fileHash": "64692441",
"needsInterop": false
},
"vitepress > @vueuse/core": {
"src": "../../../../node_modules/@vueuse/core/index.mjs",
"file": "vitepress___@vueuse_core.js",
"fileHash": "98a22420",
"needsInterop": false
},
"vitepress-theme-teek": {
"src": "../../../../node_modules/vitepress-theme-teek/es/index.mjs",
"file": "vitepress-theme-teek.js",
"fileHash": "124483c7",
"needsInterop": false
}
},
"chunks": {
"chunk-BY246V3E": {
"file": "chunk-BY246V3E.js"
},
"chunk-PZ5AY32C": {
"file": "chunk-PZ5AY32C.js"
}
}
}
File diff suppressed because it is too large Load Diff
File diff suppressed because one or more lines are too long
+10
View File
@@ -0,0 +1,10 @@
var __defProp = Object.defineProperty;
var __export = (target, all) => {
for (var name in all)
__defProp(target, name, { get: all[name], enumerable: true });
};
export {
__export
};
//# sourceMappingURL=chunk-PZ5AY32C.js.map
+7
View File
@@ -0,0 +1,7 @@
{
"version": 3,
"sources": [],
"sourcesContent": [],
"mappings": "",
"names": []
}
+3
View File
@@ -0,0 +1,3 @@
{
"type": "module"
}
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large Load Diff
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large Load Diff
File diff suppressed because one or more lines are too long
+348
View File
@@ -0,0 +1,348 @@
import {
BaseTransition,
BaseTransitionPropsValidators,
Comment,
DeprecationTypes,
EffectScope,
ErrorCodes,
ErrorTypeStrings,
Fragment,
KeepAlive,
ReactiveEffect,
Static,
Suspense,
Teleport,
Text,
TrackOpTypes,
Transition,
TransitionGroup,
TriggerOpTypes,
VueElement,
assertNumber,
callWithAsyncErrorHandling,
callWithErrorHandling,
camelize,
capitalize,
cloneVNode,
compatUtils,
compile,
computed,
createApp,
createBaseVNode,
createBlock,
createCommentVNode,
createElementBlock,
createHydrationRenderer,
createPropsRestProxy,
createRenderer,
createSSRApp,
createSlots,
createStaticVNode,
createTextVNode,
createVNode,
customRef,
defineAsyncComponent,
defineComponent,
defineCustomElement,
defineEmits,
defineExpose,
defineModel,
defineOptions,
defineProps,
defineSSRCustomElement,
defineSlots,
devtools,
effect,
effectScope,
getCurrentInstance,
getCurrentScope,
getCurrentWatcher,
getTransitionRawChildren,
guardReactiveProps,
h,
handleError,
hasInjectionContext,
hydrate,
hydrateOnIdle,
hydrateOnInteraction,
hydrateOnMediaQuery,
hydrateOnVisible,
initCustomFormatter,
initDirectivesForSSR,
inject,
isMemoSame,
isProxy,
isReactive,
isReadonly,
isRef,
isRuntimeOnly,
isShallow,
isVNode,
markRaw,
mergeDefaults,
mergeModels,
mergeProps,
nextTick,
nodeOps,
normalizeClass,
normalizeProps,
normalizeStyle,
onActivated,
onBeforeMount,
onBeforeUnmount,
onBeforeUpdate,
onDeactivated,
onErrorCaptured,
onMounted,
onRenderTracked,
onRenderTriggered,
onScopeDispose,
onServerPrefetch,
onUnmounted,
onUpdated,
onWatcherCleanup,
openBlock,
patchProp,
popScopeId,
provide,
proxyRefs,
pushScopeId,
queuePostFlushCb,
reactive,
readonly,
ref,
registerRuntimeCompiler,
render,
renderList,
renderSlot,
resolveComponent,
resolveDirective,
resolveDynamicComponent,
resolveFilter,
resolveTransitionHooks,
setBlockTracking,
setDevtoolsHook,
setTransitionHooks,
shallowReactive,
shallowReadonly,
shallowRef,
ssrContextKey,
ssrUtils,
stop,
toDisplayString,
toHandlerKey,
toHandlers,
toRaw,
toRef,
toRefs,
toValue,
transformVNodeArgs,
triggerRef,
unref,
useAttrs,
useCssModule,
useCssVars,
useHost,
useId,
useModel,
useSSRContext,
useShadowRoot,
useSlots,
useTemplateRef,
useTransitionState,
vModelCheckbox,
vModelDynamic,
vModelRadio,
vModelSelect,
vModelText,
vShow,
version,
warn,
watch,
watchEffect,
watchPostEffect,
watchSyncEffect,
withAsyncContext,
withCtx,
withDefaults,
withDirectives,
withKeys,
withMemo,
withModifiers,
withScopeId
} from "./chunk-BY246V3E.js";
import "./chunk-PZ5AY32C.js";
export {
BaseTransition,
BaseTransitionPropsValidators,
Comment,
DeprecationTypes,
EffectScope,
ErrorCodes,
ErrorTypeStrings,
Fragment,
KeepAlive,
ReactiveEffect,
Static,
Suspense,
Teleport,
Text,
TrackOpTypes,
Transition,
TransitionGroup,
TriggerOpTypes,
VueElement,
assertNumber,
callWithAsyncErrorHandling,
callWithErrorHandling,
camelize,
capitalize,
cloneVNode,
compatUtils,
compile,
computed,
createApp,
createBlock,
createCommentVNode,
createElementBlock,
createBaseVNode as createElementVNode,
createHydrationRenderer,
createPropsRestProxy,
createRenderer,
createSSRApp,
createSlots,
createStaticVNode,
createTextVNode,
createVNode,
customRef,
defineAsyncComponent,
defineComponent,
defineCustomElement,
defineEmits,
defineExpose,
defineModel,
defineOptions,
defineProps,
defineSSRCustomElement,
defineSlots,
devtools,
effect,
effectScope,
getCurrentInstance,
getCurrentScope,
getCurrentWatcher,
getTransitionRawChildren,
guardReactiveProps,
h,
handleError,
hasInjectionContext,
hydrate,
hydrateOnIdle,
hydrateOnInteraction,
hydrateOnMediaQuery,
hydrateOnVisible,
initCustomFormatter,
initDirectivesForSSR,
inject,
isMemoSame,
isProxy,
isReactive,
isReadonly,
isRef,
isRuntimeOnly,
isShallow,
isVNode,
markRaw,
mergeDefaults,
mergeModels,
mergeProps,
nextTick,
nodeOps,
normalizeClass,
normalizeProps,
normalizeStyle,
onActivated,
onBeforeMount,
onBeforeUnmount,
onBeforeUpdate,
onDeactivated,
onErrorCaptured,
onMounted,
onRenderTracked,
onRenderTriggered,
onScopeDispose,
onServerPrefetch,
onUnmounted,
onUpdated,
onWatcherCleanup,
openBlock,
patchProp,
popScopeId,
provide,
proxyRefs,
pushScopeId,
queuePostFlushCb,
reactive,
readonly,
ref,
registerRuntimeCompiler,
render,
renderList,
renderSlot,
resolveComponent,
resolveDirective,
resolveDynamicComponent,
resolveFilter,
resolveTransitionHooks,
setBlockTracking,
setDevtoolsHook,
setTransitionHooks,
shallowReactive,
shallowReadonly,
shallowRef,
ssrContextKey,
ssrUtils,
stop,
toDisplayString,
toHandlerKey,
toHandlers,
toRaw,
toRef,
toRefs,
toValue,
transformVNodeArgs,
triggerRef,
unref,
useAttrs,
useCssModule,
useCssVars,
useHost,
useId,
useModel,
useSSRContext,
useShadowRoot,
useSlots,
useTemplateRef,
useTransitionState,
vModelCheckbox,
vModelDynamic,
vModelRadio,
vModelSelect,
vModelText,
vShow,
version,
warn,
watch,
watchEffect,
watchPostEffect,
watchSyncEffect,
withAsyncContext,
withCtx,
withDefaults,
withDirectives,
withKeys,
withMemo,
withModifiers,
withScopeId
};
//# sourceMappingURL=vue.js.map
+7
View File
@@ -0,0 +1,7 @@
{
"version": 3,
"sources": [],
"sourcesContent": [],
"mappings": "",
"names": []
}
+230
View File
@@ -0,0 +1,230 @@
<script setup lang="ts">
import { onMounted, ref } from "vue";
interface Sponsor {
logo: string;
title: string;
description: string;
expiryDate: string;
href: string;
}
interface Props {
loadingText?: string;
errorText?: string;
emptyText?: string;
visitTitle?: string;
}
const props = withDefaults(defineProps<Props>(), {
loadingText: "Loading...",
errorText: "Failed to load sponsors data",
emptyText: "No sponsors available",
visitTitle: "Visit sponsor",
});
const sponsors = ref<Sponsor[]>([]);
const loading = ref(true);
const error = ref("");
const isExpired = (expiryDate: string): boolean => {
try {
const expiry = new Date(expiryDate);
const today = new Date();
today.setHours(0, 0, 0, 0);
expiry.setHours(0, 0, 0, 0);
return expiry < today;
} catch {
return false;
}
};
onMounted(async () => {
try {
const response = await fetch(
"https://raw.githubusercontent.com/perfect-panel/ppanel-assets/main/billing/index.json"
);
if (!response.ok) throw new Error("Failed to fetch sponsors data");
const data = await response.json();
const allSponsors = [
...(data.billing || []),
...(data.dashboard || []),
...(data.payment || []),
];
sponsors.value = allSponsors.filter(
(sponsor: Sponsor) => !isExpired(sponsor.expiryDate)
);
} catch (e) {
error.value = props.errorText;
console.error(e);
} finally {
loading.value = false;
}
});
</script>
<template>
<div class="sponsors-container">
<div v-if="loading" class="loading">{{ loadingText }}</div>
<div v-else-if="error" class="error">{{ error }}</div>
<div v-else-if="sponsors.length === 0" class="empty-state">{{ emptyText }}</div>
<div v-else class="sponsors-grid">
<a
v-for="sponsor in sponsors"
:key="sponsor.title"
:href="sponsor.href"
target="_blank"
rel="noopener noreferrer"
class="sponsor-card"
:title="sponsor.title"
>
<img
:src="sponsor.logo"
:alt="sponsor.title"
class="sponsor-logo"
/>
<div class="sponsor-content">
<h3 class="sponsor-title">{{ sponsor.title }}</h3>
<p class="sponsor-description">{{ sponsor.description }}</p>
</div>
</a>
</div>
</div>
</template>
<style scoped>
.sponsors-container {
max-width: 1200px;
margin: 0 auto;
padding: 0 1.5rem 2.5rem;
}
.loading,
.error {
text-align: center;
padding: 3rem;
font-size: 1rem;
}
.loading {
color: var(--vp-c-text-2);
}
.error {
color: var(--vp-c-danger);
}
.sponsors-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
gap: 1.5rem;
margin-top: 2rem;
}
.sponsor-card {
background: var(--vp-c-bg-soft);
border-radius: 12px;
padding: 1.25rem;
border: 1px solid var(--vp-c-divider);
transition: all 0.25s cubic-bezier(0.4, 0, 0.2, 1);
display: flex;
flex-direction: row;
gap: 1rem;
align-items: center;
position: relative;
overflow: hidden;
text-decoration: none;
color: inherit;
cursor: pointer;
}
.sponsor-card::before {
content: '';
position: absolute;
top: 0;
left: 0;
right: 0;
height: 3px;
background: linear-gradient(90deg, var(--vp-c-brand) 0%, var(--vp-c-brand-dark) 100%);
transform: scaleX(0);
transition: transform 0.25s ease;
}
.sponsor-card:hover::before {
transform: scaleX(1);
}
.sponsor-card:hover {
transform: translateY(-4px);
box-shadow: 0 12px 28px rgba(0, 0, 0, 0.12);
border-color: var(--vp-c-brand-light);
}
.sponsor-logo {
width: 80px;
height: 80px;
flex-shrink: 0;
object-fit: contain;
border-radius: 8px;
background: var(--vp-c-bg);
padding: 0.5rem;
transition: transform 0.25s ease;
}
.sponsor-card:hover .sponsor-logo {
transform: scale(1.05);
}
.sponsor-content {
flex: 1;
min-width: 0;
display: flex;
flex-direction: column;
gap: 0.25rem;
}
.sponsor-title {
font-size: 1.125rem;
font-weight: 700;
color: var(--vp-c-text-1);
line-height: 1.4;
}
.sponsor-description {
color: var(--vp-c-text-2);
font-size: 0.875rem;
line-height: 1.6;
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
overflow: hidden;
}
.empty-state {
text-align: center;
padding: 5rem 2rem;
color: var(--vp-c-text-2);
font-size: 1rem;
}
@media (max-width: 768px) {
.sponsors-container {
padding: 0 1rem 2rem;
}
.sponsors-grid {
grid-template-columns: 1fr;
gap: 1.25rem;
}
.sponsor-card {
padding: 1rem;
}
.sponsor-logo {
width: 60px;
height: 60px;
}
}
</style>
+367
View File
@@ -0,0 +1,367 @@
import { defineConfig } from "vitepress";
import { defineTeekConfig } from "vitepress-theme-teek/config";
// Teek 主题配置
const teekConfig = defineTeekConfig({
// 文章元数据配置
article: {
author: "PPanel",
},
// 页脚配置
footer: {
copyright: "Copyright © 2025 PPanel",
},
// 评论按钮配置 - 跳转到 Telegram 群组
toComment: {
enabled: true,
done: () => {
window.open("https://t.me/PPanelChat", "_blank");
},
},
});
// https://vitepress.dev/reference/site-config
export default defineConfig({
extends: teekConfig,
title: "PPanel",
description:
"PPanel is a pure, professional, and perfect open-source proxy panel tool, designed to be your ideal choice for learning and practical use.",
// 国际化配置
locales: {
root: {
label: "English",
lang: "en-US",
themeConfig: {
nav: [
{ text: "Home", link: "/" },
{ text: "Guide", link: "/zh/guide/intro" },
{ text: "Features", link: "/zh/admin/dashboard" },
{ text: "API", link: "/zh/api/reference" },
{ text: "Sponsors", link: "/zh/sponsors" },
{
text: "Stats",
link: "https://stats.ppanel.dev",
target: "_blank",
},
{ text: "Buy Theme", link: "https://bygga.app", target: "_blank" },
],
sidebar: {
"/api/": [
{
text: "API",
items: [
{ text: "Overview", link: "/api/reference" },
{ text: "Common Service", link: "/api/common" },
{ text: "User Service", link: "/api/user" },
{ text: "Admin Service", link: "/api/admin" },
{ text: "Gateway", link: "/api/gateway" },
],
},
],
"/guide/": [
{
text: "Guide",
items: [
{ text: "Introduction", link: "/guide/intro" },
{ text: "Contributing", link: "/guide/contributing" },
],
},
{
text: "Installation",
items: [
{ text: "Overview", link: "/guide/installation/" },
{
text: "Docker Compose",
link: "/guide/installation/docker-compose",
},
{
text: "Docker Run",
link: "/guide/installation/docker-run",
},
{
text: "Binary Deployment",
link: "/guide/installation/binary",
},
],
},
{
text: "Separation Deployment",
items: [
{
text: "Backend Deployment",
link: "/guide/separation/backend",
},
{
text: "Frontend Deployment",
link: "/guide/separation/frontend",
},
],
},
{
text: "Node Agent",
items: [
{
text: "Installation",
link: "/guide/node/installation",
},
],
},
],
"/admin/": [
{
text: "Dashboard",
items: [{ text: "Overview", link: "/admin/dashboard" }],
},
{
text: "Maintenance",
items: [
{
text: "Server Management",
link: "/admin/maintenance/servers",
},
{
text: "Node Management",
link: "/admin/maintenance/nodes",
},
{
text: "Subscribe Config",
link: "/admin/maintenance/subscribe",
},
{
text: "Product Management",
link: "/admin/maintenance/products",
},
],
},
{
text: "Commerce",
items: [
{ text: "Order Management", link: "/admin/commerce/orders" },
{
text: "Coupon Management",
link: "/admin/commerce/coupons",
},
{
text: "Marketing Management",
link: "/admin/commerce/marketing",
},
{
text: "Announcement Management",
link: "/admin/commerce/announcements",
},
],
},
{
text: "Users & Support",
items: [
{
text: "User Management",
link: "/admin/users-support/users",
},
{
text: "Ticket Management",
link: "/admin/users-support/tickets",
},
{
text: "Document Management",
link: "/admin/users-support/documents",
},
],
},
{
text: "System",
items: [
{ text: "System Config", link: "/admin/system/config" },
{ text: "Auth Control", link: "/admin/system/auth-control" },
{ text: "Payment Config", link: "/admin/system/payment" },
{ text: "ADS Config", link: "/admin/system/ads" },
],
},
{
text: "Logs & Analytics",
items: [
{ text: "Login Logs", link: "/admin/logs/login" },
{ text: "Register Logs", link: "/admin/logs/register" },
{ text: "Email Logs", link: "/admin/logs/email" },
{ text: "Mobile Logs", link: "/admin/logs/mobile" },
{ text: "Subscribe Logs", link: "/admin/logs/subscribe" },
{
text: "Reset Subscribe Logs",
link: "/admin/logs/reset-subscribe",
},
{
text: "Subscribe Traffic",
link: "/admin/logs/subscribe-traffic",
},
{
text: "Server Traffic",
link: "/admin/logs/server-traffic",
},
{
text: "Traffic Details",
link: "/admin/logs/traffic-details",
},
{ text: "Balance Logs", link: "/admin/logs/balance" },
{ text: "Commission Logs", link: "/admin/logs/commission" },
{ text: "Gift Logs", link: "/admin/logs/gift" },
],
},
],
},
},
},
zh: {
label: "简体中文",
lang: "zh-CN",
link: "/zh/",
themeConfig: {
nav: [
{ text: "首页", link: "/zh/" },
{ text: "指南", link: "/zh/guide/intro" },
{ text: "功能文档", link: "/zh/admin/dashboard" },
{ text: "API 文档", link: "/zh/api/reference" },
{ text: "赞助商", link: "/zh/sponsors" },
{
text: "统计",
link: "https://stats.ppanel.dev",
target: "_blank",
},
{ text: "主题购买", link: "https://bygga.app", target: "_blank" },
],
sidebar: {
"/zh/api/": [
{
text: "API",
items: [
{ text: "概览", link: "/zh/api/reference" },
{ text: "通用服务", link: "/zh/api/common" },
{ text: "用户服务", link: "/zh/api/user" },
{ text: "管理端服务", link: "/zh/api/admin" },
{ text: "网关", link: "/zh/api/gateway" },
],
},
],
"/zh/guide/": [
{
text: "指南",
items: [
{ text: "简介", link: "/zh/guide/intro" },
{ text: "贡献指南", link: "/zh/guide/contributing" },
],
},
{
text: "安装部署",
items: [
{ text: "概览", link: "/zh/guide/installation/" },
{
text: "Docker Compose",
link: "/zh/guide/installation/docker-compose",
},
{
text: "Docker Run",
link: "/zh/guide/installation/docker-run",
},
{
text: "二进制部署",
link: "/zh/guide/installation/binary",
},
],
},
{
text: "分离部署",
items: [
{
text: "后端部署",
link: "/zh/guide/separation/backend",
},
{
text: "前端部署",
link: "/zh/guide/separation/frontend",
},
],
},
{
text: "节点端",
items: [
{
text: "安装",
link: "/zh/guide/node/installation",
},
],
},
],
"/zh/admin/": [
{
text: "仪表盘",
items: [{ text: "总览", link: "/zh/admin/dashboard" }],
},
{
text: "运维管理",
items: [
{ text: "服务器管理", link: "/zh/admin/maintenance/servers" },
{ text: "节点管理", link: "/zh/admin/maintenance/nodes" },
{ text: "订阅配置", link: "/zh/admin/maintenance/subscribe" },
{ text: "产品管理", link: "/zh/admin/maintenance/products" },
],
},
{
text: "商务管理",
items: [
{ text: "订单管理", link: "/zh/admin/commerce/orders" },
{ text: "优惠券管理", link: "/zh/admin/commerce/coupons" },
{ text: "营销管理", link: "/zh/admin/commerce/marketing" },
{ text: "公告管理", link: "/zh/admin/commerce/announcements" },
],
},
{
text: "用户与支持",
items: [
{ text: "用户管理", link: "/zh/admin/users-support/users" },
{ text: "工单管理", link: "/zh/admin/users-support/tickets" },
{ text: "文档管理", link: "/zh/admin/users-support/documents" },
],
},
{
text: "系统管理",
items: [
{ text: "系统配置", link: "/zh/admin/system/config" },
{ text: "认证控制", link: "/zh/admin/system/auth-control" },
{ text: "支付配置", link: "/zh/admin/system/payment" },
{ text: "广告配置", link: "/zh/admin/system/ads" },
],
},
{
text: "日志与分析",
items: [
{ text: "登录日志", link: "/zh/admin/logs/login" },
{ text: "注册日志", link: "/zh/admin/logs/register" },
{ text: "邮件日志", link: "/zh/admin/logs/email" },
{ text: "短信日志", link: "/zh/admin/logs/mobile" },
{ text: "订阅日志", link: "/zh/admin/logs/subscribe" },
{
text: "重置订阅日志",
link: "/zh/admin/logs/reset-subscribe",
},
{ text: "订阅流量", link: "/zh/admin/logs/subscribe-traffic" },
{ text: "服务器流量", link: "/zh/admin/logs/server-traffic" },
{ text: "流量详情", link: "/zh/admin/logs/traffic-details" },
{ text: "余额日志", link: "/zh/admin/logs/balance" },
{ text: "佣金日志", link: "/zh/admin/logs/commission" },
{ text: "赠送日志", link: "/zh/admin/logs/gift" },
],
},
],
},
},
},
},
themeConfig: {
logo: "/logo.svg",
socialLinks: [
{ icon: "github", link: "https://github.com/perfect-panel/ppanel" },
],
},
});
@@ -0,0 +1,75 @@
<template>
<iframe
class="scalar-iframe"
:src="iframeSrc"
frameborder="0"
loading="lazy"
referrerpolicy="no-referrer"
scrolling="no"
:style="{ height: iframeHeight }"
@wheel.stop
@touchstart.stop
@touchmove.stop
@mousedown.stop
/>
</template>
<script setup lang="ts">
import { withBase } from "vitepress";
import { computed, onBeforeUnmount, onMounted, ref } from "vue";
const props = withDefaults(
defineProps<{
specUrl: string;
title?: string;
theme?: string;
layout?: "modern" | "classic";
operationId?: string;
}>(),
{
title: "API Reference",
theme: "saturn",
layout: "classic",
}
);
const iframeHeight = ref("1000px");
const iframeSrc = computed(() => {
const params = new URLSearchParams();
params.set("spec", props.specUrl);
params.set("title", props.title);
params.set("theme", props.theme);
params.set("layout", props.layout);
if (props.operationId) {
params.set("operationId", props.operationId);
}
return withBase(`/scalar/index.html?${params.toString()}`);
});
const handleMessage = (event: MessageEvent) => {
if (event.origin !== window.location.origin) return;
const data = event.data;
if (data?.type === "scalar-height" && typeof data.height === "number") {
iframeHeight.value = `${Math.max(data.height, 800)}px`;
}
};
onMounted(() => {
window.addEventListener("message", handleMessage);
});
onBeforeUnmount(() => {
window.removeEventListener("message", handleMessage);
});
</script>
<style scoped>
.scalar-iframe {
width: 100%;
border: none;
border-radius: 12px;
background: transparent;
overflow: hidden;
}
</style>
+15
View File
@@ -0,0 +1,15 @@
// https://vitepress.dev/guide/custom-theme
import Teek from "vitepress-theme-teek";
import "vitepress-theme-teek/index.css";
import type { Theme } from "vitepress";
import ScalarIframe from "./components/ScalarIframe.vue";
const theme: Theme = {
extends: Teek,
enhanceApp(ctx) {
Teek.enhanceApp?.(ctx);
ctx.app.component("ScalarIframe", ScalarIframe);
},
};
export default theme;