diff --git a/.gitignore b/.gitignore index 435dfd5ac..aedce2c6a 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 c76a10bc0..312fec0a2 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 5e22f53cc..9a8009e25 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 81095e1b1..5312f8113 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 000000000..b849d7e50 --- /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 488ba03e7..e6f0e5e4c 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 =