ONI Agent Bridge - initial mod implementation

This commit is contained in:
JianFeeeee
2026-05-30 11:00:36 +08:00
commit 1f25d3d879
95 changed files with 50686 additions and 0 deletions

View File

@ -0,0 +1,61 @@
{
"hash": "5368ccde",
"configHash": "2733785a",
"lockfileHash": "3ae99b52",
"browserHash": "26eaea40",
"optimized": {
"vue": {
"src": "../../../node_modules/vue/dist/vue.runtime.esm-bundler.js",
"file": "vue.js",
"fileHash": "605c659d",
"needsInterop": false
},
"vitepress > @vue/devtools-api": {
"src": "../../../node_modules/@vue/devtools-api/dist/index.js",
"file": "vitepress___@vue_devtools-api.js",
"fileHash": "3cacbefb",
"needsInterop": false
},
"vitepress > @vueuse/core": {
"src": "../../../node_modules/@vueuse/core/index.mjs",
"file": "vitepress___@vueuse_core.js",
"fileHash": "770ea158",
"needsInterop": false
},
"vitepress > @vueuse/integrations/useFocusTrap": {
"src": "../../../node_modules/@vueuse/integrations/useFocusTrap.mjs",
"file": "vitepress___@vueuse_integrations_useFocusTrap.js",
"fileHash": "b915752a",
"needsInterop": false
},
"vitepress > mark.js/src/vanilla.js": {
"src": "../../../node_modules/mark.js/src/vanilla.js",
"file": "vitepress___mark__js_src_vanilla__js.js",
"fileHash": "38aa2e8f",
"needsInterop": false
},
"vitepress > minisearch": {
"src": "../../../node_modules/minisearch/dist/es/index.js",
"file": "vitepress___minisearch.js",
"fileHash": "f5e205a1",
"needsInterop": false
},
"rss-parser": {
"src": "../../../node_modules/rss-parser/index.js",
"file": "rss-parser.js",
"fileHash": "c4c7877b",
"needsInterop": true
}
},
"chunks": {
"chunk-LJKO4TMH": {
"file": "chunk-LJKO4TMH.js"
},
"chunk-QE257C5J": {
"file": "chunk-QE257C5J.js"
},
"chunk-BUSYA2B4": {
"file": "chunk-BUSYA2B4.js"
}
}
}

View File

@ -0,0 +1,9 @@
var __getOwnPropNames = Object.getOwnPropertyNames;
var __commonJS = (cb, mod) => function __require() {
return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;
};
export {
__commonJS
};
//# sourceMappingURL=chunk-BUSYA2B4.js.map

View File

@ -0,0 +1,7 @@
{
"version": 3,
"sources": [],
"sourcesContent": [],
"mappings": "",
"names": []
}

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

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

View File

