mirror of
https://github.com/hugo-fixit/FixIt.git
synced 2026-08-30 18:22:40 +00:00
Compare commits
29 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 92b0a6b56e | |||
| 9132b408c6 | |||
| a53ef80035 | |||
| 0f2712f182 | |||
| d5aa013106 | |||
| d9e45154ff | |||
| b09555981b | |||
| 65ad407cd0 | |||
| 9e384b666f | |||
| 96b4b4512c | |||
| de38644d75 | |||
| c79badcf44 | |||
| ccf17c6444 | |||
| 9bac69b2ec | |||
| 0bf5d3a49b | |||
| 74b80bd94d | |||
| 789973da78 | |||
| d51dfd35e8 | |||
| 8534cddc7c | |||
| 02ac3f5259 | |||
| c8d028aa0e | |||
| f2c112e402 | |||
| 8bf2fea735 | |||
| fe7105a38a | |||
| fda650ad58 | |||
| d97e69ad0a | |||
| 98a82d845a | |||
| 423a7ddbf7 | |||
| da297a71f8 |
@@ -120,7 +120,7 @@ FixIt/
|
||||
```javascript
|
||||
// 使用 ES6 类
|
||||
export default class Util {
|
||||
copyText(text) {
|
||||
static copyText(text) {
|
||||
// ...
|
||||
}
|
||||
}
|
||||
@@ -129,7 +129,7 @@ export default class Util {
|
||||
class FixIt {
|
||||
constructor() {
|
||||
this.config = window.config
|
||||
this.util = new Util()
|
||||
this.scrollTop = Util.getScrollTop()
|
||||
}
|
||||
|
||||
init() {
|
||||
|
||||
@@ -30,3 +30,4 @@ $RECYCLE.BIN/
|
||||
|
||||
CHANGELOG.md
|
||||
.stash/
|
||||
docs
|
||||
|
||||
@@ -119,3 +119,45 @@ Finally, create a new pull request at <https://github.com/hugo-fixit/FixIt/pulls
|
||||
## Git Commit Guidelines
|
||||
|
||||
We follow the [Conventional Commits](https://www.conventionalcommits.org/) specification for commit messages. This enables automatic changelog generation using our custom template: [conventional.hbs](https://github.com/Lruihao/auto-changelog-plus/blob/main/settings/conventional.hbs).
|
||||
|
||||
> [!NOTE]
|
||||
>
|
||||
> Commits in a PR will normally be squashed into one commit, so you don't need to rebase locally.
|
||||
|
||||
### Commit Message Format
|
||||
|
||||
```
|
||||
<type>(<scope>): <subject>
|
||||
^ ^ ^
|
||||
| | |__ Subject: Concise description of the change (imperative mood, lowercase).
|
||||
| |____________ Scope: The specific part of the codebase affected (optional but recommended).
|
||||
|___________________ Type: Indicates the kind of change.
|
||||
```
|
||||
|
||||
### Allowed Types
|
||||
|
||||
- `feat`: A new feature.
|
||||
- `fix`: A bug fix.
|
||||
- `refactor`: Code changes that neither fix a bug nor add a feature.
|
||||
- `chore`: Changes to the build process, auxiliary tools, libraries, documentation generation etc.
|
||||
- `docs`: Documentation only changes.
|
||||
- Other conventional types like `perf`, `style`, `test`, `ci`, `build` are also acceptable.
|
||||
|
||||
### Allowed Scopes
|
||||
|
||||
- `workflow`: CI/CD workflow changes (`.github/workflows/`)
|
||||
- `archetypes`: Content templates (`archetypes/`)
|
||||
- `assets`: Changes to theme assets like CSS, JS (`assets/`)
|
||||
- `i18n`: Internationalization and translation files (`i18n/`)
|
||||
- `layouts`: Root-level Hugo template files (`layouts/*.html`)
|
||||
- `config`: Theme configuration (`hugo.toml`, `theme.toml`)
|
||||
- _(All top-level directories in the layouts and packages directories)_
|
||||
- _(Consider adding other scopes as needed for better granularity)_
|
||||
|
||||
### Examples
|
||||
|
||||
- `feat(_shortcodes): add mapbox zoom control options`
|
||||
- `fix(_partials): avoid duplicate canonical link tags`
|
||||
- `docs(i18n): update translation key naming guidelines`
|
||||
- `ci(workflow): optimize preview deployment cache`
|
||||
- `refactor(assets): split theme initialization into smaller modules`
|
||||
|
||||
@@ -0,0 +1,166 @@
|
||||
---
|
||||
title: Code Tabs Test
|
||||
date: 2026-01-25T07:50:22+08:00
|
||||
collections:
|
||||
- Tests
|
||||
categories:
|
||||
- Markdown
|
||||
tags:
|
||||
- Code Tabs
|
||||
---
|
||||
|
||||
Code tabs test cases for grouped code blocks.
|
||||
|
||||
<!--more-->
|
||||
|
||||
## Default Name
|
||||
|
||||
```python {group=tab2}
|
||||
print("Python")
|
||||
```
|
||||
|
||||
```go {group=tab2}
|
||||
fmt.Println("Go")
|
||||
```
|
||||
|
||||
## Custom Name
|
||||
|
||||
```js {group=tab-actions name="[JavaScript]"}
|
||||
function sum(a, b) {
|
||||
return a + b
|
||||
}
|
||||
|
||||
console.log(sum(1, 2))
|
||||
```
|
||||
|
||||
```python {group=tab-actions name="[Python]"}
|
||||
def sum(a, b):
|
||||
return a + b
|
||||
|
||||
print(sum(1, 2))
|
||||
```
|
||||
|
||||
## Active Tab
|
||||
|
||||
```js {group=tab-active name="Inactive Tab"}
|
||||
function greet(name) {
|
||||
return `Hello, ${name}!`
|
||||
}
|
||||
```
|
||||
|
||||
```python {group=tab-active name="Active Tab" .active}
|
||||
def greet(name):
|
||||
return f"Hello, {name}!"
|
||||
```
|
||||
|
||||
## Shadow
|
||||
|
||||
```js {group=tab-shadow name="Always" shadow="always"}
|
||||
function greet(name) {
|
||||
return `Hello, ${name}!`
|
||||
}
|
||||
```
|
||||
|
||||
```python {group=tab-shadow name="Hover" shadow="hover"}
|
||||
def greet(name):
|
||||
return f"Hello, {name}!"
|
||||
```
|
||||
|
||||
```go {group=tab-shadow name="Never" shadow="never"}
|
||||
func greet(name string) string {
|
||||
return fmt.Sprintf("Hello, %s!", name)
|
||||
}
|
||||
```
|
||||
|
||||
## Mode
|
||||
|
||||
```ts {group=tab-mode name="Classic"}
|
||||
interface User {
|
||||
id: number
|
||||
name: string
|
||||
}
|
||||
```
|
||||
|
||||
```ts {group=tab-mode name="Mac" mode="mac"}
|
||||
interface User {
|
||||
id: number
|
||||
name: string
|
||||
}
|
||||
```
|
||||
|
||||
```ts {group=tab-mode name="Simple" mode="simple"}
|
||||
interface User {
|
||||
id: number
|
||||
name: string
|
||||
}
|
||||
```
|
||||
|
||||
## Actions
|
||||
|
||||
```bash {group=tab-actions name="lineNos=true" lineNos=true}
|
||||
echo "line 1"
|
||||
echo "line 2"
|
||||
echo "line 3"
|
||||
```
|
||||
|
||||
```bash {group=tab-actions name="lineNos=false" lineNos=false}
|
||||
echo "line 1"
|
||||
echo "line 2"
|
||||
echo "line 3"
|
||||
```
|
||||
|
||||
```bash {group=tab-actions name="wrapping" .line-wrapping}
|
||||
printf "this is a very very very very very very very very very very very very very very very very long line"
|
||||
```
|
||||
|
||||
```js {group=tab-actions name="editable" editable=true}
|
||||
function greet(name) {
|
||||
return `Hello, ${name}!`
|
||||
}
|
||||
```
|
||||
|
||||
## Expanded/Collapsed
|
||||
|
||||
```js {group=tab-expand name="Expanded"}
|
||||
function longFunction() {
|
||||
console.log('This is a long function.')
|
||||
console.log('It has many lines of code.')
|
||||
console.log('The code block should be expanded by default.')
|
||||
console.log('Lorem ipsum dolor sit amet, consectetur adipiscing elit.')
|
||||
console.log('Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.')
|
||||
console.log('Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.')
|
||||
console.log('Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur.')
|
||||
console.log('Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.')
|
||||
console.log('The end of the long function.')
|
||||
}
|
||||
```
|
||||
|
||||
```js {group=tab-expand name="Collapsed" .is-collapsed}
|
||||
function shortFunction() {
|
||||
console.log('This is a short function.')
|
||||
}
|
||||
```
|
||||
|
||||
## Code tabs inside other blocks
|
||||
|
||||
> [!NOTE]+
|
||||
>
|
||||
> ```python {group=tab-nested}
|
||||
> print("Python")
|
||||
> ```
|
||||
>
|
||||
> ```go {group=tab-nested}
|
||||
> fmt.Println("Go")
|
||||
> ```
|
||||
|
||||
{{< details open=true >}}
|
||||
|
||||
```python {group=tab2}
|
||||
print("Python")
|
||||
```
|
||||
|
||||
```go {group=tab2}
|
||||
fmt.Println("Go")
|
||||
```
|
||||
|
||||
{{< /details >}}
|
||||
@@ -64,6 +64,38 @@ hello('FixIt')
|
||||
// Hello, FixIt!
|
||||
```
|
||||
|
||||
## Shadow
|
||||
|
||||
{{< tabs >}}
|
||||
{{% tab title="Always" %}}
|
||||
|
||||
```js {shadow="always"}
|
||||
function hello(x = 'world') {
|
||||
console.log(`Hello, ${x}!`)
|
||||
}
|
||||
```
|
||||
|
||||
{{% /tab %}}
|
||||
{{% tab title="Hover" %}}
|
||||
|
||||
```js {shadow="hover"}
|
||||
function hello(x = 'world') {
|
||||
console.log(`Hello, ${x}!`)
|
||||
}
|
||||
```
|
||||
|
||||
{{% /tab %}}
|
||||
{{% tab title="Never" %}}
|
||||
|
||||
```js {shadow="never"}
|
||||
function hello(x = 'world') {
|
||||
console.log(`Hello, ${x}!`)
|
||||
}
|
||||
```
|
||||
|
||||
{{% /tab %}}
|
||||
{{< /tabs >}}
|
||||
|
||||
## Collapsed/Expanded
|
||||
|
||||
```
|
||||
@@ -148,6 +180,18 @@ function add(a, b) {
|
||||
|
||||
<p>Lorem ipsum dolor sit amet, graecis denique ei vel, at duo primis mandamus. Et legere ocurreret pri, animal tacimates complectitur ad cum. Cu eum inermis inimicus efficiendi. Labore officiis his ex, soluta officiis concludaturque ei qui, vide sensibus vim ad.</p>
|
||||
|
||||
## Code Toggle
|
||||
|
||||
```toggle {before_tabs="hugo."}
|
||||
[params]
|
||||
description = ''
|
||||
keywords = []
|
||||
|
||||
[params.codeblock]
|
||||
mode = 'classic'
|
||||
wrapper = true
|
||||
```
|
||||
|
||||
## Code block inside other blocks
|
||||
|
||||
> [!NOTE]+
|
||||
|
||||
@@ -71,3 +71,11 @@ $$ \ce{CO2 + C -> 2 CO} $$
|
||||
$$ \ce{Hg^2+ ->[I-] HgI2 ->[I-] [Hg^{II}I4]^2-} $$
|
||||
|
||||
$$C_p[\ce{H2O(l)}] = \pu{75.3 J // mol K}$$
|
||||
|
||||
## $\KaTeX$ in headings
|
||||
|
||||
This is a bug:
|
||||
|
||||
```latex
|
||||
## $\KaTeX$ in headings
|
||||
```
|
||||
|
||||
@@ -117,3 +117,29 @@ U \ar@/_/[ddr]_y \ar@{.>}[dr]|{\langle x,y \rangle} \ar@/^/[drr]^x \\
|
||||
}
|
||||
\end{xy}
|
||||
$$
|
||||
|
||||
## \$\\text{MathJax}\$ in headings
|
||||
|
||||
```latex
|
||||
## \$\\text{MathJax}\$ in headings
|
||||
```
|
||||
|
||||
## More
|
||||
|
||||
### Dark Mode Adaptation
|
||||
|
||||
Use `.auto-dark-mode` class to automatically adapt to dark mode by inverting the color and hue:
|
||||
|
||||
For inline formula:
|
||||
|
||||
$\bbox[border: solid .4pt magenta, pink]{x^2=4}$
|
||||
{.auto-dark-mode}
|
||||
|
||||
For formula blocks:
|
||||
|
||||
{{< style "[data-theme=dark] & { filter: invert(1) hue-rotate(0.5turn); }" >}}
|
||||
$$
|
||||
\bbox[border: solid .4pt magenta, pink]{x^2=4}
|
||||
$$
|
||||
{.auto-dark-mode}
|
||||
{{< /style >}}
|
||||
|
||||
@@ -216,3 +216,16 @@ type = "file"
|
||||
|
||||
{{% /tab %}}
|
||||
{{< /tabs >}}
|
||||
|
||||
### MathJax inside tabs
|
||||
|
||||
{{< tabs >}}
|
||||
{{% tab title="Inline" %}}
|
||||
$c = \pm\sqrt{a^2 + b^2}$ and \(f(x)=\int_{-\infty}^{\infty} \hat{f}(\xi) e^{2 \pi i \xi x} d \xi\)
|
||||
{{% /tab %}}
|
||||
{{% tab title="Block" %}}
|
||||
$$ x = {-b \pm \sqrt{b^2-4ac} \over 2a} $$
|
||||
|
||||
\[ f(a) = \frac{1}{2\pi i} \oint\frac{f(z)}{z-a}dz \]
|
||||
{{% /tab %}}
|
||||
{{< /tabs >}}
|
||||
|
||||
@@ -9,10 +9,8 @@ html {
|
||||
width: 100%;
|
||||
scroll-behavior: smooth;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
@media screen and (prefers-reduced-motion: reduce) {
|
||||
html {
|
||||
@include media('reduce-motion') {
|
||||
scroll-behavior: auto;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -58,3 +58,42 @@
|
||||
.rounded-full {
|
||||
@include border-radius(full);
|
||||
}
|
||||
|
||||
// auto adapt to dark mode by inverting the color and hue
|
||||
.auto-dark-mode {
|
||||
[data-theme=dark] & {
|
||||
filter: invert(1) hue-rotate(0.5turn);
|
||||
}
|
||||
}
|
||||
|
||||
// print view helper
|
||||
.page-break-before {
|
||||
break-before: page;
|
||||
}
|
||||
|
||||
.page-break-after {
|
||||
break-after: page;
|
||||
}
|
||||
|
||||
@include media('xs', 'up') {
|
||||
.d-none-desktop {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
@include media('xs') {
|
||||
.d-none-mobile {
|
||||
display: none !important;
|
||||
}
|
||||
}
|
||||
|
||||
@include media('print') {
|
||||
@page {
|
||||
size: A4 portrait;
|
||||
margin: 1.27cm;
|
||||
}
|
||||
|
||||
.d-none-print {
|
||||
display: none !important;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,7 +5,6 @@
|
||||
display: flex;
|
||||
gap: 0.5rem;
|
||||
@include z-index(sticky);
|
||||
@extend .print-d-none;
|
||||
|
||||
&:has(~ main[data-reverse]) {
|
||||
flex-direction: row-reverse;
|
||||
@@ -16,11 +15,35 @@
|
||||
content: '';
|
||||
flex: 1;
|
||||
padding-inline: 0.5rem;
|
||||
|
||||
@include media('sm', 'down') {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
&.sticky {
|
||||
position: sticky;
|
||||
top: fixit-var(header-height);
|
||||
|
||||
@include media('sm', 'up') {
|
||||
body:not([data-header-desktop='sticky']) & {
|
||||
top: 0;
|
||||
}
|
||||
// adjust the scroll margin top of the content anchors on the page
|
||||
body:not([data-header-desktop='auto']) &+.fi-container .content [id] {
|
||||
scroll-margin-top: calc(#{fixit-var(scroll-mt)} + #{fixit-var(breadcrumb-height)});
|
||||
}
|
||||
}
|
||||
|
||||
@include media('xs') {
|
||||
body:not([data-header-mobile='sticky']) & {
|
||||
top: 0;
|
||||
}
|
||||
// adjust the scroll margin top of the content anchors on the page
|
||||
body:not([data-header-mobile='auto']) &+.fi-container .content [id] {
|
||||
scroll-margin-top: calc(#{fixit-var(scroll-mt)} + #{fixit-var(breadcrumb-height)});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.breadcrumb {
|
||||
@@ -47,4 +70,8 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@include media('print') {
|
||||
display: none !important;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,7 +4,6 @@ footer {
|
||||
text-align: center;
|
||||
line-height: 1.25rem;
|
||||
padding: 1rem 0;
|
||||
@extend .print-d-none;
|
||||
|
||||
.footer-container {
|
||||
display: flex;
|
||||
@@ -25,9 +24,17 @@ footer {
|
||||
animation: icon-animate 1.33s ease-in-out infinite;
|
||||
}
|
||||
}
|
||||
|
||||
@include media('xs') {
|
||||
font-size: 0.618rem;
|
||||
}
|
||||
}
|
||||
|
||||
@include blur;
|
||||
|
||||
@include media('print') {
|
||||
display: none !important;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes icon-animate {
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
header {
|
||||
width: 100%;
|
||||
background-color: fixit-var(header-background-color);
|
||||
@include z-index(fixed);
|
||||
transition: box-shadow 0.3s ease;
|
||||
@extend .print-d-none;
|
||||
@include z-index(fixed);
|
||||
|
||||
&:hover {
|
||||
box-shadow: 0 0 1.5rem 0 rgba(0, 0, 0, 0.1);
|
||||
@@ -12,6 +11,10 @@ header {
|
||||
box-shadow: 0 0 1.5rem 0 rgba(255, 255, 255, 0.1);
|
||||
}
|
||||
}
|
||||
|
||||
@include media('print') {
|
||||
display: none !important;
|
||||
}
|
||||
}
|
||||
|
||||
.header-wrapper {
|
||||
@@ -262,6 +265,14 @@ header {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@include media('md', 'down') {
|
||||
padding-inline-end: 1rem;
|
||||
}
|
||||
|
||||
@include media('sm', 'down') {
|
||||
padding-inline-start: 1rem;
|
||||
}
|
||||
}
|
||||
|
||||
&.open .header-wrapper .menu .menu-item.search {
|
||||
@@ -269,6 +280,10 @@ header {
|
||||
width: 24rem;
|
||||
}
|
||||
}
|
||||
|
||||
@include media('xs') {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
#header-mobile {
|
||||
@@ -480,6 +495,10 @@ header {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@include media('xs') {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
|
||||
.search-dropdown {
|
||||
@@ -495,6 +514,10 @@ header {
|
||||
&.desktop {
|
||||
right: 2rem;
|
||||
width: 30rem;
|
||||
|
||||
@include media('md', 'down') {
|
||||
right: 1rem;
|
||||
}
|
||||
}
|
||||
|
||||
&.mobile {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/** Layout **/
|
||||
// Responsive layout
|
||||
.wrapper {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
@@ -16,6 +16,10 @@
|
||||
&:not(:has(~ aside)) {
|
||||
display: none;
|
||||
}
|
||||
|
||||
@include media('sm', 'down') {
|
||||
display: none !important;
|
||||
}
|
||||
}
|
||||
|
||||
&.fi-container {
|
||||
@@ -34,9 +38,45 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@include media('md') {
|
||||
main {
|
||||
// hide empty aside and expand article when only one aside is empty
|
||||
aside:first-child:empty:has(~ aside:not(:empty)),
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
// adjust breadcrumb flex when asides are empty
|
||||
&:has(main > aside:first-child:empty ~ aside:not(:empty)) .breadcrumb-container {
|
||||
.breadcrumb {
|
||||
flex: 3;
|
||||
}
|
||||
|
||||
&::before {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
&:has(main > aside:first-child:not(:empty) ~ aside#toc-auto:empty) .breadcrumb-container {
|
||||
.breadcrumb {
|
||||
flex: 3;
|
||||
}
|
||||
|
||||
&::after {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@import "header";
|
||||
@import "breadcrumb";
|
||||
@import "footer";
|
||||
@import "page-style";
|
||||
@import "pagination";
|
||||
@import "footer";
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
%page-style {
|
||||
@include media('xl') {
|
||||
width: ROUND(60%, 2px);
|
||||
max-width: 1200px;
|
||||
}
|
||||
|
||||
@include media('lg') {
|
||||
width: ROUND(56%, 2px);
|
||||
}
|
||||
|
||||
@include media('md') {
|
||||
width: ROUND(52%, 2px);
|
||||
}
|
||||
|
||||
@include media('sm') {
|
||||
width: ROUND(80%, 2px);
|
||||
}
|
||||
|
||||
@include media('xs') {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
[data-page-style='wide'] {
|
||||
%page-style {
|
||||
@include media('xl') {
|
||||
width: ROUND(64%, 2px);
|
||||
max-width: 1600px;
|
||||
}
|
||||
|
||||
@include media('lg') {
|
||||
width: ROUND(60%, 2px);
|
||||
}
|
||||
|
||||
@include media('md') {
|
||||
width: ROUND(56%, 2px);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[data-page-style='narrow'] {
|
||||
%page-style {
|
||||
@include media('xl') {
|
||||
max-width: 800px;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -69,5 +69,9 @@
|
||||
width: 50%;
|
||||
}
|
||||
}
|
||||
|
||||
@include media('xs') {
|
||||
margin: 0 5px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
/// Mixin to apply box-shadow with timing control
|
||||
/// @param {String} $when - When to show shadow: always | hover | never (default: always)
|
||||
/// @param {List} $shadow - The box-shadow value
|
||||
/// @example
|
||||
/// @include box-shadow;
|
||||
/// @include box-shadow(hover);
|
||||
/// @include box-shadow(never);
|
||||
/// @include box-shadow(always, 0 4px 8px rgba(0, 0, 0, 0.15));
|
||||
@mixin box-shadow(
|
||||
$when: always,
|
||||
$shadow: (0 1px 2px -2px rgba(0, 0, 0, 0.08), 0 3px 6px 0 rgba(0, 0, 0, 0.06), 0 5px 12px 4px rgba(0, 0, 0, 0.04))
|
||||
) {
|
||||
@if $when == always {
|
||||
box-shadow: $shadow;
|
||||
} @else if $when == hover {
|
||||
transition: box-shadow 0.3s fixit-var(bezier);
|
||||
|
||||
&:hover {
|
||||
box-shadow: $shadow;
|
||||
}
|
||||
} @else if $when == never {
|
||||
box-shadow: none;
|
||||
} @else {
|
||||
@warn "box-shadow mixin: unknown $when value '#{$when}'. Expected always, hover, or never.";
|
||||
}
|
||||
}
|
||||
@@ -1,8 +1,10 @@
|
||||
@import 'theme-vars';
|
||||
@import 'blur';
|
||||
@import 'border-radius';
|
||||
@import 'box-shadow';
|
||||
@import 'compatibility';
|
||||
@import 'link';
|
||||
@import 'loading';
|
||||
@import 'media';
|
||||
@import 'scrollbar-width';
|
||||
@import 'theme-vars';
|
||||
@import 'z-index';
|
||||
|
||||
@@ -0,0 +1,100 @@
|
||||
// Responsive breakpoints matching the project's media query design
|
||||
// ┌──────────────────────────────────────────────────────┐
|
||||
// │ xs │ sm │ md │ lg │ xl │
|
||||
// │ ≤680 │ 681–960 │ 961–1200 │ 1201–1440 │ ≥1441 │
|
||||
// └──────────────────────────────────────────────────────┘
|
||||
$breakpoints: (
|
||||
'xs': (
|
||||
'min': null,
|
||||
'max': 680px,
|
||||
),
|
||||
'sm': (
|
||||
'min': 681px,
|
||||
'max': 960px,
|
||||
),
|
||||
'md': (
|
||||
'min': 961px,
|
||||
'max': 1200px,
|
||||
),
|
||||
'lg': (
|
||||
'min': 1201px,
|
||||
'max': 1440px,
|
||||
),
|
||||
'xl': (
|
||||
'min': 1441px,
|
||||
'max': null,
|
||||
),
|
||||
) !default;
|
||||
|
||||
/// Apply styles within a named media target.
|
||||
/// @param {String} $target - Target name: xs | sm | md | lg | xl | print | reduce-motion
|
||||
/// @param {String} $direction - Range direction: only | up | down [default: only]
|
||||
///
|
||||
/// @example
|
||||
/// // xs: ≤ 680px
|
||||
/// @include media('xs') { ... }
|
||||
/// // sm: 681px – 960px
|
||||
/// @include media('sm') { ... }
|
||||
/// // md and above: ≥ 961px
|
||||
/// @include media('md', 'up') { ... }
|
||||
/// // lg and below: ≤ 1440px
|
||||
/// @include media('lg', 'down') { ... }
|
||||
/// // print media
|
||||
/// @include media('print') { ... }
|
||||
/// // reduced motion
|
||||
/// @include media('reduce-motion') { ... }
|
||||
@mixin media($target, $direction: 'only') {
|
||||
@if $target == 'print' {
|
||||
@media only print {
|
||||
@content;
|
||||
}
|
||||
} @else if $target == 'reduce-motion' {
|
||||
@media screen and (prefers-reduced-motion: reduce) {
|
||||
@content;
|
||||
}
|
||||
} @else {
|
||||
$range: map-get($breakpoints, $target);
|
||||
$min: if($range != null, map-get($range, 'min'), null);
|
||||
$max: if($range != null, map-get($range, 'max'), null);
|
||||
|
||||
@if $range == null {
|
||||
@warn "Unknown media target `#{$target}`. Expected: xs, sm, md, lg, xl, print, reduce-motion.";
|
||||
} @else if $direction == 'only' {
|
||||
@if $min == null {
|
||||
// xs: max-width only, no lower bound
|
||||
@media only screen and (max-width: $max) {
|
||||
@content;
|
||||
}
|
||||
} @else if $max == null {
|
||||
// xl: min-width only, no upper bound
|
||||
@media only screen and (min-width: $min) {
|
||||
@content;
|
||||
}
|
||||
} @else {
|
||||
@media only screen and (min-width: $min) and (max-width: $max) {
|
||||
@content;
|
||||
}
|
||||
}
|
||||
} @else if $direction == 'up' {
|
||||
@if $min == null {
|
||||
// xs-up: no lower bound, always applies
|
||||
@content;
|
||||
} @else {
|
||||
@media only screen and (min-width: $min) {
|
||||
@content;
|
||||
}
|
||||
}
|
||||
} @else if $direction == 'down' {
|
||||
@if $max == null {
|
||||
// xl-down: no upper bound, always applies
|
||||
@content;
|
||||
} @else {
|
||||
@media only screen and (max-width: $max) {
|
||||
@content;
|
||||
}
|
||||
}
|
||||
} @else {
|
||||
@warn "Unknown direction `#{$direction}`. Expected: only, up, down.";
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -55,6 +55,9 @@
|
||||
scrollbar-width: thin,
|
||||
scrollbar-width-legacy: 12px,
|
||||
|
||||
// Animations
|
||||
bezier: cubic-bezier(.4, 0, .2, 1),
|
||||
|
||||
// Other utilities
|
||||
breadcrumb-height: 0px,
|
||||
divider-edge-weak: linear-gradient(to right, transparent, fixit-var(divider-bg, #d0d0d5), transparent),
|
||||
@@ -74,6 +77,7 @@
|
||||
// Global
|
||||
global-background-color: (light: $global-background-color, dark: $global-background-color-dark),
|
||||
global-font-color: (light: $global-font-color, dark: $global-font-color-dark),
|
||||
global-font-secondary-color: (light: $global-font-secondary-color, dark: $global-font-secondary-color-dark),
|
||||
global-placeholder-color: (light: $global-placeholder-color, dark: $global-placeholder-color-dark),
|
||||
global-link-color: (light: $global-link-color, dark: $global-link-color-dark),
|
||||
global-link-hover-color: (light: $global-link-hover-color, dark: $global-link-hover-color-dark),
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
body {
|
||||
.cell-tooltip {
|
||||
--tooltip-bg: #ffffff;
|
||||
--tooltip-color: #0f172a;
|
||||
|
||||
[data-theme=dark] & {
|
||||
--tooltip-bg: #39393c;
|
||||
--tooltip-color: #e2e2e6;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -17,7 +17,6 @@
|
||||
transition: color 0.4s ease;
|
||||
@include border-radius(full);
|
||||
@include blur;
|
||||
@extend .print-d-none;
|
||||
|
||||
[data-theme=dark] & {
|
||||
background-color: darken($header-background-color-dark, 3%);
|
||||
@@ -34,6 +33,10 @@
|
||||
&:hover {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
@include media('print') {
|
||||
display: none !important;
|
||||
}
|
||||
}
|
||||
|
||||
.back-to-top {
|
||||
|
||||
@@ -17,11 +17,11 @@
|
||||
position: fixed;
|
||||
top: 0;
|
||||
@include z-index(fixed);
|
||||
@extend .print-d-none;
|
||||
|
||||
:hover .octo-arm {
|
||||
animation: octocat-wave 560ms ease-in-out;
|
||||
}
|
||||
|
||||
svg {
|
||||
width: fixit-var(header-height);
|
||||
height: fixit-var(header-height);
|
||||
@@ -29,6 +29,7 @@
|
||||
color: fixit-var(github-corner-color);
|
||||
fill: fixit-var(github-corner-fill);
|
||||
}
|
||||
|
||||
&.left {
|
||||
left: 0;
|
||||
|
||||
@@ -36,7 +37,12 @@
|
||||
transform: scale(-1, 1);
|
||||
}
|
||||
}
|
||||
|
||||
&.right {
|
||||
right: 0;
|
||||
}
|
||||
|
||||
@include media('print') {
|
||||
display: none !important;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
@import 'cell-tooltip';
|
||||
@import 'cookieconsent';
|
||||
@import 'details';
|
||||
@import 'fixed-button';
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
#postChat_button,
|
||||
.post-TianliGPT {
|
||||
@extend .print-d-none;
|
||||
@include media('print') {
|
||||
display: none !important;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,9 +4,12 @@
|
||||
background-color: fixit-var(bg-progress, fixit-var(success));
|
||||
position: fixed;
|
||||
@include z-index(fixed);
|
||||
@extend .print-d-none;
|
||||
|
||||
|
||||
[data-theme=dark] & {
|
||||
background-color: fixit-var(bg-progress-dark, fixit-var(global-font-color));
|
||||
}
|
||||
|
||||
@include media('print') {
|
||||
display: none !important;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +1,12 @@
|
||||
/* Modern browsers with `scrollbar-*` support (high priority) */
|
||||
@supports (scrollbar-width: auto) {
|
||||
* {
|
||||
scrollbar-color: fixit-var(scrollbar-thumb-color) fixit-var(scrollbar-track-color);;
|
||||
scrollbar-color: fixit-var(scrollbar-thumb-color) fixit-var(scrollbar-track-color);
|
||||
scrollbar-width: fixit-var(scrollbar-width);
|
||||
|
||||
@include media('print') {
|
||||
scrollbar-width: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,6 +15,10 @@
|
||||
height: fixit-var(scrollbar-width-legacy);
|
||||
width: fixit-var(scrollbar-width-legacy);
|
||||
overflow: visible;
|
||||
|
||||
@include media('print') {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
::-webkit-scrollbar-button {
|
||||
height: 0;
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
white-space: pre-wrap;
|
||||
}
|
||||
|
||||
--ti-cursor-color: fixit-var(global-font-color);
|
||||
--ti-cursor-font-family: fixit-var(global-font-family);
|
||||
--ti-cursor-transform: translateX(0);
|
||||
}
|
||||
|
||||
@@ -38,6 +38,10 @@
|
||||
padding-right: 1rem;
|
||||
}
|
||||
}
|
||||
|
||||
@include media('xs') {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -5,10 +5,27 @@
|
||||
.content {
|
||||
[id] {
|
||||
scroll-margin-top: fixit-var(scroll-mt);
|
||||
|
||||
[data-header-desktop='normal'] & {
|
||||
@include media('xs', 'up') {
|
||||
@include set-fixit-var(scroll-mt, fixit-var(global-scroll-margin-top));
|
||||
}
|
||||
}
|
||||
|
||||
[data-header-mobile='normal'] & {
|
||||
@include media('xs') {
|
||||
@include set-fixit-var(scroll-mt, fixit-var(global-scroll-margin-top));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@include blur;
|
||||
|
||||
@include media('print') {
|
||||
width: 100% !important;
|
||||
padding-top: 0 !important;
|
||||
}
|
||||
}
|
||||
|
||||
@import '_single';
|
||||
@@ -17,5 +34,4 @@
|
||||
@import 'home';
|
||||
@import '404';
|
||||
@import 'offline';
|
||||
@import "media";
|
||||
@import "patch";
|
||||
|
||||
@@ -1,272 +0,0 @@
|
||||
@media only screen and (min-width: 1441px) {
|
||||
%page-style {
|
||||
width: ROUND(60%, 2px);
|
||||
max-width: 1200px;
|
||||
|
||||
[data-page-style='wide'] & {
|
||||
width: ROUND(64%, 2px);
|
||||
max-width: 1600px;
|
||||
}
|
||||
|
||||
[data-page-style='narrow'] & {
|
||||
max-width: 800px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 1440px) {
|
||||
%page-style {
|
||||
width: ROUND(56%, 2px);
|
||||
|
||||
[data-page-style='wide'] & {
|
||||
width: ROUND(60%, 2px);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@media only screen and (min-width: 1201px) {
|
||||
.single .content {
|
||||
:is(.code-block, .diagram-container) {
|
||||
:is(.code-copy-btn, .diagram-copy-btn) {
|
||||
display: none;
|
||||
opacity: 0;
|
||||
transform: scale(0.9);
|
||||
transition:
|
||||
display 0.25s allow-discrete,
|
||||
opacity 0.25s,
|
||||
transform 0.25s,
|
||||
color 0.1s,
|
||||
background-color 0.1s,
|
||||
box-shadow 0.1s,
|
||||
border-color 0.1s;
|
||||
}
|
||||
&:hover {
|
||||
:is(.code-copy-btn, .diagram-copy-btn) {
|
||||
display: block;
|
||||
opacity: 1;
|
||||
transform: scale(1);
|
||||
@starting-style {
|
||||
opacity: 0;
|
||||
transform: scale(0.9);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 1200px) {
|
||||
%page-style {
|
||||
width: ROUND(52%, 2px);
|
||||
|
||||
[data-page-style='wide'] & {
|
||||
width: ROUND(56%, 2px);
|
||||
}
|
||||
}
|
||||
|
||||
#header-desktop .header-wrapper {
|
||||
padding-right: 1rem;
|
||||
}
|
||||
|
||||
.search-dropdown.desktop {
|
||||
right: 1rem;
|
||||
}
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 1200px) and (min-width: 961px) {
|
||||
.wrapper {
|
||||
main {
|
||||
// Hide empty aside and expand article when only one aside is empty
|
||||
aside:first-child:empty:has(~ aside:not(:empty)),
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
// Adjust breadcrumb flex when asides are empty
|
||||
&:has(main > aside:first-child:empty ~ aside:not(:empty)) .breadcrumb-container {
|
||||
.breadcrumb {
|
||||
flex: 3;
|
||||
}
|
||||
|
||||
&::before {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
&:has(main > aside:first-child:not(:empty) ~ aside#toc-auto:empty) .breadcrumb-container {
|
||||
.breadcrumb {
|
||||
flex: 3;
|
||||
}
|
||||
|
||||
&::after {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 960px) {
|
||||
%page-style {
|
||||
width: ROUND(80%, 2px) !important;
|
||||
}
|
||||
|
||||
aside,
|
||||
.breadcrumb-container::before,
|
||||
.breadcrumb-container::after {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
#toc-static {
|
||||
display: block;
|
||||
}
|
||||
|
||||
#header-desktop .header-wrapper {
|
||||
padding-left: 1rem;
|
||||
}
|
||||
}
|
||||
|
||||
@media only screen and (min-width: 681px) {
|
||||
.d-none-desktop {
|
||||
display: none;
|
||||
}
|
||||
.breadcrumb-container.sticky {
|
||||
body:not([data-header-desktop='sticky']) & {
|
||||
top: 0;
|
||||
}
|
||||
// adjust the scroll margin top of the content anchors on the page
|
||||
body:not([data-header-desktop='auto']) &+.fi-container .content [id] {
|
||||
scroll-margin-top: calc(#{fixit-var(scroll-mt)} + #{fixit-var(breadcrumb-height)});
|
||||
}
|
||||
}
|
||||
|
||||
[data-header-desktop='normal'] .page .content [id] {
|
||||
@include set-fixit-var(scroll-mt, fixit-var(global-scroll-margin-top));
|
||||
}
|
||||
[data-header-desktop='normal'] {
|
||||
#toc-auto,
|
||||
.aside-collection {
|
||||
top: 0;
|
||||
}
|
||||
}
|
||||
[data-header-desktop='sticky'] {
|
||||
.page .content .highlight.code-block .code-header {
|
||||
top: calc(#{fixit-var(header-height)} + #{fixit-var(breadcrumb-height)});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 680px) {
|
||||
#header-desktop {
|
||||
display: none;
|
||||
}
|
||||
|
||||
#header-mobile {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.d-none-mobile {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
.breadcrumb-container.sticky {
|
||||
body:not([data-header-mobile='sticky']) & {
|
||||
top: 0;
|
||||
}
|
||||
// adjust the scroll margin top of the content anchors on the page
|
||||
body:not([data-header-mobile='auto']) &+.fi-container .content [id] {
|
||||
scroll-margin-top: calc(#{fixit-var(scroll-mt)} + #{fixit-var(breadcrumb-height)});
|
||||
}
|
||||
}
|
||||
|
||||
[data-header-mobile='normal'] .page .content [id] {
|
||||
@include set-fixit-var(scroll-mt, fixit-var(global-scroll-margin-top));
|
||||
}
|
||||
[data-header-mobile='normal'] {
|
||||
#toc-auto,
|
||||
.aside-collection {
|
||||
top: 0;
|
||||
}
|
||||
}
|
||||
[data-header-mobile='sticky'] {
|
||||
.page .content .highlight.code-block .code-header {
|
||||
top: calc(#{fixit-var(header-height)} + #{fixit-var(breadcrumb-height)});
|
||||
}
|
||||
}
|
||||
|
||||
%page-style {
|
||||
width: 100% !important;
|
||||
}
|
||||
|
||||
.page {
|
||||
.taxonomy-cards {
|
||||
.card-item {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
footer {
|
||||
.footer-container {
|
||||
font-size: 0.618rem;
|
||||
}
|
||||
}
|
||||
|
||||
.pagination .page-item {
|
||||
margin: 0 5px;
|
||||
}
|
||||
}
|
||||
|
||||
@media only print {
|
||||
|
||||
// 确保打印时没有 scrollbar
|
||||
:root {
|
||||
@include set-fixit-var(scrollbar-width, none !important);
|
||||
}
|
||||
|
||||
*::-webkit-scrollbar {
|
||||
display: none !important;
|
||||
width: 0 !important;
|
||||
height: 0 !important;
|
||||
}
|
||||
|
||||
.scrollbar-thin {
|
||||
scrollbar-width: none !important;
|
||||
-ms-overflow-style: none !important;
|
||||
}
|
||||
|
||||
@page {
|
||||
size: A4 portrait;
|
||||
margin: 1.27cm;
|
||||
}
|
||||
|
||||
.page {
|
||||
width: 100% !important;
|
||||
padding-top: 0 !important;
|
||||
|
||||
&.single {
|
||||
.single-title,
|
||||
.single-subtitle,
|
||||
.post-meta {
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
|
||||
h1:not(.single-title),
|
||||
.page-break-before {
|
||||
page-break-before: always;
|
||||
}
|
||||
|
||||
.page-break-after {
|
||||
page-break-after: always;
|
||||
}
|
||||
}
|
||||
|
||||
.print-d-none {
|
||||
display: none !important;
|
||||
}
|
||||
}
|
||||
@@ -19,56 +19,6 @@
|
||||
animation-name: #{$prefix}pulse !important;
|
||||
}
|
||||
|
||||
/**
|
||||
* After the CSS round() function exceeds 90% browser support in the future, this code can be removed
|
||||
* See https://caniuse.com/mdn-css_types_round
|
||||
*/
|
||||
@supports not (width: ROUND(60%, 2px)) {
|
||||
@media only screen and (min-width: 1441px) {
|
||||
%page-style {
|
||||
width: 60%;
|
||||
max-width: 1200px;
|
||||
|
||||
[data-page-style='wide'] & {
|
||||
width: 64%;
|
||||
max-width: 1600px;
|
||||
}
|
||||
|
||||
[data-page-style='narrow'] & {
|
||||
max-width: 800px;
|
||||
}
|
||||
}
|
||||
}
|
||||
@media only screen and (max-width: 1440px) {
|
||||
%page-style {
|
||||
width: 56%;
|
||||
|
||||
[data-page-style='wide'] & {
|
||||
width: 60%;
|
||||
}
|
||||
}
|
||||
}
|
||||
@media only screen and (max-width: 1200px) {
|
||||
%page-style {
|
||||
width: 52%;
|
||||
|
||||
[data-page-style='wide'] & {
|
||||
width: 56%;
|
||||
}
|
||||
}
|
||||
}
|
||||
@media only screen and (max-width: 960px) {
|
||||
%page-style {
|
||||
width: 80% !important;
|
||||
}
|
||||
}
|
||||
@media only screen and (max-width: 680px) {
|
||||
%page-style {
|
||||
width: 100% !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// fix ZenUML intersection height error
|
||||
#zenuml-intersection-detector-container {
|
||||
display: none;
|
||||
|
||||
@@ -132,6 +132,14 @@ pre {
|
||||
|
||||
// code fences blocks wrapped
|
||||
&.code-block {
|
||||
&[data-shadow='always'] {
|
||||
@include box-shadow(always);
|
||||
}
|
||||
|
||||
&[data-shadow='hover'] {
|
||||
@include box-shadow(hover);
|
||||
}
|
||||
|
||||
&.instant-height .code-wrapper {
|
||||
transition: height 0s !important;
|
||||
}
|
||||
@@ -229,7 +237,10 @@ pre {
|
||||
|
||||
[role='button'] {
|
||||
padding: 0.4rem;
|
||||
@extend .print-d-none;
|
||||
|
||||
@include media('print') {
|
||||
display: none !important;
|
||||
}
|
||||
}
|
||||
|
||||
.copy-btn[data-copied] .fa-clone {
|
||||
@@ -256,6 +267,22 @@ pre {
|
||||
@include set-fixit-var(code-type, '"#{$text}"');
|
||||
}
|
||||
}
|
||||
|
||||
[data-header-desktop='sticky'] & {
|
||||
@include media('sm', 'up') {
|
||||
top: calc(#{fixit-var(header-height)} + #{fixit-var(breadcrumb-height)});
|
||||
}
|
||||
}
|
||||
|
||||
[data-header-mobile='sticky'] & {
|
||||
@include media('xs') {
|
||||
top: calc(#{fixit-var(header-height)} + #{fixit-var(breadcrumb-height)});
|
||||
}
|
||||
}
|
||||
|
||||
@include media('print') {
|
||||
top: initial !important;
|
||||
}
|
||||
}
|
||||
|
||||
&.line-wrapping .line-wrap-btn,
|
||||
@@ -267,10 +294,7 @@ pre {
|
||||
|
||||
&.is-fullscreen {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
inset: 0;
|
||||
margin: 0 !important;
|
||||
overflow: auto;
|
||||
@include z-index(fixed, 1);
|
||||
@@ -359,7 +383,7 @@ pre {
|
||||
display: inline;
|
||||
}
|
||||
|
||||
.code-header [role='button']:not(.ellipses-btn),
|
||||
.code-header .action-btn,
|
||||
.code-expand-btn {
|
||||
display: none;
|
||||
}
|
||||
@@ -559,3 +583,153 @@ pre {
|
||||
@import './_code-syntax/github-dark-dimmed';
|
||||
}
|
||||
}
|
||||
|
||||
// Code Tabs
|
||||
.code-tabs {
|
||||
margin: 0.5rem 0;
|
||||
background-color: fixit-var(code-block-background-color);
|
||||
border: 1px solid fixit-var(global-border-color);
|
||||
@include border-radius;
|
||||
|
||||
&[data-shadow='always'] {
|
||||
@include box-shadow(always);
|
||||
}
|
||||
|
||||
&[data-shadow='hover'] {
|
||||
@include box-shadow(hover);
|
||||
}
|
||||
|
||||
&:has(.code-block.active .code-expand-btn) {
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
&:has(.code-block.active.line-wrapping) .tabs-actions .line-wrap-btn,
|
||||
&:has(.code-block.active:not(.line-nos-hidden)) .tabs-actions .line-nos-btn,
|
||||
&:has(.code-block.active [contenteditable='true']) .tabs-actions .edit-btn {
|
||||
color: fixit-var(global-link-hover-color);
|
||||
}
|
||||
|
||||
.tabs-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
line-height: 1.4em;
|
||||
font-size: fixit-var(code-block-font-size);
|
||||
color: fixit-var(code-header-color);
|
||||
background-color: fixit-var(code-header-background-color);
|
||||
position: sticky;
|
||||
top: fixit-var(breadcrumb-height);
|
||||
@include z-index(base);
|
||||
@include border-radius(top);
|
||||
|
||||
.tabs-items {
|
||||
display: flex;
|
||||
overflow-x: auto;
|
||||
position: relative;
|
||||
@include border-radius(top);
|
||||
|
||||
.before-tabs {
|
||||
padding: 0.4rem 0.6rem;
|
||||
white-space: nowrap;
|
||||
color: fixit-var(global-font-color);
|
||||
}
|
||||
|
||||
.tab-item {
|
||||
padding: 0.4rem 0.8rem;
|
||||
cursor: pointer;
|
||||
font-weight: bold;
|
||||
color: fixit-var(secondary);
|
||||
white-space: nowrap;
|
||||
@include user-select(none);
|
||||
@include border-radius;
|
||||
|
||||
@include media('xs') {
|
||||
padding-inline: 0.6rem;
|
||||
}
|
||||
}
|
||||
|
||||
&:not(:has(.tab-item:hover)) .tab-item.active,
|
||||
.tab-item:hover {
|
||||
anchor-name: #{$rootPrefix}tab-hover;
|
||||
color: fixit-var(global-font-color);
|
||||
}
|
||||
|
||||
&::before {
|
||||
content: '';
|
||||
position-anchor: #{$rootPrefix}tab-hover;
|
||||
position: absolute;
|
||||
inset: anchor(inside);
|
||||
translate: 2px 2px;
|
||||
width: calc(anchor-size(width) - 0.25rem);
|
||||
height: calc(anchor-size(height) - 0.25rem);
|
||||
box-shadow: inset 0 0 1rem 0.25rem color-mix(in srgb, fixit-var(secondary) 30%, transparent);
|
||||
pointer-events: none;
|
||||
transition: 0.3s ease-in-out;
|
||||
@include border-radius;
|
||||
}
|
||||
}
|
||||
|
||||
.tabs-actions {
|
||||
display: flex;
|
||||
flex-shrink: 0;
|
||||
|
||||
.action-btn {
|
||||
padding: 0.4rem;
|
||||
|
||||
&:hover {
|
||||
color: fixit-var(global-link-hover-color);
|
||||
}
|
||||
|
||||
&.copy-btn[data-copied] .fa-clone {
|
||||
--fa: "\f00c";
|
||||
font-weight: 900;
|
||||
color: fixit-var(global-link-hover-color);
|
||||
}
|
||||
|
||||
&.download-btn[data-downloaded] .fa-download {
|
||||
--fa: "\f1ce";
|
||||
font-weight: 900;
|
||||
color: fixit-var(global-link-hover-color);
|
||||
}
|
||||
|
||||
&.fullscreen-btn {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[data-header-desktop='sticky'] & {
|
||||
@include media('sm', 'up') {
|
||||
top: calc(#{fixit-var(header-height)} + #{fixit-var(breadcrumb-height)});
|
||||
}
|
||||
}
|
||||
|
||||
[data-header-mobile='sticky'] & {
|
||||
@include media('xs') {
|
||||
top: calc(#{fixit-var(header-height)} + #{fixit-var(breadcrumb-height)});
|
||||
}
|
||||
}
|
||||
|
||||
@include media('print') {
|
||||
top: initial !important;
|
||||
}
|
||||
}
|
||||
|
||||
.tabs-content {
|
||||
.code-block {
|
||||
display: none;
|
||||
margin: 0;
|
||||
@include border-radius(bottom);
|
||||
@include box-shadow(never);
|
||||
|
||||
&.active {
|
||||
display: block;
|
||||
}
|
||||
|
||||
// hide original header
|
||||
.code-header {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,7 +7,6 @@
|
||||
position: relative;
|
||||
@include border-radius(0.5rem);
|
||||
@include user-select(none);
|
||||
@extend .print-d-none;
|
||||
|
||||
[data-theme=dark] & {
|
||||
background-color: lighten($global-background-color-dark, 3%);
|
||||
@@ -81,6 +80,10 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@include media('print') {
|
||||
display: none !important;
|
||||
}
|
||||
}
|
||||
|
||||
// Collection aside container
|
||||
@@ -90,7 +93,10 @@
|
||||
box-sizing: border-box;
|
||||
@include overflow-wrap(break-word);
|
||||
@include blur;
|
||||
@extend .print-d-none;
|
||||
|
||||
@include media('print') {
|
||||
display: none !important;
|
||||
}
|
||||
}
|
||||
|
||||
// Collection List
|
||||
|
||||
@@ -1,8 +1,11 @@
|
||||
>#comments {
|
||||
> #comments {
|
||||
padding: 2rem 0;
|
||||
@extend .print-d-none;
|
||||
|
||||
iframe {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
@include media('print') {
|
||||
display: none !important;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,17 +13,14 @@
|
||||
right: 0.5rem;
|
||||
line-height: 1;
|
||||
padding: 0.5rem;
|
||||
user-select: none;
|
||||
border: 1px solid;
|
||||
color: fixit-var(dcb-color, #25292e);
|
||||
border-color: fixit-var(dcb-border-color, #d1d9e0);
|
||||
background-color: fixit-var(dcb-background-color, #f6f8fa);
|
||||
box-shadow: fixit-var(dcb-box-shadow, 0px 1px 0px 0px #1f23280a);
|
||||
transition-duration: 0.1s;
|
||||
transition-property: color, background-color, box-shadow, border-color;
|
||||
@include user-select(none);
|
||||
@include border-radius;
|
||||
@include z-index(base);
|
||||
@extend .print-d-none;
|
||||
|
||||
[data-theme=dark] & {
|
||||
@include set-fixit-vars((
|
||||
@@ -59,4 +56,8 @@
|
||||
color: fixit-var(global-link-hover-color);
|
||||
}
|
||||
}
|
||||
|
||||
@include media('print') {
|
||||
display: none !important;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -42,10 +42,13 @@
|
||||
}
|
||||
|
||||
.post-info-share {
|
||||
@extend .print-d-none;
|
||||
a * {
|
||||
vertical-align: text-bottom;
|
||||
}
|
||||
|
||||
@include media('print') {
|
||||
display: none !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -57,7 +60,9 @@
|
||||
font-size: 0.9rem;
|
||||
|
||||
section:last-child {
|
||||
@extend .print-d-none;
|
||||
@include media('print') {
|
||||
display: none !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -81,7 +86,6 @@
|
||||
justify-content: space-between;
|
||||
gap: 1.5rem;
|
||||
padding-block: 0.5rem;
|
||||
@extend .print-d-none;
|
||||
|
||||
.post-nav-item {
|
||||
flex: 1;
|
||||
@@ -101,5 +105,9 @@
|
||||
transform: translateX(4px);
|
||||
}
|
||||
}
|
||||
|
||||
@include media('print') {
|
||||
display: none !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,6 +26,10 @@
|
||||
margin-right: 0.25em;
|
||||
color: fixit-var(success);
|
||||
}
|
||||
|
||||
@include media('print') {
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
|
||||
.single-subtitle {
|
||||
@@ -33,17 +37,16 @@
|
||||
font-size: 1.2rem;
|
||||
font-weight: normal;
|
||||
line-height: 1.15;
|
||||
|
||||
@include media('print') {
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
|
||||
.post-meta {
|
||||
font-size: 0.875rem;
|
||||
color: fixit-var(secondary);
|
||||
|
||||
.comment-visitors,
|
||||
.comment-count {
|
||||
@extend .print-d-none;
|
||||
}
|
||||
|
||||
span {
|
||||
display: inline-block;
|
||||
}
|
||||
@@ -64,6 +67,15 @@
|
||||
object-fit: cover;
|
||||
}
|
||||
}
|
||||
|
||||
@include media('print') {
|
||||
text-align: center;
|
||||
|
||||
.comment-visitors,
|
||||
.comment-count {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.featured-image {
|
||||
@@ -125,6 +137,7 @@
|
||||
|
||||
.content {
|
||||
margin-block: 1rem;
|
||||
|
||||
> h1,
|
||||
> h2 {
|
||||
font-size: 1.5em;
|
||||
@@ -458,6 +471,9 @@
|
||||
max-width: 100%;
|
||||
overflow-x: auto;
|
||||
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;
|
||||
}
|
||||
.katex-error {
|
||||
padding: 0.14em 0.28em;
|
||||
@@ -467,10 +483,13 @@
|
||||
}
|
||||
|
||||
// MathJax styles
|
||||
mjx-container[jax='CHTML'][display='true'] {
|
||||
max-width: 100%;
|
||||
overflow-x: auto;
|
||||
overflow-y: hidden;
|
||||
mjx-container[jax='CHTML'] {
|
||||
// formula Blocks
|
||||
&[display='true'] {
|
||||
max-width: 100%;
|
||||
overflow-x: auto;
|
||||
overflow-y: hidden;
|
||||
}
|
||||
|
||||
// fix the color of xypic diagrams
|
||||
mjx-xypic {
|
||||
@@ -487,6 +506,35 @@
|
||||
json-viewer[boxed] + json-viewer[boxed] {
|
||||
margin-top: 0.5rem;
|
||||
}
|
||||
|
||||
@include media('lg', 'up') {
|
||||
:is(.code-block, .diagram-container) {
|
||||
:is(.code-copy-btn, .diagram-copy-btn) {
|
||||
display: none;
|
||||
opacity: 0;
|
||||
transform: scale(0.9);
|
||||
transition:
|
||||
display 0.25s allow-discrete,
|
||||
opacity 0.25s,
|
||||
transform 0.25s,
|
||||
color 0.1s,
|
||||
background-color 0.1s,
|
||||
box-shadow 0.1s,
|
||||
border-color 0.1s;
|
||||
}
|
||||
&:hover {
|
||||
:is(.code-copy-btn, .diagram-copy-btn) {
|
||||
display: block;
|
||||
opacity: 1;
|
||||
transform: scale(1);
|
||||
@starting-style {
|
||||
opacity: 0;
|
||||
transform: scale(0.9);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@import '_shortcodes';
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
.post-reward {
|
||||
padding: 1rem;
|
||||
text-align: center;
|
||||
@extend .print-d-none;
|
||||
|
||||
&:has(.reward-ways:empty) {
|
||||
display: none;
|
||||
@@ -85,4 +84,8 @@
|
||||
border-color: lighten($reward-color, 5%);
|
||||
}
|
||||
}
|
||||
|
||||
@include media('print') {
|
||||
display: none !important;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -60,7 +60,9 @@
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.code-block .code-header {
|
||||
// reset sticky header position in code block when inside admonition
|
||||
.code-block .code-header,
|
||||
.code-tabs .tabs-header {
|
||||
top: initial !important;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -56,6 +56,10 @@
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.file-tree-label__inner {
|
||||
word-break: break-all;
|
||||
}
|
||||
|
||||
&.is-highlighted {
|
||||
color: fixit-var(global-link-hover-color);
|
||||
}
|
||||
|
||||
@@ -43,10 +43,10 @@
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
@media only print {
|
||||
@include media('print') {
|
||||
.mermaid,
|
||||
.mermaid-dark {
|
||||
display: none;
|
||||
display: none !important;
|
||||
}
|
||||
.mermaid-neutral {
|
||||
height: auto;
|
||||
|
||||
@@ -107,17 +107,15 @@ tab-container {
|
||||
|
||||
// ========================================
|
||||
// Style 1: Underline Style (Default)
|
||||
// Clean, minimal with bottom border
|
||||
// Clean, minimal with sliding indicator
|
||||
// ========================================
|
||||
&[type='underline'] {
|
||||
> .tab-button {
|
||||
@include set-fixit-var(tab-button-border-color, transparent);
|
||||
@include tab-button-size(0);
|
||||
|
||||
&[aria-selected='true'],
|
||||
&[aria-selected='false']:hover {
|
||||
color: fixit-var(primary);
|
||||
@include set-fixit-var(tab-button-border-color, fixit-var(primary));
|
||||
}
|
||||
|
||||
&[aria-selected='false']:hover {
|
||||
@@ -125,6 +123,29 @@ tab-container {
|
||||
}
|
||||
}
|
||||
|
||||
// Anchor tracking: follows hover, rests on active tab
|
||||
&:not(:has(> .tab-button:hover)) > .tab-button[aria-selected='true'],
|
||||
> .tab-button:hover {
|
||||
anchor-name: #{$rootPrefix}tab-underline-active;
|
||||
}
|
||||
|
||||
&::part(tablist-wrapper) {
|
||||
position: relative;
|
||||
|
||||
// Sliding box-shadow indicator via anchor positioning
|
||||
&::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
position-anchor: #{$rootPrefix}tab-underline-active;
|
||||
inset: anchor(inside);
|
||||
pointer-events: none;
|
||||
}
|
||||
}
|
||||
|
||||
&:has(> .tab-button[aria-selected='false']:hover)::part(tablist-wrapper)::before {
|
||||
opacity: 0.7;
|
||||
}
|
||||
|
||||
&[placement='top'],
|
||||
&[placement='bottom'] {
|
||||
gap: 0.5rem;
|
||||
@@ -134,9 +155,13 @@ tab-container {
|
||||
}
|
||||
|
||||
&::part(tablist-wrapper) {
|
||||
overflow-x: scroll;
|
||||
overflow-x: auto;
|
||||
overflow-y: hidden;
|
||||
}
|
||||
|
||||
&::part(tablist-wrapper)::before {
|
||||
transition: all 0.3s ease-in-out, top, bottom 0s;
|
||||
}
|
||||
}
|
||||
|
||||
&[placement='left'],
|
||||
@@ -144,6 +169,10 @@ tab-container {
|
||||
> .tab-button {
|
||||
@include tab-button-size(1rem, 0);
|
||||
}
|
||||
|
||||
&::part(tablist-wrapper)::before {
|
||||
transition: all 0.3s ease-in-out, left, right 0s;
|
||||
}
|
||||
}
|
||||
|
||||
// Top placement
|
||||
@@ -152,9 +181,9 @@ tab-container {
|
||||
border-bottom: 2px solid fixit-var(global-border-color);
|
||||
}
|
||||
|
||||
> .tab-button {
|
||||
&::part(tablist-wrapper)::before {
|
||||
box-shadow: inset 0 -2px 0 fixit-var(primary);
|
||||
translate: 0 2px;
|
||||
border-bottom: 2px solid fixit-var(tab-button-border-color);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -164,9 +193,9 @@ tab-container {
|
||||
border-top: 2px solid fixit-var(global-border-color);
|
||||
}
|
||||
|
||||
> .tab-button {
|
||||
&::part(tablist-wrapper)::before {
|
||||
box-shadow: inset 0 2px 0 fixit-var(primary);
|
||||
translate: 0 -2px;
|
||||
border-top: 2px solid fixit-var(tab-button-border-color);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -176,10 +205,13 @@ tab-container {
|
||||
border-right: 2px solid fixit-var(global-border-color);
|
||||
}
|
||||
|
||||
&::part(tablist-wrapper)::before {
|
||||
box-shadow: inset -2px 0 0 fixit-var(primary);
|
||||
translate: 2px 0;
|
||||
}
|
||||
|
||||
> .tab-button {
|
||||
text-align: right;
|
||||
translate: 2px 0;
|
||||
border-right: 2px solid fixit-var(tab-button-border-color);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -189,10 +221,13 @@ tab-container {
|
||||
border-left: 2px solid fixit-var(global-border-color);
|
||||
}
|
||||
|
||||
&::part(tablist-wrapper)::before {
|
||||
box-shadow: inset 2px 0 0 fixit-var(primary);
|
||||
translate: -2px 0;
|
||||
}
|
||||
|
||||
> .tab-button {
|
||||
text-align: left;
|
||||
translate: -2px 0;
|
||||
border-left: 2px solid fixit-var(tab-button-border-color);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -326,7 +361,7 @@ tab-container {
|
||||
&[placement='top'],
|
||||
&[placement='bottom'] {
|
||||
&::part(tablist-wrapper) {
|
||||
overflow-x: scroll;
|
||||
overflow-x: auto;
|
||||
overflow-y: hidden;
|
||||
}
|
||||
|
||||
@@ -436,7 +471,7 @@ tab-container {
|
||||
// ========================================
|
||||
&[type='segment'] {
|
||||
&::part(tablist-wrapper) {
|
||||
overflow-x: scroll;
|
||||
overflow-x: auto;
|
||||
overflow-y: hidden;
|
||||
}
|
||||
|
||||
@@ -451,8 +486,10 @@ tab-container {
|
||||
}
|
||||
|
||||
> .tab-button {
|
||||
position: relative;
|
||||
font-weight: 400;
|
||||
flex-grow: 1;
|
||||
@include z-index(base);
|
||||
|
||||
@include tab-button-size;
|
||||
@include border-radius;
|
||||
@@ -463,12 +500,26 @@ tab-container {
|
||||
}
|
||||
|
||||
&[aria-selected='true'] {
|
||||
background-color: fixit-var(global-background-color);
|
||||
box-shadow: 0 0.125rem 0.25rem rgba(0, 0, 0, 0.1);
|
||||
anchor-name: #{$rootPrefix}tab-segment-active;
|
||||
background-color: transparent;
|
||||
box-shadow: none;
|
||||
}
|
||||
}
|
||||
|
||||
[data-theme=dark] & {
|
||||
box-shadow: 0 0.125rem 0.25rem rgba(255, 255, 255, 0.1);
|
||||
}
|
||||
// Sliding active indicator via anchor positioning
|
||||
&::part(tablist-tab-wrapper)::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
pointer-events: none;
|
||||
position-anchor: #{$rootPrefix}tab-segment-active;
|
||||
inset: anchor(inside);
|
||||
background-color: fixit-var(global-background-color);
|
||||
box-shadow: 0 0.125rem 0.25rem rgba(0, 0, 0, 0.1);
|
||||
transition: all 0.15s ease, top, bottom 0s;
|
||||
@include border-radius;
|
||||
|
||||
[data-theme=dark] & {
|
||||
box-shadow: 0 0.125rem 0.25rem rgba(255, 255, 255, 0.1);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -531,3 +582,54 @@ tab-container {
|
||||
tab-container .tab-panel:not([hidden]) {
|
||||
animation: tabSlideIn 0.3s ease-out;
|
||||
}
|
||||
|
||||
// Firefox fallback: `::part(...)::before` is not supported.
|
||||
// We use a CSS variable to control the border color of the active tab button,
|
||||
// and apply the sliding effect directly on the button itself.
|
||||
@supports (-moz-appearance: none) {
|
||||
tab-container {
|
||||
&[type='underline'] {
|
||||
> .tab-button {
|
||||
@include set-fixit-var(tab-button-border-color, transparent);
|
||||
|
||||
&[aria-selected='true'],
|
||||
&[aria-selected='false']:hover {
|
||||
@include set-fixit-var(tab-button-border-color, fixit-var(primary));
|
||||
}
|
||||
}
|
||||
|
||||
&[placement='top'] > .tab-button {
|
||||
translate: 0 2px;
|
||||
border-bottom: 2px solid fixit-var(tab-button-border-color);
|
||||
}
|
||||
|
||||
&[placement='bottom'] > .tab-button {
|
||||
translate: 0 -2px;
|
||||
border-top: 2px solid fixit-var(tab-button-border-color);
|
||||
}
|
||||
|
||||
&[placement='left'] > .tab-button {
|
||||
translate: 2px 0;
|
||||
border-right: 2px solid fixit-var(tab-button-border-color);
|
||||
}
|
||||
|
||||
&[placement='right'] > .tab-button {
|
||||
translate: -2px 0;
|
||||
border-left: 2px solid fixit-var(tab-button-border-color);
|
||||
}
|
||||
}
|
||||
|
||||
&[type='segment'] {
|
||||
> .tab-button {
|
||||
&[aria-selected='true'] {
|
||||
background-color: fixit-var(global-background-color);
|
||||
box-shadow: 0 0.125rem 0.25rem rgba(0, 0, 0, 0.1);
|
||||
|
||||
[data-theme=dark] & {
|
||||
box-shadow: 0 0.125rem 0.25rem rgba(255, 255, 255, 0.1);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -94,7 +94,6 @@
|
||||
visibility: hidden;
|
||||
@include overflow-wrap(break-word);
|
||||
@include blur;
|
||||
@extend .print-d-none;
|
||||
|
||||
[data-header-desktop='normal'] & {
|
||||
@include set-fixit-var(scroll-mt, fixit-var(global-scroll-margin-top));
|
||||
@@ -157,6 +156,10 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@include media('print') {
|
||||
display: none !important;
|
||||
}
|
||||
}
|
||||
|
||||
#toc-static {
|
||||
@@ -211,6 +214,10 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@include media('sm', 'down') {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
|
||||
#toc-dialog {
|
||||
|
||||
@@ -28,6 +28,10 @@ $global-background-color-dark: #292a2e !default;
|
||||
$global-font-color: #161209 !default;
|
||||
$global-font-color-dark: #b1b1ba !default;
|
||||
|
||||
// Color of the secondary text
|
||||
$global-font-secondary-color: #8b949e !default;
|
||||
$global-font-secondary-color-dark: #7d8792 !default;
|
||||
|
||||
// Color of the placeholder
|
||||
$global-placeholder-color: #b1b1ba !default;
|
||||
$global-placeholder-color-dark: #909092 !default;
|
||||
|
||||
@@ -19,6 +19,8 @@ libFiles:
|
||||
# autocomplete-js@0.38.1 https://github.com/algolia/autocomplete
|
||||
# TODO update autocompleteJS: '@algolia/autocomplete-js@1.7.1/dist/umd/index.production.js'
|
||||
autocompleteJS: autocomplete.js@0.38.1/dist/autocomplete.min.js
|
||||
# cell-tooltip@0.3.0 https://github.com/Lruihao/cell-tooltip
|
||||
cellTooltipJS: cell-tooltip@0.3.0/dist/cell-tooltip.umd.js
|
||||
# cell-watermark@1.0.3 https://github.com/Lruihao/watermark
|
||||
cellWatermarkJS: cell-watermark@1.0.3/src/watermark.min.js
|
||||
# cookieconsent@3.1.1 https://github.com/osano/cookieconsent
|
||||
@@ -67,8 +69,8 @@ libFiles:
|
||||
tabContainerElementJS: '@github/tab-container-element@4.8.2/dist/index.min.js'
|
||||
# twemoji@14.0.2 https://github.com/twitter/twemoji
|
||||
twemojiJS: twemoji@14.0.2/dist/twemoji.min.js
|
||||
# twikoo@1.6.44 https://github.com/imaegoo/twikoo
|
||||
twikooJS: twikoo@1.6.44/dist/twikoo.all.min.js
|
||||
# twikoo@1.7.3 https://github.com/imaegoo/twikoo
|
||||
twikooJS: twikoo@1.7.3/dist/twikoo.all.min.js
|
||||
# typeit@8.8.4 https://github.com/alexmacarthur/typeit
|
||||
typeitJS: typeit@8.8.4/dist/index.umd.js
|
||||
# valine@1.5.2 https://github.com/xCss/Valine
|
||||
|
||||
@@ -19,6 +19,8 @@ libFiles:
|
||||
# autocomplete-js@0.38.1 https://github.com/algolia/autocomplete
|
||||
# TODO update autocompleteJS: '@algolia/autocomplete-js@1.7.1/dist/umd/index.production.js'
|
||||
autocompleteJS: autocomplete.js@0.38.1/dist/autocomplete.min.js
|
||||
# cell-tooltip@0.3.0 https://github.com/Lruihao/cell-tooltip
|
||||
cellTooltipJS: cell-tooltip@0.3.0/dist/cell-tooltip.umd.js
|
||||
# cell-watermark@1.0.3 https://github.com/Lruihao/watermark
|
||||
cellWatermarkJS: cell-watermark@1.0.3/src/watermark.min.js
|
||||
# cookieconsent@3.1.1 https://github.com/osano/cookieconsent
|
||||
@@ -67,8 +69,8 @@ libFiles:
|
||||
tabContainerElementJS: '@github/tab-container-element@4.8.2/dist/index.js'
|
||||
# twemoji@14.0.2 https://github.com/twitter/twemoji
|
||||
twemojiJS: twemoji@14.0.2/dist/twemoji.min.js
|
||||
# twikoo@1.6.44 https://github.com/imaegoo/twikoo
|
||||
twikooJS: twikoo@1.6.44/dist/twikoo.all.min.js
|
||||
# twikoo@1.7.3 https://github.com/imaegoo/twikoo
|
||||
twikooJS: twikoo@1.7.3/dist/twikoo.all.min.js
|
||||
# typeit@8.8.4 https://github.com/alexmacarthur/typeit
|
||||
typeitJS: typeit@8.8.4/dist/index.umd.js
|
||||
# valine@1.5.2 https://github.com/xCss/Valine
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
* FileTree class to handle file tree interactions
|
||||
*/
|
||||
export default class FileTree {
|
||||
init(target = document) {
|
||||
static init(target = document) {
|
||||
target.querySelectorAll('.file-tree-toggle:not([data-init])').forEach((label) => {
|
||||
label.addEventListener('click', (e) => {
|
||||
e.stopPropagation()
|
||||
@@ -18,7 +18,7 @@ export default class FileTree {
|
||||
this.updateLineHeight(target)
|
||||
}
|
||||
|
||||
updateLineHeight(target = document) {
|
||||
static updateLineHeight(target = document) {
|
||||
const uls = target.querySelectorAll('.file-tree .file-tree')
|
||||
uls.forEach((ul) => {
|
||||
const parentItem = ul.closest('.file-tree-item.is-collapsed')
|
||||
@@ -47,4 +47,14 @@ export default class FileTree {
|
||||
ul.style.setProperty('--fi-file-tree-line-height', `${height}px`)
|
||||
})
|
||||
}
|
||||
|
||||
static expandAll(target = document) {
|
||||
target.querySelectorAll('.file-tree-folder').forEach(folder => folder.classList.remove('is-collapsed'))
|
||||
this.updateLineHeight(target)
|
||||
}
|
||||
|
||||
static collapseAll(target = document) {
|
||||
target.querySelectorAll('.file-tree-folder').forEach(folder => folder.classList.add('is-collapsed'))
|
||||
this.updateLineHeight(target)
|
||||
}
|
||||
}
|
||||
|
||||
+320
-76
@@ -1,21 +1,33 @@
|
||||
// TODO use ESLint to check the code style
|
||||
// 按需加载主题内部库和功能模块
|
||||
import Util from './util';
|
||||
import {
|
||||
forEach,
|
||||
getScrollTop,
|
||||
isMobile,
|
||||
isTocStatic,
|
||||
animateCSS,
|
||||
isValidDate,
|
||||
scrollIntoView,
|
||||
getStagingDOM,
|
||||
createCopyText,
|
||||
isObjectLiteral,
|
||||
HTMLEscape,
|
||||
} from './utils/common';
|
||||
import FileTree from './lib/file-tree.js'
|
||||
|
||||
const copyText = createCopyText();
|
||||
|
||||
class FixIt {
|
||||
constructor() {
|
||||
this.config = window.config;
|
||||
this.isDark = document.documentElement.dataset.theme === 'dark';
|
||||
this.util = new Util();
|
||||
this.fileTree = new FileTree();
|
||||
this.newScrollTop = this.util.getScrollTop();
|
||||
this.newScrollTop = getScrollTop();
|
||||
this.oldScrollTop = this.newScrollTop;
|
||||
this.scrollEventSet = new Set();
|
||||
this.resizeEventSet = new Set();
|
||||
this.switchThemeEventSet = new Set();
|
||||
this.clickMaskEventSet = new Set();
|
||||
this.beforeprintEventSet = new Set();
|
||||
this.afterprintEventSet = new Set();
|
||||
this.disableScrollEvent = false;
|
||||
window.objectFitImages && objectFitImages();
|
||||
}
|
||||
@@ -31,7 +43,7 @@ class FixIt {
|
||||
}
|
||||
|
||||
initSVGIcon() {
|
||||
this.util.forEach(document.querySelectorAll('[data-svg-src]'), ($icon) => {
|
||||
forEach(document.querySelectorAll('[data-svg-src]'), ($icon) => {
|
||||
fetch($icon.dataset.svgSrc)
|
||||
.then((response) => response.text())
|
||||
.then((svg) => {
|
||||
@@ -60,7 +72,7 @@ class FixIt {
|
||||
}
|
||||
|
||||
initMenuDesktop() {
|
||||
this.util.forEach(document.querySelectorAll('.has-children'), ($item) => {
|
||||
forEach(document.querySelectorAll('.has-children'), ($item) => {
|
||||
$item.querySelector('.sub-menu').style.minWidth = `${$item.offsetWidth - 8}px`;
|
||||
});
|
||||
}
|
||||
@@ -80,7 +92,7 @@ class FixIt {
|
||||
});
|
||||
this.clickMaskEventSet.add(this._menuMobileOnClickMask);
|
||||
// add nested menu toggler
|
||||
this.util.forEach(document.querySelectorAll('.menu-item>.nested-item'), ($nestedItem) => {
|
||||
forEach(document.querySelectorAll('.menu-item>.nested-item'), ($nestedItem) => {
|
||||
$nestedItem.addEventListener('click', function () {
|
||||
this.parentNode.querySelector('.sub-menu').classList.toggle('open');
|
||||
this.querySelector('.dropdown-icon').classList.toggle('open');
|
||||
@@ -89,7 +101,7 @@ class FixIt {
|
||||
}
|
||||
|
||||
initSwitchTheme() {
|
||||
this.util.forEach(document.getElementsByClassName('theme-switch'), ($themeSwitch) => {
|
||||
forEach(document.getElementsByClassName('theme-switch'), ($themeSwitch) => {
|
||||
$themeSwitch.addEventListener('click', () => {
|
||||
document.documentElement.dataset.theme = this.isDark ? 'light' : 'dark';
|
||||
document.documentElement.style.setProperty('color-scheme', this.isDark ? 'light' : 'dark');
|
||||
@@ -136,11 +148,11 @@ class FixIt {
|
||||
|
||||
initSearch() {
|
||||
const searchConfig = this.config.search;
|
||||
const isMobile = this.util.isMobile();
|
||||
const _isMobile = isMobile();
|
||||
if (
|
||||
!searchConfig ||
|
||||
(isMobile && this._searchMobileOnce) ||
|
||||
(!isMobile && this._searchDesktopOnce)
|
||||
(_isMobile && this._searchMobileOnce) ||
|
||||
(!_isMobile && this._searchDesktopOnce)
|
||||
)
|
||||
return;
|
||||
// Initialize default search config
|
||||
@@ -156,7 +168,7 @@ class FixIt {
|
||||
const ignoreLocation = searchConfig.ignoreLocation ?? false;
|
||||
const useExtendedSearch = searchConfig.useExtendedSearch ?? false;
|
||||
const ignoreFieldNorm = searchConfig.ignoreFieldNorm ?? false;
|
||||
const suffix = isMobile ? 'mobile' : 'desktop';
|
||||
const suffix = _isMobile ? 'mobile' : 'desktop';
|
||||
const $header = document.getElementById(`header-${suffix}`);
|
||||
const $searchInput = document.getElementById(`search-input-${suffix}`);
|
||||
const $searchToggle = document.getElementById(`search-toggle-${suffix}`);
|
||||
@@ -166,7 +178,7 @@ class FixIt {
|
||||
|
||||
// goto the PostChat panel rather than search results
|
||||
if (searchConfig.type === 'post-chat' && window.postChatUser) {
|
||||
if (isMobile) {
|
||||
if (_isMobile) {
|
||||
$searchInput.addEventListener('focus', () => {
|
||||
window.postChatUser.setSearchInput('');
|
||||
}, false);
|
||||
@@ -178,7 +190,7 @@ class FixIt {
|
||||
return;
|
||||
}
|
||||
|
||||
if (isMobile) {
|
||||
if (_isMobile) {
|
||||
this._searchMobileOnce = true;
|
||||
$searchInput.addEventListener('focus', () => {
|
||||
this.disableScrollEvent = true;
|
||||
@@ -343,7 +355,7 @@ class FixIt {
|
||||
templates: {
|
||||
suggestion: ({ title, uri, date, context }) =>
|
||||
`<div><a href="${uri}"><span class="suggestion-title">${title}</span></a><span class="suggestion-date">${date}</span></div><div class="suggestion-context">${context}</div>`,
|
||||
empty: ({ query }) => `<div class="search-empty">${searchConfig.noResultsFound}: <span class="search-query">"${this.util.HTMLEscape(query)}"</span></div>`,
|
||||
empty: ({ query }) => `<div class="search-empty">${searchConfig.noResultsFound}: <span class="search-query">"${HTMLEscape(query)}"</span></div>`,
|
||||
footer: ({ }) => {
|
||||
let searchType, icon, href;
|
||||
switch (searchConfig.type) {
|
||||
@@ -378,7 +390,7 @@ class FixIt {
|
||||
document.getElementById('mask')?.click();
|
||||
window.location.assign(suggestion.uri);
|
||||
});
|
||||
if (isMobile) {
|
||||
if (_isMobile) {
|
||||
this._searchMobile = autosearch;
|
||||
} else {
|
||||
this._searchDesktop = autosearch;
|
||||
@@ -396,7 +408,7 @@ class FixIt {
|
||||
}
|
||||
|
||||
initDetails(target = document) {
|
||||
this.util.forEach(target.querySelectorAll('.details:not(.disabled)'), ($details) => {
|
||||
forEach(target.querySelectorAll('.details:not(.disabled)'), ($details) => {
|
||||
const $summary = $details.querySelector('.details-summary');
|
||||
$summary.addEventListener('click', () => {
|
||||
$details.classList.toggle('open');
|
||||
@@ -438,14 +450,21 @@ class FixIt {
|
||||
const iswWrap = codeBlock.classList.contains('line-wrapping');
|
||||
const highlightLines = codeBlock.querySelectorAll('.hl');
|
||||
iswWrap && codeBlock.classList.toggle('line-wrapping');
|
||||
this.util.forEach(highlightLines, $hl => $hl.classList.toggle('hl'));
|
||||
this.util.copyText(codePreEl.innerText.trim()).then(() => {
|
||||
this.util.animateCSS(codePreEl, 'animate__flash');
|
||||
forEach(highlightLines, $hl => $hl.classList.toggle('hl'));
|
||||
copyText(codePreEl.innerText.trim()).then(() => {
|
||||
animateCSS(codePreEl, 'animate__flash');
|
||||
iswWrap && codeBlock.classList.toggle('line-wrapping');
|
||||
this.util.forEach(highlightLines, $hl => $hl.classList.toggle('hl'));
|
||||
forEach(highlightLines, $hl => $hl.classList.toggle('hl'));
|
||||
const copiedText = copyBtn.dataset.copiedText;
|
||||
const originalTitle = copyBtn.dataset.ctOriginalTitle;
|
||||
copyBtn.toggleAttribute('data-copied', true);
|
||||
copyBtn.dataset.ctTitle = copiedText;
|
||||
const instance = window.CellTooltip.getOrCreateInstance(copyBtn);
|
||||
instance.refresh();
|
||||
setTimeout(() => {
|
||||
copyBtn.toggleAttribute('data-copied', false);
|
||||
copyBtn.dataset.ctTitle = originalTitle;
|
||||
instance.hide();
|
||||
}, 2000);
|
||||
}, () => {
|
||||
console.error('Clipboard write failed!', 'Your browser does not support clipboard API!');
|
||||
@@ -464,15 +483,18 @@ class FixIt {
|
||||
if (!downloadBtn) return;
|
||||
downloadBtn.addEventListener('click', () => {
|
||||
const $codeHeader = codeBlock.querySelector('.code-header');
|
||||
const fileNameFromTitle = $codeHeader?.querySelector('.code-title')?.dataset.name?.trim();
|
||||
const name = codeBlock.dataset.name?.trim();
|
||||
const language = Array.from($codeHeader?.classList || []).find((className) => className.startsWith('language-'))?.replace('language-', '');
|
||||
const fallbackName = language && language !== 'fallback' ? `code.${language}` : 'code.txt';
|
||||
const fileName = (fileNameFromTitle || fallbackName).replace(/[\\/:*?"<>|\r\n]+/g, '-');
|
||||
const ext = language && language !== 'fallback' ? language : 'txt';
|
||||
const fallbackName = name
|
||||
? (name.includes('.') ? name : `${name}.${ext}`)
|
||||
: `code.${ext}`;
|
||||
const fileName = codeBlock.getAttribute('filename')?.trim();
|
||||
const blob = new Blob([codePreEl.innerText], { type: 'text/plain;charset=utf-8' });
|
||||
const url = URL.createObjectURL(blob);
|
||||
const link = document.createElement('a');
|
||||
link.href = url;
|
||||
link.download = fileName || fallbackName;
|
||||
link.download = (fileName || fallbackName).replace(/[\\/:*?"<>|\r\n]+/g, '-');
|
||||
document.body.appendChild(link);
|
||||
link.click();
|
||||
document.body.removeChild(link);
|
||||
@@ -543,7 +565,7 @@ class FixIt {
|
||||
*/
|
||||
initCodeWrapper() {
|
||||
const $codeBlocks = document.querySelectorAll('.code-block.highlight:not([data-init])');
|
||||
this.util.forEach($codeBlocks, ($codeBlock) => {
|
||||
forEach($codeBlocks, ($codeBlock) => {
|
||||
const $preElements = $codeBlock.querySelectorAll('pre.chroma');
|
||||
if (!$preElements.length) return;
|
||||
const $codePreEl = $preElements[$preElements.length - 1];
|
||||
@@ -583,7 +605,7 @@ class FixIt {
|
||||
$codePreEl.setAttribute('contenteditable', false);
|
||||
$codePreEl.blur();
|
||||
} else {
|
||||
this.util.forEach($codeBlock.querySelectorAll('.hl'), ($hl) => {
|
||||
forEach($codeBlock.querySelectorAll('.hl'), ($hl) => {
|
||||
$hl.classList.remove('hl');
|
||||
});
|
||||
$codeBlock.classList.add('is-expanded');
|
||||
@@ -596,19 +618,152 @@ class FixIt {
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* init code tabs
|
||||
*/
|
||||
initCodeTabs() {
|
||||
const $codeBlocks = document.querySelectorAll('.code-block[group]:not([data-tab-init])');
|
||||
const processed = new Set();
|
||||
|
||||
forEach($codeBlocks, ($block) => {
|
||||
if (processed.has($block)) return;
|
||||
|
||||
const groupName = $block.getAttribute('group');
|
||||
const $tabs = [];
|
||||
let $curr = $block;
|
||||
|
||||
// collect consecutive blocks with same group
|
||||
while ($curr && $curr.classList?.contains('code-block') && $curr.getAttribute('group') === groupName) {
|
||||
$tabs.push($curr);
|
||||
processed.add($curr);
|
||||
$curr = $curr.nextElementSibling;
|
||||
}
|
||||
|
||||
if ($tabs.length < 2) return;
|
||||
|
||||
// create DOM structure
|
||||
const $container = document.createElement('div');
|
||||
$container.className = 'code-tabs';
|
||||
|
||||
const $header = document.createElement('div');
|
||||
$header.className = 'tabs-header';
|
||||
|
||||
const $items = document.createElement('div');
|
||||
$items.className = 'tabs-items';
|
||||
|
||||
const $actions = document.createElement('div');
|
||||
$actions.className = 'tabs-actions';
|
||||
|
||||
$header.appendChild($items);
|
||||
$header.appendChild($actions);
|
||||
|
||||
const $content = document.createElement('div');
|
||||
$content.className = 'tabs-content';
|
||||
|
||||
// insert container before the first block
|
||||
const $firstBlock = $tabs[0];
|
||||
$firstBlock.parentNode.insertBefore($container, $firstBlock);
|
||||
|
||||
const activeTabIndex = $tabs.findIndex(tab => tab.classList.contains('active'));
|
||||
const langPref = window.localStorage.getItem('config_lang_perf');
|
||||
const hasCodeToggle = $tabs.some(tab => tab.dataset.codeToggle === 'true');
|
||||
const langPrefIndex = (langPref && hasCodeToggle) ? $tabs.findIndex(tab => tab.dataset.tabTitle.toLowerCase() === langPref) : -1;
|
||||
const resolvedIndex = langPrefIndex !== -1 ? langPrefIndex : activeTabIndex;
|
||||
const beforeTabs = $tabs[0]?.getAttribute('before_tabs');
|
||||
if (beforeTabs) {
|
||||
const $before = document.createElement('span');
|
||||
$before.className = 'before-tabs';
|
||||
$before.textContent = beforeTabs;
|
||||
$items.appendChild($before);
|
||||
}
|
||||
$tabs.forEach(($tab, index) => {
|
||||
const title = $tab.dataset.tabTitle || 'Code';
|
||||
const defaultActiveTab = resolvedIndex === -1 && index === 0;
|
||||
|
||||
// tab button
|
||||
const $btn = document.createElement('span');
|
||||
$btn.className = 'tab-item';
|
||||
if (defaultActiveTab) $btn.classList.add('active');
|
||||
$btn.textContent = title;
|
||||
$btn.dataset.index = index;
|
||||
$btn.title = title;
|
||||
|
||||
$btn.addEventListener('click', () => {
|
||||
// 1. restore buttons to the currently active tab
|
||||
const $activeTab = $tabs.find(t => t.classList.contains('active'));
|
||||
if ($activeTab) {
|
||||
const $activeHeader = $activeTab.querySelector('.code-header');
|
||||
if ($activeHeader) {
|
||||
Array.from($actions.children).forEach(btn => $activeHeader.appendChild(btn));
|
||||
}
|
||||
}
|
||||
|
||||
// 2. switch active tab UI
|
||||
$items.querySelectorAll('.tab-item').forEach(b => b.classList.remove('active'));
|
||||
$btn.classList.add('active');
|
||||
if ($tab.dataset.codeToggle === 'true') {
|
||||
window.localStorage.setItem('config_lang_perf', $tab.dataset.tabTitle.toLowerCase());
|
||||
const activeItems = document.querySelectorAll(`
|
||||
.tab-item[title="${$tab.dataset.tabTitle.toLowerCase()}"]:not(.active),
|
||||
.tab-item[title="${$tab.dataset.tabTitle.toUpperCase()}"]:not(.active)`
|
||||
);
|
||||
forEach(activeItems, t => t.click());
|
||||
}
|
||||
|
||||
// 3. switch content
|
||||
$tabs.forEach(b => b.classList.remove('active'));
|
||||
$tab.classList.add('active');
|
||||
|
||||
// 4. sync shadow mode data attribute
|
||||
const shadowMode = $tab?.dataset.shadow;
|
||||
if (shadowMode) {
|
||||
$container.dataset.shadow = shadowMode;
|
||||
} else {
|
||||
delete $container.dataset.shadow;
|
||||
}
|
||||
|
||||
// 5. move new buttons to actions
|
||||
const $codeHeader = $tab.querySelector('.code-header');
|
||||
if ($codeHeader) {
|
||||
$codeHeader.querySelectorAll('.action-btn').forEach(btn => $actions.appendChild(btn));
|
||||
}
|
||||
});
|
||||
$items.appendChild($btn);
|
||||
|
||||
// move block to content
|
||||
$tab.classList.toggle('active', resolvedIndex === index || defaultActiveTab);
|
||||
$tab.classList.remove('is-collapsed');
|
||||
$tab.classList.remove('d-none');
|
||||
$tab.dataset.tabInit = 'true';
|
||||
$content.appendChild($tab);
|
||||
});
|
||||
|
||||
$container.appendChild($header);
|
||||
$container.appendChild($content);
|
||||
|
||||
// initialize actions for the active tab
|
||||
if (resolvedIndex !== -1) {
|
||||
const $activeBtn = $items.querySelector(`.tab-item[data-index="${resolvedIndex}"]`);
|
||||
if ($activeBtn) $activeBtn.click();
|
||||
} else {
|
||||
$items.firstElementChild.click();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* init diagram copy button
|
||||
*/
|
||||
initDiagramCopyBtn() {
|
||||
const stagingDOM = this.util.getStagingDOM()
|
||||
this.util.forEach(document.querySelectorAll('.diagram-copy-btn'), ($btn) => {
|
||||
const stagingDOM = getStagingDOM()
|
||||
forEach(document.querySelectorAll('.diagram-copy-btn'), ($btn) => {
|
||||
$btn.addEventListener('click', () => {
|
||||
stagingDOM.stage($btn.parentElement.querySelector('template').content.cloneNode(true))
|
||||
let code = stagingDOM.contentAsText();
|
||||
try {
|
||||
code = JSON.stringify(JSON.parse(code), null, 2);
|
||||
} catch { }
|
||||
this.util.copyText(code).then(() => {
|
||||
copyText(code).then(() => {
|
||||
$btn.toggleAttribute('data-copied', true);
|
||||
setTimeout(() => {
|
||||
$btn.toggleAttribute('data-copied', false);
|
||||
@@ -632,10 +787,10 @@ class FixIt {
|
||||
const $tocLiElements = $tocContainer.getElementsByTagName('li');
|
||||
|
||||
// Remove all active classes
|
||||
this.util.forEach($tocLinkElements, ($tocLink) => {
|
||||
forEach($tocLinkElements, ($tocLink) => {
|
||||
$tocLink.classList.remove('active');
|
||||
});
|
||||
this.util.forEach($tocLiElements, ($tocLi) => {
|
||||
forEach($tocLiElements, ($tocLi) => {
|
||||
$tocLi.classList.remove('has-active');
|
||||
});
|
||||
|
||||
@@ -674,10 +829,10 @@ class FixIt {
|
||||
// TOC Drawer Button Visibility
|
||||
const openButton = document.querySelector("#toc-drawer-button");
|
||||
if (openButton) {
|
||||
openButton.classList.toggle('d-none', !this.util.isTocStatic());
|
||||
openButton.classList.toggle('d-none', !isTocStatic());
|
||||
}
|
||||
// TOC Static and TOC Dialog
|
||||
if (this.util.isTocStatic()) {
|
||||
if (isTocStatic()) {
|
||||
const $tocContentStatic = document.getElementById('toc-content-static');
|
||||
if ($tocCore.parentElement !== $tocContentStatic) {
|
||||
$tocCore.parentElement.removeChild($tocCore);
|
||||
@@ -704,7 +859,7 @@ class FixIt {
|
||||
}
|
||||
const $toc = document.getElementById('toc-auto');
|
||||
$toc.style.visibility = 'visible';
|
||||
this.util.animateCSS($toc, ['animate__fadeIn', 'animate__faster'], true);
|
||||
animateCSS($toc, ['animate__fadeIn', 'animate__faster'], true);
|
||||
const $postMeta = document.querySelector('.post-meta');
|
||||
$toc.style.marginTop = `${$postMeta.offsetTop + $postMeta.clientHeight}px`;
|
||||
|
||||
@@ -733,7 +888,7 @@ class FixIt {
|
||||
} else {
|
||||
$tocContentAuto.classList.remove('animate__fadeIn');
|
||||
}
|
||||
this.util.animateCSS($tocContentAuto, animation, true, () => {
|
||||
animateCSS($tocContentAuto, animation, true, () => {
|
||||
$tocContentAuto.classList.contains('animate__fadeOut') && $tocContentAuto.classList.add('d-none');
|
||||
});
|
||||
$toc.classList.toggle('toc-hidden');
|
||||
@@ -768,7 +923,7 @@ class FixIt {
|
||||
$tocCore = $newTocCore;
|
||||
}
|
||||
// remove APlayer click event listener of the heading mark
|
||||
this.util.forEach(document.querySelectorAll('.heading-mark'), ($headingMark) => {
|
||||
forEach(document.querySelectorAll('.heading-mark'), ($headingMark) => {
|
||||
const $newHeadingMark = $headingMark.cloneNode(true);
|
||||
$headingMark.parentElement.replaceChild($newHeadingMark, $headingMark);
|
||||
});
|
||||
@@ -785,8 +940,8 @@ class FixIt {
|
||||
this._echartsArr[i].dispose();
|
||||
}
|
||||
this._echartsArr = [];
|
||||
const stagingDOM = this.util.getStagingDOM()
|
||||
this.util.forEach(document.getElementsByClassName('echarts'), ($echarts) => {
|
||||
const stagingDOM = getStagingDOM()
|
||||
forEach(document.getElementsByClassName('echarts'), ($echarts) => {
|
||||
const $dataEl = $echarts.nextElementSibling;
|
||||
if ($dataEl.tagName !== 'TEMPLATE') return;
|
||||
const chart = echarts.init($echarts, this.isDark ? 'dark' : 'light', { renderer: 'svg' });
|
||||
@@ -818,7 +973,7 @@ class FixIt {
|
||||
* @returns {Object|Promise} ECharts option or Promise
|
||||
*/
|
||||
const _getOption = new Function('fixit', 'chart',
|
||||
this.util.isObjectLiteral(jsCodes) ? `return ${jsCodes}` : jsCodes
|
||||
isObjectLiteral(jsCodes) ? `return ${jsCodes}` : jsCodes
|
||||
);
|
||||
if ($dataEl.dataset.async === 'true') {
|
||||
return Promise.resolve(_getOption(this, chart)).then(option => {
|
||||
@@ -852,7 +1007,7 @@ class FixIt {
|
||||
mapboxgl.setRTLTextPlugin(this.config.mapbox.RTLTextPlugin);
|
||||
this._mapboxArr = this._mapboxArr || [];
|
||||
}
|
||||
this.util.forEach(document.querySelectorAll('.mapbox:empty'), ($mapbox) => {
|
||||
forEach(document.querySelectorAll('.mapbox:empty'), ($mapbox) => {
|
||||
const { lng, lat, zoom, lightStyle, darkStyle, marked, markers, navigation, geolocate, scale, fullscreen } = JSON.parse($mapbox.dataset.options);
|
||||
const mapbox = new mapboxgl.Map({
|
||||
container: $mapbox,
|
||||
@@ -901,7 +1056,7 @@ class FixIt {
|
||||
this._mapboxArr.push(mapbox);
|
||||
});
|
||||
this._mapboxOnSwitchTheme = this._mapboxOnSwitchTheme || (() => {
|
||||
this.util.forEach(this._mapboxArr, (mapbox) => {
|
||||
forEach(this._mapboxArr, (mapbox) => {
|
||||
const $mapbox = mapbox.getContainer();
|
||||
const { lightStyle, darkStyle } = JSON.parse($mapbox.dataset.options);
|
||||
mapbox.setStyle(this.isDark ? darkStyle : lightStyle);
|
||||
@@ -928,7 +1083,7 @@ class FixIt {
|
||||
acc[group].push(ele);
|
||||
return acc;
|
||||
}, {});
|
||||
const stagingDOM = this.util.getStagingDOM()
|
||||
const stagingDOM = getStagingDOM()
|
||||
|
||||
Object.values(groupMap).forEach((group) => {
|
||||
const typeone = (i) => {
|
||||
@@ -1002,7 +1157,7 @@ class FixIt {
|
||||
$viewCommentsBtn.classList.remove('d-none');
|
||||
// view comments button click event
|
||||
$viewCommentsBtn.addEventListener('click', () => {
|
||||
this.util.scrollIntoView('#comments');
|
||||
scrollIntoView('#comments');
|
||||
}, false);
|
||||
}
|
||||
this.config.comment.expired && document.querySelector('#comments').remove();
|
||||
@@ -1119,7 +1274,7 @@ class FixIt {
|
||||
let now = new Date();
|
||||
let run = new Date(this.config.siteTime);
|
||||
let $runTimes = document.querySelector('.run-times');
|
||||
if (!this.util.isValidDate(run) || !$runTimes) {
|
||||
if (!isValidDate(run) || !$runTimes) {
|
||||
clearInterval(this.siteTime);
|
||||
$runTimes && $runTimes.parentNode.remove();
|
||||
return;
|
||||
@@ -1197,7 +1352,7 @@ class FixIt {
|
||||
initJsonViewer() {
|
||||
if (!window.JsonViewerElement) return;
|
||||
this._jsonViewerOnSwitchTheme = this._jsonViewerOnSwitchTheme || (() => {
|
||||
this.util.forEach(document.getElementsByTagName('json-viewer'), ($el) => {
|
||||
forEach(document.getElementsByTagName('json-viewer'), ($el) => {
|
||||
$el.setAttribute('theme', this.isDark ? 'dark' : 'light');
|
||||
});
|
||||
});
|
||||
@@ -1207,11 +1362,50 @@ class FixIt {
|
||||
|
||||
initTabEvents(target = document) {
|
||||
target.addEventListener('tab-container-changed', () => {
|
||||
this.fileTree.updateLineHeight(target);
|
||||
FileTree.updateLineHeight(target);
|
||||
window.FixItMermaid?.init?.();
|
||||
}, false);
|
||||
}
|
||||
|
||||
initFootnotes() {
|
||||
const $footnoteRefs = document.querySelectorAll('#content sup[id^="fnref:"]');
|
||||
const $footnotes = document.querySelector('.footnotes[role="doc-endnotes"]');
|
||||
if (!$footnoteRefs.length || !$footnotes) return;
|
||||
const footnoteMap = new Map();
|
||||
$footnoteRefs.forEach(($ref) => {
|
||||
if (this.config.tooltip) {
|
||||
const $link = $ref.querySelector('a.footnote-ref');
|
||||
if ($link) {
|
||||
$link.addEventListener('click', (e) => {
|
||||
e.preventDefault();
|
||||
}, false);
|
||||
}
|
||||
}
|
||||
const id = $ref.id.replace('fnref:', '');
|
||||
const $footnoteContent = $footnotes.querySelector(`[id="fn:${id}"]`);
|
||||
if ($footnoteContent) {
|
||||
const $clonedContent = $footnoteContent.cloneNode(true);
|
||||
const $backref = $clonedContent.querySelector('.footnote-backref');
|
||||
if ($backref) {
|
||||
$backref.remove();
|
||||
}
|
||||
footnoteMap.set($ref, $clonedContent);
|
||||
}
|
||||
});
|
||||
footnoteMap.forEach(($content, $ref) => {
|
||||
if ($ref.hasAttribute('title')) return;
|
||||
$ref.setAttribute('title', $content.textContent.trim());
|
||||
if (this.config.tooltip) {
|
||||
window.CellTooltip.getOrCreateInstance($ref);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
initTooltip() {
|
||||
if (!this.config.tooltip) return;
|
||||
window.CellTooltip.initAll('[data-ct-tooltip]');
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper method to initialize content components
|
||||
* @param {Element} target - The target element (optional, defaults to document)
|
||||
@@ -1222,21 +1416,26 @@ class FixIt {
|
||||
this.initDetails(target);
|
||||
this.initLightGallery();
|
||||
this.initCodeWrapper();
|
||||
this.initCodeTabs();
|
||||
this.initDiagramCopyBtn();
|
||||
this.initEcharts();
|
||||
this.initTypeit(target);
|
||||
this.initMapbox();
|
||||
this.initFootnotes();
|
||||
this.initTooltip();
|
||||
if (includeToc) {
|
||||
this.fixTocScroll();
|
||||
this.initToc();
|
||||
this.initTocListener();
|
||||
this.initTocDialog();
|
||||
window.setTimeout(() => {
|
||||
this.fixTocScroll();
|
||||
this.initToc();
|
||||
this.initTocListener();
|
||||
this.initTocDialog();
|
||||
}, 100);
|
||||
}
|
||||
this.initPangu();
|
||||
this.initMathJax();
|
||||
this.initJsonViewer();
|
||||
this.initTabEvents(target);
|
||||
this.fileTree.init(target);
|
||||
FileTree.init(target);
|
||||
window.FixItMermaid?.init?.();
|
||||
window.FixItAPlayer?.init?.();
|
||||
}
|
||||
@@ -1249,7 +1448,7 @@ class FixIt {
|
||||
_toggleEncryptedClass(container, show) {
|
||||
const fromClass = show ? 'encrypted-hidden' : 'decrypted-shown';
|
||||
const toClass = show ? 'decrypted-shown' : 'encrypted-hidden';
|
||||
this.util.forEach(container.querySelectorAll(`.${fromClass}`), ($element) => {
|
||||
forEach(container.querySelectorAll(`.${fromClass}`), ($element) => {
|
||||
$element.classList.replace(fromClass, toClass);
|
||||
});
|
||||
}
|
||||
@@ -1274,7 +1473,7 @@ class FixIt {
|
||||
initAutoMark() {
|
||||
if (!this.config.autoBookmark) return;
|
||||
window.addEventListener('beforeunload', () => {
|
||||
window.sessionStorage?.setItem(`fixit-bookmark/#${location.pathname}`, this.util.getScrollTop());
|
||||
window.sessionStorage?.setItem(`fixit-bookmark/#${location.pathname}`, getScrollTop());
|
||||
});
|
||||
const scrollTop = Number(window.sessionStorage?.getItem(`fixit-bookmark/#${location.pathname}`));
|
||||
// If the page opens with a specific hash, just jump out
|
||||
@@ -1290,15 +1489,15 @@ class FixIt {
|
||||
const $rewards = document.querySelectorAll('.post-reward [data-mode="fixed"]');
|
||||
if (!$rewards.length) return;
|
||||
// `fixed` mode only supports desktop
|
||||
if (this.util.isMobile()) {
|
||||
this.util.forEach($rewards, ($reward) => {
|
||||
if (isMobile()) {
|
||||
forEach($rewards, ($reward) => {
|
||||
$reward.removeAttribute('data-mode');
|
||||
});
|
||||
return;
|
||||
}
|
||||
// Close post reward images exclude special id
|
||||
const _closeRewardExclude = (id) => {
|
||||
this.util.forEach($rewards, ($reward) => {
|
||||
forEach($rewards, ($reward) => {
|
||||
const $rewardInput = $reward.parentElement.querySelector('.reward-input');
|
||||
if ($rewardInput.id !== id) {
|
||||
$rewardInput.checked = false;
|
||||
@@ -1306,7 +1505,7 @@ class FixIt {
|
||||
});
|
||||
};
|
||||
// Add additional click event to reward buttons
|
||||
this.util.forEach($rewards, ($reward) => {
|
||||
forEach($rewards, ($reward) => {
|
||||
$reward.previousElementSibling.addEventListener('click', function () {
|
||||
_closeRewardExclude(this.getAttribute('for'));
|
||||
}, false)
|
||||
@@ -1339,7 +1538,7 @@ class FixIt {
|
||||
$headers.push(document.getElementById('header-mobile'));
|
||||
}
|
||||
$backToTop?.addEventListener('click', () => {
|
||||
this.util.scrollIntoView('body');
|
||||
scrollIntoView('body');
|
||||
});
|
||||
window.addEventListener('scroll', (event) => {
|
||||
if (this.disableScrollEvent) {
|
||||
@@ -1347,17 +1546,17 @@ class FixIt {
|
||||
return;
|
||||
}
|
||||
const $mask = document.getElementById('mask');
|
||||
this.newScrollTop = this.util.getScrollTop();
|
||||
this.newScrollTop = getScrollTop();
|
||||
const scroll = this.newScrollTop - this.oldScrollTop;
|
||||
// header animation
|
||||
this.util.forEach($headers, ($header) => {
|
||||
forEach($headers, ($header) => {
|
||||
if (scroll > ACCURACY) {
|
||||
$header.classList.remove('animate__fadeInDown');
|
||||
this.util.animateCSS($header, ['animate__fadeOutUp'], true);
|
||||
animateCSS($header, ['animate__fadeOutUp'], true);
|
||||
$mask.click();
|
||||
} else if (scroll < -ACCURACY) {
|
||||
$header.classList.remove('animate__fadeOutUp');
|
||||
this.util.animateCSS($header, ['animate__fadeInDown'], true);
|
||||
animateCSS($header, ['animate__fadeInDown'], true);
|
||||
$mask.click();
|
||||
}
|
||||
});
|
||||
@@ -1370,10 +1569,10 @@ class FixIt {
|
||||
if ($backToTop) {
|
||||
if (scrollPercent > 1) {
|
||||
$backToTop.classList.remove('d-none', 'animate__fadeOut');
|
||||
this.util.animateCSS($backToTop, ['animate__fadeIn'], true);
|
||||
animateCSS($backToTop, ['animate__fadeIn'], true);
|
||||
} else {
|
||||
$backToTop.classList.remove('animate__fadeIn');
|
||||
this.util.animateCSS($backToTop, ['animate__fadeOut'], true, () => {
|
||||
animateCSS($backToTop, ['animate__fadeOut'], true, () => {
|
||||
$backToTop.classList.contains('animate__fadeOut') && $backToTop.classList.add('d-none');
|
||||
});
|
||||
}
|
||||
@@ -1393,7 +1592,7 @@ class FixIt {
|
||||
}
|
||||
|
||||
onResize() {
|
||||
let resizeBefore = this.util.isMobile();
|
||||
let resizeBefore = isMobile();
|
||||
window.addEventListener('resize', () => {
|
||||
if (!this._resizeTimeout) {
|
||||
this._resizeTimeout = window.setTimeout(() => {
|
||||
@@ -1404,10 +1603,10 @@ class FixIt {
|
||||
this.initToc();
|
||||
this.initSearch();
|
||||
|
||||
const isMobile = this.util.isMobile()
|
||||
if (isMobile !== resizeBefore) {
|
||||
const _isMobile = isMobile();
|
||||
if (_isMobile !== resizeBefore) {
|
||||
document.getElementById('mask').click();
|
||||
resizeBefore = isMobile;
|
||||
resizeBefore = _isMobile;
|
||||
}
|
||||
}, 100);
|
||||
}
|
||||
@@ -1425,14 +1624,60 @@ class FixIt {
|
||||
}, false);
|
||||
}
|
||||
|
||||
beforeprint() {
|
||||
initPrint() {
|
||||
window.addEventListener('beforeprint', () => {
|
||||
this.util.forEach(document.querySelectorAll('.chroma'), ($el) => {
|
||||
$el.classList.toggle('open', true)
|
||||
});
|
||||
const $content = document.getElementById('content');
|
||||
const printConfig = this.config.print || {};
|
||||
|
||||
if (printConfig.expandAdmonition) {
|
||||
forEach($content.querySelectorAll('.admonition'), ($el) => $el.classList.add('open'));
|
||||
}
|
||||
if (printConfig.expandCode) {
|
||||
// revert code tabs to code blocks for better printing support
|
||||
forEach($content.querySelectorAll('.code-tabs'), ($codeTabs) => {
|
||||
// restore action buttons to the active tab's code-header before reverting
|
||||
const $actions = $codeTabs.querySelector('.tabs-actions');
|
||||
const $activeBlock = $codeTabs.querySelector('.code-block.active');
|
||||
if ($actions && $activeBlock) {
|
||||
const $codeHeader = $activeBlock.querySelector('.code-header');
|
||||
if ($codeHeader) {
|
||||
Array.from($actions.children).forEach(btn => $codeHeader.appendChild(btn));
|
||||
}
|
||||
}
|
||||
const $codeBlocks = $codeTabs.querySelectorAll('.code-block');
|
||||
$codeBlocks.forEach(($codeBlock) => {
|
||||
delete $codeBlock.dataset.tabInit;
|
||||
$codeTabs.parentElement.insertBefore($codeBlock, $codeTabs);
|
||||
});
|
||||
$codeTabs.parentElement.removeChild($codeTabs);
|
||||
});
|
||||
forEach($content.querySelectorAll('.code-block'), ($el) => {
|
||||
// line wrapping
|
||||
$el.classList.add('line-wrapping');
|
||||
// expand all code blocks
|
||||
$el.classList.remove('is-collapsed');
|
||||
// expand code preview
|
||||
if ($el.querySelector('.code-expand-btn')) {
|
||||
$el.classList.add('is-expanded');
|
||||
}
|
||||
});
|
||||
}
|
||||
if (printConfig.expandDetails) {
|
||||
forEach($content.querySelectorAll('details'), ($el) => $el.setAttribute('open', ''));
|
||||
}
|
||||
for (let event of this.beforeprintEventSet) {
|
||||
event();
|
||||
}
|
||||
if (printConfig.expandFileTree) {
|
||||
FileTree.expandAll($content);
|
||||
}
|
||||
}, false);
|
||||
|
||||
window.addEventListener('afterprint', () => {
|
||||
this.initCodeTabs();
|
||||
for (let event of this.afterprintEventSet) {
|
||||
event();
|
||||
}
|
||||
}, false);
|
||||
}
|
||||
|
||||
@@ -1458,7 +1703,6 @@ class FixIt {
|
||||
this.initReward();
|
||||
this.initPostChatUser();
|
||||
|
||||
// [todo] refactor async init toc
|
||||
window.setTimeout(() => {
|
||||
this.initComment();
|
||||
if (!this.config.encryption?.all) {
|
||||
@@ -1470,7 +1714,7 @@ class FixIt {
|
||||
this.onScroll();
|
||||
this.onResize();
|
||||
this.onClickMask();
|
||||
this.beforeprint();
|
||||
this.initPrint();
|
||||
}, 100);
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
|
||||
@@ -1,146 +0,0 @@
|
||||
export default class Util {
|
||||
forEach(elements, handler) {
|
||||
elements = elements || [];
|
||||
const promises = [];
|
||||
for (let i = 0; i < elements.length; i++) {
|
||||
const result = handler(elements[i], i);
|
||||
if (result instanceof Promise) {
|
||||
promises.push(result);
|
||||
}
|
||||
}
|
||||
return Promise.all(promises);
|
||||
}
|
||||
|
||||
getScrollTop() {
|
||||
return (document.documentElement ?? document.body).scrollTop;
|
||||
}
|
||||
|
||||
isMobile() {
|
||||
return window.matchMedia('only screen and (max-width: 680px)').matches;
|
||||
}
|
||||
|
||||
isTocStatic() {
|
||||
return document.getElementById('toc-static').dataset.kept === 'true' || window.matchMedia('only screen and (max-width: 960px)').matches;
|
||||
}
|
||||
|
||||
/**
|
||||
* add animate to element
|
||||
* @param {Element} element animate element
|
||||
* @param {String|Array<String>} animation animation name
|
||||
* @param {Boolean} reserved reserved animation
|
||||
* @param {Function} callback remove callback
|
||||
*/
|
||||
animateCSS(element, animation, reserved, callback) {
|
||||
!Array.isArray(animation) && (animation = [animation]);
|
||||
element.classList.add('animate__animated', ...animation);
|
||||
element.addEventListener('animationend', () => {
|
||||
!reserved && element.classList.remove('animate__animated', ...animation);
|
||||
typeof callback === 'function' && callback();
|
||||
}, { once: true });
|
||||
}
|
||||
|
||||
/**
|
||||
* date validator
|
||||
* @param {*} date may be date or not
|
||||
* @returns {Boolean}
|
||||
*/
|
||||
isValidDate(date) {
|
||||
return date instanceof Date && !isNaN(date.getTime());
|
||||
}
|
||||
|
||||
/**
|
||||
* scroll some element into view
|
||||
* @param {String} selector element to scroll
|
||||
*/
|
||||
scrollIntoView(selector) {
|
||||
const element = selector.startsWith('#')
|
||||
? document.getElementById(selector.slice(1))
|
||||
: document.querySelector(selector);
|
||||
element?.scrollIntoView({
|
||||
behavior: 'smooth'
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* get a hidden element for temporary use
|
||||
* @returns {Object} { $el: Element, destroy: Function }
|
||||
*/
|
||||
getStagingDOM() {
|
||||
const stagingElement = document.createElement('div')
|
||||
stagingElement.style.display = 'none';
|
||||
stagingElement.dataset.stagingId = Math.random().toString(36).slice(2);
|
||||
document.body.appendChild(stagingElement);
|
||||
|
||||
return {
|
||||
$el: stagingElement,
|
||||
stage(dom) {
|
||||
stagingElement.innerHTML = '';
|
||||
stagingElement.appendChild(dom);
|
||||
},
|
||||
contentAsHtml() {
|
||||
return stagingElement.innerHTML;
|
||||
},
|
||||
contentAsText() {
|
||||
return stagingElement.innerText;
|
||||
},
|
||||
contentAsJson() {
|
||||
return JSON.parse(stagingElement.innerHTML);
|
||||
},
|
||||
destroy() {
|
||||
document.body.removeChild(stagingElement);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* copy text to clipboard
|
||||
* @param {String} text text to copy
|
||||
* @returns {Promise} promise
|
||||
*/
|
||||
copyText(text) {
|
||||
if (navigator.clipboard) {
|
||||
this.copyText = (text) => navigator.clipboard.writeText(text);
|
||||
return this.copyText(text);
|
||||
}
|
||||
this.copyText = (text) => new Promise((resolve, reject) => {
|
||||
const input = document.createElement('input');
|
||||
input.value = text;
|
||||
document.body.appendChild(input);
|
||||
input.select();
|
||||
if (document.execCommand('copy')) {
|
||||
document.body.removeChild(input);
|
||||
resolve();
|
||||
} else {
|
||||
reject();
|
||||
}
|
||||
});
|
||||
return this.copyText(text);
|
||||
}
|
||||
|
||||
/**
|
||||
* check if a string is a JS object string
|
||||
* @example isObjectLiteral("{a:1,b:2}") // true
|
||||
* @param {String} str string to check
|
||||
* @returns {Boolean} whether the string is a JS object string
|
||||
*/
|
||||
isObjectLiteral(str) {
|
||||
if (typeof str !== 'string') {
|
||||
return false;
|
||||
}
|
||||
str = str.replace(/\s+/g, ' ').trim().replace(/;$/, '')
|
||||
if (str.startsWith('{') && str.endsWith('}')) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
HTMLEscape(str) {
|
||||
return str.replace(/[&<>"']/g, char => ({
|
||||
'&': '&',
|
||||
'<': '<',
|
||||
'>': '>',
|
||||
'"': '"',
|
||||
"'": '''
|
||||
})[char]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,141 @@
|
||||
export function forEach(elements, handler) {
|
||||
elements = elements || [];
|
||||
const promises = [];
|
||||
for (let i = 0; i < elements.length; i++) {
|
||||
const result = handler(elements[i], i);
|
||||
if (result instanceof Promise) {
|
||||
promises.push(result);
|
||||
}
|
||||
}
|
||||
return Promise.all(promises);
|
||||
}
|
||||
|
||||
export function getScrollTop() {
|
||||
return (document.documentElement ?? document.body).scrollTop;
|
||||
}
|
||||
|
||||
export function isMobile() {
|
||||
return window.matchMedia('only screen and (max-width: 680px)').matches;
|
||||
}
|
||||
|
||||
export function isTocStatic() {
|
||||
return document.getElementById('toc-static').dataset.kept === 'true' || window.matchMedia('only screen and (max-width: 960px)').matches;
|
||||
}
|
||||
|
||||
/**
|
||||
* add animate to element
|
||||
* @param {Element} element animate element
|
||||
* @param {String|Array<String>} animation animation name
|
||||
* @param {Boolean} reserved reserved animation
|
||||
* @param {Function} callback remove callback
|
||||
*/
|
||||
export function animateCSS(element, animation, reserved, callback) {
|
||||
!Array.isArray(animation) && (animation = [animation]);
|
||||
element.classList.add('animate__animated', ...animation);
|
||||
element.addEventListener('animationend', () => {
|
||||
!reserved && element.classList.remove('animate__animated', ...animation);
|
||||
typeof callback === 'function' && callback();
|
||||
}, { once: true });
|
||||
}
|
||||
|
||||
/**
|
||||
* date validator
|
||||
* @param {*} date may be date or not
|
||||
* @returns {Boolean}
|
||||
*/
|
||||
export function isValidDate(date) {
|
||||
return date instanceof Date && !isNaN(date.getTime());
|
||||
}
|
||||
|
||||
/**
|
||||
* scroll some element into view
|
||||
* @param {String} selector element to scroll
|
||||
*/
|
||||
export function scrollIntoView(selector) {
|
||||
const element = selector.startsWith('#')
|
||||
? document.getElementById(selector.slice(1))
|
||||
: document.querySelector(selector);
|
||||
element?.scrollIntoView({
|
||||
behavior: 'smooth'
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* get a hidden element for temporary use
|
||||
* @returns {Object} { $el: Element, destroy: Function }
|
||||
*/
|
||||
export function getStagingDOM() {
|
||||
const stagingElement = document.createElement('div')
|
||||
stagingElement.style.display = 'none';
|
||||
stagingElement.dataset.stagingId = Math.random().toString(36).slice(2);
|
||||
document.body.appendChild(stagingElement);
|
||||
|
||||
return {
|
||||
$el: stagingElement,
|
||||
stage(dom) {
|
||||
stagingElement.innerHTML = '';
|
||||
stagingElement.appendChild(dom);
|
||||
},
|
||||
contentAsHtml() {
|
||||
return stagingElement.innerHTML;
|
||||
},
|
||||
contentAsText() {
|
||||
return stagingElement.innerText;
|
||||
},
|
||||
contentAsJson() {
|
||||
return JSON.parse(stagingElement.innerHTML);
|
||||
},
|
||||
destroy() {
|
||||
document.body.removeChild(stagingElement);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* create a copy text function with fallback
|
||||
* @returns {Function} copy text function
|
||||
*/
|
||||
export function createCopyText() {
|
||||
if (navigator.clipboard) {
|
||||
return (text) => navigator.clipboard.writeText(text);
|
||||
}
|
||||
return (text) => new Promise((resolve, reject) => {
|
||||
const input = document.createElement('input');
|
||||
input.value = text;
|
||||
document.body.appendChild(input);
|
||||
input.select();
|
||||
if (document.execCommand('copy')) {
|
||||
document.body.removeChild(input);
|
||||
resolve();
|
||||
} else {
|
||||
reject();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* check if a string is a JS object string
|
||||
* @example isObjectLiteral("{a:1,b:2}") // true
|
||||
* @param {String} str string to check
|
||||
* @returns {Boolean} whether the string is a JS object string
|
||||
*/
|
||||
export function isObjectLiteral(str) {
|
||||
if (typeof str !== 'string') {
|
||||
return false;
|
||||
}
|
||||
str = str.replace(/\s+/g, ' ').trim().replace(/;$/, '')
|
||||
if (str.startsWith('{') && str.endsWith('}')) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
export function HTMLEscape(str) {
|
||||
return str.replace(/[&<>"']/g, char => ({
|
||||
'&': '&',
|
||||
'<': '<',
|
||||
'>': '>',
|
||||
'"': '"',
|
||||
"'": '''
|
||||
})[char]);
|
||||
}
|
||||
Vendored
+1
-1
@@ -24,7 +24,7 @@ sharer.js@0.5.1 https://github.com/ellisonleao/sharer.js
|
||||
simple-icons@9.19.0 https://github.com/simple-icons/simple-icons
|
||||
tab-container-element@4.8.2 https://github.com/github/tab-container-element
|
||||
twemoji@14.0.2 https://github.com/twitter/twemoji
|
||||
twikoo@1.6.44 https://github.com/imaegoo/twikoo
|
||||
twikoo@1.7.3 https://github.com/imaegoo/twikoo
|
||||
typeit@8.8.4 https://github.com/alexmacarthur/typeit
|
||||
valine@1.5.2 https://github.com/xCss/Valine
|
||||
waline@3.12.1 https://github.com/walinejs/waline
|
||||
|
||||
+1
File diff suppressed because one or more lines are too long
Vendored
+1
-1
File diff suppressed because one or more lines are too long
@@ -453,6 +453,8 @@ summaryPlainify = false
|
||||
# default is false for better performance, especially for single-language sites
|
||||
# set to true if your site has multiple languages and you want to show all content
|
||||
enableTranslationMerge = false
|
||||
# FixIt 0.4.4 | NEW whether to enable tooltip replacement for elements with title attribute, such as footnote references
|
||||
tooltip = true
|
||||
# FixIt 0.2.14 | NEW FixIt will, by default, inject a theme meta tag in the HTML head on the home page only.
|
||||
# You can turn it off, but we would really appreciate if you don’t, as this is a good way to watch FixIt's popularity on the rise.
|
||||
disableThemeInject = false
|
||||
@@ -1142,6 +1144,8 @@ mode = "classic"
|
||||
wrapperClass = ""
|
||||
# the maximum number of lines to show in the code block preview
|
||||
maxShownLines = 10
|
||||
# whether to show shadow effect for code blocks, available values: ["always", "hover", "never"]
|
||||
shadow = "never"
|
||||
# whether to enable code copy button
|
||||
copyable = true
|
||||
# [classic] whether to enable code download button in the code block header
|
||||
@@ -1174,6 +1178,17 @@ folderSlash = false
|
||||
# list of file or folder names to ignore
|
||||
ignoreList = []
|
||||
|
||||
# FixIt 0.4.5 | NEW Print config
|
||||
[params.print]
|
||||
# whether to expand all admonitions before printing
|
||||
expand_admonition = true
|
||||
# whether to expand all code blocks and code tabs before printing
|
||||
expand_code = true
|
||||
# whether to expand all details elements before printing
|
||||
expand_details = true
|
||||
# whether to expand all file trees before printing
|
||||
expand_file_tree = false
|
||||
|
||||
# FixIt 0.3.12 | NEW Custom partials config
|
||||
# Custom partials must be stored in the /layouts/_partials/ directory.
|
||||
# Depends on open custom blocks https://fixit.lruihao.cn/references/blocks/
|
||||
|
||||
@@ -0,0 +1,64 @@
|
||||
{{- $parsed := dict -}}
|
||||
{{- $ok := false -}}
|
||||
{{- with try (transform.Unmarshal .Inner) -}}
|
||||
{{- with .Err -}}
|
||||
{{- warnf "Toggle code block: unable to parse content at %s" $.Position -}}
|
||||
{{- else with .Value -}}
|
||||
{{- $parsed = . -}}
|
||||
{{- $ok = true -}}
|
||||
{{- end -}}
|
||||
{{- end -}}
|
||||
|
||||
{{- if $ok -}}
|
||||
{{- $groupId := printf "tab-%s" (md5 (dict "Page" .Page | partial "function/id.html")) -}}
|
||||
{{- $activeFormat := "" -}}
|
||||
{{- $raw := strings.TrimSpace .Inner -}}
|
||||
{{- range $candidate := slice "json" "toml" "yaml" -}}
|
||||
{{- if not $activeFormat -}}
|
||||
{{- with try (transform.Unmarshal (dict "format" $candidate) $raw) -}}
|
||||
{{- if not .Err -}}
|
||||
{{- $activeFormat = $candidate -}}
|
||||
{{- end -}}
|
||||
{{- end -}}
|
||||
{{- end -}}
|
||||
{{- end -}}
|
||||
{{- $commonAttrsStr := printf ", group=%q" $groupId -}}
|
||||
{{- if ne .Attributes.copyable nil -}}
|
||||
{{- $commonAttrsStr = printf "%s, copyable=%v" $commonAttrsStr .Attributes.copyable -}}
|
||||
{{- end -}}
|
||||
{{- if ne .Attributes.downloadable nil -}}
|
||||
{{- $commonAttrsStr = printf "%s, downloadable=%v" $commonAttrsStr .Attributes.downloadable -}}
|
||||
{{- end -}}
|
||||
{{- if ne .Attributes.fullscreen nil -}}
|
||||
{{- $commonAttrsStr = printf "%s, fullscreen=%v" $commonAttrsStr .Attributes.fullscreen -}}
|
||||
{{- end -}}
|
||||
{{- if ne .Attributes.linenostoggler nil -}}
|
||||
{{- $commonAttrsStr = printf "%s, lineNosToggler=%v" $commonAttrsStr .Attributes.linenostoggler -}}
|
||||
{{- end -}}
|
||||
{{- if ne .Attributes.linewraptoggler nil -}}
|
||||
{{- $commonAttrsStr = printf "%s, lineWrapToggler=%v" $commonAttrsStr .Attributes.linewraptoggler -}}
|
||||
{{- end -}}
|
||||
{{- if ne .Attributes.editable nil -}}
|
||||
{{- $commonAttrsStr = printf "%s, editable=%v" $commonAttrsStr .Attributes.editable -}}
|
||||
{{- end -}}
|
||||
{{- with .Attributes.before_tabs -}}
|
||||
{{- $commonAttrsStr = printf "%s, before_tabs=%q" $commonAttrsStr . -}}
|
||||
{{- end -}}
|
||||
{{- $renderText := "" -}}
|
||||
{{- range $format := slice "toml" "yaml" "json" -}}
|
||||
{{- $content := transform.Remarshal $format $parsed -}}
|
||||
{{- if eq $format "toml" -}}
|
||||
{{- $content = replaceRE `(?m)^[\t ]+` "" $content -}}
|
||||
{{- else if eq $format "json" -}}
|
||||
{{- $content = $parsed | jsonify (dict "indent" " ") -}}
|
||||
{{- end -}}
|
||||
{{- $active := cond (eq $format $activeFormat) ", .active" "" -}}
|
||||
{{- $jsonViewer := cond (eq $format "json") ", enable=false" "" -}}
|
||||
{{- $labelName := cond (ne $.Attributes.before_tabs nil) $format (upper $format) -}}
|
||||
{{- $block := printf "```%s {data-code-toggle=true, name=%q%s%s%s}\n%s\n```\n\n" $format $labelName $active $jsonViewer $commonAttrsStr $content -}}
|
||||
{{- $renderText = add $renderText $block -}}
|
||||
{{- end -}}
|
||||
{{- $renderText | .Page.RenderString -}}
|
||||
{{- else -}}
|
||||
{{- partial "plugin/code-block-wrapper.html" . -}}
|
||||
{{- end -}}
|
||||
@@ -156,7 +156,7 @@ FixIt theme assets partial
|
||||
{{- end -}}
|
||||
|
||||
{{- /* Mapbox GL */ -}}
|
||||
{{- if .HasShortcode "mapbox" -}}
|
||||
{{- if .Store.Get "hasMapbox" -}}
|
||||
{{- $source := $cdn.mapboxGLCSS | default "lib/mapbox-gl/mapbox-gl.css" -}}
|
||||
{{- dict "Source" $source "Minify" true "Fingerprint" $fingerprint "Preload" true | dict "Page" . "Data" | partial "store/style.html" -}}
|
||||
{{- $source = $cdn.mapboxGLJS | default "lib/mapbox-gl/mapbox-gl.js" -}}
|
||||
@@ -166,7 +166,7 @@ FixIt theme assets partial
|
||||
{{- end -}}
|
||||
|
||||
{{- /* Music */ -}}
|
||||
{{- if (.HasShortcode "aplayer") | or (.HasShortcode "music") -}}
|
||||
{{- if (.Store.Get "hasAplayer") | or (.Store.Get "hasMusic") -}}
|
||||
{{- /* APlayer */ -}}
|
||||
{{- $source := $cdn.aplayerCSS | default "lib/aplayer/APlayer.min.css" -}}
|
||||
{{- dict "Source" $source "Fingerprint" $fingerprint "Preload" true | dict "Page" . "Data" | partial "store/style.html" -}}
|
||||
@@ -175,14 +175,14 @@ FixIt theme assets partial
|
||||
{{- $source := $cdn.aplayerJS | default "lib/aplayer/APlayer.min.js" -}}
|
||||
{{- dict "Source" $source "Fingerprint" $fingerprint "Defer" true | dict "Page" . "Data" | partial "store/script.html" -}}
|
||||
|
||||
{{- if .HasShortcode "aplayer" -}}
|
||||
{{- if .Store.Get "hasAplayer" -}}
|
||||
{{- $options := dict "targetPath" "js/lib/aplayer.min.js" "minify" hugo.IsProduction -}}
|
||||
{{- if not hugo.IsProduction -}}
|
||||
{{- $options = dict "sourceMap" "external" | merge $options -}}
|
||||
{{- end -}}
|
||||
{{- dict "Source" (resources.Get "js/lib/aplayer.js") "Build" $options "Fingerprint" $fingerprint "Defer" true | dict "Page" . "Data" | partial "store/script.html" -}}
|
||||
{{- end -}}
|
||||
{{- if .HasShortcode "music" -}}
|
||||
{{- if .Store.Get "hasMusic" -}}
|
||||
{{- /* MetingJS */ -}}
|
||||
{{- $source := $cdn.metingJS | default "lib/meting/Meting.min.js" -}}
|
||||
{{- dict "Source" $source "Fingerprint" $fingerprint "Defer" true | dict "Page" . "Data" | partial "store/script.html" -}}
|
||||
@@ -214,6 +214,14 @@ FixIt theme assets partial
|
||||
{{- $config = dict "pangu" .Site.Params.pangu | merge $config -}}
|
||||
{{- end -}}
|
||||
|
||||
{{- /* Cell Tooltip */ -}}
|
||||
{{- if eq .Site.Params.tooltip true -}}
|
||||
{{- /* [todo] 临时可行性验证,需要寻找一个更稳定的替代品(Floating UI) */ -}}
|
||||
{{- $source := $cdn.cellTooltipJS | default "lib/cell-tooltip/cell-tooltip.umd.js" -}}
|
||||
{{- dict "Source" $source "Fingerprint" $fingerprint "Defer" true | dict "Page" . "Data" | partial "store/script.html" -}}
|
||||
{{- $config = dict "tooltip" true | merge $config -}}
|
||||
{{- end -}}
|
||||
|
||||
{{- /* Watermark */ -}}
|
||||
{{- if eq .Site.Params.watermark.enable true -}}
|
||||
{{- $source := $cdn.cellWatermarkJS | default "lib/cell-watermark/watermark.min.js" -}}
|
||||
@@ -232,7 +240,7 @@ FixIt theme assets partial
|
||||
{{- end -}}
|
||||
|
||||
{{- /* Content Decryption */ -}}
|
||||
{{- $encryptPartial := .HasShortcode "fixit-encryptor" -}}
|
||||
{{- $encryptPartial := .Store.Get "hasEncryptor" -}}
|
||||
{{- if $params.password | or $encryptPartial -}}
|
||||
{{- $cryptoCoreJS := $cdn.cryptoCoreJS | default "lib/crypto-js/core.js" -}}
|
||||
{{- $cryptoEncBase64JS := $cdn.cryptoEncBase64JS | default "lib/crypto-js/enc-base64.js" -}}
|
||||
@@ -300,6 +308,9 @@ FixIt theme assets partial
|
||||
{{- end -}}
|
||||
{{- end -}}
|
||||
|
||||
{{- /* Print */ -}}
|
||||
{{- $config = partial "function/snake2camel.html" .Site.Params.print | dict "print" | merge $config -}}
|
||||
|
||||
{{- /* PostChat */ -}}
|
||||
{{- partial "plugin/post-chat-ai.html" . -}}
|
||||
|
||||
|
||||
@@ -18,27 +18,27 @@
|
||||
{{- end -}}
|
||||
{{- if $config.linenostoggler -}}
|
||||
{{- $lineNosIcon := dict "Class" "fa-solid fa-list-ol" | partial "plugin/icon.html" -}}
|
||||
{{- $lineNosBtn = printf `<span class="line-nos-btn" aria-label="%s" role="button" title="%s">%s</span>` (T "assets.toggleLineNumbers") (T "assets.toggleLineNumbers") $lineNosIcon -}}
|
||||
{{- $lineNosBtn = printf `<span class="action-btn line-nos-btn" aria-label="%s" role="button" title="%s" data-ct-tooltip>%s</span>` (T "assets.toggleLineNumbers") (T "assets.toggleLineNumbers") $lineNosIcon -}}
|
||||
{{- end -}}
|
||||
{{- if $config.linewraptoggler -}}
|
||||
{{- $lineWrapIcon := dict "Class" "fa-solid fa-right-left" | partial "plugin/icon.html" -}}
|
||||
{{- $lineWrapBtn = printf `<span class="line-wrap-btn" aria-label="%s" role="button" title="%s">%s</span>` (T "assets.toggleLineWrap") (T "assets.toggleLineWrap") $lineWrapIcon -}}
|
||||
{{- $lineWrapBtn = printf `<span class="action-btn line-wrap-btn" aria-label="%s" role="button" title="%s" data-ct-tooltip>%s</span>` (T "assets.toggleLineWrap") (T "assets.toggleLineWrap") $lineWrapIcon -}}
|
||||
{{- end -}}
|
||||
{{- if $config.editable -}}
|
||||
{{- $editIcon := dict "Class" "fa-solid fa-pen-to-square" | partial "plugin/icon.html" -}}
|
||||
{{- $editBtn = printf `<span class="edit-btn" aria-label="%s" role="button" title="%s">%s</span>` (T "assets.toggleCodeEditable") (T "assets.toggleCodeEditable") $editIcon -}}
|
||||
{{- $editBtn = printf `<span class="action-btn edit-btn" aria-label="%s" role="button" title="%s" data-ct-tooltip>%s</span>` (T "assets.toggleCodeEditable") (T "assets.toggleCodeEditable") $editIcon -}}
|
||||
{{- end -}}
|
||||
{{- if $config.copyable -}}
|
||||
{{- $copyIcon := dict "Class" "fa-regular fa-clone" | partial "plugin/icon.html" -}}
|
||||
{{- $copyBtn = printf `<span class="copy-btn" aria-label="%s" role="button" title="%s">%s</span>` (T "assets.copyToClipboard") (T "assets.copyToClipboard") $copyIcon -}}
|
||||
{{- $copyBtn = printf `<span class="action-btn copy-btn" aria-label="%s" role="button" title="%s" data-copied-text="%s" data-ct-tooltip>%s</span>` (T "assets.copyToClipboard") (T "assets.copyToClipboard") (T "assets.copiedText") $copyIcon -}}
|
||||
{{- end -}}
|
||||
{{- if $config.downloadable -}}
|
||||
{{- $downloadIcon := dict "Class" "fa-solid fa-download" | partial "plugin/icon.html" -}}
|
||||
{{- $downloadBtn = printf `<span class="download-btn" aria-label="%s" role="button" title="%s">%s</span>` (T "assets.downloadCode") (T "assets.downloadCode") $downloadIcon -}}
|
||||
{{- $downloadBtn = printf `<span class="action-btn download-btn" aria-label="%s" role="button" title="%s" data-ct-tooltip>%s</span>` (T "assets.downloadCode") (T "assets.downloadCode") $downloadIcon -}}
|
||||
{{- end -}}
|
||||
{{- if $config.fullscreen -}}
|
||||
{{- $fullscreenIcon := dict "Class" "fa-solid fa-expand" | partial "plugin/icon.html" -}}
|
||||
{{- $fullscreenBtn = printf `<span class="fullscreen-btn" aria-label="%s" role="button" title="%s">%s</span>` (T "assets.toggleCodeFullscreen") (T "assets.toggleCodeFullscreen") $fullscreenIcon -}}
|
||||
{{- $fullscreenBtn = printf `<span class="action-btn fullscreen-btn" aria-label="%s" role="button" title="%s" data-ct-tooltip>%s</span>` (T "assets.toggleCodeFullscreen") (T "assets.toggleCodeFullscreen") $fullscreenIcon -}}
|
||||
{{- end -}}
|
||||
{{- with $config.name -}}
|
||||
{{- $titleEl = replace $titleEl `<span class="code-title">` (printf `<span class="code-title" data-name="%s">` .) -}}
|
||||
|
||||
@@ -0,0 +1,91 @@
|
||||
{{- /*
|
||||
Convert snake_case config keys to camelCase recursively.
|
||||
|
||||
This is mainly used to avoid problems caused by Hugo's case-insensitive config key parsing.
|
||||
Keep config keys in snake_case, then convert them here for stable access in templates and JS.
|
||||
|
||||
@param {map} . - The input map/object with snake_case keys
|
||||
@return {map} A new map with camelCase keys, or the input if it's not a map
|
||||
|
||||
@example
|
||||
// Simple Map conversion
|
||||
Input: dict "code_block" true "max_shown_lines" 10 "line_nos" false
|
||||
Output: dict "codeBlock" true "maxShownLines" 10 "lineNos" false
|
||||
|
||||
@example
|
||||
// Nested Map conversion
|
||||
Input: dict "code_block" (dict "enable_wrapper" true)
|
||||
Output: dict "codeBlock" (dict "enableWrapper" true)
|
||||
|
||||
@example
|
||||
// Array of Maps conversion
|
||||
Input: slice (dict "user_name" "John") (dict "user_age" 30)
|
||||
Output: slice (dict "userName" "John") (dict "userAge" 30)
|
||||
|
||||
@example
|
||||
// Practical usage in templates
|
||||
{{- $siteParams := partial "function/snake2camel.html" .Site.Params }}
|
||||
// Now access parameters with camelCase: $siteParams.codeBlock.enableWrapper
|
||||
// [todo] refactor all config to use snake_case in Hugo config files, and camelCase in templates and JS (v1.0 breaking change)
|
||||
*/ -}}
|
||||
{{- $input := . -}}
|
||||
{{- $output := dict -}}
|
||||
|
||||
{{- if reflect.IsMap $input -}}
|
||||
{{- /* Process map: iterate through all key-value pairs */ -}}
|
||||
{{- range $key, $value := $input -}}
|
||||
{{- /*
|
||||
Convert key from snake_case to camelCase
|
||||
Example: "max_shown_lines" -> "maxShownLines"
|
||||
Strategy: split by "_", capitalize first letter of each part (except first), then concatenate
|
||||
*/ -}}
|
||||
{{- $parts := split $key "_" -}}
|
||||
{{- $newKey := index $parts 0 -}}
|
||||
{{- range $i, $part := $parts -}}
|
||||
{{- if gt $i 0 -}}
|
||||
{{- /* Capitalize first letter, preserve the rest */ -}}
|
||||
{{- $first := upper (substr $part 0 1) -}}
|
||||
{{- $rest := substr $part 1 -}}
|
||||
{{- $newKey = printf "%s%s%s" $newKey $first $rest -}}
|
||||
{{- end -}}
|
||||
{{- end -}}
|
||||
|
||||
{{- /*
|
||||
Recursively process value based on its type
|
||||
- For nested maps: recursively convert all nested keys
|
||||
- For arrays: process each map element in the array
|
||||
- For scalar values: keep as-is
|
||||
*/ -}}
|
||||
{{- $newValue := $value -}}
|
||||
{{- if reflect.IsMap $value -}}
|
||||
{{- $newValue = partial "function/snake2camel.html" $value -}}
|
||||
{{- else if reflect.IsSlice $value -}}
|
||||
{{- $newValue = slice -}}
|
||||
{{- range $item := $value -}}
|
||||
{{- if reflect.IsMap $item -}}
|
||||
{{- $newValue = $newValue | append (partial "function/snake2camel.html" $item) -}}
|
||||
{{- else -}}
|
||||
{{- $newValue = $newValue | append $item -}}
|
||||
{{- end -}}
|
||||
{{- end -}}
|
||||
{{- end -}}
|
||||
|
||||
{{- /* Add the converted key-value pair to output */ -}}
|
||||
{{- $output = merge $output (dict $newKey $newValue) -}}
|
||||
{{- end -}}
|
||||
{{- return $output -}}
|
||||
{{- else if reflect.IsSlice $input -}}
|
||||
{{- /* Process array: recursively convert each map element */ -}}
|
||||
{{- $output := slice -}}
|
||||
{{- range $item := $input -}}
|
||||
{{- if reflect.IsMap $item -}}
|
||||
{{- $output = $output | append (partial "function/snake2camel.html" $item) -}}
|
||||
{{- else -}}
|
||||
{{- $output = $output | append $item -}}
|
||||
{{- end -}}
|
||||
{{- end -}}
|
||||
{{- return $output -}}
|
||||
{{- else -}}
|
||||
{{- /* Return scalar values unchanged */ -}}
|
||||
{{- return $input -}}
|
||||
{{- end -}}
|
||||
@@ -1,4 +1,4 @@
|
||||
{{- hugo.Store.Set "version" "v0.4.3" -}}
|
||||
{{- hugo.Store.Set "version" "v0.4.5" -}}
|
||||
{{- .Store.Set "this" dict -}}
|
||||
|
||||
{{- partial "init/detection-env.html" . -}}
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
|
||||
{{- /*
|
||||
* The extended syntax of alert is compatible with Obsidian and FixIt admonition shortcode.
|
||||
* @param {String} .Text the content of the admonition box
|
||||
* @param {String} [.Type] the type of the admonition box
|
||||
* @param {String} [.Title] the title of the admonition box
|
||||
* @param {Boolean} [.Open] whether the admonition box is open, default is true
|
||||
* @param {Boolean} [.Foldable] whether the admonition box is foldable, default is true
|
||||
* For custom admonitions, see https://fixit.lruihao.cn/documentation/content-management/shortcodes/extended/admonition/#customize-admonitions
|
||||
The extended syntax of alert is compatible with Obsidian and FixIt admonition shortcode.
|
||||
@param {String} .Text the content of the admonition box
|
||||
@param {String} [.Type] the type of the admonition box
|
||||
@param {String} [.Title] the title of the admonition box
|
||||
@param {Boolean} [.Open] whether the admonition box is open, default is true
|
||||
@param {Boolean} [.Foldable] whether the admonition box is foldable, default is true
|
||||
For custom admonitions, see https://fixit.lruihao.cn/documentation/content-management/shortcodes/extended/admonition/#customize-admonitions
|
||||
*/ -}}
|
||||
|
||||
{{- $iconMap := dict
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
{{- /*
|
||||
* The basic syntax of alert is compatible with GitHub, Obsidian, and Typora.
|
||||
* @param {String} .Type the type of the alert box
|
||||
* @param {String} .Text the content of the alert box
|
||||
* @param {Map} [.Attributes] the attributes of the alert box
|
||||
* @example {{- dict "Text" .Text "Type" .AlertType "Attributes" .Attributes | partial "plugin/alert.html" -}}
|
||||
The basic syntax of alert is compatible with GitHub, Obsidian, and Typora.
|
||||
@param {String} .Type the type of the alert box
|
||||
@param {String} .Text the content of the alert box
|
||||
@param {Map} [.Attributes] the attributes of the alert box
|
||||
@example {{- dict "Text" .Text "Type" .AlertType "Attributes" .Attributes | partial "plugin/alert.html" -}}
|
||||
*/ -}}
|
||||
|
||||
{{- $iconMap := dict
|
||||
|
||||
@@ -58,11 +58,33 @@
|
||||
(printf `class="%s" data-copyable="%v"` $class $config.copyable)
|
||||
-}}
|
||||
{{- end -}}
|
||||
{{- if ne $config.shadow "never" -}}
|
||||
{{- $wrapper =
|
||||
replace $wrapper
|
||||
(printf `class="%s"` $class)
|
||||
(printf `class="%s" data-shadow="%s"` $class $config.shadow)
|
||||
-}}
|
||||
{{- end -}}
|
||||
{{- if $config.group -}}
|
||||
{{- $tabTitle := $config.name | default (strings.FirstUpper .Type) | default "Code" -}}
|
||||
{{- $groupKey := printf "code-tab-group-first:%s" $config.group -}}
|
||||
{{- $isFirst := not (.Page.Store.Get $groupKey) -}}
|
||||
{{- if $isFirst -}}
|
||||
{{- .Page.Store.Set $groupKey true -}}
|
||||
{{- else -}}
|
||||
{{- $targetClass = add $targetClass " d-none" -}}
|
||||
{{- end -}}
|
||||
{{- $wrapper =
|
||||
replace $wrapper
|
||||
(printf `class="%s"` $class)
|
||||
(printf `class="%s" data-tab-title="%s"` $class $tabTitle)
|
||||
-}}
|
||||
{{- end -}}
|
||||
{{- if ne .Options.linenostart nil -}}
|
||||
{{- $wrapper =
|
||||
replace $wrapper
|
||||
`class="code-wrapper"`
|
||||
(printf `class="code-wrapper" data-line-start="%v"` .)
|
||||
(printf `class="code-wrapper" data-line-start="%v"` .Options.linenostart)
|
||||
-}}
|
||||
{{- end -}}
|
||||
{{- $wrapperStyle := printf "--fi-max-shown-lines:%v;--fi-line-digit:%v;--fi-line-start:%v;" $maxShownLines $lineNosDigit $lineNoStart -}}
|
||||
|
||||
@@ -16,11 +16,11 @@
|
||||
<input type="password" id="{{ $id }}" class="fixit-decryptor-input" placeholder="🔑 {{ $message }}" />
|
||||
</label>
|
||||
<button class="fixit-decryptor-btn">
|
||||
{{- dict "Class" "fa-solid fa-unlock" | partial "plugin/icon.html" }} {{ T "single.enterBtn" -}}
|
||||
{{- dict "Class" "fa-solid fa-unlock" | partial "plugin/icon.html" }}{{ T "single.enterBtn" -}}
|
||||
</button>
|
||||
{{- if not .IsPartial -}}
|
||||
<button class="fixit-encryptor-btn">
|
||||
{{- dict "Class" "fa-solid fa-lock" | partial "plugin/icon.html" }} {{ T "single.encryptyAgain" -}}
|
||||
{{- dict "Class" "fa-solid fa-lock" | partial "plugin/icon.html" }}{{ T "single.encryptyAgain" -}}
|
||||
</button>
|
||||
{{- end -}}
|
||||
</div>
|
||||
|
||||
@@ -9,10 +9,10 @@
|
||||
<input type="password" id="fixit-decryptor-input" class="fixit-decryptor-input d-none" placeholder="🔑 {{ $msg }}" />
|
||||
</label>
|
||||
<button class="fixit-decryptor-btn d-none">
|
||||
{{- dict "Class" "fa-solid fa-unlock" | partial "plugin/icon.html" }} {{ T "single.enterBtn" -}}
|
||||
{{- dict "Class" "fa-solid fa-unlock" | partial "plugin/icon.html" }}{{ T "single.enterBtn" -}}
|
||||
</button>
|
||||
<button class="fixit-encryptor-btn d-none">
|
||||
{{- dict "Class" "fa-solid fa-lock" | partial "plugin/icon.html" }} {{ T "single.encryptyAgain" -}}
|
||||
{{- dict "Class" "fa-solid fa-lock" | partial "plugin/icon.html" }}{{ T "single.encryptyAgain" -}}
|
||||
</button>
|
||||
</div>
|
||||
{{- end -}}
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
{{- $audio = replace $audio "}" "}," | printf "[%s]" -}}
|
||||
{{- $audio = replace $audio ",]" "]" -}}
|
||||
<div class="aplayer-shortcode" data-audio="{{ $audio }}" data-options="{{ $options }}"></div>
|
||||
{{- .Page.Store.Set "hasAplayer" true -}}
|
||||
{{- else -}}
|
||||
{{- errorf "Only named params is supported: %s" .Position -}}
|
||||
{{- end -}}
|
||||
|
||||
@@ -1,54 +1,53 @@
|
||||
{{- /*
|
||||
Renders an HTML details element.
|
||||
Renders an HTML details element.
|
||||
|
||||
@param {string} [class] The value of the element's class attribute.
|
||||
@param {string} [name] The value of the element's name attribute.
|
||||
@param {string} [summary] The content of the child summary element.
|
||||
@param {string} [title] The value of the element's title attribute.
|
||||
@param {bool} [open=false] Whether to initially display the content of the details element.
|
||||
@param {string} [class] The value of the element's class attribute.
|
||||
@param {string} [name] The value of the element's name attribute.
|
||||
@param {string} [summary] The content of the child summary element.
|
||||
@param {string} [title] The value of the element's title attribute.
|
||||
@param {bool} [open=false] Whether to initially display the content of the details element.
|
||||
|
||||
@reference https://developer.mozilla.org/en-US/docs/Web/HTML/Element/details
|
||||
@reference https://developer.mozilla.org/en-US/docs/Web/HTML/Element/details
|
||||
|
||||
@examples
|
||||
@examples
|
||||
|
||||
{{< details >}}
|
||||
A basic collapsible section.
|
||||
{{< /details >}}
|
||||
{{< details >}}
|
||||
A basic collapsible section.
|
||||
{{< /details >}}
|
||||
|
||||
{{< details summary="Custom Summary Text" >}}
|
||||
Showing custom `summary` text.
|
||||
{{< /details >}}
|
||||
{{< details summary="Custom Summary Text" >}}
|
||||
Showing custom `summary` text.
|
||||
{{< /details >}}
|
||||
|
||||
{{< details summary="Open Details" open=true >}}
|
||||
Contents displayed initially by using `open`.
|
||||
{{< /details >}}
|
||||
{{< details summary="Open Details" open=true >}}
|
||||
Contents displayed initially by using `open`.
|
||||
{{< /details >}}
|
||||
|
||||
{{< details summary="Styled Content" class="my-custom-class" >}}
|
||||
Content can be styled with CSS by specifying a `class`.
|
||||
{{< details summary="Styled Content" class="my-custom-class" >}}
|
||||
Content can be styled with CSS by specifying a `class`.
|
||||
|
||||
Target details element:
|
||||
Target details element:
|
||||
|
||||
```css
|
||||
details.my-custom-class { }
|
||||
```
|
||||
```css
|
||||
details.my-custom-class { }
|
||||
```
|
||||
|
||||
Target summary element:
|
||||
Target summary element:
|
||||
|
||||
```css
|
||||
details.my-custom-class > summary > * { }
|
||||
```
|
||||
```css
|
||||
details.my-custom-class > summary > * { }
|
||||
```
|
||||
|
||||
Target inner content:
|
||||
Target inner content:
|
||||
|
||||
```css
|
||||
details.my-custom-class > :not(summary) { }
|
||||
```
|
||||
{{< /details >}}
|
||||
|
||||
{{< details summary="Grouped Details" name="my-details" >}}
|
||||
Specifying a `name` allows elements to be connected, with only one able to be open at a time.
|
||||
{{< /details >}}
|
||||
```css
|
||||
details.my-custom-class > :not(summary) { }
|
||||
```
|
||||
{{< /details >}}
|
||||
|
||||
{{< details summary="Grouped Details" name="my-details" >}}
|
||||
Specifying a `name` allows elements to be connected, with only one able to be open at a time.
|
||||
{{< /details >}}
|
||||
*/}}
|
||||
|
||||
{{- /* Get arguments. */}}
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
{{- $content := .Inner | .Page.RenderString -}}
|
||||
{{- /* Content Encryption */ -}}
|
||||
{{- dict "Content" $content "Password" $password "Message" $message "IsPartial" true "Page" .Page | partial "plugin/fixit-encryptor.html" -}}
|
||||
{{- .Page.Store.Set "hasEncryptor" true -}}
|
||||
{{- else -}}
|
||||
{{- .Inner -}}
|
||||
{{- end -}}
|
||||
|
||||
@@ -33,4 +33,4 @@
|
||||
|
||||
{{- $attrs := printf `style="width: %v; height: %v;"` $width $height -}}
|
||||
<div class="mapbox" data-options="{{ $options | jsonify }}" {{ $attrs | safeHTMLAttr }}></div>
|
||||
{{- /* EOF */ -}}
|
||||
{{- .Page.Store.Set "hasMapbox" true -}}
|
||||
|
||||
@@ -47,3 +47,4 @@
|
||||
{{- else -}}
|
||||
<meting-js server="{{ .Get 0 }}" type="{{ .Get 1 }}" id="{{ .Get 2 }}" theme="{{ $theme }}"></meting-js>
|
||||
{{- end -}}
|
||||
{{- .Page.Store.Set "hasMusic" true -}}
|
||||
|
||||
+7
-7
@@ -1,9 +1,9 @@
|
||||
{
|
||||
"name": "@hugo-fixit/core",
|
||||
"type": "module",
|
||||
"version": "0.4.3",
|
||||
"version": "0.4.5",
|
||||
"private": true,
|
||||
"packageManager": "pnpm@10.22.0",
|
||||
"packageManager": "pnpm@10.32.1",
|
||||
"description": "Hugo FixIt core theme component source files",
|
||||
"author": {
|
||||
"name": "Lruihao",
|
||||
@@ -42,12 +42,12 @@
|
||||
"prepare": "simple-git-hooks"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@antfu/eslint-config": "^7.6.1",
|
||||
"@types/node": "^24.11.0",
|
||||
"@antfu/eslint-config": "^7.7.3",
|
||||
"@types/node": "^25.5.0",
|
||||
"auto-changelog-plus": "^1.2.3",
|
||||
"eslint": "^9.39.2",
|
||||
"lint-staged": "^16.3.0",
|
||||
"serve": "^14.2.5",
|
||||
"eslint": "^10.0.3",
|
||||
"lint-staged": "^16.4.0",
|
||||
"serve": "^14.2.6",
|
||||
"simple-git-hooks": "^2.13.1",
|
||||
"tsx": "^4.21.0",
|
||||
"typescript": "^5.9.3"
|
||||
|
||||
Generated
+408
-424
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user