Fix preloader

This commit is contained in:
2026-05-21 20:12:53 +04:00
parent 03fc7cc9d5
commit 73dfd4e750
15 changed files with 333 additions and 676 deletions

View File

@@ -178,7 +178,6 @@ export default defineConfig({
tagName: "span", tagName: "span",
properties: { properties: {
className: ["anchor-icon"], className: ["anchor-icon"],
"data-pagefind-ignore": true,
}, },
children: [ children: [
{ {

View File

@@ -10,7 +10,8 @@
"format": "biome format --write ./src", "format": "biome format --write ./src",
"check": "astro check", "check": "astro check",
"dev": "astro dev", "dev": "astro dev",
"build": "node scripts/build-with-pagefind.cjs", "build": "node scripts/copy-iconify.cjs && astro build",
"copy-iconify": "node scripts/copy-iconify.cjs",
"preview": "astro preview", "preview": "astro preview",
"astro": "astro", "astro": "astro",
"new-post": "node scripts/new-post.js" "new-post": "node scripts/new-post.js"
@@ -32,7 +33,18 @@
"@iconify-json/fa6-brands": "^1.2.6", "@iconify-json/fa6-brands": "^1.2.6",
"@iconify-json/fa6-regular": "^1.2.4", "@iconify-json/fa6-regular": "^1.2.4",
"@iconify-json/fa6-solid": "^1.2.4", "@iconify-json/fa6-solid": "^1.2.4",
"@iconify-json/devicon": "^1.2.5",
"@iconify-json/game-icons": "^1.2.3",
"@iconify-json/hugeicons": "^1.2.18",
"@iconify-json/iconoir": "^1.2.10",
"@iconify-json/logos": "^1.2.10",
"@iconify-json/material-icon-theme": "^1.2.21",
"@iconify-json/material-symbols": "^1.2.53", "@iconify-json/material-symbols": "^1.2.53",
"@iconify-json/mdi": "^1.2.3",
"@iconify-json/mingcute": "^1.2.5",
"@iconify-json/skill-icons": "^1.2.3",
"@iconify-json/vscode-icons": "^1.2.40",
"@iconify/utils": "^2.3.0",
"@iconify/svelte": "^5.2.1", "@iconify/svelte": "^5.2.1",
"@swup/astro": "^1.7.0", "@swup/astro": "^1.7.0",
"@tailwindcss/typography": "^0.5.19", "@tailwindcss/typography": "^0.5.19",
@@ -53,8 +65,8 @@
"marked": "^17.0.1", "marked": "^17.0.1",
"mdast-util-to-string": "^4.0.0", "mdast-util-to-string": "^4.0.0",
"node-html-parser": "^7.0.2", "node-html-parser": "^7.0.2",
"iconify-icon": "1.0.7",
"overlayscrollbars": "^2.14.0", "overlayscrollbars": "^2.14.0",
"pagefind": "^1.4.0",
"photoswipe": "^5.4.4", "photoswipe": "^5.4.4",
"reading-time": "^1.5.0", "reading-time": "^1.5.0",
"rehype-autolink-headings": "^7.1.0", "rehype-autolink-headings": "^7.1.0",
@@ -78,7 +90,6 @@
"devDependencies": { "devDependencies": {
"@astrojs/ts-plugin": "^1.10.6", "@astrojs/ts-plugin": "^1.10.6",
"@biomejs/biome": "2.3.13", "@biomejs/biome": "2.3.13",
"@iconify-json/mdi": "^1.2.3",
"@rollup/plugin-yaml": "^4.1.2", "@rollup/plugin-yaml": "^4.1.2",
"@types/hast": "^3.0.4", "@types/hast": "^3.0.4",
"@types/markdown-it": "^14.1.2", "@types/markdown-it": "^14.1.2",

View File

@@ -1,6 +0,0 @@
exclude_selectors:
- "span.katex"
- "span.katex-display"
- "[data-pagefind-ignore]"
- ".search-panel"
- "#search-panel"

173
pnpm-lock.yaml generated
View File

@@ -47,6 +47,9 @@ importers:
'@fontsource/roboto': '@fontsource/roboto':
specifier: ^5.2.9 specifier: ^5.2.9
version: 5.2.9 version: 5.2.9
'@iconify-json/devicon':
specifier: ^1.2.5
version: 1.2.62
'@iconify-json/fa6-brands': '@iconify-json/fa6-brands':
specifier: ^1.2.6 specifier: ^1.2.6
version: 1.2.6 version: 1.2.6
@@ -56,12 +59,42 @@ importers:
'@iconify-json/fa6-solid': '@iconify-json/fa6-solid':
specifier: ^1.2.4 specifier: ^1.2.4
version: 1.2.4 version: 1.2.4
'@iconify-json/game-icons':
specifier: ^1.2.3
version: 1.2.4
'@iconify-json/hugeicons':
specifier: ^1.2.18
version: 1.2.28
'@iconify-json/iconoir':
specifier: ^1.2.10
version: 1.2.11
'@iconify-json/logos':
specifier: ^1.2.10
version: 1.2.11
'@iconify-json/material-icon-theme':
specifier: ^1.2.21
version: 1.2.64
'@iconify-json/material-symbols': '@iconify-json/material-symbols':
specifier: ^1.2.53 specifier: ^1.2.53
version: 1.2.53 version: 1.2.53
'@iconify-json/mdi':
specifier: ^1.2.3
version: 1.2.3
'@iconify-json/mingcute':
specifier: ^1.2.5
version: 1.2.7
'@iconify-json/skill-icons':
specifier: ^1.2.3
version: 1.2.4
'@iconify-json/vscode-icons':
specifier: ^1.2.40
version: 1.2.50
'@iconify/svelte': '@iconify/svelte':
specifier: ^5.2.1 specifier: ^5.2.1
version: 5.2.1(svelte@5.49.1) version: 5.2.1(svelte@5.49.1)
'@iconify/utils':
specifier: ^2.3.0
version: 2.3.0
'@swup/astro': '@swup/astro':
specifier: ^1.7.0 specifier: ^1.7.0
version: 1.7.0 version: 1.7.0
@@ -101,6 +134,9 @@ importers:
hastscript: hastscript:
specifier: ^9.0.1 specifier: ^9.0.1
version: 9.0.1 version: 9.0.1
iconify-icon:
specifier: 1.0.7
version: 1.0.7
js-yaml: js-yaml:
specifier: ^4.1.1 specifier: ^4.1.1
version: 4.1.1 version: 4.1.1
@@ -122,9 +158,6 @@ importers:
overlayscrollbars: overlayscrollbars:
specifier: ^2.14.0 specifier: ^2.14.0
version: 2.14.0 version: 2.14.0
pagefind:
specifier: ^1.4.0
version: 1.4.0
photoswipe: photoswipe:
specifier: ^5.4.4 specifier: ^5.4.4
version: 5.4.4 version: 5.4.4
@@ -189,9 +222,6 @@ importers:
'@biomejs/biome': '@biomejs/biome':
specifier: 2.3.13 specifier: 2.3.13
version: 2.3.13 version: 2.3.13
'@iconify-json/mdi':
specifier: ^1.2.3
version: 1.2.3
'@rollup/plugin-yaml': '@rollup/plugin-yaml':
specifier: ^4.1.2 specifier: ^4.1.2
version: 4.1.2(rollup@2.79.2) version: 4.1.2(rollup@2.79.2)
@@ -1323,6 +1353,9 @@ packages:
'@fontsource/roboto@5.2.9': '@fontsource/roboto@5.2.9':
resolution: {integrity: sha512-ZTkyHiPk74B/aj8BZWbsxD5Yu+Lq+nR64eV4wirlrac2qXR7jYk2h6JlLYuOuoruTkGQWNw2fMuKNavw7/rg0w==} resolution: {integrity: sha512-ZTkyHiPk74B/aj8BZWbsxD5Yu+Lq+nR64eV4wirlrac2qXR7jYk2h6JlLYuOuoruTkGQWNw2fMuKNavw7/rg0w==}
'@iconify-json/devicon@1.2.62':
resolution: {integrity: sha512-X0o0+fOJL2t5JTbDCGLRzrmvf8Pv5q8dr86+fW+F/VJ9qvXTtny6lqGIwjE9AY4VTLm3i8f/N+VtRheSW6OkEg==}
'@iconify-json/fa6-brands@1.2.6': '@iconify-json/fa6-brands@1.2.6':
resolution: {integrity: sha512-twL3X4KWcxAhbc1vz/mIDsVr+CAItk1/EIfxKUVQtpv6O4eydk5KNYqTZWdvJNHGInUgd6vKg21aWfVgb5DXEg==} resolution: {integrity: sha512-twL3X4KWcxAhbc1vz/mIDsVr+CAItk1/EIfxKUVQtpv6O4eydk5KNYqTZWdvJNHGInUgd6vKg21aWfVgb5DXEg==}
@@ -1332,12 +1365,36 @@ packages:
'@iconify-json/fa6-solid@1.2.4': '@iconify-json/fa6-solid@1.2.4':
resolution: {integrity: sha512-LmDNNdJVyvF5mPm1yxWvL8KjCc/E8LzoqnF1LNTVpyY2ZJRUlGOWuPIThdbuFBF2IovgttkIyumhyqfmlHdwKg==} resolution: {integrity: sha512-LmDNNdJVyvF5mPm1yxWvL8KjCc/E8LzoqnF1LNTVpyY2ZJRUlGOWuPIThdbuFBF2IovgttkIyumhyqfmlHdwKg==}
'@iconify-json/game-icons@1.2.4':
resolution: {integrity: sha512-/HDDZxowXgDJcOEwPspUCq5RuDdl25OyZKdtxqqiPJQNTrWIym7r4StLzIVs77DyNzOWWhH5wDqwYR3WHeDfkg==}
'@iconify-json/hugeicons@1.2.28':
resolution: {integrity: sha512-974r0jTel8JBjJhoEAnUEtEhNMmPek2WegAbdlpQaQp1cIJfcxl3fM8/bfPceMKtvbN8vBokaQqHeDgikGRl5w==}
'@iconify-json/iconoir@1.2.11':
resolution: {integrity: sha512-clBrCsZPkmdksYOkufVp7aoAU1j1hFQPrLUooWWmoYIMvKskUHQ+IDnwV7agTR+tGYHbud7OP2SRfYEpnqFDNw==}
'@iconify-json/logos@1.2.11':
resolution: {integrity: sha512-fOo4pGEatuyuCFNL+cwquYMa2Im0oJHRHV7lt/Qqs5Ode/lPImHCQcfTtPzZj7qYMPb/h8YHN3TG54uEowrjNQ==}
'@iconify-json/material-icon-theme@1.2.64':
resolution: {integrity: sha512-YUTQ7IQ919XwcSz7qKDixj5+63PD8lDgimtRtQwo5clcLZAwf9SyzeUOTLTPR17plPNo0xjlgDi4UcMXfz2xFQ==}
'@iconify-json/material-symbols@1.2.53': '@iconify-json/material-symbols@1.2.53':
resolution: {integrity: sha512-2jXBKFdNzL9zy6chnJqubykL9WZno7rEP6/isSzpp6fKJJMXXhRtVkaGw1Clle0RlXGWzVkd/eiYUH8f9/ILrQ==} resolution: {integrity: sha512-2jXBKFdNzL9zy6chnJqubykL9WZno7rEP6/isSzpp6fKJJMXXhRtVkaGw1Clle0RlXGWzVkd/eiYUH8f9/ILrQ==}
'@iconify-json/mdi@1.2.3': '@iconify-json/mdi@1.2.3':
resolution: {integrity: sha512-O3cLwbDOK7NNDf2ihaQOH5F9JglnulNDFV7WprU2dSoZu3h3cWH//h74uQAB87brHmvFVxIOkuBX2sZSzYhScg==} resolution: {integrity: sha512-O3cLwbDOK7NNDf2ihaQOH5F9JglnulNDFV7WprU2dSoZu3h3cWH//h74uQAB87brHmvFVxIOkuBX2sZSzYhScg==}
'@iconify-json/mingcute@1.2.7':
resolution: {integrity: sha512-bp4z6F53KAQp99aSL7qPCHL7HWUNGMNKIhfo/dx9pwVHiad/WusUbTOFXEZfpA44syJPzSOxa3O6Pwiq7qZz1g==}
'@iconify-json/skill-icons@1.2.4':
resolution: {integrity: sha512-S6iRKHGlGCb/zfx3Isv1TBe5PunbDE5FEwJcaXji60+yRZMrcFFtYEI1xivKqaGKXyVZ508yjGDItsehQdJmSg==}
'@iconify-json/vscode-icons@1.2.50':
resolution: {integrity: sha512-XqG9nO1m+IiY8/RvT5BJjM/eG3CDRXBGRRPqR/MAcVi734yZ0Pa+jqObshZfnpUeIsuDKNn9C/7ZW00ART6kog==}
'@iconify/svelte@5.2.1': '@iconify/svelte@5.2.1':
resolution: {integrity: sha512-zHmsIPmnIhGd5gc95bNN5FL+GifwMZv7M2rlZEpa7IXYGFJm/XGHdWf6PWQa6OBoC+R69WyiPO9NAj5wjfjbow==} resolution: {integrity: sha512-zHmsIPmnIhGd5gc95bNN5FL+GifwMZv7M2rlZEpa7IXYGFJm/XGHdWf6PWQa6OBoC+R69WyiPO9NAj5wjfjbow==}
peerDependencies: peerDependencies:
@@ -1632,36 +1689,6 @@ packages:
'@oslojs/encoding@1.1.0': '@oslojs/encoding@1.1.0':
resolution: {integrity: sha512-70wQhgYmndg4GCPxPPxPGevRKqTIJ2Nh4OkiMWmDAVYsTQ+Ta7Sq+rPevXyXGdzr30/qZBnyOalCszoMxlyldQ==} resolution: {integrity: sha512-70wQhgYmndg4GCPxPPxPGevRKqTIJ2Nh4OkiMWmDAVYsTQ+Ta7Sq+rPevXyXGdzr30/qZBnyOalCszoMxlyldQ==}
'@pagefind/darwin-arm64@1.4.0':
resolution: {integrity: sha512-2vMqkbv3lbx1Awea90gTaBsvpzgRs7MuSgKDxW0m9oV1GPZCZbZBJg/qL83GIUEN2BFlY46dtUZi54pwH+/pTQ==}
cpu: [arm64]
os: [darwin]
'@pagefind/darwin-x64@1.4.0':
resolution: {integrity: sha512-e7JPIS6L9/cJfow+/IAqknsGqEPjJnVXGjpGm25bnq+NPdoD3c/7fAwr1OXkG4Ocjx6ZGSCijXEV4ryMcH2E3A==}
cpu: [x64]
os: [darwin]
'@pagefind/freebsd-x64@1.4.0':
resolution: {integrity: sha512-WcJVypXSZ+9HpiqZjFXMUobfFfZZ6NzIYtkhQ9eOhZrQpeY5uQFqNWLCk7w9RkMUwBv1HAMDW3YJQl/8OqsV0Q==}
cpu: [x64]
os: [freebsd]
'@pagefind/linux-arm64@1.4.0':
resolution: {integrity: sha512-PIt8dkqt4W06KGmQjONw7EZbhDF+uXI7i0XtRLN1vjCUxM9vGPdtJc2mUyVPevjomrGz5M86M8bqTr6cgDp1Uw==}
cpu: [arm64]
os: [linux]
'@pagefind/linux-x64@1.4.0':
resolution: {integrity: sha512-z4oddcWwQ0UHrTHR8psLnVlz6USGJ/eOlDPTDYZ4cI8TK8PgwRUPQZp9D2iJPNIPcS6Qx/E4TebjuGJOyK8Mmg==}
cpu: [x64]
os: [linux]
'@pagefind/windows-x64@1.4.0':
resolution: {integrity: sha512-NkT+YAdgS2FPCn8mIA9bQhiBs+xmniMGq1LFPDhcFn0+2yIUEiIG06t7bsZlhdjknEQRTSdT7YitP6fC5qwP0g==}
cpu: [x64]
os: [win32]
'@pkgjs/parseargs@0.11.0': '@pkgjs/parseargs@0.11.0':
resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==} resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==}
engines: {node: '>=14'} engines: {node: '>=14'}
@@ -3342,6 +3369,9 @@ packages:
resolution: {integrity: sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==} resolution: {integrity: sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==}
engines: {node: '>= 14'} engines: {node: '>= 14'}
iconify-icon@1.0.7:
resolution: {integrity: sha512-MxaO3Jhf3f5ymPWGHR9x74f90TNKcq1D+B2iGucGhVtqAgbC9EtM06kKiTGH2CKELNnexckwhrA3/+OpT4HKFw==}
iconv-lite@0.6.3: iconv-lite@0.6.3:
resolution: {integrity: sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==} resolution: {integrity: sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==}
engines: {node: '>=0.10.0'} engines: {node: '>=0.10.0'}
@@ -4135,10 +4165,6 @@ packages:
package-manager-detector@1.6.0: package-manager-detector@1.6.0:
resolution: {integrity: sha512-61A5ThoTiDG/C8s8UMZwSorAGwMJ0ERVGj2OjoW5pAalsNOg15+iQiPzrLJ4jhZ1HJzmC2PIHT2oEiH3R5fzNA==} resolution: {integrity: sha512-61A5ThoTiDG/C8s8UMZwSorAGwMJ0ERVGj2OjoW5pAalsNOg15+iQiPzrLJ4jhZ1HJzmC2PIHT2oEiH3R5fzNA==}
pagefind@1.4.0:
resolution: {integrity: sha512-z2kY1mQlL4J8q5EIsQkLzQjilovKzfNVhX8De6oyE6uHpfFtyBaqUpcl/XzJC/4fjD8vBDyh1zolimIcVrCn9g==}
hasBin: true
pako@0.2.9: pako@0.2.9:
resolution: {integrity: sha512-NUcwaKxUxWrZLpDG+z/xZaCgQITkA/Dv4V/T6bw7VON6l1Xz/VnrBqrYjZQ12TamKHzITTfOEIYUj48y2KXImA==} resolution: {integrity: sha512-NUcwaKxUxWrZLpDG+z/xZaCgQITkA/Dv4V/T6bw7VON6l1Xz/VnrBqrYjZQ12TamKHzITTfOEIYUj48y2KXImA==}
@@ -6867,6 +6893,10 @@ snapshots:
'@fontsource/roboto@5.2.9': {} '@fontsource/roboto@5.2.9': {}
'@iconify-json/devicon@1.2.62':
dependencies:
'@iconify/types': 2.0.0
'@iconify-json/fa6-brands@1.2.6': '@iconify-json/fa6-brands@1.2.6':
dependencies: dependencies:
'@iconify/types': 2.0.0 '@iconify/types': 2.0.0
@@ -6879,6 +6909,26 @@ snapshots:
dependencies: dependencies:
'@iconify/types': 2.0.0 '@iconify/types': 2.0.0
'@iconify-json/game-icons@1.2.4':
dependencies:
'@iconify/types': 2.0.0
'@iconify-json/hugeicons@1.2.28':
dependencies:
'@iconify/types': 2.0.0
'@iconify-json/iconoir@1.2.11':
dependencies:
'@iconify/types': 2.0.0
'@iconify-json/logos@1.2.11':
dependencies:
'@iconify/types': 2.0.0
'@iconify-json/material-icon-theme@1.2.64':
dependencies:
'@iconify/types': 2.0.0
'@iconify-json/material-symbols@1.2.53': '@iconify-json/material-symbols@1.2.53':
dependencies: dependencies:
'@iconify/types': 2.0.0 '@iconify/types': 2.0.0
@@ -6887,6 +6937,18 @@ snapshots:
dependencies: dependencies:
'@iconify/types': 2.0.0 '@iconify/types': 2.0.0
'@iconify-json/mingcute@1.2.7':
dependencies:
'@iconify/types': 2.0.0
'@iconify-json/skill-icons@1.2.4':
dependencies:
'@iconify/types': 2.0.0
'@iconify-json/vscode-icons@1.2.50':
dependencies:
'@iconify/types': 2.0.0
'@iconify/svelte@5.2.1(svelte@5.49.1)': '@iconify/svelte@5.2.1(svelte@5.49.1)':
dependencies: dependencies:
'@iconify/types': 2.0.0 '@iconify/types': 2.0.0
@@ -7149,24 +7211,6 @@ snapshots:
'@oslojs/encoding@1.1.0': {} '@oslojs/encoding@1.1.0': {}
'@pagefind/darwin-arm64@1.4.0':
optional: true
'@pagefind/darwin-x64@1.4.0':
optional: true
'@pagefind/freebsd-x64@1.4.0':
optional: true
'@pagefind/linux-arm64@1.4.0':
optional: true
'@pagefind/linux-x64@1.4.0':
optional: true
'@pagefind/windows-x64@1.4.0':
optional: true
'@pkgjs/parseargs@0.11.0': '@pkgjs/parseargs@0.11.0':
optional: true optional: true
@@ -9155,6 +9199,10 @@ snapshots:
transitivePeerDependencies: transitivePeerDependencies:
- supports-color - supports-color
iconify-icon@1.0.7:
dependencies:
'@iconify/types': 2.0.0
iconv-lite@0.6.3: iconv-lite@0.6.3:
dependencies: dependencies:
safer-buffer: 2.1.2 safer-buffer: 2.1.2
@@ -10153,15 +10201,6 @@ snapshots:
package-manager-detector@1.6.0: {} package-manager-detector@1.6.0: {}
pagefind@1.4.0:
optionalDependencies:
'@pagefind/darwin-arm64': 1.4.0
'@pagefind/darwin-x64': 1.4.0
'@pagefind/freebsd-x64': 1.4.0
'@pagefind/linux-arm64': 1.4.0
'@pagefind/linux-x64': 1.4.0
'@pagefind/windows-x64': 1.4.0
pako@0.2.9: {} pako@0.2.9: {}
parent-module@1.0.1: parent-module@1.0.1:

File diff suppressed because one or more lines are too long

View File

@@ -1,83 +0,0 @@
/* This is a script to build the site with Pagefind */
const { execSync } = require('child_process');
const { existsSync } = require('fs');
const { join } = require('path');
// Detect the platform
function detectPlatform() {
// Check environment variables
if (process.env.VERCEL) {
return 'vercel';
}
if (process.env.CF_PAGES) {
return 'cloudflare';
}
if (process.env.NETLIFY) {
return 'netlify';
}
if (process.env.GITHUB_ACTIONS) {
return 'github';
}
// Check if specific directories exist
if (existsSync('.vercel')) {
return 'vercel';
}
// Default to standard dist directory
return 'default';
}
// Get Pagefind output directory
function getPagefindOutputDir(platform) {
const outputDirs = {
vercel: '.vercel/output/static',
cloudflare: 'dist',
netlify: 'dist',
github: 'dist',
default: 'dist'
};
return outputDirs[platform] || 'dist';
}
// Main function
function main() {
const platform = detectPlatform();
const outputDir = getPagefindOutputDir(platform);
console.log(`🚀 Detected deployment platform: ${platform}`);
console.log(`📁 Pagefind output directory: ${outputDir}`);
try {
// Run Astro build
console.log('🔨 Running Astro build...');
execSync(`npx astro build`.trim(), {
stdio: 'inherit',
cwd: process.cwd() // Ensure in the correct directory
});
// Check if output directory exists
if (!existsSync(outputDir)) {
console.error(`❌ Output directory does not exist: ${outputDir}`);
process.exit(1);
}
// Run Pagefind
console.log(`🔍 Running Pagefind search index generation...`);
execSync(`npx pagefind --site ${outputDir}`, {
stdio: 'inherit',
cwd: process.cwd() // Ensure in the correct directory
});
console.log('✅ Build completed!');
console.log(`📊 Search index generated at: ${outputDir}/pagefind/`);
} catch (error) {
console.error('❌ Build failed:', error.message);
process.exit(1);
}
}
main();

9
scripts/copy-iconify.cjs Normal file
View File

@@ -0,0 +1,9 @@
const { copyFileSync, mkdirSync } = require("fs");
const { dirname, join } = require("path");
const src = join(__dirname, "../node_modules/iconify-icon/dist/iconify-icon.min.js");
const dest = join(__dirname, "../public/assets/iconify/iconify-icon.min.js");
mkdirSync(dirname(dest), { recursive: true });
copyFileSync(src, dest);
console.log(`Copied iconify-icon.min.js to ${dest}`);

View File

@@ -1,29 +1,45 @@
--- ---
// 全局Iconify加载器组件 import { url } from "@utils/url";
// 在页面头部加载,确保图标库尽早可用
export interface Props { export interface Props {
preloadIcons?: string[]; // 需要预加载的图标列表 preloadIcons?: string[];
timeout?: number; // 加载超时时间
retryCount?: number; // 重试次数
} }
const { preloadIcons = [], timeout = 10000, retryCount = 3 } = Astro.props; const { preloadIcons = [] } = Astro.props;
const iconifyScriptUrl = url("/assets/iconify/iconify-icon.min.js");
--- ---
<!-- Iconify图标库加载器 --> <div
<script define:vars={{ preloadIcons, timeout, retryCount }}> id="iconify-loader-config"
// 全局图标加载逻辑 data-script-url={iconifyScriptUrl}
(function() { data-preload-icons={JSON.stringify(preloadIcons)}
'use strict'; hidden
aria-hidden="true"
></div>
<script>
import {
registerSvelteIconifyCollections,
registerWebComponentIconifyCollections,
} from "@/utils/iconify-offline";
const LOAD_TIMEOUT_MS = 3000;
const RETRY_COUNT = 1;
(function () {
"use strict";
// 避免重复加载
if (window.__iconifyLoaderInitialized) { if (window.__iconifyLoaderInitialized) {
return; return;
} }
window.__iconifyLoaderInitialized = true; window.__iconifyLoaderInitialized = true;
// 图标加载器类 registerSvelteIconifyCollections();
const configEl = document.getElementById("iconify-loader-config");
const iconifyScriptUrl = configEl?.dataset.scriptUrl ?? "/assets/iconify/iconify-icon.min.js";
const preloadIcons = JSON.parse(configEl?.dataset.preloadIcons ?? "[]");
class IconifyLoader { class IconifyLoader {
constructor() { constructor() {
this.isLoaded = false; this.isLoaded = false;
@@ -34,7 +50,10 @@ const { preloadIcons = [], timeout = 10000, retryCount = 3 } = Astro.props;
} }
async load(options = {}) { async load(options = {}) {
const { timeout: loadTimeout = timeout, retryCount: maxRetries = retryCount } = options; const {
timeout: loadTimeout = LOAD_TIMEOUT_MS,
retryCount: maxRetries = RETRY_COUNT,
} = options;
if (this.isLoaded) { if (this.isLoaded) {
return Promise.resolve(); return Promise.resolve();
@@ -53,61 +72,74 @@ const { preloadIcons = [], timeout = 10000, retryCount = 3 } = Astro.props;
this.notifyObservers(); this.notifyObservers();
await this.processPreloadQueue(); await this.processPreloadQueue();
} catch (error) { } catch (error) {
console.error('Failed to load Iconify:', error); console.error("Failed to load Iconify:", error);
throw error; throw error;
} finally { } finally {
this.isLoading = false; this.isLoading = false;
} }
} }
async loadWithRetry(timeout, retryCount) { async loadWithRetry(timeoutMs, maxRetries) {
for (let attempt = 1; attempt <= retryCount; attempt++) { for (let attempt = 1; attempt <= maxRetries; attempt++) {
try { try {
await this.loadScript(timeout); await this.loadScript(timeoutMs);
return; return;
} catch (error) { } catch (error) {
console.warn(`Iconify load attempt ${attempt} failed:`, error); console.warn(`Iconify load attempt ${attempt} failed:`, error);
if (attempt === retryCount) { if (attempt === maxRetries) {
throw new Error(`Failed to load Iconify after ${retryCount} attempts`); throw new Error(
`Failed to load Iconify after ${maxRetries} attempts`,
);
} }
await new Promise(resolve => setTimeout(resolve, 1000)); await new Promise((resolve) => setTimeout(resolve, 500));
} }
} }
} }
loadScript(timeout) { loadScript(timeoutMs) {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
// 检查是否已经存在
if (this.isIconifyReady()) { if (this.isIconifyReady()) {
registerWebComponentIconifyCollections();
resolve(); resolve();
return; return;
} }
const existingScript = document.querySelector('script[src*="iconify-icon"]'); const existingScript = document.querySelector(
'script[src*="iconify-icon"]',
);
if (existingScript) { if (existingScript) {
this.waitForIconifyReady().then(resolve).catch(reject); this.waitForIconifyReady()
.then(() => {
registerWebComponentIconifyCollections();
resolve();
})
.catch(reject);
return; return;
} }
const script = document.createElement('script'); const script = document.createElement("script");
script.src = 'https://code.iconify.design/iconify-icon/1.0.7/iconify-icon.min.js'; script.src = iconifyScriptUrl;
script.async = true; script.async = true;
script.crossOrigin = 'anonymous';
const timeoutId = setTimeout(() => { const timeoutId = setTimeout(() => {
script.remove(); script.remove();
reject(new Error('Script load timeout')); reject(new Error("Script load timeout"));
}, timeout); }, timeoutMs);
script.onload = () => { script.onload = () => {
clearTimeout(timeoutId); clearTimeout(timeoutId);
this.waitForIconifyReady().then(resolve).catch(reject); this.waitForIconifyReady()
.then(() => {
registerWebComponentIconifyCollections();
resolve();
})
.catch(reject);
}; };
script.onerror = () => { script.onerror = () => {
clearTimeout(timeoutId); clearTimeout(timeoutId);
script.remove(); script.remove();
reject(new Error('Script load error')); reject(new Error("Script load error"));
}; };
document.head.appendChild(script); document.head.appendChild(script);
@@ -125,7 +157,7 @@ const { preloadIcons = [], timeout = 10000, retryCount = 3 } = Astro.props;
} }
if (Date.now() - startTime > maxWait) { if (Date.now() - startTime > maxWait) {
reject(new Error('Iconify initialization timeout')); reject(new Error("Iconify initialization timeout"));
return; return;
} }
@@ -137,7 +169,11 @@ const { preloadIcons = [], timeout = 10000, retryCount = 3 } = Astro.props;
} }
isIconifyReady() { isIconifyReady() {
return typeof window !== 'undefined' && 'customElements' in window && customElements.get('iconify-icon') !== undefined; return (
typeof window !== "undefined" &&
"customElements" in window &&
customElements.get("iconify-icon") !== undefined
);
} }
onLoad(callback) { onLoad(callback) {
@@ -149,11 +185,11 @@ const { preloadIcons = [], timeout = 10000, retryCount = 3 } = Astro.props;
} }
notifyObservers() { notifyObservers() {
this.observers.forEach(callback => { this.observers.forEach((callback) => {
try { try {
callback(); callback();
} catch (error) { } catch (error) {
console.error('Error in icon load observer:', error); console.error("Error in icon load observer:", error);
} }
}); });
this.observers.clear(); this.observers.clear();
@@ -161,7 +197,7 @@ const { preloadIcons = [], timeout = 10000, retryCount = 3 } = Astro.props;
addToPreloadQueue(icons) { addToPreloadQueue(icons) {
if (Array.isArray(icons)) { if (Array.isArray(icons)) {
icons.forEach(icon => this.preloadQueue.add(icon)); icons.forEach((icon) => this.preloadQueue.add(icon));
} else { } else {
this.preloadQueue.add(icons); this.preloadQueue.add(icons);
} }
@@ -172,88 +208,34 @@ const { preloadIcons = [], timeout = 10000, retryCount = 3 } = Astro.props;
} }
async processPreloadQueue() { async processPreloadQueue() {
if (this.preloadQueue.size === 0) return;
const iconsToLoad = Array.from(this.preloadQueue);
this.preloadQueue.clear(); this.preloadQueue.clear();
await this.preloadIcons(iconsToLoad);
}
async preloadIcons(icons) {
if (!this.isLoaded || icons.length === 0) return;
return new Promise((resolve) => {
let loadedCount = 0;
const totalIcons = icons.length;
const tempElements = [];
const cleanup = () => {
tempElements.forEach(el => {
if (el.parentNode) {
el.parentNode.removeChild(el);
}
});
};
const checkComplete = () => {
loadedCount++;
if (loadedCount >= totalIcons) {
cleanup();
resolve();
}
};
icons.forEach(icon => {
const tempIcon = document.createElement('iconify-icon');
tempIcon.setAttribute('icon', icon);
tempIcon.style.cssText = 'position:absolute;top:-9999px;left:-9999px;width:1px;height:1px;opacity:0;pointer-events:none;';
tempIcon.addEventListener('load', checkComplete);
tempIcon.addEventListener('error', checkComplete);
tempElements.push(tempIcon);
document.body.appendChild(tempIcon);
});
// 设置超时清理
setTimeout(() => {
cleanup();
resolve();
}, 3000);
});
} }
} }
// 创建全局实例
window.__iconifyLoader = new IconifyLoader(); window.__iconifyLoader = new IconifyLoader();
// 立即开始加载 window.__iconifyLoader.load().catch((error) => {
window.__iconifyLoader.load().catch(error => { console.error("Failed to initialize Iconify:", error);
console.error('Failed to initialize Iconify:', error);
}); });
// 如果有预加载图标,添加到队列 if (preloadIcons.length > 0) {
if (preloadIcons && preloadIcons.length > 0) {
window.__iconifyLoader.addToPreloadQueue(preloadIcons); window.__iconifyLoader.addToPreloadQueue(preloadIcons);
} }
// 导出便捷函数到全局
window.loadIconify = () => window.__iconifyLoader.load(); window.loadIconify = () => window.__iconifyLoader.load();
window.preloadIcons = (icons) => window.__iconifyLoader.addToPreloadQueue(icons); window.preloadIcons = (icons) =>
window.onIconifyReady = (callback) => window.__iconifyLoader.onLoad(callback); window.__iconifyLoader.addToPreloadQueue(icons);
window.onIconifyReady = (callback) =>
window.__iconifyLoader.onLoad(callback);
// 页面可见性变化时重新检查 document.addEventListener("visibilitychange", () => {
document.addEventListener('visibilitychange', () => {
if (!document.hidden && !window.__iconifyLoader.isLoaded) { if (!document.hidden && !window.__iconifyLoader.isLoaded) {
window.__iconifyLoader.load().catch(console.error); window.__iconifyLoader.load().catch(console.error);
} }
}); });
})(); })();
</script> </script>
<!-- 为不支持JavaScript的情况提供备用方案 -->
<noscript> <noscript>
<style> <style>
iconify-icon { iconify-icon {

View File

@@ -9,7 +9,7 @@ interface Props {
const className = Astro.props.class; const className = Astro.props.class;
--- ---
<div data-pagefind-body class={`prose dark:prose-invert prose-base max-w-none! custom-md ${className}`}> <div class={`prose dark:prose-invert prose-base max-w-none! custom-md ${className}`}>
<!--<div class="prose dark:prose-invert max-w-none custom-md">--> <!--<div class="prose dark:prose-invert max-w-none custom-md">-->
<!--<div class="max-w-none custom-md">--> <!--<div class="max-w-none custom-md">-->
<slot/> <slot/>

View File

@@ -8,7 +8,6 @@ import { url } from "@utils/url";
import { getNavbarTransparentModeForWallpaperMode, getDefaultWallpaperMode } from "@utils/wallpaper"; import { getNavbarTransparentModeForWallpaperMode, getDefaultWallpaperMode } from "@utils/wallpaper";
import NavLinks from "@components/navbar/navLinks.astro"; import NavLinks from "@components/navbar/navLinks.astro";
import NavMenu from "@components/navbar/navMenu.svelte"; import NavMenu from "@components/navbar/navMenu.svelte";
import Search from "@components/navbar/search.svelte";
import Translator from "@components/navbar/translator.svelte"; import Translator from "@components/navbar/translator.svelte";
import DisplaySettings from "@components/navbar/displaySettings.svelte"; import DisplaySettings from "@components/navbar/displaySettings.svelte";
import LightDarkSwitch from "@components/navbar/lightDarkSwitch.svelte"; import LightDarkSwitch from "@components/navbar/lightDarkSwitch.svelte";
@@ -51,7 +50,6 @@ let links: NavbarLink[] = navbarConfig.links.map(
})} })}
</div> </div>
<div class="flex items-center navbar-buttons" id="navbar-buttons"> <div class="flex items-center navbar-buttons" id="navbar-buttons">
<!-- <Search client:load></Search> -->
<Translator client:load></Translator> <Translator client:load></Translator>
<!-- <DisplaySettings client:load></DisplaySettings> --> <!-- <DisplaySettings client:load></DisplaySettings> -->
<LightDarkSwitch client:load></LightDarkSwitch> <LightDarkSwitch client:load></LightDarkSwitch>
@@ -134,60 +132,3 @@ if (document.readyState === 'loading') {
initSemifullScrollDetection(); initSemifullScrollDetection();
} }
</script> </script>
{import.meta.env.PROD && <script is:inline define:vars={{scriptUrl: url('/pagefind/pagefind.js')}}>
async function loadPagefind() {
try {
const response = await fetch(scriptUrl, { method: 'HEAD' });
if (!response.ok) {
throw new Error(`Pagefind script not found: ${response.status}`);
}
const pagefind = await import(scriptUrl);
await pagefind.options({
excerptLength: 20
});
window.pagefind = pagefind;
document.dispatchEvent(new CustomEvent('pagefindready'));
console.log('Pagefind loaded and initialized successfully, event dispatched.');
} catch (error) {
console.error('Failed to load Pagefind:', error);
window.pagefind = {
search: () => Promise.resolve({ results: [] }),
options: () => Promise.resolve(),
};
document.dispatchEvent(new CustomEvent('pagefindloaderror'));
console.log('Pagefind load error, event dispatched.');
}
}
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', loadPagefind);
} else {
loadPagefind();
}
// Переинициализация Pagefind после навигации Swup
if (typeof window !== 'undefined' && window.swup) {
window.swup.hooks.on('page:view', () => {
// Переинициализируем Pagefind после навигации, если он еще не загружен
if (!window.pagefind || typeof window.pagefind.search !== 'function') {
loadPagefind();
}
});
} else if (typeof window !== 'undefined') {
// Если Swup еще не загружен, ждем события
document.addEventListener('swup:enable', () => {
if (window.swup) {
window.swup.hooks.on('page:view', () => {
if (!window.pagefind || typeof window.pagefind.search !== 'function') {
loadPagefind();
}
});
}
});
}
</script>}