@ -0,0 +1,584 @@
import {
DefaultMagicKeysAliasMap,
StorageSerializers,
TransitionPresets,
assert,
breakpointsAntDesign,
breakpointsBootstrapV5,
breakpointsElement,
breakpointsMasterCss,
breakpointsPrimeFlex,
breakpointsQuasar,
breakpointsSematic,
breakpointsTailwind,
breakpointsVuetify,
breakpointsVuetifyV2,
breakpointsVuetifyV3,
bypassFilter,
camelize,
clamp,
cloneFnJSON,
computedAsync,
computedEager,
computedInject,
computedWithControl,
containsProp,
controlledRef,
createEventHook,
createFetch,
createFilterWrapper,
createGlobalState,
createInjectionState,
createRef,
createReusableTemplate,
createSharedComposable,
createSingletonPromise,
createTemplatePromise,
createUnrefFn,
customStorageEventName,
debounceFilter,
defaultDocument,
defaultLocation,
defaultNavigator,
defaultWindow,
executeTransition,
extendRef,
formatDate,
formatTimeAgo,
get,
getLifeCycleTarget,
getSSRHandler,
hasOwn,
hyphenate,
identity,
increaseWithUnit,
injectLocal,
invoke,
isClient,
isDef,
isDefined,
isIOS,
isObject,
isWorker,
makeDestructurable,
mapGamepadToXbox360Controller,
noop,
normalizeDate,
notNullish,
now,
objectEntries,
objectOmit,
objectPick,
onClickOutside,
onElementRemoval,
onKeyDown,
onKeyPressed,
onKeyStroke,
onKeyUp,
onLongPress,
onStartTyping,
pausableFilter,
promiseTimeout,
provideLocal,
provideSSRWidth,
pxValue,
rand,
reactify,
reactifyObject,
reactiveComputed,
reactiveOmit,
reactivePick,
refAutoReset,
refDebounced,
refDefault,
refThrottled,
refWithControl,
resolveRef,
resolveUnref,
set,
setSSRHandler,
syncRef,
syncRefs,
templateRef,
throttleFilter,
timestamp,
toArray,
toReactive,
toRef,
toRefs,
toValue,
tryOnBeforeMount,
tryOnBeforeUnmount,
tryOnMounted,
tryOnScopeDispose,
tryOnUnmounted,
unrefElement,
until,
useActiveElement,
useAnimate,
useArrayDifference,
useArrayEvery,
useArrayFilter,
useArrayFind,
useArrayFindIndex,
useArrayFindLast,
useArrayIncludes,
useArrayJoin,
useArrayMap,
useArrayReduce,
useArraySome,
useArrayUnique,
useAsyncQueue,
useAsyncState,
useBase64,
useBattery,
useBluetooth,
useBreakpoints,
useBroadcastChannel,
useBrowserLocation,
useCached,
useClipboard,
useClipboardItems,
useCloned,
useColorMode,
useConfirmDialog,
useCountdown,
useCounter,
useCssVar,
useCurrentElement,
useCycleList,
useDark,
useDateFormat,
useDebounceFn,
useDebouncedRefHistory,
useDeviceMotion,
useDeviceOrientation,
useDevicePixelRatio,
useDevicesList,
useDisplayMedia,
useDocumentVisibility,
useDraggable,
useDropZone,
useElementBounding,
useElementByPoint,
useElementHover,
useElementSize,
useElementVisibility,
useEventBus,
useEventListener,
useEventSource,
useEyeDropper,
useFavicon,
useFetch,
useFileDialog,
useFileSystemAccess,
useFocus,
useFocusWithin,
useFps,
useFullscreen,
useGamepad,
useGeolocation,
useIdle,
useImage,
useInfiniteScroll,
useIntersectionObserver,
useInterval,
useIntervalFn,
useKeyModifier,
useLastChanged,
useLocalStorage,
useMagicKeys,
useManualRefHistory,
useMediaControls,
useMediaQuery,
useMemoize,
useMemory,
useMounted,
useMouse,
useMouseInElement,
useMousePressed,
useMutationObserver,
useNavigatorLanguage,
useNetwork,
useNow,
useObjectUrl,
useOffsetPagination,
useOnline,
usePageLeave,
useParallax,
useParentElement,
usePerformanceObserver,
usePermission,
usePointer,
usePointerLock,
usePointerSwipe,
usePreferredColorScheme,
usePreferredContrast,
usePreferredDark,
usePreferredLanguages,
usePreferredReducedMotion,
usePreferredReducedTransparency,
usePrevious,
useRafFn,
useRefHistory,
useResizeObserver,
useSSRWidth,
useScreenOrientation,
useScreenSafeArea,
useScriptTag,
useScroll,
useScrollLock,
useSessionStorage,
useShare,
useSorted,
useSpeechRecognition,
useSpeechSynthesis,
useStepper,
useStorage,
useStorageAsync,
useStyleTag,
useSupported,
useSwipe,
useTemplateRefsList,
useTextDirection,
useTextSelection,
useTextareaAutosize,
useThrottleFn,
useThrottledRefHistory,
useTimeAgo,
useTimeout,
useTimeoutFn,
useTimeoutPoll,
useTimestamp,
useTitle,
useToNumber,
useToString,
useToggle,
useTransition,
useUrlSearchParams,
useUserMedia,
useVModel,
useVModels,
useVibrate,
useVirtualList,
useWakeLock,
useWebNotification,
useWebSocket,
useWebWorker,
useWebWorkerFn,
useWindowFocus,
useWindowScroll,
useWindowSize,
watchArray,
watchAtMost,
watchDebounced,
watchDeep,
watchIgnorable,
watchImmediate,
watchOnce,
watchPausable,
watchThrottled,
watchTriggerable,
watchWithFilter,
whenever
} from "./chunk-LJKO4TMH.js";
import "./chunk-QE257C5J.js";
import "./chunk-BUSYA2B4.js";
export {
DefaultMagicKeysAliasMap,
StorageSerializers,
TransitionPresets,
assert,
computedAsync as asyncComputed,
refAutoReset as autoResetRef,
breakpointsAntDesign,
breakpointsBootstrapV5,
breakpointsElement,
breakpointsMasterCss,
breakpointsPrimeFlex,
breakpointsQuasar,
breakpointsSematic,
breakpointsTailwind,
breakpointsVuetify,
breakpointsVuetifyV2,
breakpointsVuetifyV3,
bypassFilter,
camelize,
clamp,
cloneFnJSON,
computedAsync,
computedEager,
computedInject,
computedWithControl,
containsProp,
computedWithControl as controlledComputed,
controlledRef,
createEventHook,
createFetch,
createFilterWrapper,
createGlobalState,
createInjectionState,
reactify as createReactiveFn,
createRef,
createReusableTemplate,
createSharedComposable,
createSingletonPromise,
createTemplatePromise,
createUnrefFn,
customStorageEventName,
debounceFilter,
refDebounced as debouncedRef,
watchDebounced as debouncedWatch,
defaultDocument,
defaultLocation,
defaultNavigator,
defaultWindow,
computedEager as eagerComputed,
executeTransition,
extendRef,
formatDate,
formatTimeAgo,
get,
getLifeCycleTarget,
getSSRHandler,
hasOwn,
hyphenate,
identity,
watchIgnorable as ignorableWatch,
increaseWithUnit,
injectLocal,
invoke,
isClient,
isDef,
isDefined,
isIOS,
isObject,
isWorker,
makeDestructurable,
mapGamepadToXbox360Controller,
noop,
normalizeDate,
notNullish,
now,
objectEntries,
objectOmit,
objectPick,
onClickOutside,
onElementRemoval,
onKeyDown,
onKeyPressed,
onKeyStroke,
onKeyUp,
onLongPress,
onStartTyping,
pausableFilter,
watchPausable as pausableWatch,
promiseTimeout,
provideLocal,
provideSSRWidth,
pxValue,
rand,
reactify,
reactifyObject,
reactiveComputed,
reactiveOmit,
reactivePick,
refAutoReset,
refDebounced,
refDefault,
refThrottled,
refWithControl,
resolveRef,
resolveUnref,
set,
setSSRHandler,
syncRef,
syncRefs,
templateRef,
throttleFilter,
refThrottled as throttledRef,
watchThrottled as throttledWatch,
timestamp,
toArray,
toReactive,
toRef,
toRefs,
toValue,
tryOnBeforeMount,
tryOnBeforeUnmount,
tryOnMounted,
tryOnScopeDispose,
tryOnUnmounted,
unrefElement,
until,
useActiveElement,
useAnimate,
useArrayDifference,
useArrayEvery,
useArrayFilter,
useArrayFind,
useArrayFindIndex,
useArrayFindLast,
useArrayIncludes,
useArrayJoin,
useArrayMap,
useArrayReduce,
useArraySome,
useArrayUnique,
useAsyncQueue,
useAsyncState,
useBase64,
useBattery,
useBluetooth,
useBreakpoints,
useBroadcastChannel,
useBrowserLocation,
useCached,
useClipboard,
useClipboardItems,
useCloned,
useColorMode,
useConfirmDialog,
useCountdown,
useCounter,
useCssVar,
useCurrentElement,
useCycleList,
useDark,
useDateFormat,
refDebounced as useDebounce,
useDebounceFn,
useDebouncedRefHistory,
useDeviceMotion,
useDeviceOrientation,
useDevicePixelRatio,
useDevicesList,
useDisplayMedia,
useDocumentVisibility,
useDraggable,
useDropZone,
useElementBounding,
useElementByPoint,
useElementHover,
useElementSize,
useElementVisibility,
useEventBus,
useEventListener,
useEventSource,
useEyeDropper,
useFavicon,
useFetch,
useFileDialog,
useFileSystemAccess,
useFocus,
useFocusWithin,
useFps,
useFullscreen,
useGamepad,
useGeolocation,
useIdle,
useImage,
useInfiniteScroll,
useIntersectionObserver,
useInterval,
useIntervalFn,
useKeyModifier,
useLastChanged,
useLocalStorage,
useMagicKeys,
useManualRefHistory,
useMediaControls,
useMediaQuery,
useMemoize,
useMemory,
useMounted,
useMouse,
useMouseInElement,
useMousePressed,
useMutationObserver,
useNavigatorLanguage,
useNetwork,
useNow,
useObjectUrl,
useOffsetPagination,
useOnline,
usePageLeave,
useParallax,
useParentElement,
usePerformanceObserver,
usePermission,
usePointer,
usePointerLock,
usePointerSwipe,
usePreferredColorScheme,
usePreferredContrast,
usePreferredDark,
usePreferredLanguages,
usePreferredReducedMotion,
usePreferredReducedTransparency,
usePrevious,
useRafFn,
useRefHistory,
useResizeObserver,
useSSRWidth,
useScreenOrientation,
useScreenSafeArea,
useScriptTag,
useScroll,
useScrollLock,
useSessionStorage,
useShare,
useSorted,
useSpeechRecognition,
useSpeechSynthesis,
useStepper,
useStorage,
useStorageAsync,
useStyleTag,
useSupported,
useSwipe,
useTemplateRefsList,
useTextDirection,
useTextSelection,
useTextareaAutosize,
refThrottled as useThrottle,
useThrottleFn,
useThrottledRefHistory,
useTimeAgo,
useTimeout,
useTimeoutFn,
useTimeoutPoll,
useTimestamp,
useTitle,
useToNumber,
useToString,
useToggle,
useTransition,
useUrlSearchParams,
useUserMedia,
useVModel,
useVModels,
useVibrate,
useVirtualList,
useWakeLock,
useWebNotification,
useWebSocket,
useWebWorker,
useWebWorkerFn,
useWindowFocus,
useWindowScroll,
useWindowSize,
watchArray,
watchAtMost,
watchDebounced,
watchDeep,
watchIgnorable,
watchImmediate,
watchOnce,
watchPausable,
watchThrottled,
watchTriggerable,
watchWithFilter,
whenever
};
//# sourceMappingURL=vitepress___@vueuse_core.js.map

