refactor(search): support local/mirrored FlexSearch assets (#956)

* feat(search): support local and mirrored FlexSearch assets

Add params.search.flexsearch.base/js overrides in search script loading.

Document offline/local and mirror-based script asset configuration examples.

* refactor(scripts): normalize remote asset URL joins

* docs(config): clarify local asset examples

* docs(i18n): add local asset config guidance
This commit is contained in:
Xin
2026-03-06 22:41:40 +00:00
committed by GitHub
parent 792c750dec
commit e12d3b98cc
9 changed files with 368 additions and 19 deletions

View File

@@ -340,6 +340,73 @@ params:
# js: "js/medium-zoom.min.js" # اختیاری، نسبت به base یا assetهای محلی
```
### اسکریپت‌های محلی و آینه‌شده
Hextra می‌تواند وابستگی‌های اختیاری فرانت‌اند را از منابع مختلف بارگیری کند:
- تنظیمات پیش‌فرض قالب (CDN)
- URLهای آینه داخلی از طریق `base`
- assetهای محلی Hugo از طریق `js` / `css`
برای assetهای محلی، فایل‌های vendor را در پوشه `assets/` سایت خود قرار دهید و مقادیر پیکربندی را به همان مسیرهای asset اشاره دهید:
```yaml {filename="hugo.yaml"}
params:
imageZoom:
enable: true
js: "js/vendor/medium-zoom.min.js"
mermaid:
js: "js/vendor/mermaid.min.js"
asciinema:
js: "js/vendor/asciinema-player.min.js"
css: "css/vendor/asciinema-player.css"
math:
engine: katex
katex:
css: "css/vendor/katex.min.css"
assets:
- "fonts/KaTeX_Main-Regular.woff2"
- "fonts/KaTeX_Math-Italic.woff2"
search:
type: flexsearch
flexsearch:
js: "js/vendor/flexsearch.bundle.min.js"
```
`imageZoom.enable: true` فقط به این دلیل لازم است که بزرگ‌نمایی تصویر به‌صورت پیش‌فرض غیرفعال است.
برای KaTeX، مطمئن شوید همه فایل‌های فونتی که فایل CSS انتخابی شما به آن‌ها ارجاع می‌دهد منتشر می‌شوند، نه فقط دو موردی که در این مثال آمده‌اند.
برای استفاده از یک آینه داخلی، `base` را تنظیم کنید (و در صورت تفاوت نام فایل، در صورت نیاز `js` / `css` را نیز مشخص کنید):
```yaml {filename="hugo.yaml"}
params:
imageZoom:
base: "https://mirror.example.com/medium-zoom/dist"
mermaid:
base: "https://mirror.example.com/mermaid/dist"
asciinema:
base: "https://mirror.example.com/asciinema-player/dist/bundle"
math:
engine: katex
katex:
base: "https://mirror.example.com/katex/dist"
search:
flexsearch:
base: "https://mirror.example.com/flexsearch/dist"
# js: "flexsearch.bundle.min.js"
```
> [!NOTE]
> برای سفارشی‌سازی منبع بارگذاری MathJax، قالب `layouts/_partials/scripts/mathjax.html` را در پروژه خود بازنویسی کنید.
### عرض صفحه
عرض پوستهٔ صفحه را می‌توان با پارامتر `params.page.width` تنظیم کرد:
@@ -440,6 +507,17 @@ params:
index: content
```
همچنین می‌توانید محل بارگیری runtime مربوط به FlexSearch را کنترل کنید:
```yaml {filename="hugo.yaml"}
params:
search:
flexsearch:
version: "0.8.143" # نسخه پیش‌فرض CDN
# base: "https://mirror.example.com/flexsearch/dist" # آدرس پایهٔ remote اختیاری
# js: "js/vendor/flexsearch.bundle.min.js" # مسیر asset محلی یا فایل سفارشی زیر base راه دور
```
گزینه‌های `flexsearch.index`:
- `content` - محتوای کامل صفحه (پیش‌فرض)

View File

@@ -340,6 +340,73 @@ params:
# js: "js/medium-zoom.min.js" # オプション、base またはローカルアセットからの相対パス
```
### ローカルおよびミラー済みスクリプトアセット
Hextra はオプションのフロントエンド依存ファイルを複数のソースから読み込めます:
- テーマのデフォルト設定CDN
- `base` を使った社内ミラー URL
- `js` / `css` を使った Hugo のローカルアセット
ローカルアセットを使う場合は、vendor ファイルをサイトの `assets/` ディレクトリに配置し、そのアセットパスを設定値に指定します:
```yaml {filename="hugo.yaml"}
params:
imageZoom:
enable: true
js: "js/vendor/medium-zoom.min.js"
mermaid:
js: "js/vendor/mermaid.min.js"
asciinema:
js: "js/vendor/asciinema-player.min.js"
css: "css/vendor/asciinema-player.css"
math:
engine: katex
katex:
css: "css/vendor/katex.min.css"
assets:
- "fonts/KaTeX_Main-Regular.woff2"
- "fonts/KaTeX_Math-Italic.woff2"
search:
type: flexsearch
flexsearch:
js: "js/vendor/flexsearch.bundle.min.js"
```
`imageZoom.enable: true` が必要なのは、画像ズームがデフォルトで無効になっているためです。
KaTeX については、この例の 2 つだけでなく、選択した CSS が参照するすべてのフォントファイルを公開してください。
社内ミラーを使う場合は `base` を設定し、ファイル名が異なる場合のみ `js` / `css` を追加してください:
```yaml {filename="hugo.yaml"}
params:
imageZoom:
base: "https://mirror.example.com/medium-zoom/dist"
mermaid:
base: "https://mirror.example.com/mermaid/dist"
asciinema:
base: "https://mirror.example.com/asciinema-player/dist/bundle"
math:
engine: katex
katex:
base: "https://mirror.example.com/katex/dist"
search:
flexsearch:
base: "https://mirror.example.com/flexsearch/dist"
# js: "flexsearch.bundle.min.js"
```
> [!NOTE]
> MathJax の読み込み元をカスタマイズするには、プロジェクト内で `layouts/_partials/scripts/mathjax.html` を上書きしてください。
### ページ幅
レイアウト全体の幅は `params.page.width` で設定できます:
@@ -440,6 +507,17 @@ params:
index: content
```
FlexSearch ランタイムの読み込み元も制御できます:
```yaml {filename="hugo.yaml"}
params:
search:
flexsearch:
version: "0.8.143" # デフォルトの CDN バージョン
# base: "https://mirror.example.com/flexsearch/dist" # オプションのリモート base URL
# js: "js/vendor/flexsearch.bundle.min.js" # ローカルアセットのパス、またはリモート base 配下のカスタムファイル
```
`flexsearch.index` のオプション:
- `content` - ページの全文(デフォルト)

View File

@@ -362,6 +362,73 @@ params:
# js: "js/medium-zoom.min.js" # optional, relative to the base or local assets
```
### Local and Mirrored Script Assets
Hextra can load optional frontend dependencies from different sources:
- Theme defaults (CDN)
- Internal mirror URLs via `base`
- Local Hugo assets via `js` / `css`
For local assets, place vendor files under your site's `assets/` directory and point config values to those asset paths:
```yaml {filename="hugo.yaml"}
params:
imageZoom:
enable: true
js: "js/vendor/medium-zoom.min.js"
mermaid:
js: "js/vendor/mermaid.min.js"
asciinema:
js: "js/vendor/asciinema-player.min.js"
css: "css/vendor/asciinema-player.css"
math:
engine: katex
katex:
css: "css/vendor/katex.min.css"
assets:
- "fonts/KaTeX_Main-Regular.woff2"
- "fonts/KaTeX_Math-Italic.woff2"
search:
type: flexsearch
flexsearch:
js: "js/vendor/flexsearch.bundle.min.js"
```
`imageZoom.enable: true` is only needed here because image zoom is disabled by default.
For KaTeX, make sure to publish all font files referenced by your chosen CSS file, not just the two shown in this example.
To use an internal mirror instead, set `base` (and optionally `js` / `css` when the filename differs):
```yaml {filename="hugo.yaml"}
params:
imageZoom:
base: "https://mirror.example.com/medium-zoom/dist"
mermaid:
base: "https://mirror.example.com/mermaid/dist"
asciinema:
base: "https://mirror.example.com/asciinema-player/dist/bundle"
math:
engine: katex
katex:
base: "https://mirror.example.com/katex/dist"
search:
flexsearch:
base: "https://mirror.example.com/flexsearch/dist"
# js: "flexsearch.bundle.min.js"
```
> [!NOTE]
> To customize MathJax source loading, override `layouts/_partials/scripts/mathjax.html` in your site.
### Page Width
The layout shell width can be customized by the `params.page.width` parameter in the config file:
@@ -462,6 +529,17 @@ params:
index: content
```
You can also control where the FlexSearch runtime is loaded from:
```yaml {filename="hugo.yaml"}
params:
search:
flexsearch:
version: "0.8.143" # default CDN version
# base: "https://mirror.example.com/flexsearch/dist" # optional remote base URL
# js: "js/vendor/flexsearch.bundle.min.js" # local asset path, or custom file under remote base
```
Options for `flexsearch.index`:
- `content` - full content of the page (default)

View File

@@ -340,6 +340,73 @@ params:
# js: "js/medium-zoom.min.js" # 可选,相对于 base 或本地资源
```
### 本地与镜像脚本资源
Hextra 可以从多种来源加载可选的前端依赖:
- 主题默认配置CDN
- 通过 `base` 指定的内部镜像 URL
- 通过 `js` / `css` 指定的 Hugo 本地资源
对于本地资源,请将 vendor 文件放在站点的 `assets/` 目录下,并在配置中引用这些资源路径:
```yaml {filename="hugo.yaml"}
params:
imageZoom:
enable: true
js: "js/vendor/medium-zoom.min.js"
mermaid:
js: "js/vendor/mermaid.min.js"
asciinema:
js: "js/vendor/asciinema-player.min.js"
css: "css/vendor/asciinema-player.css"
math:
engine: katex
katex:
css: "css/vendor/katex.min.css"
assets:
- "fonts/KaTeX_Main-Regular.woff2"
- "fonts/KaTeX_Math-Italic.woff2"
search:
type: flexsearch
flexsearch:
js: "js/vendor/flexsearch.bundle.min.js"
```
这里只有因为图片缩放默认关闭,才需要设置 `imageZoom.enable: true`。
对于 KaTeX请确保发布你所选 CSS 文件引用的全部字体文件,而不仅仅是此示例中的两个。
如果要使用内部镜像,请设置 `base`;只有当文件名不同时,才需要额外设置 `js` / `css`
```yaml {filename="hugo.yaml"}
params:
imageZoom:
base: "https://mirror.example.com/medium-zoom/dist"
mermaid:
base: "https://mirror.example.com/mermaid/dist"
asciinema:
base: "https://mirror.example.com/asciinema-player/dist/bundle"
math:
engine: katex
katex:
base: "https://mirror.example.com/katex/dist"
search:
flexsearch:
base: "https://mirror.example.com/flexsearch/dist"
# js: "flexsearch.bundle.min.js"
```
> [!NOTE]
> 如需自定义 MathJax 的加载来源,请在项目中覆盖 `layouts/_partials/scripts/mathjax.html`。
### 页面宽度
页面整体布局宽度可通过 `params.page.width` 配置:
@@ -440,6 +507,17 @@ params:
index: content
```
你也可以控制 FlexSearch runtime 的加载来源:
```yaml {filename="hugo.yaml"}
params:
search:
flexsearch:
version: "0.8.143" # 默认 CDN 版本
# base: "https://mirror.example.com/flexsearch/dist" # 可选的远程 base URL
# js: "js/vendor/flexsearch.bundle.min.js" # 本地资源路径,或远程 base 下的自定义文件
```
`flexsearch.index` 的选项:
- `content` - 页面的完整内容(默认)

View File

@@ -33,7 +33,7 @@
{{- /* CSS retrieval: get raw CSS from either local asset or remote, then process */ -}}
{{- if $isRemoteBase -}}
{{- $cssPath := cond (ne $asciinemaCssAsset "") $asciinemaCssAsset "asciinema-player.css" -}}
{{- $asciinemaCssUrl := printf "%s/%s" $asciinemaBase $cssPath -}}
{{- $asciinemaCssUrl := urls.JoinPath $asciinemaBase $cssPath -}}
{{- with try (resources.GetRemote $asciinemaCssUrl) -}}
{{- with .Err -}}
{{- errorf "Could not retrieve Asciinema css file from %s. Reason: %s." $asciinemaCssUrl . -}}
@@ -56,7 +56,7 @@
{{- /* JS retrieval: get raw JS from either local asset or remote, then process */ -}}
{{- if $isRemoteBase -}}
{{- $jsPath := cond (ne $asciinemaJsAsset "") $asciinemaJsAsset (printf "asciinema-player%s.js" $minSuffix) -}}
{{- $asciinemaJsUrl := printf "%s/%s" $asciinemaBase $jsPath -}}
{{- $asciinemaJsUrl := urls.JoinPath $asciinemaBase $jsPath -}}
{{- with try (resources.GetRemote $asciinemaJsUrl) -}}
{{- with .Err -}}
{{- errorf "Could not retrieve Asciinema js file from %s. Reason: %s." $asciinemaJsUrl . -}}

View File

@@ -39,7 +39,7 @@
{{- $minSuffix := cond hugo.IsProduction ".min" "" -}}
{{- if $isRemoteBase -}}
{{- $cssPath := cond (ne $katexCssAsset "") $katexCssAsset (printf "katex%s.css" $minSuffix) -}}
{{- $katexCssUrl := printf "%s/%s" $katexBase $cssPath -}}
{{- $katexCssUrl := urls.JoinPath $katexBase $cssPath -}}
{{- with try (resources.GetRemote $katexCssUrl) -}}
{{- with .Err -}}
{{- errorf "Could not retrieve KaTeX css file from %s. Reason: %s." $katexCssUrl . -}}
@@ -59,7 +59,7 @@
{{- $cssContent := . -}}
{{- if $isRemoteBase -}}
{{- $fontPattern := "url(fonts/" -}}
{{- $fontSub := printf "url(%s/fonts/" $katexBase -}}
{{- $fontSub := printf "url(%s/" (urls.JoinPath $katexBase "fonts") -}}
{{- $cssContent = strings.Replace $cssContent $fontPattern $fontSub -}}
{{- end -}}
{{- with resources.FromString (printf "css/katex%s.css" $minSuffix) $cssContent -}}
@@ -69,7 +69,7 @@
{{- else -}}
{{- if not $isRemoteBase -}}
{{- $cssPath := cond (ne $katexCssAsset "") $katexCssAsset (printf "katex%s.css" $minSuffix) -}}
<link rel="stylesheet" href="{{ printf "%s/%s" $katexBase $cssPath }}" />
<link rel="stylesheet" href="{{ urls.JoinPath $katexBase $cssPath }}" />
{{- end -}}
{{- end -}}

View File

@@ -28,7 +28,7 @@
{{- /* JS retrieval: get raw JS from either local asset or remote, then process */ -}}
{{- if $isRemoteBase -}}
{{- $jsPath := cond (ne $zoomJsAsset "") $zoomJsAsset (printf "medium-zoom%s.js" $minSuffix) -}}
{{- $zoomJsUrl := printf "%s/%s" $zoomBase $jsPath -}}
{{- $zoomJsUrl := urls.JoinPath $zoomBase $jsPath -}}
{{- with try (resources.GetRemote $zoomJsUrl) -}}
{{- with .Err -}}
{{- errorf "Could not retrieve Medium Zoom js file from %s. Reason: %s." $zoomJsUrl . -}}

View File

@@ -28,7 +28,7 @@
{{- /* JS retrieval: get raw JS from either local asset or remote, then process */ -}}
{{- if $isRemoteBase -}}
{{- $jsPath := cond (ne $mermaidJsAsset "") $mermaidJsAsset (printf "mermaid%s.js" $minSuffix) -}}
{{- $mermaidJsUrl := printf "%s/%s" $mermaidBase $jsPath -}}
{{- $mermaidJsUrl := urls.JoinPath $mermaidBase $jsPath -}}
{{- with try (resources.GetRemote $mermaidJsUrl) -}}
{{- with .Err -}}
{{- errorf "Could not retrieve Mermaid js file from %s. Reason: %s." $mermaidJsUrl . -}}

View File

@@ -7,18 +7,55 @@
{{- if hugo.IsProduction -}}
{{- $jsSearch = $jsSearch | minify | fingerprint -}}
{{- end -}}
{{- $flexSearchBase := "" -}}
{{- $useDefaultCdn := true -}}
{{- with site.Params.search.flexsearch.base -}}
{{- $flexSearchBase = . -}}
{{- $useDefaultCdn = false -}}
{{- end -}}
{{- $flexSearchJsAsset := "" -}}
{{- with site.Params.search.flexsearch.js -}}
{{- $flexSearchJsAsset = . -}}
{{- end -}}
{{- /* If only js is set without base, use local asset loading. */ -}}
{{- if and $useDefaultCdn (ne $flexSearchJsAsset "") -}}
{{- $useDefaultCdn = false -}}
{{- end -}}
{{- $bundleSuffix := cond hugo.IsProduction ".min" ".debug" -}}
{{- if $useDefaultCdn -}}
{{- $flexSearchVersion := site.Params.search.flexsearch.version | default "0.8.143" -}}
{{- $flexSearchJsUrl := printf "https://cdn.jsdelivr.net/npm/flexsearch@%s/dist/flexsearch.bundle%s.js" $flexSearchVersion (cond hugo.IsProduction ".min" ".debug") -}}
{{ with try (resources.GetRemote $flexSearchJsUrl) -}}
{{ with .Err -}}
{{ errorf "Could not retrieve FlexSearch js file from %s. Reason: %s." $flexSearchJsUrl . -}}
{{ else with.Value -}}
{{ with resources.Copy (printf "js/flexsearch.js") . -}}
{{ $flexSearchJs := . | fingerprint -}}
{{- $flexSearchBase = printf "https://cdn.jsdelivr.net/npm/flexsearch@%s/dist" $flexSearchVersion -}}
{{- end -}}
{{- $isRemoteBase := or (strings.HasPrefix $flexSearchBase "http://") (strings.HasPrefix $flexSearchBase "https://") -}}
{{- if $isRemoteBase -}}
{{- $jsPath := cond (ne $flexSearchJsAsset "") $flexSearchJsAsset (printf "flexsearch.bundle%s.js" $bundleSuffix) -}}
{{- $flexSearchJsUrl := urls.JoinPath $flexSearchBase $jsPath -}}
{{- with try (resources.GetRemote $flexSearchJsUrl) -}}
{{- with .Err -}}
{{- errorf "Could not retrieve FlexSearch js file from %s. Reason: %s." $flexSearchJsUrl . -}}
{{- else with .Value -}}
{{- with resources.Copy "js/flexsearch.js" . -}}
{{- $flexSearchJs := . | fingerprint -}}
<script defer src="{{ $flexSearchJs.RelPermalink }}" integrity="{{ $flexSearchJs.Data.Integrity }}" crossorigin="anonymous"></script>
{{ end -}}
{{ end -}}
{{ end -}}
{{- end -}}
{{- end -}}
{{- end -}}
{{- else if $flexSearchJsAsset -}}
{{- with resources.Get $flexSearchJsAsset -}}
{{- $flexSearchJs := . | fingerprint -}}
<script defer src="{{ $flexSearchJs.RelPermalink }}" integrity="{{ $flexSearchJs.Data.Integrity }}" crossorigin="anonymous"></script>
{{- else -}}
{{- errorf "FlexSearch js asset not found at %q" $flexSearchJsAsset -}}
{{- end -}}
{{- else if not $useDefaultCdn -}}
{{- errorf "FlexSearch local loading requires params.search.flexsearch.js when using non-remote base %q" $flexSearchBase -}}
{{- end -}}
<script defer src="{{ $jsSearch.RelPermalink }}" integrity="{{ $jsSearch.Data.Integrity }}"></script>
{{- else -}}
{{- warnf `search type "%s" is not supported` $searchType -}}