feat: 完善主题功能并适配插件 (v1.1.5)
功能新增 - 新增页面布局契约 templates/layout.html,插件前台页面可复用主题外观,避免回退到 Halo fallback 布局 - 新增微信读书页面模板 (weread.html),集成 halo-weread-plugin 书架 - 新增订阅页面模板 (page_subscriptions.html) - 新增欢迎弹窗组件(IP 地理定位,多 API 容错) - 新增点击特效组件(9 种效果) - 页脚新增全站统计,支持开关与统计项配置,默认开启 插件适配 - 适配 extra-api:文章字数改服务端统计、正文可选中英混排与服务端代码高亮 - 修正各页面插件应用市场链接与插件名 修复 - 修复文章正文图片/视频无法居中(正文样式移至全局,scoped 样式对 Thymeleaf 注入内容无效) - 修复正文 H1-H6 字号过大,重排层级与移动端断点 - 修复 th: 属性中文字符串缺少引号导致的 Thymeleaf 解析异常(/links、/moments 500) - 修复 JS 内联表达式与 Thymeleaf 内联语法冲突,全部内联脚本加 th:inline="none" - 修复移除 i18n 后 steam 页 Elvis 表达式回归问题 - 修正订阅页 th:attr 引号嵌套错误 优化 - Hero 移除灰色遮罩,下滑箭头改为可点击平滑滚动 - 优化目录样式:移除圆点与下拉箭头,支持两行显示,修复长目录截断 - 移除 i18n 多语言支持 清理 - 移除未使用的 Vue/图标依赖与废弃脚本、重复资源
This commit is contained in:
+11
-2
@@ -1,18 +1,24 @@
|
||||
# build output
|
||||
dist/
|
||||
templates/
|
||||
|
||||
# generated types
|
||||
.astro/
|
||||
|
||||
# dependencies
|
||||
node_modules/
|
||||
|
||||
# docs site
|
||||
docs/.vitepress/cache/
|
||||
docs/.vitepress/dist/
|
||||
docs/node_modules/
|
||||
|
||||
# logs
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
pnpm-debug.log*
|
||||
|
||||
|
||||
# environment variables
|
||||
.env
|
||||
.env.production
|
||||
@@ -23,4 +29,7 @@ pnpm-debug.log*
|
||||
# jetbrains setting folder
|
||||
.idea/
|
||||
|
||||
templates/
|
||||
# AI working artifacts
|
||||
llms.txt
|
||||
|
||||
.workbuddy/
|
||||
|
||||
@@ -1,91 +0,0 @@
|
||||
const http = require('http');
|
||||
|
||||
function apiCall(method, path, data) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const options = {
|
||||
hostname: 'localhost',
|
||||
port: 8090,
|
||||
path: path,
|
||||
method: method,
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
}
|
||||
};
|
||||
|
||||
const req = http.request(options, (res) => {
|
||||
let body = '';
|
||||
res.on('data', chunk => body += chunk);
|
||||
res.on('end', () => {
|
||||
try {
|
||||
const cookies = res.headers['set-cookie'];
|
||||
resolve({ status: res.statusCode, data: JSON.parse(body), cookies });
|
||||
} catch(e) {
|
||||
resolve({ status: res.statusCode, data: body, cookies: res.headers['set-cookie'] });
|
||||
}
|
||||
});
|
||||
});
|
||||
req.on('error', reject);
|
||||
if (data) req.write(JSON.stringify(data));
|
||||
req.end();
|
||||
});
|
||||
}
|
||||
|
||||
async function main() {
|
||||
// Login first
|
||||
const loginRes = await apiCall('POST', '/api/auth/signin', { username: 'admin', password: 'admin' });
|
||||
console.log('Login status:', loginRes.status);
|
||||
|
||||
// Get the session cookie
|
||||
const cookies = loginRes.cookies || [];
|
||||
const cookieStr = cookies.map(c => c.split(';')[0]).join('; ');
|
||||
console.log('Cookies:', cookieStr);
|
||||
|
||||
// Now get theme settings using the cookie
|
||||
const http2 = require('http');
|
||||
function apiWithCookie(method, path, data) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const options = {
|
||||
hostname: 'localhost',
|
||||
port: 8090,
|
||||
path: path,
|
||||
method: method,
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Cookie': cookieStr
|
||||
}
|
||||
};
|
||||
const req = http2.request(options, (res) => {
|
||||
let body = '';
|
||||
res.on('data', chunk => body += chunk);
|
||||
res.on('end', () => {
|
||||
try {
|
||||
resolve({ status: res.statusCode, data: JSON.parse(body) });
|
||||
} catch(e) {
|
||||
resolve({ status: res.statusCode, data: body });
|
||||
}
|
||||
});
|
||||
});
|
||||
req.on('error', reject);
|
||||
if (data) req.write(JSON.stringify(data));
|
||||
req.end();
|
||||
});
|
||||
}
|
||||
|
||||
// Get activated theme
|
||||
const themeRes = await apiWithCookie('GET', '/apis/api.console.halo.run/v1alpha1/themes?sort=creationTimestamp%2Cdesc');
|
||||
console.log('Themes status:', themeRes.status);
|
||||
|
||||
if (themeRes.data && themeRes.data.items) {
|
||||
const activeTheme = themeRes.data.items.find(t => t.spec && t.spec.activationTime);
|
||||
if (activeTheme) {
|
||||
console.log('Active theme:', activeTheme.metadata.name);
|
||||
|
||||
// Get theme settings
|
||||
const settingsRes = await apiWithCookie('GET', `/apis/api.console.halo.run/v1alpha1/themes/${activeTheme.metadata.name}/setting`);
|
||||
console.log('Settings status:', settingsRes.status);
|
||||
console.log('Settings:', JSON.stringify(settingsRes.data).substring(0, 500));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
main().catch(console.error);
|
||||
-52
@@ -1,52 +0,0 @@
|
||||
{
|
||||
"hash": "0ed7ed0d",
|
||||
"configHash": "c707d7b6",
|
||||
"lockfileHash": "4f412479",
|
||||
"browserHash": "3aefc297",
|
||||
"optimized": {
|
||||
"vue": {
|
||||
"src": "../../../node_modules/vue/dist/vue.runtime.esm-bundler.js",
|
||||
"file": "vue.js",
|
||||
"fileHash": "9027419d",
|
||||
"needsInterop": false
|
||||
},
|
||||
"vitepress > @vue/devtools-api": {
|
||||
"src": "../../../node_modules/@vue/devtools-api/dist/index.js",
|
||||
"file": "vitepress___@vue_devtools-api.js",
|
||||
"fileHash": "3cd67bf3",
|
||||
"needsInterop": false
|
||||
},
|
||||
"vitepress > @vueuse/core": {
|
||||
"src": "../../../node_modules/@vueuse/core/index.mjs",
|
||||
"file": "vitepress___@vueuse_core.js",
|
||||
"fileHash": "cda81787",
|
||||
"needsInterop": false
|
||||
},
|
||||
"vitepress > @vueuse/integrations/useFocusTrap": {
|
||||
"src": "../../../node_modules/@vueuse/integrations/useFocusTrap.mjs",
|
||||
"file": "vitepress___@vueuse_integrations_useFocusTrap.js",
|
||||
"fileHash": "7b4ec920",
|
||||
"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": "3efbcd2b",
|
||||
"needsInterop": false
|
||||
},
|
||||
"vitepress > minisearch": {
|
||||
"src": "../../../node_modules/minisearch/dist/es/index.js",
|
||||
"file": "vitepress___minisearch.js",
|
||||
"fileHash": "8ca06377",
|
||||
"needsInterop": false
|
||||
}
|
||||
},
|
||||
"chunks": {
|
||||
"chunk-4USRTV5H": {
|
||||
"file": "chunk-4USRTV5H.js"
|
||||
},
|
||||
"chunk-XY26LBYQ": {
|
||||
"file": "chunk-XY26LBYQ.js"
|
||||
}
|
||||
}
|
||||
}
|
||||
-9719
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
-13035
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
-3
@@ -1,3 +0,0 @@
|
||||
{
|
||||
"type": "module"
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
@@ -1,583 +0,0 @@
|
||||
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-4USRTV5H.js";
|
||||
import "./chunk-XY26LBYQ.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
|
||||
@@ -1,7 +0,0 @@
|
||||
{
|
||||
"version": 3,
|
||||
"sources": [],
|
||||
"sourcesContent": [],
|
||||
"mappings": "",
|
||||
"names": []
|
||||
}
|
||||
-1352
File diff suppressed because it is too large
Load Diff
-7
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
-1813
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
Vendored
-347
@@ -1,347 +0,0 @@
|
||||
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-XY26LBYQ.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
@@ -1,7 +0,0 @@
|
||||
{
|
||||
"version": 3,
|
||||
"sources": [],
|
||||
"sourcesContent": [],
|
||||
"mappings": "",
|
||||
"names": []
|
||||
}
|
||||
@@ -1,2 +0,0 @@
|
||||
const js = `(function(){var h=document.querySelector(".hero");if(!h)return "No .hero element";var s=getComputedStyle(h);return JSON.stringify({width:s.width,marginLeft:s.marginLeft,marginTop:s.marginTop,paddingTop:s.paddingTop})})()`;
|
||||
console.log(Buffer.from(js).toString('base64'));
|
||||
@@ -1,130 +0,0 @@
|
||||
common.home=Home
|
||||
common.search=Search
|
||||
common.themeSwitch=Switch Theme
|
||||
common.menu=Menu
|
||||
common.close=Close
|
||||
common.toc=Table of Contents
|
||||
common.openToc=Open Table of Contents
|
||||
common.closeToc=Close Table of Contents
|
||||
common.newer=Newer
|
||||
common.older=Older
|
||||
common.loading=Loading...
|
||||
common.allLoaded=All posts loaded
|
||||
common.allMomentsLoaded=All moments loaded
|
||||
common.noPosts=No posts yet.
|
||||
common.postCount=Total {0} posts
|
||||
common.categoryCount=Total {0} categories
|
||||
common.tagCount=Total {0} tags
|
||||
common.readMore=Read more
|
||||
common.words=words
|
||||
common.readTime=min read
|
||||
common.views=views
|
||||
common.like=Like
|
||||
common.pagination=Pagination
|
||||
common.toggleMenu=Toggle menu
|
||||
common.all=All
|
||||
common.noPhotos=No photos yet.
|
||||
common.noPhotosInGroup=No photos in this group.
|
||||
common.pluginError=Plugin loading error, please check plugin status.
|
||||
common.installPlugin=This feature requires the {0} plugin, <a href="{1}">go to the marketplace to install</a>
|
||||
common.featured=Featured
|
||||
common.latestPosts=Latest Posts
|
||||
common.timeline=Timeline
|
||||
common.viewAll=View all
|
||||
|
||||
navbar.search=Search
|
||||
navbar.themeSwitch=Switch Theme
|
||||
|
||||
hero.pinned=Pinned
|
||||
hero.posts=Posts
|
||||
|
||||
article.previousPost=Previous
|
||||
article.nextPost=Next
|
||||
article.toc=Table of Contents
|
||||
article.wordCount=words
|
||||
article.readTime=min read
|
||||
article.views=views
|
||||
article.relatedPosts=Related Posts
|
||||
|
||||
share.title=Share
|
||||
share.qq=QQ
|
||||
share.wechat=WeChat
|
||||
share.weibo=Weibo
|
||||
share.twitter=X (Twitter)
|
||||
share.facebook=Facebook
|
||||
share.telegram=Telegram
|
||||
share.copyLink=Copy Link
|
||||
share.copied=Copied!
|
||||
share.qrcode=QR Code
|
||||
share.wechatQR=Scan to Share
|
||||
share.wechatHint=Open WeChat and scan the QR code to share
|
||||
|
||||
archives.title=Archives
|
||||
|
||||
categories.title=Categories
|
||||
|
||||
tags.title=Tags
|
||||
|
||||
links.title=Friends
|
||||
links.subtitle=Mountains and rivers, a journey of a thousand miles
|
||||
|
||||
moments.title=Moments
|
||||
moments.noMoments=No moments yet, go record your mood \U0001f319
|
||||
|
||||
photos.title=Gallery
|
||||
|
||||
friends.title=Friends Circle
|
||||
|
||||
equipment.title=Equipment
|
||||
|
||||
steam.title=Steam
|
||||
steam.homepage=Steam Homepage
|
||||
steam.level=Level
|
||||
steam.ownedGames=Owned Games
|
||||
steam.totalPlaytime=Total Playtime
|
||||
steam.recentPlaytime=Recent 2 Weeks
|
||||
steam.badges=Badges
|
||||
steam.hours=hours
|
||||
steam.recentlyPlayed=Recently Played
|
||||
steam.gameLibrary=Game Library
|
||||
steam.lastPlayed=Last played {0}
|
||||
|
||||
douban.title=Douban
|
||||
douban.wish=Wish
|
||||
douban.done=Done
|
||||
douban.wishRead=Wish
|
||||
douban.reading=Reading
|
||||
douban.listened=Listened
|
||||
douban.wishPlay=Wish
|
||||
douban.played=Played
|
||||
douban.wishListen=Wish
|
||||
douban.listening=Listening
|
||||
douban.playing=Playing
|
||||
douban.myRating=My:
|
||||
|
||||
messageboard.title=Message Board
|
||||
messageboard.subtitle=Leave your footprints here \U0001f319
|
||||
|
||||
footer.poweredBy=Powered by Halo
|
||||
|
||||
error.404.title=Page Not Found
|
||||
error.404.desc=The page you are looking for seems to have wandered off this island
|
||||
error.404.home=Back to Home
|
||||
error.404.back=Go Back
|
||||
error.500.title=Server Error
|
||||
error.500.desc=The island encountered some issues, please try again later
|
||||
error.500.home=Back to Home
|
||||
error.500.back=Go Back
|
||||
|
||||
sidebar.authorInfo=About Author
|
||||
sidebar.latestPosts=Latest Posts
|
||||
sidebar.recommendedPosts=Recommended
|
||||
sidebar.timeProgress=Time Progress
|
||||
sidebar.dayProgress=Day
|
||||
sidebar.monthProgress=Month
|
||||
sidebar.yearProgress=Year
|
||||
sidebar.categories=Categories
|
||||
sidebar.tags=Tags
|
||||
|
||||
comment.style=Warm
|
||||
comment.showAvatar=Show Avatar
|
||||
@@ -1,130 +0,0 @@
|
||||
common.home=Home
|
||||
common.search=Search
|
||||
common.themeSwitch=Switch Theme
|
||||
common.menu=Menu
|
||||
common.close=Close
|
||||
common.toc=Table of Contents
|
||||
common.openToc=Open Table of Contents
|
||||
common.closeToc=Close Table of Contents
|
||||
common.newer=Newer
|
||||
common.older=Older
|
||||
common.loading=Loading...
|
||||
common.allLoaded=All posts loaded
|
||||
common.noPosts=No posts yet.
|
||||
common.postCount=Total {0} posts
|
||||
common.readMore=Read more
|
||||
common.words=words
|
||||
common.readTime=min read
|
||||
common.views=views
|
||||
common.like=Like
|
||||
common.pagination=Pagination
|
||||
common.toggleMenu=Toggle menu
|
||||
common.allMomentsLoaded=All moments loaded
|
||||
common.categoryCount=Total {0} categories
|
||||
common.tagCount=Total {0} tags
|
||||
common.all=All
|
||||
common.noPhotos=No photos yet.
|
||||
common.noPhotosInGroup=No photos in this group.
|
||||
common.pluginError=Plugin loading error, please check plugin status.
|
||||
common.installPlugin=This feature requires the {0} plugin, <a href="{1}">go to the marketplace to install</a>
|
||||
common.featured=Featured
|
||||
common.latestPosts=Latest Posts
|
||||
common.timeline=Timeline
|
||||
common.viewAll=View all
|
||||
|
||||
navbar.search=Search
|
||||
navbar.themeSwitch=Switch Theme
|
||||
|
||||
hero.pinned=Pinned
|
||||
hero.posts=Posts
|
||||
|
||||
article.previousPost=Previous
|
||||
article.nextPost=Next
|
||||
article.toc=Table of Contents
|
||||
article.wordCount=words
|
||||
article.readTime=min read
|
||||
article.views=views
|
||||
article.relatedPosts=Related Posts
|
||||
|
||||
share.title=Share
|
||||
share.qq=QQ
|
||||
share.wechat=WeChat
|
||||
share.weibo=Weibo
|
||||
share.twitter=X (Twitter)
|
||||
share.facebook=Facebook
|
||||
share.telegram=Telegram
|
||||
share.copyLink=Copy Link
|
||||
share.copied=Copied!
|
||||
share.qrcode=QR Code
|
||||
share.wechatQR=Scan to Share
|
||||
share.wechatHint=Open WeChat and scan the QR code to share
|
||||
|
||||
archives.title=Archives
|
||||
|
||||
categories.title=Categories
|
||||
|
||||
tags.title=Tags
|
||||
|
||||
links.title=Friends
|
||||
links.subtitle=Mountains and rivers, a journey of a thousand miles
|
||||
|
||||
moments.title=Moments
|
||||
moments.noMoments=No moments yet, go record your mood \U0001f319
|
||||
|
||||
photos.title=Gallery
|
||||
|
||||
friends.title=Friends Circle
|
||||
|
||||
equipment.title=Equipment
|
||||
|
||||
steam.title=Steam
|
||||
steam.homepage=Steam Homepage
|
||||
steam.level=Level
|
||||
steam.ownedGames=Owned Games
|
||||
steam.totalPlaytime=Total Playtime
|
||||
steam.recentPlaytime=Recent 2 Weeks
|
||||
steam.badges=Badges
|
||||
steam.hours=hours
|
||||
steam.recentlyPlayed=Recently Played
|
||||
steam.gameLibrary=Game Library
|
||||
steam.lastPlayed=Last played {0}
|
||||
|
||||
douban.title=Douban
|
||||
douban.wish=Wish
|
||||
douban.done=Done
|
||||
douban.wishRead=Wish
|
||||
douban.reading=Reading
|
||||
douban.listened=Listened
|
||||
douban.wishPlay=Wish
|
||||
douban.played=Played
|
||||
douban.wishListen=Wish
|
||||
douban.listening=Listening
|
||||
douban.playing=Playing
|
||||
douban.myRating=My:
|
||||
|
||||
messageboard.title=Message Board
|
||||
messageboard.subtitle=Leave your footprints here \U0001f319
|
||||
|
||||
footer.poweredBy=Powered by Halo
|
||||
|
||||
error.404.title=Page Not Found
|
||||
error.404.desc=The page you are looking for seems to have wandered off this island
|
||||
error.404.home=Back to Home
|
||||
error.404.back=Go Back
|
||||
error.500.title=Server Error
|
||||
error.500.desc=The island encountered some issues, please try again later
|
||||
error.500.home=Back to Home
|
||||
error.500.back=Go Back
|
||||
|
||||
sidebar.authorInfo=About Author
|
||||
sidebar.latestPosts=Latest Posts
|
||||
sidebar.recommendedPosts=Recommended
|
||||
sidebar.timeProgress=Time Progress
|
||||
sidebar.dayProgress=Day
|
||||
sidebar.monthProgress=Month
|
||||
sidebar.yearProgress=Year
|
||||
sidebar.categories=Categories
|
||||
sidebar.tags=Tags
|
||||
|
||||
comment.style=Warm
|
||||
comment.showAvatar=Show Avatar
|
||||
@@ -1,130 +0,0 @@
|
||||
common.home=\u9996\u9875
|
||||
common.search=\u641c\u7d22
|
||||
common.themeSwitch=\u5207\u6362\u4e3b\u9898
|
||||
common.menu=\u83dc\u5355
|
||||
common.close=\u5173\u95ed
|
||||
common.toc=\u76ee\u5f55
|
||||
common.openToc=\u6253\u5f00\u76ee\u5f55
|
||||
common.closeToc=\u5173\u95ed\u76ee\u5f55
|
||||
common.newer=\u8f83\u65b0
|
||||
common.older=\u8f83\u65e7
|
||||
common.loading=\u52a0\u8f7d\u4e2d...
|
||||
common.allLoaded=\u5df2\u52a0\u8f7d\u5168\u90e8\u6587\u7ae0
|
||||
common.noPosts=\u6682\u65e0\u6587\u7ae0
|
||||
common.postCount=\u5171 {0} \u7bc7\u6587\u7ae0
|
||||
common.readMore=\u9605\u8bfb\u66f4\u591a
|
||||
common.words=\u5b57
|
||||
common.readTime=\u5206\u949f
|
||||
common.views=\u9605\u8bfb
|
||||
common.like=\u70b9\u8d5e
|
||||
common.pagination=\u5206\u9875
|
||||
common.toggleMenu=\u5207\u6362\u83dc\u5355
|
||||
common.allMomentsLoaded=\u5df2\u52a0\u8f7d\u5168\u90e8\u77ac\u95f4
|
||||
common.categoryCount=\u5171 {0} \u4e2a\u5206\u7c7b
|
||||
common.tagCount=\u5171 {0} \u4e2a\u6807\u7b7e
|
||||
common.all=\u5168\u90e8
|
||||
common.noPhotos=\u6682\u65e0\u56fe\u7247
|
||||
common.noPhotosInGroup=\u8be5\u5206\u7ec4\u6682\u65e0\u56fe\u7247
|
||||
common.pluginError=\u63d2\u4ef6\u52a0\u8f7d\u5f02\u5e38\uff0c\u8bf7\u68c0\u67e5\u63d2\u4ef6\u72b6\u6001
|
||||
common.installPlugin=\u8be5\u529f\u80fd\u9700\u8981\u5b89\u88c5\u300c{0}\u300d\u63d2\u4ef6\uff0c<a href="{1}">\u524d\u5f80\u5e94\u7528\u5e02\u573a\u5b89\u88c5</a>
|
||||
common.featured=\u7cbe\u9009
|
||||
common.latestPosts=\u6700\u65b0\u6587\u7ae0
|
||||
common.timeline=\u65f6\u95f4\u7ebf
|
||||
common.viewAll=\u67e5\u770b\u5168\u90e8
|
||||
|
||||
navbar.search=\u641c\u7d22
|
||||
navbar.themeSwitch=\u5207\u6362\u4e3b\u9898
|
||||
|
||||
hero.pinned=\u7f6e\u9876
|
||||
hero.posts=\u6587\u7ae0
|
||||
|
||||
article.previousPost=\u4e0a\u4e00\u7bc7
|
||||
article.nextPost=\u4e0b\u4e00\u7bc7
|
||||
article.toc=\u76ee\u5f55
|
||||
article.wordCount=\u5b57
|
||||
article.readTime=\u5206\u949f
|
||||
article.views=\u9605\u8bfb
|
||||
article.relatedPosts=\u76f8\u5173\u63a8\u8350
|
||||
|
||||
share.title=\u5206\u4eab
|
||||
share.qq=QQ
|
||||
share.wechat=\u5fae\u4fe1
|
||||
share.weibo=\u5fae\u535a
|
||||
share.twitter=X
|
||||
share.facebook=Facebook
|
||||
share.telegram=Telegram
|
||||
share.copyLink=\u590d\u5236\u94fe\u63a5
|
||||
share.copied=\u5df2\u590d\u5236
|
||||
share.qrcode=\u4e8c\u7ef4\u7801
|
||||
share.wechatQR=\u5fae\u4fe1\u626b\u7801\u5206\u4eab
|
||||
share.wechatHint=\u6253\u5f00\u5fae\u4fe1\uff0c\u626b\u63cf\u4e8c\u7ef4\u7801\u5373\u53ef\u5206\u4eab
|
||||
|
||||
archives.title=\u5f52\u6863
|
||||
|
||||
categories.title=\u5206\u7c7b
|
||||
|
||||
tags.title=\u6807\u7b7e
|
||||
|
||||
links.title=\u53cb\u94fe
|
||||
links.subtitle=\u5c71\u6c34\u4e00\u7a0b\uff0c\u4e09\u751f\u6709\u5e78
|
||||
|
||||
moments.title=\u52a8\u6001
|
||||
moments.noMoments=\u8fd8\u6ca1\u6709\u77ac\u95f4\uff0c\u5feb\u53bb\u8bb0\u5f55\u5f53\u4e0b\u7684\u5fc3\u60c5\u5427 \U0001f319
|
||||
|
||||
photos.title=\u76f8\u518c
|
||||
|
||||
friends.title=\u53cb\u4eba\u5708
|
||||
|
||||
equipment.title=\u88c5\u5907
|
||||
|
||||
steam.title=Steam
|
||||
steam.homepage=Steam \u4e3b\u9875
|
||||
steam.level=\u7b49\u7ea7
|
||||
steam.ownedGames=\u62e5\u6709\u6e38\u620f
|
||||
steam.totalPlaytime=\u603b\u6e38\u73a9\u65f6\u957f
|
||||
steam.recentPlaytime=\u8fd1\u4e24\u5468
|
||||
steam.badges=\u5fbd\u7ae0
|
||||
steam.hours=\u5c0f\u65f6
|
||||
steam.recentlyPlayed=\u6700\u8fd1\u6e38\u73a9
|
||||
steam.gameLibrary=\u6e38\u620f\u5e93
|
||||
steam.lastPlayed=\u6700\u540e\u6e38\u73a9 {0}
|
||||
|
||||
douban.title=\u8c46\u74e3
|
||||
douban.wish=\u60f3\u770b
|
||||
douban.done=\u770b\u8fc7
|
||||
douban.wishRead=\u60f3\u8bfb
|
||||
douban.reading=\u5728\u8bfb
|
||||
douban.listened=\u542c\u8fc7
|
||||
douban.wishPlay=\u60f3\u73a9
|
||||
douban.played=\u73a9\u8fc7
|
||||
douban.wishListen=\u60f3\u542c
|
||||
douban.listening=\u5728\u542c
|
||||
douban.playing=\u5728\u73a9
|
||||
douban.myRating=\u6211\u7684\uff1a
|
||||
|
||||
messageboard.title=\u7559\u8a00\u677f
|
||||
messageboard.subtitle=\u5728\u8fd9\u91cc\u7559\u4e0b\u4f60\u7684\u8db3\u8ff9 \U0001f319
|
||||
|
||||
footer.poweredBy=Powered by Halo
|
||||
|
||||
error.404.title=\u9875\u9762\u672a\u627e\u5230
|
||||
error.404.desc=\u4f60\u5bfb\u627e\u7684\u9875\u9762\u4f3c\u4e4e\u5df2\u7ecf\u8ff7\u8def\u4e86
|
||||
error.404.home=\u8fd4\u56de\u9996\u9875
|
||||
error.404.back=\u8fd4\u56de\u4e0a\u9875
|
||||
error.500.title=\u670d\u52a1\u5668\u9519\u8bef
|
||||
error.500.desc=\u5c0f\u5c9b\u9047\u5230\u4e86\u4e00\u4e9b\u95ee\u9898\uff0c\u8bf7\u7a0d\u540e\u518d\u8bd5
|
||||
error.500.home=\u8fd4\u56de\u9996\u9875
|
||||
error.500.back=\u8fd4\u56de\u4e0a\u9875
|
||||
|
||||
sidebar.authorInfo=\u535a\u4e3b\u4fe1\u606f
|
||||
sidebar.latestPosts=\u6700\u65b0\u6587\u7ae0
|
||||
sidebar.recommendedPosts=\u63a8\u8350\u6587\u7ae0
|
||||
sidebar.timeProgress=\u65f6\u5149\u8fdb\u5ea6
|
||||
sidebar.dayProgress=\u65e5
|
||||
sidebar.monthProgress=\u6708
|
||||
sidebar.yearProgress=\u5e74
|
||||
sidebar.categories=\u5206\u7c7b
|
||||
sidebar.tags=\u6807\u7b7e
|
||||
|
||||
comment.style=\u6e29\u6696
|
||||
comment.showAvatar=\u663e\u793a\u5934\u50cf
|
||||
@@ -1,130 +0,0 @@
|
||||
common.home=\u9996\u9801
|
||||
common.search=\u641c\u5c0b
|
||||
common.themeSwitch=\u5207\u63db\u4e3b\u984c
|
||||
common.menu=\u9078\u55ae
|
||||
common.close=\u95dc\u9589
|
||||
common.toc=\u76ee\u9304
|
||||
common.openToc=\u6253\u958b\u76ee\u9304
|
||||
common.closeToc=\u95dc\u9589\u76ee\u9304
|
||||
common.newer=\u8f03\u65b0
|
||||
common.older=\u8f03\u820a
|
||||
common.loading=\u8f09\u5165\u4e2d...
|
||||
common.allLoaded=\u5df2\u8f09\u5165\u5168\u90e8\u6587\u7ae0
|
||||
common.noPosts=\u66ab\u7121\u6587\u7ae0
|
||||
common.postCount=\u5171 {0} \u7bc7\u6587\u7ae0
|
||||
common.readMore=\u95b1\u8b80\u66f4\u591a
|
||||
common.words=\u5b57
|
||||
common.readTime=\u5206\u9418
|
||||
common.views=\u95b1\u8b80
|
||||
common.like=\u6309\u8b9a
|
||||
common.pagination=\u5206\u9801
|
||||
common.toggleMenu=\u5207\u63db\u9078\u55ae
|
||||
common.allMomentsLoaded=\u5df2\u8f09\u5165\u5168\u90e8\u77ac\u9593
|
||||
common.categoryCount=\u5171 {0} \u500b\u5206\u985e
|
||||
common.tagCount=\u5171 {0} \u500b\u6a19\u7c64
|
||||
common.all=\u5168\u90e8
|
||||
common.noPhotos=\u66ab\u7121\u5716\u7247
|
||||
common.noPhotosInGroup=\u8a72\u5206\u7d44\u66ab\u7121\u5716\u7247
|
||||
common.pluginError=\u5916\u639b\u7a0b\u5f0f\u8f09\u5165\u7570\u5e38\uff0c\u8acb\u6aa2\u67e5\u5916\u639b\u7a0b\u5f0f\u72c0\u614b
|
||||
common.installPlugin=\u8a72\u529f\u80fd\u9700\u8981\u5b89\u88dd\u300c{0}\u300d\u5916\u639b\u7a0b\u5f0f\uff0c<a href="{1}">\u524d\u5f80\u61c9\u7528\u5e02\u5834\u5b89\u88dd</a>
|
||||
common.featured=\u7cbe\u9078
|
||||
common.latestPosts=\u6700\u65b0\u6587\u7ae0
|
||||
common.timeline=\u6642\u9593\u7dda
|
||||
common.viewAll=\u67e5\u770b\u5168\u90e8
|
||||
|
||||
navbar.search=\u641c\u5c0b
|
||||
navbar.themeSwitch=\u5207\u63db\u4e3b\u984c
|
||||
|
||||
hero.pinned=\u7f6e\u9802
|
||||
hero.posts=\u6587\u7ae0
|
||||
|
||||
article.previousPost=\u4e0a\u4e00\u7bc7
|
||||
article.nextPost=\u4e0b\u4e00\u7bc7
|
||||
article.toc=\u76ee\u9304
|
||||
article.wordCount=\u5b57
|
||||
article.readTime=\u5206\u9418
|
||||
article.views=\u95b1\u8b80
|
||||
article.relatedPosts=\u76f8\u95dc\u63a8\u85a6
|
||||
|
||||
share.title=\u5206\u4eab
|
||||
share.qq=QQ
|
||||
share.wechat=\u5fae\u4fe1
|
||||
share.weibo=\u5fae\u535a
|
||||
share.twitter=X
|
||||
share.facebook=Facebook
|
||||
share.telegram=Telegram
|
||||
share.copyLink=\u8907\u88fd\u9023\u7d50
|
||||
share.copied=\u5df2\u8907\u88fd
|
||||
share.qrcode=\u4e8c\u7dad\u78bc
|
||||
share.wechatQR=\u5fae\u4fe1\u6383\u78bc\u5206\u4eab
|
||||
share.wechatHint=\u6253\u958b\u5fae\u4fe1\uff0c\u6383\u63cf\u4e8c\u7dad\u78bc\u5373\u53ef\u5206\u4eab
|
||||
|
||||
archives.title=\u6b78\u6a94
|
||||
|
||||
categories.title=\u5206\u985e
|
||||
|
||||
tags.title=\u6a19\u7c64
|
||||
|
||||
links.title=\u53cb\u93c8
|
||||
links.subtitle=\u5c71\u6c34\u4e00\u7a0b\uff0c\u4e09\u751f\u6709\u5e78
|
||||
|
||||
moments.title=\u52d5\u614b
|
||||
moments.noMoments=\u9084\u6c92\u6709\u77ac\u9593\uff0c\u5feb\u53bb\u8a18\u9304\u7576\u4e0b\u7684\u5fc3\u60c5\u5427 \U0001f319
|
||||
|
||||
photos.title=\u76f8\u7c3f
|
||||
|
||||
friends.title=\u53cb\u4eba\u5708
|
||||
|
||||
equipment.title=\u88dd\u5099
|
||||
|
||||
steam.title=Steam
|
||||
steam.homepage=Steam \u4e3b\u9801
|
||||
steam.level=\u7b49\u7d1a
|
||||
steam.ownedGames=\u64c1\u6709\u904a\u6232
|
||||
steam.totalPlaytime=\u7e3d\u904a\u73a9\u6642\u9577
|
||||
steam.recentPlaytime=\u8fd1\u5169\u9031
|
||||
steam.badges=\u5fbd\u7ae0
|
||||
steam.hours=\u5c0f\u6642
|
||||
steam.recentlyPlayed=\u6700\u8fd1\u904a\u73a9
|
||||
steam.gameLibrary=\u904a\u6232\u5eab
|
||||
steam.lastPlayed=\u6700\u5f8c\u904a\u73a9 {0}
|
||||
|
||||
douban.title=\u8c46\u74e3
|
||||
douban.wish=\u60f3\u770b
|
||||
douban.done=\u770b\u904e
|
||||
douban.wishRead=\u60f3\u8b80
|
||||
douban.reading=\u5728\u8b80
|
||||
douban.listened=\u807d\u904e
|
||||
douban.wishPlay=\u60f3\u73a9
|
||||
douban.played=\u73a9\u904e
|
||||
douban.wishListen=\u60f3\u807d
|
||||
douban.listening=\u5728\u807d
|
||||
douban.playing=\u5728\u73a9
|
||||
douban.myRating=\u6211\u7684\uff1a
|
||||
|
||||
messageboard.title=\u7559\u8a00\u677f
|
||||
messageboard.subtitle=\u5728\u9019\u88cf\u7559\u4e0b\u4f60\u7684\u8db3\u8de1 \U0001f319
|
||||
|
||||
footer.poweredBy=Powered by Halo
|
||||
|
||||
error.404.title=\u9801\u9762\u672a\u627e\u5230
|
||||
error.404.desc=\u4f60\u5c0b\u627e\u7684\u9801\u9762\u4f3c\u4e4e\u5df2\u7d93\u8ff7\u8def\u4e86
|
||||
error.404.home=\u8fd4\u56de\u9996\u9801
|
||||
error.404.back=\u8fd4\u56de\u4e0a\u9801
|
||||
error.500.title=\u4f3a\u670d\u5668\u932f\u8aa4
|
||||
error.500.desc=\u5c0f\u5cf6\u9047\u5230\u4e86\u4e00\u4e9b\u554f\u984c\uff0c\u8acb\u7a0d\u5f8c\u518d\u8a66
|
||||
error.500.home=\u8fd4\u56de\u9996\u9801
|
||||
error.500.back=\u8fd4\u56de\u4e0a\u9801
|
||||
|
||||
sidebar.authorInfo=\u535a\u4e3b\u8cc7\u8a0a
|
||||
sidebar.latestPosts=\u6700\u65b0\u6587\u7ae0
|
||||
sidebar.recommendedPosts=\u63a8\u85a6\u6587\u7ae0
|
||||
sidebar.timeProgress=\u6642\u5149\u9032\u5ea6
|
||||
sidebar.dayProgress=\u65e5
|
||||
sidebar.monthProgress=\u6708
|
||||
sidebar.yearProgress=\u5e74
|
||||
sidebar.categories=\u5206\u985e
|
||||
sidebar.tags=\u6a19\u7c64
|
||||
|
||||
comment.style=\u6eab\u6696
|
||||
comment.showAvatar=\u986f\u793a\u982d\u50cf
|
||||
+4
-13
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"version": "1.1.1",
|
||||
"version": "1.1.5",
|
||||
"type": "module",
|
||||
"engines": {
|
||||
"node": ">=22.12.0"
|
||||
@@ -23,24 +23,15 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
"pnpm": {
|
||||
"onlyBuiltDependencies": [
|
||||
"@parcel/watcher",
|
||||
"sharp"
|
||||
]
|
||||
},
|
||||
"dependencies": {
|
||||
"@astrojs/vue": "^6.0.1",
|
||||
"astro": "^6.1.4",
|
||||
"lightgallery": "^2.9.0",
|
||||
"vue": "^3.5.32"
|
||||
"lightgallery": "^2.9.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@iconify-json/mingcute": "^1.2.7",
|
||||
"@halo-dev/theme-package-cli": "^1.1.1",
|
||||
"nodemon": "^3.1.14",
|
||||
"prettier": "3.8.1",
|
||||
"prettier-plugin-astro": "0.14.1",
|
||||
"sass": "^1.87.0",
|
||||
"unplugin-icons": "^23.0.1"
|
||||
"sass": "^1.87.0"
|
||||
}
|
||||
}
|
||||
|
||||
Generated
+682
-1061
File diff suppressed because it is too large
Load Diff
+1
-1
@@ -1,5 +1,5 @@
|
||||
allowBuilds:
|
||||
'@parcel/watcher': true
|
||||
"@parcel/watcher": true
|
||||
esbuild: true
|
||||
sharp: true
|
||||
onlyBuiltDependencies:
|
||||
|
||||
@@ -1,455 +0,0 @@
|
||||
<!doctype html>
|
||||
<html lang="zh-cn">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<title>字体分包构建报告</title>
|
||||
<script src="https://unpkg.com/vue@3/dist/vue.global.js"></script>
|
||||
<link
|
||||
rel="stylesheet"
|
||||
href="https://fastly.jsdelivr.net/npm/vant@4.9.15/lib/index.css"
|
||||
/>
|
||||
<link
|
||||
href="https://unpkg.com/vue3-easy-data-table@1.5.47/dist/style.css"
|
||||
rel="stylesheet"
|
||||
/>
|
||||
<link rel="stylesheet" href="./result.css" />
|
||||
<style>
|
||||
body {
|
||||
overflow-wrap: break-word;
|
||||
font-size: 18px;
|
||||
background-color: #edf0f3;
|
||||
--easy-table-border: 0;
|
||||
}
|
||||
main {
|
||||
padding: 20px;
|
||||
margin-top: var(--van-nav-bar-height);
|
||||
margin-bottom: var(--van-tabbar-height);
|
||||
}
|
||||
.light-blue {
|
||||
--easy-table-body-row-font-color: #427cb7;
|
||||
}
|
||||
.light-red {
|
||||
--easy-table-body-row-font-color: #b74273;
|
||||
}
|
||||
.hint {
|
||||
display: block;
|
||||
text-align: center;
|
||||
color: gray;
|
||||
margin: 1rem;
|
||||
}
|
||||
.scroller {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.user {
|
||||
height: 32%;
|
||||
padding: 0 12px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div id="app">
|
||||
<van-nav-bar
|
||||
:title="reporter.state?.css?.family +' 分包报告'"
|
||||
right-text="Github"
|
||||
@click-right="toGithub"
|
||||
style="position: fixed; width: 100%; top: 0"
|
||||
></van-nav-bar>
|
||||
<div v-if="reporter.isLoading">加载中</div>
|
||||
<div v-if="reporter.isError">加载错误</div>
|
||||
|
||||
<main :style="style">
|
||||
<van-cell-group inset title="样式控制">
|
||||
<van-field
|
||||
v-model="fontSize"
|
||||
label="字体大小"
|
||||
placeholder="请输入字体大小"
|
||||
></van-field>
|
||||
<van-field
|
||||
v-model="fontWeight"
|
||||
label="字体重量"
|
||||
placeholder="请输入字重"
|
||||
></van-field>
|
||||
</van-cell-group>
|
||||
<!-- 汇总信息 -->
|
||||
<display-chart
|
||||
v-if="reporter.isOk() && activePage === 0"
|
||||
:reporter="reporter.state"
|
||||
></display-chart>
|
||||
<!-- 字体信息表格 -->
|
||||
<name-table
|
||||
v-if="reporter.isOk() && activePage === 1"
|
||||
:reporter="reporter.state"
|
||||
></name-table>
|
||||
<!-- 单包查询 -->
|
||||
<pkg-list
|
||||
v-if="reporter.isOk() && activePage === 2"
|
||||
:reporter="reporter.state"
|
||||
></pkg-list>
|
||||
<div class="hint">
|
||||
<a href="https://chinese-font.netlify.app">中文网字计划</a>
|
||||
<span> cn-font-split 生成 </span>
|
||||
</div>
|
||||
</main>
|
||||
|
||||
<!-- 底部导航栏 -->
|
||||
<van-tabbar v-model="activePage">
|
||||
<van-tabbar-item icon="home-o">汇总</van-tabbar-item>
|
||||
<van-tabbar-item icon="home-o">字体信息</van-tabbar-item>
|
||||
<van-tabbar-item icon="search">分包</van-tabbar-item>
|
||||
</van-tabbar>
|
||||
</div>
|
||||
</body>
|
||||
<script src="https://unpkg.com/vue@3.4.33"></script>
|
||||
<script src="https://unpkg.com/vant@4/lib/vant.min.js"></script>
|
||||
<script src="https://unpkg.com/vue3-easy-data-table@1.5.47/dist/vue3-easy-data-table.umd.js"></script>
|
||||
<script src="https://unpkg.com/echarts@5.5.1"></script>
|
||||
<script src="https://unpkg.com/vue-echarts@7.0.3"></script>
|
||||
<script src="https://unpkg.com/protobufjs@7.4.0/dist/protobuf.min.js"></script>
|
||||
<script>
|
||||
const { createApp } = Vue;
|
||||
const app = createApp({
|
||||
data() {
|
||||
return {
|
||||
reporter: PromiseToState(loadReport),
|
||||
activePage: 0,
|
||||
fontWeight: '',
|
||||
fontSize: 16,
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
toGithub() {
|
||||
window.open('https://github.com/KonghaYao/cn-font-split');
|
||||
},
|
||||
},
|
||||
async mounted() {
|
||||
const state = await this.reporter.refetch();
|
||||
this.fontWeight = state.css.weight;
|
||||
},
|
||||
computed: {
|
||||
style() {
|
||||
const size = this.fontSize < 12 ? 12 : this.fontSize;
|
||||
return (
|
||||
`font-family: '${this.reporter.state?.css?.family}';` +
|
||||
`font-weight: ${this.fontWeight};` +
|
||||
`--van-cell-font-size: ${size}px;`
|
||||
);
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
/** 封装Promise为状态对象 **/
|
||||
function PromiseToState(fn) {
|
||||
return {
|
||||
state: {},
|
||||
isOk() {
|
||||
return !this.isLoading && !this.isError && this.state;
|
||||
},
|
||||
isLoading: false,
|
||||
isError: false,
|
||||
async refetch() {
|
||||
this.isLoading = true;
|
||||
this.isError = false;
|
||||
try {
|
||||
this.state = await fn();
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
this.isError = true;
|
||||
}
|
||||
this.isLoading = false;
|
||||
return this.state;
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
/** 载入构建出来的二进制报告文件 **/
|
||||
async function loadReport() {
|
||||
return new Promise((resolve, reject) => {
|
||||
protobuf.load('./index.proto', (err, root) => {
|
||||
if (err) return reject(err);
|
||||
fetch('./reporter.bin')
|
||||
.then((res) => res.arrayBuffer())
|
||||
.then((buf) => {
|
||||
const OutputReport = root.lookup('OutputReport');
|
||||
const data = OutputReport.decode(
|
||||
new Uint8Array(buf),
|
||||
);
|
||||
console.log(data);
|
||||
resolve(data);
|
||||
})
|
||||
.catch(reject);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
globalThis.app = app;
|
||||
app.use(vant);
|
||||
app.component('v-chart', VueECharts);
|
||||
app.component('easy-data-table', window['vue3-easy-data-table']);
|
||||
</script>
|
||||
|
||||
<!-- 汇总数据 -->
|
||||
<template id="display-chart">
|
||||
<van-cell-group inset title="构建信息">
|
||||
<van-cell title="构建工具" value="cn-font-split"></van-cell>
|
||||
<van-cell title="构建版本" :value="reporter.version"></van-cell>
|
||||
<van-cell title="构建平台" :value="reporter.platform"></van-cell>
|
||||
</van-cell-group>
|
||||
<van-cell-group inset title="构建详情">
|
||||
<div style="height: 300px; width: 400px">
|
||||
<v-chart :option="pieOptions"></v-chart>
|
||||
</div>
|
||||
<div style="height: 400px; width: 100%">
|
||||
<v-chart :option="option"></v-chart>
|
||||
</div>
|
||||
</van-cell-group>
|
||||
</template>
|
||||
<script>
|
||||
const tooltip = {
|
||||
trigger: 'axis',
|
||||
axisPointer: {
|
||||
type: 'cross',
|
||||
crossStyle: {
|
||||
color: '#999',
|
||||
},
|
||||
},
|
||||
};
|
||||
app.component('display-chart', {
|
||||
template: '#display-chart',
|
||||
props: {
|
||||
reporter: Object,
|
||||
},
|
||||
computed: {
|
||||
option() {
|
||||
const data = this.reporter.subsetDetail ?? [];
|
||||
return {
|
||||
title: {
|
||||
text: '构建耗时',
|
||||
x: 'center',
|
||||
},
|
||||
xAxis: { type: 'category' },
|
||||
tooltip,
|
||||
yAxis: [
|
||||
{
|
||||
type: 'value',
|
||||
|
||||
name: 'time / ms',
|
||||
},
|
||||
{
|
||||
type: 'value',
|
||||
name: 'bytes / KB',
|
||||
},
|
||||
],
|
||||
series: [
|
||||
{
|
||||
name: 'time',
|
||||
type: 'bar',
|
||||
data: data.map((i) => i.duration),
|
||||
},
|
||||
{
|
||||
name: 'bytes',
|
||||
type: 'line',
|
||||
data: data.map((i) =>
|
||||
(i.bytes / 1024).toFixed(2),
|
||||
),
|
||||
},
|
||||
],
|
||||
};
|
||||
},
|
||||
pieOptions() {
|
||||
const message = this.reporter.bundleMessage;
|
||||
if (!message) return {};
|
||||
return {
|
||||
title: {
|
||||
text: '构建大小对比',
|
||||
x: 'center',
|
||||
},
|
||||
series: [
|
||||
{
|
||||
type: 'pie',
|
||||
radius: ['45%', '60%'],
|
||||
label: {
|
||||
formatter(b, c) {
|
||||
return `${b.name}\n${(
|
||||
b.value /
|
||||
(1024 * 1024)
|
||||
).toFixed(2)}MB`;
|
||||
},
|
||||
},
|
||||
data: [
|
||||
{
|
||||
value: message.bundledBytes,
|
||||
name: 'bundled',
|
||||
},
|
||||
{
|
||||
value: message.originBytes,
|
||||
name: 'origin',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
type: 'pie',
|
||||
radius: [0, '30%'],
|
||||
label: {
|
||||
formatter(b, c) {
|
||||
return `${b.name}\n${b.value}`;
|
||||
},
|
||||
},
|
||||
data: [
|
||||
{
|
||||
value: message.bundledSize,
|
||||
name: 'bundled',
|
||||
},
|
||||
{
|
||||
value: message.originSize,
|
||||
name: 'origin',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
};
|
||||
},
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<!-- 字体信息表格 -->
|
||||
<template id="name-table">
|
||||
<van-cell-group inset title="CSS 信息">
|
||||
<van-cell
|
||||
title="font-family"
|
||||
:value="reporter.css?.family"
|
||||
></van-cell>
|
||||
<van-cell
|
||||
title="font-weight"
|
||||
:value="reporter.css?.weight"
|
||||
></van-cell>
|
||||
<van-cell
|
||||
title="font-style"
|
||||
:value="reporter.css?.style"
|
||||
></van-cell>
|
||||
<van-cell
|
||||
title="font-display"
|
||||
:value="reporter.css?.display"
|
||||
></van-cell>
|
||||
</van-cell-group>
|
||||
<van-cell-group inset title="字体信息">
|
||||
<easy-data-table
|
||||
:headers="columns"
|
||||
:items="reporter.nameTable??[]"
|
||||
:rows-per-page="1000"
|
||||
:body-row-class-name="useColor"
|
||||
></easy-data-table>
|
||||
</van-cell-group>
|
||||
</template>
|
||||
<script>
|
||||
app.component('name-table', {
|
||||
template: '#name-table',
|
||||
props: {
|
||||
reporter: Object,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
color: {
|
||||
Windows: 'light-blue',
|
||||
Macintosh: 'light-red',
|
||||
},
|
||||
columns: [
|
||||
{
|
||||
text: '语言',
|
||||
value: 'language',
|
||||
sortable: true,
|
||||
|
||||
minWidth: '36px',
|
||||
},
|
||||
{
|
||||
text: '平台',
|
||||
value: 'platform',
|
||||
sortable: true,
|
||||
|
||||
minWidth: '20px',
|
||||
},
|
||||
{
|
||||
text: '名称',
|
||||
value: 'name',
|
||||
sortable: true,
|
||||
minWidth: '50px',
|
||||
},
|
||||
{
|
||||
text: '值',
|
||||
value: 'value',
|
||||
sortable: true,
|
||||
width: '200px',
|
||||
},
|
||||
],
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
useColor(item) {
|
||||
return this.color[item.platform];
|
||||
},
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<!-- 每个包的数据 -->
|
||||
<template id="pkg-list">
|
||||
<van-cell-group inset>
|
||||
<van-field
|
||||
v-model="search"
|
||||
label="搜索"
|
||||
placeholder="请输入关键字"
|
||||
></van-field>
|
||||
</van-cell-group>
|
||||
|
||||
<van-cell-group inset title="字体分包详情">
|
||||
<v-list
|
||||
ref="list"
|
||||
:items="items"
|
||||
:first-render="10"
|
||||
style="height: 80vh"
|
||||
>
|
||||
<template #default="{ item, index }">
|
||||
<div style="padding: 0 1rem">
|
||||
<h2 :style="{ width: item.width }">
|
||||
ITEM: {{ index }} - {{ item.hash }}
|
||||
</h2>
|
||||
<span> {{String.fromCodePoint(...item.chars)}} </span>
|
||||
</div>
|
||||
</template>
|
||||
</v-list>
|
||||
</van-cell-group>
|
||||
</template>
|
||||
<script src="https://unpkg.com/@virtual-list/vue/dist/v3/index.iife.js"></script>
|
||||
<script>
|
||||
app.component('v-list', virtualListVue);
|
||||
app.component('pkg-list', {
|
||||
template: '#pkg-list',
|
||||
props: {
|
||||
reporter: Object,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
activeNames: [],
|
||||
search: '',
|
||||
};
|
||||
},
|
||||
mounted() {},
|
||||
computed: {
|
||||
items() {
|
||||
const list = this.reporter.subsetDetail ?? [];
|
||||
if (this.search) {
|
||||
return list.filter((i) => i.hash.includes(this.search));
|
||||
}
|
||||
return list;
|
||||
},
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<script>
|
||||
app.mount('#app');
|
||||
</script>
|
||||
</html>
|
||||
@@ -1,117 +0,0 @@
|
||||
syntax = "proto3";
|
||||
|
||||
package api_interface;
|
||||
|
||||
message InputTemplate {
|
||||
bytes input = 1; // ttf/woff2 文件的 buffer
|
||||
optional string out_dir = 2; // 切割后放置文件的文件夹
|
||||
|
||||
// ====== 构建产物配置 ======
|
||||
|
||||
optional CssProperties css = 5; // CSS 配置
|
||||
optional string target_type = 6; // 目标类型
|
||||
repeated bytes subsets = 7; // 子集
|
||||
optional int32 chunk_size = 9; // 包大小
|
||||
optional float chunk_size_tolerance = 10; // 包大小容差
|
||||
optional int32 max_allow_subsets_count = 11; // 最大允许子集数量
|
||||
|
||||
optional bool test_html = 13; // 是否生成测试 HTML
|
||||
optional bool reporter = 14; // 是否生成 reporter.bin
|
||||
optional PreviewImage preview_image = 15; // 预览图像
|
||||
|
||||
optional string rename_output_font = 18; // 重命名输出字体
|
||||
optional string build_mode = 20; // TODO 构建模式
|
||||
|
||||
// ====== 预分包优化项配置 =======
|
||||
|
||||
optional bool language_areas = 8; // 是否进行语言区域优化
|
||||
optional bool multi_threads = 21; // TODO 是否使用多线程
|
||||
optional bool font_feature = 22; // 是否启用字体特性
|
||||
optional bool reduce_mins = 23; // 是否减少最小分包,
|
||||
optional bool auto_subset = 24; // 是否自动子集化
|
||||
optional bool subset_remain_chars = 25; // 是否自动添加没有声明的字符
|
||||
|
||||
// CSS 属性配置
|
||||
message CssProperties {
|
||||
optional string font_family = 1; // 字体家族名称
|
||||
optional string font_weight = 2; // 字体粗细
|
||||
optional string font_style = 3; // 字体样式
|
||||
optional string font_display = 4; // 字体显示方式
|
||||
repeated string local_family = 5; // 本地字体家族名称
|
||||
repeated PolyfillType polyfill = 6; // Polyfill 类型
|
||||
|
||||
// 注释属性
|
||||
|
||||
optional bool comment_base = 11; // 基础注释
|
||||
optional bool comment_name_table = 12; // 名称表注释
|
||||
optional bool comment_unicodes = 13; // Unicode 注释
|
||||
optional bool compress = 8; // 是否压缩
|
||||
optional string file_name = 9; // 文件名
|
||||
}
|
||||
|
||||
// Polyfill 类型
|
||||
message PolyfillType {
|
||||
string name = 1; // 名称
|
||||
string format = 2; // 格式
|
||||
}
|
||||
|
||||
// 预览图像
|
||||
message PreviewImage {
|
||||
string text = 1; // 会显示在 svg 中的文本
|
||||
string name = 2; // svg 文件的名称
|
||||
}
|
||||
}
|
||||
|
||||
enum EventName {
|
||||
UNSPECIFIED = 0;
|
||||
OUTPUT_DATA = 1; // "output_data"
|
||||
END = 2; // "end"
|
||||
}
|
||||
|
||||
message EventMessage {
|
||||
EventName event = 1;
|
||||
string message = 2;
|
||||
optional bytes data = 3;
|
||||
}
|
||||
message MultiMessages {
|
||||
repeated EventMessage messages = 1;
|
||||
}
|
||||
message OutputReport {
|
||||
string version = 1; // 版本号
|
||||
Css css = 2; // CSS相关信息,可以直接用
|
||||
string platform = 3; // 平台信息,rust 的构建平台
|
||||
|
||||
BundleMessage bundle_message = 24; // 构建消息
|
||||
repeated NameTable name_table = 25; // name 表信息
|
||||
repeated SubsetDetail subset_detail = 26; // 子集详情
|
||||
|
||||
message NameTable {
|
||||
string platform = 1; // 平台
|
||||
string language = 2; // 语言
|
||||
string name = 3; // 名称
|
||||
string value = 4; // 值
|
||||
}
|
||||
|
||||
message SubsetDetail {
|
||||
uint32 id = 1; // ID
|
||||
string hash = 2; // 哈希值
|
||||
string file_name = 6; // 文件名称
|
||||
uint32 bytes = 3; // 字节数
|
||||
repeated uint32 chars = 4; // 字符数组
|
||||
uint32 duration = 5; // 构建时间
|
||||
}
|
||||
|
||||
message BundleMessage {
|
||||
uint32 origin_size = 1; // 原始大小
|
||||
uint32 bundled_size = 2; // 产物大小
|
||||
uint32 origin_bytes = 3; // 原始字节数
|
||||
uint32 bundled_bytes = 4; // 产物字节数
|
||||
}
|
||||
|
||||
message Css {
|
||||
string family = 1; // 字体家族
|
||||
string style = 2; // 样式
|
||||
string weight = 3; // 字重
|
||||
string display = 4; // 显示方式
|
||||
}
|
||||
}
|
||||
Binary file not shown.
@@ -1,5 +1,5 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh">
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
@@ -126,6 +126,5 @@
|
||||
<a class="wi-error__back" href="javascript:history.back()">返回上页</a>
|
||||
</div>
|
||||
</div>
|
||||
<script>(function(){var lang=navigator.language||'';var locale=lang.startsWith('zh-TW')||lang.startsWith('zh-HK')?'zh_TW':lang.startsWith('zh')?'zh_CN':'en';var i18n={zh_CN:{title:'页面走丢了',desc:'你寻找的页面似乎不在这个小岛上',home:'回到首页',back:'返回上页',pageTitle:'页面走丢了'},zh_TW:{title:'頁面走丟了',desc:'你尋找的頁面似乎不在這個小島上',home:'回到首頁',back:'返回上頁',pageTitle:'頁面走丟了'},en:{title:'Page Not Found',desc:'The page you are looking for seems to have wandered off this island',home:'Back to Home',back:'Go Back',pageTitle:'Page Not Found'}};var t=i18n[locale];var el=document.querySelector('.wi-error__title');if(el)el.textContent=t.title;var desc=document.querySelector('.wi-error__desc');if(desc)desc.textContent=t.desc;var home=document.querySelector('.wi-error__home');if(home)home.textContent=t.home;var back=document.querySelector('.wi-error__back');if(back)back.textContent=t.back;document.title=t.pageTitle+' - WarmIsland';})();</script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh">
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
@@ -126,6 +126,5 @@
|
||||
<a class="wi-error__back" href="javascript:history.back()">返回上页</a>
|
||||
</div>
|
||||
</div>
|
||||
<script>(function(){var lang=navigator.language||'';var locale=lang.startsWith('zh-TW')||lang.startsWith('zh-HK')?'zh_TW':lang.startsWith('zh')?'zh_CN':'en';var i18n={zh_CN:{title:'服务器开小差了',desc:'小岛遇到了一些问题,稍后再来看看吧',home:'回到首页',back:'返回上页',pageTitle:'服务器开小差了'},zh_TW:{title:'伺服器開小差了',desc:'小島遇到了一些問題,稍後再來看看吧',home:'回到首頁',back:'返回上頁',pageTitle:'伺服器開小差了'},en:{title:'Server Error',desc:'The island encountered some issues, please try again later',home:'Back to Home',back:'Go Back',pageTitle:'Server Error'}};var t=i18n[locale];var el=document.querySelector('.wi-error__title');if(el)el.textContent=t.title;var desc=document.querySelector('.wi-error__desc');if(desc)desc.textContent=t.desc;var home=document.querySelector('.wi-error__home');if(home)home.textContent=t.home;var back=document.querySelector('.wi-error__back');if(back)back.textContent=t.back;document.title=t.pageTitle+' - WarmIsland';})();</script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -1,21 +0,0 @@
|
||||
<ul class="wi-post-feed" role="list">
|
||||
<li th:each="post : ${posts.items}" class="wi-post-feed__item">
|
||||
<a class="wi-post-feed__link" th:href="@{${post.status.permalink}}">
|
||||
<div class="wi-post-feed__image-wrap" th:unless="${#strings.isEmpty(post.spec.cover)}">
|
||||
<img
|
||||
class="wi-post-feed__image"
|
||||
th:src="${post.spec.cover}"
|
||||
th:srcset="|${thumbnail.gen(post.spec.cover, 's')} 400w,
|
||||
${thumbnail.gen(post.spec.cover, 'm')} 800w,
|
||||
${thumbnail.gen(post.spec.cover, 'l')} 1200w|"
|
||||
sizes="(max-width: 768px) 100vw, 400px"
|
||||
/>
|
||||
</div>
|
||||
<div class="wi-post-feed__body">
|
||||
<p class="wi-post-feed__date" th:text="${#dates.format(post.spec.publishTime, 'yyyy-MM-dd')}"></p>
|
||||
<h2 class="wi-post-feed__title" th:text="${post.spec.title}"></h2>
|
||||
<p class="wi-post-feed__excerpt" th:text="${post.status.excerpt}"></p>
|
||||
</div>
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
+288
-12
@@ -28,20 +28,63 @@ spec:
|
||||
- $formkit: text
|
||||
name: label_archives_title
|
||||
label: 归档页标题
|
||||
- group: welcome_popup
|
||||
label: 欢迎弹窗
|
||||
formSchema:
|
||||
- $formkit: switch
|
||||
name: welcome_popup_enabled
|
||||
id: welcome_popup_enabled
|
||||
label: 启用欢迎弹窗
|
||||
value: false
|
||||
help: 首页显示"欢迎来自 XX·XX·XX 的朋友"的浮动提示弹窗,基于访客 IP 地理定位
|
||||
- $formkit: select
|
||||
name: language
|
||||
label: 语言
|
||||
name: welcome_popup_position
|
||||
id: welcome_popup_position
|
||||
label: 弹窗位置
|
||||
options:
|
||||
- label: 跟随浏览器
|
||||
value: auto
|
||||
- label: 简体中文
|
||||
value: zh_CN
|
||||
- label: 繁體中文
|
||||
value: zh_TW
|
||||
- label: English
|
||||
value: en
|
||||
value: auto
|
||||
help: 设置主题界面语言,选择"跟随浏览器"将根据浏览器语言自动匹配
|
||||
- label: 顶端漂窗(顶部居中)
|
||||
value: top-center
|
||||
- label: 右上
|
||||
value: top-right
|
||||
- label: 左上
|
||||
value: top-left
|
||||
- label: 右下
|
||||
value: bottom-right
|
||||
- label: 左下
|
||||
value: bottom-left
|
||||
value: top-center
|
||||
if: "$get(welcome_popup_enabled).value === true"
|
||||
- $formkit: number
|
||||
name: welcome_popup_duration
|
||||
id: welcome_popup_duration
|
||||
label: 自动消失时间(秒)
|
||||
value: 5
|
||||
min: 3
|
||||
max: 60
|
||||
step: 1
|
||||
help: 弹窗自动消失的秒数,范围 3-60
|
||||
if: "$get(welcome_popup_enabled).value === true"
|
||||
- $formkit: url
|
||||
name: welcome_popup_api
|
||||
id: welcome_popup_api
|
||||
label: IP 定位主 API
|
||||
value: "https://myip.ipip.net/json"
|
||||
help: 返回 JSON 的 IP 定位主接口,默认 IPIP;留空使用默认值。支持 IPIP/腾讯/ipapi.co 等常见格式
|
||||
if: "$get(welcome_popup_enabled).value === true"
|
||||
- $formkit: url
|
||||
name: welcome_popup_fallback_api
|
||||
id: welcome_popup_fallback_api
|
||||
label: IP 定位备选 API
|
||||
value: "https://r.inews.qq.com/api/ip2city?otype=json"
|
||||
help: 主接口失败时尝试的备选接口,默认腾讯 ip2city;留空则不使用备选
|
||||
if: "$get(welcome_popup_enabled).value === true"
|
||||
- $formkit: switch
|
||||
name: welcome_popup_show_once
|
||||
id: welcome_popup_show_once
|
||||
label: 同会话仅显示一次
|
||||
value: true
|
||||
help: 开启后同一浏览器会话内不再重复弹出;关闭则每次访问首页都显示
|
||||
if: "$get(welcome_popup_enabled).value === true"
|
||||
- group: style
|
||||
label: 总体样式
|
||||
formSchema:
|
||||
@@ -102,6 +145,37 @@ spec:
|
||||
name: animation_cursor_glow
|
||||
label: 光感跟随
|
||||
value: false
|
||||
- $formkit: switch
|
||||
name: click_effect_enabled
|
||||
id: click_effect_enabled
|
||||
label: 鼠标点击效果
|
||||
value: false
|
||||
help: 在页面上点击鼠标时触发的视觉特效
|
||||
- $formkit: select
|
||||
name: click_effect_type
|
||||
id: click_effect_type
|
||||
label: 点击效果类型
|
||||
value: off
|
||||
if: "$get(click_effect_enabled).value === true"
|
||||
options:
|
||||
- label: 关闭
|
||||
value: off
|
||||
- label: 社会主义核心价值观
|
||||
value: values
|
||||
- label: 随机彩色爱心
|
||||
value: hearts
|
||||
- label: 烟花
|
||||
value: fireworks
|
||||
- label: 烟花波纹
|
||||
value: ripples
|
||||
- label: 粒子
|
||||
value: particles
|
||||
- label: 星星
|
||||
value: stars
|
||||
- label: 星星拖尾
|
||||
value: star-trail
|
||||
- label: 粒子拖尾
|
||||
value: particle-trail
|
||||
- group: navbar
|
||||
label: 导航栏
|
||||
formSchema:
|
||||
@@ -116,6 +190,35 @@ spec:
|
||||
- label: Float
|
||||
value: float
|
||||
value: glass
|
||||
- $formkit: select
|
||||
name: navbar_brand_type
|
||||
id: navbar_brand_type
|
||||
label: 左侧显示
|
||||
options:
|
||||
- label: 站点名称
|
||||
value: title
|
||||
- label: Logo
|
||||
value: logo
|
||||
value: title
|
||||
- $formkit: attachment
|
||||
name: navbar_logo
|
||||
id: navbar_logo
|
||||
label: 导航栏 Logo
|
||||
help: 选择"Logo"模式后在此上传图片;留空则使用基础设置中的自定义 Logo
|
||||
if: "$get(navbar_brand_type).value === 'logo'"
|
||||
- $formkit: select
|
||||
name: navbar_logo_radius
|
||||
id: navbar_logo_radius
|
||||
label: Logo 圆角
|
||||
options:
|
||||
- label: 直角
|
||||
value: square
|
||||
- label: 圆角
|
||||
value: rounded
|
||||
- label: 圆形
|
||||
value: circle
|
||||
value: rounded
|
||||
if: "$get(navbar_brand_type).value === 'logo'"
|
||||
- $formkit: switch
|
||||
name: navbar_show_search
|
||||
label: 显示搜索按钮
|
||||
@@ -188,9 +291,53 @@ spec:
|
||||
label: 自定义描述文案
|
||||
key: hero_custom_description
|
||||
if: "$get(hero_description_mode).value === 'custom'"
|
||||
- $formkit: switch
|
||||
name: hero_hitokoto_auto_refresh
|
||||
id: hero_hitokoto_auto_refresh
|
||||
label: 一言自动刷新
|
||||
value: false
|
||||
help: 开启后按设定间隔自动刷新一言内容
|
||||
- $formkit: select
|
||||
name: hero_hitokoto_refresh_interval
|
||||
label: 一言刷新间隔
|
||||
if: "$get(hero_hitokoto_auto_refresh).value === true"
|
||||
options:
|
||||
- label: 5 秒
|
||||
value: 5000
|
||||
- label: 10 秒
|
||||
value: 10000
|
||||
- label: 15 秒
|
||||
value: 15000
|
||||
- label: 20 秒
|
||||
value: 20000
|
||||
- label: 25 秒
|
||||
value: 25000
|
||||
- label: 30 秒
|
||||
value: 30000
|
||||
value: 10000
|
||||
- $formkit: attachment
|
||||
name: hero_background_image
|
||||
label: 背景图片
|
||||
- $formkit: attachment
|
||||
name: hero_background_image_dark
|
||||
label: 深色模式背景图片
|
||||
help: 深色模式下显示的背景图;留空则深色模式也使用上方背景图
|
||||
- $formkit: color
|
||||
name: hero_title_color_light
|
||||
label: 浅色模式大标题颜色
|
||||
help: Hero 大标题文字颜色(浅色模式);留空使用主题默认
|
||||
- $formkit: color
|
||||
name: hero_title_color_dark
|
||||
label: 深色模式大标题颜色
|
||||
help: Hero 大标题文字颜色(深色模式);留空使用主题默认
|
||||
- $formkit: color
|
||||
name: hero_subtitle_color_light
|
||||
label: 浅色模式副标题颜色
|
||||
help: Hero 副标题和一言文字颜色(浅色模式);留空使用主题默认
|
||||
- $formkit: color
|
||||
name: hero_subtitle_color_dark
|
||||
label: 深色模式副标题颜色
|
||||
help: Hero 副标题和一言文字颜色(深色模式);留空使用主题默认
|
||||
- group: home
|
||||
label: 首页
|
||||
formSchema:
|
||||
@@ -304,6 +451,16 @@ spec:
|
||||
- label: Cold
|
||||
value: cold
|
||||
value: warm
|
||||
- $formkit: switch
|
||||
name: article_extra_spacing
|
||||
label: 中英文混排自动加空格
|
||||
value: true
|
||||
help: 依赖「API 扩展包」插件,在服务端为正文自动添加中英文间距;插件未安装时自动跳过
|
||||
- $formkit: switch
|
||||
name: article_extra_highlight
|
||||
label: 使用服务端代码高亮
|
||||
value: false
|
||||
help: 依赖「API 扩展包」全量版(含 JS 运行时),启用后由服务端渲染代码高亮并替代主题自带样式;如环境不支持请勿开启
|
||||
- $formkit: select
|
||||
name: article_toc_expand
|
||||
label: 目录展开模式
|
||||
@@ -371,6 +528,10 @@ spec:
|
||||
- label: Masonry
|
||||
value: masonry
|
||||
value: cards
|
||||
- $formkit: attachment
|
||||
name: moments_cover
|
||||
label: 顶部封面图
|
||||
help: 朋友圈样式顶部的封面图片,建议尺寸 1500×500
|
||||
- $formkit: select
|
||||
name: moments_loading_mode
|
||||
label: 加载方式
|
||||
@@ -432,12 +593,75 @@ spec:
|
||||
- label: Cards
|
||||
value: cards
|
||||
value: cards
|
||||
- $formkit: switch
|
||||
name: links_show_comment
|
||||
label: 显示评论
|
||||
value: false
|
||||
help: 默认关闭评论功能,开启后在友情链接页面底部显示评论区
|
||||
- $formkit: switch
|
||||
name: links_random_order
|
||||
label: 随机排列
|
||||
value: false
|
||||
help: 开启后友情链接卡片随机排列顺序
|
||||
- $formkit: switch
|
||||
name: links_random_portal
|
||||
label: 随机传送门按钮
|
||||
value: true
|
||||
help: 开启后页面右上角显示"随机传送"按钮,点击随机访问一位博友
|
||||
- group: subscriptions
|
||||
label: 订阅
|
||||
formSchema:
|
||||
- $formkit: text
|
||||
name: subscriptions_page_title
|
||||
label: 页面标题
|
||||
placeholder: "订阅"
|
||||
- $formkit: text
|
||||
name: subscriptions_subtitle
|
||||
label: 页面副标题
|
||||
placeholder: "博友们的最新文章"
|
||||
- $formkit: number
|
||||
name: subscriptions_page_size
|
||||
label: 每页显示数量
|
||||
value: 20
|
||||
min: 1
|
||||
max: 100
|
||||
help: 单页展示的订阅动态条数,范围 1-100
|
||||
- $formkit: select
|
||||
name: subscriptions_loading_mode
|
||||
label: 加载方式
|
||||
options:
|
||||
- label: 分页
|
||||
value: pagination
|
||||
- label: 下滑加载
|
||||
value: infinite_scroll
|
||||
value: pagination
|
||||
- $formkit: select
|
||||
name: subscriptions_layout
|
||||
label: 布局样式
|
||||
options:
|
||||
- label: 列表卡片
|
||||
value: list
|
||||
- label: 网格卡片
|
||||
value: grid
|
||||
value: list
|
||||
help: 网格卡片布局一行显示3个,内容区域更宽
|
||||
- group: friends
|
||||
label: 朋友圈
|
||||
formSchema:
|
||||
- $formkit: text
|
||||
name: friends_page_title
|
||||
label: 页面标题
|
||||
- group: weread
|
||||
label: 微信读书
|
||||
formSchema:
|
||||
- $formkit: text
|
||||
name: weread_page_title
|
||||
label: 页面标题
|
||||
placeholder: "书架"
|
||||
- $formkit: text
|
||||
name: weread_page_subtitle
|
||||
label: 页面副标题
|
||||
placeholder: "我在读的书"
|
||||
- group: messageboard
|
||||
label: 留言板
|
||||
formSchema:
|
||||
@@ -469,9 +693,61 @@ spec:
|
||||
- $formkit: text
|
||||
name: footer_copyright
|
||||
label: 版权信息(留空则使用默认格式 © 年份 站点标题)
|
||||
- $formkit: switch
|
||||
name: footer_show_stats
|
||||
id: footer_show_stats
|
||||
label: 显示全站统计
|
||||
value: true
|
||||
help: 在页脚展示文章数、分类数、评论数、访问量等站点统计数据
|
||||
- $formkit: checkbox
|
||||
name: footer_stats_items
|
||||
id: footer_stats_items
|
||||
label: 统计项
|
||||
if: "$get(footer_show_stats).value !== false"
|
||||
options:
|
||||
- label: 文章数
|
||||
value: post
|
||||
- label: 分类数
|
||||
value: category
|
||||
- label: 评论数
|
||||
value: comment
|
||||
- label: 访问量
|
||||
value: visit
|
||||
- label: 点赞数
|
||||
value: upvote
|
||||
- label: 总字数
|
||||
value: word
|
||||
value:
|
||||
- post
|
||||
- category
|
||||
- comment
|
||||
- visit
|
||||
help: 总字数依赖「API 扩展包」插件,未安装时自动隐藏该项
|
||||
- $formkit: switch
|
||||
name: footer_show_icp
|
||||
label: 显示 ICP 备案号
|
||||
value: false
|
||||
help: 开启后页脚显示 ICP 备案链接
|
||||
- $formkit: text
|
||||
name: footer_icp
|
||||
label: ICP 备案号
|
||||
help: 工信部ICP备案号,填写后页脚显示备案链接(指向 beian.miit.gov.cn)
|
||||
if: "$get(footer_show_icp).value === true"
|
||||
- $formkit: switch
|
||||
name: footer_show_mps
|
||||
label: 显示公网安备号
|
||||
value: false
|
||||
help: 开启后页脚显示公网安备图标和链接
|
||||
- $formkit: text
|
||||
name: footer_mps
|
||||
label: 公网安备号
|
||||
help: 公安部联网备案号,填写后页脚显示备案图标和链接(指向 beian.mps.gov.cn)
|
||||
if: "$get(footer_show_mps).value === true"
|
||||
- $formkit: text
|
||||
name: footer_mps_url
|
||||
label: 公网安备跳转链接
|
||||
help: 留空时默认指向 https://beian.mps.gov.cn/ ,可填写其他备案查询链接
|
||||
if: "$get(footer_show_mps).value === true"
|
||||
- $formkit: repeater
|
||||
name: footer_socials
|
||||
label: 社交链接
|
||||
|
||||
@@ -0,0 +1,243 @@
|
||||
---
|
||||
---
|
||||
|
||||
<div id="wi-click-effect" th:if="${theme.config?.style?.click_effect_enabled == true}" th:attr="data-effect=${theme.config?.style?.click_effect_type}">
|
||||
|
||||
<script is:inline th:inline="none">
|
||||
(function () {
|
||||
var container = document.getElementById("wi-click-effect");
|
||||
if (!container) return;
|
||||
var effect = container.getAttribute("data-effect") || "off";
|
||||
if (effect === "off" || !effect) return;
|
||||
|
||||
/* 社会主义核心价值观 */
|
||||
var CORE_VALUES = [
|
||||
"富强", "民主", "文明", "和谐",
|
||||
"自由", "平等", "公正", "法治",
|
||||
"爱国", "敬业", "诚信", "友善"
|
||||
];
|
||||
|
||||
var RANDOM_COLORS = [
|
||||
"#ff6b6b", "#f06595", "#cc5de8", "#845ef7",
|
||||
"#5c7cfa", "#339af0", "#22b8cf", "#20c997",
|
||||
"#51cf66", "#94d82d", "#fcc419", "#ff922b"
|
||||
];
|
||||
|
||||
/* 创建单个 DOM 元素并加入舞台 */
|
||||
function spawn(el, x, y) {
|
||||
el.style.left = x + "px";
|
||||
el.style.top = y + "px";
|
||||
container.appendChild(el);
|
||||
}
|
||||
|
||||
/* ========== 1. 社会主义核心价值观 ========== */
|
||||
function spawnValueText(x, y) {
|
||||
var el = document.createElement("span");
|
||||
el.className = "wi-click-fx wi-click-fx--text";
|
||||
el.textContent = CORE_VALUES[Math.floor(Math.random() * CORE_VALUES.length)];
|
||||
el.style.color = RANDOM_COLORS[Math.floor(Math.random() * RANDOM_COLORS.length)];
|
||||
spawn(el, x, y);
|
||||
setTimeout(function () {
|
||||
if (el.parentNode) el.parentNode.removeChild(el);
|
||||
}, 1200);
|
||||
}
|
||||
|
||||
/* ========== 2. 随机彩色爱心 ========== */
|
||||
function spawnHeart(x, y) {
|
||||
var el = document.createElement("span");
|
||||
el.className = "wi-click-fx wi-click-fx--heart";
|
||||
el.innerHTML = "❤";
|
||||
el.style.color = RANDOM_COLORS[Math.floor(Math.random() * RANDOM_COLORS.length)];
|
||||
var dx = (Math.random() - 0.5) * 80;
|
||||
spawn(el, x, y);
|
||||
requestAnimationFrame(function () {
|
||||
el.style.transform = "translate(" + dx + "px, -80px) scale(0.2)";
|
||||
el.style.opacity = "0";
|
||||
});
|
||||
setTimeout(function () {
|
||||
if (el.parentNode) el.parentNode.removeChild(el);
|
||||
}, 1000);
|
||||
}
|
||||
|
||||
/* ========== 3. 烟花 ========== */
|
||||
function spawnFirework(x, y) {
|
||||
var count = 16;
|
||||
var color = RANDOM_COLORS[Math.floor(Math.random() * RANDOM_COLORS.length)];
|
||||
for (var i = 0; i < count; i++) {
|
||||
var el = document.createElement("span");
|
||||
el.className = "wi-click-fx wi-click-fx--spark";
|
||||
el.style.background = color;
|
||||
el.style.boxShadow = "0 0 6px " + color;
|
||||
var angle = (Math.PI * 2 * i) / count;
|
||||
var dist = 40 + Math.random() * 30;
|
||||
var dx = Math.cos(angle) * dist;
|
||||
var dy = Math.sin(angle) * dist;
|
||||
spawn(el, x, y);
|
||||
requestAnimationFrame(function (e, tx, ty) {
|
||||
return function () {
|
||||
e.style.transform = "translate(" + tx + "px, " + ty + "px) scale(0)";
|
||||
e.style.opacity = "0";
|
||||
};
|
||||
}(el, dx, dy));
|
||||
(function (e) {
|
||||
setTimeout(function () {
|
||||
if (e.parentNode) e.parentNode.removeChild(e);
|
||||
}, 800);
|
||||
})(el);
|
||||
}
|
||||
}
|
||||
|
||||
/* ========== 4. 烟花波纹 ========== */
|
||||
function spawnRipple(x, y) {
|
||||
var color = RANDOM_COLORS[Math.floor(Math.random() * RANDOM_COLORS.length)];
|
||||
for (var r = 0; r < 2; r++) {
|
||||
var el = document.createElement("span");
|
||||
el.className = "wi-click-fx wi-click-fx--ripple";
|
||||
el.style.borderColor = color;
|
||||
spawn(el, x, y);
|
||||
(function (e, delay) {
|
||||
setTimeout(function () {
|
||||
requestAnimationFrame(function () {
|
||||
e.style.transform = "translate(-50%, -50%) scale(4)";
|
||||
e.style.opacity = "0";
|
||||
});
|
||||
}, delay);
|
||||
setTimeout(function () {
|
||||
if (e.parentNode) e.parentNode.removeChild(e);
|
||||
}, delay + 700);
|
||||
})(el, r * 150);
|
||||
}
|
||||
}
|
||||
|
||||
/* ========== 5. 粒子 ========== */
|
||||
function spawnParticles(x, y, n) {
|
||||
n = n || 8;
|
||||
for (var i = 0; i < n; i++) {
|
||||
var el = document.createElement("span");
|
||||
el.className = "wi-click-fx wi-click-fx--particle";
|
||||
el.style.background = RANDOM_COLORS[Math.floor(Math.random() * RANDOM_COLORS.length)];
|
||||
var dx = (Math.random() - 0.5) * 60;
|
||||
var dy = (Math.random() - 0.5) * 60 - 20;
|
||||
spawn(el, x, y);
|
||||
requestAnimationFrame(function (e, tx, ty) {
|
||||
return function () {
|
||||
e.style.transform = "translate(" + tx + "px, " + ty + "px) scale(0)";
|
||||
e.style.opacity = "0";
|
||||
};
|
||||
}(el, dx, dy));
|
||||
(function (e) {
|
||||
setTimeout(function () {
|
||||
if (e.parentNode) e.parentNode.removeChild(e);
|
||||
}, 700);
|
||||
})(el);
|
||||
}
|
||||
}
|
||||
|
||||
/* ========== 6. 星星 ========== */
|
||||
function spawnStar(x, y) {
|
||||
var el = document.createElement("span");
|
||||
el.className = "wi-click-fx wi-click-fx--star";
|
||||
el.innerHTML = "★";
|
||||
el.style.color = RANDOM_COLORS[Math.floor(Math.random() * RANDOM_COLORS.length)];
|
||||
var dx = (Math.random() - 0.5) * 50;
|
||||
var dy = (Math.random() - 0.5) * 50 - 30;
|
||||
spawn(el, x, y);
|
||||
requestAnimationFrame(function () {
|
||||
el.style.transform = "translate(" + dx + "px, " + dy + "px) scale(1.6)";
|
||||
el.style.opacity = "0";
|
||||
});
|
||||
setTimeout(function () {
|
||||
if (el.parentNode) el.parentNode.removeChild(el);
|
||||
}, 800);
|
||||
}
|
||||
|
||||
/* 点击分发 */
|
||||
var clickHandlers = {
|
||||
"values": spawnValueText,
|
||||
"hearts": spawnHeart,
|
||||
"fireworks": spawnFirework,
|
||||
"ripples": spawnRipple,
|
||||
"particles": function (x, y) { spawnParticles(x, y, 10); },
|
||||
"stars": spawnStar
|
||||
};
|
||||
|
||||
/* 拖尾效果 */
|
||||
var trailHandlers = {
|
||||
"star-trail": function (x, y) {
|
||||
var el = document.createElement("span");
|
||||
el.className = "wi-click-fx wi-click-fx--trail-star";
|
||||
el.innerHTML = "★";
|
||||
el.style.color = RANDOM_COLORS[Math.floor(Math.random() * RANDOM_COLORS.length)];
|
||||
spawn(el, x, y);
|
||||
setTimeout(function () {
|
||||
if (el.parentNode) el.parentNode.removeChild(el);
|
||||
}, 600);
|
||||
},
|
||||
"particle-trail": function (x, y) {
|
||||
var el = document.createElement("span");
|
||||
el.className = "wi-click-fx wi-click-fx--trail-particle";
|
||||
el.style.background = RANDOM_COLORS[Math.floor(Math.random() * RANDOM_COLORS.length)];
|
||||
spawn(el, x, y);
|
||||
setTimeout(function () {
|
||||
if (el.parentNode) el.parentNode.removeChild(el);
|
||||
}, 500);
|
||||
}
|
||||
};
|
||||
|
||||
var isTrail = effect.indexOf("trail") !== -1;
|
||||
|
||||
if (isTrail) {
|
||||
var handler = trailHandlers[effect];
|
||||
if (!handler) return;
|
||||
var lastX = 0, lastY = 0;
|
||||
var throttle = false;
|
||||
document.addEventListener("mousemove", function (e) {
|
||||
lastX = e.clientX;
|
||||
lastY = e.clientY;
|
||||
if (throttle) return;
|
||||
throttle = true;
|
||||
requestAnimationFrame(function () {
|
||||
throttle = false;
|
||||
handler(lastX, lastY);
|
||||
});
|
||||
}, { passive: true });
|
||||
} else {
|
||||
var handler = clickHandlers[effect];
|
||||
if (!handler) return;
|
||||
document.addEventListener("click", function (e) {
|
||||
handler(e.clientX, e.clientY);
|
||||
}, { passive: true });
|
||||
}
|
||||
})();
|
||||
</script>
|
||||
|
||||
<style is:global>
|
||||
.wi-click-fx{position:fixed;pointer-events:none;will-change:transform,opacity
|
||||
}
|
||||
|
||||
.wi-click-fx--text{font-size:18px;font-weight:700;transform:translate(-50%,-50%);opacity:0;animation:wi-fx-text 1.2s ease-out forwards
|
||||
}
|
||||
|
||||
.wi-click-fx--heart{font-size:20px;transform:translate(-50%,-50%) scale(1);opacity:1;transition:transform 1s cubic-bezier(.16,1,.3,1),opacity 1s ease-out
|
||||
}
|
||||
|
||||
.wi-click-fx--spark{width:6px;height:6px;border-radius:50%;transform:translate(-50%,-50%) scale(1);opacity:1;transition:transform .8s cubic-bezier(.16,1,.3,1),opacity .8s ease-out
|
||||
}
|
||||
|
||||
.wi-click-fx--ripple{width:30px;height:30px;border:2px solid;border-radius:50%;transform:translate(-50%,-50%) scale(.5);opacity:1;transition:transform .7s ease-out,opacity .7s ease-out
|
||||
}
|
||||
|
||||
.wi-click-fx--particle{width:8px;height:8px;border-radius:50%;transform:translate(-50%,-50%) scale(1);opacity:1;transition:transform .7s cubic-bezier(.16,1,.3,1),opacity .7s ease-out
|
||||
}
|
||||
|
||||
.wi-click-fx--star{font-size:16px;transform:translate(-50%,-50%) scale(1);opacity:1;transition:transform .8s cubic-bezier(.16,1,.3,1),opacity .8s ease-out
|
||||
}
|
||||
|
||||
.wi-click-fx--trail-star{font-size:12px;transform:translate(-50%,-50%) scale(1);opacity:.8;transition:opacity .6s ease-out,transform .6s ease-out
|
||||
}
|
||||
|
||||
.wi-click-fx--trail-particle{width:6px;height:6px;border-radius:50%;transform:translate(-50%,-50%) scale(1);opacity:.8;transition:opacity .5s ease-out,transform .5s ease-out
|
||||
}
|
||||
|
||||
@keyframes wi-fx-text{0%{opacity:0;transform:translate(-50%,-50%) scale(.5)}20%{opacity:1;transform:translate(-50%,-80%) scale(1.1)}to{opacity:0;transform:translate(-50%,-120%) scale(.9)}}
|
||||
</style>
|
||||
@@ -3,7 +3,7 @@
|
||||
|
||||
<div class="wi-cursor-glow" id="wi-cursor-glow"></div>
|
||||
|
||||
<script is:inline>
|
||||
<script is:inline th:inline="none">
|
||||
(function(){
|
||||
var glowEl = document.getElementById("wi-cursor-glow");
|
||||
if (!glowEl) return;
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
>
|
||||
<div class="wi-container">
|
||||
<div class="wi-section__header">
|
||||
<h2 class="wi-section__title" th:text="${theme.config?.home?.home_featured_title ?: #messages.msg('common.featured')}">精选</h2>
|
||||
<h2 class="wi-section__title" th:text="${theme.config?.home?.home_featured_title ?: '精选'}">精选</h2>
|
||||
</div>
|
||||
<div class="wi-featured">
|
||||
<a
|
||||
@@ -32,7 +32,7 @@
|
||||
<span
|
||||
class="wi-featured__pinned"
|
||||
th:if="${post.spec.pinned}"
|
||||
th:text="#{hero.pinned}">Pinned</span>
|
||||
th:text="'置顶'">置顶</span>
|
||||
</div>
|
||||
<h3
|
||||
class="wi-featured__title"
|
||||
|
||||
+107
-6
@@ -1,19 +1,42 @@
|
||||
---
|
||||
const THEME_VERSION = "1.0.0";
|
||||
const today = new Date();
|
||||
import { readFileSync } from "node:fs";
|
||||
import { resolve } from "node:path";
|
||||
|
||||
// 版本号直接读取 theme.yaml,避免与包版本不同步
|
||||
function readThemeVersion() {
|
||||
try {
|
||||
const yaml = readFileSync(resolve(process.cwd(), "theme.yaml"), "utf-8");
|
||||
return yaml.match(/^\s*version:\s*"?([^"\s]+)"?/m)?.[1] ?? "";
|
||||
} catch {
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
const THEME_VERSION = readThemeVersion();
|
||||
---
|
||||
|
||||
<footer th:with="showPowered=${theme.config?.footer?.footer_show_powered != false}, showTheme=${theme.config?.footer?.footer_show_theme != false}, hideRight=${showPowered == false and showTheme == false}">
|
||||
<footer
|
||||
th:with="showPowered=${theme.config?.footer?.footer_show_powered != false}, showTheme=${theme.config?.footer?.footer_show_theme != false}, hideRight=${showPowered == false and showTheme == false}, icpText=${theme.config?.footer?.footer_icp}, mpsText=${theme.config?.footer?.footer_mps}, showIcp=${theme.config?.footer?.footer_show_icp == true and not #strings.isEmpty(#strings.trim(icpText ?: ''))}, showMps=${theme.config?.footer?.footer_show_mps == true and not #strings.isEmpty(#strings.trim(mpsText ?: ''))}"
|
||||
>
|
||||
<div class="footer-inner">
|
||||
<div class="footer-bottom" th:classappend="${hideRight} ? 'footer-bottom--center' : ''">
|
||||
<div class="footer-bottom__left">
|
||||
<span class="footer-copyright" th:if="${theme.config?.footer?.footer_copyright}" th:text="${theme.config?.footer?.footer_copyright}"></span>
|
||||
<span class="footer-copyright" th:unless="${theme.config?.footer?.footer_copyright}">
|
||||
© {today.getFullYear()}
|
||||
© <th:block th:text="${#dates.format(#dates.createNow(), 'yyyy')}">2026</th:block>
|
||||
<th:block th:text="${theme.config?.basic?.owner_name ?: site.title}"></th:block>
|
||||
</span>
|
||||
<span class="footer-icp" th:if="${theme.config?.footer?.footer_icp}">
|
||||
<a href="https://beian.miit.gov.cn/" target="_blank" rel="noopener noreferrer" th:text="${theme.config?.footer?.footer_icp}"></a>
|
||||
<span class="footer-icp" th:if="${showIcp}">
|
||||
<a href="https://beian.miit.gov.cn/" target="_blank" rel="noopener noreferrer" th:text="${#strings.trim(icpText)}"></a>
|
||||
</span>
|
||||
<span class="footer-mps" th:if="${showMps}" th:with="mpsUrl=${theme.config?.footer?.footer_mps_url ?: 'https://beian.mps.gov.cn/'}">
|
||||
<a th:href="${mpsUrl}" target="_blank" rel="noopener noreferrer">
|
||||
<svg class="footer-mps__icon" xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true">
|
||||
<path d="M12 2L2 7v6c0 5.5 3.8 10.7 10 12 6.2-1.3 10-6.5 10-12V7l-10-5z"></path>
|
||||
<path d="M9.5 12.5l2 2 3.5-4" stroke="#fff" stroke-width="1.8" fill="none" stroke-linecap="round" stroke-linejoin="round"></path>
|
||||
</svg>
|
||||
<span th:text="${#strings.trim(mpsText)}"></span>
|
||||
</a>
|
||||
</span>
|
||||
</div>
|
||||
<div class="footer-socials" th:if="${theme.config?.footer?.footer_socials}">
|
||||
@@ -34,6 +57,39 @@ const today = new Date();
|
||||
<span th:if="${showTheme}">WarmIsland v{THEME_VERSION}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="footer-stats"
|
||||
th:if="${theme.config?.footer?.footer_show_stats != false}"
|
||||
th:with="stats=${siteStatsFinder.getStats()}, items=${theme.config?.footer?.footer_stats_items ?: {'post','category','comment','visit'}}"
|
||||
>
|
||||
<span class="footer-stat" th:if="${#lists.contains(items, 'post')}">
|
||||
<span class="footer-stat__value" th:text="${stats.post ?: 0}">0</span>
|
||||
<span class="footer-stat__label">文章</span>
|
||||
</span>
|
||||
<span class="footer-stat" th:if="${#lists.contains(items, 'category')}">
|
||||
<span class="footer-stat__value" th:text="${stats.category ?: 0}">0</span>
|
||||
<span class="footer-stat__label">分类</span>
|
||||
</span>
|
||||
<span class="footer-stat" th:if="${#lists.contains(items, 'comment')}">
|
||||
<span class="footer-stat__value" th:text="${stats.comment ?: 0}">0</span>
|
||||
<span class="footer-stat__label">评论</span>
|
||||
</span>
|
||||
<span class="footer-stat" th:if="${#lists.contains(items, 'visit')}">
|
||||
<span class="footer-stat__value" th:text="${stats.visit ?: 0}">0</span>
|
||||
<span class="footer-stat__label">访问</span>
|
||||
</span>
|
||||
<span class="footer-stat" th:if="${#lists.contains(items, 'upvote')}">
|
||||
<span class="footer-stat__value" th:text="${stats.upvote ?: 0}">0</span>
|
||||
<span class="footer-stat__label">点赞</span>
|
||||
</span>
|
||||
<span
|
||||
class="footer-stat"
|
||||
th:if="${#lists.contains(items, 'word') and pluginFinder.available('extra-api')}"
|
||||
>
|
||||
<span class="footer-stat__value" th:text="${extraApiStatsFinder.getPostWordCount()}">0</span>
|
||||
<span class="footer-stat__label">字数</span>
|
||||
</span>
|
||||
</div>
|
||||
<div th:if="${theme.config?.footer?.footer_custom_html}" th:utext="${theme.config?.footer?.footer_custom_html}" class="footer-custom"></div>
|
||||
<div class="footer-injected">
|
||||
<halo:footer />
|
||||
@@ -104,6 +160,51 @@ const today = new Date();
|
||||
color: var(--ink-2);
|
||||
}
|
||||
|
||||
.footer-mps a {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 0.3rem;
|
||||
color: var(--ink-3);
|
||||
text-decoration: none;
|
||||
transition: color 0.15s ease;
|
||||
}
|
||||
|
||||
.footer-mps a:hover {
|
||||
color: var(--ink-2);
|
||||
}
|
||||
|
||||
.footer-mps__icon {
|
||||
flex: none;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.footer-stats {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex-wrap: wrap;
|
||||
gap: 0.5rem 1.25rem;
|
||||
width: 100%;
|
||||
font-size: 0.8rem;
|
||||
color: var(--ink-3);
|
||||
}
|
||||
|
||||
.footer-stat {
|
||||
display: inline-flex;
|
||||
align-items: baseline;
|
||||
gap: 0.3rem;
|
||||
}
|
||||
|
||||
.footer-stat__value {
|
||||
font-weight: 600;
|
||||
color: var(--ink-2);
|
||||
font-variant-numeric: tabular-nums;
|
||||
}
|
||||
|
||||
.footer-stat__label {
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
.footer-socials {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
|
||||
@@ -1,6 +1,11 @@
|
||||
---
|
||||
---
|
||||
|
||||
<style is:inline th:if="${theme.config?.hero?.hero_title_color_light != null and theme.config?.hero?.hero_title_color_light != ''}" th:utext="${' :root { --hero-title-text: ' + theme.config?.hero?.hero_title_color_light + '; } '}">/*hero-title-light*/</style>
|
||||
<style is:inline th:if="${theme.config?.hero?.hero_title_color_dark != null and theme.config?.hero?.hero_title_color_dark != ''}" th:utext="${' html.dark { --hero-title-text: ' + theme.config?.hero?.hero_title_color_dark + '; } '}">/*hero-title-dark*/</style>
|
||||
<style is:inline th:if="${theme.config?.hero?.hero_subtitle_color_light != null and theme.config?.hero?.hero_subtitle_color_light != ''}" th:utext="${' :root { --hero-subtitle-text: ' + theme.config?.hero?.hero_subtitle_color_light + '; } '}">/*hero-subtitle-light*/</style>
|
||||
<style is:inline th:if="${theme.config?.hero?.hero_subtitle_color_dark != null and theme.config?.hero?.hero_subtitle_color_dark != ''}" th:utext="${' html.dark { --hero-subtitle-text: ' + theme.config?.hero?.hero_subtitle_color_dark + '; } '}">/*hero-subtitle-dark*/</style>
|
||||
|
||||
<section
|
||||
class="hero"
|
||||
id="wi-hero"
|
||||
@@ -13,9 +18,24 @@
|
||||
<div class="hero__orb hero__orb--3"></div>
|
||||
<div
|
||||
class="hero__bg-image"
|
||||
th:if="${theme.config?.hero?.hero_background_image}"
|
||||
th:if="${!#strings.isEmpty(theme.config?.hero?.hero_background_image) and #strings.isEmpty(theme.config?.hero?.hero_background_image_dark)}"
|
||||
th:style="'background-image: url(' + ${theme.config?.hero?.hero_background_image} + ')'"
|
||||
></div>
|
||||
<div
|
||||
class="hero__bg-image"
|
||||
th:if="${#strings.isEmpty(theme.config?.hero?.hero_background_image) and !#strings.isEmpty(theme.config?.hero?.hero_background_image_dark)}"
|
||||
th:style="'background-image: url(' + ${theme.config?.hero?.hero_background_image_dark} + ')'"
|
||||
></div>
|
||||
<div
|
||||
class="hero__bg-image hero__bg-image--light"
|
||||
th:if="${!#strings.isEmpty(theme.config?.hero?.hero_background_image) and !#strings.isEmpty(theme.config?.hero?.hero_background_image_dark)}"
|
||||
th:style="'background-image: url(' + ${theme.config?.hero?.hero_background_image} + ')'"
|
||||
></div>
|
||||
<div
|
||||
class="hero__bg-image hero__bg-image--dark"
|
||||
th:if="${!#strings.isEmpty(theme.config?.hero?.hero_background_image) and !#strings.isEmpty(theme.config?.hero?.hero_background_image_dark)}"
|
||||
th:style="'background-image: url(' + ${theme.config?.hero?.hero_background_image_dark} + ')'"
|
||||
></div>
|
||||
</div>
|
||||
|
||||
<div class="hero__blur-overlay" id="wi-hero-blur-overlay"></div>
|
||||
@@ -37,7 +57,7 @@
|
||||
class="hero__description"
|
||||
th:if="${theme.config?.hero?.hero_description_mode == 'hitokoto'}"
|
||||
id="wi-hitokoto"
|
||||
th:attr="data-api=${theme.config?.hero?.hero_hitokoto_api ?: 'https://v1.hitokoto.cn/'},data-categories=${theme.config?.hero?.hero_hitokoto_categories}"
|
||||
th:attr="data-api=${theme.config?.hero?.hero_hitokoto_api ?: 'https://v1.hitokoto.cn/'},data-categories=${theme.config?.hero?.hero_hitokoto_categories},data-auto-refresh=${theme.config?.hero?.hero_hitokoto_auto_refresh == true ? 'true' : 'false'},data-refresh-interval=${theme.config?.hero?.hero_hitokoto_refresh_interval ?: 10000}"
|
||||
></p>
|
||||
<p
|
||||
class="hero__description"
|
||||
@@ -46,7 +66,12 @@
|
||||
></p>
|
||||
</div>
|
||||
|
||||
<div class="hero__scroll-indicator" id="wi-hero-scroll-indicator" aria-hidden="true">
|
||||
<button
|
||||
class="hero__scroll-indicator"
|
||||
id="wi-hero-scroll-indicator"
|
||||
type="button"
|
||||
aria-label="向下浏览"
|
||||
>
|
||||
<svg
|
||||
width="24"
|
||||
height="24"
|
||||
@@ -56,21 +81,58 @@
|
||||
stroke-width="2"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
aria-hidden="true"
|
||||
>
|
||||
<polyline points="6 9 12 15 18 9"></polyline>
|
||||
</svg>
|
||||
</div>
|
||||
</button>
|
||||
</section>
|
||||
|
||||
<script is:inline>
|
||||
<script is:inline th:inline="none">
|
||||
/* Hero 下滑箭头:平滑滚动到首屏内容区 */
|
||||
(function () {
|
||||
var indicator = document.getElementById("wi-hero-scroll-indicator");
|
||||
if (!indicator) return;
|
||||
|
||||
indicator.addEventListener("click", function () {
|
||||
var content = document.querySelector(".wi-home-content");
|
||||
var hero = document.getElementById("wi-hero");
|
||||
var top;
|
||||
|
||||
if (content) {
|
||||
top = content.getBoundingClientRect().top + window.pageYOffset;
|
||||
} else if (hero) {
|
||||
top = hero.offsetTop + hero.offsetHeight;
|
||||
} else {
|
||||
top = window.innerHeight;
|
||||
}
|
||||
|
||||
/* 预留固定导航栏的高度,避免内容被顶栏遮住 */
|
||||
var navbar = document.getElementById("wi-navbar");
|
||||
var offset = navbar ? navbar.offsetHeight + 8 : 8;
|
||||
|
||||
var reduce =
|
||||
window.matchMedia &&
|
||||
window.matchMedia("(prefers-reduced-motion: reduce)").matches;
|
||||
|
||||
window.scrollTo({
|
||||
top: Math.max(0, top - offset),
|
||||
behavior: reduce ? "auto" : "smooth",
|
||||
});
|
||||
});
|
||||
})();
|
||||
|
||||
(function () {
|
||||
var el = document.getElementById("wi-hitokoto");
|
||||
if (!el) return;
|
||||
|
||||
var api = el.getAttribute("data-api") || "https://v1.hitokoto.cn/";
|
||||
var categories = el.getAttribute("data-categories");
|
||||
var url = api + "?encode=json";
|
||||
var autoRefresh = el.getAttribute("data-auto-refresh") === "true";
|
||||
var refreshInterval = parseInt(el.getAttribute("data-refresh-interval"), 10) || 10000;
|
||||
|
||||
function buildUrl() {
|
||||
var url = api + "?encode=json";
|
||||
if (categories) {
|
||||
try {
|
||||
var cats = JSON.parse(categories);
|
||||
@@ -81,11 +143,16 @@
|
||||
}
|
||||
} catch (e) {}
|
||||
}
|
||||
return url;
|
||||
}
|
||||
|
||||
function load(initial) {
|
||||
if (initial) {
|
||||
el.style.opacity = "0.5";
|
||||
el.textContent = "\u2026";
|
||||
}
|
||||
|
||||
fetch(url)
|
||||
return fetch(buildUrl())
|
||||
.then(function (res) {
|
||||
if (!res.ok) throw new Error("HTTP " + res.status);
|
||||
return res.json();
|
||||
@@ -101,8 +168,19 @@
|
||||
})
|
||||
.catch(function () {
|
||||
el.style.opacity = "1";
|
||||
el.textContent = "";
|
||||
// 自动刷新失败时保留原文案,避免闪烁成空白
|
||||
if (initial) el.textContent = "";
|
||||
});
|
||||
}
|
||||
|
||||
load(true);
|
||||
|
||||
// 自动刷新:仅在开启且间隔不小于 3 秒时启用,避免请求过于频繁
|
||||
if (autoRefresh && refreshInterval >= 3000) {
|
||||
setInterval(function () {
|
||||
load(false);
|
||||
}, refreshInterval);
|
||||
}
|
||||
})();
|
||||
</script>
|
||||
|
||||
@@ -199,6 +277,16 @@
|
||||
opacity: 0.15;
|
||||
}
|
||||
|
||||
/* 深色模式背景图切换:未配置双图时不生效,保持单一背景图 */
|
||||
.hero__bg-image--dark,
|
||||
html.dark .hero__bg-image--light {
|
||||
display: none;
|
||||
}
|
||||
|
||||
html.dark .hero__bg-image--dark {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.hero__content {
|
||||
position: relative;
|
||||
z-index: 2;
|
||||
@@ -216,14 +304,14 @@
|
||||
font-size: clamp(3rem, 8vw, 6rem);
|
||||
font-weight: 700;
|
||||
letter-spacing: -0.03em;
|
||||
color: var(--ink);
|
||||
color: var(--hero-title-text, var(--ink));
|
||||
line-height: var(--leading-tight);
|
||||
}
|
||||
|
||||
.hero__subtitle {
|
||||
font-family: var(--font-body);
|
||||
font-size: var(--text-xl);
|
||||
color: var(--ink-2);
|
||||
color: var(--hero-subtitle-text, var(--ink-2));
|
||||
letter-spacing: 0.05em;
|
||||
line-height: var(--leading-normal);
|
||||
}
|
||||
@@ -243,9 +331,29 @@
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
z-index: 2;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
padding: 0;
|
||||
border: none;
|
||||
border-radius: 50%;
|
||||
background: transparent;
|
||||
color: var(--ink-3);
|
||||
cursor: pointer;
|
||||
animation: scrollHint 2s ease-in-out infinite;
|
||||
transition: opacity 0.4s ease;
|
||||
transition: opacity 0.4s ease, color 0.2s ease, background 0.2s ease;
|
||||
}
|
||||
|
||||
.hero__scroll-indicator:hover {
|
||||
color: var(--accent);
|
||||
background: color-mix(in srgb, var(--accent) 10%, transparent);
|
||||
}
|
||||
|
||||
.hero__scroll-indicator:focus-visible {
|
||||
outline: 2px solid var(--accent);
|
||||
outline-offset: 2px;
|
||||
}
|
||||
|
||||
.hero--no-breath .hero__orb {
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
>
|
||||
<div class="wi-container">
|
||||
<div class="wi-section__header">
|
||||
<h2 class="wi-section__title" th:text="${theme.config?.home?.home_latest_title ?: #messages.msg('common.latestPosts')}">Latest Posts</h2>
|
||||
<h2 class="wi-section__title" th:text="${theme.config?.home?.home_latest_title ?: '最新文章'}">最新文章</h2>
|
||||
</div>
|
||||
<div
|
||||
class="wi-latest"
|
||||
@@ -58,7 +58,7 @@
|
||||
th:if="${posts.hasPrevious()}"
|
||||
th:href="@{${posts.prevUrl}}"
|
||||
class="wi-pagination__prev"
|
||||
th:text="${theme.config?.home?.home_label_newer ?: #messages.msg('common.newer')}"
|
||||
th:text="${theme.config?.home?.home_label_newer ?: '较新'}"
|
||||
>← Newer</a>
|
||||
<span
|
||||
class="wi-pagination__info"
|
||||
@@ -68,7 +68,7 @@
|
||||
th:if="${posts.hasNext()}"
|
||||
th:href="@{${posts.nextUrl}}"
|
||||
class="wi-pagination__next"
|
||||
th:text="${theme.config?.home?.home_label_older ?: #messages.msg('common.older')}"
|
||||
th:text="${theme.config?.home?.home_label_older ?: '较旧'}"
|
||||
>Older →</a>
|
||||
</nav>
|
||||
</div>
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
>
|
||||
<div class="wi-container">
|
||||
<div class="wi-section__header">
|
||||
<h2 class="wi-section__title" th:text="${theme.config?.home?.home_message_wall_title ?: #messages.msg('messageboard.title')}">留言墙</h2>
|
||||
<h2 class="wi-section__title" th:text="${theme.config?.home?.home_message_wall_title ?: '留言板'}">留言墙</h2>
|
||||
<span class="wi-section__accent"></span>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -35,19 +35,19 @@
|
||||
onclick="SearchWidget.open()"
|
||||
>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="11" cy="11" r="8"/><path d="m21 21-4.3-4.3"/></svg>
|
||||
<span th:text="${theme.config?.basic?.label_search ?: #messages.msg('common.search')}">Search</span>
|
||||
<span th:text="${theme.config?.basic?.label_search ?: '搜索'}">搜索</span>
|
||||
</button>
|
||||
|
||||
<button class="wi-mobile-menu__action-btn wi-mobile-menu__theme-btn" type="button" aria-label="Toggle theme">
|
||||
<svg class="wi-mobile-menu__icon-moon" xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 3a6 6 0 0 0 9 9 9 9 0 1 1-9-9Z"/></svg>
|
||||
<svg class="wi-mobile-menu__icon-sun" xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="4"/><path d="M12 2v2"/><path d="M12 20v2"/><path d="m4.93 4.93 1.41 1.41"/><path d="m17.66 17.66 1.41 1.41"/><path d="M2 12h2"/><path d="M20 12h2"/><path d="m6.34 17.66-1.41 1.41"/><path d="m19.07 4.93-1.41 1.41"/></svg>
|
||||
<span class="wi-mobile-menu__theme-label" th:text="${theme.config?.basic?.label_theme_switch ?: #messages.msg('common.themeSwitch')}">Switch Theme</span>
|
||||
<span class="wi-mobile-menu__theme-label" th:text="${theme.config?.basic?.label_theme_switch ?: '切换主题'}">切换主题</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script is:inline>
|
||||
<script is:inline th:inline="none">
|
||||
const menu = document.getElementById("wi-mobile-menu");
|
||||
const backdrop = menu?.querySelector(".wi-mobile-menu__backdrop");
|
||||
const closeBtn = menu?.querySelector(".wi-mobile-menu__close");
|
||||
|
||||
@@ -9,7 +9,7 @@ const pageConfig = findPageBySlug("moments");
|
||||
<th:block th:if="${pluginFinder.available('PluginMoments')}">
|
||||
<section class="wi-moments-section">
|
||||
<div class="wi-container">
|
||||
<h2 class="wi-moments-section__title" th:text="${pageConfig?.title ?: theme.config?.moments?.moments_title ?: #messages.msg('moments.title')}">瞬间</h2>
|
||||
<h2 class="wi-moments-section__title" th:text="${pageConfig?.title ?: theme.config?.moments?.moments_title ?: '动态'}">瞬间</h2>
|
||||
<div
|
||||
class="wi-moments-section__list"
|
||||
th:attr="data-style=${theme.config?.moments?.moments_style ?: 'timeline'}"
|
||||
@@ -29,7 +29,7 @@ const pageConfig = findPageBySlug("moments");
|
||||
</th:block>
|
||||
</div>
|
||||
<a th:href="@{'/moments'}" class="wi-moments-section__more">
|
||||
<span th:text="${theme.config?.home?.home_label_view_all ?: #messages.msg('common.viewAll')}">查看全部</span>
|
||||
<span th:text="${theme.config?.home?.home_label_view_all ?: '查看全部'}">查看全部</span>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path d="M5 12h14M12 5l7 7-7 7"/>
|
||||
</svg>
|
||||
|
||||
@@ -4,18 +4,22 @@ import ThemeSwitcher from "./ThemeSwitcher.astro";
|
||||
|
||||
<header class="wi-navbar-wrapper" id="wi-navbar" th:classappend="${theme.config?.navbar?.navbar_style == 'minimal'} ? 'wi-navbar--minimal' : (${theme.config?.navbar?.navbar_style == 'float'} ? 'wi-navbar--float' : 'wi-navbar--glass')">
|
||||
<div class="wi-navbar__capsule">
|
||||
<a class="wi-navbar__brand" th:href="@{/}">
|
||||
<a
|
||||
class="wi-navbar__brand"
|
||||
th:href="@{/}"
|
||||
th:with="brandType=${theme.config?.navbar?.navbar_brand_type}, logoUrl=${!#strings.isEmpty(theme.config?.navbar?.navbar_logo) ? theme.config?.navbar?.navbar_logo : theme.config?.basic?.logo}, logoRadius=${theme.config?.navbar?.navbar_logo_radius}"
|
||||
>
|
||||
<img
|
||||
th:if="${theme.config?.basic?.logo}"
|
||||
th:src="${theme.config?.basic?.logo}"
|
||||
th:if="${brandType == 'logo' and !#strings.isEmpty(logoUrl)}"
|
||||
th:src="${logoUrl}"
|
||||
alt=""
|
||||
class="wi-navbar__logo"
|
||||
th:class="${'wi-navbar__logo wi-navbar__logo--' + (logoRadius != null ? logoRadius : 'rounded')}"
|
||||
loading="eager"
|
||||
decoding="async"
|
||||
onerror="this.style.display='none';this.nextElementSibling&&this.nextElementSibling.classList.add('wi-navbar__title')"
|
||||
onerror="this.style.display='none'"
|
||||
/>
|
||||
<span
|
||||
th:if="${#strings.isEmpty(theme.config?.basic?.logo)}"
|
||||
th:if="${brandType != 'logo' or #strings.isEmpty(logoUrl)}"
|
||||
class="wi-navbar__title"
|
||||
th:text="${site.title}"
|
||||
></span>
|
||||
@@ -47,14 +51,14 @@ import ThemeSwitcher from "./ThemeSwitcher.astro";
|
||||
th:if="${pluginFinder.available('PluginSearchWidget') and theme.config?.navbar?.navbar_show_search != false}"
|
||||
class="wi-navbar__action-btn wi-navbar__search-btn"
|
||||
type="button"
|
||||
th:aria-label="${theme.config?.basic?.label_search ?: #messages.msg('common.search')}"
|
||||
th:aria-label="${theme.config?.basic?.label_search ?: '搜索'}"
|
||||
onclick="SearchWidget.open()"
|
||||
>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="11" cy="11" r="8"/><path d="m21 21-4.3-4.3"/></svg>
|
||||
</button>
|
||||
|
||||
<th:block th:if="${theme.config?.navbar?.navbar_show_theme_switch != false}">
|
||||
<span th:attr="data-label=${theme.config?.basic?.label_theme_switch ?: #messages.msg('common.themeSwitch')}" style="display:inline-flex">
|
||||
<span th:attr="data-label=${theme.config?.basic?.label_theme_switch ?: '切换主题'}" style="display:inline-flex">
|
||||
<ThemeSwitcher />
|
||||
</span>
|
||||
</th:block>
|
||||
@@ -70,7 +74,7 @@ import ThemeSwitcher from "./ThemeSwitcher.astro";
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<script is:inline>
|
||||
<script is:inline th:inline="none">
|
||||
const navbar = document.getElementById("wi-navbar");
|
||||
let lastScrollY = window.scrollY;
|
||||
let ticking = false;
|
||||
@@ -218,6 +222,18 @@ import ThemeSwitcher from "./ThemeSwitcher.astro";
|
||||
object-fit: contain;
|
||||
}
|
||||
|
||||
.wi-navbar__logo--square {
|
||||
border-radius: 0;
|
||||
}
|
||||
|
||||
.wi-navbar__logo--rounded {
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
.wi-navbar__logo--circle {
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
.wi-navbar__title {
|
||||
font-family: var(--font-sans);
|
||||
font-weight: 700;
|
||||
|
||||
@@ -9,7 +9,7 @@ const pageConfig = findPageBySlug("photos");
|
||||
<th:block th:if="${pluginFinder.available('PluginPhotos')}">
|
||||
<section class="wi-photos-section">
|
||||
<div class="wi-container">
|
||||
<h2 class="wi-photos-section__title" th:text="${pageConfig?.title ?: theme.config?.photos?.photos_title ?: #messages.msg('photos.title')}">图库</h2>
|
||||
<h2 class="wi-photos-section__title" th:text="${pageConfig?.title ?: theme.config?.photos?.photos_title ?: '相册'}">图库</h2>
|
||||
<div
|
||||
class="wi-photos-section__grid"
|
||||
th:attr="data-style=${theme.config?.photos?.photos_style ?: 'masonry'}, data-columns=${theme.config?.photos?.photos_columns ?: 3}"
|
||||
@@ -37,7 +37,7 @@ const pageConfig = findPageBySlug("photos");
|
||||
</th:block>
|
||||
</div>
|
||||
<a th:href="@{'/photos'}" class="wi-photos-section__more">
|
||||
<span th:text="${theme.config?.home?.home_label_view_all ?: #messages.msg('common.viewAll')}">查看全部</span>
|
||||
<span th:text="${theme.config?.home?.home_label_view_all ?: '查看全部'}">查看全部</span>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path d="M5 12h14M12 5l7 7-7 7"/>
|
||||
</svg>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
---
|
||||
---
|
||||
|
||||
<script is:inline>
|
||||
<script is:inline th:inline="none">
|
||||
(function(){
|
||||
var enabled = !document.documentElement.classList.contains("wi-no-animations");
|
||||
if (!enabled) {
|
||||
|
||||
@@ -1,56 +0,0 @@
|
||||
<script setup lang="ts">
|
||||
import { onMounted, onUnmounted } from "vue";
|
||||
|
||||
function handleKeydown(e: KeyboardEvent) {
|
||||
if ((e.metaKey || e.ctrlKey) && e.key === "k") {
|
||||
e.preventDefault();
|
||||
const searchWidget = (window as any).SearchWidget;
|
||||
if (searchWidget && typeof searchWidget.open === "function") {
|
||||
searchWidget.open();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
document.addEventListener("keydown", handleKeydown);
|
||||
});
|
||||
|
||||
onUnmounted(() => {
|
||||
document.removeEventListener("keydown", handleKeydown);
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<span class="wi-search-hint" aria-hidden="true">
|
||||
<kbd class="wi-search-hint__key">⌘</kbd>
|
||||
<kbd class="wi-search-hint__key">K</kbd>
|
||||
</span>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.wi-search-hint {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
pointer-events: none;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.wi-search-hint__key {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
min-width: 22px;
|
||||
height: 22px;
|
||||
padding: 0 5px;
|
||||
font-family: var(--font-body);
|
||||
font-size: 11px;
|
||||
font-weight: 500;
|
||||
line-height: 1;
|
||||
color: var(--ink-3);
|
||||
background: var(--bg-raised);
|
||||
border: 1px solid var(--rule);
|
||||
border-radius: 6px;
|
||||
box-shadow: 0 1px 0 var(--rule);
|
||||
}
|
||||
</style>
|
||||
@@ -24,7 +24,7 @@
|
||||
</svg>
|
||||
</button>
|
||||
|
||||
<script is:inline>
|
||||
<script is:inline th:inline="none">
|
||||
(function(){
|
||||
var btn = document.getElementById("wi-theme-toggle");
|
||||
if (!btn) return;
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
>
|
||||
<div class="wi-container">
|
||||
<div class="wi-section__header">
|
||||
<h2 class="wi-section__title" th:text="${theme.config?.home?.home_timeline_title ?: #messages.msg('common.timeline')}">时间线</h2>
|
||||
<h2 class="wi-section__title" th:text="${theme.config?.home?.home_timeline_title ?: '时间线'}">时间线</h2>
|
||||
<span class="wi-section__accent"></span>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -0,0 +1,443 @@
|
||||
---
|
||||
---
|
||||
|
||||
<section
|
||||
class="wi-welcome-popup"
|
||||
id="wi-welcome-popup"
|
||||
th:if="${theme.config?.welcome_popup?.welcome_popup_enabled == true}"
|
||||
th:attr="data-position=${theme.config?.welcome_popup?.welcome_popup_position ?: 'top-center'},data-duration=${theme.config?.welcome_popup?.welcome_popup_duration ?: 5},data-api=${theme.config?.welcome_popup?.welcome_popup_api},data-fallback-api=${theme.config?.welcome_popup?.welcome_popup_fallback_api},data-show-once=${theme.config?.welcome_popup?.welcome_popup_show_once != false ? 'true' : 'false'}"
|
||||
aria-live="polite"
|
||||
aria-atomic="true"
|
||||
>
|
||||
<div class="wi-welcome-popup__inner">
|
||||
<div class="wi-welcome-popup__icon" aria-hidden="true">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path d="M20 10c0 6-8 12-8 12s-8-6-8-12a8 8 0 0 1 16 0Z"></path>
|
||||
<circle cx="12" cy="10" r="3"></circle>
|
||||
</svg>
|
||||
</div>
|
||||
<div class="wi-welcome-popup__content" id="wi-welcome-popup-text">加载中…</div>
|
||||
<button class="wi-welcome-popup__close" type="button" aria-label="关闭">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.4" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path d="M18 6 6 18"></path>
|
||||
<path d="m6 6 12 12"></path>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
<div class="wi-welcome-popup__progress" id="wi-welcome-popup-progress" aria-hidden="true"></div>
|
||||
</section>
|
||||
|
||||
<script is:inline th:inline="none">
|
||||
(function () {
|
||||
var popup = document.getElementById("wi-welcome-popup");
|
||||
if (!popup) return;
|
||||
|
||||
/* 同会话已显示过则不再弹出(可通过"同会话仅显示一次"开关关闭) */
|
||||
var sessionKey = "wi-welcome-shown";
|
||||
var showOnce = popup.getAttribute("data-show-once") !== "false";
|
||||
try {
|
||||
if (showOnce && sessionStorage.getItem(sessionKey)) {
|
||||
popup.style.display = "none";
|
||||
return;
|
||||
}
|
||||
} catch (e) {}
|
||||
|
||||
var textEl = document.getElementById("wi-welcome-popup-text");
|
||||
var closeBtn = popup.querySelector(".wi-welcome-popup__close");
|
||||
var progress = document.getElementById("wi-welcome-popup-progress");
|
||||
var duration = parseInt(popup.getAttribute("data-duration"), 10) || 5;
|
||||
var api = popup.getAttribute("data-api") || "https://myip.ipip.net/json";
|
||||
var fallbackApi = popup.getAttribute("data-fallback-api") || "https://r.inews.qq.com/api/ip2city?otype=json";
|
||||
|
||||
var hideTimer = null;
|
||||
var progressRaf = null;
|
||||
var progressStart = 0;
|
||||
var isHidden = false;
|
||||
|
||||
function hidePopup() {
|
||||
if (isHidden) return;
|
||||
isHidden = true;
|
||||
popup.classList.remove("wi-welcome-popup--visible");
|
||||
popup.classList.add("wi-welcome-popup--leaving");
|
||||
if (hideTimer) {
|
||||
clearTimeout(hideTimer);
|
||||
hideTimer = null;
|
||||
}
|
||||
if (progressRaf) {
|
||||
cancelAnimationFrame(progressRaf);
|
||||
progressRaf = null;
|
||||
}
|
||||
try {
|
||||
if (showOnce) sessionStorage.setItem(sessionKey, "1");
|
||||
} catch (e) {}
|
||||
setTimeout(function () {
|
||||
popup.style.display = "none";
|
||||
}, 400);
|
||||
}
|
||||
|
||||
function startAutoHide() {
|
||||
progressStart = performance.now();
|
||||
var step = function (now) {
|
||||
var elapsed = (now - progressStart) / 1000;
|
||||
var ratio = Math.min(elapsed / duration, 1);
|
||||
if (progress) {
|
||||
progress.style.transform = "scaleX(" + (1 - ratio) + ")";
|
||||
}
|
||||
if (ratio < 1 && !isHidden) {
|
||||
progressRaf = requestAnimationFrame(step);
|
||||
}
|
||||
};
|
||||
progressRaf = requestAnimationFrame(step);
|
||||
hideTimer = setTimeout(hidePopup, duration * 1000);
|
||||
}
|
||||
|
||||
function renderMessage(location) {
|
||||
var loc = (location || "").trim();
|
||||
if (!loc) loc = "未知星球";
|
||||
if (textEl) {
|
||||
textEl.textContent = "欢迎来自 " + loc + " 的朋友";
|
||||
}
|
||||
popup.classList.add("wi-welcome-popup--visible");
|
||||
startAutoHide();
|
||||
}
|
||||
|
||||
function fetchLocation() {
|
||||
if (textEl) {
|
||||
textEl.textContent = "加载中…";
|
||||
}
|
||||
popup.classList.add("wi-welcome-popup--visible");
|
||||
|
||||
/* 按顺序尝试主 API -> 备选 API */
|
||||
var endpoints = [];
|
||||
if (api) endpoints.push(api);
|
||||
if (fallbackApi && fallbackApi !== api) endpoints.push(fallbackApi);
|
||||
|
||||
if (endpoints.length === 0) {
|
||||
renderMessage("");
|
||||
return;
|
||||
}
|
||||
|
||||
var idx = 0;
|
||||
|
||||
function tryNext() {
|
||||
if (idx >= endpoints.length) {
|
||||
renderMessage("");
|
||||
return;
|
||||
}
|
||||
var url = endpoints[idx++];
|
||||
fetchSingle(url, 6000)
|
||||
.then(function (location) {
|
||||
if (location) {
|
||||
renderMessage(location);
|
||||
} else {
|
||||
tryNext();
|
||||
}
|
||||
})
|
||||
.catch(function () {
|
||||
tryNext();
|
||||
});
|
||||
}
|
||||
|
||||
tryNext();
|
||||
}
|
||||
|
||||
function fetchSingle(url, timeoutMs) {
|
||||
var controller = null;
|
||||
var timeoutId = null;
|
||||
|
||||
if (window.AbortController) {
|
||||
controller = new AbortController();
|
||||
timeoutId = setTimeout(function () {
|
||||
controller.abort();
|
||||
}, timeoutMs);
|
||||
}
|
||||
|
||||
return fetch(url, controller ? { signal: controller.signal } : {})
|
||||
.then(function (res) {
|
||||
if (!res.ok) throw new Error("HTTP " + res.status);
|
||||
return res.json();
|
||||
})
|
||||
.then(function (data) {
|
||||
if (timeoutId) clearTimeout(timeoutId);
|
||||
return parseLocation(data);
|
||||
})
|
||||
.catch(function (err) {
|
||||
if (timeoutId) clearTimeout(timeoutId);
|
||||
throw err;
|
||||
});
|
||||
}
|
||||
|
||||
/* 解析多种 IP API 的返回格式:
|
||||
- IPIP: {"ret":"ok","data":{"location":["中国","河北","张家口","","联通"]}}
|
||||
- 腾讯 ip2city: {"ret":0,"province":"河北","city":"张家口",...}
|
||||
- ipapi.co / ip-api.com: {"country":"China","regionName":"Hebei","city":"Zhangjiakou"}
|
||||
- 太平洋: {"pro":"广东省","city":"广州市",...} */
|
||||
function parseLocation(data) {
|
||||
if (!data) return "";
|
||||
|
||||
/* IPIP: data.data.location 数组 */
|
||||
if (data.data && Array.isArray(data.data.location)) {
|
||||
var loc = data.data.location
|
||||
.filter(function (s) {
|
||||
return s && String(s).trim();
|
||||
})
|
||||
.map(function (s) {
|
||||
return String(s).trim();
|
||||
});
|
||||
/* 去掉末尾的运营商字段(如"联通""电信""移动") */
|
||||
var ispKeywords = ["联通", "电信", "移动", "铁通", "广电", "教育网", "长城"];
|
||||
while (loc.length > 1 && ispKeywords.indexOf(loc[loc.length - 1]) !== -1) {
|
||||
loc.pop();
|
||||
}
|
||||
return loc.length ? loc.join("·") : "";
|
||||
}
|
||||
|
||||
var country = data.country || data.country_name || data.nation || "";
|
||||
var region = data.regionName || data.region || data.province || data.pro || "";
|
||||
var city = data.city || data.cityName || "";
|
||||
var district = data.district || data.area || data.info3 || "";
|
||||
|
||||
/* 短英文国家代码(如 CN)舍弃,避免显示无意义的两字母 */
|
||||
if (country && country.length <= 3 && !/[一-龥]/.test(country)) {
|
||||
country = "";
|
||||
}
|
||||
|
||||
var parts = [];
|
||||
if (country) parts.push(country);
|
||||
if (region && region !== country) parts.push(region);
|
||||
if (city && city !== region) parts.push(city);
|
||||
if (district && district !== city) parts.push(district);
|
||||
|
||||
return parts.length ? parts.join("·") : "";
|
||||
}
|
||||
|
||||
/* 交互:悬停/触摸暂停倒计时,离开恢复 */
|
||||
function pauseTimer() {
|
||||
if (hideTimer) {
|
||||
clearTimeout(hideTimer);
|
||||
hideTimer = null;
|
||||
}
|
||||
if (progressRaf) {
|
||||
cancelAnimationFrame(progressRaf);
|
||||
progressRaf = null;
|
||||
}
|
||||
}
|
||||
|
||||
function resumeTimer() {
|
||||
var remaining = duration;
|
||||
if (progress) {
|
||||
var matrix = progress.style.transform;
|
||||
var match = matrix && matrix.match(/scaleX\(([\d.]+)\)/);
|
||||
if (match) {
|
||||
var ratio = parseFloat(match[1]);
|
||||
remaining = duration * ratio;
|
||||
}
|
||||
}
|
||||
progressStart = performance.now() - (duration - remaining) * 1000;
|
||||
var step = function (now) {
|
||||
var elapsed = (now - progressStart) / 1000;
|
||||
var ratio = Math.min(elapsed / duration, 1);
|
||||
if (progress) {
|
||||
progress.style.transform = "scaleX(" + (1 - ratio) + ")";
|
||||
}
|
||||
if (ratio < 1 && !isHidden) {
|
||||
progressRaf = requestAnimationFrame(step);
|
||||
}
|
||||
};
|
||||
progressRaf = requestAnimationFrame(step);
|
||||
hideTimer = setTimeout(hidePopup, remaining * 1000);
|
||||
}
|
||||
|
||||
popup.addEventListener("mouseenter", pauseTimer);
|
||||
popup.addEventListener("mouseleave", resumeTimer);
|
||||
popup.addEventListener("touchstart", pauseTimer, { passive: true });
|
||||
popup.addEventListener("touchend", resumeTimer, { passive: true });
|
||||
|
||||
if (closeBtn) closeBtn.addEventListener("click", hidePopup);
|
||||
|
||||
/* 延迟 800ms 显示,避免与首屏渲染争抢资源 */
|
||||
setTimeout(fetchLocation, 800);
|
||||
})();
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.wi-welcome-popup {
|
||||
position: fixed;
|
||||
z-index: 9999;
|
||||
max-width: min(380px, calc(100vw - 32px));
|
||||
pointer-events: none;
|
||||
opacity: 0;
|
||||
transform: translateY(-12px);
|
||||
transition: opacity 0.4s var(--ease-out-expo), transform 0.4s var(--ease-out-expo);
|
||||
will-change: opacity, transform;
|
||||
}
|
||||
|
||||
.wi-welcome-popup[data-position="top-left"] {
|
||||
top: 88px;
|
||||
left: 24px;
|
||||
}
|
||||
|
||||
.wi-welcome-popup[data-position="top-right"] {
|
||||
top: 88px;
|
||||
right: 24px;
|
||||
}
|
||||
|
||||
.wi-welcome-popup[data-position="bottom-left"] {
|
||||
bottom: 24px;
|
||||
left: 24px;
|
||||
transform: translateY(12px);
|
||||
}
|
||||
|
||||
.wi-welcome-popup[data-position="bottom-right"] {
|
||||
bottom: 24px;
|
||||
right: 24px;
|
||||
transform: translateY(12px);
|
||||
}
|
||||
|
||||
.wi-welcome-popup[data-position="top-center"] {
|
||||
top: 88px;
|
||||
left: 50%;
|
||||
transform: translate(-50%) translateY(-12px);
|
||||
}
|
||||
|
||||
.wi-welcome-popup--visible {
|
||||
opacity: 1;
|
||||
pointer-events: auto;
|
||||
}
|
||||
|
||||
.wi-welcome-popup--visible[data-position="top-left"],
|
||||
.wi-welcome-popup--visible[data-position="top-right"],
|
||||
.wi-welcome-popup--visible[data-position="bottom-left"],
|
||||
.wi-welcome-popup--visible[data-position="bottom-right"] {
|
||||
transform: translateY(0);
|
||||
}
|
||||
|
||||
.wi-welcome-popup--visible[data-position="top-center"] {
|
||||
transform: translate(-50%) translateY(0);
|
||||
}
|
||||
|
||||
.wi-welcome-popup--leaving {
|
||||
opacity: 0;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.wi-welcome-popup--leaving[data-position^="top"] {
|
||||
transform: translateY(-12px);
|
||||
}
|
||||
|
||||
.wi-welcome-popup--leaving[data-position="bottom-left"],
|
||||
.wi-welcome-popup--leaving[data-position="bottom-right"] {
|
||||
transform: translateY(12px);
|
||||
}
|
||||
|
||||
.wi-welcome-popup--leaving[data-position="top-center"] {
|
||||
transform: translate(-50%) translateY(-12px);
|
||||
}
|
||||
|
||||
.wi-welcome-popup__inner {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
padding: 10px 14px;
|
||||
border-radius: 9999px;
|
||||
background: color-mix(in srgb, var(--bg-raised) 92%, transparent);
|
||||
backdrop-filter: blur(20px) saturate(180%);
|
||||
-webkit-backdrop-filter: blur(20px) saturate(180%);
|
||||
border: 1px solid var(--rule);
|
||||
box-shadow: 0 8px 24px -8px #0000001f, 0 2px 8px -2px #0000000f;
|
||||
color: var(--ink);
|
||||
font-size: var(--text-sm);
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
.wi-welcome-popup__icon {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 28px;
|
||||
height: 28px;
|
||||
flex-shrink: 0;
|
||||
border-radius: 50%;
|
||||
background: color-mix(in srgb, var(--accent) 12%, transparent);
|
||||
color: var(--accent);
|
||||
}
|
||||
|
||||
.wi-welcome-popup__icon svg {
|
||||
animation: wi-welcome-popup-pulse 2s ease-in-out infinite;
|
||||
}
|
||||
|
||||
.wi-welcome-popup__content {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.wi-welcome-popup__close {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
flex-shrink: 0;
|
||||
padding: 0;
|
||||
border: none;
|
||||
border-radius: 50%;
|
||||
background: transparent;
|
||||
color: var(--ink-3);
|
||||
cursor: pointer;
|
||||
opacity: 0.6;
|
||||
transition: opacity 0.2s ease, background 0.2s ease, color 0.2s ease;
|
||||
}
|
||||
|
||||
.wi-welcome-popup__close:hover {
|
||||
opacity: 1;
|
||||
background: color-mix(in srgb, var(--ink-3) 12%, transparent);
|
||||
color: var(--ink);
|
||||
}
|
||||
|
||||
.wi-welcome-popup__progress {
|
||||
position: absolute;
|
||||
left: 14px;
|
||||
right: 14px;
|
||||
bottom: 4px;
|
||||
height: 2px;
|
||||
border-radius: 1px;
|
||||
background: color-mix(in srgb, var(--accent) 30%, transparent);
|
||||
transform-origin: left center;
|
||||
transform: scaleX(1);
|
||||
opacity: 0.7;
|
||||
}
|
||||
|
||||
.wi-welcome-popup--visible .wi-welcome-popup__progress {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
@keyframes wi-welcome-popup-pulse {
|
||||
0%,
|
||||
to {
|
||||
transform: scale(1);
|
||||
}
|
||||
50% {
|
||||
transform: scale(1.12);
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 480px) {
|
||||
.wi-welcome-popup[data-position] {
|
||||
max-width: calc(100vw - 24px);
|
||||
}
|
||||
}
|
||||
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
.wi-welcome-popup,
|
||||
.wi-welcome-popup--visible,
|
||||
.wi-welcome-popup--leaving,
|
||||
.wi-welcome-popup__icon svg {
|
||||
transition: opacity 0.2s ease !important;
|
||||
transform: none !important;
|
||||
animation: none !important;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
+28
-38
@@ -5,19 +5,30 @@ import Footer from "../components/Footer.astro";
|
||||
import ScrollReveal from "../components/ScrollReveal.astro";
|
||||
import CursorGlow from "../components/CursorGlow.astro";
|
||||
import MobileMenu from "../components/MobileMenu.astro";
|
||||
import ClickEffect from "../components/ClickEffect.astro";
|
||||
|
||||
interface Props {
|
||||
pageTitle?: string;
|
||||
contentClass?: string;
|
||||
wide?: boolean;
|
||||
home?: boolean;
|
||||
/** 渲染为 Halo 页面布局契约(templates/layout.html 的 html(head, content) 片段) */
|
||||
layoutFragment?: boolean;
|
||||
}
|
||||
|
||||
const { pageTitle, contentClass, wide, home } = Astro.props;
|
||||
const { pageTitle, contentClass, wide, home, layoutFragment = false } = Astro.props;
|
||||
const fragmentAttr = layoutFragment ? "html (head, content)" : undefined;
|
||||
---
|
||||
|
||||
<!doctype html>
|
||||
<html class={home ? 'wi-home-snap' : ''} style="overflow-x:hidden" th:lang="${#locale.toLanguageTag()}" th:classappend="${theme.config?.animation?.animation_enabled == false} ? 'wi-no-animations'" th:attr="data-lang=${theme.config?.basic?.language}">
|
||||
<html
|
||||
lang="zh-CN"
|
||||
class={home ? 'wi-home-snap' : ''}
|
||||
style="overflow-x:hidden"
|
||||
th:classappend="${theme.config?.animation?.animation_enabled == false} ? 'wi-no-animations'"
|
||||
xmlns:th={layoutFragment ? "https://www.thymeleaf.org" : undefined}
|
||||
th:fragment={fragmentAttr}
|
||||
>
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
@@ -73,47 +84,31 @@ const { pageTitle, contentClass, wide, home } = Astro.props;
|
||||
<style is:inline th:if="${theme.config?.style?.layout_container_width == 'medium' or theme.config?.style?.layout_container_width == null}" th:utext="${'.wi-content-wrap { max-width: 800px; } .wi-content-wrap--wide { max-width: 1200px; } .wi-main--wide { max-width: 1400px; }'}">/*width*/</style>
|
||||
<style is:inline th:if="${theme.config?.style?.layout_container_width == 'wide'}" th:utext="${'.wi-content-wrap { max-width: 900px; } .wi-content-wrap--wide { max-width: 1400px; } .wi-main--wide { max-width: 1600px; }'}">/*width*/</style>
|
||||
<style is:inline th:if="${theme.config?.style?.custom_css}" th:utext="${theme.config?.style?.custom_css}">/*custom*/</style>
|
||||
{
|
||||
layoutFragment && (
|
||||
<title th:if="${head == null}" th:text="${site.title}"></title>
|
||||
<th:block th:if="${head != null}">
|
||||
<th:block th:replace="${head}" />
|
||||
</th:block>
|
||||
)
|
||||
}
|
||||
<slot name="head" />
|
||||
</head>
|
||||
<body class="wi-body">
|
||||
<script is:inline>
|
||||
<script is:inline th:inline="none">
|
||||
window.addEventListener("unhandledrejection", function (e) {
|
||||
if (e.reason && e.reason.message && e.reason.message.indexOf("Could not establish connection") !== -1) {
|
||||
e.preventDefault();
|
||||
}
|
||||
});
|
||||
</script>
|
||||
<script is:inline>
|
||||
(function(){
|
||||
try{
|
||||
var lang=document.documentElement.getAttribute("data-lang");
|
||||
if(!lang||lang==="auto"||lang==="null"||lang===""){
|
||||
var cookies=document.cookie.split(";");
|
||||
for(var i=0;i<cookies.length;i++){
|
||||
var c=cookies[i].trim();
|
||||
if(c.indexOf("language=")===0){
|
||||
document.cookie="language=;path=/;max-age=0;SameSite=Lax";
|
||||
break;
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
var tag=lang.replace(/_/g,"-");
|
||||
var cookies=document.cookie.split(";");
|
||||
var current="";
|
||||
for(var i=0;i<cookies.length;i++){
|
||||
var c=cookies[i].trim();
|
||||
if(c.indexOf("language=")===0){current=c.substring(9);break;}
|
||||
}
|
||||
if(current!==tag){
|
||||
document.cookie="language="+tag+";path=/;max-age=31536000;SameSite=Lax";
|
||||
}
|
||||
}catch(e){}
|
||||
})();
|
||||
</script>
|
||||
<Navbar />
|
||||
<main class:list={["wi-main", { "wi-main--wide": wide || home, "wi-main--home": home }]}>
|
||||
{home ? (
|
||||
{layoutFragment ? (
|
||||
<div class:list={["wi-content-wrap", { "wi-content-wrap--wide": wide }]}>
|
||||
<th:block th:replace="${content}" />
|
||||
</div>
|
||||
) : home ? (
|
||||
<slot />
|
||||
) : (
|
||||
<div class:list={["wi-content-wrap", { "wi-content-wrap--wide": wide }]}>
|
||||
@@ -125,6 +120,7 @@ const { pageTitle, contentClass, wide, home } = Astro.props;
|
||||
<MobileMenu />
|
||||
<ScrollReveal th:if="${theme.config?.animation?.animation_enabled != false and theme.config?.animation?.animation_scroll_reveal != false}" />
|
||||
<CursorGlow th:if="${theme.config?.animation?.animation_enabled != false and theme.config?.animation?.animation_cursor_glow == true}" />
|
||||
<ClickEffect />
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -169,10 +165,4 @@ html.wi-no-animations .wi-reveal {
|
||||
transition: none;
|
||||
}
|
||||
|
||||
.wi-post__content img {
|
||||
max-width: 100%;
|
||||
height: auto;
|
||||
}
|
||||
|
||||
|
||||
</style>
|
||||
|
||||
@@ -4,14 +4,14 @@ import Layout from "../layouts/Layout.astro";
|
||||
|
||||
<Layout>
|
||||
<Fragment slot="head">
|
||||
<title th:text="|${theme.config?.basic?.label_archives_title ?: #messages.msg('archives.title')} - ${site.title}|"></title>
|
||||
<title th:text="|${theme.config?.basic?.label_archives_title ?: '归档'} - ${site.title}|"></title>
|
||||
</Fragment>
|
||||
|
||||
<div class="wi-archives">
|
||||
<header class="wi-archives__header">
|
||||
<h1 class="wi-archives__title" th:text="${theme.config?.basic?.label_archives_title ?: #messages.msg('archives.title')}">归档</h1>
|
||||
<h1 class="wi-archives__title" th:text="${theme.config?.basic?.label_archives_title ?: '归档'}">归档</h1>
|
||||
<span class="wi-page-accent"></span>
|
||||
<p class="wi-archives__subtitle" th:text="${#strings.replace(#messages.msg('common.postCount'), '{0}', #strings.toString(archives.total))}"></p>
|
||||
<p class="wi-archives__subtitle" th:text="|共 ${archives.total} 篇文章|">共 0 篇文章</p>
|
||||
</header>
|
||||
|
||||
<section class="wi-archives__timeline" aria-label="Archive timeline">
|
||||
@@ -57,7 +57,7 @@ import Layout from "../layouts/Layout.astro";
|
||||
<div
|
||||
th:if="${archives.total == 0}"
|
||||
class="wi-archives__empty"
|
||||
th:text="${theme.config?.home?.home_label_no_posts ?: #messages.msg('common.noPosts')}"
|
||||
th:text="${theme.config?.home?.home_label_no_posts ?: '暂无文章'}"
|
||||
>No posts yet.</div>
|
||||
</section>
|
||||
|
||||
@@ -70,7 +70,7 @@ import Layout from "../layouts/Layout.astro";
|
||||
th:if="${archives.hasPrevious()}"
|
||||
th:href="@{${archives.prevUrl}}"
|
||||
class="wi-archives__page-link wi-archives__page-link--prev"
|
||||
th:text="${theme.config?.home?.home_label_newer ?: #messages.msg('common.newer')}"
|
||||
th:text="${theme.config?.home?.home_label_newer ?: '较新'}"
|
||||
>← Newer</a>
|
||||
<span
|
||||
class="wi-archives__page-info"
|
||||
@@ -80,7 +80,7 @@ import Layout from "../layouts/Layout.astro";
|
||||
th:if="${archives.hasNext()}"
|
||||
th:href="@{${archives.nextUrl}}"
|
||||
class="wi-archives__page-link wi-archives__page-link--next"
|
||||
th:text="${theme.config?.home?.home_label_older ?: #messages.msg('common.older')}"
|
||||
th:text="${theme.config?.home?.home_label_older ?: '较旧'}"
|
||||
>Older →</a>
|
||||
</nav>
|
||||
</div>
|
||||
|
||||
@@ -4,14 +4,14 @@ import Layout from "../layouts/Layout.astro";
|
||||
|
||||
<Layout>
|
||||
<Fragment slot="head">
|
||||
<title th:text="|#{categories.title} - ${site.title}|"></title>
|
||||
<title th:text="|分类 - ${site.title}|"></title>
|
||||
</Fragment>
|
||||
|
||||
<div class="wi-categories">
|
||||
<header class="wi-categories__header">
|
||||
<h1 class="wi-categories__title" th:text="#{categories.title}">分类</h1>
|
||||
<h1 class="wi-categories__title" th:text="'分类'">分类</h1>
|
||||
<span class="wi-page-accent"></span>
|
||||
<p class="wi-categories__subtitle" th:text="${#messages.msg('common.categoryCount', categories.size())}"></p>
|
||||
<p class="wi-categories__subtitle" th:text="|共 ${categories.size()} 个分类|">共 0 个分类</p>
|
||||
</header>
|
||||
|
||||
<nav class="wi-categories__list" aria-label="Category list">
|
||||
|
||||
@@ -14,7 +14,7 @@ import Layout from "../layouts/Layout.astro";
|
||||
th:text="${category.spec.displayName}"
|
||||
></h1>
|
||||
<span class="wi-page-accent"></span>
|
||||
<p class="wi-category__subtitle" th:text="#{common.postCount(${posts.total})}"></p>
|
||||
<p class="wi-category__subtitle" th:text="|共 ${posts.total} 篇文章|">共 0 篇文章</p>
|
||||
<p
|
||||
th:if="${not #strings.isEmpty(category.spec.description)}"
|
||||
class="wi-category__description"
|
||||
@@ -58,7 +58,7 @@ import Layout from "../layouts/Layout.astro";
|
||||
<div
|
||||
th:if="${posts.total == 0}"
|
||||
class="wi-category__empty"
|
||||
th:text="${theme.config?.home?.home_label_no_posts ?: #messages.msg('common.noPosts')}"
|
||||
th:text="${theme.config?.home?.home_label_no_posts ?: '暂无文章'}"
|
||||
>No posts yet.</div>
|
||||
|
||||
<nav
|
||||
@@ -70,7 +70,7 @@ import Layout from "../layouts/Layout.astro";
|
||||
th:if="${posts.hasPrevious()}"
|
||||
th:href="@{${posts.prevUrl}}"
|
||||
class="wi-category__page-link wi-category__page-link--prev"
|
||||
th:text="${theme.config?.home?.home_label_newer ?: #messages.msg('common.newer')}"
|
||||
th:text="${theme.config?.home?.home_label_newer ?: '较新'}"
|
||||
>← Newer</a>
|
||||
<span
|
||||
class="wi-category__page-info"
|
||||
@@ -80,7 +80,7 @@ import Layout from "../layouts/Layout.astro";
|
||||
th:if="${posts.hasNext()}"
|
||||
th:href="@{${posts.nextUrl}}"
|
||||
class="wi-category__page-link wi-category__page-link--next"
|
||||
th:text="${theme.config?.home?.home_label_older ?: #messages.msg('common.older')}"
|
||||
th:text="${theme.config?.home?.home_label_older ?: '较旧'}"
|
||||
>Older →</a>
|
||||
</nav>
|
||||
</div>
|
||||
|
||||
+13
-13
@@ -10,7 +10,7 @@ import Layout from "../layouts/Layout.astro";
|
||||
<section class="wi-douban">
|
||||
<div class="wi-container">
|
||||
<div class="wi-douban__header">
|
||||
<h1 class="wi-douban__title" th:text="${title ?: #messages.msg('douban.title')}">Douban</h1>
|
||||
<h1 class="wi-douban__title" th:text="${title ?: '豆瓣'}">豆瓣</h1>
|
||||
<span class="wi-page-accent"></span>
|
||||
</div>
|
||||
|
||||
@@ -32,63 +32,63 @@ import Layout from "../layouts/Layout.astro";
|
||||
class="wi-douban__filter-tag wi-douban__filter-tag--sm"
|
||||
th:href="@{/douban(type=${currentType},status='wish')}"
|
||||
th:classappend="${hasStatus and currentStatus == 'wish'} ? 'wi-douban__filter-tag--active' : ''"
|
||||
th:text="#{douban.wish}">Wish</a>
|
||||
th:text="'想看'">想看</a>
|
||||
<a
|
||||
class="wi-douban__filter-tag wi-douban__filter-tag--sm"
|
||||
th:href="@{/douban(type=${currentType},status='done')}"
|
||||
th:classappend="${(!hasStatus and isDefaultType) or (hasStatus and currentStatus == 'done')} ? 'wi-douban__filter-tag--active' : ''"
|
||||
th:text="#{douban.done}">Done</a>
|
||||
th:text="'看过'">看过</a>
|
||||
</th:block>
|
||||
<th:block th:if="${currentType == 'book'}">
|
||||
<a
|
||||
class="wi-douban__filter-tag wi-douban__filter-tag--sm"
|
||||
th:href="@{/douban(type='book',status='wish')}"
|
||||
th:classappend="${hasStatus and currentStatus == 'wish'} ? 'wi-douban__filter-tag--active' : ''"
|
||||
th:text="#{douban.wishRead}">Wish</a>
|
||||
th:text="'想读'">想读</a>
|
||||
<a
|
||||
class="wi-douban__filter-tag wi-douban__filter-tag--sm"
|
||||
th:href="@{/douban(type='book',status='doing')}"
|
||||
th:classappend="${hasStatus and currentStatus == 'doing'} ? 'wi-douban__filter-tag--active' : ''"
|
||||
th:text="#{douban.reading}">Reading</a>
|
||||
th:text="'在读'">在读</a>
|
||||
<a
|
||||
class="wi-douban__filter-tag wi-douban__filter-tag--sm"
|
||||
th:href="@{/douban(type='book',status='done')}"
|
||||
th:classappend="${(!hasStatus and isDefaultType) or (hasStatus and currentStatus == 'done')} ? 'wi-douban__filter-tag--active' : ''"
|
||||
th:text="#{douban.done}">Done</a>
|
||||
th:text="'看过'">看过</a>
|
||||
</th:block>
|
||||
<th:block th:if="${currentType == 'music'}">
|
||||
<a
|
||||
class="wi-douban__filter-tag wi-douban__filter-tag--sm"
|
||||
th:href="@{/douban(type='music',status='wish')}"
|
||||
th:classappend="${hasStatus and currentStatus == 'wish'} ? 'wi-douban__filter-tag--active' : ''"
|
||||
th:text="#{douban.wishListen}">Wish</a>
|
||||
th:text="'想听'">想听</a>
|
||||
<a
|
||||
class="wi-douban__filter-tag wi-douban__filter-tag--sm"
|
||||
th:href="@{/douban(type='music',status='doing')}"
|
||||
th:classappend="${hasStatus and currentStatus == 'doing'} ? 'wi-douban__filter-tag--active' : ''"
|
||||
th:text="#{douban.listening}">Listening</a>
|
||||
th:text="'在听'">在听</a>
|
||||
<a
|
||||
class="wi-douban__filter-tag wi-douban__filter-tag--sm"
|
||||
th:href="@{/douban(type='music',status='done')}"
|
||||
th:classappend="${(!hasStatus and isDefaultType) or (hasStatus and currentStatus == 'done')} ? 'wi-douban__filter-tag--active' : ''"
|
||||
th:text="#{douban.listened}">Listened</a>
|
||||
th:text="'听过'">听过</a>
|
||||
</th:block>
|
||||
<th:block th:if="${currentType == 'game'}">
|
||||
<a
|
||||
class="wi-douban__filter-tag wi-douban__filter-tag--sm"
|
||||
th:href="@{/douban(type='game',status='wish')}"
|
||||
th:classappend="${hasStatus and currentStatus == 'wish'} ? 'wi-douban__filter-tag--active' : ''"
|
||||
th:text="#{douban.wishPlay}">Wish</a>
|
||||
th:text="'想玩'">想玩</a>
|
||||
<a
|
||||
class="wi-douban__filter-tag wi-douban__filter-tag--sm"
|
||||
th:href="@{/douban(type='game',status='doing')}"
|
||||
th:classappend="${hasStatus and currentStatus == 'doing'} ? 'wi-douban__filter-tag--active' : ''"
|
||||
th:text="#{douban.playing}">Playing</a>
|
||||
th:text="'在玩'">在玩</a>
|
||||
<a
|
||||
class="wi-douban__filter-tag wi-douban__filter-tag--sm"
|
||||
th:href="@{/douban(type='game',status='done')}"
|
||||
th:classappend="${(!hasStatus and isDefaultType) or (hasStatus and currentStatus == 'done')} ? 'wi-douban__filter-tag--active' : ''"
|
||||
th:text="#{douban.played}">Played</a>
|
||||
th:text="'玩过'">玩过</a>
|
||||
</th:block>
|
||||
</div>
|
||||
|
||||
@@ -162,7 +162,7 @@ import Layout from "../layouts/Layout.astro";
|
||||
class="wi-douban__my-score"
|
||||
th:if="${!#strings.isEmpty(item.faves?.score)}"
|
||||
>
|
||||
<span th:text="#{douban.myRating}">My:</span><th:block th:text="${item.faves?.score}"></th:block>
|
||||
<span th:text="'我的:'">My:</span><th:block th:text="${item.faves?.score}"></th:block>
|
||||
</span>
|
||||
<span
|
||||
class="wi-douban__year"
|
||||
|
||||
@@ -4,7 +4,7 @@ import Layout from "../layouts/Layout.astro";
|
||||
|
||||
<Layout wide>
|
||||
<Fragment slot="head">
|
||||
<title th:text="|${theme.config?.friends?.friends_page_title ?: #messages.msg('friends.title')} - ${site.title}|"></title>
|
||||
<title th:text="|${theme.config?.friends?.friends_page_title ?: '友人圈'} - ${site.title}|"></title>
|
||||
</Fragment>
|
||||
|
||||
<section
|
||||
@@ -15,7 +15,7 @@ import Layout from "../layouts/Layout.astro";
|
||||
<div class="wi-friends-page__header">
|
||||
<h1
|
||||
class="wi-friends-page__title"
|
||||
th:text="${theme.config?.friends?.friends_page_title ?: #messages.msg('friends.title')}"
|
||||
th:text="${theme.config?.friends?.friends_page_title ?: '友人圈'}"
|
||||
>
|
||||
Friends Circle
|
||||
</h1>
|
||||
@@ -87,7 +87,7 @@ import Layout from "../layouts/Layout.astro";
|
||||
th:if="${friends.hasPrevious()}"
|
||||
th:href="@{${friends.prevUrl}}"
|
||||
class="wi-friends-page__page-link"
|
||||
th:text="#{common.newer}"
|
||||
th:text="'较新'"
|
||||
>
|
||||
← Newer
|
||||
</a>
|
||||
@@ -99,7 +99,7 @@ import Layout from "../layouts/Layout.astro";
|
||||
th:if="${friends.hasNext()}"
|
||||
th:href="@{${friends.nextUrl}}"
|
||||
class="wi-friends-page__page-link"
|
||||
th:text="#{common.older}"
|
||||
th:text="'较旧'"
|
||||
>
|
||||
Older →
|
||||
</a>
|
||||
@@ -113,11 +113,11 @@ import Layout from "../layouts/Layout.astro";
|
||||
>
|
||||
<div class="wi-container">
|
||||
<div class="wi-friends-page__header">
|
||||
<h1 class="wi-friends-page__title" th:text="#{friends.title}">Friends Circle</h1>
|
||||
<h1 class="wi-friends-page__title" th:text="'友人圈'">友人圈</h1>
|
||||
<span class="wi-page-accent"></span>
|
||||
</div>
|
||||
<div class="wi-friends-page__empty">
|
||||
<p th:utext="#{common.installPlugin('Friends Circle', 'https://www.halo.run/store/apps/app-friends')}">This feature requires the Friends Circle plugin</p>
|
||||
<p>该功能需要安装「朋友圈」插件,<a href="https://www.halo.run/store/apps/app-yISsV" target="_blank" rel="noopener noreferrer">前往应用市场安装</a></p>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
+18
-15
@@ -1,6 +1,7 @@
|
||||
---
|
||||
import Layout from "../layouts/Layout.astro";
|
||||
import HeroSection from "../components/HeroSection.astro";
|
||||
import WelcomePopup from "../components/WelcomePopup.astro";
|
||||
---
|
||||
|
||||
<Layout home>
|
||||
@@ -8,12 +9,13 @@ import HeroSection from "../components/HeroSection.astro";
|
||||
<title th:text="${site.title}"></title>
|
||||
</Fragment>
|
||||
|
||||
<WelcomePopup />
|
||||
|
||||
<HeroSection />
|
||||
|
||||
<div
|
||||
class="wi-home-content"
|
||||
th:classappend="${theme.config?.hero?.hero_enabled != false} ? 'wi-home-content--with-hero'"
|
||||
th:attr="data-i18n-all-loaded=#{common.allLoaded},data-i18n-loading=#{common.loading},data-i18n-words=#{common.words},data-i18n-read-time=#{common.readTime},data-i18n-views=#{common.views}"
|
||||
>
|
||||
<section
|
||||
class="wi-home-pinned"
|
||||
@@ -21,7 +23,7 @@ import HeroSection from "../components/HeroSection.astro";
|
||||
>
|
||||
<div class="wi-container">
|
||||
<div class="wi-section__header">
|
||||
<h2 class="wi-section__title" th:text="${theme.config?.home?.home_pinned_title ?: #messages.msg('hero.pinned')}">置顶</h2>
|
||||
<h2 class="wi-section__title" th:text="${theme.config?.home?.home_pinned_title ?: '置顶'}">置顶</h2>
|
||||
</div>
|
||||
<div class="wi-pinned">
|
||||
<a
|
||||
@@ -66,7 +68,7 @@ import HeroSection from "../components/HeroSection.astro";
|
||||
<section class="wi-home-posts" id="wi-home-posts">
|
||||
<div class="wi-container">
|
||||
<div class="wi-section__header">
|
||||
<h2 class="wi-section__title" th:text="${theme.config?.home?.home_posts_title ?: #messages.msg('hero.posts')}">文章</h2>
|
||||
<h2 class="wi-section__title" th:text="${theme.config?.home?.home_posts_title ?: '文章'}">文章</h2>
|
||||
</div>
|
||||
<div class="wi-flow" id="wi-post-grid">
|
||||
<article
|
||||
@@ -129,7 +131,7 @@ import HeroSection from "../components/HeroSection.astro";
|
||||
class="wi-flow__stat"
|
||||
th:if="${theme.config?.home?.home_card_show_visit ?: true}"
|
||||
>
|
||||
<th:block th:text="${post.stats?.visit ?: 0}"></th:block> <th:block th:text="#{common.views}">阅读</th:block>
|
||||
<th:block th:text="${post.stats?.visit ?: 0}"></th:block> <th:block th:text="'阅读'">阅读</th:block>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
@@ -153,7 +155,7 @@ import HeroSection from "../components/HeroSection.astro";
|
||||
th:if="${posts.hasPrevious()}"
|
||||
th:href="@{${posts.prevUrl}}"
|
||||
class="wi-pagination__prev"
|
||||
th:text="${theme.config?.home?.home_label_newer ?: #messages.msg('common.newer')}"
|
||||
th:text="${theme.config?.home?.home_label_newer ?: '较新'}"
|
||||
>← 较新</a>
|
||||
<span
|
||||
class="wi-pagination__info"
|
||||
@@ -163,18 +165,18 @@ import HeroSection from "../components/HeroSection.astro";
|
||||
th:if="${posts.hasNext()}"
|
||||
th:href="@{${posts.nextUrl}}"
|
||||
class="wi-pagination__next"
|
||||
th:text="${theme.config?.home?.home_label_older ?: #messages.msg('common.older')}"
|
||||
th:text="${theme.config?.home?.home_label_older ?: '较旧'}"
|
||||
>较旧 →</a>
|
||||
</nav>
|
||||
|
||||
<div
|
||||
th:if="${posts.hasNext() and theme.config?.home?.home_post_loading == 'infinite_scroll'}"
|
||||
id="wi-infinite-scroll-sentinel"
|
||||
th:attr="data-next-url=${posts.nextUrl}, data-all-loaded-text=${theme.config?.home?.home_label_all_loaded ?: #messages.msg('common.allLoaded')}"
|
||||
th:attr="data-next-url=${posts.nextUrl}, data-all-loaded-text=${theme.config?.home?.home_label_all_loaded ?: '已加载全部文章'}"
|
||||
>
|
||||
<div class="wi-infinite-scroll__loader">
|
||||
<div class="wi-infinite-scroll__spinner"></div>
|
||||
<span th:text="${theme.config?.home?.home_label_loading ?: #messages.msg('common.loading')}">加载中...</span>
|
||||
<span th:text="${theme.config?.home?.home_label_loading ?: '加载中...'}">加载中...</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -182,23 +184,22 @@ import HeroSection from "../components/HeroSection.astro";
|
||||
th:if="${!posts.hasNext() and posts.totalPages gt 1 and theme.config?.home?.home_post_loading == 'infinite_scroll'}"
|
||||
class="wi-infinite-scroll__end"
|
||||
>
|
||||
<span th:text="${theme.config?.home?.home_label_all_loaded ?: #messages.msg('common.allLoaded')}">已加载全部文章</span>
|
||||
<span th:text="${theme.config?.home?.home_label_all_loaded ?: '已加载全部文章'}">已加载全部文章</span>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
</Layout>
|
||||
|
||||
<script is:inline>
|
||||
<script is:inline th:inline="none">
|
||||
(function () {
|
||||
var sentinel = document.getElementById("wi-infinite-scroll-sentinel");
|
||||
if (!sentinel) return;
|
||||
|
||||
var grid = document.getElementById("wi-post-grid");
|
||||
var i18nEl = document.querySelector(".wi-home-content");
|
||||
var loading = false;
|
||||
var nextUrl = sentinel.getAttribute("data-next-url");
|
||||
var allLoadedText = sentinel.getAttribute("data-all-loaded-text") || (i18nEl && i18nEl.getAttribute("data-i18n-all-loaded")) || "All posts loaded";
|
||||
var allLoadedText = sentinel.getAttribute("data-all-loaded-text") || "已加载全部文章";
|
||||
|
||||
var observer = new IntersectionObserver(
|
||||
function (entries) {
|
||||
@@ -240,7 +241,9 @@ import HeroSection from "../components/HeroSection.astro";
|
||||
sentinel.style.display = "none";
|
||||
var endMsg = document.createElement("div");
|
||||
endMsg.className = "wi-infinite-scroll__end";
|
||||
endMsg.innerHTML = "<span>" + allLoadedText + "</span>";
|
||||
var endText = document.createElement("span");
|
||||
endText.textContent = allLoadedText;
|
||||
endMsg.appendChild(endText);
|
||||
sentinel.parentNode.appendChild(endMsg);
|
||||
}
|
||||
|
||||
@@ -260,7 +263,7 @@ import HeroSection from "../components/HeroSection.astro";
|
||||
})();
|
||||
</script>
|
||||
|
||||
<script is:inline>
|
||||
<script is:inline th:inline="none">
|
||||
(function () {
|
||||
var hero = document.getElementById("wi-hero");
|
||||
var blurOverlay = document.getElementById("wi-hero-blur-overlay");
|
||||
@@ -304,7 +307,7 @@ import HeroSection from "../components/HeroSection.astro";
|
||||
})();
|
||||
</script>
|
||||
|
||||
<script is:inline>
|
||||
<script is:inline th:inline="none">
|
||||
(function () {
|
||||
var hero = document.getElementById("wi-hero");
|
||||
var content = document.querySelector(".wi-home-content--with-hero");
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
---
|
||||
import Layout from "../layouts/Layout.astro";
|
||||
---
|
||||
|
||||
<!--
|
||||
页面布局契约(Halo 2.26+)
|
||||
供插件前台页面通过 ~{layout :: html(head = ..., content = ...)} 复用主题外观,
|
||||
避免这些页面回退到 Halo 内置的 fallback 布局导致风格不一致。
|
||||
|
||||
本文件只负责声明契约:实际结构、配色、导航、页脚均由 Layout 组件提供,
|
||||
因此修改主题外观时无需同步改这里。
|
||||
-->
|
||||
<Layout layoutFragment wide />
|
||||
+108
-4
@@ -4,24 +4,39 @@ import Layout from "../layouts/Layout.astro";
|
||||
|
||||
<Layout wide>
|
||||
<Fragment slot="head">
|
||||
<title th:text="|${theme.config?.links?.links_page_title ?: #messages.msg('links.title')} - ${site.title}|"></title>
|
||||
<title th:text="|${theme.config?.links?.links_page_title ?: '友链'} - ${site.title}|"></title>
|
||||
</Fragment>
|
||||
|
||||
<section
|
||||
class="wi-links"
|
||||
th:if="${pluginFinder.available('PluginLinks')}"
|
||||
th:attr="data-style=${theme.config?.links?.links_style ?: 'cards'}"
|
||||
th:attr="data-style=${theme.config?.links?.links_style ?: 'cards'},data-random=${theme.config?.links?.links_random_order == true ? 'true' : 'false'}"
|
||||
>
|
||||
<div class="wi-container">
|
||||
<div class="wi-links__header">
|
||||
<h1
|
||||
class="wi-links__title"
|
||||
th:text="${theme.config?.links?.links_page_title ?: #messages.msg('links.title')}"
|
||||
th:text="${theme.config?.links?.links_page_title ?: '友链'}"
|
||||
>
|
||||
友情链接
|
||||
</h1>
|
||||
<p class="wi-links__subtitle" th:text="#{links.subtitle}">Mountains and rivers, a journey of a thousand miles</p>
|
||||
<p class="wi-links__subtitle" th:text="'山水一程,三生有幸'">山水一程,三生有幸</p>
|
||||
<span class="wi-page-accent"></span>
|
||||
<button
|
||||
type="button"
|
||||
class="wi-links__portal"
|
||||
id="wi-links-portal"
|
||||
th:if="${theme.config?.links?.links_random_portal != false}"
|
||||
title="随机传送"
|
||||
aria-label="随机传送"
|
||||
>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path d="M21 16V8a2 2 0 0 0-1-1.73l-7-4a2 2 0 0 0-2 0l-7 4A2 2 0 0 0 3 8v8a2 2 0 0 0 1 1.73l7 4a2 2 0 0 0 2 0l7-4A2 2 0 0 0 21 16z"></path>
|
||||
<polyline points="3.27 6.96 12 12.01 20.73 6.96"></polyline>
|
||||
<polyline points="3.27 6.96 12 12.01 20.73 6.96" transform="translate(0 8)"></polyline>
|
||||
</svg>
|
||||
<span class="wi-links__portal-text">随机传送</span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<th:block th:each="group : ${groups}">
|
||||
@@ -71,8 +86,50 @@ import Layout from "../layouts/Layout.astro";
|
||||
</a>
|
||||
</div>
|
||||
</th:block>
|
||||
|
||||
<div class="wi-links__comment" th:if="${theme.config?.links?.links_show_comment == true}">
|
||||
<halo:comment group="plugin.halo.run" kind="Plugin" name="PluginLinks"></halo:comment>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<script is:inline th:inline="none">
|
||||
(function () {
|
||||
var section = document.querySelector(".wi-links");
|
||||
if (!section) return;
|
||||
|
||||
var cards = Array.prototype.slice.call(section.querySelectorAll(".wi-links__card"));
|
||||
if (!cards.length) return;
|
||||
|
||||
// 随机排列:Fisher-Yates 打乱每个分组内的卡片顺序
|
||||
if (section.getAttribute("data-random") === "true") {
|
||||
Array.prototype.forEach.call(section.querySelectorAll(".wi-links__grid"), function (grid) {
|
||||
for (var i = grid.children.length; i >= 0; i--) {
|
||||
var j = Math.floor(Math.random() * i);
|
||||
grid.appendChild(grid.children[j]);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// 随机传送:从当前页所有友链中随机打开一个
|
||||
var portal = document.getElementById("wi-links-portal");
|
||||
if (portal) {
|
||||
portal.addEventListener("click", function () {
|
||||
var links = Array.prototype.slice
|
||||
.call(section.querySelectorAll(".wi-links__card"))
|
||||
.map(function (a) {
|
||||
return a.getAttribute("href");
|
||||
})
|
||||
.filter(function (h) {
|
||||
return h && h !== "#";
|
||||
});
|
||||
if (!links.length) return;
|
||||
var target = links[Math.floor(Math.random() * links.length)];
|
||||
window.open(target, "_blank", "noopener");
|
||||
});
|
||||
}
|
||||
})();
|
||||
</script>
|
||||
</Layout>
|
||||
|
||||
<style>
|
||||
@@ -85,6 +142,53 @@ import Layout from "../layouts/Layout.astro";
|
||||
margin-bottom: var(--space-3xl);
|
||||
}
|
||||
|
||||
.wi-links__portal {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 0.375rem;
|
||||
padding: 0.6rem 1.1rem;
|
||||
min-height: 44px;
|
||||
margin-top: var(--space-md);
|
||||
border-radius: 9999px;
|
||||
font-size: var(--text-sm);
|
||||
color: #fff;
|
||||
background: linear-gradient(
|
||||
135deg,
|
||||
var(--accent),
|
||||
color-mix(in srgb, var(--accent) 70%, #000)
|
||||
);
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
font-family: inherit;
|
||||
font-weight: 600;
|
||||
box-shadow: 0 2px 8px color-mix(in srgb, var(--accent) 30%, transparent);
|
||||
transition: transform var(--duration-fast) var(--ease-out-quart),
|
||||
box-shadow var(--duration-fast) var(--ease-out-quart),
|
||||
opacity var(--duration-fast) var(--ease-out-quart);
|
||||
}
|
||||
|
||||
.wi-links__portal:hover {
|
||||
transform: translateY(-1px);
|
||||
box-shadow: 0 4px 14px color-mix(in srgb, var(--accent) 40%, transparent);
|
||||
opacity: 0.95;
|
||||
}
|
||||
|
||||
.wi-links__portal:active {
|
||||
transform: translateY(0) scale(0.97);
|
||||
}
|
||||
|
||||
.wi-links__portal svg {
|
||||
transition: transform var(--duration-normal) var(--ease-out-expo);
|
||||
}
|
||||
|
||||
.wi-links__portal:hover svg {
|
||||
transform: rotate(180deg) scale(1.1);
|
||||
}
|
||||
|
||||
.wi-links__comment {
|
||||
margin-top: var(--space-3xl);
|
||||
}
|
||||
|
||||
.wi-links__title {
|
||||
font-size: var(--text-4xl);
|
||||
margin-bottom: var(--space-xs);
|
||||
|
||||
+40
-14
@@ -5,13 +5,22 @@ import LightGallery from "../components/LightGallery.astro";
|
||||
|
||||
<Layout>
|
||||
<Fragment slot="head">
|
||||
<title th:text="|${theme.config?.moments?.moments_page_title ?: #messages.msg('moments.title')} - ${site.title}|"></title>
|
||||
<title th:text="|${theme.config?.moments?.moments_page_title ?: '动态'} - ${site.title}|"></title>
|
||||
</Fragment>
|
||||
|
||||
<section class="wi-moments-page" th:attr="data-i18n-all-moments-loaded=#{common.allMomentsLoaded}">
|
||||
<section class="wi-moments-page">
|
||||
<div class="wi-moments-page__hero" th:if="${theme.config?.moments?.moments_cover}">
|
||||
<img
|
||||
class="wi-moments-page__cover"
|
||||
th:src="${theme.config?.moments?.moments_cover}"
|
||||
alt=""
|
||||
loading="eager"
|
||||
decoding="async"
|
||||
/>
|
||||
</div>
|
||||
<div class="wi-container">
|
||||
<div class="wi-moments-page__header">
|
||||
<h1 class="wi-moments-page__title" th:text="${theme.config?.moments?.moments_page_title ?: #messages.msg('moments.title')}">瞬间</h1>
|
||||
<div class="wi-moments-page__header" th:unless="${theme.config?.moments?.moments_cover}">
|
||||
<h1 class="wi-moments-page__title" th:text="${theme.config?.moments?.moments_page_title ?: '动态'}">瞬间</h1>
|
||||
<span class="wi-page-accent"></span>
|
||||
</div>
|
||||
|
||||
@@ -70,7 +79,7 @@ import LightGallery from "../components/LightGallery.astro";
|
||||
</th:block>
|
||||
<th:block th:if="${momentsResult == null or #lists.isEmpty(momentsResult.items)}">
|
||||
<div class="wi-moments-page__empty">
|
||||
<p th:text="#{moments.noMoments}">No moments yet</p>
|
||||
<p th:text="'还没有瞬间,快去记录当下的心情吧 🌙'">还没有瞬间,快去记录当下的心情吧 🌙</p>
|
||||
</div>
|
||||
</th:block>
|
||||
</div>
|
||||
@@ -126,7 +135,7 @@ import LightGallery from "../components/LightGallery.astro";
|
||||
</th:block>
|
||||
<th:block th:if="${momentsResult == null or #lists.isEmpty(momentsResult.items)}">
|
||||
<div class="wi-moments-page__empty">
|
||||
<p th:text="#{moments.noMoments}">No moments yet</p>
|
||||
<p th:text="'还没有瞬间,快去记录当下的心情吧 🌙'">还没有瞬间,快去记录当下的心情吧 🌙</p>
|
||||
</div>
|
||||
</th:block>
|
||||
</div>
|
||||
@@ -141,13 +150,13 @@ import LightGallery from "../components/LightGallery.astro";
|
||||
class="wi-moments-page__page-btn"
|
||||
th:if="${momentsResult.hasPrevious()}"
|
||||
th:href="${momentsResult.prevUrl}"
|
||||
th:text="#{common.newer}">Newer</a>
|
||||
th:text="'较新'">较新</a>
|
||||
<span class="wi-moments-page__page-spacer" th:if="${momentsResult.hasPrevious() and momentsResult.hasNext()}"></span>
|
||||
<a
|
||||
class="wi-moments-page__page-btn"
|
||||
th:if="${momentsResult.hasNext()}"
|
||||
th:href="${momentsResult.nextUrl}"
|
||||
th:text="#{common.older}">Older</a>
|
||||
th:text="'较旧'">较旧</a>
|
||||
</nav>
|
||||
|
||||
<div
|
||||
@@ -157,7 +166,7 @@ import LightGallery from "../components/LightGallery.astro";
|
||||
></div>
|
||||
<th:block th:if="${momentFinder == null}">
|
||||
<div class="wi-moments-page__empty">
|
||||
<p th:text="#{common.pluginError}">Plugin error</p>
|
||||
<p th:text="'插件加载异常,请检查插件状态'">插件加载异常,请检查插件状态</p>
|
||||
</div>
|
||||
</th:block>
|
||||
</div>
|
||||
@@ -165,7 +174,7 @@ import LightGallery from "../components/LightGallery.astro";
|
||||
|
||||
<th:block th:unless="${pluginFinder.available('PluginMoments')}">
|
||||
<div class="wi-moments-page__empty">
|
||||
<p th:utext="#{common.installPlugin('Moments', 'https://www.halo.run/store/apps/app-hqbe')}">This feature requires the Moments plugin</p>
|
||||
<p>该功能需要安装「瞬间」插件,<a href="https://www.halo.run/store/apps/app-SnwWD" target="_blank" rel="noopener noreferrer">前往应用市场安装</a></p>
|
||||
</div>
|
||||
</th:block>
|
||||
</div>
|
||||
@@ -174,7 +183,7 @@ import LightGallery from "../components/LightGallery.astro";
|
||||
<LightGallery />
|
||||
</Layout>
|
||||
|
||||
<script is:inline>
|
||||
<script is:inline th:inline="none">
|
||||
(function () {
|
||||
document.addEventListener("click", function (e) {
|
||||
var likeBtn = e.target.closest(".wi-moments-page__like-btn");
|
||||
@@ -350,9 +359,7 @@ import LightGallery from "../components/LightGallery.astro";
|
||||
sentinel.style.display = "none";
|
||||
var endMsg = document.createElement("div");
|
||||
endMsg.className = "wi-moments-page__empty";
|
||||
var momentsSection = document.querySelector(".wi-moments-page");
|
||||
var allMomentsText = momentsSection ? momentsSection.getAttribute("data-i18n-all-moments-loaded") : "All moments loaded";
|
||||
endMsg.innerHTML = "<p>" + allMomentsText + "</p>";
|
||||
endMsg.innerHTML = "<p>已加载全部瞬间</p>";
|
||||
sentinel.parentNode.appendChild(endMsg);
|
||||
}
|
||||
|
||||
@@ -376,6 +383,21 @@ import LightGallery from "../components/LightGallery.astro";
|
||||
padding-block: 1rem;
|
||||
}
|
||||
|
||||
.wi-moments-page__hero {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
height: clamp(220px, 30vw, 380px);
|
||||
overflow: hidden;
|
||||
background: var(--bg-raised);
|
||||
}
|
||||
|
||||
.wi-moments-page__cover {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.wi-moments-page__header {
|
||||
text-align: center;
|
||||
margin-bottom: var(--space-3xl);
|
||||
@@ -678,6 +700,10 @@ import LightGallery from "../components/LightGallery.astro";
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.wi-moments-page__hero {
|
||||
height: clamp(180px, 36vw, 260px);
|
||||
}
|
||||
|
||||
.wi-moments-page__masonry {
|
||||
columns: 1;
|
||||
}
|
||||
|
||||
@@ -92,21 +92,6 @@ import LightGallery from "../components/LightGallery.astro";
|
||||
color: var(--ink);
|
||||
}
|
||||
|
||||
.wi-page__body :is(h1, h2, h3, h4, h5, h6) {
|
||||
font-family: var(--font-sans);
|
||||
margin-top: 2em;
|
||||
margin-bottom: 0.6em;
|
||||
}
|
||||
|
||||
.wi-page__body p {
|
||||
margin-block-end: 1.2em;
|
||||
}
|
||||
|
||||
.wi-page__body img {
|
||||
border-radius: 12px;
|
||||
margin-block: var(--space-lg);
|
||||
}
|
||||
|
||||
.wi-page__body a {
|
||||
text-decoration: underline;
|
||||
text-decoration-thickness: 1px;
|
||||
|
||||
@@ -5,7 +5,7 @@ import Layout from "../layouts/Layout.astro";
|
||||
<Layout>
|
||||
<Fragment slot="head">
|
||||
<title
|
||||
th:text="|${theme.config?.messageboard?.messageboard_title ?: #messages.msg('messageboard.title')} - ${site.title}|"
|
||||
th:text="|${theme.config?.messageboard?.messageboard_title ?: '留言板'} - ${site.title}|"
|
||||
></title>
|
||||
</Fragment>
|
||||
|
||||
@@ -14,12 +14,12 @@ import Layout from "../layouts/Layout.astro";
|
||||
<span class="wi-messageboard__deco">"</span>
|
||||
<h1
|
||||
class="wi-messageboard__title"
|
||||
th:text="${theme.config?.messageboard?.messageboard_title ?: #messages.msg('messageboard.title')}"
|
||||
th:text="${theme.config?.messageboard?.messageboard_title ?: '留言板'}"
|
||||
></h1>
|
||||
<span class="wi-page-accent"></span>
|
||||
<p
|
||||
class="wi-messageboard__subtitle"
|
||||
th:text="${theme.config?.messageboard?.messageboard_subtitle ?: #messages.msg('messageboard.subtitle')}"
|
||||
th:text="${theme.config?.messageboard?.messageboard_subtitle ?: '在这里留下你的足迹 🌙'}"
|
||||
></p>
|
||||
</header>
|
||||
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -5,7 +5,7 @@ import LightGallery from "../components/LightGallery.astro";
|
||||
|
||||
<Layout wide>
|
||||
<Fragment slot="head">
|
||||
<title th:text="|${title ?: (theme.config?.photos?.photos_page_title ?: #messages.msg('photos.title'))} - ${site.title}|"></title>
|
||||
<title th:text="|${title ?: (theme.config?.photos?.photos_page_title ?: '相册')} - ${site.title}|"></title>
|
||||
</Fragment>
|
||||
|
||||
<section class="wi-photos-page">
|
||||
@@ -13,7 +13,7 @@ import LightGallery from "../components/LightGallery.astro";
|
||||
<div class="wi-photos-page__header">
|
||||
<h1
|
||||
class="wi-photos-page__title"
|
||||
th:text="${theme.config?.photos?.photos_page_title ?: #messages.msg('photos.title')}"
|
||||
th:text="${theme.config?.photos?.photos_page_title ?: '相册'}"
|
||||
>
|
||||
Gallery
|
||||
</h1>
|
||||
@@ -30,7 +30,7 @@ import LightGallery from "../components/LightGallery.astro";
|
||||
th:href="@{${photoUrl.list()}}"
|
||||
class="wi-photos-page__group-link"
|
||||
th:classappend="${#strings.isEmpty(currentGroup)} ? 'wi-photos-page__group-link--active' : ''"
|
||||
th:text="#{common.all}"
|
||||
th:text="'全部'"
|
||||
>
|
||||
All
|
||||
</a>
|
||||
@@ -49,7 +49,7 @@ import LightGallery from "../components/LightGallery.astro";
|
||||
<div class="wi-photos-page__all-groups">
|
||||
<th:block th:if="${showUngrouped and photos != null and !#lists.isEmpty(photos.items)}">
|
||||
<div class="wi-photos-page__section">
|
||||
<h2 class="wi-photos-page__section-title" th:text="#{common.all}">All</h2>
|
||||
<h2 class="wi-photos-page__section-title" th:text="'全部'">全部</h2>
|
||||
<div
|
||||
class="wi-photos-page__grid"
|
||||
th:attr="data-style=${theme.config?.photos?.photos_style ?: 'masonry'}, data-columns=${theme.config?.photos?.photos_columns ?: 3}"
|
||||
@@ -241,7 +241,7 @@ import LightGallery from "../components/LightGallery.astro";
|
||||
</div>
|
||||
</div>
|
||||
<div class="wi-photos-page__empty" th:if="${#lists.isEmpty(group.photos)}">
|
||||
<p th:text="#{common.noPhotosInGroup}">No photos in this group.</p>
|
||||
<p th:text="'该分组暂无图片'">该分组暂无图片</p>
|
||||
</div>
|
||||
</th:block>
|
||||
</th:block>
|
||||
@@ -249,20 +249,20 @@ import LightGallery from "../components/LightGallery.astro";
|
||||
</th:block>
|
||||
|
||||
<div class="wi-photos-page__empty" th:if="${#lists.isEmpty(groups) and !(showUngrouped and photos != null and !#lists.isEmpty(photos.items))}">
|
||||
<p th:text="#{common.noPhotos}">No photos yet.</p>
|
||||
<p th:text="'暂无图片'">暂无图片</p>
|
||||
</div>
|
||||
</th:block>
|
||||
|
||||
<th:block th:if="${groups == null}">
|
||||
<div class="wi-photos-page__empty">
|
||||
<p th:text="#{common.pluginError}">Plugin error</p>
|
||||
<p th:text="'插件加载异常,请检查插件状态'">插件加载异常,请检查插件状态</p>
|
||||
</div>
|
||||
</th:block>
|
||||
</th:block>
|
||||
|
||||
<th:block th:unless="${pluginFinder.available('PluginPhotos')}">
|
||||
<div class="wi-photos-page__empty">
|
||||
<p th:utext="#{common.installPlugin('Gallery', 'https://www.halo.run/store/apps/app-PgHgw')}">This feature requires the Gallery plugin</p>
|
||||
<p>该功能需要安装「相册」插件,<a href="https://www.halo.run/store/apps/app-BmQJW" target="_blank" rel="noopener noreferrer">前往应用市场安装</a></p>
|
||||
</div>
|
||||
</th:block>
|
||||
</div>
|
||||
@@ -547,7 +547,7 @@ import LightGallery from "../components/LightGallery.astro";
|
||||
}
|
||||
</style>
|
||||
|
||||
<script is:inline>
|
||||
<script is:inline th:inline="none">
|
||||
(function () {
|
||||
var rafPending = false;
|
||||
|
||||
|
||||
+88
-237
@@ -38,7 +38,7 @@ import LightGallery from "../components/LightGallery.astro";
|
||||
|
||||
<div class="wi-reading-progress" id="wi-reading-progress"></div>
|
||||
|
||||
<article class="wi-post" th:attr="data-i18n-words=#{common.words},data-i18n-read-time=#{common.readTime}">
|
||||
<article class="wi-post">
|
||||
<header class="wi-post__header">
|
||||
<div
|
||||
th:if="${theme.config?.article?.article_show_cover != false and !#strings.isEmpty(post.spec.cover)}"
|
||||
@@ -111,12 +111,14 @@ import LightGallery from "../components/LightGallery.astro";
|
||||
th:if="${theme.config?.article?.article_show_word_count ?: true}"
|
||||
class="wi-post__stat-item"
|
||||
id="wi-word-count"
|
||||
>-- words</span>
|
||||
th:with="serverWords=${pluginFinder.available('extra-api') ? extraApiStatsFinder.getPostWordCount({name: post.metadata.name}) : 0}"
|
||||
th:text="${serverWords > 0 ? serverWords + ' 字' : ''}"
|
||||
></span>
|
||||
<span
|
||||
th:if="${theme.config?.article?.article_show_visit_count ?: true}"
|
||||
class="wi-post__stat-item"
|
||||
>
|
||||
<th:block th:text="${post.stats?.visit ?: 0}"></th:block> <th:block th:text="#{common.views}"></th:block>
|
||||
<th:block th:text="${post.stats?.visit ?: 0}"></th:block> <th:block th:text="'阅读'"></th:block>
|
||||
</span>
|
||||
<span
|
||||
th:if="${theme.config?.article?.article_show_read_time ?: false}"
|
||||
@@ -131,7 +133,10 @@ import LightGallery from "../components/LightGallery.astro";
|
||||
<div
|
||||
class="wi-post__body"
|
||||
id="wi-post-body"
|
||||
th:utext="${post.content.content}"
|
||||
th:with="hasExtra=${pluginFinder.available('extra-api')},
|
||||
spaced=${hasExtra and (theme.config?.article?.article_extra_spacing ?: true) ? extraApiRenderFinder.applySpacingInHtml(post.content.content) : post.content.content},
|
||||
rendered=${hasExtra and (theme.config?.article?.article_extra_highlight ?: false) and extraApiPluginInfoFinder.isFullVersion() ? extraApiJsRenderFinder.highlightCodeInHtml(spaced) : spaced}"
|
||||
th:utext="${rendered}"
|
||||
></div>
|
||||
|
||||
<aside
|
||||
@@ -141,7 +146,7 @@ import LightGallery from "../components/LightGallery.astro";
|
||||
th:attr="data-position=${theme.config?.article?.article_toc_position ?: 'left'},data-expand=${theme.config?.article?.article_toc_expand ?: 'active'}"
|
||||
>
|
||||
<div class="wi-toc__header">
|
||||
<span class="wi-toc__title" th:text="#{article.toc}">目录</span>
|
||||
<span class="wi-toc__title" th:text="'目录'">目录</span>
|
||||
</div>
|
||||
<nav class="wi-toc__nav" id="wi-toc-nav"></nav>
|
||||
</aside>
|
||||
@@ -158,7 +163,7 @@ import LightGallery from "../components/LightGallery.astro";
|
||||
</div>
|
||||
|
||||
<div class="wi-post__share" th:if="${theme.config?.article?.article_show_share != false}" th:with="shareUrl=${post.status.permalink}, shareTitle=${post.spec.title}, shareChannels=${theme.config?.article?.article_share_channels ?: 'qq,wechat,weibo'}">
|
||||
<span class="wi-post__share-label" th:text="${theme.config?.article?.article_share_title ?: #messages.msg('share.title')}">分享</span>
|
||||
<span class="wi-post__share-label" th:text="${theme.config?.article?.article_share_title ?: '分享'}">分享</span>
|
||||
<div class="wi-post__share-buttons">
|
||||
<button th:if="${#strings.contains(shareChannels, 'qq')}" class="wi-post__share-btn wi-post__share-btn--qq" type="button" th:attr="data-url=${shareUrl},data-title=${shareTitle}" title="QQ">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 640" width="18" height="18" fill="currentColor"><!--!Font Awesome Free v7.2.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2026 Fonticons, Inc.--><path d="M530.1 484.4C518.6 485.8 485.2 431.7 485.2 431.7C485.2 463 469.1 503.9 434.2 533.5C451 538.7 489 552.7 480 567.9C472.7 580.2 354.5 575.8 320.4 571.9C286.3 575.7 168.1 580.2 160.8 567.9C151.8 552.7 189.7 538.7 206.6 533.5C171.7 504 155.5 463.1 155.5 431.7C155.5 431.7 122.2 485.8 110.6 484.4C105.2 483.8 98.2 454.8 119.9 384.7C130.2 351.7 141.9 324.2 160 278.9C156.9 162 205.2 63.9 320.3 63.9C434 63.9 483.5 160 480.6 278.9C498.7 324.1 510.5 351.8 520.7 384.7C542.5 454.8 535.4 483.8 530 484.4z"/></svg>
|
||||
@@ -189,9 +194,9 @@ import LightGallery from "../components/LightGallery.astro";
|
||||
<button class="wi-wechat-qr__close" type="button" aria-label="Close">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M18 6 6 18"/><path d="m6 6 12 12"/></svg>
|
||||
</button>
|
||||
<div class="wi-wechat-qr__title" th:text="#{share.wechatQR ?: '微信扫码分享'}">微信扫码分享</div>
|
||||
<div class="wi-wechat-qr__title" th:text="'微信扫码分享'">微信扫码分享</div>
|
||||
<canvas class="wi-wechat-qr__canvas" id="wi-wechat-qr-canvas"></canvas>
|
||||
<div class="wi-wechat-qr__hint" th:text="#{share.wechatHint ?: '打开微信,扫描二维码即可分享'}">打开微信,扫描二维码即可分享</div>
|
||||
<div class="wi-wechat-qr__hint" th:text="'打开微信,扫描二维码即可分享'">打开微信,扫描二维码即可分享</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -205,7 +210,7 @@ import LightGallery from "../components/LightGallery.astro";
|
||||
th:href="@{${cursor.previous.status.permalink}}"
|
||||
class="wi-post__nav-link wi-post__nav-link--prev"
|
||||
>
|
||||
<span class="wi-post__nav-label" th:text="#{article.previousPost}">上一篇</span>
|
||||
<span class="wi-post__nav-label" th:text="'上一篇'">上一篇</span>
|
||||
<span
|
||||
class="wi-post__nav-title"
|
||||
th:text="${cursor.previous.spec.title}"
|
||||
@@ -220,7 +225,7 @@ import LightGallery from "../components/LightGallery.astro";
|
||||
th:href="@{${cursor.next.status.permalink}}"
|
||||
class="wi-post__nav-link wi-post__nav-link--next"
|
||||
>
|
||||
<span class="wi-post__nav-label" th:text="#{article.nextPost}">下一篇</span>
|
||||
<span class="wi-post__nav-label" th:text="'下一篇'">下一篇</span>
|
||||
<span
|
||||
class="wi-post__nav-title"
|
||||
th:text="${cursor.next.spec.title}"
|
||||
@@ -236,7 +241,7 @@ import LightGallery from "../components/LightGallery.astro";
|
||||
|
||||
<th:block th:if="${theme.config?.article?.article_show_related == true}">
|
||||
<section class="wi-post__related" th:with="relatedCat=${post.categories != null and !post.categories.isEmpty()} ? ${post.categories[0].metadata.name} : null, relatedPosts=${relatedCat != null} ? ${postFinder.list({size: 3, categoryName: relatedCat})} : ${postFinder.list({size: 3})}">
|
||||
<h2 class="wi-post__related-title" th:text="${theme.config?.article?.article_related_title ?: #messages.msg('article.relatedPosts')}">相关推荐</h2>
|
||||
<h2 class="wi-post__related-title" th:text="${theme.config?.article?.article_related_title ?: '相关推荐'}">相关推荐</h2>
|
||||
<div class="wi-post__related-grid">
|
||||
<a th:each="rp : ${relatedPosts.items}" th:if="${rp.metadata.name != post.metadata.name}" th:href="@{${rp.status.permalink}}" class="wi-post__related-card">
|
||||
<div class="wi-post__related-cover" th:if="${!#strings.isEmpty(rp.spec.cover)}">
|
||||
@@ -266,7 +271,7 @@ import LightGallery from "../components/LightGallery.astro";
|
||||
class="wi-toc-fab"
|
||||
id="wi-toc-fab"
|
||||
type="button"
|
||||
th:aria-label="#{common.openToc}"
|
||||
th:aria-label="'打开目录'"
|
||||
>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><line x1="8" x2="21" y1="6" y2="6"/><line x1="8" x2="21" y1="12" y2="12"/><line x1="8" x2="21" y1="18" y2="18"/><line x1="3" x2="3.01" y1="6" y2="6"/><line x1="3" x2="3.01" y1="12" y2="12"/><line x1="3" x2="3.01" y1="18" y2="18"/></svg>
|
||||
</button>
|
||||
@@ -279,8 +284,8 @@ import LightGallery from "../components/LightGallery.astro";
|
||||
<div class="wi-toc-drawer__overlay"></div>
|
||||
<div class="wi-toc-drawer__panel">
|
||||
<div class="wi-toc-drawer__header">
|
||||
<span class="wi-toc-drawer__title" th:text="#{article.toc}">目录</span>
|
||||
<button class="wi-toc-drawer__close" type="button" th:aria-label="#{common.closeToc}">
|
||||
<span class="wi-toc-drawer__title" th:text="'目录'">目录</span>
|
||||
<button class="wi-toc-drawer__close" type="button" th:aria-label="'关闭目录'">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M18 6 6 18"/><path d="m6 6 12 12"/></svg>
|
||||
</button>
|
||||
</div>
|
||||
@@ -305,7 +310,7 @@ import LightGallery from "../components/LightGallery.astro";
|
||||
|
||||
<script is:inline defer src="https://cdn.jsdelivr.net/npm/prismjs@1.29.0/prism.min.js"></script>
|
||||
<script is:inline defer src="https://cdn.jsdelivr.net/npm/prismjs@1.29.0/plugins/autoloader/prism-autoloader.min.js"></script>
|
||||
<script is:inline>
|
||||
<script is:inline th:inline="none">
|
||||
(function () {
|
||||
if (typeof Prism !== "undefined") {
|
||||
Prism.plugins.autoloader.languages_path = "https://cdn.jsdelivr.net/npm/prismjs@1.29.0/components/";
|
||||
@@ -321,7 +326,7 @@ import LightGallery from "../components/LightGallery.astro";
|
||||
})();
|
||||
</script>
|
||||
|
||||
<script is:inline>
|
||||
<script is:inline th:inline="none">
|
||||
(function () {
|
||||
var progressBar = document.getElementById("wi-reading-progress");
|
||||
if (progressBar) {
|
||||
@@ -347,6 +352,7 @@ import LightGallery from "../components/LightGallery.astro";
|
||||
var body = document.getElementById("wi-post-body");
|
||||
if (!body) return;
|
||||
|
||||
/* 图片:补齐懒加载属性 + 加载前的模糊占位 */
|
||||
body.querySelectorAll("img").forEach(function(img) {
|
||||
if (!img.hasAttribute("loading")) {
|
||||
img.setAttribute("loading", "lazy");
|
||||
@@ -354,22 +360,64 @@ import LightGallery from "../components/LightGallery.astro";
|
||||
if (!img.hasAttribute("decoding")) {
|
||||
img.setAttribute("decoding", "async");
|
||||
}
|
||||
if (img.complete && img.naturalWidth > 0) {
|
||||
img.classList.add("wi-media-loaded");
|
||||
} else {
|
||||
img.classList.add("wi-media-loading");
|
||||
img.addEventListener("load", function () {
|
||||
img.classList.remove("wi-media-loading");
|
||||
img.classList.add("wi-media-loaded");
|
||||
});
|
||||
img.addEventListener("error", function () {
|
||||
img.classList.remove("wi-media-loading");
|
||||
img.classList.add("wi-media-loaded");
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
/* 嵌入媒体(B站 / 腾讯视频 / YouTube 等 iframe):
|
||||
按原始 width/height 生成 aspect-ratio 容器,保证居中且自适应不形变 */
|
||||
body.querySelectorAll("iframe").forEach(function(frame) {
|
||||
if (frame.closest(".wi-embed")) return;
|
||||
var w = parseInt(frame.getAttribute("width"), 10);
|
||||
var h = parseInt(frame.getAttribute("height"), 10);
|
||||
var wrap = document.createElement("div");
|
||||
wrap.className = "wi-embed";
|
||||
if (w > 0 && h > 0) {
|
||||
wrap.style.aspectRatio = w + " / " + h;
|
||||
} else {
|
||||
wrap.style.aspectRatio = "16 / 9";
|
||||
}
|
||||
frame.parentNode.insertBefore(wrap, frame);
|
||||
wrap.appendChild(frame);
|
||||
frame.removeAttribute("width");
|
||||
frame.removeAttribute("height");
|
||||
});
|
||||
|
||||
var showWordCount = document.getElementById("wi-word-count");
|
||||
var showReadTime = document.getElementById("wi-read-time");
|
||||
var articleEl = document.querySelector(".wi-post");
|
||||
var wordsLabel = articleEl ? articleEl.getAttribute("data-i18n-words") : "words";
|
||||
var readTimeLabel = articleEl ? articleEl.getAttribute("data-i18n-read-time") : "min read";
|
||||
var wordsLabel = "字";
|
||||
var readTimeLabel = "分钟";
|
||||
|
||||
if (showWordCount || showReadTime) {
|
||||
var text = (body.textContent || body.innerText || "").trim();
|
||||
var charCount = text.replace(/\s/g, "").length;
|
||||
var readMinutes = Math.max(1, Math.ceil(charCount / 500));
|
||||
|
||||
/* 服务端已统计字数时(API 扩展包插件)直接复用,避免重复计算 */
|
||||
if (showWordCount) {
|
||||
if (showWordCount.textContent.trim()) {
|
||||
var servered = parseInt(showWordCount.textContent.replace(/\D/g, ""), 10);
|
||||
if (servered > 0) {
|
||||
charCount = servered;
|
||||
readMinutes = Math.max(1, Math.ceil(charCount / 500));
|
||||
} else {
|
||||
showWordCount.textContent = charCount + " " + wordsLabel;
|
||||
}
|
||||
} else {
|
||||
showWordCount.textContent = charCount + " " + wordsLabel;
|
||||
}
|
||||
}
|
||||
if (showReadTime) {
|
||||
showReadTime.textContent = readMinutes + " " + readTimeLabel;
|
||||
}
|
||||
@@ -727,20 +775,6 @@ import LightGallery from "../components/LightGallery.astro";
|
||||
});
|
||||
});
|
||||
|
||||
var lazyImgs = document.querySelectorAll(".wi-post__content img[loading='lazy']");
|
||||
lazyImgs.forEach(function (img) {
|
||||
if (img.complete && img.naturalWidth > 0) {
|
||||
img.setAttribute("data-blur", "loaded");
|
||||
} else {
|
||||
img.setAttribute("data-blur", "true");
|
||||
img.addEventListener("load", function () {
|
||||
img.setAttribute("data-blur", "loaded");
|
||||
});
|
||||
img.addEventListener("error", function () {
|
||||
img.setAttribute("data-blur", "loaded");
|
||||
});
|
||||
}
|
||||
});
|
||||
})();
|
||||
</script>
|
||||
</Layout>
|
||||
@@ -874,109 +908,9 @@ import LightGallery from "../components/LightGallery.astro";
|
||||
color: #d4cdc4;
|
||||
}
|
||||
|
||||
.wi-post__body * {
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
.wi-post__body :is(pre, code, table) {
|
||||
overflow-x: auto;
|
||||
}
|
||||
|
||||
.wi-post__body pre {
|
||||
margin-block: 1.5em;
|
||||
padding: 1.25em;
|
||||
border-radius: 12px;
|
||||
border: 1px solid var(--rule);
|
||||
background: var(--bg-raised);
|
||||
font-size: 0.875em;
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
.wi-post__body pre code {
|
||||
background: none;
|
||||
padding: 0;
|
||||
border: none;
|
||||
border-radius: 0;
|
||||
font-size: inherit;
|
||||
}
|
||||
|
||||
.wi-post__body code {
|
||||
background: var(--bg-raised);
|
||||
padding: 0.15em 0.4em;
|
||||
border-radius: 4px;
|
||||
font-size: 0.875em;
|
||||
border: 1px solid var(--rule);
|
||||
}
|
||||
|
||||
.wi-post__body :is(h1, h2, h3, h4, h5, h6) {
|
||||
font-family: var(--font-sans);
|
||||
margin-top: 1.8em;
|
||||
margin-bottom: 0.5em;
|
||||
scroll-margin-top: 80px;
|
||||
}
|
||||
|
||||
.wi-post__body h2 {
|
||||
font-size: 1.5rem;
|
||||
margin-top: 2em;
|
||||
margin-bottom: 0.5em;
|
||||
}
|
||||
|
||||
.wi-post__body h3 {
|
||||
font-size: 1.25rem;
|
||||
}
|
||||
|
||||
.wi-post__body .ai-summary,
|
||||
.wi-post__body [class*="ai-summary"],
|
||||
.wi-post__body [class*="ai-abstract"],
|
||||
.wi-post__body .abstract,
|
||||
.wi-post__body [class*="likcc-article-summary"] {
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
.wi-post__body p {
|
||||
margin-block-end: 1.2em;
|
||||
}
|
||||
|
||||
.wi-post__body img {
|
||||
border-radius: 8px;
|
||||
margin-block: 1.5em;
|
||||
}
|
||||
|
||||
.wi-post__content img {
|
||||
transition: filter 0.4s ease, opacity 0.4s ease;
|
||||
}
|
||||
|
||||
.wi-post__content img[data-blur="true"] {
|
||||
filter: blur(20px);
|
||||
opacity: 0.6;
|
||||
transform: scale(1.05);
|
||||
}
|
||||
|
||||
.wi-post__content img[data-blur="loaded"] {
|
||||
filter: blur(0);
|
||||
opacity: 1;
|
||||
transform: scale(1);
|
||||
}
|
||||
|
||||
.wi-post__body a {
|
||||
text-decoration: underline;
|
||||
text-decoration-thickness: 1px;
|
||||
text-underline-offset: 0.2em;
|
||||
}
|
||||
|
||||
.wi-post__body ul,
|
||||
.wi-post__body ol {
|
||||
padding-inline-start: 1.5em;
|
||||
margin-block-end: 1.2em;
|
||||
}
|
||||
|
||||
.wi-post__body li {
|
||||
margin-block-end: 0.6em;
|
||||
}
|
||||
|
||||
.wi-post__body blockquote {
|
||||
margin-block: 2em;
|
||||
}
|
||||
/* 正文本身及内部元素样式统一放在全局样式 src/styles/_content.scss,
|
||||
因为正文由 Thymeleaf 在运行时注入,不会带 Astro 的 data-astro-cid 作用域属性,
|
||||
写在组件 scoped 样式里对正文内容完全无效。 */
|
||||
|
||||
.wi-toc {
|
||||
display: none;
|
||||
@@ -1135,12 +1069,6 @@ import LightGallery from "../components/LightGallery.astro";
|
||||
gap: 2px;
|
||||
}
|
||||
|
||||
.wi-toc-drawer__nav {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 2px;
|
||||
}
|
||||
|
||||
.wi-post__like {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
@@ -1558,51 +1486,10 @@ import LightGallery from "../components/LightGallery.astro";
|
||||
line-height: 1.82;
|
||||
}
|
||||
|
||||
.wi-post__body p {
|
||||
margin-block-end: 1.4em;
|
||||
}
|
||||
|
||||
.wi-post__body h2 {
|
||||
font-size: 1.6rem;
|
||||
}
|
||||
|
||||
.wi-post__body h3 {
|
||||
font-size: 1.35rem;
|
||||
}
|
||||
|
||||
.wi-post__body h4 {
|
||||
font-size: 1.2rem;
|
||||
}
|
||||
|
||||
.wi-post__body pre {
|
||||
font-size: 0.9em;
|
||||
padding: 1em;
|
||||
}
|
||||
|
||||
.wi-post__body code {
|
||||
font-size: 0.9em;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
<style is:global>
|
||||
.wi-post__body figure {
|
||||
margin-block: 1.5em;
|
||||
}
|
||||
|
||||
.wi-post__body figcaption {
|
||||
color: #545164;
|
||||
font-size: 0.875rem;
|
||||
text-align: center;
|
||||
margin-top: 0.5em;
|
||||
margin-bottom: 1.2em;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
html.dark .wi-post__body figcaption {
|
||||
color: #8a8494;
|
||||
}
|
||||
|
||||
.wi-toc__group {
|
||||
position: relative;
|
||||
}
|
||||
@@ -1615,36 +1502,38 @@ import LightGallery from "../components/LightGallery.astro";
|
||||
}
|
||||
|
||||
.wi-toc__group--active .wi-toc__sub {
|
||||
max-height: 600px;
|
||||
max-height: 1600px;
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.wi-toc__group--expanded .wi-toc__sub {
|
||||
max-height: 600px;
|
||||
max-height: 1600px;
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.wi-toc[data-expand="all"] .wi-toc__sub {
|
||||
max-height: 600px;
|
||||
max-height: 1600px;
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.wi-toc__link {
|
||||
display: block;
|
||||
padding: 4px 0;
|
||||
display: -webkit-box;
|
||||
-webkit-line-clamp: 2;
|
||||
-webkit-box-orient: vertical;
|
||||
overflow: hidden;
|
||||
padding: 5px 8px;
|
||||
font-size: var(--text-xs);
|
||||
line-height: var(--leading-snug);
|
||||
color: var(--ink-3);
|
||||
text-decoration: none;
|
||||
transition: color 0.15s ease, background 0.15s ease, border-color 0.15s ease;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
border-radius: 6px;
|
||||
border-left: 2px solid transparent;
|
||||
transition: color 0.15s ease, background 0.15s ease, border-color 0.15s ease;
|
||||
}
|
||||
|
||||
.wi-toc__link:hover {
|
||||
color: var(--accent);
|
||||
background: color-mix(in srgb, var(--accent) 5%, transparent);
|
||||
}
|
||||
|
||||
.wi-toc__link--active {
|
||||
@@ -1655,56 +1544,23 @@ import LightGallery from "../components/LightGallery.astro";
|
||||
}
|
||||
|
||||
.wi-toc__link--group {
|
||||
padding-left: 0;
|
||||
padding-right: 4px;
|
||||
font-weight: 500;
|
||||
padding-left: 8px;
|
||||
padding-right: 8px;
|
||||
font-weight: 600;
|
||||
color: var(--ink-2);
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.wi-toc__link--group::before {
|
||||
content: "";
|
||||
display: inline-block;
|
||||
width: 4px;
|
||||
height: 4px;
|
||||
border-radius: 50%;
|
||||
background: var(--ink-3);
|
||||
margin-right: 6px;
|
||||
vertical-align: middle;
|
||||
transition: background 0.15s ease;
|
||||
}
|
||||
|
||||
.wi-toc__link--group::after {
|
||||
content: '';
|
||||
display: inline-block;
|
||||
width: 5px;
|
||||
height: 5px;
|
||||
border-right: 1.5px solid var(--ink-3);
|
||||
border-bottom: 1.5px solid var(--ink-3);
|
||||
transform: rotate(-45deg);
|
||||
margin-left: 6px;
|
||||
vertical-align: middle;
|
||||
transition: transform 0.2s ease, border-color 0.15s ease;
|
||||
}
|
||||
|
||||
.wi-toc__group--expanded > .wi-toc__link--group::after,
|
||||
.wi-toc__group--active > .wi-toc__link--group::after {
|
||||
transform: rotate(45deg);
|
||||
}
|
||||
|
||||
.wi-toc__link--active.wi-toc__link--group::before {
|
||||
background: var(--accent);
|
||||
}
|
||||
|
||||
.wi-toc__link--h3 {
|
||||
padding-left: 16px;
|
||||
padding-left: 22px;
|
||||
}
|
||||
|
||||
.wi-toc__link--h4 {
|
||||
padding-left: 32px;
|
||||
padding-left: 36px;
|
||||
}
|
||||
|
||||
.wi-toc__link--h5 {
|
||||
padding-left: 48px;
|
||||
padding-left: 50px;
|
||||
}
|
||||
|
||||
.wi-toc__link--h6 {
|
||||
@@ -1715,11 +1571,6 @@ import LightGallery from "../components/LightGallery.astro";
|
||||
border-left-color: var(--accent);
|
||||
}
|
||||
|
||||
.wi-toc__sub .wi-toc__link--h3.wi-toc__link--active { padding-left: 14px; }
|
||||
.wi-toc__sub .wi-toc__link--h4.wi-toc__link--active { padding-left: 30px; }
|
||||
.wi-toc__sub .wi-toc__link--h5.wi-toc__link--active { padding-left: 46px; }
|
||||
.wi-toc__sub .wi-toc__link--h6.wi-toc__link--active { padding-left: 62px; }
|
||||
|
||||
.wi-toc-drawer__nav .wi-toc__link {
|
||||
font-size: var(--text-sm);
|
||||
padding: 6px 0;
|
||||
|
||||
+13
-13
@@ -55,35 +55,35 @@ import Layout from "../layouts/Layout.astro";
|
||||
th:href="${profile.summary?.profileUrl}"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
th:text="#{steam.homepage}">Steam Homepage →</a>
|
||||
th:text="'Steam 主页'">Steam Homepage →</a>
|
||||
</div>
|
||||
<div class="wi-steam__level" th:if="${profile.steamLevel != null}">
|
||||
<span class="wi-steam__level-num" th:text="${profile.steamLevel}"></span>
|
||||
<span class="wi-steam__level-label" th:text="#{steam.level}">Level</span>
|
||||
<span class="wi-steam__level-label" th:text="'等级'">等级</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="wi-steam__stats" th:if="${stats != null}">
|
||||
<div class="wi-steam__stat-card">
|
||||
<span class="wi-steam__stat-value" th:text="${stats.totalGames} ?: 0">0</span>
|
||||
<span class="wi-steam__stat-label" th:text="#{steam.ownedGames}">Owned Games</span>
|
||||
<span class="wi-steam__stat-label" th:text="'拥有游戏'">拥有游戏</span>
|
||||
</div>
|
||||
<div class="wi-steam__stat-card">
|
||||
<span class="wi-steam__stat-value" th:text="${stats.totalPlaytimeFormatted} ?: '0 hours'">0 hours</span>
|
||||
<span class="wi-steam__stat-label" th:text="#{steam.totalPlaytime}">Total Playtime</span>
|
||||
<span class="wi-steam__stat-label" th:text="'总游玩时长'">总游玩时长</span>
|
||||
</div>
|
||||
<div class="wi-steam__stat-card">
|
||||
<span class="wi-steam__stat-value" th:text="${stats.recentPlaytimeFormatted} ?: '0 hours'">0 hours</span>
|
||||
<span class="wi-steam__stat-label" th:text="#{steam.recentPlaytime}">Recent 2 Weeks</span>
|
||||
<span class="wi-steam__stat-label" th:text="'近两周'">近两周</span>
|
||||
</div>
|
||||
<div class="wi-steam__stat-card">
|
||||
<span class="wi-steam__stat-value" th:text="${badges != null and badges.totalBadges != null ? badges.totalBadges : 0}">0</span>
|
||||
<span class="wi-steam__stat-label" th:text="#{steam.badges}">Badges</span>
|
||||
<span class="wi-steam__stat-label" th:text="'徽章'">徽章</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="wi-steam__section" th:if="${recentGames != null and !recentGames.isEmpty()}">
|
||||
<h3 class="wi-steam__section-title" th:text="#{steam.recentlyPlayed}">Recently Played</h3>
|
||||
<h3 class="wi-steam__section-title" th:text="'最近游玩'">最近游玩</h3>
|
||||
<div class="wi-steam__recent-scroll">
|
||||
<article class="wi-steam__recent-card" th:each="game : ${recentGames}">
|
||||
<a
|
||||
@@ -107,7 +107,7 @@ import Layout from "../layouts/Layout.astro";
|
||||
<div class="wi-steam__recent-body">
|
||||
<h4 class="wi-steam__recent-name" th:text="${game.name}"></h4>
|
||||
<div class="wi-steam__recent-meta">
|
||||
<span class="wi-steam__recent-time" th:text="${game.playtime2WeeksFormatted} ?: #{steam.hours}"></span>
|
||||
<span class="wi-steam__recent-time" th:text="${game.playtime2WeeksFormatted} ?: '小时'"></span>
|
||||
<span
|
||||
class="wi-steam__recent-achievements"
|
||||
th:if="${game.achievementProgressText != null}"
|
||||
@@ -122,7 +122,7 @@ import Layout from "../layouts/Layout.astro";
|
||||
</th:block>
|
||||
|
||||
<div class="wi-steam__section">
|
||||
<h3 class="wi-steam__section-title" th:text="#{steam.gameLibrary}">Game Library</h3>
|
||||
<h3 class="wi-steam__section-title" th:text="'游戏库'">游戏库</h3>
|
||||
<div class="wi-steam__games-grid">
|
||||
<article class="wi-steam__game-card" th:each="game : ${games.items}">
|
||||
<a
|
||||
@@ -146,11 +146,11 @@ import Layout from "../layouts/Layout.astro";
|
||||
<div class="wi-steam__game-body">
|
||||
<h4 class="wi-steam__game-name" th:text="${game.name}"></h4>
|
||||
<div class="wi-steam__game-meta">
|
||||
<span class="wi-steam__game-time" th:text="${game.playtimeFormatted} ?: #{steam.hours}"></span>
|
||||
<span class="wi-steam__game-time" th:text="${game.playtimeFormatted} ?: '小时'"></span>
|
||||
<span
|
||||
class="wi-steam__game-last"
|
||||
th:if="${!#strings.isEmpty(game.lastPlayedFormatted)}"
|
||||
th:text="#{steam.lastPlayed(${game.lastPlayedFormatted})}"
|
||||
th:text="|最后游玩 ${game.lastPlayedFormatted}|"
|
||||
></span>
|
||||
</div>
|
||||
</div>
|
||||
@@ -166,13 +166,13 @@ import Layout from "../layouts/Layout.astro";
|
||||
class="wi-steam__page-btn"
|
||||
th:if="${games.hasPrevious()}"
|
||||
th:href="${games.prevUrl}"
|
||||
th:text="#{common.newer}">Previous</a>
|
||||
th:text="'较新'">较新</a>
|
||||
<span class="wi-steam__page-spacer" th:if="${games.hasPrevious() and games.hasNext()}"></span>
|
||||
<a
|
||||
class="wi-steam__page-btn"
|
||||
th:if="${games.hasNext()}"
|
||||
th:href="${games.nextUrl}"
|
||||
th:text="#{common.older}">Next</a>
|
||||
th:text="'较旧'">较旧</a>
|
||||
</nav>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
+4
-4
@@ -11,7 +11,7 @@ import Layout from "../layouts/Layout.astro";
|
||||
<header class="wi-tag__header">
|
||||
<h1 class="wi-tag__title" th:text="${tag.spec.displayName}"></h1>
|
||||
<span class="wi-page-accent"></span>
|
||||
<p class="wi-tag__subtitle" th:text="#{common.postCount(${posts.total})}"></p>
|
||||
<p class="wi-tag__subtitle" th:text="|共 ${posts.total} 篇文章|">共 0 篇文章</p>
|
||||
<p
|
||||
th:if="${not #strings.isEmpty(tag.spec.description)}"
|
||||
class="wi-tag__description"
|
||||
@@ -55,7 +55,7 @@ import Layout from "../layouts/Layout.astro";
|
||||
<div
|
||||
th:if="${posts.total == 0}"
|
||||
class="wi-tag__empty"
|
||||
th:text="${theme.config?.home?.home_label_no_posts ?: #messages.msg('common.noPosts')}"
|
||||
th:text="${theme.config?.home?.home_label_no_posts ?: '暂无文章'}"
|
||||
>No posts yet.</div>
|
||||
|
||||
<nav
|
||||
@@ -67,7 +67,7 @@ import Layout from "../layouts/Layout.astro";
|
||||
th:if="${posts.hasPrevious()}"
|
||||
th:href="@{${posts.prevUrl}}"
|
||||
class="wi-tag__page-link wi-tag__page-link--prev"
|
||||
th:text="${theme.config?.home?.home_label_newer ?: #messages.msg('common.newer')}"
|
||||
th:text="${theme.config?.home?.home_label_newer ?: '较新'}"
|
||||
>← Newer</a>
|
||||
<span
|
||||
class="wi-tag__page-info"
|
||||
@@ -77,7 +77,7 @@ import Layout from "../layouts/Layout.astro";
|
||||
th:if="${posts.hasNext()}"
|
||||
th:href="@{${posts.nextUrl}}"
|
||||
class="wi-tag__page-link wi-tag__page-link--next"
|
||||
th:text="${theme.config?.home?.home_label_older ?: #messages.msg('common.older')}"
|
||||
th:text="${theme.config?.home?.home_label_older ?: '较旧'}"
|
||||
>Older →</a>
|
||||
</nav>
|
||||
</div>
|
||||
|
||||
@@ -4,14 +4,14 @@ import Layout from "../layouts/Layout.astro";
|
||||
|
||||
<Layout>
|
||||
<Fragment slot="head">
|
||||
<title th:text="|#{tags.title} - ${site.title}|"></title>
|
||||
<title th:text="|标签 - ${site.title}|"></title>
|
||||
</Fragment>
|
||||
|
||||
<div class="wi-tags">
|
||||
<header class="wi-tags__header">
|
||||
<h1 class="wi-tags__title" th:text="#{tags.title}">标签</h1>
|
||||
<h1 class="wi-tags__title" th:text="'标签'">标签</h1>
|
||||
<span class="wi-page-accent"></span>
|
||||
<p class="wi-tags__subtitle" th:text="${#messages.msg('common.tagCount', tags.size())}"></p>
|
||||
<p class="wi-tags__subtitle" th:text="|共 ${tags.size()} 个标签|">共 0 个标签</p>
|
||||
</header>
|
||||
|
||||
<div class="wi-tags__cloud" role="list" aria-label="Tag list">
|
||||
|
||||
@@ -0,0 +1,158 @@
|
||||
---
|
||||
import Layout from "../layouts/Layout.astro";
|
||||
---
|
||||
|
||||
<Layout wide>
|
||||
<Fragment slot="head">
|
||||
<title th:text="|${theme.config?.weread?.weread_page_title ?: '书架'} - ${site.title}|"></title>
|
||||
</Fragment>
|
||||
|
||||
<section class="wi-weread">
|
||||
<div class="wi-container">
|
||||
<div class="wi-weread__header">
|
||||
<h1
|
||||
class="wi-weread__title"
|
||||
th:text="${theme.config?.weread?.weread_page_title ?: '书架'}"
|
||||
>
|
||||
书架
|
||||
</h1>
|
||||
<p
|
||||
class="wi-weread__subtitle"
|
||||
th:if="${theme.config?.weread?.weread_page_subtitle}"
|
||||
th:text="${theme.config?.weread?.weread_page_subtitle}"
|
||||
>
|
||||
我在读的书
|
||||
</p>
|
||||
<span class="wi-page-accent"></span>
|
||||
</div>
|
||||
|
||||
<th:block th:if="${pluginFinder.available('halo-weread-plugin')}">
|
||||
<div
|
||||
class="wi-weread__shelf"
|
||||
id="weread-shelf-wrapper"
|
||||
th:attr="data-endpoint=@{/halo-weread-plugin/shelf-html}"
|
||||
>
|
||||
<p class="wi-weread__loading">正在展开书架…</p>
|
||||
</div>
|
||||
</th:block>
|
||||
|
||||
<div
|
||||
class="wi-weread__empty"
|
||||
th:unless="${pluginFinder.available('halo-weread-plugin')}"
|
||||
>
|
||||
<p>
|
||||
该功能需要安装「微信读书(WeRead)信息展示」插件,<a
|
||||
href="https://github.com/yeora26/halo-weread-plugin"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>前往插件仓库了解</a
|
||||
>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<script is:inline th:inline="none">
|
||||
(function () {
|
||||
var container = document.getElementById("weread-shelf-wrapper");
|
||||
if (!container) return;
|
||||
|
||||
var endpoint =
|
||||
container.getAttribute("data-endpoint") ||
|
||||
"/halo-weread-plugin/shelf-html";
|
||||
|
||||
fetch(endpoint)
|
||||
.then(function (res) {
|
||||
if (!res.ok) throw new Error("HTTP " + res.status);
|
||||
return res.text();
|
||||
})
|
||||
.then(function (html) {
|
||||
container.innerHTML = html;
|
||||
|
||||
/* innerHTML 插入的脚本不会执行,需要重建后才会运行 */
|
||||
var oldScripts = container.querySelectorAll("script");
|
||||
Array.prototype.forEach.call(oldScripts, function (oldScript) {
|
||||
var script = document.createElement("script");
|
||||
script.textContent = oldScript.textContent;
|
||||
oldScript.parentNode.replaceChild(script, oldScript);
|
||||
});
|
||||
})
|
||||
.catch(function () {
|
||||
container.innerHTML =
|
||||
'<p class="wi-weread__loading">书架暂时无法展开,请稍后再试。</p>';
|
||||
});
|
||||
})();
|
||||
</script>
|
||||
</Layout>
|
||||
|
||||
<style>
|
||||
.wi-weread {
|
||||
padding-block: 2rem 3rem;
|
||||
}
|
||||
|
||||
.wi-weread__header {
|
||||
text-align: center;
|
||||
margin-bottom: var(--space-2xl);
|
||||
}
|
||||
|
||||
.wi-weread__title {
|
||||
font-family: var(--font-sans);
|
||||
font-size: var(--text-3xl);
|
||||
font-weight: 700;
|
||||
color: var(--ink);
|
||||
letter-spacing: var(--tracking-tight);
|
||||
margin-bottom: var(--space-sm);
|
||||
}
|
||||
|
||||
.wi-weread__subtitle {
|
||||
font-size: var(--text-base);
|
||||
color: var(--ink-3);
|
||||
line-height: var(--leading-relaxed);
|
||||
}
|
||||
|
||||
.wi-weread__shelf {
|
||||
min-height: 200px;
|
||||
}
|
||||
|
||||
.wi-weread__loading {
|
||||
text-align: center;
|
||||
color: var(--ink-3);
|
||||
font-size: var(--text-sm);
|
||||
padding: var(--space-2xl) 0;
|
||||
}
|
||||
|
||||
.wi-weread__empty {
|
||||
text-align: center;
|
||||
padding: var(--space-2xl) var(--space-lg);
|
||||
border: 1px dashed var(--rule);
|
||||
border-radius: var(--radius-lg, 16px);
|
||||
color: var(--ink-3);
|
||||
font-size: var(--text-sm);
|
||||
}
|
||||
|
||||
.wi-weread__empty a {
|
||||
color: var(--accent);
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.wi-weread__empty a:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
/* 插件返回的书架内容:收敛为与主题一致的卡片观感 */
|
||||
.wi-weread__shelf :global(img) {
|
||||
max-width: 100%;
|
||||
height: auto;
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
.wi-weread__shelf :global(a) {
|
||||
color: var(--accent);
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.wi-weread__title {
|
||||
font-size: var(--text-2xl);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,331 @@
|
||||
@use 'variables' as *;
|
||||
|
||||
/* ==========================================================================
|
||||
正文排版(文章页 .wi-post__body / 单页 .wi-page__body 共用)
|
||||
--------------------------------------------------------------------------
|
||||
目标:
|
||||
1. 标题层级清晰、字号克制,移动端逐级收敛(而不是反向放大)
|
||||
2. 图片 / 视频 / 嵌入 iframe / figure 一律水平居中且不破坏响应式
|
||||
========================================================================== */
|
||||
|
||||
/* 正文容器:运行时由 Thymeleaf 注入内容,因此这里的规则必须是全局的,
|
||||
不能写进组件的 scoped <style>(那样不会命中注入的内容)。 */
|
||||
.wi-post__body,
|
||||
.wi-page__body,
|
||||
.wi-messageboard__body {
|
||||
/* ---------- 标题层级 ---------- */
|
||||
:is(h1, h2, h3, h4, h5, h6) {
|
||||
font-family: var(--font-sans);
|
||||
font-weight: 700;
|
||||
line-height: 1.35;
|
||||
letter-spacing: -0.01em;
|
||||
color: var(--ink);
|
||||
scroll-margin-top: 88px;
|
||||
text-wrap: balance;
|
||||
/* 覆盖全局标题装饰(左侧竖线 / 圆点),正文内保持干净 */
|
||||
padding-left: 0;
|
||||
border-left: 0;
|
||||
|
||||
&::before,
|
||||
&::after {
|
||||
content: none;
|
||||
}
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-size: clamp(1.45rem, 1.3rem + 0.7vw, 1.75rem);
|
||||
margin-top: 2em;
|
||||
margin-bottom: 0.7em;
|
||||
}
|
||||
|
||||
h2 {
|
||||
font-size: clamp(1.3rem, 1.2rem + 0.5vw, 1.5rem);
|
||||
margin-top: 1.9em;
|
||||
margin-bottom: 0.65em;
|
||||
}
|
||||
|
||||
h3 {
|
||||
font-size: clamp(1.15rem, 1.08rem + 0.35vw, 1.28rem);
|
||||
margin-top: 1.7em;
|
||||
margin-bottom: 0.6em;
|
||||
}
|
||||
|
||||
h4 {
|
||||
font-size: clamp(1.05rem, 1rem + 0.25vw, 1.13rem);
|
||||
margin-top: 1.5em;
|
||||
margin-bottom: 0.5em;
|
||||
}
|
||||
|
||||
h5 {
|
||||
font-size: 1rem;
|
||||
margin-top: 1.4em;
|
||||
margin-bottom: 0.45em;
|
||||
}
|
||||
|
||||
h6 {
|
||||
font-size: 0.95rem;
|
||||
font-weight: 600;
|
||||
color: var(--ink-3);
|
||||
margin-top: 1.4em;
|
||||
margin-bottom: 0.45em;
|
||||
}
|
||||
|
||||
/* 标题紧跟在另一个标题后时去掉多余上间距 */
|
||||
:is(h1, h2, h3, h4, h5, h6) + :is(h1, h2, h3, h4, h5, h6) {
|
||||
margin-top: 0.6em;
|
||||
}
|
||||
|
||||
/* 段落内首个元素不需要上间距 */
|
||||
> :is(h1, h2, h3, h4, h5, h6):first-child {
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
p {
|
||||
margin-block-end: 1.2em;
|
||||
}
|
||||
|
||||
/* ---------- 代码块 / 引用 / 列表 ---------- */
|
||||
|
||||
* {
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
:is(pre, code, table) {
|
||||
overflow-x: auto;
|
||||
}
|
||||
|
||||
pre {
|
||||
margin-block: 1.5em;
|
||||
padding: 1.25em;
|
||||
border-radius: $border-radius-md;
|
||||
border: 1px solid var(--rule);
|
||||
background: var(--bg-raised);
|
||||
font-size: 0.875em;
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
pre code {
|
||||
background: none;
|
||||
padding: 0;
|
||||
border: none;
|
||||
border-radius: 0;
|
||||
font-size: inherit;
|
||||
}
|
||||
|
||||
code {
|
||||
background: var(--bg-raised);
|
||||
padding: 0.15em 0.4em;
|
||||
border-radius: $border-radius-sm;
|
||||
font-size: 0.875em;
|
||||
border: 1px solid var(--rule);
|
||||
}
|
||||
|
||||
blockquote {
|
||||
margin-block: 2em;
|
||||
}
|
||||
|
||||
:is(ul, ol) {
|
||||
padding-inline-start: 1.5em;
|
||||
margin-block-end: 1.2em;
|
||||
}
|
||||
|
||||
li {
|
||||
margin-block-end: 0.6em;
|
||||
}
|
||||
|
||||
a {
|
||||
text-decoration: underline;
|
||||
text-decoration-thickness: 1px;
|
||||
text-underline-offset: 0.2em;
|
||||
}
|
||||
|
||||
/* 第三方摘要插件容器 */
|
||||
:is(.ai-summary, .abstract),
|
||||
[class*='ai-summary'],
|
||||
[class*='ai-abstract'],
|
||||
[class*='likcc-article-summary'] {
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
/* ---------- 媒体居中 ---------- */
|
||||
|
||||
/* 图片 / 视频:块级 + 自动外边距实现居中,max-width 保证不溢出容器 */
|
||||
img,
|
||||
video {
|
||||
display: block;
|
||||
max-width: 100%;
|
||||
height: auto;
|
||||
margin-inline: auto;
|
||||
}
|
||||
|
||||
img,
|
||||
video {
|
||||
border-radius: $border-radius-md;
|
||||
margin-block: 1.5em;
|
||||
}
|
||||
|
||||
/* 行内小图标 / 表情不受居中规则影响 */
|
||||
img[role='presentation'],
|
||||
img[aria-hidden='true'],
|
||||
:is(.emoji, .icon, .avatar, .badge) {
|
||||
display: inline;
|
||||
margin-inline: 0;
|
||||
border-radius: 0;
|
||||
}
|
||||
|
||||
/* 编辑器可能输出 align="center|left|right" 或 text-align 样式,保持一致居中 */
|
||||
[align='center'],
|
||||
[style*='text-align:center'],
|
||||
[style*='text-align: center'] {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
iframe {
|
||||
display: block;
|
||||
max-width: 100%;
|
||||
margin-inline: auto;
|
||||
border: none;
|
||||
}
|
||||
|
||||
figure {
|
||||
display: block;
|
||||
max-width: 100%;
|
||||
margin-inline: auto;
|
||||
margin-block: 1.5em;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
figure :is(img, video) {
|
||||
margin-block: 0;
|
||||
}
|
||||
|
||||
figcaption {
|
||||
color: var(--ink-3);
|
||||
font-size: 0.875rem;
|
||||
line-height: 1.6;
|
||||
text-align: center;
|
||||
margin-top: 0.6em;
|
||||
}
|
||||
|
||||
/* lightgallery 注入的 <a> 包裹层:收缩到图片宽度并居中 */
|
||||
.wi-lightgallery-item {
|
||||
display: block;
|
||||
width: fit-content;
|
||||
max-width: 100%;
|
||||
margin-inline: auto;
|
||||
border-radius: $border-radius-md;
|
||||
line-height: 0;
|
||||
}
|
||||
|
||||
.wi-lightgallery-item > img {
|
||||
margin-block: 0;
|
||||
}
|
||||
|
||||
/* 响应式嵌入容器(由脚本按 width/height 生成 aspect-ratio) */
|
||||
.wi-embed {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
max-width: 100%;
|
||||
margin-inline: auto;
|
||||
margin-block: 1.5em;
|
||||
overflow: hidden;
|
||||
border-radius: $border-radius-md;
|
||||
background: var(--bg-raised);
|
||||
}
|
||||
|
||||
.wi-embed > :is(iframe, video, embed, object) {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
margin: 0;
|
||||
border-radius: 0;
|
||||
border: 0;
|
||||
}
|
||||
|
||||
/* 图片懒加载占位模糊 */
|
||||
.wi-media-loading {
|
||||
filter: blur(20px);
|
||||
opacity: 0.6;
|
||||
}
|
||||
|
||||
.wi-media-loaded {
|
||||
filter: blur(0);
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
/* 移动端:标题逐级收敛,正文行高放宽 */
|
||||
@media (max-width: 768px) {
|
||||
.wi-post__body,
|
||||
.wi-page__body,
|
||||
.wi-messageboard__body {
|
||||
h1 {
|
||||
font-size: 1.4rem;
|
||||
}
|
||||
|
||||
h2 {
|
||||
font-size: 1.28rem;
|
||||
}
|
||||
|
||||
h3 {
|
||||
font-size: 1.16rem;
|
||||
}
|
||||
|
||||
h4 {
|
||||
font-size: 1.05rem;
|
||||
}
|
||||
|
||||
h5 {
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
h6 {
|
||||
font-size: 0.95rem;
|
||||
}
|
||||
|
||||
p {
|
||||
margin-block-end: 1.35em;
|
||||
}
|
||||
|
||||
pre {
|
||||
font-size: 0.9em;
|
||||
padding: 1em;
|
||||
}
|
||||
|
||||
code {
|
||||
font-size: 0.9em;
|
||||
}
|
||||
|
||||
img,
|
||||
video {
|
||||
margin-block: 1.2em;
|
||||
}
|
||||
|
||||
/* 窄屏图片允许撑满内容宽度,避免留白过大 */
|
||||
img,
|
||||
video,
|
||||
.wi-lightgallery-item,
|
||||
figure {
|
||||
max-width: 100%;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 480px) {
|
||||
.wi-post__body,
|
||||
.wi-page__body,
|
||||
.wi-messageboard__body {
|
||||
h1 {
|
||||
font-size: 1.32rem;
|
||||
}
|
||||
|
||||
h2 {
|
||||
font-size: 1.22rem;
|
||||
}
|
||||
|
||||
h3 {
|
||||
font-size: 1.12rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -39,19 +39,19 @@ h1, h2, h3, h4, h5, h6 {
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-size: var(--text-4xl);
|
||||
font-size: var(--text-3xl);
|
||||
margin-bottom: var(--space-lg);
|
||||
}
|
||||
|
||||
h2 {
|
||||
font-size: var(--text-3xl);
|
||||
font-size: var(--text-2xl);
|
||||
padding-left: var(--space-md);
|
||||
border-left: 3px solid var(--accent);
|
||||
margin-bottom: var(--space-sm);
|
||||
}
|
||||
|
||||
h3 {
|
||||
font-size: var(--text-2xl);
|
||||
font-size: var(--text-xl);
|
||||
position: relative;
|
||||
padding-left: var(--space-lg);
|
||||
|
||||
@@ -71,16 +71,16 @@ h3 {
|
||||
}
|
||||
|
||||
h4 {
|
||||
font-size: var(--text-xl);
|
||||
font-size: var(--text-md);
|
||||
color: var(--ink-2);
|
||||
}
|
||||
|
||||
h5 {
|
||||
font-size: var(--text-lg);
|
||||
font-size: var(--text-base);
|
||||
color: var(--ink-2);
|
||||
}
|
||||
|
||||
h6 {
|
||||
font-size: var(--text-base);
|
||||
font-size: var(--text-sm);
|
||||
color: var(--ink-3);
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
@use 'mixins';
|
||||
@use 'colors';
|
||||
@use 'typography';
|
||||
@use 'content';
|
||||
@use 'spacing';
|
||||
@use 'animations';
|
||||
@use 'comment';
|
||||
|
||||
@@ -1,8 +0,0 @@
|
||||
(function(){
|
||||
return fetch('/apis/api.halo.run/v1alpha1/posts/5152aea5-c2e8-4717-8bba-2263d46e19d5/likes', {method: 'POST'})
|
||||
.then(function(r){
|
||||
return r.text().then(function(t){
|
||||
return JSON.stringify({status: r.status, body: t.substring(0, 500)});
|
||||
});
|
||||
});
|
||||
})()
|
||||
+9
-1
@@ -20,7 +20,15 @@ spec:
|
||||
description: 留言板
|
||||
screenshot:
|
||||
file: page_messageboard.html
|
||||
version: "1.1.1"
|
||||
- name: Subscriptions
|
||||
description: 订阅页面(展示博友文章订阅动态,依赖链接管理插件)
|
||||
screenshot:
|
||||
file: page_subscriptions.html
|
||||
- name: WeRead
|
||||
description: 微信读书书架(依赖微信读书插件)
|
||||
screenshot:
|
||||
file: weread.html
|
||||
version: "1.1.5"
|
||||
requires: ">=2.24.0"
|
||||
license:
|
||||
- name: "GPL-3.0"
|
||||
|
||||
+3
-4
@@ -1,10 +1,9 @@
|
||||
{
|
||||
"extends": "astro/tsconfigs/strict",
|
||||
"include": [".astro/types.d.ts", "**/*"],
|
||||
"exclude": ["dist"],
|
||||
"include": [".astro/types.d.ts", "src/**/*"],
|
||||
"exclude": ["dist", "templates", "node_modules", "docs"],
|
||||
"compilerOptions": {
|
||||
"strictNullChecks": true,
|
||||
"jsx": "preserve",
|
||||
"types": ["unplugin-icons/types/vue"]
|
||||
"jsx": "preserve"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,60 +0,0 @@
|
||||
const path = require('path');
|
||||
|
||||
const pageCdpUrl = process.argv[2];
|
||||
const filePath = process.argv[3];
|
||||
|
||||
async function uploadFile() {
|
||||
const ws = new WebSocket(pageCdpUrl);
|
||||
|
||||
let msgId = 1;
|
||||
const pending = new Map();
|
||||
|
||||
ws.addEventListener('message', function(event) {
|
||||
const msg = JSON.parse(event.data);
|
||||
if (msg.id && pending.has(msg.id)) {
|
||||
const { resolve, reject } = pending.get(msg.id);
|
||||
pending.delete(msg.id);
|
||||
if (msg.error) reject(msg.error);
|
||||
else resolve(msg.result);
|
||||
}
|
||||
});
|
||||
|
||||
function send(method, params = {}) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const id = msgId++;
|
||||
pending.set(id, { resolve, reject });
|
||||
ws.send(JSON.stringify({ id, method, params }));
|
||||
});
|
||||
}
|
||||
|
||||
await new Promise(resolve => ws.addEventListener('open', resolve, { once: true }));
|
||||
|
||||
const docResult = await send('DOM.getDocument');
|
||||
const { nodeId } = await send('DOM.querySelector', {
|
||||
nodeId: docResult.root.nodeId,
|
||||
selector: 'input[type="file"]'
|
||||
});
|
||||
|
||||
if (!nodeId) {
|
||||
console.error('File input not found');
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const absolutePath = path.resolve(filePath);
|
||||
console.log('Setting file:', absolutePath);
|
||||
|
||||
await send('DOM.setFileInputFiles', {
|
||||
nodeId,
|
||||
files: [absolutePath]
|
||||
});
|
||||
|
||||
console.log('File upload triggered successfully');
|
||||
|
||||
await new Promise(resolve => setTimeout(resolve, 3000));
|
||||
ws.close();
|
||||
}
|
||||
|
||||
uploadFile().catch(err => {
|
||||
console.error('Error:', err);
|
||||
process.exit(1);
|
||||
});
|
||||
@@ -1,53 +0,0 @@
|
||||
const CDP_URL = 'ws://127.0.0.1:62529/devtools/browser/a36c1b63-8ff4-41d5-93fa-5c228657ef70';
|
||||
const ZIP_FILE = 'c:\\Users\\Zhang\\Documents\\Halo\\WarmIsland\\dist\\warm-island-1.0.0.zip';
|
||||
|
||||
async function main() {
|
||||
const ws = new WebSocket(CDP_URL);
|
||||
let msgId = 1;
|
||||
const pending = new Map();
|
||||
|
||||
ws.addEventListener('message', (event) => {
|
||||
const msg = JSON.parse(event.data);
|
||||
if (msg.id && pending.has(msg.id)) {
|
||||
const { resolve, reject } = pending.get(msg.id);
|
||||
pending.delete(msg.id);
|
||||
if (msg.error) reject(msg.error);
|
||||
else resolve(msg.result);
|
||||
}
|
||||
});
|
||||
|
||||
const send = (method, params = {}, sessionId) => new Promise((resolve, reject) => {
|
||||
const id = msgId++;
|
||||
const msg = { id, method, params };
|
||||
if (sessionId) msg.sessionId = sessionId;
|
||||
pending.set(id, { resolve, reject });
|
||||
ws.send(JSON.stringify(msg));
|
||||
});
|
||||
|
||||
await new Promise(r => ws.addEventListener('open', r, { once: true }));
|
||||
|
||||
try {
|
||||
const { targetInfos } = await send('Target.getTargets');
|
||||
const pageTarget = targetInfos.find(t => t.type === 'page' && t.url.includes('localhost:8090'));
|
||||
if (!pageTarget) { console.error('No page target found'); process.exit(1); }
|
||||
|
||||
const { sessionId } = await send('Target.attachToTarget', { targetId: pageTarget.targetId, flatten: true });
|
||||
|
||||
const doc = await send('DOM.getDocument', {}, sessionId);
|
||||
const { nodeId } = await send('DOM.querySelector', { nodeId: doc.root.nodeId, selector: 'input[type="file"][accept=".zip"]' }, sessionId);
|
||||
|
||||
if (!nodeId) { console.error('File input not found'); process.exit(1); }
|
||||
console.log('Found file input node:', nodeId);
|
||||
|
||||
await send('DOM.setFileInputFiles', { nodeId, files: [ZIP_FILE] }, sessionId);
|
||||
console.log('File upload triggered successfully!');
|
||||
|
||||
ws.close();
|
||||
} catch (err) {
|
||||
console.error('Error:', JSON.stringify(err));
|
||||
ws.close();
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
main();
|
||||
Reference in New Issue
Block a user