diff --git a/.gitignore b/.gitignore index 435dfd5a..aedce2c6 100644 --- a/.gitignore +++ b/.gitignore @@ -30,3 +30,4 @@ $RECYCLE.BIN/ CHANGELOG.md .stash/ +docs diff --git a/apps/test/content/posts/codeblock-test.md b/apps/test/content/posts/codeblock-test.md index c76a10bc..312fec0a 100644 --- a/apps/test/content/posts/codeblock-test.md +++ b/apps/test/content/posts/codeblock-test.md @@ -180,6 +180,18 @@ function add(a, b) {

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.

+## Code Toggle + +```toggle {before_tabs="hugo."} +[params] +description = '' +keywords = [] + +[params.codeblock] +mode = 'classic' +wrapper = true +``` + ## Code block inside other blocks > [!NOTE]+ diff --git a/assets/css/_page/_single/_code.scss b/assets/css/_page/_single/_code.scss index 5e22f53c..9a8009e2 100644 --- a/assets/css/_page/_single/_code.scss +++ b/assets/css/_page/_single/_code.scss @@ -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; diff --git a/assets/js/theme.js b/assets/js/theme.js index 81095e1b..5312f811 100644 --- a/assets/js/theme.js +++ b/assets/js/theme.js @@ -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; diff --git a/layouts/_markup/render-codeblock-toggle.html b/layouts/_markup/render-codeblock-toggle.html new file mode 100644 index 00000000..b849d7e5 --- /dev/null +++ b/layouts/_markup/render-codeblock-toggle.html @@ -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 -}} diff --git a/layouts/_partials/plugin/code-block-wrapper.html b/layouts/_partials/plugin/code-block-wrapper.html index 488ba03e..e6f0e5e4 100644 --- a/layouts/_partials/plugin/code-block-wrapper.html +++ b/layouts/_partials/plugin/code-block-wrapper.html @@ -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 =