mirror of
https://github.com/hugo-fixit/FixIt.git
synced 2026-08-24 15:28:57 +00:00
feat(code): add toggle code block functionality and enhance code tabs (#719)
* feat(markup): add toggle code block functionality with multiple format support * fix(assets): improve filename generation for code downloads * feat(code): add `before_tabs` attribute support for tabbed code blocks * feat(code): implement visibility control for first tab content in code blocks
This commit is contained in:
@@ -30,3 +30,4 @@ $RECYCLE.BIN/
|
||||
|
||||
CHANGELOG.md
|
||||
.stash/
|
||||
docs
|
||||
|
||||
@@ -180,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]+
|
||||
|
||||
@@ -532,6 +532,11 @@ pre {
|
||||
padding-block: 0.875rem;
|
||||
}
|
||||
}
|
||||
|
||||
// for code tabs, only show the first tab content before JS loaded
|
||||
&[data-hidden] {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -628,6 +633,12 @@ pre {
|
||||
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;
|
||||
|
||||
+18
-7
@@ -462,15 +462,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);
|
||||
@@ -598,18 +601,19 @@ class FixIt {
|
||||
* init code tabs
|
||||
*/
|
||||
initCodeTabs() {
|
||||
const $codeBlocks = document.querySelectorAll('.code-block[data-tab-group]');
|
||||
const $codeBlocks = document.querySelectorAll('.code-block[group]');
|
||||
const processed = new Set();
|
||||
|
||||
Util.forEach($codeBlocks, ($block) => {
|
||||
if (processed.has($block)) return;
|
||||
|
||||
const groupName = $block.dataset.tabGroup;
|
||||
const groupName = $block.getAttribute('group');
|
||||
const $tabs = [];
|
||||
let $curr = $block;
|
||||
|
||||
// collect consecutive blocks with same group
|
||||
while ($curr && $curr.classList?.contains('code-block') && $curr.dataset.tabGroup === groupName) {
|
||||
while ($curr && $curr.classList?.contains('code-block') && $curr.getAttribute('group') === groupName) {
|
||||
delete $curr.dataset.hidden;
|
||||
$tabs.push($curr);
|
||||
processed.add($curr);
|
||||
$curr = $curr.nextElementSibling;
|
||||
@@ -641,6 +645,13 @@ class FixIt {
|
||||
$firstBlock.parentNode.insertBefore($container, $firstBlock);
|
||||
|
||||
const activeTabIndex = $tabs.findIndex(tab => tab.classList.contains('active'));
|
||||
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 = activeTabIndex === -1 && index === 0;
|
||||
|
||||
@@ -0,0 +1,65 @@
|
||||
{{- $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 -}}
|
||||
{{- $pageID := .Page.RelPermalink | default .Page.File.Path -}}
|
||||
{{- $seed := printf "%s|%s|%s" $pageID .Position .Inner -}}
|
||||
{{- $group := printf "toggle-%s" (md5 $seed) -}}
|
||||
{{- $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" $group -}}
|
||||
{{- 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 -}}
|
||||
{{- if ne .Attributes.before_tabs nil -}}
|
||||
{{- $commonAttrsStr = printf "%s, before_tabs=%q" $commonAttrsStr .Attributes.before_tabs -}}
|
||||
{{- 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" "" -}}
|
||||
{{- $block := printf "```%s {name=%q%s%s%s}\n%s\n```\n\n" $format $format $active $jsonViewer $commonAttrsStr $content -}}
|
||||
{{- $renderText = add $renderText $block -}}
|
||||
{{- end -}}
|
||||
{{- $renderText | .Page.RenderString -}}
|
||||
{{- else -}}
|
||||
{{- partial "plugin/code-block-wrapper.html" . -}}
|
||||
{{- end -}}
|
||||
@@ -67,10 +67,16 @@
|
||||
{{- 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 -}}
|
||||
{{- end -}}
|
||||
{{- $hidden := cond $isFirst "" " data-hidden" -}}
|
||||
{{- $wrapper =
|
||||
replace $wrapper
|
||||
(printf `class="%s"` $class)
|
||||
(printf `class="%s" data-tab-group="%s" data-tab-title="%s"` $class $config.group $tabTitle) -}}
|
||||
(printf `class="%s" data-tab-group="%s" data-tab-title="%s"%s` $class $config.group $tabTitle $hidden) -}}
|
||||
{{- end -}}
|
||||
{{- if ne .Options.linenostart nil -}}
|
||||
{{- $wrapper =
|
||||
|
||||
Reference in New Issue
Block a user