View File

@ -0,0 +1,7 @@
{
"version": 3,
"sources": [],
"sourcesContent": [],
"mappings": "",
"names": []
}

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

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-QE257C5J.js";
import "./chunk-BUSYA2B4.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

View File

@ -0,0 +1,7 @@
{
"version": 3,
"sources": [],
"sourcesContent": [],
"mappings": "",
"names": []
}

View File

@ -0,0 +1,131 @@
import { defineConfig } from 'vitepress'
// https://vitepress.dev/reference/site-config
export default defineConfig({
base: process.env.CF_PAGES ? '/' : '/ONIModTutorial/',
title: "ONIModTutorial",
description: "缺氧MOD教程",
themeConfig: {
// https://vitepress.dev/reference/default-theme-config
nav: [
{ text: '首页', link: '/' },
{ text: '教程示例', link: '/markdown-examples' }
],
// 导航栏右侧的社交链接
socialLinks: [
{
icon: {
svg: '<svg viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg"><title>前往我的 GitHub 仓库</title><path d="M10,0 C15.523,0 20,4.59 20,10.253 C20,14.782 17.138,18.624 13.167,19.981 C12.66,20.082 12.48,19.762 12.48,19.489 C12.48,19.151 12.492,18.047 12.492,16.675 C12.492,15.719 12.172,15.095 11.813,14.777 C14.04,14.523 16.38,13.656 16.38,9.718 C16.38,8.598 15.992,7.684 15.35,6.966 C15.454,6.707 15.797,5.664 15.252,4.252 C15.252,4.252 14.414,3.977 12.505,5.303 C11.706,5.076 10.85,4.962 10,4.958 C9.15,4.962 8.295,5.076 7.497,5.303 C5.586,3.977 4.746,4.252 4.746,4.252 C4.203,5.664 4.546,6.707 4.649,6.966 C4.01,7.684 3.619,8.598 3.619,9.718 C3.619,13.646 5.954,14.526 8.175,14.785 C7.889,15.041 7.63,15.493 7.54,16.156 C6.97,16.418 5.522,16.871 4.63,15.304 C4.63,15.304 4.101,14.319 3.097,14.247 C3.097,14.247 2.122,14.234 3.029,14.87 C3.029,14.87 3.684,15.185 4.139,16.37 C4.139,16.37 4.726,18.2 7.508,17.58 C7.513,18.437 7.522,19.245 7.522,19.489 C7.522,19.76 7.338,20.077 6.839,19.982 C2.865,18.627 0,14.783 0,10.253 C0,4.59 4.478,0 10,0" fill="currentColor"></path></svg>'
},
link: 'https://github.com/ChiYuKe/ONIModTutorial',
ariaLabel: '前往 GitHub 仓库'
},
{
icon: {
svg: '<svg viewBox="0 0 32 32" xmlns="http://www.w3.org/2000/svg"><title>我的Steam创意工坊</title><path d="M18.102 12.129c0-0 0-0 0-0.001 0-1.564 1.268-2.831 2.831-2.831s2.831 1.268 2.831 2.831c0 1.564-1.267 2.831-2.831 2.831-0 0-0 0-0.001 0h0c-0 0-0 0-0.001 0-1.563 0-2.83-1.267-2.83-2.83 0-0 0-0 0-0.001v0zM24.691 12.135c0-2.081-1.687-3.768-3.768-3.768s-3.768 1.687-3.768 3.768c0 2.081 1.687 3.768 3.768 3.768v0c2.080-0.003 3.765-1.688 3.768-3.767v-0zM10.427 23.76l-1.841-0.762c0.524 1.078 1.611 1.808 2.868 1.808 1.317 0 2.448-0.801 2.93-1.943l0.008-0.021c0.155-0.362 0.246-0.784 0.246-1.226 0-1.757-1.424-3.181-3.181-3.181-0.405 0-0.792 0.076-1.148 0.213l0.022-0.007 1.903 0.787c0.852 0.364 1.439 1.196 1.439 2.164 0 1.296-1.051 2.347-2.347 2.347-0.324 0-0.632-0.066-0.913-0.184l0.015 0.006zM15.974 1.004c-7.857 0.001-14.301 6.046-14.938 13.738l-0.004 0.054 8.038 3.322c0.668-0.462 1.495-0.737 2.387-0.737 0.001 0 0.002 0 0.002 0h-0c0.079 0 0.156 0.005 0.235 0.008l3.575-5.176v-0.074c0.003-3.12 2.533-5.648 5.653-5.648 3.122 0 5.653 2.531 5.653 5.653s-2.531 5.653-5.653 5.653h-0.131l-5.094 3.638c0 0.065 0.005 0.131 0.005 0.199 0 0.001 0 0.002 0 0.003 0 2.342-1.899 4.241-4.241 4.241-2.047 0-3.756-1.451-4.153-3.38l-0.005-0.027-5.755-2.383c1.841 6.345 7.601 10.905 14.425 10.905 8.281 0 14.994-6.713 14.994-14.994s-6.713-14.994-14.994-14.994c-0 0-0.001 0-0.001 0h0z" fill="currentColor"></path></svg>'
},
link: 'https://steamcommunity.com/profiles/76561199064454583/myworkshopfiles/?appid=457140',
ariaLabel: '查看我的 Steam 创意工坊'
}
],
sidebar: {
// 1. 默认侧边栏:显示在根目录及基础章节
'/': [
{
text: '01. 项目概述',
collapsed: false,
items: [
{ text: '开发引言', link: '/preface' },
{ text: '开发环境配置', link: '/development-environment' },
{ text: '游戏资产提取', link: '/asset-extraction' },
{ text: '游戏源码分析工具', link: '/dnspy-analysis' }
]
},
{
text: '02. 核心开发',
collapsed: false,
items: [
{ text: 'C# 基础与补丁语法', link: '/csharp-basics' },
{ text: 'Mod 结构', link: '/mod-structure' },
{ text: '第一个Mod', link: '/first-mod-tutorial' },
{ text: '编译流程扩展', link: '/build-and-debug' },
{ text: 'Unity篇', link: '/resource-unity' }
]
},
{
text: '03. 游戏内容扩展',
collapsed: true, // 在主站默认折叠,避免太长
items: [
{ text: '新增建筑', link: '/content/buildings' },
{ text: '新增植物', link: '/content/plants' },
{ text: '新增物品', link: '/content/items' },
{ text: '新增生物', link: '/content/creatures' },
{ text: '新增元素', link: '/content/elements' }
]
},
{
text: '04. 发布与维护',
collapsed: true,
items: [
{ text: 'Steam Workshop 上传', link: '/steam-workshop-upload' },
{ text: '多版本兼容性处理', link: '/version-compatibility' },
{ text: '开源协议与社区规范', link: '/community-guidelines' }
]
},
{
text: '05. 其他',
collapsed: true,
items: [
{ text: '贡献者名单', link: '/contributors' },
{ text: '加入贡献', link: '/contribute' },
{ text: '新闻公告', link: '/announcements' },
{ text: '创意工坊', link: '/workshop-list' }
]
}
],
// 2. 内容扩展专用侧边栏
'/content/': [
{
text: '🏗️ 游戏内容扩展',
items: [
{ text: '← 返回教程主页', link: '/preface' },
{ text: '新增建筑 (Buildings)', link: '/content/buildings' },
{ text: '新增植物 (Plants)', link: '/content/plants' },
{ text: '新增物品 (Items)', link: '/content/items' },
{ text: '新增生物 (Creatures)', link: '/content/creatures' },
{ text: '新增元素 (Elements)', link: '/content/elements' }
]
}
]
},
editLink: {
pattern: 'https://github.com/ChiYuKe/ONIModTutorial/edit/main/:path',
text: '在 GitHub 上编辑此页'
},
search: {
provider: 'local'
},
lastUpdated: {
text: '最后更新于',
formatOptions: {
dateStyle: 'full',
timeStyle: 'medium'
}
},
}
})

