diff --git a/apps/test/content/posts/tabs-comprehensive-test.md b/apps/test/content/posts/tabs-comprehensive-test.md index 8d2ab0ba..b00b25b5 100644 --- a/apps/test/content/posts/tabs-comprehensive-test.md +++ b/apps/test/content/posts/tabs-comprehensive-test.md @@ -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 >}} diff --git a/assets/js/theme.js b/assets/js/theme.js index de326d9c..c9b55bf5 100644 --- a/assets/js/theme.js +++ b/assets/js/theme.js @@ -1613,44 +1613,50 @@ class FixIt { initPrint() { window.addEventListener('beforeprint', () => { const $content = document.getElementById('content'); - // revert code tabs to code blocks for better printing support - Util.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 printConfig = this.config.print || {}; + + if (printConfig.expandAdmonition) { + Util.forEach($content.querySelectorAll('.admonition'), ($el) => $el.classList.add('open')); + } + if (printConfig.expandCode) { + // revert code tabs to code blocks for better printing support + Util.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); + const $codeBlocks = $codeTabs.querySelectorAll('.code-block'); + $codeBlocks.forEach(($codeBlock) => { + delete $codeBlock.dataset.tabInit; + $codeTabs.parentElement.insertBefore($codeBlock, $codeTabs); + }); + $codeTabs.parentElement.removeChild($codeTabs); }); - $codeTabs.parentElement.removeChild($codeTabs); - }); - Util.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'); - } - }); - FileTree.expandAll($content); - Util.forEach($content.querySelectorAll('.details'), ($el) => { - $el.classList.add('open'); - }); - Util.forEach($content.querySelectorAll('details'), ($el) => { - $el.setAttribute('open', ''); - }); + Util.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) { + Util.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', () => { diff --git a/hugo.toml b/hugo.toml index a523b12e..a22277fb 100644 --- a/hugo.toml +++ b/hugo.toml @@ -1178,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/ diff --git a/layouts/_partials/assets.html b/layouts/_partials/assets.html index d645695d..a82491db 100644 --- a/layouts/_partials/assets.html +++ b/layouts/_partials/assets.html @@ -216,6 +216,7 @@ {{- /* 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 -}} @@ -307,6 +308,9 @@ {{- end -}} {{- end -}} +{{- /* Print */ -}} +{{- $config = partial "function/snake2camel.html" .Site.Params.print | dict "print" | merge $config -}} + {{- /* PostChat */ -}} {{- partial "plugin/post-chat-ai.html" . -}} diff --git a/layouts/_partials/function/snake2camel.html b/layouts/_partials/function/snake2camel.html new file mode 100644 index 00000000..5ba4959b --- /dev/null +++ b/layouts/_partials/function/snake2camel.html @@ -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 -}}