@dogsbay/ui 0.2.0-beta.162 → 0.2.0-beta.163
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dogsbay/ui",
|
|
3
|
-
"version": "0.2.0-beta.
|
|
3
|
+
"version": "0.2.0-beta.163",
|
|
4
4
|
"description": "Accessible UI components for Astro, inspired by Base UI and shadcn",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"exports": {
|
|
@@ -22,9 +22,9 @@
|
|
|
22
22
|
"katex": "^0.16.47",
|
|
23
23
|
"maplibre-gl": "^5.24.0",
|
|
24
24
|
"shiki": "^4.3.1",
|
|
25
|
-
"@dogsbay/elements": "0.2.0-beta.
|
|
26
|
-
"@dogsbay/
|
|
27
|
-
"@dogsbay/
|
|
25
|
+
"@dogsbay/elements": "0.2.0-beta.163",
|
|
26
|
+
"@dogsbay/primitives": "0.2.0-beta.163",
|
|
27
|
+
"@dogsbay/icons": "0.2.0-beta.163"
|
|
28
28
|
},
|
|
29
29
|
"files": [
|
|
30
30
|
"src",
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
import { Code } from "astro/components";
|
|
3
|
+
import { ansiColoursOnly, stripAnsi } from "./ansi";
|
|
3
4
|
import { tv } from "tailwind-variants";
|
|
4
5
|
|
|
5
6
|
const codeBlockWrapper = tv({
|
|
@@ -55,6 +56,16 @@ const LANG_ALIASES: Record<string, string> = {
|
|
|
55
56
|
};
|
|
56
57
|
const shikiLang = LANG_ALIASES[lang.toLowerCase()] ?? lang.toLowerCase();
|
|
57
58
|
|
|
59
|
+
// Tags that mean "no language": no label, as render-html (its PLAIN set, @dogsbay/render-html/lines).
|
|
60
|
+
// Only `plaintext` was hidden, so `txt` and `output` blocks showed "txt" and "output".
|
|
61
|
+
const PLAIN = new Set(["", "text", "txt", "plain", "plaintext", "none", "output", "log", "code", "codeblock"]);
|
|
62
|
+
const plain = PLAIN.has(lang.toLowerCase().trim());
|
|
63
|
+
// An `ansi` block shows its colours, not its escape codes: copy the text as shown
|
|
64
|
+
const isAnsi = shikiLang === "ansi";
|
|
65
|
+
const copyText = isAnsi ? stripAnsi(code) : code;
|
|
66
|
+
// Shiki draws colours only; any other sequence would garble the text (./ansi.ts)
|
|
67
|
+
const shownCode = isAnsi ? ansiColoursOnly(code) : code;
|
|
68
|
+
|
|
58
69
|
// Show header bar based on showTitle prop:
|
|
59
70
|
// - true: show when there's a title or a declared language (default, original behavior)
|
|
60
71
|
// - false: never show
|
|
@@ -66,7 +77,7 @@ const showHeader =
|
|
|
66
77
|
hasCaption ? true
|
|
67
78
|
: showTitle === false ? false
|
|
68
79
|
: showTitle === "auto" ? !!title
|
|
69
|
-
: !!title ||
|
|
80
|
+
: !!title || !plain;
|
|
70
81
|
// When no header, copy button floats inside the code body
|
|
71
82
|
const floatingCopy = copy && !showHeader;
|
|
72
83
|
---
|
|
@@ -90,12 +101,12 @@ const floatingCopy = copy && !showHeader;
|
|
|
90
101
|
<span class="select-none">{lang}</span>
|
|
91
102
|
)}
|
|
92
103
|
<span class="flex shrink-0 items-center gap-2">
|
|
93
|
-
{(hasCaption || title) &&
|
|
104
|
+
{(hasCaption || title) && !plain && <span class="select-none" data-part="language">{lang}</span>}
|
|
94
105
|
{copy && (
|
|
95
106
|
<button
|
|
96
107
|
class={codeBlockCopy()}
|
|
97
108
|
data-part="copy"
|
|
98
|
-
data-copy-text={
|
|
109
|
+
data-copy-text={copyText}
|
|
99
110
|
type="button"
|
|
100
111
|
aria-label="Copy code"
|
|
101
112
|
>
|
|
@@ -137,12 +148,12 @@ const floatingCopy = copy && !showHeader;
|
|
|
137
148
|
</Header>
|
|
138
149
|
)}
|
|
139
150
|
<div class="relative overflow-x-auto [&_pre]:!m-0 [&_pre]:!rounded-none [&_pre]:!border-0 [&_pre]:px-4 [&_pre]:py-3">
|
|
140
|
-
<Code code={
|
|
151
|
+
<Code code={shownCode} lang={shikiLang} themes={{ light: "github-light-default", dark: theme }} defaultColor={false} />
|
|
141
152
|
{floatingCopy && (
|
|
142
153
|
<button
|
|
143
154
|
class:list={[codeBlockCopy(), "absolute right-2 top-2 opacity-0 group-hover:opacity-100 transition-opacity"]}
|
|
144
155
|
data-part="copy"
|
|
145
|
-
data-copy-text={
|
|
156
|
+
data-copy-text={copyText}
|
|
146
157
|
type="button"
|
|
147
158
|
aria-label="Copy code"
|
|
148
159
|
>
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A terminal's escape sequences in an `ansi` code block (dogsbay-ssg, 2026-11-04). Shared with
|
|
3
|
+
* @dogsbay/render-html (copied by its sync-sources as `code-ansi.ts`), so both pipelines show and
|
|
4
|
+
* copy the same text.
|
|
5
|
+
*
|
|
6
|
+
* One pattern, linear (each alternative is a run of one character class with a fixed end):
|
|
7
|
+
* - CSI: `ESC [` or the 8-bit `\x9b`, parameters, intermediates, a final byte — or cut off at the
|
|
8
|
+
* end of the text;
|
|
9
|
+
* - OSC (titles, `ESC ] 8` hyperlinks): to BEL or `ESC \`, or the end of the text;
|
|
10
|
+
* - `ESC` + intermediates + a final byte (`ESC ( B`, which `tput sgr0` writes), and any other
|
|
11
|
+
* two-byte escape.
|
|
12
|
+
*/
|
|
13
|
+
const SEQUENCE = /(?:\x1b\[|\x9b)[0-?]*[ -/]*(?:[@-~]|$)|\x1b\][^\x07\x1b]*(?:\x07|\x1b\\|$)|\x1b[ -/]*[0-~]?/g;
|
|
14
|
+
|
|
15
|
+
/** Is this sequence a colour or style (SGR, `ESC [ … m`), the only kind a highlighter draws? */
|
|
16
|
+
const SGR = /^(?:\x1b\[|\x9b)[0-9;:]*m$/;
|
|
17
|
+
|
|
18
|
+
/** The text as a terminal shows it, every escape sequence left out (plain rendering, copy). */
|
|
19
|
+
export function stripAnsi(s: string): string {
|
|
20
|
+
return s.replace(SEQUENCE, "");
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Only the colour and style sequences kept, for Shiki's `ansi`: it reads `ESC [` as running to the
|
|
25
|
+
* next `m`, so `ESC [2K` (erase line) ate the next letters (`Compiling` → `piling`), and it shows
|
|
26
|
+
* an OSC hyperlink's bytes as text. What is left is what it draws correctly. 8-bit CSI is written
|
|
27
|
+
* in its 7-bit form.
|
|
28
|
+
*/
|
|
29
|
+
export function ansiColoursOnly(s: string): string {
|
|
30
|
+
return s.replace(SEQUENCE, (m) => (SGR.test(m) ? m.replace(/^\x9b/, "\x1b[") : ""));
|
|
31
|
+
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
import "./code-rich.css";
|
|
3
3
|
import { stripMarkersForCopy, stripUnprocessableMarkers } from "./markers";
|
|
4
|
+
import { ansiColoursOnly, stripAnsi } from "../code-block/ansi";
|
|
4
5
|
import { codeToHtml, bundledLanguages, type BundledLanguage, type BundledTheme } from "shiki";
|
|
5
6
|
import {
|
|
6
7
|
transformerNotationDiff,
|
|
@@ -246,19 +247,22 @@ if (wordHighlights) metaStr += `/${wordHighlights}/ `;
|
|
|
246
247
|
// Copy text: explicit override (diff blocks pass the post-change code)
|
|
247
248
|
// or the block's own code — either way every annotation form is
|
|
248
249
|
// stripped (see ./markers.ts).
|
|
249
|
-
const copyText = stripMarkersForCopy(copyTextOverride ?? code);
|
|
250
|
+
const copyText = stripMarkersForCopy(copyTextOverride ?? (lang.toLowerCase() === "ansi" ? stripAnsi(code) : code));
|
|
250
251
|
|
|
251
252
|
// Unknown languages (package-install, mdx-ish doc tokens) fall back
|
|
252
253
|
// to plaintext instead of throwing — same behavior as CodeBlock's
|
|
253
254
|
// <Code>. The header still shows the declared language label.
|
|
254
255
|
const requestedLang = lang.toLowerCase();
|
|
256
|
+
// `ansi` is no grammar: Shiki colours its escape codes itself (as CodeBlock's <Code>), and it has
|
|
257
|
+
// no comments for [!code] markers, so they are stripped as in plain text.
|
|
258
|
+
const isAnsi = requestedLang === "ansi";
|
|
255
259
|
const isPlainGrammar =
|
|
256
|
-
!(requestedLang in bundledLanguages) || ["plaintext", "text", "txt"
|
|
257
|
-
const shikiLang = isPlainGrammar ? "plaintext" : requestedLang;
|
|
260
|
+
!isAnsi && (!(requestedLang in bundledLanguages) || ["plaintext", "text", "txt"].includes(requestedLang));
|
|
261
|
+
const shikiLang = isAnsi ? "ansi" : isPlainGrammar ? "plaintext" : requestedLang;
|
|
258
262
|
|
|
259
263
|
// Markers Shiki cannot process would render as literal text — strip
|
|
260
264
|
// exactly those, leave the rest for the notation transformers.
|
|
261
|
-
const highlightCode = stripUnprocessableMarkers(code, isPlainGrammar);
|
|
265
|
+
const highlightCode = stripUnprocessableMarkers(isAnsi ? ansiColoursOnly(code) : code, isPlainGrammar || isAnsi);
|
|
262
266
|
|
|
263
267
|
const html = await codeToHtml(highlightCode, {
|
|
264
268
|
lang: shikiLang as BundledLanguage,
|
|
@@ -275,7 +279,10 @@ const html = await codeToHtml(highlightCode, {
|
|
|
275
279
|
const hasCaption = Astro.slots.has("caption");
|
|
276
280
|
const Wrapper = hasCaption ? "figure" : "div";
|
|
277
281
|
const Header = hasCaption ? "figcaption" : "div";
|
|
278
|
-
|
|
282
|
+
// Tags that mean "no language" show no label, as CodeBlock and render-html
|
|
283
|
+
const PLAIN = new Set(["", "text", "txt", "plain", "plaintext", "none", "output", "log", "code", "codeblock"]);
|
|
284
|
+
const plain = PLAIN.has(requestedLang.trim());
|
|
285
|
+
const hasLabel = hasCaption || !!title || !plain;
|
|
279
286
|
const showHeader = hasLabel;
|
|
280
287
|
// When no header, copy button floats inside the code body
|
|
281
288
|
const floatingCopy = copy && !showHeader;
|
|
@@ -301,7 +308,7 @@ const floatingCopy = copy && !showHeader;
|
|
|
301
308
|
<span class="select-none">{lang}</span>
|
|
302
309
|
)}
|
|
303
310
|
<span class="flex shrink-0 items-center gap-2">
|
|
304
|
-
{(hasCaption || title) &&
|
|
311
|
+
{(hasCaption || title) && !plain && <span class="select-none" data-part="language">{lang}</span>}
|
|
305
312
|
{copy && (
|
|
306
313
|
<button
|
|
307
314
|
class={codeRichCopy()}
|