View File

@ -0,0 +1,89 @@
<template>
<div class="contributors-container">
<div v-for="user in contributors" :key="user.id" class="contributor-card">
<a :href="user.html_url" target="_blank" rel="noreferrer">
<img :src="user.avatar_url" :alt="user.login" class="avatar" />
<span class="username">{{ user.login }}</span>
</a>
</div>
</div>
</template>
<script setup>
import { ref, onMounted } from 'vue'
const props = defineProps({
repo: { type: String, required: true } // 格式: "Owner/RepoName"
})
const contributors = ref([])
onMounted(async () => {
const cacheKey = `contributors-${props.repo}`;
const cachedData = localStorage.getItem(cacheKey);
const cacheTime = localStorage.getItem(`${cacheKey}-timestamp`);
// 如果缓存存在且没超过 1 小时,直接用缓存
if (cachedData && cacheTime && Date.now() - cacheTime < 3600000) {
contributors.value = JSON.parse(cachedData);
return;
}
try {
const response = await fetch(`https://api.github.com/repos/${props.repo}/contributors`);
const data = await response.json();
if (Array.isArray(data)) {
const users = data.filter(user => user.type === 'User');
contributors.value = users;
// 存入缓存
localStorage.setItem(cacheKey, JSON.stringify(users));
localStorage.setItem(`${cacheKey}-timestamp`, Date.now().toString());
}
} catch (e) {
console.error('获取失败', e);
}
});
</script>
<style scoped>
.contributors-container {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(80px, 1fr));
gap: 20px;
margin-top: 24px;
justify-items: center;
}
.contributor-card a {
display: flex;
flex-direction: column;
align-items: center;
text-decoration: none;
transition: transform 0.2s ease;
width: 80px;
}
.contributor-card a:hover {
transform: translateY(-5px);
}
.avatar {
width: 60px;
height: 60px;
border-radius: 50%;
border: 2px solid var(--vp-c-brand);
object-fit: cover;
background-color: var(--vp-c-bg-soft);
}
.username {
font-size: 12px;
margin-top: 8px;
color: var(--vp-c-text-1);
text-align: center;
width: 100%;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
</style>

View File

@ -0,0 +1,35 @@
<template>
<div class="timeline">
<div class="timeline-item" v-for="(item, index) in steps" :key="index">
<div class="timeline-dot"></div>
<div class="timeline-content">
<div class="timeline-date">{{ item.status }}</div>
<h4 class="timeline-title">{{ item.title }}</h4>
<p class="timeline-desc">{{ item.desc }}</p>
</div>
</div>
</div>
</template>
<script setup>
const steps = [
{ status: '已完成', title: '摸鱼', desc: '了解上班摸鱼写文档的流程。' },
{ status: '进行中', title: 'Mod 基础教程', desc: '掌握变量、类与对象,这是制作 Mod 的基石。' },
{ status: '计划中', title: 'UI 界面开发', desc: '教你如何在游戏里画出自己的窗口和按钮。' }
]
</script>
<style scoped>
.timeline { margin: 2rem 0; padding-left: 20px; border-left: 2px solid var(--vp-c-divider); }
.timeline-item { position: relative; margin-bottom: 2rem; padding-left: 30px; }
.timeline-dot {
position: absolute; left: -31px; top: 5px;
width: 20px; height: 20px;
background: var(--vp-c-brand);
border: 4px solid var(--vp-c-bg);
border-radius: 50%;
}
.timeline-date { font-size: 0.8rem; color: var(--vp-c-brand); font-weight: bold; }
.timeline-title { margin: 5px 0 !important; color: var(--vp-c-text-1); }
.timeline-desc { font-size: 0.9rem; color: var(--vp-c-text-2); margin: 0; }
</style>

View File

@ -0,0 +1,292 @@
<script setup>
import { ref, reactive, onMounted } from 'vue'
const articles = ref([])
const loading = ref(true)
const APP_ID = '457140'
// Tooltip 相关状态
const tooltip = reactive({
visible: false,
lines: [],
style: {}
})
// 解析 Steam 补丁说明为纯文本行数组
function parseSteamDescription(html) {
if (!html) return []
return html
.replace(/\r?\n/g, '')
.replace(/<li>(.*?)<\/li>/gi, '• $1\n')
.replace(/<\/p>|<br\s*\/?>/gi, '\n')
.replace(/<[^>]+>/g, '')
.split('\n')
.map(l => l.trim())
.filter(Boolean)
}
// 显示 Tooltip
function showTooltip(e, item) {
const rect = e.currentTarget.getBoundingClientRect()
const MAX_LINES = 10
const GAP = 12
const WIDTH = 700
const LINE_HEIGHT = 22
const PADDING = 24
const total = item.parsedLines.length
if (total > MAX_LINES) {
tooltip.lines = [
...item.parsedLines
.slice(0, MAX_LINES - 1)
.map(t => ({ text: t })),
{ text: '......', ellipsis: true }
]
} else {
tooltip.lines = item.parsedLines.map(t => ({ text: t }))
}
const estimatedHeight =
tooltip.lines.length * LINE_HEIGHT + PADDING
let top
// 优先显示在上方
if (rect.top < estimatedHeight + GAP) {
top = rect.bottom + GAP
} else {
top = rect.top - estimatedHeight - GAP
}
tooltip.style = {
position: 'fixed',
top: `${top}px`,
left: `${rect.left}px`,
width: `${WIDTH}px`
}
tooltip.visible = true
}
function hideTooltip() {
tooltip.visible = false
}
// 获取 Steam 新闻数据
onMounted(async () => {
try {
const res = await fetch(
`https://api.rss2json.com/v1/api.json?rss_url=https%3A%2F%2Fstore.steampowered.com%2Ffeeds%2Fnews%2Fapp%2F${APP_ID}`
)
const data = await res.json()
if (data.status === 'ok') {
articles.value = data.items.slice(0, 10).map(item => {
const imgReg = /<img.*?src=["'](.*?)["']/
const match = item.content?.match(imgReg)
const fallbackImg =
`https://cdn.akamai.steamstatic.com/steam/apps/${APP_ID}/header.jpg`
return {
...item,
displayImage: (match ? match[1] : item.thumbnail) || fallbackImg,
displayDate: item.pubDate.split(' ')[0],
parsedLines: parseSteamDescription(item.description)
}
})
}
} catch (e) {
console.error(e)
} finally {
loading.value = false
}
})
</script>
<template>
<div class="steam-container">
<div v-if="loading" class="loading">
正在同步 Steam 补丁说明...
</div>
<div v-else class="news-list">
<a
v-for="item in articles"
:key="item.guid"
:href="item.link"
target="_blank"
class="news-card"
@mouseenter="e => showTooltip(e, item)"
@mouseleave="hideTooltip"
>
<div class="news-card-inner">
<div class="card-text">
<div class="card-header">
<span class="tag">游戏更新</span>
<span class="date">{{ item.displayDate }}</span>
</div>
<h3 class="title">{{ item.title }}</h3>
<p class="description">
{{ item.parsedLines.join(' ').slice(0, 60) }}...
</p>
</div>
<div class="card-image-box">
<img :src="item.displayImage" />
</div>
</div>
</a>
</div>
<Teleport to="body">
<div
v-if="tooltip.visible"
class="global-tooltip"
:style="tooltip.style"
>
<div
v-for="(line, i) in tooltip.lines"
:key="i"
class="tooltip-line"
:class="{
bullet: line.text.startsWith('•'),
ellipsis: line.ellipsis
}"
>
{{ line.text }}
</div>
</div>
</Teleport>
</div>
</template>
<style scoped>
.news-list {
display: flex;
flex-direction: column;
gap: 1rem;
}
.news-card {
text-decoration: none;
color: inherit;
}
.news-card-inner {
display: flex;
justify-content: space-between;
background: #2a313d;
border: 1px solid #535a66;
border-radius: 4px;
transition: transform 0.2s, background 0.2s;
}
.news-card:hover .news-card-inner {
background: #363f4c;
transform: translateX(4px);
}
.card-text {
padding: 1rem;
}
.card-header {
font-size: 12px;
display: flex;
gap: 10px;
}
.tag {
color: #8f98a0;
}
.date {
color: #66c0f4;
}
.title {
margin: 6px 0 0;
font-size: 1.1rem;
color: #fff;
}
.description {
font-size: 0.85rem;
color: #acb2b8;
}
.card-image-box {
width: 200px;
height: 112px;
margin: 0.75rem;
overflow: hidden;
border-radius: 4px;
}
.card-image-box img {
width: 100%;
height: 100%;
object-fit: cover;
}
.global-tooltip {
background: rgba(18, 22, 28, 0.96);
border: 1px solid #3d4450;
border-radius: 4px;
padding: 12px;
width: 700px;
box-shadow: 0 8px 20px rgba(0, 0, 0, 0.6);
z-index: 9999999;
pointer-events: none;
}
.tooltip-line {
font-size: 0.8rem;
color: #dcdedf;
line-height: 1.45;
margin-bottom: 6px;
}
.tooltip-line.bullet {
padding-left: 14px;
text-indent: -14px;
color: #c7d5e0;
}
.tooltip-line.ellipsis {
color: #66c0f4;
font-weight: 500;
}
</style>

View File

@ -0,0 +1,290 @@
<script setup>
import { ref, onMounted } from 'vue'
const mods = ref([])
const loading = ref(true)
const STATS_CACHE_TTL = 60 * 60 * 1000 // 60 分钟
function sleep(ms) {
return new Promise(resolve => setTimeout(resolve, ms))
}
function getCachedStats(id) {
const raw = localStorage.getItem('workshop_stats_' + id)
if (!raw) return null
const { time, data } = JSON.parse(raw)
if (Date.now() - time > STATS_CACHE_TTL) return null
return data
}
function setCachedStats(id, data) {
localStorage.setItem(
'workshop_stats_' + id,
JSON.stringify({
time: Date.now(),
data
})
)
}
async function fetchWorkshopStats(detailUrl) {
try {
const match = detailUrl.match(/id=(\d+)/)
if (!match) return null
const cleanUrl = `https://steamcommunity.com/sharedfiles/filedetails/?id=${match[1]}`
const cached = getCachedStats(match[1])
if (cached) return cached
const workerUrl =
`https://aged-dream-7a55.liukele015.workers.dev/?url=${encodeURIComponent(cleanUrl)}`
const res = await fetch(workerUrl)
if (!res.ok) {
console.warn('Worker 返回失败:', res.status, res.statusText)
return null
}
const data = await res.json()
if (!data || !data.stats) {
console.warn('Worker 返回数据不正确:', data)
return null
}
setCachedStats(match[1], data.stats)
return data.stats
} catch (err) {
console.warn('统计获取失败:', detailUrl, err)
return null
}
}
async function loadStats(mod) {
if (!mod.link || mod.link === '#') return
if (mod.stats || mod.statsLoading) return
mod.statsLoading = true
mod.stats = await fetchWorkshopStats(mod.link)
mod.statsLoading = false
}
async function loadStatsSequentially(list) {
for (const mod of list) {
await loadStats(mod)
await sleep(150)
}
}
onMounted(async () => {
try {
const steamUrl =
'https://steamcommunity.com/workshop/browse/' +
'?appid=457140' +
'&browssemethod=trend' +
'&section=readytouseitems' +
'&actualsort=trend' +
'&p=1' +
'&numperpage=6'
// 这里使用 Cloudflare做为代理避免 CORS 问题
const proxyUrl =
`https://aged-dream-7a55.liukele015.workers.dev/?mode=workshop&url=${encodeURIComponent(steamUrl)}`
const res = await fetch(proxyUrl)
const html = await res.text()
const doc = new DOMParser().parseFromString(html, 'text/html')
const items = doc.querySelectorAll('.workshopItem')
mods.value = Array.from(items).map(item => {
const titleEl = item.querySelector('.workshopItemTitle')
const linkEl = item.querySelector('a')
const authorEl = item.querySelector('.workshopItemAuthorName a')
const imgDiv = item.querySelector('.workshopItemImg')
const bg = imgDiv?.style?.backgroundImage || ''
const bgUrl = bg.match(/url\(["']?(.*?)["']?\)/)?.[1] || null
const imgPreview = item.querySelector('img.preview_image')
const previewUrl = imgPreview?.src || null
const imgAny = item.querySelector('img')
const anyUrl = imgAny?.src || null
let thumbnail =
bgUrl || previewUrl || anyUrl ||
'https://via.placeholder.com/200x120?text=No+Image'
if (thumbnail.startsWith('//')) {
thumbnail = 'https:' + thumbnail
}
thumbnail = `https://images.weserv.nl/?url=${encodeURIComponent(thumbnail)}`
return {
title: titleEl?.innerText.trim() ?? '未知模组',
link: linkEl?.href ?? '#',
author: authorEl?.innerText.trim() ?? '匿名作者',
thumbnail,
stats: null,
statsLoading: false
}
})
loading.value = false
loadStatsSequentially(mods.value)
} catch (err) {
console.error('抓取 Workshop 失败:', err)
loading.value = false
}
})
</script>
<template>
<div class="workshop-container">
<div v-if="loading" class="loading-box">
正在连接 Steam 创意工坊...
</div>
<div v-else class="mod-grid">
<a
v-for="mod in mods"
:key="mod.link"
:href="mod.link"
target="_blank"
class="mod-card"
>
<div class="mod-img">
<img :src="mod.thumbnail" loading="lazy" />
</div>
<div class="mod-card__body">
<div class="mod-card__title">{{ mod.title }}</div>
<div class="mod-card__meta">By {{ mod.author }}</div>
<div class="mod-card__stats">
<template v-if="mod.stats">
<span>访客 {{ mod.stats.visitors }}</span>
<span>订阅 {{ mod.stats.subscribers }}</span>
<span>收藏 {{ mod.stats.favorites }}</span>
</template>
<template v-else-if="mod.statsLoading">
<span>统计加载中</span>
</template>
<template v-else>
<span></span>
</template>
</div>
</div>
</a>
</div>
</div>
</template>
<style scoped>
.workshop-container {
margin: 1.5rem 0;
}
.loading-box {
text-align: center;
padding: 2rem;
color: var(--vp-c-text-2);
background: var(--vp-c-bg-soft);
border-radius: 8px;
}
.mod-grid {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 16px;
}
@media (max-width: 900px) {
.mod-grid {
grid-template-columns: repeat(2, 1fr);
}
}
@media (max-width: 600px) {
.mod-grid {
grid-template-columns: 1fr;
}
}
.mod-card {
background: var(--vp-c-bg-soft);
border: 1px solid var(--vp-c-divider);
border-radius: 8px;
overflow: hidden;
text-decoration: none !important;
transition: all 0.25s ease;
}
.mod-card:hover {
border-color: var(--vp-c-brand);
transform: translateY(-4px);
background: var(--vp-c-bg-mute);
}
.mod-img {
width: 100%;
height: 125px;
background: #000;
}
.mod-img img {
width: 100%;
height: 100%;
object-fit: cover;
}
.mod-card__body {
padding: 12px;
}
.mod-card__title {
color: var(--vp-c-text-1);
font-weight: 600;
font-size: 0.95rem;
line-height: 1.3;
height: 2.6em;
overflow: hidden;
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
}
.mod-card__meta {
color: var(--vp-c-text-2);
font-size: 0.8rem;
margin-top: 8px;
}
.mod-card__stats {
margin-top: 10px;
display: flex;
gap: 8px;
flex-wrap: wrap;
font-size: 0.75rem;
color: var(--vp-c-text-2);
}
.mod-card__stats span {
padding: 4px 8px;
border-radius: 999px;
border: 1px solid var(--vp-c-divider);
background: var(--vp-c-bg-soft);
}
</style>

View File

@ -0,0 +1,61 @@
/**
* VitePress 布局宽度自定义
*/
:root {
/* 1. 文档内容的最大宽度 (默认是 1152px) */
/* 设置为 1280px 或 1440px 可以让代码块更宽,减少横向滚动条 */
--vp-content-max-width: 1280px;
/* 2. 整个页面的最大宽度 (包括侧边栏和正文) */
--vp-layout-max-width: 1600px;
/* --vp-c-brand-1: #8f2a3b;
--vp-c-brand-2: #8f2a3b; */
}
.VPHero .name {
/* 撤销默认的渐变色背景 */
background: none !important;
-webkit-background-clip: initial !important;
background-clip: initial !important;
-webkit-text-fill-color: initial !important;
}
/* 强制覆盖首页标题的渐变效果 */
.VPHero .name span.color-w {
background: none !important;
-webkit-text-fill-color: initial !important;
color: var(--vp-c-text-1) !important;
}
.VPHero .name span.color-r {
background: none !important;
-webkit-text-fill-color: #ff4d4f !important; /* 红色 */
color: #ff4d4f !important;
}
/* 正文里的颜色 */
span.color-white {
color: #ffffff !important;
}
span.color-white {
color: #ff4d4f !important;
}
/* 如果你希望“代码块”在宽屏下表现更好,可以加上这个微调 */
@media (min-width: 1280px) {
.VPDoc .container {
/* 这里的 margin 会自动平衡左右空间 */
margin: 0 auto;
}
}
/* 3. (可选) 首页 Hero 部分的宽度调整 */
:root {
--vp-home-hero-max-width: 1152px;
}

View File

@ -0,0 +1,19 @@
// .vitepress/theme/index.mts
import DefaultTheme from 'vitepress/theme'
import './custom.css' // 这里的引入才是有效的
// @ts-ignore
import Contributors from './components/Contributors.vue'
import SteamNews from './components/SteamNews.vue'
import LearningTimeline from './components/LearningTimeline.vue'
import WorkshopList from './components/WorkshopList.vue'
export default {
extends: DefaultTheme,
enhanceApp({ app }: any) {
app.component('Contributors', Contributors)
app.component('SteamNews', SteamNews)
app.component('LearningTimeline', LearningTimeline)
app.component('WorkshopList', WorkshopList)
}
}