View File

@@ -1,285 +0,0 @@
<script lang="ts">
import { onMount, onDestroy } from "svelte";
import Icon from "@iconify/svelte";
import type { SearchResult } from "@/global";
import { url } from "@utils/url";
import { navigateToPage } from "@utils/navigation";
import { onClickOutside } from "@utils/widget";
import { i18n } from "@i18n/translation";
import I18nKey from "@i18n/i18nKey";
import DropdownPanel from "@/components/common/DropdownPanel.svelte";
let keywordDesktop = $state("");
let keywordMobile = $state("");
let result: SearchResult[] = $state([]);
let isSearching = $state(false);
let pagefindLoaded = false;
let initialized = $state(false);
let isDesktopSearchExpanded = $state(false);
let debounceTimer: NodeJS.Timeout;
const fakeResult: SearchResult[] = [
{
url: url("/"),
meta: {
title: "This Is a Fake Search Result",
},
excerpt:
"Because the search cannot work in the <mark>dev</mark> environment.",
},
{
url: url("/"),
meta: {
title: "If You Want to Test the Search",
},
excerpt: "Try running <mark>npm build && npm preview</mark> instead.",
},
];
const togglePanel = () => {
const panel = document.getElementById("search-panel");
panel?.classList.toggle("float-panel-closed");
};
const toggleDesktopSearch = () => {
isDesktopSearchExpanded = !isDesktopSearchExpanded;
if (isDesktopSearchExpanded) {
setTimeout(() => {
const input = document.getElementById("search-input-desktop") as HTMLInputElement;
input?.focus();
}, 0);
}
};
const collapseDesktopSearch = () => {
if (!keywordDesktop) {
isDesktopSearchExpanded = false;
}
};
const handleBlur = () => {
// 延迟处理以允许搜索结果的点击事件先于折叠逻辑执行
setTimeout(() => {
isDesktopSearchExpanded = false;
// 仅隐藏面板并折叠,保留搜索关键词和结果以便下次展开时查看
setPanelVisibility(false, true);
}, 200);
};
const setPanelVisibility = (show: boolean, isDesktop: boolean): void => {
const panel = document.getElementById("search-panel");
if (!panel || !isDesktop) return;
if (show) {
panel.classList.remove("float-panel-closed");
} else {
panel.classList.add("float-panel-closed");
}
};
const closeSearchPanel = (): void => {
const panel = document.getElementById("search-panel");
if (panel) {
panel.classList.add("float-panel-closed");
}
// 清空搜索关键词和结果
keywordDesktop = "";
keywordMobile = "";
result = [];
};
const handleResultClick = (event: Event, url: string): void => {
event.preventDefault();
closeSearchPanel();
navigateToPage(url);
};
const search = async (keyword: string, isDesktop: boolean): Promise<void> => {
if (!keyword) {
setPanelVisibility(false, isDesktop);
result = [];
return;
}
if (!initialized) {
return;
}
isSearching = true;
try {
let searchResults: SearchResult[] = [];
if (import.meta.env.PROD && pagefindLoaded && window.pagefind) {
const response = await window.pagefind.search(keyword);
searchResults = await Promise.all(
response.results.map((item) => item.data()),
);
} else if (import.meta.env.DEV) {
searchResults = fakeResult;
} else {
searchResults = [];
console.error("Pagefind is not available in production environment.");
}
result = searchResults;
setPanelVisibility(result.length > 0, isDesktop);
} catch (error) {
console.error("Search error:", error);
result = [];
setPanelVisibility(false, isDesktop);
} finally {
isSearching = false;
}
};
const handleClickOutside = (event: MouseEvent) => {
const panel = document.getElementById("search-panel");
if (!panel || panel.classList.contains("float-panel-closed")) {
return;
}
onClickOutside(event, "search-panel", ["search-switch", "search-bar"], () => {
const panel = document.getElementById("search-panel");
panel?.classList.add("float-panel-closed");
isDesktopSearchExpanded = false;
});
};
onMount(() => {
document.addEventListener("click", handleClickOutside);
const initializeSearch = () => {
initialized = true;
pagefindLoaded =
typeof window !== "undefined" &&
!!window.pagefind &&
typeof window.pagefind.search === "function";
console.log("Pagefind status on init:", pagefindLoaded);
};
if (import.meta.env.DEV) {
console.log(
"Pagefind is not available in development mode. Using mock data.",
);
initializeSearch();
} else {
document.addEventListener("pagefindready", () => {
console.log("Pagefind ready event received.");
initializeSearch();
});
document.addEventListener("pagefindloaderror", () => {
console.warn(
"Pagefind load error event received. Search functionality will be limited.",
);
initializeSearch(); // Initialize with pagefindLoaded as false
});
// Fallback in case events are not caught or pagefind is already loaded by the time this script runs
setTimeout(() => {
if (!initialized) {
console.log("Fallback: Initializing search after timeout.");
initializeSearch();
}
}, 2000); // Adjust timeout as needed
}
});
$effect(() => {
if (initialized) {
const keyword = keywordDesktop || keywordMobile;
const isDesktop = !!keywordDesktop || isDesktopSearchExpanded;
clearTimeout(debounceTimer);
if (keyword) {
debounceTimer = setTimeout(() => {
search(keyword, isDesktop);
}, 300);
} else {
result = [];
setPanelVisibility(false, isDesktop);
}
}
});
$effect(() => {
if (typeof document !== 'undefined') {
const navbar = document.getElementById('navbar');
if (isDesktopSearchExpanded) {
navbar?.classList.add('is-searching');
} else {
navbar?.classList.remove('is-searching');
}
}
});
onDestroy(() => {
if (typeof document !== 'undefined') {
document.removeEventListener("click", handleClickOutside);
const navbar = document.getElementById('navbar');
navbar?.classList.remove('is-searching');
}
clearTimeout(debounceTimer);
});
</script>
<!-- search bar for desktop view (collapsed by default) -->
<div
id="search-bar"
class="hidden lg:flex transition-all items-center h-11 rounded-lg
{isDesktopSearchExpanded ? 'bg-black/4 hover:bg-black/6 focus-within:bg-black/6 dark:bg-white/5 dark:hover:bg-white/10 dark:focus-within:bg-white/10' : 'btn-plain scale-animation active:scale-90'}
{isDesktopSearchExpanded ? 'w-48' : 'w-11'}"
role="button"
tabindex="0"
aria-label="Search"
onmouseenter={() => {if (!isDesktopSearchExpanded) toggleDesktopSearch()}}
onmouseleave={collapseDesktopSearch}
>
<Icon icon="material-symbols:search" class="absolute text-[1.25rem] pointer-events-none {isDesktopSearchExpanded ? 'ml-3' : 'left-1/2 -translate-x-1/2'} transition my-auto {isDesktopSearchExpanded ? 'text-black/30 dark:text-white/30' : ''}"></Icon>
<input id="search-input-desktop" placeholder="{i18n(I18nKey.search)}" bind:value={keywordDesktop}
onfocus={() => {if (!isDesktopSearchExpanded) toggleDesktopSearch(); search(keywordDesktop, true)}}
onblur={handleBlur}
class="transition-all pl-10 text-sm bg-transparent outline-0
h-full {isDesktopSearchExpanded ? 'w-36' : 'w-0'} text-black/50 dark:text-white/50"
>
</div>
<!-- toggle btn for phone/tablet view -->
<button onclick={togglePanel} aria-label="Search Panel" id="search-switch"
class="btn-plain scale-animation lg:hidden! rounded-lg w-11 h-11 active:scale-90">
<Icon icon="material-symbols:search" class="text-[1.25rem]"></Icon>
</button>
<!-- search panel -->
<DropdownPanel
id="search-panel"
class="float-panel-closed absolute md:w-120 top-20 left-4 md:left-[unset] right-4 z-50 search-panel"
>
<!-- search bar inside panel for phone/tablet -->
<div id="search-bar-inside" class="flex relative lg:hidden transition-all items-center h-11 rounded-xl
bg-black/4 hover:bg-black/6 focus-within:bg-black/6
dark:bg-white/5 dark:hover:bg-white/10 dark:focus-within:bg-white/10
">
<Icon icon="material-symbols:search" class="absolute text-[1.25rem] pointer-events-none ml-3 transition my-auto text-black/30 dark:text-white/30"></Icon>
<input placeholder="Search" bind:value={keywordMobile}
class="pl-10 absolute inset-0 text-sm bg-transparent outline-0
focus:w-60 text-black/50 dark:text-white/50"
>
</div>
<!-- search results -->
{#each result as item}
<a href={item.url}
onclick={(e) => handleResultClick(e, item.url)}
class="transition first-of-type:mt-2 lg:first-of-type:mt-0 group block
rounded-xl text-lg px-3 py-2 hover:bg-(--btn-plain-bg-hover) active:bg-(--btn-plain-bg-active)">
<div class="transition text-90 inline-flex font-bold group-hover:text-(--primary)">
{item.meta.title}<Icon icon="fa6-solid:chevron-right" class="transition text-[0.75rem] translate-x-1 my-auto text-(--primary)"></Icon>
</div>
<div class="transition text-sm text-50">
{@html item.excerpt}
</div>
</a>
{/each}
</DropdownPanel>
<style>
input:focus {
outline: 0;
}
:global(.search-panel) {
max-height: calc(100vh - 100px);
overflow-y: auto;
}
</style>

37
src/global.d.ts vendored
View File

@@ -14,13 +14,6 @@ declare global {
__iconifyLoader: { __iconifyLoader: {
load: () => Promise<void>; load: () => Promise<void>;
}; };
pagefind: {
search: (query: string) => Promise<{
results: Array<{
data: () => Promise<SearchResult>;
}>;
}>;
};
translate?: { translate?: {
service: { service: {
use: (service: string) => void; use: (service: string) => void;
@@ -51,32 +44,4 @@ declare global {
} }
} }
export {};
interface SearchResult {
url: string;
meta: {
title: string;
};
excerpt: string;
content?: string;
word_count?: number;
filters?: Record<string, unknown>;
anchors?: Array<{
element: string;
id: string;
text: string;
location: number;
}>;
weighted_locations?: Array<{
weight: number;
balanced_score: number;
location: number;
}>;
locations?: number[];
raw_content?: string;
raw_url?: string;
sub_results?: SearchResult[];
}
export { SearchResult };

View File

@@ -127,7 +127,6 @@ const umamiScripts = umamiConfig.scripts || ""; // 获取Umami scripts配置
<!-- Resource Preconnect --> <!-- Resource Preconnect -->
<link rel="preconnect" href="https://fonts.googleapis.com"> <link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin> <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link rel="preconnect" href="https://code.iconify.design">
<!-- Font Loader --> <!-- Font Loader -->
<FontLoader /> <FontLoader />
<!-- title --> <!-- title -->
@@ -481,26 +480,19 @@ loadAndInitTranslate();
// Initialize particle effects // Initialize particle effects
setupParticleEffects(); setupParticleEffects();
// Remove is-loading class and trigger initial-animation after page load function hideLoadingOverlay() {
if (typeof window !== 'undefined') {
window.addEventListener('load', () => {
const overlay = document.getElementById('loading-overlay'); const overlay = document.getElementById('loading-overlay');
const enableBanner = document.body.classList.contains('enable-banner'); const enableBanner = document.body.classList.contains('enable-banner');
// 触发 LoadingOverlay 淡出
if (overlay) { if (overlay) {
overlay.classList.add('fade-out'); overlay.classList.add('fade-out');
} }
// 等待 LoadingOverlay 淡出600ms
setTimeout(() => { setTimeout(() => {
if (overlay) { if (overlay) {
overlay.style.display = 'none'; overlay.style.display = 'none';
} }
if (enableBanner) { if (enableBanner) {
// banner 开始恢复过渡:添加 banner-restoring 类(解除 CSS 中的 height: 0
document.documentElement.classList.add('banner-restoring'); document.documentElement.classList.add('banner-restoring');
// 等待 banner 恢复过渡完成600ms
setTimeout(() => { setTimeout(() => {
// 触发各容器淡入动画
document.documentElement.classList.remove('is-loading'); document.documentElement.classList.remove('is-loading');
document.documentElement.classList.add('show-initial-animation'); document.documentElement.classList.add('show-initial-animation');
setTimeout(() => { setTimeout(() => {
@@ -509,7 +501,6 @@ if (typeof window !== 'undefined') {
}, 1200); }, 1200);
}, 600); }, 600);
} else { } else {
// 非 banner 模式,直接触发淡入
document.documentElement.classList.remove('is-loading'); document.documentElement.classList.remove('is-loading');
document.documentElement.classList.add('show-initial-animation'); document.documentElement.classList.add('show-initial-animation');
setTimeout(() => { setTimeout(() => {
@@ -517,6 +508,13 @@ if (typeof window !== 'undefined') {
}, 1200); }, 1200);
} }
}, 600); }, 600);
}); }
if (typeof window !== 'undefined') {
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', hideLoadingOverlay);
} else {
hideLoadingOverlay();
}
} }
</script> </script>

View File

@@ -153,9 +153,6 @@ const jsonLd = {
<!-- title --> <!-- title -->
<div class="relative onload-animation-up"> <div class="relative onload-animation-up">
<div <div
data-pagefind-body
data-pagefind-weight="10"
data-pagefind-meta="title"
class="transition w-full block font-bold mb-3 class="transition w-full block font-bold mb-3
text-3xl md:text-[2.25rem]/[2.75rem] text-3xl md:text-[2.25rem]/[2.75rem]
text-black/90 dark:text-white/90 text-black/90 dark:text-white/90

View File

@@ -0,0 +1,78 @@
import { addCollection } from "@iconify/svelte/dist/functions.js";
import type { IconifyJSON } from "@iconify/types";
import devicon from "@iconify-json/devicon/icons.json";
import gameIcons from "@iconify-json/game-icons/icons.json";
import hugeicons from "@iconify-json/hugeicons/icons.json";
import iconoir from "@iconify-json/iconoir/icons.json";
import logos from "@iconify-json/logos/icons.json";
import materialIconTheme from "@iconify-json/material-icon-theme/icons.json";
import materialSymbols from "@iconify-json/material-symbols/icons.json";
import mdi from "@iconify-json/mdi/icons.json";
import mingcute from "@iconify-json/mingcute/icons.json";
import skillIcons from "@iconify-json/skill-icons/icons.json";
import vscodeIcons from "@iconify-json/vscode-icons/icons.json";
const collections: IconifyJSON[] = [
materialSymbols,
logos,
devicon,
hugeicons,
mingcute,
mdi,
skillIcons,
vscodeIcons,
materialIconTheme,
gameIcons,
iconoir,
];
let svelteRegistered = false;
let webComponentRegistered = false;
function addCollectionsToWebComponent(): void {
if (typeof window === "undefined" || webComponentRegistered) {
return;
}
const IconifyIcon = customElements.get("iconify-icon") as
| (CustomElementConstructor & {
addCollection?: (data: IconifyJSON, provider?: string) => boolean;
disableCache?: (storage: string) => void;
})
| undefined;
if (!IconifyIcon?.addCollection) {
return;
}
IconifyIcon.disableCache?.("all");
for (const collection of collections) {
IconifyIcon.addCollection(collection);
}
webComponentRegistered = true;
}
/** Register offline collections for @iconify/svelte (navbar, player, etc.). */
export function registerSvelteIconifyCollections(): void {
if (svelteRegistered) {
return;
}
svelteRegistered = true;
for (const collection of collections) {
addCollection(collection);
}
}
/** Register offline collections for iconify-icon web component (icon.astro). */
export function registerWebComponentIconifyCollections(): void {
addCollectionsToWebComponent();
}
/** Register all offline icon sets (Svelte + web component when available). */
export function registerIconifyCollections(): void {
registerSvelteIconifyCollections();
registerWebComponentIconifyCollections();
}