mirror of
https://github.com/hugo-fixit/FixIt.git
synced 2026-08-26 00:08:55 +00:00
Compare commits
63 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 57f98b48c5 | |||
| 1d07b40d08 | |||
| 47b7c969f1 | |||
| 152e7fa5a2 | |||
| df90a0eb96 | |||
| 5f1e79f49c | |||
| 8b6806bbf3 | |||
| c65852d882 | |||
| 224621b114 | |||
| e0940bd4d3 | |||
| 8782b46f53 | |||
| 9f378691f1 | |||
| 9f4d33a670 | |||
| 3c449a9017 | |||
| 65e9f2ff92 | |||
| 54fe7bdc83 | |||
| 0bc1862853 | |||
| b680d609a1 | |||
| a9c29b1641 | |||
| 7bdd0c5869 | |||
| 8d7703a418 | |||
| da2f085969 | |||
| e3acdb9732 | |||
| ece55d0d36 | |||
| a73416e2be | |||
| 9e2f388022 | |||
| 610fa1d561 | |||
| a5a7b98c95 | |||
| a5d24f395e | |||
| f5509e5b82 | |||
| d35dd6a120 | |||
| 5322a15ceb | |||
| a254e10136 | |||
| 15b415ff2e | |||
| 2c0b48370b | |||
| e07eefd506 | |||
| 7a28234de3 | |||
| 0d6af76f06 | |||
| 10da1f731f | |||
| bfee4041d6 | |||
| 78f1a8583a | |||
| 3e4cb8d699 | |||
| b274b1b33b | |||
| 06419f7968 | |||
| 3326ea4515 | |||
| 5811b21f02 | |||
| 54fb90388f | |||
| 3242dbf2fb | |||
| 966ace5a66 | |||
| 20c0997cc5 | |||
| 69920e0140 | |||
| d81d1a0ff4 | |||
| e6625fb118 | |||
| 405024989c | |||
| 874337f7d1 | |||
| b95a29e6e4 | |||
| 6188165504 | |||
| e4bd5731df | |||
| 391d2ecd56 | |||
| b5419ef704 | |||
| a2d756777f | |||
| 43c439eee0 | |||
| e58bdd4db5 |
@@ -43,7 +43,7 @@ jobs:
|
||||
|
||||
# Initializes the CodeQL tools for scanning.
|
||||
- name: Initialize CodeQL
|
||||
uses: github/codeql-action/init@v4
|
||||
uses: github/codeql-action/init@v4.37.3
|
||||
with:
|
||||
languages: ${{ matrix.language }}
|
||||
config-file: ./.github/configs/codeql.yml
|
||||
@@ -55,7 +55,7 @@ jobs:
|
||||
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
|
||||
# If this step fails, then you should remove it and run the build manually (see below)
|
||||
- name: Autobuild
|
||||
uses: github/codeql-action/autobuild@v4
|
||||
uses: github/codeql-action/autobuild@v4.37.3
|
||||
|
||||
# ℹ️ Command-line programs to run using the OS shell.
|
||||
# 📚 https://git.io/JvXDl
|
||||
@@ -69,4 +69,4 @@ jobs:
|
||||
# make release
|
||||
|
||||
- name: Perform CodeQL Analysis
|
||||
uses: github/codeql-action/analyze@v4
|
||||
uses: github/codeql-action/analyze@v4.37.3
|
||||
|
||||
@@ -15,7 +15,7 @@ jobs:
|
||||
uses: actions/checkout@v7
|
||||
- uses: pnpm/action-setup@v6
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v6
|
||||
uses: actions/setup-node@v7
|
||||
with:
|
||||
node-version-file: .nvmrc
|
||||
cache: pnpm
|
||||
@@ -23,6 +23,8 @@ jobs:
|
||||
run: pnpm install --frozen-lockfile
|
||||
- name: Generate lexer map
|
||||
run: pnpm gen:lexers
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
- name: Create Pull Request
|
||||
uses: peter-evans/create-pull-request@v8
|
||||
with:
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
name: Publish post-encrypt
|
||||
name: Publish @hugo-fixit/encrypt
|
||||
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- 'post-encrypt-v*.*.*'
|
||||
- 'encrypt-v*.*.*'
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
@@ -11,7 +11,7 @@ permissions:
|
||||
id-token: write
|
||||
|
||||
concurrency:
|
||||
group: publish-post-encrypt-${{ github.ref }}
|
||||
group: publish-encrypt-${{ github.ref }}
|
||||
cancel-in-progress: false
|
||||
|
||||
jobs:
|
||||
@@ -23,7 +23,7 @@ jobs:
|
||||
|
||||
- uses: pnpm/action-setup@v6
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v6
|
||||
uses: actions/setup-node@v7
|
||||
with:
|
||||
node-version-file: .nvmrc
|
||||
registry-url: 'https://registry.npmjs.org'
|
||||
@@ -36,8 +36,8 @@ jobs:
|
||||
run: pnpm install --frozen-lockfile
|
||||
|
||||
- name: Build package
|
||||
run: pnpm -F post-encrypt build
|
||||
run: pnpm -F encrypt build
|
||||
|
||||
- name: Publish package to npm
|
||||
working-directory: packages/post-encrypt
|
||||
working-directory: packages/encrypt
|
||||
run: npm publish --access public --provenance
|
||||
@@ -17,7 +17,7 @@ jobs:
|
||||
|
||||
- uses: pnpm/action-setup@v6
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v6
|
||||
uses: actions/setup-node@v7
|
||||
with:
|
||||
node-version-file: .nvmrc
|
||||
cache: pnpm
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"display": {
|
||||
"access": ["public"],
|
||||
"alias": false
|
||||
},
|
||||
"sort": ["group"]
|
||||
}
|
||||
Vendored
+1
-9
@@ -1,13 +1,5 @@
|
||||
{
|
||||
"todo-tree.tree.scanMode": "workspace only",
|
||||
"explorer.fileNesting.enabled": true,
|
||||
"explorer.fileNesting.patterns": {
|
||||
"tsconfig.json": "tsconfig.*.json, env.d.ts",
|
||||
"package.json": "package-lock.json, pnpm*, .npmrc, .nvmrc, .eslint*, eslint*, .editorconfig, librarybot.yml",
|
||||
"README.md": "README.*.md, CODE_OF_CONDUCT.md, CONTRIBUTING.md, LICENSE, SECURITY.md, CHANGELOG.md",
|
||||
"hugo.toml": "theme.toml, .hugo_build.lock",
|
||||
"vercel.json": "build.sh"
|
||||
},
|
||||
"files.associations": {
|
||||
"*.template.ts": "gots",
|
||||
"*.template.js": "gojs",
|
||||
@@ -18,7 +10,7 @@
|
||||
"**/layouts/**/*.xml": "gohtml",
|
||||
"**/layouts/**/*.md": "gotemplate"
|
||||
},
|
||||
// Disable the default formatter, use eslint instead
|
||||
// Disable the default formatter, use ESLint instead
|
||||
"prettier.enable": false,
|
||||
"editor.formatOnSave": false,
|
||||
// Auto fix
|
||||
|
||||
@@ -106,6 +106,7 @@ Hugo Pipes processes all assets. The key orchestration is in `_partials/base/ass
|
||||
- CSS classes: BEM or semantic naming (`header-desktop`, `menu-item`)
|
||||
- SCSS variables: hyphen-separated, semantic (`$global-font-family`)
|
||||
- CSS custom properties: prefixed with `fi-` / `--fi-`
|
||||
- Use `fi-var()` function to reference CSS custom properties (auto-adds `--fi-` prefix), never use `var(--fi-...)` directly
|
||||
- Use CSS variables for theme switching; SCSS variables for colors (no hardcoded values)
|
||||
- Prefer relative units (rem, em, %) over absolute units
|
||||
- Keep selector nesting shallow
|
||||
|
||||
@@ -32,9 +32,8 @@
|
||||
[](https://gitee.com/lruihao/FixIt/stargazers)
|
||||
|
||||
> [!WARNING]
|
||||
> **Version v0.x only accepts bug fixes. For new feature submissions, please develop on the [v1](https://github.com/hugo-fixit/FixIt/tree/v1) branch.**
|
||||
>
|
||||
> Version 1.0 is planned for release in **the first half of 2027**. Welcome to join the development and discussion! See [v1 Milestone](https://github.com/hugo-fixit/FixIt/milestone/8) for details.
|
||||
> **The `main` branch contains the v1 code. For upgrade instructions, see the [v1 migration guide](https://fixit.lruihao.cn/guides/upgrade-to-v1/).**\
|
||||
> Version 1.0.0 is planned for release in **the first half of 2027**. Welcome to join the development and [discussion](https://github.com/orgs/hugo-fixit/discussions/723)!
|
||||
|
||||
## Live Preview
|
||||
|
||||
@@ -49,9 +48,9 @@
|
||||
|
||||
### Documentation
|
||||
|
||||
Head to the [Quick Start](https://fixit.lruihao.cn/documentation/getting-started/quick-start/) page for a step-by-step guide on how to create a new website with FixIt.
|
||||
Head to the [Quick Start](https://fixit.lruihao.cn/docs/getting-started/quick-start/) page for a step-by-step guide on how to create a new website with FixIt.
|
||||
|
||||
Outline: [Installation](https://fixit.lruihao.cn/documentation/installation/) ➜ [Getting Started](https://fixit.lruihao.cn/documentation/getting-started/) ➜ [Content Management](https://fixit.lruihao.cn/documentation/content-management/) ➜ [Advanced Usage](https://fixit.lruihao.cn/documentation/advanced/)
|
||||
Outline: [Installation](https://fixit.lruihao.cn/docs/installation/) ➜ [Getting Started](https://fixit.lruihao.cn/docs/getting-started/) ➜ [Content Management](https://fixit.lruihao.cn/docs/content-management/) ➜ [Advanced Usage](https://fixit.lruihao.cn/docs/advanced/)
|
||||
|
||||
### Starter Wizard
|
||||
|
||||
@@ -106,10 +105,11 @@ Click the following links to generate a new repository with template:
|
||||
|
||||
- **Responsive** layout
|
||||
- **Light/Dark** mode
|
||||
- **RTL (Right-to-Left)** layout supported
|
||||
- Globally consistent **design language**
|
||||
- **Pagination** supported
|
||||
- Easy-to-use and self-expanding **table of contents**
|
||||
- **Multilanguage** supported and i18n ready
|
||||
- **Multilanguage** supported and I18n ready
|
||||
- Beautiful **CSS animation**
|
||||
|
||||
### Social and Comment Systems
|
||||
@@ -168,9 +168,9 @@ Click the following links to generate a new repository with template:
|
||||
|
||||
</details>
|
||||
|
||||
## Multilingual and i18n
|
||||
## Multilingual and I18n
|
||||
|
||||
FixIt supports multilingual and i18n. For more information, see the [Content Management](https://fixit.lruihao.cn/documentation/content-management/introduction#multilingual) documentation. You are welcome to [contribute with a new language](https://github.com/hugo-fixit/FixIt/pulls).
|
||||
FixIt supports multilingual and i18n. For more information, see the [Content Management](https://fixit.lruihao.cn/docs/content-management/introduction#multilingual) documentation. You are welcome to [contribute with a new language](https://github.com/hugo-fixit/FixIt/pulls).
|
||||
|
||||
<details>
|
||||
<summary>FixIt supports the following languages.</summary>
|
||||
@@ -191,9 +191,21 @@ FixIt supports multilingual and i18n. For more information, see the [Content Man
|
||||
- Hindi
|
||||
- Japanese
|
||||
- Korean
|
||||
- Arabic
|
||||
- Persian
|
||||
- Urdu
|
||||
|
||||
</details>
|
||||
|
||||
## Browsers Support
|
||||
|
||||
The FixIt theme supports the last two versions of all major browsers.
|
||||
|
||||

|
||||

|
||||

|
||||

|
||||
|
||||
## Theme Components
|
||||
|
||||
To balance **simplicity** and **extensibility**, FixIt theme provides extra [Hugo theme components](https://fixit.lruihao.cn/ecosystem/#-components) for customization.
|
||||
@@ -252,6 +264,10 @@ All feedback is welcome! Head over to the [discussions][discussions], [Pull requ
|
||||
[qq-group]: https://qm.qq.com/q/Mn1I7ljqsS
|
||||
[dev-group]: https://qm.qq.com/q/ky3uru0mbu
|
||||
|
||||
## China Mirror
|
||||
|
||||
A mirror is available on Gitee for users in mainland China: [gitee.com/lruihao/FixIt](https://gitee.com/lruihao/FixIt)
|
||||
|
||||
## Contributing
|
||||
|
||||
We welcome you to join the development of FixIt. Please see [contributing document](/CONTRIBUTING.md). 🤗
|
||||
|
||||
+22
-6
@@ -32,9 +32,8 @@
|
||||
[](https://gitee.com/lruihao/FixIt/stargazers)
|
||||
|
||||
> [!WARNING]
|
||||
> **v0.x 版本仅接受 bug 修复,如需提交新功能,请基于 [v1](https://github.com/hugo-fixit/FixIt/tree/v1) 分支开发。**
|
||||
>
|
||||
> 1.0 版本计划在 **2027 年上半年** 发布,欢迎加入开发和讨论!详见 [v1 里程碑](https://github.com/hugo-fixit/FixIt/milestone/8)。
|
||||
> **当前 `main` 分支包含 v1 代码,升级详见 [v1 升级指南](https://fixit.lruihao.cn/guides/upgrade-to-v1/)**。\
|
||||
> 1.0 版本计划在 **2027 年上半年** 发布,欢迎加入开发和 [讨论](https://github.com/orgs/hugo-fixit/discussions/723)!
|
||||
|
||||
## 即时预览
|
||||
|
||||
@@ -49,9 +48,9 @@
|
||||
|
||||
### 主题文档
|
||||
|
||||
前往 [快速上手](https://fixit.lruihao.cn/zh-cn/documentation/getting-started/quick-start/) 页面,了解如何使用 FixIt 主题创建一个新网站的详细步骤。
|
||||
前往 [快速上手](https://fixit.lruihao.cn/zh-cn/docs/getting-started/quick-start/) 页面,了解如何使用 FixIt 主题创建一个新网站的详细步骤。
|
||||
|
||||
大纲:[安装篇](https://fixit.lruihao.cn/zh-cn/documentation/installation/) ➜ [入门篇](https://fixit.lruihao.cn/zh-cn/documentation/getting-started/) ➜ [内容管理](https://fixit.lruihao.cn/zh-cn/documentation/content-management/) ➜ [进阶篇](https://fixit.lruihao.cn/zh-cn/documentation/advanced/)
|
||||
大纲:[安装篇](https://fixit.lruihao.cn/zh-cn/docs/installation/) ➜ [入门篇](https://fixit.lruihao.cn/zh-cn/docs/getting-started/) ➜ [内容管理](https://fixit.lruihao.cn/zh-cn/docs/content-management/) ➜ [进阶篇](https://fixit.lruihao.cn/zh-cn/docs/advanced/)
|
||||
|
||||
### 快速开始
|
||||
|
||||
@@ -106,6 +105,7 @@ pnpx fixit-cli create my-blog
|
||||
|
||||
- **响应式** 布局
|
||||
- **浅色/深色** 主题模式
|
||||
- 支持 **RTL(从右到左)** 布局
|
||||
- 全局一致的 **设计语言**
|
||||
- 支持 **分页**
|
||||
- 易用和自动展开的 **文章目录**
|
||||
@@ -170,7 +170,7 @@ pnpx fixit-cli create my-blog
|
||||
|
||||
## 多语言和国际化
|
||||
|
||||
FixIt 主题多语言基本配置及自动翻译等详见 [内容管理](https://fixit.lruihao.cn/zh-cn/documentation/content-management/introduction#multilingual) 文档,欢迎 [贡献一种新的语言](https://github.com/hugo-fixit/FixIt/pulls)。
|
||||
FixIt 主题多语言基本配置及自动翻译等详见 [内容管理](https://fixit.lruihao.cn/zh-cn/docs/content-management/introduction#multilingual) 文档,欢迎 [贡献一种新的语言](https://github.com/hugo-fixit/FixIt/pulls)。
|
||||
|
||||
<details>
|
||||
<summary>FixIt 支持下列语言。</summary>
|
||||
@@ -191,9 +191,21 @@ FixIt 主题多语言基本配置及自动翻译等详见 [内容管理](https:/
|
||||
- 印地语
|
||||
- 日语
|
||||
- 韩语
|
||||
- 阿拉伯语
|
||||
- 波斯语
|
||||
- 乌尔都语
|
||||
|
||||
</details>
|
||||
|
||||
## 浏览器支持
|
||||
|
||||
FixIt 主题支持所有主流浏览器的最近两个版本。
|
||||
|
||||

|
||||

|
||||

|
||||

|
||||
|
||||
## 主题组件
|
||||
|
||||
为了平衡 FixIt 主题的 **简洁性** 和 **可扩展性**,我们额外开发了一系列 [Hugo 主题组件](https://fixit.lruihao.cn/zh-cn/ecosystem/#-组件) 供用户选择。
|
||||
@@ -252,6 +264,10 @@ FixIt 主题多语言基本配置及自动翻译等详见 [内容管理](https:/
|
||||
[qq-group]: https://qm.qq.com/q/Mn1I7ljqsS
|
||||
[dev-group]: https://qm.qq.com/q/ky3uru0mbu
|
||||
|
||||
## 中国区镜像
|
||||
|
||||
为方便中国大陆用户访问,FixIt 在 Gitee 上提供了镜像仓库:[gitee.com/lruihao/FixIt](https://gitee.com/lruihao/FixIt)
|
||||
|
||||
## 参与贡献
|
||||
|
||||
我们欢迎你加入 FixIt 的开发,贡献出你的一份力量。请看开源 [贡献指南](/CONTRIBUTING.md)。 🤗
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
# Apps
|
||||
|
||||
This directory contains the Hugo sites used for development, testing, and demonstration of the [FixIt](https://fixit.lruihao.cn) theme.
|
||||
|
||||
## Overview
|
||||
|
||||
| App | Description | Live |
|
||||
| -------------- | ----------------------------------------------------- | ----------------------------------------------------------------- |
|
||||
| [demo](./demo) | Demo site showcasing FixIt theme features | [demo.fixit.lruihao.cn](https://demo.fixit.lruihao.cn) |
|
||||
| [test](./test) | Test site for exercising and verifying theme features | [demo.fixit.lruihao.cn/test](https://demo.fixit.lruihao.cn/test/) |
|
||||
|
||||
> [!NOTE]
|
||||
> The documentation site (`fixit-docs`) lives in a separate repository: [hugo-fixit/fixit-docs](https://github.com/hugo-fixit/fixit-docs).
|
||||
|
||||
## Development
|
||||
|
||||
From the repository root:
|
||||
|
||||
```bash
|
||||
pnpm dev:demo # Start demo site dev server
|
||||
pnpm dev:test # Start test site dev server
|
||||
pnpm build:demo # Build demo site
|
||||
pnpm build:test # Build test site
|
||||
pnpm build # Build all sites (merged into public/)
|
||||
```
|
||||
+1
-1
@@ -22,7 +22,7 @@ _merge = "shallow"
|
||||
|
||||
# -------------------------------------------------------------------------------------
|
||||
# Theme Core Configuration
|
||||
# See: https://fixit.lruihao.cn/documentation/basics/#theme-configuration
|
||||
# See: https://fixit.lruihao.cn/docs/getting-started/configuration/params/
|
||||
# -------------------------------------------------------------------------------------
|
||||
|
||||
[params]
|
||||
|
||||
@@ -26,6 +26,17 @@ $$ \ce{CO2 + C -> 2 CO} $$
|
||||
|
||||
$$ \ce{Hg^2+ ->[I-] HgI2 ->[I-] [Hg^{II}I4]^2-} $$
|
||||
|
||||
## JSON
|
||||
|
||||
```json
|
||||
{
|
||||
"name": "fixit",
|
||||
"version": "1.0.0",
|
||||
"description": "A Hugo theme for creating encrypted content.",
|
||||
"author": "Lruihao"
|
||||
}
|
||||
```
|
||||
|
||||
## style
|
||||
|
||||
{{< style "text-align:right; strong{color:#00b1ff;}" >}}
|
||||
|
||||
@@ -0,0 +1,189 @@
|
||||
---
|
||||
title: اختبار دعم اللغة العربية والكتابة من اليمين إلى اليسار
|
||||
date: 2026-08-23T10:00:00+08:00
|
||||
collections:
|
||||
- اختبارات
|
||||
categories:
|
||||
- ميزات
|
||||
tags:
|
||||
- RTL
|
||||
- عربي
|
||||
- اختبار
|
||||
---
|
||||
|
||||
هذا مقال اختباري شامل لدعم اللغة العربية والكتابة من اليمين إلى اليسار في سمة [FixIt](https://github.com/hugo-fixit/FixIt/). يهدف هذا المقال إلى اختبار جميع عناصر الويب المختلفة مع المحتوى العربي.
|
||||
|
||||
<!--more-->
|
||||
|
||||
## المقدمة
|
||||
|
||||
مرحباً بكم في هذا المقال الاختباري الشامل. اللغة العربية هي واحدة من أكثر اللغات انتشاراً في العالم، وتتميز بكتابتها من اليمين إلى اليسار. يتطلب دعم هذه اللغة في تصميمات الويب اهتماماً خاصاً بالعديد من الجوانب التقنية.
|
||||
|
||||
### لماذا RTL مهم؟
|
||||
|
||||
عندما نصمم مواقع ويب تدعم اللغات التي تُكتب من اليمين إلى اليسار، يجب علينا مراعاة عدة عوامل:
|
||||
|
||||
- **اتجاه النص**: يجب أن يتدفق النص بشكل طبيعي من اليمين إلى اليسار
|
||||
- **تخطيط الصفحة**: يجب أن تكون العناصر مرتبة بشكل منطقي
|
||||
- **الرموز والأيقونات**: قد تحتاج بعضها إلى انعكاس
|
||||
- **التباعد بين الأسطر**: يحتاج النص العربي إلى مساحة أكبر للتشكيل
|
||||
|
||||
## النص المختلط
|
||||
|
||||
هذا نص يحتوي على كلمات إنجليزية مثل JavaScript و HTML و CSS مع نص عربي. يجب أن يظهر كل شيء بشكل صحيح.
|
||||
|
||||
## الروابط
|
||||
|
||||
- [موقع Hugo](https://gohugo.io)
|
||||
- [سمة FixIt](https://fixit.lruihao.cn)
|
||||
- [وثائق RTL](https://rtlstyling.com)
|
||||
|
||||
## القوائم
|
||||
|
||||
القوائم غير المرتبة:
|
||||
|
||||
- عنصر القائمة الأول
|
||||
- عنصر القائمة الثاني
|
||||
- عنصر القائمة الثالث
|
||||
- عنصر فرعي
|
||||
- عنصر فرعي آخر
|
||||
|
||||
القوائم المرتبة:
|
||||
|
||||
1. الخطوة الأولى في العملية
|
||||
2. الخطوة الثانية
|
||||
3. الخطوة الثالثة والأخيرة
|
||||
|
||||
قوائم المهام:
|
||||
|
||||
- [x] إعداد بيئة التطوير
|
||||
- [x] كتابة المحتوى الأساسي
|
||||
- [ ] اختبار التوافق مع المتصفحات
|
||||
- [ ] مراجعة التصميم النهائي
|
||||
- [ ] نشر الموقع
|
||||
|
||||
## الجداول
|
||||
|
||||
| اللغة | الاتجاه | الحروف المتصلة |
|
||||
| ---------: | ------: | -------------: |
|
||||
| العربية | RTL | نعم |
|
||||
| الفارسية | RTL | نعم |
|
||||
| العبرية | RTL | لا |
|
||||
| الإنجليزية | LTR | لا |
|
||||
|
||||
## الاقتباسات
|
||||
|
||||
> العلم نور والجهل ظلام
|
||||
> — مثل عربي
|
||||
|
||||
اقتباس طويل:
|
||||
|
||||
> إن الله لا يغير ما بقوم حتى يغيروا ما بأنفسهم وإذا أراد الله بقوم سوءاً فلا مرد له وما لهم من دونه من وال
|
||||
|
||||
## الأيقونات
|
||||
|
||||
أيقونات يتم قلبها في RTL:
|
||||
|
||||
- :(fa-solid fa-tag): وسم
|
||||
- :(fa-solid fa-tags): وسوم
|
||||
- :(fa-solid fa-folder): مجلد
|
||||
- :(fa-solid fa-folder-open): مجلد مفتوح
|
||||
- :(fa-solid fa-pen-to-square): قلم تعديل
|
||||
|
||||
أيقونات لا يتم قلبها في RTL:
|
||||
|
||||
- :(fa-solid fa-share): مشاركة
|
||||
- :(fa-solid fa-search): بحث
|
||||
|
||||
## التبويبات
|
||||
|
||||
{{< tabs >}}
|
||||
{{% tab title="العلامة الأولى" %}}
|
||||
محتوى التبويب الأول.
|
||||
{{% /tab %}}
|
||||
{{% tab title="العلامة الثانية" %}}
|
||||
محتوى التبويب الثاني.
|
||||
{{% /tab %}}
|
||||
{{< /tabs >}}
|
||||
|
||||
## التفاصيل القابلة للطي
|
||||
|
||||
<details>
|
||||
<summary>انقر لعرض التفاصيل</summary>
|
||||
|
||||
هذا محتوى مخفي يمكنBOOLE توسيعه بالنقر.
|
||||
|
||||
المحتوى يمكن أن يكون نصاً أو كوداً أو أي عنصر آخر.
|
||||
|
||||
</details>
|
||||
|
||||
## التنبيهات
|
||||
|
||||
{{< admonition >}}
|
||||
هذا تنبيه عادي يحتوي على معلومات مفيدة للمستخدم.
|
||||
{{< /admonition >}}
|
||||
|
||||
> [!IMPORTANT]
|
||||
> تأكد من اختبار جميع التغييرات قبل النشر.
|
||||
|
||||
## المحتوى المشفر
|
||||
|
||||
{{< fixit-encryptor password="1212" message="أدخل كلمة المرور (1212) لعرض المحتوى" >}}
|
||||
هذا محتوى مشفر. يجب إدخال كلمة المرور لعرضه.
|
||||
{{< /fixit-encryptor >}}
|
||||
|
||||
## أمثلة على المحتوى LTR
|
||||
|
||||
في صفحة RTL، بعض العناصر تبقى باتجاه LTR لأن محتاها لغوي بحت أو برمجي. فيما يلي أمثلة:
|
||||
|
||||
### كتلة الكود
|
||||
|
||||
```c
|
||||
#include <stdio.h>
|
||||
|
||||
int main() {
|
||||
printf("Hello, World!\n");
|
||||
return 0;
|
||||
}
|
||||
```
|
||||
|
||||
### عارض JSON
|
||||
|
||||
```json
|
||||
{
|
||||
"name": "FixIt",
|
||||
"version": "1.0.0",
|
||||
"features": ["RTL", "i18n", "shortcodes"]
|
||||
}
|
||||
```
|
||||
|
||||
### تبويبات الكود
|
||||
|
||||
```python {group=tab1}
|
||||
print("Python")
|
||||
```
|
||||
|
||||
```go {group=tab1}
|
||||
fmt.Println("Go")
|
||||
```
|
||||
|
||||
### الصيغ الرياضية
|
||||
|
||||
معادلة بسيطة: $E = mc^2$
|
||||
|
||||
معادلة معقدة:
|
||||
|
||||
$$\sum_{i=1}^{n} i = \frac{n(n+1)}{2}$$
|
||||
|
||||
### مخططات Mermaid
|
||||
|
||||
```mermaid
|
||||
graph LR
|
||||
A[بداية] --> B[عملية]
|
||||
B --> C[نهاية]
|
||||
```
|
||||
|
||||
---
|
||||
{.awesome-hr}
|
||||
|
||||
_تم إنشاء هذا المقال لاختبار دعم اللغة العربية في سمة FixIt._
|
||||
@@ -0,0 +1,187 @@
|
||||
---
|
||||
title: Arabic RTL Test
|
||||
date: 2026-08-23T10:00:00+08:00
|
||||
collections:
|
||||
- Tests
|
||||
categories:
|
||||
- Features
|
||||
tags:
|
||||
- RTL
|
||||
- Arabic
|
||||
---
|
||||
|
||||
A comprehensive test article for Arabic language and Right-to-Left layout support in the [FixIt](https://github.com/hugo-fixit/FixIt/) theme. This article tests various web elements with Arabic content.
|
||||
|
||||
<!--more-->
|
||||
|
||||
## Introduction
|
||||
|
||||
Welcome to this comprehensive test article. Arabic is one of the most widely spoken languages in the world, characterized by its Right-to-Left writing direction. Supporting this language in web designs requires special attention to many technical aspects.
|
||||
|
||||
### Why RTL Matters
|
||||
|
||||
When designing websites that support Right-to-Left languages, we need to consider several factors:
|
||||
|
||||
- **Text direction**: Text should flow naturally from right to left
|
||||
- **Page layout**: Elements should be arranged in a logical order
|
||||
- **Symbols and icons**: Some may need to be mirrored
|
||||
- **Line spacing**: Arabic text needs more space for diacritics
|
||||
|
||||
## Mixed Text
|
||||
|
||||
This text contains English words like JavaScript, HTML, and CSS alongside Arabic text. Everything should display correctly.
|
||||
|
||||
## Links
|
||||
|
||||
- [Hugo Website](https://gohugo.io)
|
||||
- [FixIt Theme](https://fixit.lruihao.cn)
|
||||
- [RTL Styling](https://rtlstyling.com)
|
||||
|
||||
## Lists
|
||||
|
||||
Unordered lists:
|
||||
|
||||
- First list item
|
||||
- Second list item
|
||||
- Third list item
|
||||
- Sub item
|
||||
- Another sub item
|
||||
|
||||
Ordered lists:
|
||||
|
||||
1. First step in the process
|
||||
2. Second step
|
||||
3. Third and final step
|
||||
|
||||
Task lists:
|
||||
|
||||
- [x] Set up development environment
|
||||
- [x] Write basic content
|
||||
- [ ] Test browser compatibility
|
||||
- [ ] Review final design
|
||||
- [ ] Deploy website
|
||||
|
||||
## Tables
|
||||
|
||||
| Language | Direction | Connected Letters |
|
||||
| ---------- | --------- | ----------------- |
|
||||
| Arabic | RTL | Yes |
|
||||
| Persian | RTL | Yes |
|
||||
| Hebrew | RTL | No |
|
||||
| English | LTR | No |
|
||||
|
||||
## Blockquotes
|
||||
|
||||
> Knowledge is light and ignorance is darkness
|
||||
> — Arabic proverb
|
||||
|
||||
Long quote:
|
||||
|
||||
> Indeed, Allah does not change the condition of a people until they change what is in themselves
|
||||
|
||||
## Icons
|
||||
|
||||
Icons that are flipped in RTL:
|
||||
|
||||
- :(fa-solid fa-tag): Tag
|
||||
- :(fa-solid fa-tags): Tags
|
||||
- :(fa-solid fa-folder): Folder
|
||||
- :(fa-solid fa-folder-open): Folder Open
|
||||
- :(fa-solid fa-pen-to-square): Edit
|
||||
|
||||
Icons that are NOT flipped in RTL:
|
||||
|
||||
- :(fa-solid fa-share): Share
|
||||
- :(fa-solid fa-search): Search
|
||||
|
||||
## Tabs
|
||||
|
||||
{{< tabs >}}
|
||||
{{% tab title="First Tab" %}}
|
||||
Content of the first tab.
|
||||
{{% /tab %}}
|
||||
{{% tab title="Second Tab" %}}
|
||||
Content of the second tab.
|
||||
{{% /tab %}}
|
||||
{{< /tabs >}}
|
||||
|
||||
## Collapsible Details
|
||||
|
||||
<details>
|
||||
<summary>Click to show details</summary>
|
||||
|
||||
This is hidden content that can be expanded by clicking.
|
||||
|
||||
Content can be text, code, or any other element.
|
||||
|
||||
</details>
|
||||
|
||||
## Alerts
|
||||
|
||||
{{< admonition >}}
|
||||
This is a regular admonition with useful information for the user.
|
||||
{{< /admonition >}}
|
||||
|
||||
> [!IMPORTANT]
|
||||
> Make sure to test all changes before deploying.
|
||||
|
||||
## Encrypted Content
|
||||
|
||||
{{< fixit-encryptor password="1212" message="Enter password (1212) to view content" >}}
|
||||
This is encrypted content. You must enter the password to view it.
|
||||
{{< /fixit-encryptor >}}
|
||||
|
||||
## LTR Content Examples
|
||||
|
||||
In an RTL page, some elements remain in LTR direction because their content is purely linguistic or programmatic. Here are examples:
|
||||
|
||||
### Code Block
|
||||
|
||||
```c
|
||||
#include <stdio.h>
|
||||
|
||||
int main() {
|
||||
printf("Hello, World!\n");
|
||||
return 0;
|
||||
}
|
||||
```
|
||||
|
||||
### JSON Viewer
|
||||
|
||||
```json
|
||||
{
|
||||
"name": "FixIt",
|
||||
"version": "1.0.0",
|
||||
"features": ["RTL", "i18n", "shortcodes"]
|
||||
}
|
||||
```
|
||||
|
||||
### Code Tabs
|
||||
|
||||
```python {group=tab1}
|
||||
print("Python")
|
||||
```
|
||||
|
||||
```go {group=tab1}
|
||||
fmt.Println("Go")
|
||||
```
|
||||
|
||||
### Math
|
||||
|
||||
Simple equation: $E = mc^2$
|
||||
|
||||
Complex equation:
|
||||
|
||||
$$\sum_{i=1}^{n} i = \frac{n(n+1)}{2}$$
|
||||
|
||||
### Mermaid Diagram
|
||||
|
||||
```mermaid
|
||||
graph LR
|
||||
A[Start] --> B[Process]
|
||||
B --> C[End]
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
_Created to test Arabic language support in the FixIt theme._
|
||||
@@ -13,7 +13,18 @@ tags:
|
||||
Testing various configurations and nested scenarios for the Tabs shortcode.
|
||||
|
||||
{{< tabs >}}
|
||||
{{% tab title="Test Cases" %}}See Types section for various test cases.{{% /tab %}}
|
||||
{{% tab title="Test Cases" %}}
|
||||
See Types section for various test cases.
|
||||
|
||||
```go-template {name="Shortcode"}
|
||||
{{</* tabs */>}}
|
||||
{{%/* tab title="Test Cases" */%}}
|
||||
{{%/* tab title="Test Cases" */%}}See Types section for various test cases.{{%/* /tab */%}}
|
||||
{{%/* tab title="Nested Tabs" */%}}See Nested Tabs section for examples.{{%/* /tab */%}}
|
||||
{{</* /tabs */>}}
|
||||
```
|
||||
|
||||
{{% /tab %}}
|
||||
{{% tab title="Nested Tabs" %}}See Nested Tabs section for examples.{{% /tab %}}
|
||||
{{< /tabs >}}
|
||||
|
||||
@@ -23,7 +34,7 @@ Testing various configurations and nested scenarios for the Tabs shortcode.
|
||||
|
||||
### Underline (Default)
|
||||
|
||||
<!-- placement: top, bottom, left, right -->
|
||||
<!-- placement: top, bottom, start, end -->
|
||||
|
||||
{{< tabs placement="top" >}}
|
||||
{{% tab title="Tab 1" %}}Content for underline style tab 1{{% /tab %}}
|
||||
@@ -33,7 +44,7 @@ Testing various configurations and nested scenarios for the Tabs shortcode.
|
||||
|
||||
### Pill
|
||||
|
||||
<!-- placement: top, bottom, left, right -->
|
||||
<!-- placement: top, bottom, start, end -->
|
||||
|
||||
{{< tabs type="pill" placement="top" >}}
|
||||
{{% tab title="Tab 1" %}}Content for pill style tab 1{{% /tab %}}
|
||||
@@ -43,7 +54,7 @@ Testing various configurations and nested scenarios for the Tabs shortcode.
|
||||
|
||||
### Card
|
||||
|
||||
<!-- placement: top, bottom, left, right -->
|
||||
<!-- placement: top, bottom, start, end -->
|
||||
|
||||
{{< tabs type="card" placement="top" >}}
|
||||
{{% tab title="Tab 1" %}}Content for card style tab 1{{% /tab %}}
|
||||
@@ -115,7 +126,7 @@ Nested tab C with a table:
|
||||
{{% tab title="Outer Tab 3" %}}
|
||||
This tab contains vertical nested tabs:
|
||||
|
||||
{{< tabs type="card" placement="left" >}}
|
||||
{{< tabs type="card" placement="end" >}}
|
||||
|
||||
{{% tab title="Vertical A" %}}
|
||||
Content for vertical nested tab A.
|
||||
|
||||
+17
-8
@@ -5,19 +5,28 @@
|
||||
|
||||
title = "FixIt TEST"
|
||||
baseURL = "https://demo.fixit.lruihao.cn/test/"
|
||||
ignoreLogs = [ "warning-file-tree" ]
|
||||
ignoreLogs = [
|
||||
"warning-file-tree",
|
||||
"warning-encryption"
|
||||
]
|
||||
defaultContentLanguage = "en"
|
||||
|
||||
# test for multilingual support
|
||||
# [languages]
|
||||
[languages]
|
||||
|
||||
# [languages.en]
|
||||
# languageCode = "en"
|
||||
# languageName = "English"
|
||||
[languages.en]
|
||||
locale = "en"
|
||||
label = "English"
|
||||
|
||||
# [todo] 优化中文排版
|
||||
# [languages.zh-cn]
|
||||
# languageCode = "zh-CN"
|
||||
# languageName = "简体中文"
|
||||
# locale = "zh-CN"
|
||||
# label = "简体中文"
|
||||
|
||||
[languages.ar]
|
||||
direction = "rtl"
|
||||
locale = "ar"
|
||||
label = "العربية"
|
||||
|
||||
[permalinks]
|
||||
|
||||
@@ -38,7 +47,7 @@ _merge = "shallow"
|
||||
|
||||
# -------------------------------------------------------------------------------------
|
||||
# Theme Core Configuration
|
||||
# See: https://fixit.lruihao.cn/documentation/basics/#theme-configuration
|
||||
# See: https://fixit.lruihao.cn/docs/getting-started/configuration/params/
|
||||
# -------------------------------------------------------------------------------------
|
||||
|
||||
[params]
|
||||
|
||||
@@ -25,7 +25,7 @@ repost:
|
||||
enable: false
|
||||
url:
|
||||
|
||||
# See: https://fixit.lruihao.cn/documentation/content-management/introduction/#front-matter
|
||||
# See: https://fixit.lruihao.cn/docs/content-management/front-matter/
|
||||
---
|
||||
|
||||
<!--more-->
|
||||
|
||||
+1
-1
@@ -22,7 +22,7 @@ repost:
|
||||
enable: false
|
||||
url:
|
||||
|
||||
# See: https://fixit.lruihao.cn/documentation/content-management/introduction/#front-matter
|
||||
# See: https://fixit.lruihao.cn/docs/content-management/front-matter/
|
||||
---
|
||||
|
||||
<!--more-->
|
||||
|
||||
+13
-12
@@ -58,11 +58,11 @@
|
||||
.whitespace-nowrap{white-space:nowrap;}
|
||||
.b,
|
||||
.border{border-width:1px;}
|
||||
.bg-danger{background-color:var(--fi-danger) /* var(--fi-danger) */;}
|
||||
.bg-info{background-color:var(--fi-info) /* var(--fi-info) */;}
|
||||
.bg-primary{background-color:var(--fi-primary) /* var(--fi-primary) */;}
|
||||
.bg-success{background-color:var(--fi-success) /* var(--fi-success) */;}
|
||||
.bg-warning{background-color:var(--fi-warning) /* var(--fi-warning) */;}
|
||||
.bg-danger{background-color:var(--fi-danger);}
|
||||
.bg-info{background-color:var(--fi-info);}
|
||||
.bg-primary{background-color:var(--fi-primary);}
|
||||
.bg-success{background-color:var(--fi-success);}
|
||||
.bg-warning{background-color:var(--fi-warning);}
|
||||
.px{padding-left:1rem;padding-right:1rem;}
|
||||
.pl{padding-left:1rem;}
|
||||
.pt{padding-top:1rem;}
|
||||
@@ -73,13 +73,13 @@
|
||||
.text-5xl{font-size:3rem;line-height:1;}
|
||||
.text-base{font-size:1rem;line-height:1.5rem;}
|
||||
.text-lg{font-size:1.125rem;line-height:1.75rem;}
|
||||
.text-danger{color:var(--fi-danger) /* var(--fi-danger) */;}
|
||||
.text-info{color:var(--fi-info) /* var(--fi-info) */;}
|
||||
.text-primary{color:var(--fi-primary) /* var(--fi-primary) */;}
|
||||
.text-secondary{color:var(--fi-secondary) /* var(--fi-secondary) */;}
|
||||
.text-success{color:var(--fi-success) /* var(--fi-success) */;}
|
||||
.text-warning{color:var(--fi-warning) /* var(--fi-warning) */;}
|
||||
.text-white{--un-text-opacity:1;color:rgb(255 255 255 / var(--un-text-opacity)) /* #fff */;}
|
||||
.text-danger{color:var(--fi-danger);}
|
||||
.text-info{color:var(--fi-info);}
|
||||
.text-primary{color:var(--fi-primary);}
|
||||
.text-secondary{color:var(--fi-secondary);}
|
||||
.text-success{color:var(--fi-success);}
|
||||
.text-warning{color:var(--fi-warning);}
|
||||
.text-white{--un-text-opacity:1;color:rgb(255 255 255 / var(--un-text-opacity));}
|
||||
.font-bold{font-weight:700;}
|
||||
.capitalize{text-transform:capitalize;}
|
||||
.lining-nums{--un-numeric-figure:lining-nums;font-variant-numeric:var(--un-ordinal) var(--un-slashed-zero) var(--un-numeric-figure) var(--un-numeric-spacing) var(--un-numeric-fraction);}
|
||||
@@ -91,6 +91,7 @@
|
||||
.opacity-50{opacity:0.5;}
|
||||
.shadow{--un-shadow:var(--un-shadow-inset) 0 1px 3px 0 var(--un-shadow-color, rgb(0 0 0 / 0.1)),var(--un-shadow-inset) 0 1px 2px -1px var(--un-shadow-color, rgb(0 0 0 / 0.1));box-shadow:var(--un-ring-offset-shadow), var(--un-ring-shadow), var(--un-shadow);}
|
||||
.blur{--un-blur:blur(8px);filter:var(--un-blur) var(--un-brightness) var(--un-contrast) var(--un-drop-shadow) var(--un-grayscale) var(--un-hue-rotate) var(--un-invert) var(--un-saturate) var(--un-sepia);}
|
||||
.ease-out{transition-timing-function:cubic-bezier(0, 0, 0.2, 1);}
|
||||
@media print{
|
||||
.print\:hidden{display:none;}
|
||||
}
|
||||
|
||||
@@ -34,8 +34,8 @@ libFiles:
|
||||
gitalkJS: gitalk@1.8.0/dist/gitalk.min.js
|
||||
# instant.page@5.2.0 https://github.com/instantpage/instant.page
|
||||
instantPage: instant.page@5.2.0/instantpage.min.js
|
||||
# json-viewer-element@1.0.4 https://github.com/Lruihao/json-viewer-element
|
||||
jsonViewerElementJS: json-viewer-element@1.0.4/dist/json-viewer-element.umd.js
|
||||
# json-viewer-element@1.0.6 https://github.com/Lruihao/json-viewer-element
|
||||
jsonViewerElementJS: json-viewer-element@1.0.6/dist/json-viewer-element.umd.js
|
||||
# katex@0.17.0 https://github.com/KaTeX/KaTeX
|
||||
katexCopyTexJS: katex@0.17.0/dist/contrib/copy-tex.min.js
|
||||
katexCSS: katex@0.17.0/dist/katex.min.css
|
||||
|
||||
@@ -34,8 +34,8 @@ libFiles:
|
||||
gitalkJS: gitalk@1.8.0/dist/gitalk.min.js
|
||||
# instant.page@5.2.0 https://github.com/instantpage/instant.page
|
||||
instantPage: instant.page@5.2.0/instantpage.js
|
||||
# json-viewer-element@1.0.4 https://github.com/Lruihao/json-viewer-element
|
||||
jsonViewerElementJS: json-viewer-element@1.0.4/dist/json-viewer-element.umd.js
|
||||
# json-viewer-element@1.0.6 https://github.com/Lruihao/json-viewer-element
|
||||
jsonViewerElementJS: json-viewer-element@1.0.6/dist/json-viewer-element.umd.js
|
||||
# katex@0.17.0 https://github.com/KaTeX/KaTeX
|
||||
katexCopyTexJS: katex@0.17.0/dist/contrib/copy-tex.min.js
|
||||
katexCSS: katex@0.17.0/dist/katex.min.css
|
||||
|
||||
@@ -539,7 +539,7 @@ qq:
|
||||
Template: https://wpa.qq.com/msgrd?v=3&uin=%v&site=qq&menu=yes
|
||||
Title: QQ
|
||||
Icon:
|
||||
Simpleicons: tencentqq
|
||||
Simpleicons: qq
|
||||
|
||||
# 069: QQ Group
|
||||
qqgroup:
|
||||
@@ -547,7 +547,7 @@ qqgroup:
|
||||
Prefix: https://qm.qq.com/q/
|
||||
Title: QQ Group
|
||||
Icon:
|
||||
Simpleicons: tencentqq
|
||||
Simpleicons: qq
|
||||
|
||||
# 070: Diaspora
|
||||
diaspora:
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
<svg height="200" viewBox="0 0 1024 1024" width="200" xmlns="http://www.w3.org/2000/svg"><path d="m165.092285 116.164732c-25.344127 4.565483-52.891625 20.150029-71.782258 40.613251-7.39749 8.184825-12.434949 15.741121-18.575209 28.491493-14.638385 29.753014-13.537836 3.462746-13.065796 326.63738l.471998 284.136138 3.621509 11.804231c14.797126 47.856269 52.734987 81.226502 100.432514 88.15414 15.269124 2.203371 164.184377 2.046753 172.841242-.158763 17.472495-4.406741 29.278872-24.556749 24.715513-41.872626-2.205516-8.341486-10.390385-19.04725-17.47464-23.297374-5.352882-2.990749-8.500248-3.147366-88.939372-3.934744l-83.429872-.787378-10.547002-5.194119c-11.962994-5.82488-20.150051-14.325128-25.974888-26.603503l-3.775981-8.184868-.158784-275.949125c-.156596-151.747239.31538-278.467876 1.102779-281.46077 4.565505-20.93524 23.138612-38.407734 45.178756-42.50122 4.87876-.944016 108.302002-1.574777 267.605493-1.574777 259.105246 0 259.577244 0 269.33897 3.306129 11.963037 4.093485 22.351276 11.490997 29.120108 20.937386 11.019043 15.269123 10.390428 7.869488 11.334423 140.412859l.787378 119.636382 4.247978 6.453495c12.909136 19.521393 37.15265 23.456094 53.522386 8.659075 12.909136-11.649867 12.278375-3.304048 12.278375-137.737555 0-131.129524 0-131.601522-9.759624-154.898896-16.056501-38.881834-55.410376-69.104787-98.855613-76.030279-6.768875-.943953-101.533127-1.574713-276.264505-1.41595-218.335377.156617-268.236254.471997-277.995878 2.359988z"/><path d="m250.725528 303.805245c-13.694367 5.663972-22.353378 18.257727-22.509996 32.739472 0 11.021145 6.453495 23.29952 15.584504 29.753014l6.768875 4.722122h317.034309l6.612386-4.406741c10.231557-6.768875 15.110296-15.269124 15.741057-27.862879.471998-8.343631 0-11.806377-2.675368-16.841733-4.09355-8.030396-14.009748-16.687262-22.037999-19.049396-4.408887-1.415993-46.753511-1.88799-156.943504-1.731373-138.527014 0-151.434004.31538-157.574264 2.675369z"/><path d="m246.947402 467.515521c-23.769372 12.437095-25.028747 47.066703-2.359988 60.919791l7.71287 4.7221 104.680493.472126 104.526021.31538 8.341486-3.776002c23.140756-10.54919 27.706218-42.188135 8.500248-58.244551-10.705765-9.130944-7.08417-8.817774-119.479764-8.817774h-103.577735z"/><path d="m634.191337 520.565867c-10.705743 3.46281-22.040058 11.649759-27.547412 19.832482-2.677428 3.936932-9.289772 17.79002-14.640509 30.69701-5.509499 12.909135-20.778623 48.956881-34.003138 80.123743-13.22237 31.169093-29.435489 69.420039-35.88907 85.004628-33.844289 81.069884-45.178583 107.830005-49.113413 116.489016-8.028251 17.944492-6.927638 32.739472 3.462703 44.704612 7.240916 8.500248 16.056545 12.593755 26.916781 12.750372 10.390384 0 18.262018-3.303983 25.816125-10.547001 5.82488-5.666117 10.075004-14.797126 30.225012-63.438628l5.350737-13.378987 83.273255-.471998 83.114492-.31538 12.437138 30.222867c15.584503 38.0945 20.150008 46.753511 26.76012 51.319015 24.715512 16.841734 58.08789-.158763 58.08789-29.594251 0-7.712871-3.306129-16.528499-30.381629-81.069885-16.687262-39.825873-36.206509-86.577238-43.445237-103.893115-17.173505-41.437466-34.437226-82.837515-51.791013-124.19974-7.556253-17.79002-10.705764-23.29952-16.843879-29.596397-13.53775-13.853131-35.103751-19.991246-51.790927-14.638363zm27.388821 131.755993c6.453495 15.897739 18.259937 44.389232 26.131505 63.28201 7.871634 19.04725 14.166366 34.788371 13.853217 35.101606-.315402.31538-24.715598.31538-53.99447.158763l-53.520305-.471998 18.575317-43.290765c10.072794-23.769371 22.194616-52.262946 26.76012-63.279864 4.565505-11.018999 8.815629-20.150008 9.287626-20.150008.471912 0 6.296921 12.909178 12.90699 28.650256z"/><path d="m915.806665 519.937252c-7.712871 2.201225-18.418635 11.647614-21.881382 19.360549-2.831985 6.13805-2.990748 14.638363-2.990748 174.887931 0 159.621017.158763 168.74988 2.990748 174.731378 3.462747 7.871633 13.537751 17.159259 21.409384 19.67801 15.112506 5.035357 33.687758-2.205516 42.501241-16.371881l4.724267-7.712871v-169.380641c0-162.29424-.158762-169.535113-2.992894-174.887995-8.813483-16.685203-26.916737-25.028748-43.760616-20.306626z"/></svg>
|
||||
|
After Width: | Height: | Size: 4.0 KiB |
@@ -0,0 +1 @@
|
||||
<svg height="200" viewBox="0 0 1024 1024" width="200" xmlns="http://www.w3.org/2000/svg"><path d="m148.657996 137.18126c-21.954134 5.753981-36.643845 13.627683-51.784116 27.708051-16.504717 15.597267-26.498097 32.556836-32.252122 54.965778-3.029315 11.810553-3.181662 15.747404-3.181662 289.818747 0 273.916872.152347 278.003903 3.181662 289.816601 10.448189 40.730854 38.45883 69.803481 80.25169 82.978523l11.355724 3.482021h261.956139l6.208854-3.331842c21.349104-11.508048 26.498183-36.493644 11.053219-54.056076-11.508049-13.022717-2.272046-12.265383-144.453471-12.567887l-125.678897-.45483-9.843178-4.844365c-10.900894-5.603844-19.531931-15.29472-22.561268-25.742929-1.514669-4.694186-1.817173-69.803481-1.664827-276.49138.150158-267.557839.150158-270.434851 3.179474-277.248714 4.089198-9.238191 12.567909-17.869227 21.803955-22.258763l7.573386-3.634346h260.439282c249.995406 0 260.746121.150179 266.650325 2.874867 14.38506 6.513504 22.561267 17.716902 25.135775 34.828795 3.331927 21.196779 13.930231 32.704827 32.101855 34.522001 21.804041 2.274148 40.730962-18.622271 38.156454-42.093198-5.449374-47.999547-39.218331-86.160185-87.065553-98.423422-10.903039-2.724687-20.746154-2.87491-275.583865-2.724687-251.96276 0-264.831006.150179-274.978835 2.874866z"/><path d="m237.99553 314.493985c-12.567887 3.632201-21.954112 17.564578-21.954112 32.402323 0 16.202234 9.386225 28.770121 24.831124 33.311981 11.508049 3.484167 291.938425 3.331842 303.598756-.150179 14.382958-4.239356 24.376338-17.716903 24.376338-33.009477-.15018-13.627727-7.266547-24.833271-19.986824-31.344629-5.449396-2.724687-11.20548-2.874867-155.961456-2.724687-82.676017 0-152.329318.757334-154.903826 1.512523z"/><path d="m787.496268 359.011511c-20.289265 4.389535-39.518755 15.14025-55.115829 30.887654-7.721463 7.723458-19.986846 24.076022-37.853992 50.12137-48.301988 70.71314-72.832754 106.294979-104.932507 152.932183-55.115979 79.799048-68.443347 100.240595-70.865486 108.266622-3.331885 11.658228-2.424328 34.369675 3.784483 87.065553 9.238191 79.494356 9.388371 80.55634 18.626562 90.094891 7.116368 7.268693 16.047763 11.355723 25.133629 11.355723 4.089177 0 39.823341-7.116368 79.496502-15.897584 80.25169-17.869227 87.368143-20.139085 101.148109-33.768957 4.089176-4.08703 14.232735-16.957422 22.561267-28.770121 8.328532-11.808408 28.012787-39.368511 43.610012-61.172444 15.747404-21.95407 29.677635-41.488188 31.039979-43.607866 1.362344-2.119614 8.328532-11.962836 15.597225-21.956258 17.714757-24.680946 50.119225-70.25831 52.843912-74.497666 1.212164-1.817173 7.723522-11.053219 14.537385-20.44159 19.229426-26.802768 21.803934-30.889863 26.345794-41.943018 12.720213-31.042124 12.115203-64.201824-1.817173-94.486571-9.841055-21.198924-24.680946-37.248833-56.478259-61.324769-23.621106-17.716902-45.577364-29.375131-62.384608-32.859297-12.567887-2.724688-32.859297-2.724688-45.27486 0zm37.553483 71.013499c8.176207 3.484167 42.095343 27.710282 51.483714 36.948473 15.597225 15.14025 20.744094 34.522001 13.627726 50.724235-2.724687 6.056529-10.750714 18.019342-23.470927 34.979039-1.967352 2.724623-4.08703 1.36228-36.036668-22.108647-18.624417-13.627727-42.550173-31.192304-53.300888-38.913595-10.598389-7.723609-19.53193-14.537472-19.98676-15.142481-.605009-1.212164 19.98676-31.797313 25.43828-37.703663 8.631037-9.538593 29.82996-13.930231 42.245523-8.783361zm-81.766359 123.559197c11.962879 8.783361 33.614487 24.680945 48.151873 35.281523 9.976559 7.27352 19.919951 14.592308 29.82996 21.956215l3.482021 2.574465-15.597224 21.347001c-8.478712 11.660373-36.944183 51.181209-63.139797 87.977358l-47.546863 66.623963-15.292575 3.331842c-8.328531 1.967353-31.344629 7.116368-51.331388 11.658228-19.836581 4.544006-36.341362 8.178352-36.493644 7.873702-.30036-.300359-1.664849-12.113057-3.027192-26.195614-1.362344-14.232735-3.481957-34.069316-4.541861-44.212875l-2.121887-18.626562 10.298137-14.687565c5.601656-8.174062 17.564535-25.43828 26.345751-38.308672 21.351249-31.192304 68.593462-100.08827 75.257145-109.626821 2.874867-4.239356 8.326387-11.962878 11.810468-17.262137 6.056614-8.631058 6.813949-9.388306 9.388392-7.573279 1.512587 1.061985 12.565849 9.088012 24.528684 17.87133z"/><path d="m234.058679 474.542536c-24.680945 12.872537-24.226115 47.849367.90966 61.324854 6.056529 3.331756 6.964042 3.331756 104.479951 3.786586 67.683802.302505 100.540954-.15018 105.082815-1.212164 15.14454-3.784612 25.590604-16.047764 27.105273-31.797227 1.362343-14.082642-9.841055-30.43497-23.771286-34.524232-4.089176-1.362344-34.221641-1.817173-105.84444-1.817173h-99.9338l-8.023882 4.239356z"/></svg>
|
||||
|
After Width: | Height: | Size: 4.5 KiB |
@@ -0,0 +1 @@
|
||||
<svg height="200" viewBox="0 0 1024 1024" width="200" xmlns="http://www.w3.org/2000/svg"><path d="m152.811049 118.849443c-15.812621 4.297514-25.94171 8.595029-37.761527 16.269621-27.475706 17.807973-46.508803 47.276949-51.881211 80.58644-2.30433 13.967458-2.30433 541.697068 0 557.968835 5.67922 41.595562 29.471101 74.136949 66.61896 91.483631 22.410154 10.438047 25.939565 11.049525 85.956724 11.665295 36.379756.461291 56.180935.154479 61.555509-.922582 28.241728-6.292865 34.227782-42.824955 9.360986-57.099225-6.447344-3.683891-6.599677-3.683891-63.241903-4.451994l-56.792413-.768102-10.285714-5.063472c-12.585731-6.140532-22.871444-17.03987-27.321334-29.166455l-3.224702-8.595029-.152291-274.148373v-273.993894l3.529369-9.210799c4.297514-11.512961 15.655997-23.791901 27.323394-29.471121l8.595072-4.297515 263.864805-.46129 264.017224-.306791 9.515379 4.451972c12.894689 5.986053 21.182948 13.967436 27.016626 25.939564l5.063471 10.285714.768103 63.701048c.768103 69.687102.615813 67.84623 10.285714 77.057028 9.976755 9.515465 22.716965 11.204004 35.457175 4.913284 6.292865-3.072411 9.056319-5.835865 12.740209-11.974252l4.451994-7.829071-.461291-67.539418c-.459146-65.850879-.459146-67.84623-3.990703-78.284276-12.740209-38.527441-40.370459-66.310003-79.204669-79.357046l-10.744902-3.531557-270.15767-.306855c-258.797043-.306769-270.616816-.154414-280.900384 2.456685z"/><path d="m236.467754 301.818851c-6.754156 2.763454-13.81727 9.05632-17.192202 15.655997-7.829072 15.351331-1.843018 33.157158 13.967458 41.290896l8.290362 4.299659 154.418722-.306812 154.418723-.46129 5.833613-3.531557c7.520221-4.75666 12.740317-11.817628 15.040334-20.414803 3.686036-13.508312-4.295368-29.164309-17.803681-35.304842-5.833656-2.761308-14.428706-2.915787-159.638819-2.761308-97.162872 0-154.880013.613624-157.33451 1.53406z"/><path d="m696.194511 427.380076c-6.908635 3.224681-13.660603 9.363132-17.344536 16.271767-2.304309 3.990702-2.763454 11.972107-3.377078 54.337831l-.768103 49.733677-21.182863 4.297514c-75.061719 14.735561-137.074229 47.890573-188.496273 100.848908-45.436162 46.663325-77.209447 101.614866-98.08554 169.154284-7.213302 23.794026-7.520114 30.239225-2.147685 41.138562 4.297515 8.442695 11.972107 14.428749 21.642051 16.4241 7.522259 1.53406 20.10799-2.763454 28.705164-9.976756 16.117288-13.355979 60.632928-38.222774 92.251735-51.42213 47.738325-20.110135 110.67333-36.379756 157.643467-40.984083l4.297514-.461291.306898 53.724293c.461291 52.95619.461291 53.878772 3.990552 59.403535 10.59259 17.192203 30.852913 22.871444 46.665535 13.047021 3.06812-1.840872 23.330632-20.721614 45.279452-41.752186 26.169137-25.408973 52.41766-50.735686 78.745567-75.979967 124.179498-118.807069 118.193445-112.207434 118.193445-129.860885-.154479-16.576433.306812-16.117245-70.762017-79.663858-108.67798-97.165018-138.301435-123.720352-149.968918-134.465211-6.140533-5.679241-14.121894-11.819774-17.651392-13.508313-8.290276-3.990702-19.648759-4.143035-27.936975-.306812zm97.778642 144.748779c55.412832 49.581258 79.050234 70.455119 88.415511 78.284276 2.761308 2.302164 4.911138 4.604327 4.911138 5.372516 0 .613581-18.880742 19.341947-41.904519 41.290809-22.871444 22.103341-57.25585 55.106063-76.288925 73.373138-19.033075 18.264972-34.843551 33.155012-35.304842 33.155012s-.768103-15.810476-.768103-34.99803v-35.150363l-4.604326-7.367781c-6.599678-10.744772-17.192246-16.117287-31.775431-16.117287-11.972107 0-54.797062 5.831574-84.883954 11.665294-49.73359 9.669944-96.703727 24.098693-139.528683 42.979435-8.442695 3.838369-15.503663 6.447344-15.812621 6.140532-1.072769-.922582 18.880742-32.541302 29.166455-46.049701 5.063472-6.908635 17.958161-21.028426 28.550686-31.620908 38.988732-38.527484 79.51367-61.398929 133.697108-75.214053 16.117245-4.145181 26.862147-5.986097 57.101371-10.59038 13.660646-1.995351 22.564632-7.981405 27.93483-18.880742 3.840601-7.674593 3.990789-8.903922 3.990789-36.838902 0-15.812707.308957-28.857498.770248-28.857498.459145 0 25.787231 22.255675 56.333268 49.424633z"/><path d="m234.165591 463.453021c-22.719111 10.283568-23.794026 44.820307-1.688539 56.642226 6.292866 3.374932 6.445199 3.374932 110.671186 3.374932h104.380465l7.520114-3.683782c8.442781-4.145332 15.503663-13.660754 17.037724-23.178364 2.14983-12.278919-5.524763-26.094044-17.805827-32.389055-5.679242-2.761308-9.824423-2.915873-110.671186-2.915873-84.270329.154565-105.760047.461377-109.443937 2.149916z"/></svg>
|
||||
|
After Width: | Height: | Size: 4.3 KiB |
@@ -3,8 +3,8 @@ export interface FixItEventMap {
|
||||
'fixit:switch-theme': { isDark: boolean, mode: string, isChanged: boolean }
|
||||
'fixit:scroll': void
|
||||
'fixit:resize': void
|
||||
'fixit:decrypted': { target: Element }
|
||||
'fixit:partial-decrypted': { target: Element }
|
||||
'fixit:content-decrypted': { target: HTMLElement, isPage: boolean }
|
||||
'fixit:toc-decrypted': { html: string }
|
||||
'fixit:re-encrypt': void
|
||||
'fixit:code-tab-sync': { lang: string, source: HTMLElement }
|
||||
'fixit:sw-update': void
|
||||
@@ -15,20 +15,21 @@ export type FixItDocumentEventMap = {
|
||||
[K in keyof FixItEventMap]: CustomEvent<FixItEventMap[K]>
|
||||
}
|
||||
|
||||
type Handler<T> = T extends void
|
||||
/** Event handler type — infers the correct signature from the event payload. */
|
||||
export type Handler<T> = T extends void
|
||||
? (() => void) | ((event: CustomEvent<void>) => void)
|
||||
: (event: CustomEvent<T>) => void
|
||||
|
||||
/** Typed event bus — wraps DOM CustomEvents with type-safe emit/on/off. */
|
||||
export class TypedEventBus {
|
||||
private target = document
|
||||
#target = document
|
||||
|
||||
on<K extends keyof FixItEventMap>(event: K, handler: Handler<FixItEventMap[K]>): void {
|
||||
this.target.addEventListener(event as string, handler as EventListener)
|
||||
this.#target.addEventListener(event as string, handler as EventListener)
|
||||
}
|
||||
|
||||
off<K extends keyof FixItEventMap>(event: K, handler: Handler<FixItEventMap[K]>): void {
|
||||
this.target.removeEventListener(event as string, handler as EventListener)
|
||||
this.#target.removeEventListener(event as string, handler as EventListener)
|
||||
}
|
||||
|
||||
emit<K extends keyof FixItEventMap>(
|
||||
@@ -36,7 +37,7 @@ export class TypedEventBus {
|
||||
...args: FixItEventMap[K] extends void ? [] : [FixItEventMap[K]]
|
||||
): void {
|
||||
const detail = args[0]
|
||||
this.target.dispatchEvent(
|
||||
this.#target.dispatchEvent(
|
||||
detail !== undefined
|
||||
? new CustomEvent(event as string, { detail })
|
||||
: new CustomEvent(event as string),
|
||||
|
||||
@@ -50,6 +50,7 @@ export class PublicAPI implements FixItPublicAPI {
|
||||
get version() { return this.core.version }
|
||||
get themeMode() { return this.core.themeMode }
|
||||
get isDark() { return this.core.isDark }
|
||||
get isRTL() { return this.core.isRTL }
|
||||
|
||||
setThemeMode(mode: string, persist?: boolean) {
|
||||
this.theme.setThemeMode(mode, persist)
|
||||
|
||||
@@ -7,6 +7,7 @@ export interface CoreService {
|
||||
readonly config: FixItConfig
|
||||
readonly version: string
|
||||
isDark: boolean
|
||||
isRTL: boolean
|
||||
themeMode: string
|
||||
registerMaskOverlay: (name: string, handlers: MaskOverlayHandler) => void
|
||||
openMaskOverlay: (name: string) => void
|
||||
@@ -45,9 +46,9 @@ export interface CodeService {
|
||||
|
||||
// ─── TocService ───
|
||||
export interface TocService {
|
||||
renderToc: () => void
|
||||
syncTocHeight: () => void
|
||||
syncTocActiveState: () => void
|
||||
initToc: () => void
|
||||
setup: () => void
|
||||
}
|
||||
|
||||
@@ -69,8 +70,8 @@ export interface MiscService {
|
||||
initSiteTime: () => void
|
||||
initAutoMark: () => void
|
||||
initReward: () => void
|
||||
initPostChatUser: () => void
|
||||
initComment: () => void
|
||||
initPostChat: () => void
|
||||
setup: () => void
|
||||
}
|
||||
|
||||
@@ -94,6 +95,7 @@ export interface FixItPublicAPI {
|
||||
readonly version: string
|
||||
readonly themeMode: string
|
||||
readonly isDark: boolean
|
||||
readonly isRTL: boolean
|
||||
// Modules
|
||||
readonly core: CoreService
|
||||
readonly theme: ThemeService
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
// @ts-expect-error — Hugo js.Build virtual module
|
||||
import params from '@params'
|
||||
|
||||
/**
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
*
|
||||
* Responsibilities:
|
||||
* - Discover `.shortcode-aplayer` nodes and initialize APlayer instances once.
|
||||
* - Re-run initialization after decrypted or partially decrypted content is revealed.
|
||||
* - Re-run initialization when `fixit:content-decrypted` is emitted.
|
||||
* - Keep behavior idempotent through `data-processed` markers.
|
||||
*/
|
||||
import { eventBus } from '../core/event-bus'
|
||||
@@ -24,6 +24,5 @@ function initAPlayer() {
|
||||
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
initAPlayer()
|
||||
eventBus.on('fixit:decrypted', initAPlayer)
|
||||
eventBus.on('fixit:partial-decrypted', initAPlayer)
|
||||
eventBus.on('fixit:content-decrypted', initAPlayer)
|
||||
}, false)
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
* - Initialize ECharts instances with light/dark theme support.
|
||||
* - Re-render all charts on theme switch.
|
||||
* - Resize charts on window resize events.
|
||||
* - Re-run initialization after decrypted content is revealed.
|
||||
* - Re-run initialization when `fixit:content-decrypted` is emitted.
|
||||
*/
|
||||
import { eventBus } from '../core/event-bus'
|
||||
import { getStagingDOM, isDarkMode, isObjectLiteral } from '../utils'
|
||||
@@ -84,6 +84,11 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
eventBus.on('fixit:resize', () => {
|
||||
echartsArr.forEach(chart => chart.resize())
|
||||
})
|
||||
eventBus.on('fixit:decrypted', applyEchartsTheme)
|
||||
eventBus.on('fixit:partial-decrypted', ({ detail }) => initEchartsInTarget(detail.target))
|
||||
eventBus.on('fixit:content-decrypted', ({ detail }) => {
|
||||
if (detail.isPage) {
|
||||
applyEchartsTheme()
|
||||
return
|
||||
}
|
||||
initEchartsInTarget(detail.target)
|
||||
})
|
||||
}, false)
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
* Responsibilities:
|
||||
* - Initialize folder expand/collapse interactions for `.file-tree` blocks.
|
||||
* - Recalculate connector line heights when tree visibility/layout changes.
|
||||
* - Sync tree state across tab switches, print preparation, and decrypted content updates.
|
||||
* - Sync tree state across tab switches, print preparation, and `fixit:content-decrypted` updates.
|
||||
*/
|
||||
import type { TabContainerChangedEvent } from '../types'
|
||||
import { eventBus } from '../core/event-bus'
|
||||
@@ -88,11 +88,7 @@ function bindEvents() {
|
||||
}
|
||||
}, false)
|
||||
|
||||
eventBus.on('fixit:decrypted', () => {
|
||||
initFileTree()
|
||||
})
|
||||
|
||||
eventBus.on('fixit:partial-decrypted', ({ detail }) => {
|
||||
eventBus.on('fixit:content-decrypted', ({ detail }) => {
|
||||
initFileTree(detail.target)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -2,17 +2,17 @@
|
||||
* Encrypted content decryptor for FixIt pages and shortcodes.
|
||||
*
|
||||
* Responsibilities:
|
||||
* - Validate password input and decrypt AES-256-GCM payloads into target containers.
|
||||
* - Validate password input and decrypt AES-256-GCM payloads.
|
||||
* - Support both full-page and shortcode-scoped encrypted blocks.
|
||||
* - Persist and validate page-level decrypt cache with expiration.
|
||||
* - Emit and react to FixIt events for decrypted/partial-decrypted/reset flows.
|
||||
* - Emit FixIt events for content/TOC rendering and re-encrypt flows.
|
||||
*
|
||||
* Encrypted payloads are stored in `<template data-password="..." data-cipher="...">` elements.
|
||||
* Using `<template>` prevents the browser from rendering inner content (mermaid, echarts, etc.).
|
||||
*
|
||||
* Payload formats:
|
||||
* - v2 (current): PBKDF2(SHA-256(password), 100k iterations) → base64(salt).base64(iv).base64(ciphertext+tag)
|
||||
* - Password verification: data-password stores PBKDF2(SHA-256(password), data-verify-salt) when post-encrypted
|
||||
* - Password verification: data-password stores PBKDF2(SHA-256(password), data-verify-salt) when fixit-encrypted
|
||||
*/
|
||||
import { eventBus } from '../core/event-bus'
|
||||
import { flashTooltip } from '../utils'
|
||||
@@ -43,6 +43,33 @@ class FixItDecryptor {
|
||||
customElements.get('fixit-encryptor') || customElements.define('fixit-encryptor', class extends HTMLElement {})
|
||||
}
|
||||
|
||||
/** Toggle encrypted/decrypted visibility helper classes in the given scope. */
|
||||
#toggleClass(container: Element | Document, show: boolean): void {
|
||||
const fromClass = show ? 'encrypted-hidden' : 'decrypted-shown'
|
||||
const toClass = show ? 'decrypted-shown' : 'encrypted-hidden'
|
||||
container.querySelectorAll(`.${fromClass}`).forEach(($element: Element) => {
|
||||
$element.classList.replace(fromClass, toClass)
|
||||
})
|
||||
}
|
||||
|
||||
/** Copy TOC HTML to all TOC containers. */
|
||||
#renderToc(html: string): void {
|
||||
const $template = document.createElement('template')
|
||||
$template.innerHTML = html
|
||||
const $tocCore = $template.content.querySelector('#TableOfContents')
|
||||
if (!$tocCore)
|
||||
return
|
||||
for (const id of ['toc-content-static', 'toc-content-auto', 'toc-content-drawer']) {
|
||||
const $container = document.getElementById(id)
|
||||
if (!$container)
|
||||
continue
|
||||
$container.textContent = ''
|
||||
const $clone = $tocCore.cloneNode(true) as HTMLElement
|
||||
$clone.removeAttribute('id')
|
||||
$container.appendChild($clone)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Decrypt a v2 payload using PBKDF2 key derivation.
|
||||
* Format: base64(salt).base64(iv).base64(ciphertext+tag)
|
||||
@@ -76,6 +103,11 @@ class FixItDecryptor {
|
||||
return new TextDecoder().decode(plainBuffer)
|
||||
}
|
||||
|
||||
/**
|
||||
* Compute SHA-256 hex digest for user input.
|
||||
* @param input - Raw password input.
|
||||
* @returns Lowercase hex SHA-256 digest.
|
||||
*/
|
||||
async #sha256Hex(input: string): Promise<string> {
|
||||
const bytes = new TextEncoder().encode(input)
|
||||
const hashBuffer = await crypto.subtle.digest('SHA-256', bytes)
|
||||
@@ -100,6 +132,45 @@ class FixItDecryptor {
|
||||
.join('')
|
||||
}
|
||||
|
||||
/**
|
||||
* Decrypt TOC template content and refill TOC containers directly.
|
||||
* The template itself stays encrypted so re-encrypt can restore the initial state.
|
||||
* @param passwordHash - SHA-256 hash for AES key derivation.
|
||||
*/
|
||||
async #decryptToc(passwordHash: string): Promise<void> {
|
||||
const $tocTemplate = document.querySelector<HTMLTemplateElement>('template[data-toc]')
|
||||
if (!$tocTemplate)
|
||||
return
|
||||
try {
|
||||
const cipher = $tocTemplate.dataset.cipher
|
||||
const payload = $tocTemplate.innerHTML
|
||||
|
||||
let html: string
|
||||
if (!cipher) {
|
||||
// Dev mode: no post-build encryption, TOC is plaintext
|
||||
html = payload
|
||||
}
|
||||
else if (cipher === 'aes-256-gcm-v2') {
|
||||
html = await this.#decryptV2(payload, passwordHash)
|
||||
}
|
||||
else {
|
||||
throw new Error(`Unsupported cipher: ${cipher}`)
|
||||
}
|
||||
|
||||
this.#renderToc(html)
|
||||
eventBus.emit('fixit:toc-decrypted', { html })
|
||||
}
|
||||
catch (err) {
|
||||
console.error('[FixItDecryptor] Failed to restore TOC:', err)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Decrypt template payload and write plaintext into the target container.
|
||||
* @param $template - Encrypted content template element.
|
||||
* @param $target - Target container that should receive decrypted content.
|
||||
* @param passwordHash - SHA-256 hash for AES key derivation.
|
||||
*/
|
||||
async #decryptContent($template: HTMLTemplateElement, $target: HTMLElement, passwordHash: string): Promise<void> {
|
||||
try {
|
||||
const cipher = $template.dataset.cipher
|
||||
@@ -119,6 +190,9 @@ class FixItDecryptor {
|
||||
|
||||
$target.innerHTML = html
|
||||
$template.parentElement!.classList.add('decrypted')
|
||||
const isPage = $target.id === 'content'
|
||||
this.#toggleClass(isPage ? document : $target, true)
|
||||
eventBus.emit('fixit:content-decrypted', { target: $target, isPage })
|
||||
}
|
||||
catch (err) {
|
||||
const $encryptor = $template.parentElement!
|
||||
@@ -128,10 +202,6 @@ class FixItDecryptor {
|
||||
}
|
||||
return console.error(err)
|
||||
}
|
||||
if ($target.id === 'content')
|
||||
eventBus.emit('fixit:decrypted', { target: $target })
|
||||
else
|
||||
eventBus.emit('fixit:partial-decrypted', { target: $target })
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -185,10 +255,7 @@ class FixItDecryptor {
|
||||
*/
|
||||
init({ all, shortcode }: { all?: boolean, shortcode?: boolean }): void {
|
||||
if (shortcode) {
|
||||
eventBus.on('fixit:decrypted', ({ detail }) => {
|
||||
this.initShortcodes(detail.target)
|
||||
})
|
||||
eventBus.on('fixit:partial-decrypted', ({ detail }) => {
|
||||
eventBus.on('fixit:content-decrypted', ({ detail }) => {
|
||||
this.initShortcodes(detail.target)
|
||||
})
|
||||
}
|
||||
@@ -200,27 +267,7 @@ class FixItDecryptor {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Decrypt the TOC template content in place.
|
||||
* After decryption, TocModule.initToc() (via fixit:decrypted event) copies it to containers.
|
||||
* @param passwordHash - SHA-256 hash for AES key derivation.
|
||||
*/
|
||||
async #restoreToc(passwordHash: string): Promise<void> {
|
||||
const $tocTemplate = document.querySelector<HTMLTemplateElement>('template[data-toc][data-cipher]')
|
||||
if (!$tocTemplate)
|
||||
return
|
||||
try {
|
||||
const cipher = $tocTemplate.dataset.cipher
|
||||
const payload = $tocTemplate.innerHTML
|
||||
if (cipher === 'aes-256-gcm-v2')
|
||||
$tocTemplate.innerHTML = await this.#decryptV2(payload, passwordHash)
|
||||
}
|
||||
catch (err) {
|
||||
console.error('[FixItDecryptor] Failed to restore TOC:', err)
|
||||
}
|
||||
}
|
||||
|
||||
/** Initialize whole-page decryption with cache validation and encrypt/re-encrypt buttons. */
|
||||
/** Initialize whole-page decryption with cache validation and input handlers. */
|
||||
initPage(): void {
|
||||
this.validateCache()
|
||||
const $encryptor = document.querySelector<HTMLElement>('article > fixit-encryptor')!
|
||||
@@ -236,7 +283,7 @@ class FixItDecryptor {
|
||||
sha256: passwordHash,
|
||||
}),
|
||||
)
|
||||
await this.#restoreToc(passwordHash)
|
||||
await this.#decryptToc(passwordHash)
|
||||
await this.#decryptContent($template, $content, passwordHash)
|
||||
}).catch(console.error)
|
||||
}
|
||||
@@ -253,11 +300,17 @@ class FixItDecryptor {
|
||||
decryptorHandler()
|
||||
})
|
||||
|
||||
// Only for full-page decryption: re-encrypt button to clear cache and reset content
|
||||
// Re-encrypt button only orchestrates state/cache reset; modules handle DOM cleanup.
|
||||
const $reEncryptBtn = $encryptor.querySelector<HTMLElement>('.fixit-encryptor-btn')
|
||||
$reEncryptBtn?.addEventListener('click', (e) => {
|
||||
e.preventDefault()
|
||||
window.CellTooltip?.getOrCreateInstance($reEncryptBtn).dispose()
|
||||
for (const id of ['toc-content-static', 'toc-content-auto', 'toc-content-drawer']) {
|
||||
const $el = document.getElementById(id)
|
||||
if ($el) {
|
||||
$el.textContent = ''
|
||||
}
|
||||
}
|
||||
$content.animate(
|
||||
[
|
||||
{ opacity: 1, transform: 'scaleY(1)', transformOrigin: 'top' },
|
||||
@@ -269,13 +322,8 @@ class FixItDecryptor {
|
||||
$content.style.opacity = ''
|
||||
$content.style.transform = ''
|
||||
})
|
||||
for (const id of ['toc-content-static', 'toc-content-auto', 'toc-content-drawer']) {
|
||||
const $el = document.getElementById(id)
|
||||
if ($el?.querySelector('nav')) {
|
||||
$el.textContent = ''
|
||||
}
|
||||
}
|
||||
$encryptor.classList.remove('decrypted')
|
||||
this.#toggleClass(document, false)
|
||||
window.localStorage?.removeItem(`fixit-decryptor/#${location.pathname}`)
|
||||
eventBus.emit('fixit:re-encrypt')
|
||||
})
|
||||
@@ -330,7 +378,7 @@ class FixItDecryptor {
|
||||
return this
|
||||
}
|
||||
// Use sha256 hash for AES key derivation (not the verification hash)
|
||||
void this.#restoreToc(cachedStat.sha256)
|
||||
void this.#decryptToc(cachedStat.sha256)
|
||||
.then(() => this.#decryptContent($template, $content, cachedStat.sha256))
|
||||
return this
|
||||
}
|
||||
|
||||
@@ -8,8 +8,8 @@
|
||||
import { eventBus } from '../core/event-bus'
|
||||
import { isDarkMode } from '../utils'
|
||||
|
||||
function applyJsonViewerTheme(isDark: boolean) {
|
||||
document.querySelectorAll('json-viewer').forEach(($el: Element) => {
|
||||
function applyJsonViewerTheme(isDark: boolean, scope: Element | Document = document) {
|
||||
scope.querySelectorAll('json-viewer').forEach(($el: Element) => {
|
||||
$el.setAttribute('theme', isDark ? 'dark' : 'light')
|
||||
})
|
||||
}
|
||||
@@ -25,4 +25,8 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
return
|
||||
applyJsonViewerTheme(detail.isDark)
|
||||
})
|
||||
|
||||
eventBus.on('fixit:content-decrypted', ({ detail }) => {
|
||||
applyJsonViewerTheme(isDarkMode(), detail.target)
|
||||
})
|
||||
}, false)
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
*
|
||||
* Responsibilities:
|
||||
* - Initialize lightGallery for page image zoom and thumbnails.
|
||||
* - Re-initialize on decrypted or partially decrypted content.
|
||||
* - Re-initialize when `fixit:content-decrypted` is emitted.
|
||||
*/
|
||||
import { eventBus } from '../core/event-bus'
|
||||
|
||||
@@ -36,6 +36,5 @@ function initLightGallery() {
|
||||
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
initLightGallery()
|
||||
eventBus.on('fixit:decrypted', initLightGallery)
|
||||
eventBus.on('fixit:partial-decrypted', initLightGallery)
|
||||
eventBus.on('fixit:content-decrypted', initLightGallery)
|
||||
}, false)
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
* Responsibilities:
|
||||
* - Initialize Mapbox GL maps with controls and optional markers.
|
||||
* - Apply light/dark style on theme switch.
|
||||
* - Re-run initialization after decrypted content is revealed.
|
||||
* - Re-run initialization when `fixit:content-decrypted` is emitted.
|
||||
*/
|
||||
import { eventBus } from '../core/event-bus'
|
||||
import { isDarkMode } from '../utils'
|
||||
@@ -83,6 +83,5 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
mapbox.addControl(new window.MapboxLanguage())
|
||||
})
|
||||
})
|
||||
eventBus.on('fixit:decrypted', () => initMapbox())
|
||||
eventBus.on('fixit:partial-decrypted', ({ detail }) => initMapbox(detail.target))
|
||||
eventBus.on('fixit:content-decrypted', ({ detail }) => initMapbox(detail.target))
|
||||
}, false)
|
||||
|
||||
@@ -68,10 +68,7 @@ function initMathJax(el?: Element) {
|
||||
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
bootstrapMathJax()
|
||||
eventBus.on('fixit:decrypted', () => {
|
||||
initMathJax()
|
||||
})
|
||||
eventBus.on('fixit:partial-decrypted', ({ detail }) => {
|
||||
eventBus.on('fixit:content-decrypted', ({ detail }) => {
|
||||
initMathJax(detail.target)
|
||||
})
|
||||
})
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
* - Bind pan/zoom behavior and synchronize transforms across light/dark layers
|
||||
* during theme switches.
|
||||
* - Wire diagram tab controls (diagram/code switch, zoom/reset/download actions).
|
||||
* - React to FixIt events (theme switch, decrypted content, partial decrypted content)
|
||||
* - React to FixIt events (theme switch and `fixit:content-decrypted`)
|
||||
* and re-observe/re-render affected containers when context changes.
|
||||
*
|
||||
* Public entrypoints:
|
||||
@@ -793,8 +793,7 @@ function bindGlobalEventsOnce(): void {
|
||||
bindTabContainerChanged()
|
||||
|
||||
initMermaid()
|
||||
eventBus.on('fixit:decrypted', initMermaid)
|
||||
eventBus.on('fixit:partial-decrypted', initMermaid)
|
||||
eventBus.on('fixit:content-decrypted', initMermaid)
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
* Responsibilities:
|
||||
* - Automatically add spacing between CJK (Chinese, Japanese, Korean) and ASCII characters.
|
||||
* - Support both full-page spacing and selector-based spacing.
|
||||
* - Re-run spacing after decrypted or partially decrypted content is revealed.
|
||||
* - Re-run spacing when `fixit:content-decrypted` is emitted.
|
||||
*/
|
||||
import { eventBus } from '../core/event-bus'
|
||||
|
||||
@@ -30,10 +30,7 @@ function initPangu(target?: Element) {
|
||||
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
initPangu()
|
||||
eventBus.on('fixit:decrypted', () => {
|
||||
initPangu()
|
||||
})
|
||||
eventBus.on('fixit:partial-decrypted', ({ detail }) => {
|
||||
eventBus.on('fixit:content-decrypted', ({ detail }) => {
|
||||
initPangu(detail.target)
|
||||
})
|
||||
}, false)
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
*
|
||||
* Responsibilities:
|
||||
* - Parse emoji shortcodes into Twemoji images when enabled.
|
||||
* - Re-run parsing after decrypted or partially decrypted content is revealed.
|
||||
* - Re-run parsing when `fixit:content-decrypted` is emitted.
|
||||
*/
|
||||
import { eventBus } from '../core/event-bus'
|
||||
|
||||
@@ -14,10 +14,7 @@ function initTwemoji(target: Element | Document = document) {
|
||||
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
initTwemoji()
|
||||
eventBus.on('fixit:decrypted', () => {
|
||||
initTwemoji()
|
||||
})
|
||||
eventBus.on('fixit:partial-decrypted', ({ detail }) => {
|
||||
eventBus.on('fixit:content-decrypted', ({ detail }) => {
|
||||
initTwemoji(detail.target)
|
||||
})
|
||||
}, false)
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
*
|
||||
* Responsibilities:
|
||||
* - Initialize TypeIt typewriter instances, grouped and chained by data attributes.
|
||||
* - Re-run initialization after decrypted content is revealed.
|
||||
* - Re-run initialization when `fixit:content-decrypted` is emitted.
|
||||
*/
|
||||
import { eventBus } from '../core/event-bus'
|
||||
import { getStagingDOM } from '../utils'
|
||||
@@ -66,6 +66,5 @@ function initTypeit(target: Element | Document = document) {
|
||||
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
initTypeit()
|
||||
eventBus.on('fixit:decrypted', () => initTypeit())
|
||||
eventBus.on('fixit:partial-decrypted', ({ detail }) => initTypeit(detail.target))
|
||||
eventBus.on('fixit:content-decrypted', ({ detail }) => initTypeit(detail.target))
|
||||
}, false)
|
||||
|
||||
@@ -16,10 +16,13 @@ const copyText = createCopyText()
|
||||
* - Re-initialize components after encrypted content is decrypted.
|
||||
*/
|
||||
export class ContentModule implements ContentService {
|
||||
constructor(
|
||||
private readonly core: CoreService,
|
||||
private readonly code: CodeService,
|
||||
) {}
|
||||
#core: CoreService
|
||||
#code: CodeService
|
||||
|
||||
constructor(core: CoreService, code: CodeService) {
|
||||
this.#core = core
|
||||
this.#code = code
|
||||
}
|
||||
|
||||
/** Fetch and inline SVG icons referenced by `data-svg-src` attributes. */
|
||||
initSVGIcon() {
|
||||
@@ -131,7 +134,7 @@ export class ContentModule implements ContentService {
|
||||
return
|
||||
const footnoteMap = new Map<HTMLElement, HTMLElement>()
|
||||
$footnoteRefs.forEach(($ref) => {
|
||||
if (this.core.config.tooltip) {
|
||||
if (this.#core.config.tooltip) {
|
||||
const $link = $ref.querySelector<HTMLAnchorElement>('a.footnote-ref')
|
||||
if ($link) {
|
||||
$link.addEventListener('click', (e) => {
|
||||
@@ -153,7 +156,7 @@ export class ContentModule implements ContentService {
|
||||
if ($ref.hasAttribute('title'))
|
||||
return
|
||||
$ref.setAttribute('title', $content.textContent!.trim())
|
||||
if (this.core.config.tooltip) {
|
||||
if (this.#core.config.tooltip) {
|
||||
CellTooltip.getOrCreateInstance($ref)
|
||||
}
|
||||
})
|
||||
@@ -161,9 +164,10 @@ export class ContentModule implements ContentService {
|
||||
|
||||
/** Initialize CellTooltip on action buttons, copy buttons, and footnotes. */
|
||||
initTooltip() {
|
||||
if (!this.core.config.tooltip)
|
||||
if (!this.#core.config.tooltip)
|
||||
return
|
||||
CellTooltip.initAll('li[data-task] > span[title]', { placement: 'right' })
|
||||
const inlineEnd = this.#core.isRTL ? 'left' : 'right'
|
||||
CellTooltip.initAll('li[data-task] > span[title]', { placement: inlineEnd })
|
||||
CellTooltip.initAll('.action-btn[title]', { placement: 'bottom' })
|
||||
CellTooltip.initAll('.copy-icon-btn[title]', { placement: 'top' })
|
||||
CellTooltip.initAll('.fixit-encryptor-btn[title]')
|
||||
@@ -177,9 +181,9 @@ export class ContentModule implements ContentService {
|
||||
*/
|
||||
initContent(target: Element | Document = document) {
|
||||
this.initDetails(target)
|
||||
this.code.initCodeWrapper()
|
||||
this.code.initCodeTabs()
|
||||
this.code.initDiagramCopyBtn()
|
||||
this.#code.initCodeWrapper()
|
||||
this.#code.initCodeTabs()
|
||||
this.#code.initDiagramCopyBtn()
|
||||
this.initTooltip()
|
||||
this.initLinkGuardDialog(target)
|
||||
}
|
||||
@@ -187,10 +191,7 @@ export class ContentModule implements ContentService {
|
||||
setup() {
|
||||
this.initContent()
|
||||
this.initSVGIcon()
|
||||
eventBus.on('fixit:decrypted', ({ detail }) => {
|
||||
this.initContent(detail.target)
|
||||
})
|
||||
eventBus.on('fixit:partial-decrypted', ({ detail }) => {
|
||||
eventBus.on('fixit:content-decrypted', ({ detail }) => {
|
||||
this.initContent(detail.target)
|
||||
})
|
||||
}
|
||||
|
||||
+19
-17
@@ -1,6 +1,6 @@
|
||||
import type { CoreService } from '../core/tokens'
|
||||
import type { FixItConfig, MaskOverlayHandler } from '../types'
|
||||
import { getThemeMode, isDarkMode } from '../utils'
|
||||
import { getThemeMode, isDarkMode, isRTL } from '../utils'
|
||||
|
||||
/**
|
||||
* Core module — shared state initialization and mask overlay management.
|
||||
@@ -13,15 +13,17 @@ import { getThemeMode, isDarkMode } from '../utils'
|
||||
export class CoreModule implements CoreService {
|
||||
readonly config: FixItConfig
|
||||
readonly version: string
|
||||
readonly isRTL: boolean
|
||||
themeMode: string
|
||||
isDark: boolean
|
||||
|
||||
private activeMaskOverlay: string | null = null
|
||||
private readonly maskOverlays = new Map<string, MaskOverlayHandler>()
|
||||
#activeMaskOverlay: string | null = null
|
||||
#maskOverlays = new Map<string, MaskOverlayHandler>()
|
||||
|
||||
constructor() {
|
||||
this.config = window.config
|
||||
this.version = this.config.version
|
||||
this.isRTL = isRTL()
|
||||
this.themeMode = getThemeMode()
|
||||
this.isDark = isDarkMode()
|
||||
|
||||
@@ -30,46 +32,46 @@ export class CoreModule implements CoreService {
|
||||
|
||||
/** Register a named mask overlay with open/close/isActive handlers. */
|
||||
registerMaskOverlay(name: string, handlers: MaskOverlayHandler) {
|
||||
this.maskOverlays.set(name, handlers)
|
||||
this.#maskOverlays.set(name, handlers)
|
||||
}
|
||||
|
||||
/** Toggle the mask element's blur class based on active overlay state. */
|
||||
syncMaskState() {
|
||||
document.getElementById('mask')?.classList.toggle('is-blur', Boolean(this.activeMaskOverlay))
|
||||
document.getElementById('mask')?.classList.toggle('is-blur', Boolean(this.#activeMaskOverlay))
|
||||
}
|
||||
|
||||
/** Open a named mask overlay, closing any previously active one. */
|
||||
openMaskOverlay(name: string) {
|
||||
const overlay = this.maskOverlays.get(name)
|
||||
const overlay = this.#maskOverlays.get(name)
|
||||
if (!overlay)
|
||||
return
|
||||
if (this.activeMaskOverlay && this.activeMaskOverlay !== name) {
|
||||
this.closeMaskOverlay(this.activeMaskOverlay, true)
|
||||
if (this.#activeMaskOverlay && this.#activeMaskOverlay !== name) {
|
||||
this.closeMaskOverlay(this.#activeMaskOverlay, true)
|
||||
}
|
||||
overlay.onOpen?.()
|
||||
this.activeMaskOverlay = name
|
||||
this.#activeMaskOverlay = name
|
||||
this.syncMaskState()
|
||||
}
|
||||
|
||||
/** Close a named mask overlay and optionally skip mask state sync. */
|
||||
closeMaskOverlay(name: string, skipSync = false) {
|
||||
const overlay = this.maskOverlays.get(name)
|
||||
const overlay = this.#maskOverlays.get(name)
|
||||
if (!overlay)
|
||||
return
|
||||
overlay.onClose?.()
|
||||
if (this.activeMaskOverlay === name) {
|
||||
this.activeMaskOverlay = null
|
||||
if (this.#activeMaskOverlay === name) {
|
||||
this.#activeMaskOverlay = null
|
||||
}
|
||||
!skipSync && this.syncMaskState()
|
||||
}
|
||||
|
||||
/** Toggle a named mask overlay open/closed. */
|
||||
toggleMaskOverlay(name: string) {
|
||||
const overlay = this.maskOverlays.get(name)
|
||||
const overlay = this.#maskOverlays.get(name)
|
||||
if (!overlay)
|
||||
return
|
||||
const isActive = overlay.isActive?.() ?? this.activeMaskOverlay === name
|
||||
if (this.activeMaskOverlay === name && isActive) {
|
||||
const isActive = overlay.isActive?.() ?? this.#activeMaskOverlay === name
|
||||
if (this.#activeMaskOverlay === name && isActive) {
|
||||
this.closeMaskOverlay(name)
|
||||
return
|
||||
}
|
||||
@@ -78,10 +80,10 @@ export class CoreModule implements CoreService {
|
||||
|
||||
/** Close whichever mask overlay is currently active. */
|
||||
closeActiveMaskOverlay() {
|
||||
if (!this.activeMaskOverlay) {
|
||||
if (!this.#activeMaskOverlay) {
|
||||
this.syncMaskState()
|
||||
return
|
||||
}
|
||||
this.closeMaskOverlay(this.activeMaskOverlay)
|
||||
this.closeMaskOverlay(this.#activeMaskOverlay)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,45 +1,24 @@
|
||||
import type { CoreService, EncryptionService } from '../core/tokens'
|
||||
import { eventBus } from '../core/event-bus'
|
||||
|
||||
/**
|
||||
* Encryption module — page decryption via FixItDecryptor and encrypted content toggling.
|
||||
* Encryption module — bootstrap page/shortcode decryption via FixItDecryptor.
|
||||
*
|
||||
* Responsibilities:
|
||||
* - Initialize FixItDecryptor for full-page and shortcode-scoped decryption.
|
||||
* - Toggle visibility of encrypted content sections.
|
||||
*/
|
||||
export class EncryptionModule implements EncryptionService {
|
||||
constructor(private readonly core: CoreService) {}
|
||||
#core: CoreService
|
||||
|
||||
/**
|
||||
* Toggle between encrypted-hidden and decrypted-shown classes.
|
||||
* @param container - The root element containing encrypted elements.
|
||||
* @param show - `true` to show decrypted content, `false` to hide.
|
||||
*/
|
||||
#toggleEncryptedClass(container: Element | Document, show: boolean) {
|
||||
const fromClass = show ? 'encrypted-hidden' : 'decrypted-shown'
|
||||
const toClass = show ? 'decrypted-shown' : 'encrypted-hidden'
|
||||
container.querySelectorAll(`.${fromClass}`).forEach(($element: Element) => {
|
||||
$element.classList.replace(fromClass, toClass)
|
||||
})
|
||||
constructor(core: CoreService) {
|
||||
this.#core = core
|
||||
}
|
||||
|
||||
/** Initialize the FixItDecryptor and wire up decryption/re-encryption events. */
|
||||
/** Initialize FixItDecryptor with encryption config. */
|
||||
setup() {
|
||||
if (!this.core.config.encryption || !window.FixItDecryptor)
|
||||
if (!this.#core.config.encryption || !window.FixItDecryptor)
|
||||
return
|
||||
const decryptor = new window.FixItDecryptor()
|
||||
|
||||
eventBus.on('fixit:decrypted', () => {
|
||||
this.#toggleEncryptedClass(document, true)
|
||||
})
|
||||
eventBus.on('fixit:partial-decrypted', ({ detail }) => {
|
||||
this.#toggleEncryptedClass(detail.target, true)
|
||||
})
|
||||
eventBus.on('fixit:re-encrypt', () => {
|
||||
this.#toggleEncryptedClass(document, false)
|
||||
})
|
||||
|
||||
decryptor.init(this.core.config.encryption)
|
||||
decryptor.init(this.#core.config.encryption)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,11 +15,13 @@ export class EventsModule implements EventsService {
|
||||
#resizeTimeout: number | null = null
|
||||
#newScrollTop = 0
|
||||
#oldScrollTop = 0
|
||||
#core: CoreService
|
||||
#code: CodeService
|
||||
|
||||
constructor(
|
||||
private readonly core: CoreService,
|
||||
private readonly code: CodeService,
|
||||
) {}
|
||||
constructor(core: CoreService, code: CodeService) {
|
||||
this.#core = core
|
||||
this.#code = code
|
||||
}
|
||||
|
||||
/** Bind scroll listener: auto-hide headers, reading progress, back-to-top, and TOC sync. */
|
||||
onScroll() {
|
||||
@@ -40,7 +42,7 @@ export class EventsModule implements EventsService {
|
||||
this.#newScrollTop = getScrollTop()
|
||||
const scroll = this.#newScrollTop - this.#oldScrollTop
|
||||
if (Math.abs(scroll) > ACCURACY) {
|
||||
this.core.closeActiveMaskOverlay()
|
||||
this.#core.closeActiveMaskOverlay()
|
||||
const isScrollingDown = scroll > 0
|
||||
$autoHeaders.forEach(($header) => {
|
||||
if (isScrollingDown) {
|
||||
@@ -97,7 +99,7 @@ export class EventsModule implements EventsService {
|
||||
|
||||
const _isMobile = isMobile()
|
||||
if (_isMobile !== resizeBefore) {
|
||||
this.core.closeActiveMaskOverlay()
|
||||
this.#core.closeActiveMaskOverlay()
|
||||
resizeBefore = _isMobile
|
||||
}
|
||||
}, 100)
|
||||
@@ -110,7 +112,7 @@ export class EventsModule implements EventsService {
|
||||
document.getElementById('mask')!.addEventListener('click', (e) => {
|
||||
if (!(e.target as HTMLElement).classList.contains('is-blur'))
|
||||
return
|
||||
this.core.closeActiveMaskOverlay()
|
||||
this.#core.closeActiveMaskOverlay()
|
||||
}, false)
|
||||
}
|
||||
|
||||
@@ -118,7 +120,7 @@ export class EventsModule implements EventsService {
|
||||
initPrint() {
|
||||
window.addEventListener('beforeprint', () => {
|
||||
const $content = document.getElementById('content')!
|
||||
const printConfig = this.core.config.print || {}
|
||||
const printConfig = this.#core.config.print || {}
|
||||
|
||||
if (printConfig.expandAdmonition) {
|
||||
$content.querySelectorAll('.admonition').forEach(($el: Element) => $el.classList.add('open'))
|
||||
@@ -156,7 +158,7 @@ export class EventsModule implements EventsService {
|
||||
}, false)
|
||||
|
||||
window.addEventListener('afterprint', () => {
|
||||
this.code.initCodeTabs()
|
||||
this.#code.initCodeTabs()
|
||||
}, false)
|
||||
}
|
||||
|
||||
|
||||
@@ -9,7 +9,11 @@ import type { CoreService, MenuService } from '../core/tokens'
|
||||
* - Sync menu state with mask overlay.
|
||||
*/
|
||||
export class MenuModule implements MenuService {
|
||||
constructor(private readonly core: CoreService) {}
|
||||
#core: CoreService
|
||||
|
||||
constructor(core: CoreService) {
|
||||
this.#core = core
|
||||
}
|
||||
|
||||
/** Set min-width on desktop sub-menus to match parent item width. */
|
||||
initDesktop() {
|
||||
@@ -24,7 +28,7 @@ export class MenuModule implements MenuService {
|
||||
const $menuMobile = document.getElementById('menu-mobile')
|
||||
if (!$menuToggleMobile || !$menuMobile)
|
||||
return
|
||||
this.core.registerMaskOverlay('menu-mobile', {
|
||||
this.#core.registerMaskOverlay('menu-mobile', {
|
||||
isActive: () => $menuMobile.classList.contains('active'),
|
||||
onOpen: () => {
|
||||
$menuToggleMobile.classList.add('active')
|
||||
@@ -38,7 +42,7 @@ export class MenuModule implements MenuService {
|
||||
},
|
||||
})
|
||||
$menuToggleMobile.addEventListener('click', () => {
|
||||
this.core.toggleMaskOverlay('menu-mobile')
|
||||
this.#core.toggleMaskOverlay('menu-mobile')
|
||||
}, false)
|
||||
// add nested menu toggler
|
||||
document.querySelectorAll<HTMLElement>('.menu-item>.nested-item').forEach(($nestedItem) => {
|
||||
|
||||
+47
-27
@@ -13,17 +13,20 @@ import { getScrollTop, isMobile, isValidDate, scrollIntoView } from '../utils'
|
||||
* - Initialize comment section UI and scroll-into-view.
|
||||
*/
|
||||
export class MiscModule implements MiscService {
|
||||
private siteTime: ReturnType<typeof setInterval> | undefined
|
||||
#siteTime: ReturnType<typeof setInterval> | undefined
|
||||
#core: CoreService
|
||||
|
||||
constructor(private readonly core: CoreService) {}
|
||||
constructor(core: CoreService) {
|
||||
this.#core = core
|
||||
}
|
||||
|
||||
/** Calculate and display the elapsed time since site launch. */
|
||||
getSiteTime() {
|
||||
const now = new Date()
|
||||
const run = new Date(this.core.config.siteTime!)
|
||||
const run = new Date(this.#core.config.siteTime!)
|
||||
const $runTimes = document.querySelector<HTMLElement>('.run-times')
|
||||
if (!isValidDate(run) || !$runTimes) {
|
||||
clearInterval(this.siteTime)
|
||||
clearInterval(this.#siteTime)
|
||||
$runTimes && $runTimes.parentNode!.removeChild($runTimes)
|
||||
return
|
||||
}
|
||||
@@ -38,20 +41,20 @@ export class MiscModule implements MiscService {
|
||||
|
||||
/** Start the site-time counter with visibility-change pausing. */
|
||||
initSiteTime() {
|
||||
if (this.core.config.siteTime) {
|
||||
this.siteTime = setInterval(() => this.getSiteTime(), 500)
|
||||
if (this.#core.config.siteTime) {
|
||||
this.#siteTime = setInterval(() => this.getSiteTime(), 500)
|
||||
document.addEventListener('visibilitychange', () => {
|
||||
if (document.hidden) {
|
||||
return clearInterval(this.siteTime)
|
||||
return clearInterval(this.#siteTime)
|
||||
}
|
||||
this.siteTime = setInterval(() => this.getSiteTime(), 500)
|
||||
this.#siteTime = setInterval(() => this.getSiteTime(), 500)
|
||||
}, false)
|
||||
}
|
||||
}
|
||||
|
||||
/** Save and restore scroll position as an automatic bookmark. */
|
||||
initAutoMark() {
|
||||
if (!this.core.config.autoBookmark)
|
||||
if (!this.#core.config.autoBookmark)
|
||||
return
|
||||
window.addEventListener('beforeunload', () => {
|
||||
window.sessionStorage?.setItem(`fixit-bookmark/#${location.pathname}`, String(getScrollTop()))
|
||||
@@ -89,7 +92,7 @@ export class MiscModule implements MiscService {
|
||||
|
||||
/** Initialize the comment section UI. */
|
||||
initComment() {
|
||||
if (!this.core.config.comment?.enable)
|
||||
if (!this.#core.config.comment?.enable)
|
||||
return
|
||||
|
||||
if (document.querySelector('#comments')) {
|
||||
@@ -100,26 +103,43 @@ export class MiscModule implements MiscService {
|
||||
}, false)
|
||||
}
|
||||
|
||||
if (this.core.config.comment.expired)
|
||||
if (this.#core.config.comment.expired)
|
||||
document.querySelector('#comments')!.remove()
|
||||
}
|
||||
|
||||
/** Initialize PostChat theme sync if configured. */
|
||||
initPostChatUser() {
|
||||
if (!window.postChatUser || !window.postChatConfig || window.postChatConfig.userMode === 'magic')
|
||||
return
|
||||
window.postChat_theme = this.core.isDark ? 'dark' : 'light'
|
||||
eventBus.on('fixit:switch-theme', ({ detail }) => {
|
||||
if (!detail.isChanged)
|
||||
/**
|
||||
* Initialize PostChat and postSummary for HongMoAI
|
||||
*/
|
||||
initPostChat() {
|
||||
const initThemeCompatibility = () => {
|
||||
if (this.#core.config.postChat) {
|
||||
document.body.classList.toggle('dark', this.#core.isDark)
|
||||
eventBus.on('fixit:switch-theme', ({ detail }) => {
|
||||
if (!detail.isChanged)
|
||||
return
|
||||
document.body.classList.toggle('dark', detail.isDark)
|
||||
})
|
||||
}
|
||||
}
|
||||
/** Initialize PostChat theme sync if configured. */
|
||||
const initPostChatUser = () => {
|
||||
if (!window.postChatUser || !window.postChatConfig || window.postChatConfig.userMode === 'magic')
|
||||
return
|
||||
const targetFrame = document.getElementById('postChat_iframeContainer')
|
||||
if (targetFrame) {
|
||||
window.postChatUser.setPostChatTheme(detail.isDark ? 'dark' : 'light')
|
||||
}
|
||||
else {
|
||||
window.postChat_theme = detail.isDark ? 'dark' : 'light'
|
||||
}
|
||||
})
|
||||
window.postChat_theme = this.#core.isDark ? 'dark' : 'light'
|
||||
eventBus.on('fixit:switch-theme', ({ detail }) => {
|
||||
if (!detail.isChanged)
|
||||
return
|
||||
const targetFrame = document.getElementById('postChat_iframeContainer')
|
||||
if (targetFrame) {
|
||||
window.postChatUser.setPostChatTheme(detail.isDark ? 'dark' : 'light')
|
||||
}
|
||||
else {
|
||||
window.postChat_theme = detail.isDark ? 'dark' : 'light'
|
||||
}
|
||||
})
|
||||
}
|
||||
initThemeCompatibility()
|
||||
initPostChatUser()
|
||||
}
|
||||
|
||||
/** Initialize all miscellaneous features. */
|
||||
@@ -127,7 +147,7 @@ export class MiscModule implements MiscService {
|
||||
this.initSiteTime()
|
||||
this.initAutoMark()
|
||||
this.initReward()
|
||||
this.initPostChatUser()
|
||||
this.initComment()
|
||||
this.initPostChat()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,11 +10,15 @@ import { eventBus } from '../core/event-bus'
|
||||
* - Show update notification toast and handle refresh.
|
||||
*/
|
||||
export class PWAModule implements PWAService {
|
||||
constructor(private readonly core: CoreService) {}
|
||||
#core: CoreService
|
||||
|
||||
constructor(core: CoreService) {
|
||||
this.#core = core
|
||||
}
|
||||
|
||||
/** Register the service worker with update detection and notification binding. */
|
||||
async setup() {
|
||||
const pwa = this.core.config.PWA
|
||||
const pwa = this.#core.config.PWA
|
||||
if (!pwa?.enable || !('serviceWorker' in navigator))
|
||||
return
|
||||
|
||||
@@ -49,14 +53,14 @@ export class PWAModule implements PWAService {
|
||||
window.location.reload()
|
||||
})
|
||||
|
||||
this.bindUpdateNotification(registration)
|
||||
this.#bindUpdateNotification(registration)
|
||||
}
|
||||
|
||||
/**
|
||||
* Bind the template-rendered update notification toast.
|
||||
* Shows the toast on `fixit:sw-update` and wires the refresh button to skip waiting.
|
||||
*/
|
||||
private bindUpdateNotification(registration: ServiceWorkerRegistration) {
|
||||
#bindUpdateNotification(registration: ServiceWorkerRegistration) {
|
||||
const toast = document.querySelector<HTMLElement>('.sw-update-notification')
|
||||
if (!toast)
|
||||
return
|
||||
|
||||
@@ -19,8 +19,11 @@ export class SearchModule implements SearchService {
|
||||
#openDialog: (() => void) | undefined
|
||||
#closeDialog: (() => void) | undefined
|
||||
#initialized = false
|
||||
#core: CoreService
|
||||
|
||||
constructor(private readonly core: CoreService) {}
|
||||
constructor(core: CoreService) {
|
||||
this.#core = core
|
||||
}
|
||||
|
||||
/** Create the appropriate search engine based on type. */
|
||||
#createEngine(type: string, searchConfig: SearchConfig): SearchEngine {
|
||||
@@ -30,9 +33,9 @@ export class SearchModule implements SearchService {
|
||||
case 'fuse':
|
||||
return createFuseEngine(searchConfig)
|
||||
case 'cse':
|
||||
return createCSEEngine(this.core.config.cse)
|
||||
return createCSEEngine(this.#core.config.cse)
|
||||
case 'pagefind':
|
||||
return createPagefindEngine(searchConfig, this.core.config.pagefind!)
|
||||
return createPagefindEngine(searchConfig, this.#core.config.pagefind!)
|
||||
default:
|
||||
console.warn(`[FixIt] Unknown search type: "${type}". Supported types: algolia, fuse, cse, pagefind.`)
|
||||
return {
|
||||
@@ -45,7 +48,7 @@ export class SearchModule implements SearchService {
|
||||
|
||||
/** Initialize @algolia/autocomplete-js instance. */
|
||||
#initAutosearch() {
|
||||
const searchConfig = this.core.config.search
|
||||
const searchConfig = this.#core.config.search
|
||||
if (!searchConfig || !this.#engine)
|
||||
return
|
||||
|
||||
@@ -218,7 +221,7 @@ export class SearchModule implements SearchService {
|
||||
|
||||
document.querySelector('.search-trigger.desktop')?.addEventListener('click', open)
|
||||
document.querySelector('.search-trigger.mobile')?.addEventListener('click', () => {
|
||||
this.core.closeMaskOverlay('menu-mobile')
|
||||
this.#core.closeMaskOverlay('menu-mobile')
|
||||
open()
|
||||
})
|
||||
|
||||
@@ -247,7 +250,7 @@ export class SearchModule implements SearchService {
|
||||
|
||||
/** Initialize the search overlay, autocomplete, and engine-specific logic. */
|
||||
setup() {
|
||||
const searchConfig = this.core.config.search
|
||||
const searchConfig = this.#core.config.search
|
||||
if (!searchConfig || !searchConfig.type)
|
||||
return
|
||||
|
||||
|
||||
+18
-15
@@ -10,9 +10,12 @@ import { eventBus } from '../core/event-bus'
|
||||
* - Persist user preference to localStorage.
|
||||
*/
|
||||
export class ThemeModule implements ThemeService {
|
||||
private readonly mql = window.matchMedia('(prefers-color-scheme: dark)')
|
||||
#mql = window.matchMedia('(prefers-color-scheme: dark)')
|
||||
#core: CoreService
|
||||
|
||||
constructor(private readonly core: CoreService) {}
|
||||
constructor(core: CoreService) {
|
||||
this.#core = core
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply a theme mode and emit the `fixit:switch-theme` event.
|
||||
@@ -20,19 +23,19 @@ export class ThemeModule implements ThemeService {
|
||||
* @param persist - Whether to save the choice to localStorage (default: `true`).
|
||||
*/
|
||||
setThemeMode(mode: string, persist = true) {
|
||||
const prevIsDark = this.core.isDark
|
||||
this.core.themeMode = mode
|
||||
const prevIsDark = this.#core.isDark
|
||||
this.#core.themeMode = mode
|
||||
document.documentElement.dataset.themeMode = mode
|
||||
this.core.isDark = mode === 'auto' ? this.mql.matches : mode === 'dark'
|
||||
this.#core.isDark = mode === 'auto' ? this.#mql.matches : mode === 'dark'
|
||||
|
||||
if (persist) {
|
||||
window.localStorage?.setItem('theme-mode', mode)
|
||||
}
|
||||
|
||||
eventBus.emit('fixit:switch-theme', {
|
||||
isDark: this.core.isDark,
|
||||
isDark: this.#core.isDark,
|
||||
mode,
|
||||
isChanged: prevIsDark !== this.core.isDark,
|
||||
isChanged: prevIsDark !== this.#core.isDark,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -49,7 +52,7 @@ export class ThemeModule implements ThemeService {
|
||||
return
|
||||
applyThemeColor(detail.isDark)
|
||||
})
|
||||
applyThemeColor(this.core.isDark)
|
||||
applyThemeColor(this.#core.isDark)
|
||||
}
|
||||
|
||||
/** Initialize the theme switch button cycle and system preference listener. */
|
||||
@@ -58,21 +61,21 @@ export class ThemeModule implements ThemeService {
|
||||
|
||||
document.querySelectorAll('.theme-switch').forEach(($themeSwitch: Element) => {
|
||||
$themeSwitch.addEventListener('click', () => {
|
||||
const currentIndex = modes.indexOf(this.core.themeMode as typeof modes[number])
|
||||
const currentIndex = modes.indexOf(this.#core.themeMode as typeof modes[number])
|
||||
const nextMode = modes[(currentIndex + 1) % modes.length]
|
||||
this.setThemeMode(nextMode)
|
||||
}, false)
|
||||
})
|
||||
|
||||
this.mql.addEventListener('change', (e: MediaQueryListEvent) => {
|
||||
if (this.core.themeMode !== 'auto')
|
||||
this.#mql.addEventListener('change', (e: MediaQueryListEvent) => {
|
||||
if (this.#core.themeMode !== 'auto')
|
||||
return
|
||||
const prevIsDark = this.core.isDark
|
||||
this.core.isDark = e.matches
|
||||
const prevIsDark = this.#core.isDark
|
||||
this.#core.isDark = e.matches
|
||||
eventBus.emit('fixit:switch-theme', {
|
||||
isDark: this.core.isDark,
|
||||
isDark: this.#core.isDark,
|
||||
mode: 'auto',
|
||||
isChanged: prevIsDark !== this.core.isDark,
|
||||
isChanged: prevIsDark !== this.#core.isDark,
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
+84
-88
@@ -1,59 +1,50 @@
|
||||
import type { TocService } from '../core/tokens'
|
||||
import { eventBus } from '../core/event-bus'
|
||||
import { animateCSS, isTocStatic } from '../utils'
|
||||
import { animateCSS, isMobile, isTocStatic } from '../utils'
|
||||
|
||||
const TOC_CONTAINER_IDS = ['toc-content-auto', 'toc-content-static', 'toc-content-drawer'] as const
|
||||
|
||||
/**
|
||||
* Table of Contents module — TOC scroll tracking, active state sync, and dialog.
|
||||
*
|
||||
* Responsibilities:
|
||||
* - Render TOC from template and sync layout state.
|
||||
* - Clear TOC containers on `fixit:re-encrypt`.
|
||||
* - Move TOC node to the correct container (static, auto, or drawer) on init.
|
||||
* - Track scroll position and highlight the active heading in all TOC containers.
|
||||
* - Initialize mobile TOC drawer dialog and its open/close handlers.
|
||||
* - Clone TOC nodes to detach APlayer event listeners.
|
||||
*/
|
||||
export class TocModule implements TocService {
|
||||
private activeTocId: string | null = null
|
||||
#activeTocId: string | null = null
|
||||
|
||||
/** Get the pixel height of the currently visible sticky header. */
|
||||
getVisibleHeaderOffset(): number {
|
||||
const $desktopHeader = document.getElementById('header-desktop')
|
||||
const $mobileHeader = document.getElementById('header-mobile')
|
||||
const $header = [$desktopHeader, $mobileHeader].find($el => $el && window.getComputedStyle($el).display !== 'none')
|
||||
if (!$header)
|
||||
return 0
|
||||
const isDesktop = $header.id === 'header-desktop'
|
||||
const headerMode = isDesktop ? document.body.dataset.headerDesktop : document.body.dataset.headerMobile
|
||||
if (!['sticky', 'auto'].includes(headerMode!))
|
||||
return 0
|
||||
if (headerMode === 'auto' && $header.classList.contains('header__fadeOutUp'))
|
||||
return 0
|
||||
return $header.offsetHeight
|
||||
}
|
||||
|
||||
/** Get the pixel height of the breadcrumb container. */
|
||||
getBreadcrumbHeight(): number {
|
||||
return document.querySelector<HTMLElement>('.breadcrumb-container')?.offsetHeight || 0
|
||||
}
|
||||
|
||||
/** Get the combined vertical offset used to determine the active TOC heading. */
|
||||
getTocIndexOffset(): number {
|
||||
return 20 + this.getVisibleHeaderOffset() + this.getBreadcrumbHeight()
|
||||
}
|
||||
|
||||
/** Get all heading elements that have an `id` attribute. */
|
||||
getTocHeadingElements(): HTMLElement[] {
|
||||
return Array.from(document.querySelectorAll<HTMLElement>('.heading-element[id]'))
|
||||
/** Get all TOC content containers (auto, static, and drawer). */
|
||||
#getTocContainers(): HTMLElement[] {
|
||||
return TOC_CONTAINER_IDS
|
||||
.map(id => document.getElementById(id))
|
||||
.filter(Boolean) as HTMLElement[]
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine which heading is currently active based on scroll position.
|
||||
* @param $headingElements - Array of heading elements with `id` attributes.
|
||||
* @param indexOffset - Vertical offset from the top for the active threshold.
|
||||
* @returns The active heading element, or `null` if none found.
|
||||
*/
|
||||
getActiveTocHeading($headingElements: HTMLElement[], indexOffset = this.getTocIndexOffset()): HTMLElement | null {
|
||||
#getActiveTocHeading(): HTMLElement | null {
|
||||
const $headingElements = Array.from(document.querySelectorAll<HTMLElement>('.heading-element[id]'))
|
||||
if (!$headingElements.length)
|
||||
return null
|
||||
const headerOffset = (() => {
|
||||
const headerId = isMobile() ? 'header-mobile' : 'header-desktop'
|
||||
const $header = document.getElementById(headerId)
|
||||
const headerMode = document.body.getAttribute(`data-${headerId}`)
|
||||
if (!$header || window.getComputedStyle($header).display === 'none')
|
||||
return 0
|
||||
const shouldApplyOffset = headerMode === 'sticky' || (headerMode === 'auto' && !$header.classList.contains('header__fadeOutUp'))
|
||||
return shouldApplyOffset ? $header.offsetHeight : 0
|
||||
})()
|
||||
const breadcrumbOffset = document.querySelector<HTMLElement>('.breadcrumb-container')?.offsetHeight || 0
|
||||
const indexOffset = 20 + headerOffset + breadcrumbOffset
|
||||
|
||||
const threshold = window.scrollY + indexOffset + 1
|
||||
let $activeHeading = $headingElements[0]
|
||||
for (const $heading of $headingElements) {
|
||||
@@ -68,22 +59,13 @@ export class TocModule implements TocService {
|
||||
return $activeHeading
|
||||
}
|
||||
|
||||
/** Get all TOC root containers (static, auto, and drawer). */
|
||||
getTocRoots(): HTMLElement[] {
|
||||
return [
|
||||
document.querySelector<HTMLElement>('#toc-content-auto > nav'),
|
||||
document.querySelector<HTMLElement>('#toc-content-static > nav'),
|
||||
document.querySelector<HTMLElement>('#toc-content-drawer > nav'),
|
||||
].filter(Boolean) as HTMLElement[]
|
||||
}
|
||||
|
||||
/**
|
||||
* Find the TOC link that points to the given heading id.
|
||||
* @param $tocRoot - The TOC root container element.
|
||||
* @param id - The heading id (without `#`).
|
||||
* @returns The matching anchor element, or `null`.
|
||||
*/
|
||||
getTocLinkById($tocRoot: HTMLElement, id: string): HTMLAnchorElement | null {
|
||||
#getTocLinkById($tocRoot: HTMLElement, id: string): HTMLAnchorElement | null {
|
||||
if (!$tocRoot || !id)
|
||||
return null
|
||||
const targetHash = `#${id}`
|
||||
@@ -95,7 +77,7 @@ export class TocModule implements TocService {
|
||||
* @param $tocRoot - The TOC root container element.
|
||||
* @param activeId - The id of the currently active heading.
|
||||
*/
|
||||
applyTocActiveState($tocRoot: HTMLElement, activeId: string) {
|
||||
#applyTocActiveState($tocRoot: HTMLElement, activeId: string) {
|
||||
if (!$tocRoot)
|
||||
return
|
||||
$tocRoot.querySelectorAll('a[href^="#"]').forEach(($tocLink: Element) => {
|
||||
@@ -104,7 +86,7 @@ export class TocModule implements TocService {
|
||||
$tocRoot.querySelectorAll('li').forEach(($tocLi: Element) => {
|
||||
$tocLi.classList.remove('has-active')
|
||||
})
|
||||
const $activeLink = this.getTocLinkById($tocRoot, activeId)
|
||||
const $activeLink = this.#getTocLinkById($tocRoot, activeId)
|
||||
if (!$activeLink)
|
||||
return
|
||||
$activeLink.classList.add('active')
|
||||
@@ -121,8 +103,8 @@ export class TocModule implements TocService {
|
||||
* @param activeId - The id of the currently active heading.
|
||||
* @param $scrollContainer - The scrollable container (defaults to `$tocRoot`).
|
||||
*/
|
||||
scrollActiveTocLinkIntoView($tocRoot: HTMLElement, activeId: string, $scrollContainer: HTMLElement = $tocRoot) {
|
||||
const $activeLink = this.getTocLinkById($tocRoot, activeId)
|
||||
#scrollActiveTocLinkIntoView($tocRoot: HTMLElement, activeId: string, $scrollContainer: HTMLElement = $tocRoot) {
|
||||
const $activeLink = this.#getTocLinkById($tocRoot, activeId)
|
||||
if (!$activeLink || !$scrollContainer)
|
||||
return
|
||||
const containerRect = $scrollContainer.getBoundingClientRect()
|
||||
@@ -149,60 +131,58 @@ export class TocModule implements TocService {
|
||||
|
||||
/** Sync the active heading highlight across all TOC containers. */
|
||||
syncTocActiveState() {
|
||||
const $headingElements = this.getTocHeadingElements()
|
||||
const $activeHeading = this.getActiveTocHeading($headingElements)
|
||||
const $activeHeading = this.#getActiveTocHeading()
|
||||
if (!$activeHeading?.id)
|
||||
return
|
||||
const activeId = $activeHeading.id
|
||||
const $tocRoots = this.getTocRoots()
|
||||
const $tocRoots = this.#getTocContainers()
|
||||
.map($container => $container.querySelector<HTMLElement>('nav'))
|
||||
.filter(Boolean) as HTMLElement[]
|
||||
$tocRoots.forEach(($tocRoot) => {
|
||||
this.applyTocActiveState($tocRoot, activeId)
|
||||
this.#applyTocActiveState($tocRoot, activeId)
|
||||
})
|
||||
if (this.activeTocId !== activeId) {
|
||||
this.activeTocId = activeId
|
||||
if (this.#activeTocId !== activeId) {
|
||||
this.#activeTocId = activeId
|
||||
if (!isTocStatic()) {
|
||||
const $autoTocRoot = document.querySelector<HTMLElement>('#toc-content-auto > nav')
|
||||
const $autoTocContainer = document.getElementById('toc-content-auto')
|
||||
if ($autoTocRoot && $autoTocContainer) {
|
||||
this.scrollActiveTocLinkIntoView($autoTocRoot, activeId, $autoTocContainer)
|
||||
this.#scrollActiveTocLinkIntoView($autoTocRoot, activeId, $autoTocContainer)
|
||||
}
|
||||
}
|
||||
if ((document.getElementById('toc-dialog') as HTMLDialogElement)?.open) {
|
||||
const $dialogTocRoot = document.querySelector<HTMLElement>('#toc-content-drawer > nav')!
|
||||
this.scrollActiveTocLinkIntoView($dialogTocRoot, activeId, $dialogTocRoot)
|
||||
this.#scrollActiveTocLinkIntoView($dialogTocRoot, activeId, $dialogTocRoot)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** Sync TOC layout state: drawer button visibility, height, and active heading. */
|
||||
syncTocLayout() {
|
||||
#syncTocLayout() {
|
||||
document.querySelector<HTMLElement>('#toc-drawer-button')?.classList.toggle('hidden', !isTocStatic())
|
||||
this.activeTocId = null
|
||||
this.#activeTocId = null
|
||||
this.syncTocHeight()
|
||||
this.syncTocActiveState()
|
||||
}
|
||||
|
||||
/** Initialize TOC layout: read from `<template data-toc>`, copy to the correct container and dialog. */
|
||||
initToc() {
|
||||
/** Render TOC from `<template data-toc>` into static/auto/drawer containers. */
|
||||
renderToc() {
|
||||
const $tocTemplate = document.querySelector<HTMLTemplateElement>('template[data-toc]')
|
||||
if ($tocTemplate?.dataset.password)
|
||||
return
|
||||
const $tocCore = $tocTemplate?.content.querySelector('#TableOfContents')
|
||||
if (!$tocTemplate || !$tocCore)
|
||||
return
|
||||
|
||||
// Copy TOC to target containers
|
||||
const targets = ['toc-content-drawer', 'toc-content-static', 'toc-content-auto']
|
||||
for (const id of targets) {
|
||||
const $container = document.getElementById(id)
|
||||
if ($container && !$container.querySelector('nav')) {
|
||||
const $clone = $tocCore.cloneNode(true) as HTMLElement
|
||||
$clone.removeAttribute('id')
|
||||
$container.appendChild($clone)
|
||||
}
|
||||
for (const $container of this.#getTocContainers()) {
|
||||
$container.textContent = ''
|
||||
const $clone = $tocCore.cloneNode(true) as HTMLElement
|
||||
$clone.removeAttribute('id')
|
||||
$container.appendChild($clone)
|
||||
}
|
||||
}
|
||||
|
||||
/** Bind the TOC title click handler for show/hide toggle. */
|
||||
initTocListener() {
|
||||
/** Bind the `toc-auto` title click handler for show/hide toggle. */
|
||||
#initTocToggle() {
|
||||
const $toc = document.getElementById('toc-auto')!
|
||||
const $tocContentAuto = document.getElementById('toc-content-auto')!
|
||||
document.querySelector<HTMLElement>('#toc-auto>.toc-title')?.addEventListener('click', () => {
|
||||
@@ -223,7 +203,20 @@ export class TocModule implements TocService {
|
||||
}
|
||||
|
||||
/** Initialize the mobile TOC drawer dialog and its open/close handlers. */
|
||||
initTocDialog() {
|
||||
#initTocDrawerLinkClose() {
|
||||
const dialog = document.querySelector<HTMLDialogElement>('#toc-dialog')
|
||||
if (!dialog)
|
||||
return
|
||||
document.querySelectorAll<HTMLAnchorElement>('#toc-content-drawer a[href^="#"]').forEach(($link) => {
|
||||
if ($link.dataset.tocDialogCloseBound === 'true')
|
||||
return
|
||||
$link.addEventListener('click', () => dialog.close())
|
||||
$link.dataset.tocDialogCloseBound = 'true'
|
||||
})
|
||||
}
|
||||
|
||||
/** Initialize the mobile TOC drawer dialog and its open/close handlers. */
|
||||
#initTocDrawer() {
|
||||
const dialog = document.querySelector<HTMLDialogElement>('#toc-dialog')
|
||||
const openButton = document.querySelector<HTMLElement>('#toc-drawer-button')
|
||||
if (!dialog || !openButton)
|
||||
@@ -236,19 +229,16 @@ export class TocModule implements TocService {
|
||||
this.syncTocHeight()
|
||||
this.syncTocActiveState()
|
||||
const $dialogTocRoot = document.querySelector<HTMLElement>('#toc-content-drawer > nav')!
|
||||
this.scrollActiveTocLinkIntoView($dialogTocRoot, this.activeTocId!, $dialogTocRoot)
|
||||
this.#scrollActiveTocLinkIntoView($dialogTocRoot, this.#activeTocId!, $dialogTocRoot)
|
||||
;(document.activeElement as HTMLElement)?.blur()
|
||||
})
|
||||
document.querySelectorAll<HTMLAnchorElement>('#toc-content-drawer a[href^="#"]').forEach(($link) => {
|
||||
$link.addEventListener('click', () => dialog.close())
|
||||
})
|
||||
dialog.addEventListener('close', () => {
|
||||
openButton.setAttribute('aria-expanded', 'false')
|
||||
})
|
||||
}
|
||||
|
||||
/** Clone heading-mark nodes to detach APlayer event listeners. */
|
||||
fixTocScroll() {
|
||||
/** Reset heading-mark nodes to fix the APlayer-caused anchor click issue. */
|
||||
#resetHeadingClicks() {
|
||||
if (typeof window.APlayer === 'function') {
|
||||
document.querySelectorAll('.heading-mark').forEach(($headingMark: Element) => {
|
||||
const $newHeadingMark = $headingMark.cloneNode(true)
|
||||
@@ -259,16 +249,22 @@ export class TocModule implements TocService {
|
||||
|
||||
/** Initialize all TOC components and register event listeners. */
|
||||
setup() {
|
||||
this.initToc()
|
||||
this.syncTocLayout()
|
||||
this.initTocListener()
|
||||
this.initTocDialog()
|
||||
this.fixTocScroll()
|
||||
eventBus.on('fixit:resize', () => this.syncTocLayout())
|
||||
this.renderToc()
|
||||
this.#initTocToggle()
|
||||
this.#syncTocLayout()
|
||||
this.#initTocDrawer()
|
||||
this.#initTocDrawerLinkClose()
|
||||
this.#resetHeadingClicks()
|
||||
eventBus.on('fixit:resize', () => this.#syncTocLayout())
|
||||
eventBus.on('fixit:scroll', () => this.syncTocActiveState())
|
||||
eventBus.on('fixit:decrypted', () => {
|
||||
this.initToc()
|
||||
this.syncTocLayout()
|
||||
eventBus.on('fixit:content-decrypted', ({ detail }) => {
|
||||
if (detail.isPage) {
|
||||
this.#syncTocLayout()
|
||||
this.#initTocDrawerLinkClose()
|
||||
}
|
||||
})
|
||||
eventBus.on('fixit:re-encrypt', () => {
|
||||
this.#syncTocLayout()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -31,6 +31,7 @@ export interface FixItConfig {
|
||||
autoBookmark?: boolean
|
||||
encryption?: EncryptionConfig
|
||||
print?: PrintConfig
|
||||
postChat?: boolean
|
||||
}
|
||||
|
||||
export interface EchartsConfig {
|
||||
|
||||
Vendored
+8
@@ -0,0 +1,8 @@
|
||||
/**
|
||||
* Hugo `@params` type declarations
|
||||
* This file provides TypeScript type definitions for Hugo's `@params` module
|
||||
*/
|
||||
|
||||
declare module '@params' {
|
||||
export const defaultTheme: string
|
||||
}
|
||||
@@ -1,3 +1,11 @@
|
||||
/**
|
||||
* Check whether the document direction is right-to-left.
|
||||
* @returns `true` if the document is RTL.
|
||||
*/
|
||||
export function isRTL(): boolean {
|
||||
return document.documentElement.dir === 'rtl'
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the current vertical scroll position.
|
||||
* @returns The scroll offset in pixels.
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -8,6 +8,14 @@
|
||||
@forward "hugo:vars";
|
||||
@forward "hugo:vars/internal";
|
||||
|
||||
/// CSS variable prefix for all custom properties.
|
||||
/// @type String
|
||||
$prefix: fi- !default;
|
||||
|
||||
/// Root prefix for CSS custom properties (derived from $prefix).
|
||||
/// @type String
|
||||
$rootPrefix: --#{$prefix} !default;
|
||||
|
||||
/// Default header height.
|
||||
/// @type Length
|
||||
$header-height: 3.5rem !default;
|
||||
|
||||
@@ -1,3 +0,0 @@
|
||||
@charset "utf-8";
|
||||
|
||||
@use "pages/archives";
|
||||
@@ -7,7 +7,7 @@
|
||||
padding: 0.5rem 1rem;
|
||||
margin-block: 1rem;
|
||||
color: inherit;
|
||||
border-left: 0.25em solid fi-var(alert-border-color);
|
||||
border-inline-start: 0.25em solid fi-var(alert-border-color);
|
||||
|
||||
> :first-child {
|
||||
margin-top: 0;
|
||||
@@ -22,13 +22,13 @@
|
||||
display: inline-block;
|
||||
width: fi-var(color-code-size);
|
||||
height: fi-var(color-code-size);
|
||||
@include border-radius(full);
|
||||
margin-right: 0.25em;
|
||||
margin-inline-end: 0.25em;
|
||||
vertical-align: middle;
|
||||
border: 1px solid;
|
||||
pointer-events: none;
|
||||
translate: 0 -1px;
|
||||
border-color: light-dark(#d1d9e0b3, #3d444db3);
|
||||
@include border-radius(full);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -42,6 +42,7 @@
|
||||
font-family: fi-var(code-font-family);
|
||||
background-color: fi-var(code-block-background-color);
|
||||
tab-size: 4;
|
||||
direction: ltr;
|
||||
@include border-radius;
|
||||
|
||||
+ pre {
|
||||
@@ -91,6 +92,7 @@
|
||||
.highlight {
|
||||
margin: 0.5rem 0;
|
||||
position: relative;
|
||||
direction: ltr;
|
||||
@include border-radius;
|
||||
|
||||
// lineNumbersInTable=true
|
||||
@@ -161,7 +163,7 @@
|
||||
|
||||
&:not(.code-block) {
|
||||
.lntd:last-child pre.chroma {
|
||||
padding-left: 0;
|
||||
padding-inline-start: 0;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -224,6 +226,10 @@
|
||||
@include border-radius(top);
|
||||
@include sticky-top-offset;
|
||||
|
||||
[dir="rtl"] & {
|
||||
direction: rtl;
|
||||
}
|
||||
|
||||
+ .code-wrapper {
|
||||
@include border-radius(bottom);
|
||||
|
||||
@@ -237,7 +243,7 @@
|
||||
padding: 0.4rem;
|
||||
|
||||
&::after {
|
||||
padding-left: 0.2rem;
|
||||
padding-inline-start: 0.2rem;
|
||||
content: attr(data-name, fi-var(code-type, 'Code'));
|
||||
}
|
||||
|
||||
@@ -366,7 +372,7 @@
|
||||
.copy-icon-btn {
|
||||
position: absolute;
|
||||
top: 0.5rem;
|
||||
right: 0.5rem;
|
||||
inset-inline-end: 0.5rem;
|
||||
}
|
||||
|
||||
// classic mode code block collapsed
|
||||
@@ -483,7 +489,7 @@
|
||||
counter-increment: codeNos;
|
||||
content: counter(codeNos);
|
||||
white-space: nowrap;
|
||||
text-align: right;
|
||||
text-align: end;
|
||||
width: calc(#{fi-var(line-digit)} * 1ch);
|
||||
min-width: 0.75rem;
|
||||
padding-inline: 0.75rem;
|
||||
@@ -509,7 +515,7 @@
|
||||
&:not(.line-nos-hidden) {
|
||||
.lntd:last-child pre.chroma:not([contenteditable='true']),
|
||||
.code-wrapper > pre.chroma:not(:has(.ln)) {
|
||||
padding-left: 0;
|
||||
padding-inline-start: 0;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -616,7 +622,7 @@
|
||||
&:not(:last-child)::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
right: 0;
|
||||
inset-inline-end: 0;
|
||||
top: 50%;
|
||||
translate: 0 -50%;
|
||||
width: 1px;
|
||||
@@ -653,6 +659,10 @@
|
||||
pointer-events: none;
|
||||
transition: 0.3s ease-in-out;
|
||||
@include border-radius;
|
||||
|
||||
[dir="rtl"] & {
|
||||
translate: -2px 2px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,14 +15,14 @@
|
||||
display: inline-block;
|
||||
width: max-content;
|
||||
rotate: 30deg;
|
||||
margin-right: 0.25em;
|
||||
margin-inline-end: 0.25em;
|
||||
color: fi-var(danger);
|
||||
}
|
||||
|
||||
.icon-repost {
|
||||
display: inline-block;
|
||||
width: max-content;
|
||||
margin-right: 0.25em;
|
||||
margin-inline-end: 0.25em;
|
||||
color: fi-var(success);
|
||||
}
|
||||
|
||||
@@ -116,21 +116,25 @@
|
||||
text-rendering: auto;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
position: absolute;
|
||||
left: 5%;
|
||||
inset-inline-start: 5%;
|
||||
top: 50%;
|
||||
translate: 0 -50%;
|
||||
color: fi-var(hr-active-color);
|
||||
font-size: 20px;
|
||||
line-height: 1;
|
||||
transition: left 1s ease-in-out, color 0.3s ease, border-color 0.3s ease;
|
||||
transition: inset-inline-start 1s ease-in-out, color 0.3s ease, border-color 0.3s ease;
|
||||
font-family: var(--fa-family-classic);
|
||||
font-weight: 600;
|
||||
content: '\f0c4';
|
||||
@include z-index(base);
|
||||
|
||||
[dir="rtl"] & {
|
||||
scale: -1 1;
|
||||
}
|
||||
}
|
||||
|
||||
&:hover::before {
|
||||
left: calc(95% - 20px);
|
||||
inset-inline-start: calc(95% - 20px);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -217,7 +221,7 @@
|
||||
}
|
||||
|
||||
:is(ol, ul) :is(ol, ul) {
|
||||
padding-left: 20px;
|
||||
padding-inline-start: 20px;
|
||||
}
|
||||
|
||||
li::marker {
|
||||
@@ -232,7 +236,8 @@
|
||||
&[data-task] {
|
||||
list-style: none;
|
||||
@include task-icon {
|
||||
margin: 0 0.25em 0 -1.5em;
|
||||
margin-block: 0;
|
||||
margin-inline: -1.5em 0.25em;
|
||||
color: fi-var(task-icon-color, fi-var(secondary));
|
||||
};
|
||||
@include task-text {
|
||||
@@ -343,12 +348,12 @@
|
||||
th,
|
||||
td {
|
||||
padding: 0.3rem 1rem;
|
||||
border-right: 1px solid fi-var(table-border-color);
|
||||
border-inline-end: 1px solid fi-var(table-border-color);
|
||||
border-bottom: 1px solid fi-var(table-border-color);
|
||||
}
|
||||
|
||||
tr > :last-child {
|
||||
border-right: none;
|
||||
border-inline-end: none;
|
||||
}
|
||||
|
||||
> :last-child > tr:last-child > * {
|
||||
@@ -392,14 +397,14 @@
|
||||
|
||||
blockquote {
|
||||
display: block;
|
||||
border-left: 0.25em solid fi-var(global-border-color);
|
||||
border-inline-start: 0.25em solid fi-var(global-border-color);
|
||||
color: fi-var(blockquote-color);
|
||||
padding: 0 1em;
|
||||
margin: 1rem 0;
|
||||
|
||||
ul,
|
||||
ol {
|
||||
padding-left: 1.5em;
|
||||
padding-inline-start: 1.5em;
|
||||
}
|
||||
|
||||
dl dd {
|
||||
@@ -427,7 +432,7 @@
|
||||
|
||||
ol {
|
||||
margin-block: 1em;
|
||||
padding-left: 1rem;
|
||||
padding-inline-start: 1rem;
|
||||
}
|
||||
|
||||
hr {
|
||||
@@ -465,6 +470,7 @@
|
||||
.diagram-container {
|
||||
position: relative;
|
||||
margin-block: 0.5rem;
|
||||
direction: ltr;
|
||||
|
||||
> .copy-icon-btn {
|
||||
position: absolute;
|
||||
@@ -480,12 +486,17 @@
|
||||
overflow-y: hidden;
|
||||
// neutralize the margin-right setting of .katex .vlist-t2
|
||||
// to prevent scrollbars from appearing when the width is not full
|
||||
padding-right: 2px;
|
||||
padding-inline-end: 2px;
|
||||
}
|
||||
.katex {
|
||||
display: inline-block;
|
||||
direction: ltr;
|
||||
}
|
||||
.katex-error {
|
||||
padding: 0.14em 0.28em;
|
||||
background-color: mark;
|
||||
cursor: help;
|
||||
direction: ltr;
|
||||
@include border-radius;
|
||||
}
|
||||
|
||||
@@ -526,7 +537,7 @@
|
||||
&.special {
|
||||
.single-title,
|
||||
.single-subtitle {
|
||||
text-align: right;
|
||||
text-align: end;
|
||||
}
|
||||
}
|
||||
}
|
||||
+5
-3
@@ -165,8 +165,9 @@ article fixit-encryptor {
|
||||
.fixit-decryptor-field {
|
||||
flex: 1;
|
||||
height: 2.5rem;
|
||||
border-right: none;
|
||||
border-radius: fi-var(global-border-radius) 0 0 fi-var(global-border-radius);
|
||||
border-inline-end: none;
|
||||
@include border-radius(start);
|
||||
@include border-radius(end, 0);
|
||||
|
||||
&:focus-within + .fixit-decryptor-btn {
|
||||
border-color: var(--fi-primary);
|
||||
@@ -177,8 +178,9 @@ article fixit-encryptor {
|
||||
.fixit-decryptor-btn {
|
||||
width: auto;
|
||||
padding: 0 0.85rem;
|
||||
border-radius: 0 fi-var(global-border-radius) fi-var(global-border-radius) 0;
|
||||
transition: border-color 0.2s ease, box-shadow 0.2s ease;
|
||||
@include border-radius(start, 0);
|
||||
@include border-radius(end);
|
||||
}
|
||||
|
||||
&.decrypted > .fixit-decryptor-container {
|
||||
@@ -1,7 +1,6 @@
|
||||
@forward "content";
|
||||
@forward "shortcodes";
|
||||
@forward "alert";
|
||||
@forward "code";
|
||||
@forward "content";
|
||||
@forward "fixit-decryptor";
|
||||
@forward "page";
|
||||
@forward "patch";
|
||||
+15
-6
@@ -8,10 +8,10 @@
|
||||
position: relative;
|
||||
margin: 1rem 0;
|
||||
padding: 0 0.75rem;
|
||||
border-left: .25rem solid;
|
||||
border-inline-start: .25rem solid;
|
||||
overflow: hidden;
|
||||
background-color: fi-var(admonition-bg-color);
|
||||
border-left-color: fi-var(admonition-color);
|
||||
border-inline-start-color: fi-var(admonition-color);
|
||||
@include border-radius;
|
||||
|
||||
.admonition-title {
|
||||
@@ -30,11 +30,12 @@
|
||||
// title-only admonition without content
|
||||
&:not(:has(+ .details-content)) {
|
||||
@include set-fi-var(admonition-title-font-weight, normal);
|
||||
padding: 0.75rem 1rem 0.75rem 2.2rem;
|
||||
padding-inline: 2.2rem 1rem;
|
||||
padding-block: 0.75rem;
|
||||
border: 0;
|
||||
|
||||
i.icon {
|
||||
left: 1.1rem;
|
||||
inset-inline-start: 1.1rem;
|
||||
font-size: 1rem;
|
||||
}
|
||||
}
|
||||
@@ -73,15 +74,23 @@
|
||||
font-size: 0.85rem;
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 0.9rem;
|
||||
inset-inline-start: 0.9rem;
|
||||
translate: -50% -50%;
|
||||
|
||||
[dir="rtl"] & {
|
||||
translate: 50% -50%;
|
||||
}
|
||||
}
|
||||
|
||||
i.details-icon {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
right: 0.9rem;
|
||||
inset-inline-end: 0.9rem;
|
||||
translate: 50% -50%;
|
||||
|
||||
[dir="rtl"] & {
|
||||
translate: -50% -50%;
|
||||
}
|
||||
}
|
||||
|
||||
// set color for each admonition type
|
||||
+7
-5
@@ -20,8 +20,8 @@
|
||||
position: absolute;
|
||||
top: 0.75rem;
|
||||
bottom: 0.75rem;
|
||||
left: 0;
|
||||
right: 0;
|
||||
inset-inline-start: 0;
|
||||
inset-inline-end: 0;
|
||||
background-image: fi-var(logo-img);
|
||||
background-repeat: no-repeat;
|
||||
filter: blur(0.5rem);
|
||||
@@ -67,20 +67,22 @@
|
||||
width: 1rem;
|
||||
height: 1rem;
|
||||
flex-shrink: 0;
|
||||
margin-right: 0.25rem;
|
||||
margin-inline-end: 0.25rem;
|
||||
}
|
||||
|
||||
&url {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
// URL direction should stay LTR
|
||||
direction: ltr;
|
||||
}
|
||||
|
||||
&shortcut-icon {
|
||||
width: 4rem;
|
||||
height: 4rem;
|
||||
flex-shrink: 0;
|
||||
margin-left: 0.25rem;
|
||||
margin-inline-start: 0.25rem;
|
||||
|
||||
&:is(i) {
|
||||
text-align: center;
|
||||
@@ -95,7 +97,7 @@
|
||||
width: 4rem;
|
||||
height: 4rem !important;
|
||||
flex-shrink: 0;
|
||||
margin-left: 0.25rem;
|
||||
margin-inline-start: 0.25rem;
|
||||
object-fit: cover;
|
||||
@include border-radius(full);
|
||||
}
|
||||
+12
-4
@@ -1,7 +1,7 @@
|
||||
@use "core/functions" as *;
|
||||
|
||||
.single blockquote.blockquote-center {
|
||||
border-left: none;
|
||||
border-inline-start: none;
|
||||
margin: 40px 0;
|
||||
padding: 0;
|
||||
position: relative;
|
||||
@@ -10,7 +10,7 @@
|
||||
|
||||
&::after,
|
||||
&::before {
|
||||
left: 0;
|
||||
inset-inline-start: 0;
|
||||
line-height: 1;
|
||||
opacity: 0.6;
|
||||
position: absolute;
|
||||
@@ -19,19 +19,27 @@
|
||||
|
||||
&::before {
|
||||
border-top: 1px solid fi-var(global-border-color);
|
||||
text-align: left;
|
||||
text-align: start;
|
||||
top: -20px;
|
||||
font-family: var(--fa-family-classic);
|
||||
font-weight: 900;
|
||||
content: '\f10d';
|
||||
|
||||
[dir="rtl"] & {
|
||||
content: '\f10e';
|
||||
}
|
||||
}
|
||||
|
||||
&::after {
|
||||
border-bottom: 1px solid fi-var(global-border-color);
|
||||
bottom: -20px;
|
||||
text-align: right;
|
||||
text-align: end;
|
||||
font-family: var(--fa-family-classic);
|
||||
font-weight: 900;
|
||||
content: '\f10e';
|
||||
|
||||
[dir="rtl"] & {
|
||||
content: '\f10d';
|
||||
}
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -12,7 +12,7 @@
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
left: 0;
|
||||
inset-inline-start: 0;
|
||||
top: 0;
|
||||
}
|
||||
|
||||
+1
@@ -8,6 +8,7 @@
|
||||
line-height: 1;
|
||||
margin-block: 0.5rem;
|
||||
transition: box-shadow 0.2s ease;
|
||||
direction: ltr;
|
||||
@include border-radius;
|
||||
|
||||
&:hover {
|
||||
+1
@@ -8,5 +8,6 @@
|
||||
@use "gist";
|
||||
@use "mapbox";
|
||||
@use "mermaid";
|
||||
@use "reward";
|
||||
@use "tabs";
|
||||
@use "timeline";
|
||||
+6
-2
@@ -18,7 +18,7 @@
|
||||
|
||||
svg {
|
||||
max-width: 100%;
|
||||
height: auto;
|
||||
height: auto;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -60,12 +60,16 @@
|
||||
&::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
left: 0;
|
||||
inset-inline-start: 0;
|
||||
top: 50%;
|
||||
width: 1px;
|
||||
translate: -2px -50%;
|
||||
height: fi-var(code-block-font-size);
|
||||
background-color: fi-var(global-border-color-weight);
|
||||
|
||||
[dir="rtl"] & {
|
||||
translate: 2px -50%;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+69
-35
@@ -73,8 +73,8 @@ tab-container {
|
||||
}
|
||||
}
|
||||
|
||||
&[placement='left'],
|
||||
&[placement='right'] {
|
||||
&[placement='start'],
|
||||
&[placement='end'] {
|
||||
max-width: 100%;
|
||||
|
||||
&::part(tablist-wrapper) {
|
||||
@@ -103,7 +103,7 @@ tab-container {
|
||||
flex-direction: column-reverse;
|
||||
}
|
||||
|
||||
&[placement='right'] {
|
||||
&[placement='end'] {
|
||||
flex-direction: row-reverse;
|
||||
}
|
||||
|
||||
@@ -166,8 +166,8 @@ tab-container {
|
||||
}
|
||||
}
|
||||
|
||||
&[placement='left'],
|
||||
&[placement='right'] {
|
||||
&[placement='start'],
|
||||
&[placement='end'] {
|
||||
> .tab-button {
|
||||
@include tab-button-size(1rem, 0);
|
||||
}
|
||||
@@ -202,34 +202,44 @@ tab-container {
|
||||
}
|
||||
|
||||
// Left placement
|
||||
&[placement='left'] {
|
||||
&[placement='start'] {
|
||||
&::part(tablist-tab-wrapper) {
|
||||
border-right: 2px solid fi-var(global-border-color);
|
||||
border-inline-end: 2px solid fi-var(global-border-color);
|
||||
}
|
||||
|
||||
&::part(tablist-wrapper)::before {
|
||||
box-shadow: inset -2px 0 0 fi-var(primary);
|
||||
translate: 2px 0;
|
||||
|
||||
[dir="rtl"] & {
|
||||
box-shadow: inset 2px 0 0 fi-var(primary);
|
||||
translate: -2px 0;
|
||||
}
|
||||
}
|
||||
|
||||
> .tab-button {
|
||||
text-align: right;
|
||||
text-align: end;
|
||||
}
|
||||
}
|
||||
|
||||
// Right placement
|
||||
&[placement='right'] {
|
||||
&[placement='end'] {
|
||||
&::part(tablist-tab-wrapper) {
|
||||
border-left: 2px solid fi-var(global-border-color);
|
||||
border-inline-start: 2px solid fi-var(global-border-color);
|
||||
}
|
||||
|
||||
&::part(tablist-wrapper)::before {
|
||||
box-shadow: inset 2px 0 0 fi-var(primary);
|
||||
translate: -2px 0;
|
||||
|
||||
[dir="rtl"] & {
|
||||
box-shadow: inset -2px 0 0 fi-var(primary);
|
||||
translate: 2px 0;
|
||||
}
|
||||
}
|
||||
|
||||
> .tab-button {
|
||||
text-align: left;
|
||||
text-align: start;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -268,8 +278,8 @@ tab-container {
|
||||
}
|
||||
}
|
||||
|
||||
&[placement='left'],
|
||||
&[placement='right'] {
|
||||
&[placement='start'],
|
||||
&[placement='end'] {
|
||||
padding-bottom: 0.5rem;
|
||||
|
||||
&::part(tablist) {
|
||||
@@ -296,27 +306,35 @@ tab-container {
|
||||
}
|
||||
|
||||
// Left placement
|
||||
&[placement='left'] {
|
||||
&[placement='start'] {
|
||||
&::part(tablist) {
|
||||
margin-left: 0.5rem;
|
||||
margin-inline-end: 0.5rem;
|
||||
}
|
||||
|
||||
> .tab-button {
|
||||
&:hover {
|
||||
translate: 2px 0;
|
||||
|
||||
[dir="rtl"] & {
|
||||
translate: -2px 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Right placement
|
||||
&[placement='right'] {
|
||||
&[placement='end'] {
|
||||
&::part(tablist) {
|
||||
margin-right: 0.5rem;
|
||||
margin-inline-start: 0.5rem;
|
||||
}
|
||||
|
||||
> .tab-button {
|
||||
&:hover {
|
||||
translate: -2px 0;
|
||||
|
||||
[dir="rtl"] & {
|
||||
translate: 2px 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -362,13 +380,13 @@ tab-container {
|
||||
> .tab-button {
|
||||
&:first-of-type {
|
||||
position: sticky;
|
||||
left: 0;
|
||||
inset-inline-start: 0;
|
||||
@include z-index(base, 1);
|
||||
}
|
||||
|
||||
&:last-of-type {
|
||||
position: sticky;
|
||||
right: 0;
|
||||
inset-inline-end: 0;
|
||||
@include z-index(base, 1);
|
||||
}
|
||||
}
|
||||
@@ -417,44 +435,52 @@ tab-container {
|
||||
}
|
||||
|
||||
// Left placement
|
||||
&[placement='left'] {
|
||||
&[placement='start'] {
|
||||
&::part(tablist-tab-wrapper) {
|
||||
border-right: 1px solid fi-var(global-border-color);
|
||||
border-inline-end: 1px solid fi-var(global-border-color);
|
||||
}
|
||||
|
||||
> .tab-button {
|
||||
translate: 1px 0;
|
||||
@include border-radius(left);
|
||||
@include border-radius(start);
|
||||
|
||||
[dir="rtl"] & {
|
||||
translate: -1px 0;
|
||||
}
|
||||
|
||||
&[aria-selected='true'] {
|
||||
border-right-color: fi-var(global-background-color);
|
||||
border-inline-end-color: fi-var(global-background-color);
|
||||
}
|
||||
}
|
||||
|
||||
> .tab-panel {
|
||||
border-left: none;
|
||||
@include border-radius(right);
|
||||
border-inline-start: none;
|
||||
@include border-radius(end);
|
||||
}
|
||||
}
|
||||
|
||||
// Right placement
|
||||
&[placement='right'] {
|
||||
&[placement='end'] {
|
||||
&::part(tablist-tab-wrapper) {
|
||||
border-left: 1px solid fi-var(global-border-color);
|
||||
border-inline-start: 1px solid fi-var(global-border-color);
|
||||
}
|
||||
|
||||
> .tab-button {
|
||||
translate: -1px 0;
|
||||
@include border-radius(right);
|
||||
@include border-radius(end);
|
||||
|
||||
[dir="rtl"] & {
|
||||
translate: 1px 0;
|
||||
}
|
||||
|
||||
&[aria-selected='true'] {
|
||||
border-left-color: fi-var(global-background-color);
|
||||
border-inline-start-color: fi-var(global-background-color);
|
||||
}
|
||||
}
|
||||
|
||||
> .tab-panel {
|
||||
border-right: none;
|
||||
@include border-radius(left);
|
||||
border-inline-end: none;
|
||||
@include border-radius(start);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -608,14 +634,22 @@ tab-container .tab-panel:not([hidden]) {
|
||||
border-top: 2px solid fi-var(tab-button-border-color);
|
||||
}
|
||||
|
||||
&[placement='left'] > .tab-button {
|
||||
&[placement='start'] > .tab-button {
|
||||
translate: 2px 0;
|
||||
border-right: 2px solid fi-var(tab-button-border-color);
|
||||
border-inline-end: 2px solid fi-var(tab-button-border-color);
|
||||
|
||||
[dir="rtl"] & {
|
||||
translate: -2px 0;
|
||||
}
|
||||
}
|
||||
|
||||
&[placement='right'] > .tab-button {
|
||||
&[placement='end'] > .tab-button {
|
||||
translate: -2px 0;
|
||||
border-left: 2px solid fi-var(tab-button-border-color);
|
||||
border-inline-start: 2px solid fi-var(tab-button-border-color);
|
||||
|
||||
[dir="rtl"] & {
|
||||
translate: 2px 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+2
-2
@@ -81,7 +81,7 @@
|
||||
position: absolute;
|
||||
top: 1.25rem;
|
||||
translate: 0 -50%;
|
||||
left: 0;
|
||||
inset-inline-start: 0;
|
||||
text-align: center;
|
||||
border-style: solid;
|
||||
color: transparent;
|
||||
@@ -99,7 +99,7 @@
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: calc(#{fi-var(timeline-circle-size)} / 2 + #{fi-var(timeline-width)} / 2);
|
||||
inset-inline-start: calc(#{fi-var(timeline-circle-size)} / 2 + #{fi-var(timeline-width)} / 2);
|
||||
width: fi-var(timeline-width);
|
||||
height: 100%;
|
||||
background-color: fi-var(timeline-color);
|
||||
@@ -1,4 +1,5 @@
|
||||
@use "common";
|
||||
@use "reboot";
|
||||
@use "root";
|
||||
@use "rtl";
|
||||
@use "layouts";
|
||||
|
||||
@@ -53,10 +53,12 @@ h1 {
|
||||
}
|
||||
|
||||
// 1. Add the correct box sizing in Firefox.
|
||||
// 2. Ensure `::before` pseudo-element (e.g. awesome-hr icon) is not clipped.
|
||||
|
||||
hr {
|
||||
box-sizing: content-box; // 1
|
||||
height: 0; // 1
|
||||
overflow: visible; // 2
|
||||
}
|
||||
|
||||
// 1. Correct the inheritance and scaling of font size in all browsers.
|
||||
@@ -217,7 +219,7 @@ fieldset {
|
||||
// 2. Fix wrapping bug.
|
||||
|
||||
legend {
|
||||
float: left; // 1
|
||||
float: inline-start; // 1
|
||||
width: 100%;
|
||||
padding: 0;
|
||||
margin-bottom: 0.5rem;
|
||||
@@ -226,7 +228,7 @@ legend {
|
||||
font-size: 1.5rem;
|
||||
|
||||
+ * {
|
||||
clear: left; // 2
|
||||
clear: inline-start; // 2
|
||||
}
|
||||
}
|
||||
|
||||
@@ -274,20 +276,6 @@ textarea {
|
||||
}
|
||||
}
|
||||
|
||||
// 1. A few input types should stay LTR
|
||||
// See https://rtlstyling.com/posts/rtl-styling#form-inputs
|
||||
// 2. RTL only output
|
||||
// See https://rtlcss.com/learn/usage-guide/control-directives/#raw
|
||||
|
||||
/* rtl:raw:
|
||||
[type="tel"],
|
||||
[type="url"],
|
||||
[type="email"],
|
||||
[type="number"] {
|
||||
direction: ltr;
|
||||
}
|
||||
*/
|
||||
|
||||
// Remove the inner padding in Chrome and Safari on macOS.
|
||||
|
||||
::-webkit-search-decoration {
|
||||
|
||||
@@ -0,0 +1,50 @@
|
||||
// ==================================================================================
|
||||
// Right-to-Left (RTL) Support
|
||||
//
|
||||
// See https://rtlstyling.com/posts/rtl-styling/
|
||||
// See https://developer.apple.com/design/human-interface-guidelines/right-to-left/
|
||||
// ==================================================================================
|
||||
|
||||
[dir="rtl"] {
|
||||
// A few input types should stay LTR
|
||||
[type="tel"],
|
||||
[type="url"],
|
||||
[type="email"],
|
||||
[type="number"] {
|
||||
direction: ltr;
|
||||
}
|
||||
|
||||
// Flip directional icons in RTL
|
||||
.fa-tags,
|
||||
.fa-tag,
|
||||
.fa-folder,
|
||||
.fa-folder-tree,
|
||||
.fa-folder-open,
|
||||
.fa-folder-closed,
|
||||
.fa-pencil,
|
||||
.fa-pen-to-square,
|
||||
.fa-pen-clip,
|
||||
.fa-square-pen,
|
||||
.fa-list-ul,
|
||||
.fa-list-check,
|
||||
.fa-clipboard-list,
|
||||
.fa-rectangle-list,
|
||||
.fa-comments,
|
||||
.fa-comment,
|
||||
.fa-comment-dots,
|
||||
.fa-message,
|
||||
.fa-retweet,
|
||||
.octicon-report-16 {
|
||||
scale: -1 1;
|
||||
}
|
||||
}
|
||||
|
||||
// Arabic-script languages (Arabic, Persian, Urdu) typography corrections
|
||||
// Hebrew and other non-connected RTL scripts don't need these adjustments
|
||||
|
||||
// Letter-spacing breaks connected letter forms
|
||||
:lang(ar),
|
||||
:lang(fa),
|
||||
:lang(ur) {
|
||||
letter-spacing: 0;
|
||||
}
|
||||
@@ -1,2 +1,2 @@
|
||||
@forward "rgba" as fi-*;
|
||||
@forward "var" as fi-*;
|
||||
@forward "rgba";
|
||||
@forward "var";
|
||||
|
||||
@@ -5,6 +5,6 @@
|
||||
/// @param {Number} $alpha [1] - Alpha channel value in range 0..1.
|
||||
/// @example
|
||||
/// color: fi-rgba(fi-var(secondary), 0.2);
|
||||
@function rgba($color, $alpha: 1) {
|
||||
@return string.unquote("rgb(from #{$color} r g b / #{$alpha})");
|
||||
@function fi-rgba($color, $alpha: 1) {
|
||||
@return rgb(from $color r g b / $alpha);
|
||||
}
|
||||
|
||||
@@ -9,13 +9,10 @@
|
||||
/// @example
|
||||
/// color: fi-var(global-font-color);
|
||||
/// background: fi-var(custom-bg, #fff);
|
||||
@function var($name, $fallback: null) {
|
||||
@function fi-var($name, $fallback: null) {
|
||||
@if $fallback != null {
|
||||
@if meta.type-of($fallback) == string {
|
||||
@return string.unquote('var(#{$rootPrefix}#{$name}, "#{$fallback}")');
|
||||
}
|
||||
@return string.unquote("var(#{$rootPrefix}#{$name}, #{$fallback})");
|
||||
@return var(#{$rootPrefix}#{$name}, $fallback);
|
||||
}
|
||||
|
||||
@return string.unquote("var(#{$rootPrefix}#{$name})");
|
||||
@return var(#{$rootPrefix}#{$name});
|
||||
}
|
||||
|
||||
@@ -17,8 +17,7 @@ footer {
|
||||
|
||||
.footer-divider:not(:first-child)::before {
|
||||
content: '|';
|
||||
margin-left: 0.25rem;
|
||||
margin-right: 0.25rem;
|
||||
margin-inline: 0.25rem;
|
||||
}
|
||||
|
||||
.hugo-icon,
|
||||
|
||||
@@ -119,7 +119,7 @@ header {
|
||||
list-style: none;
|
||||
|
||||
.dropdown-icon {
|
||||
margin-left: 0.25rem;
|
||||
margin-inline-start: 0.25rem;
|
||||
font-size: .875em;
|
||||
color: fi-var(global-font-color);
|
||||
|
||||
@@ -155,6 +155,10 @@ header {
|
||||
position: relative;
|
||||
@include focus-visible-ring;
|
||||
|
||||
[dir="rtl"] & {
|
||||
scale: -1 1;
|
||||
}
|
||||
|
||||
i {
|
||||
display: inline-block;
|
||||
rotate: 225deg;
|
||||
@@ -195,10 +199,10 @@ header {
|
||||
padding: 0 2rem;
|
||||
height: 100%;
|
||||
|
||||
&[data-github-corner='left'] {
|
||||
&[data-github-corner='start'] {
|
||||
padding-inline-start: fi-var(header-height);
|
||||
}
|
||||
&[data-github-corner='right'] {
|
||||
&[data-github-corner='end'] {
|
||||
padding-inline-end: fi-var(header-height);
|
||||
}
|
||||
|
||||
@@ -211,9 +215,17 @@ header {
|
||||
&:hover .sub-menu {
|
||||
display: block;
|
||||
translate: -0.5rem 0;
|
||||
|
||||
[dir="rtl"] & {
|
||||
translate: 0.5rem 0;
|
||||
}
|
||||
}
|
||||
&.language-switch:hover .sub-menu {
|
||||
translate: calc(2.25rem - 100%) 0;
|
||||
translate: calc(1.75rem - 100%) 0;
|
||||
|
||||
[dir="rtl"] & {
|
||||
translate: calc(100% - 1.75rem) 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -229,7 +241,7 @@ header {
|
||||
width: 0;
|
||||
margin: 18.5px 0.5rem;
|
||||
padding: 0;
|
||||
border-left: 1.5px solid fi-var(global-border-color);
|
||||
border-inline-start: 1.5px solid fi-var(global-border-color);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -406,7 +418,7 @@ header {
|
||||
padding: 0 1rem;
|
||||
|
||||
&:not(.menu-system):hover {
|
||||
background-color: light-dark(#f2f2f3, #535355);
|
||||
background-color: light-dark(#f2f2f3, #3e444c);
|
||||
}
|
||||
|
||||
.menu-link {
|
||||
@@ -448,7 +460,7 @@ header {
|
||||
.language-select {
|
||||
position: absolute;
|
||||
opacity: 0;
|
||||
left: 0;
|
||||
inset-inline-start: 0;
|
||||
top: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
@@ -457,7 +469,7 @@ header {
|
||||
}
|
||||
|
||||
.sub-menu {
|
||||
padding-left: 0;
|
||||
padding-inline-start: 0;
|
||||
display: none;
|
||||
|
||||
&.open {
|
||||
@@ -465,7 +477,7 @@ header {
|
||||
}
|
||||
|
||||
.menu-item:hover {
|
||||
background-color: light-dark(#e7e7ea, #5d5d5f);
|
||||
background-color: light-dark(#e7e7ea, #464c54);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
@use "wrapper";
|
||||
@use "header";
|
||||
@use "page-style";
|
||||
@use "breadcrumb";
|
||||
@use "page";
|
||||
@use "pagination";
|
||||
@use "comment";
|
||||
@use "footer";
|
||||
|
||||
@@ -1,24 +0,0 @@
|
||||
@use "core/mixins" as *;
|
||||
|
||||
@include page-style;
|
||||
|
||||
@include page-style('wide') {
|
||||
@include media('xl') {
|
||||
width: ROUND(64%, 2px);
|
||||
max-width: 1600px;
|
||||
}
|
||||
|
||||
@include media('lg') {
|
||||
width: ROUND(60%, 2px);
|
||||
}
|
||||
|
||||
@include media('md') {
|
||||
width: ROUND(56%, 2px);
|
||||
}
|
||||
}
|
||||
|
||||
@include page-style('narrow') {
|
||||
@include media('xl') {
|
||||
max-width: 800px;
|
||||
}
|
||||
}
|
||||
@@ -2,6 +2,7 @@
|
||||
@use "core/mixins" as *;
|
||||
|
||||
.page {
|
||||
position: relative;
|
||||
padding-inline: 0.5rem;
|
||||
box-sizing: border-box;
|
||||
|
||||
@@ -40,3 +41,26 @@
|
||||
padding-top: 0 !important;
|
||||
}
|
||||
}
|
||||
|
||||
@include page-style;
|
||||
|
||||
@include page-style('wide') {
|
||||
@include media('xl') {
|
||||
width: ROUND(64%, 2px);
|
||||
max-width: 1600px;
|
||||
}
|
||||
|
||||
@include media('lg') {
|
||||
width: ROUND(60%, 2px);
|
||||
}
|
||||
|
||||
@include media('md') {
|
||||
width: ROUND(56%, 2px);
|
||||
}
|
||||
}
|
||||
|
||||
@include page-style('narrow') {
|
||||
@include media('xl') {
|
||||
max-width: 800px;
|
||||
}
|
||||
}
|
||||
@@ -16,7 +16,7 @@
|
||||
padding: 0.5rem;
|
||||
max-width: 500px;
|
||||
|
||||
&:not(:has(~ aside)) {
|
||||
&:first-of-type:not(:has(~ aside)) {
|
||||
display: none;
|
||||
}
|
||||
|
||||
@@ -48,7 +48,7 @@
|
||||
aside:first-child:not(:empty) ~ aside#toc-auto:empty {
|
||||
display: none;
|
||||
}
|
||||
|
||||
|
||||
aside:first-child:empty:has(~ aside:not(:empty)) ~ article,
|
||||
aside:first-child:not(:empty) ~ article:has(~ aside:empty) {
|
||||
flex: 3;
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
// Color map of admonitions
|
||||
/// Color map of admonitions.
|
||||
/// @type Map
|
||||
$admonition-color-map: (
|
||||
note: (
|
||||
color: #448aff,
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
// Color map of basic alerts
|
||||
/// Color map of GitHub-style alerts (light theme).
|
||||
/// @type Map
|
||||
$alert-color-map: (
|
||||
note: (
|
||||
border-color: #0969da,
|
||||
@@ -22,6 +23,8 @@ $alert-color-map: (
|
||||
),
|
||||
) !default;
|
||||
|
||||
/// Color map of GitHub-style alerts (dark theme).
|
||||
/// @type Map
|
||||
$alert-color-map-dark: (
|
||||
note: (
|
||||
border-color: #316dca,
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/// Map of code identifiers/languages to Chroma supported languages.
|
||||
/// This file is auto-generated by `pnpm gen:lexers`, do not edit manually.
|
||||
/// @see https://github.com/hugo-fixit/FixIt/tree/main/packages/chroma-lexers
|
||||
/// See: https://github.com/hugo-fixit/FixIt/tree/main/packages/chroma-lexers
|
||||
$chroma-lexers: (
|
||||
"🔥": "Mojo",
|
||||
"1s": "OnesEnterprise",
|
||||
@@ -34,6 +34,7 @@ $chroma-lexers: (
|
||||
"as": "ActionScript",
|
||||
"as3": "ActionScript 3",
|
||||
"asm": "GAS",
|
||||
"astro": "Astro",
|
||||
"atl": "ATL",
|
||||
"autohotkey": "AutoHotkey",
|
||||
"autoit": "AutoIt",
|
||||
@@ -172,7 +173,7 @@ $chroma-lexers: (
|
||||
"haskell": "Haskell",
|
||||
"haxe": "Haxe",
|
||||
"hbs": "Handlebars",
|
||||
"hcl": "HCL",
|
||||
"hcl": "Terraform",
|
||||
"hexdump": "Hexdump",
|
||||
"hlb": "HLB",
|
||||
"hlsl": "HLSL",
|
||||
@@ -184,9 +185,12 @@ $chroma-lexers: (
|
||||
"html\\+kid": "Genshi HTML",
|
||||
"html\\+ruby": "ERB",
|
||||
"http": "HTTP",
|
||||
"hurl": "Hurl",
|
||||
"hx": "Haxe",
|
||||
"hxsl": "Haxe",
|
||||
"hylang": "Hy",
|
||||
"hypr": "Hyprlang",
|
||||
"hyprlang": "Hyprlang",
|
||||
"idr": "Idris",
|
||||
"idris": "Idris",
|
||||
"igor": "Igor",
|
||||
@@ -201,6 +205,7 @@ $chroma-lexers: (
|
||||
"javascript": "JavaScript",
|
||||
"jinja": "Django/Jinja",
|
||||
"jl": "Julia",
|
||||
"jq": "JQ",
|
||||
"js": "JavaScript",
|
||||
"json": "JSON",
|
||||
"jsonata": "JSONata",
|
||||
@@ -425,6 +430,7 @@ $chroma-lexers: (
|
||||
"tasm": "TASM",
|
||||
"tcl": "Tcl",
|
||||
"tcsh": "Tcsh",
|
||||
"templ": "Templ",
|
||||
"termcap": "Termcap",
|
||||
"terminfo": "Terminfo",
|
||||
"terraform": "Terraform",
|
||||
@@ -483,6 +489,7 @@ $chroma-lexers: (
|
||||
"zsh": "Bash",
|
||||
"µcad": "Microcad",
|
||||
// Custom code type
|
||||
"go-html-template": "Go HTML Template",
|
||||
"markdown": "Markdown",
|
||||
"yml": "YAML"
|
||||
) !default;
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
// Theme colors map
|
||||
/// Theme semantic color tokens.
|
||||
/// @type Map
|
||||
$theme-colors: (
|
||||
'primary': #1677ff,
|
||||
'secondary': #8b949e,
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
@use "core/functions" as *;
|
||||
|
||||
/// Mixin to set border-radius
|
||||
/// @param {String|Number|List} $value - The value of the border-radius, position (top, bottom, left, right) or keyword (full)
|
||||
/// @param {String|Number|List} $value - The value of the border-radius, position (top, bottom, start, end) or keyword (full)
|
||||
/// @param {Number} $radius - The radius of the border (only for position)
|
||||
/// @example
|
||||
/// @include border-radius;
|
||||
/// @include border-radius(full);
|
||||
/// @include border-radius(top);
|
||||
/// @include border-radius(start);
|
||||
/// @include border-radius(top, 10px);
|
||||
/// @include border-radius(10px 20px 10px 20px);
|
||||
@mixin border-radius($value: fi-var(global-border-radius), $radius: fi-var(global-border-radius)) {
|
||||
@@ -16,10 +17,12 @@
|
||||
border-radius: $radius $radius 0 0;
|
||||
} @else if $value == bottom {
|
||||
border-radius: 0 0 $radius $radius;
|
||||
} @else if $value == left {
|
||||
border-radius: $radius 0 0 $radius;
|
||||
} @else if $value == right {
|
||||
border-radius: 0 $radius $radius 0;
|
||||
} @else if $value == start {
|
||||
border-start-start-radius: $radius;
|
||||
border-end-start-radius: $radius;
|
||||
} @else if $value == end {
|
||||
border-start-end-radius: $radius;
|
||||
border-end-end-radius: $radius;
|
||||
} @else {
|
||||
border-radius: $value;
|
||||
}
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
@use "core/functions" as *;
|
||||
|
||||
// TODO Superseded by text-wrap: wrap; in modern browsers
|
||||
/// Apply word-wrap and overflow-wrap for cross-browser compatibility.
|
||||
/// TODO Superseded by `text-wrap: wrap;` in modern browsers.
|
||||
/// @param {String} $value - Wrap value (e.g. `break-word`)
|
||||
@mixin overflow-wrap($value) {
|
||||
word-wrap: $value;
|
||||
overflow-wrap: $value;
|
||||
@@ -43,6 +45,9 @@
|
||||
}
|
||||
}
|
||||
|
||||
/// Apply a focus-visible ring outline using a pseudo-element.
|
||||
/// @param {Length} $size [$size: 1.3em] - Size of the focus ring
|
||||
/// @param {Length} $offset [$offset: 0.2rem] - Offset from the element
|
||||
@mixin focus-visible-ring($size: 1.3em, $offset: 0.2rem) {
|
||||
&:focus-visible {
|
||||
outline: none;
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
@use "border-radius" as *;
|
||||
@use "compatibility" as *;
|
||||
|
||||
/// Shared inline code styles using theme CSS variables.
|
||||
@mixin inline-code() {
|
||||
padding: 0.2em 0.4em;
|
||||
margin: 0;
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user