@djangocfg/widget-code 0.1.17 → 0.1.20
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/README.md +5 -5
- package/package.json +11 -16
- package/src/diff-viewer/hooks/useHighlighter.ts +7 -43
- package/src/diff-viewer/index.ts +0 -1
- package/src/index.ts +2 -2
- package/src/markdown-message/CodeBlock.tsx +3 -10
- package/src/panel-header/CodePanelHeader.tsx +0 -121
- package/src/panel-header/index.ts +0 -1
- package/src/pretty-code/PrettyCode.client.tsx +0 -402
- package/src/pretty-code/README.md +0 -81
- package/src/pretty-code/index.tsx +0 -72
- package/src/pretty-code/lazy.tsx +0 -69
- package/src/pretty-code/pretty-code.stories.tsx +0 -353
- package/src/pretty-code/registerPrismLanguages.ts +0 -116
package/README.md
CHANGED
|
@@ -13,7 +13,6 @@ pnpm add @djangocfg/widget-code @djangocfg/ui-core @djangocfg/i18n
|
|
|
13
13
|
|---|---|---|
|
|
14
14
|
| `./editor` | Monaco, single file or diff | ~550 KB, lazy |
|
|
15
15
|
| `./json-editor` | an editable JSON tree | `json-edit-react` |
|
|
16
|
-
| `./pretty-code` | a read-only listing with a panel header | Prism |
|
|
17
16
|
| `./diff-viewer` | unified or split diff, virtualised | Prism + virtuoso |
|
|
18
17
|
| `./diff-viewer/full` | the same, eagerly | |
|
|
19
18
|
| `./markdown-message` | markdown with embedded code, diagrams and chips | |
|
|
@@ -26,10 +25,11 @@ subpaths. The root exports the role tokens and nothing else, on purpose.
|
|
|
26
25
|
|
|
27
26
|
## Read-only code elsewhere
|
|
28
27
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
`
|
|
28
|
+
`PrettyCode` (a read-only listing with a panel header, Prism) lives in
|
|
29
|
+
`@djangocfg/widget-kit/pretty-code`, with `/pretty-code/lazy` for the deferred
|
|
30
|
+
form; `DiffViewer` and `MarkdownMessage` here use it from there, and so does the
|
|
31
|
+
chat's `Response`. `@djangocfg/widget-media/code` is a second, shiki listing with
|
|
32
|
+
no consumer in this repo.
|
|
33
33
|
|
|
34
34
|
## Verify
|
|
35
35
|
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@djangocfg/widget-code",
|
|
3
|
-
"version": "0.1.
|
|
4
|
-
"description": "Code surfaces: a Monaco editor, a diff viewer, a
|
|
3
|
+
"version": "0.1.20",
|
|
4
|
+
"description": "Code surfaces: a Monaco editor, a diff viewer, a JSON editor and the markdown renderer that embeds them",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"code-editor",
|
|
7
7
|
"monaco",
|
|
@@ -38,10 +38,6 @@
|
|
|
38
38
|
"types": "./src/json-editor/lazy.tsx",
|
|
39
39
|
"default": "./src/json-editor/lazy.tsx"
|
|
40
40
|
},
|
|
41
|
-
"./pretty-code": {
|
|
42
|
-
"types": "./src/pretty-code/lazy.tsx",
|
|
43
|
-
"default": "./src/pretty-code/lazy.tsx"
|
|
44
|
-
},
|
|
45
41
|
"./diff-viewer": {
|
|
46
42
|
"types": "./src/diff-viewer/lazy.tsx",
|
|
47
43
|
"default": "./src/diff-viewer/lazy.tsx"
|
|
@@ -74,14 +70,13 @@
|
|
|
74
70
|
"test:watch": "vitest"
|
|
75
71
|
},
|
|
76
72
|
"dependencies": {
|
|
77
|
-
"@djangocfg/widget-data": "^0.1.
|
|
78
|
-
"@djangocfg/widget-diagram": "^0.1.
|
|
79
|
-
"@djangocfg/widget-editor": "^0.1.
|
|
80
|
-
"@djangocfg/widget-kit": "^0.1.
|
|
73
|
+
"@djangocfg/widget-data": "^0.1.29",
|
|
74
|
+
"@djangocfg/widget-diagram": "^0.1.24",
|
|
75
|
+
"@djangocfg/widget-editor": "^0.1.103",
|
|
76
|
+
"@djangocfg/widget-kit": "^0.1.21",
|
|
81
77
|
"json-edit-react": "^2.0.5",
|
|
82
78
|
"monaco-editor": "^0.57.0",
|
|
83
79
|
"prism-react-renderer": "^2.4.1",
|
|
84
|
-
"prismjs": "^1.30.0",
|
|
85
80
|
"react-markdown": "10.1.0",
|
|
86
81
|
"react-virtuoso": "^4.18.20",
|
|
87
82
|
"rehype-external-links": "^3.0.0",
|
|
@@ -93,16 +88,16 @@
|
|
|
93
88
|
"remark-smartypants": "^3.0.3"
|
|
94
89
|
},
|
|
95
90
|
"peerDependencies": {
|
|
96
|
-
"@djangocfg/i18n": "^2.1.
|
|
97
|
-
"@djangocfg/ui-core": "^2.1.
|
|
91
|
+
"@djangocfg/i18n": "^2.1.645",
|
|
92
|
+
"@djangocfg/ui-core": "^2.1.645",
|
|
98
93
|
"lucide-react": "^1.68.0",
|
|
99
94
|
"react": "^19.0.0",
|
|
100
95
|
"react-dom": "^19.0.0"
|
|
101
96
|
},
|
|
102
97
|
"devDependencies": {
|
|
103
|
-
"@djangocfg/i18n": "^2.1.
|
|
104
|
-
"@djangocfg/typescript-config": "^2.1.
|
|
105
|
-
"@djangocfg/ui-core": "^2.1.
|
|
98
|
+
"@djangocfg/i18n": "^2.1.645",
|
|
99
|
+
"@djangocfg/typescript-config": "^2.1.645",
|
|
100
|
+
"@djangocfg/ui-core": "^2.1.645",
|
|
106
101
|
"@storybook/react-vite": "^10.6.5",
|
|
107
102
|
"@types/node": "^25.9.5",
|
|
108
103
|
"@types/react": "19.3.0",
|
|
@@ -1,54 +1,18 @@
|
|
|
1
1
|
// Adapted from jalcoui (MIT) — github.com/jal-co/ui
|
|
2
2
|
//
|
|
3
|
-
//
|
|
4
|
-
//
|
|
5
|
-
//
|
|
6
|
-
// `ruby`, `java`, `php` work in diffs without duplicating the dynamic
|
|
7
|
-
// imports.
|
|
3
|
+
// The language answer for DiffViewer. The tokenizer and the grammar loader are
|
|
4
|
+
// the kit's `PrettyCode` ones, so a diff and a code block agree on which
|
|
5
|
+
// languages exist and what a fence name means — no second table here.
|
|
8
6
|
|
|
9
7
|
import { useMemo } from 'react';
|
|
10
8
|
import type { Language } from 'prism-react-renderer';
|
|
11
|
-
import { useEnsurePrismLanguages } from '
|
|
9
|
+
import { normalizePrismLanguage, useEnsurePrismLanguages } from '@djangocfg/widget-kit/pretty-code';
|
|
12
10
|
|
|
13
11
|
/**
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*/
|
|
17
|
-
export function normalizeLanguage(language: string | undefined): Language {
|
|
18
|
-
if (!language) return 'text' as Language;
|
|
19
|
-
const lang = language.toLowerCase();
|
|
20
|
-
switch (lang) {
|
|
21
|
-
case 'js':
|
|
22
|
-
return 'javascript' as Language;
|
|
23
|
-
case 'ts':
|
|
24
|
-
return 'typescript' as Language;
|
|
25
|
-
case 'py':
|
|
26
|
-
return 'python' as Language;
|
|
27
|
-
case 'rb':
|
|
28
|
-
return 'ruby' as Language;
|
|
29
|
-
case 'sh':
|
|
30
|
-
case 'shell':
|
|
31
|
-
return 'bash' as Language;
|
|
32
|
-
case 'html':
|
|
33
|
-
case 'xml':
|
|
34
|
-
return 'markup' as Language;
|
|
35
|
-
case 'md':
|
|
36
|
-
return 'markdown' as Language;
|
|
37
|
-
case 'yml':
|
|
38
|
-
return 'yaml' as Language;
|
|
39
|
-
case 'golang':
|
|
40
|
-
return 'go' as Language;
|
|
41
|
-
default:
|
|
42
|
-
return lang as Language;
|
|
43
|
-
}
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
/**
|
|
47
|
-
* Subscribe to PrettyCode's extra-grammar loader and return the resolved
|
|
48
|
-
* Prism language key. The component re-renders once `bash`/`ruby`/… are
|
|
49
|
-
* registered so the first paint isn't stuck on plain text.
|
|
12
|
+
* The resolved Prism language key. The component re-renders once
|
|
13
|
+
* `bash`/`ruby`/… are registered so the first paint isn't stuck on plain text.
|
|
50
14
|
*/
|
|
51
15
|
export function useDiffLanguage(language: string | undefined): Language {
|
|
52
16
|
useEnsurePrismLanguages();
|
|
53
|
-
return useMemo(() =>
|
|
17
|
+
return useMemo(() => normalizePrismLanguage(language), [language]);
|
|
54
18
|
}
|
package/src/diff-viewer/index.ts
CHANGED
package/src/index.ts
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Code surfaces: an editor, a diff, a
|
|
2
|
+
* Code surfaces: an editor, a diff, a JSON editor and
|
|
3
3
|
* the markdown renderer that embeds them.
|
|
4
4
|
*
|
|
5
5
|
* No root barrel worth importing — Monaco alone is ~550 KB and `json-edit-react`
|
|
6
6
|
* and Prism sit behind their own subpaths. Import the one you want:
|
|
7
|
-
* `@djangocfg/widget-code/editor`, `/
|
|
7
|
+
* `@djangocfg/widget-code/editor`, `/diff-viewer`,
|
|
8
8
|
* `/json-editor`, `/markdown-message`.
|
|
9
9
|
*/
|
|
10
10
|
export { ANCHOR, TOGGLE } from './role-tokens';
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import React, { memo } from 'react';
|
|
2
2
|
import { CopyButton } from '@djangocfg/ui-core/components';
|
|
3
|
-
import PrettyCode from '
|
|
3
|
+
import { LazyPrettyCode as PrettyCode } from '@djangocfg/widget-kit/pretty-code/lazy';
|
|
4
4
|
|
|
5
5
|
interface CodeBlockProps {
|
|
6
6
|
code: string;
|
|
@@ -15,8 +15,8 @@ interface CodeBlockProps {
|
|
|
15
15
|
}
|
|
16
16
|
|
|
17
17
|
/**
|
|
18
|
-
* Markdown code block. Renders <PrettyCode> directly — its
|
|
19
|
-
*
|
|
18
|
+
* Markdown code block. Renders the kit's <PrettyCode> directly — its header
|
|
19
|
+
* already carries the language tag and the Copy action.
|
|
20
20
|
*
|
|
21
21
|
* Earlier versions stacked an extra <CopyButton> here too, which
|
|
22
22
|
* surfaced as two copy buttons on every code fence. The fallback
|
|
@@ -58,13 +58,6 @@ function CodeBlockRaw({ code, language, codeTheme = 'auto' }: CodeBlockProps) {
|
|
|
58
58
|
// dark page hid it). The inline var()-based surface needs no content
|
|
59
59
|
// scan, so it renders correctly in every consumer and both themes.
|
|
60
60
|
isCompact
|
|
61
|
-
// Disable click-to-scroll-isolation in chat markdown: code
|
|
62
|
-
// fences here are part of an assistant reply, not a docs
|
|
63
|
-
// viewer. Forcing the user to click into a small block to
|
|
64
|
-
// scroll past it interrupts the reading flow. The standalone
|
|
65
|
-
// PrettyCode use cases (docs, diff viewers, long code panes)
|
|
66
|
-
// keep the default `true`.
|
|
67
|
-
scrollIsolation={false}
|
|
68
61
|
/>
|
|
69
62
|
</div>
|
|
70
63
|
);
|
|
@@ -1,121 +0,0 @@
|
|
|
1
|
-
'use client';
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* CodePanelHeader — a slim, persistent header bar for the code panel
|
|
5
|
-
* (PrettyCode), GitHub / ChatGPT style: the detected language on the left,
|
|
6
|
-
* a copy button on the right. In flow (not an absolute hover overlay), so
|
|
7
|
-
* the copy button never floats over the code.
|
|
8
|
-
*
|
|
9
|
-
* Code panels are usually DARK (like the map basemap is always light), so
|
|
10
|
-
* the header defaults to FIXED on-dark colors — theme tokens
|
|
11
|
-
* (`text-foreground`, `border-border`) would render dark-on-dark / an
|
|
12
|
-
* over-bright divider on the dark panel. Pass `dark={false}` for a
|
|
13
|
-
* light-surfaced panel, which switches to muted semantic tokens.
|
|
14
|
-
*/
|
|
15
|
-
|
|
16
|
-
import { useCallback, useState, type ReactNode } from 'react';
|
|
17
|
-
|
|
18
|
-
import { cn } from '@djangocfg/ui-core/lib';
|
|
19
|
-
import { Check, Copy, Maximize2 } from 'lucide-react';
|
|
20
|
-
|
|
21
|
-
export interface CodePanelHeaderProps {
|
|
22
|
-
/** Display label on the left (e.g. "typescript", "json", "bash"). */
|
|
23
|
-
language?: ReactNode;
|
|
24
|
-
/** Text copied when the copy button is pressed. Omit to hide copy. */
|
|
25
|
-
copyValue?: string;
|
|
26
|
-
/** a11y label for the copy button. */
|
|
27
|
-
copyLabel?: string;
|
|
28
|
-
/** When set, an expand (⤢) button is shown that calls this — used to open
|
|
29
|
-
* the panel full-screen when it's height-capped. */
|
|
30
|
-
onExpand?: () => void;
|
|
31
|
-
/** a11y / tooltip label for the expand button. */
|
|
32
|
-
expandLabel?: string;
|
|
33
|
-
/** Extra trailing actions, rendered left of the copy button. */
|
|
34
|
-
actions?: ReactNode;
|
|
35
|
-
/** Dark panel (default). `false` → light-surface muted tokens. */
|
|
36
|
-
dark?: boolean;
|
|
37
|
-
className?: string;
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
export function CodePanelHeader({
|
|
41
|
-
language,
|
|
42
|
-
copyValue,
|
|
43
|
-
copyLabel = 'Copy',
|
|
44
|
-
onExpand,
|
|
45
|
-
expandLabel = 'Expand',
|
|
46
|
-
actions,
|
|
47
|
-
dark = true,
|
|
48
|
-
className,
|
|
49
|
-
}: CodePanelHeaderProps) {
|
|
50
|
-
const [copied, setCopied] = useState(false);
|
|
51
|
-
|
|
52
|
-
const onCopy = useCallback(() => {
|
|
53
|
-
if (copyValue == null) return;
|
|
54
|
-
void navigator.clipboard?.writeText(copyValue).then(() => {
|
|
55
|
-
setCopied(true);
|
|
56
|
-
setTimeout(() => setCopied(false), 1400);
|
|
57
|
-
});
|
|
58
|
-
}, [copyValue]);
|
|
59
|
-
|
|
60
|
-
// Fixed on-dark vs semantic light. The hairline is faint so it never
|
|
61
|
-
// reads as a heavy rule across the panel.
|
|
62
|
-
const dividerCls = dark ? 'border-white/[0.06]' : 'border-border/60';
|
|
63
|
-
const labelCls = dark ? 'text-white/40' : 'text-muted-foreground';
|
|
64
|
-
const btnCls = dark
|
|
65
|
-
? 'text-white/45 hover:bg-white/10 hover:text-white/80 focus-visible:ring-white/40'
|
|
66
|
-
: 'text-muted-foreground hover:bg-foreground/10 hover:text-foreground focus-visible:ring-ring';
|
|
67
|
-
|
|
68
|
-
return (
|
|
69
|
-
<div
|
|
70
|
-
className={cn(
|
|
71
|
-
'flex items-center justify-between gap-2 border-b px-3 py-1',
|
|
72
|
-
dividerCls,
|
|
73
|
-
className,
|
|
74
|
-
)}
|
|
75
|
-
>
|
|
76
|
-
<span
|
|
77
|
-
className={cn(
|
|
78
|
-
'min-w-0 truncate font-mono text-[11px] lowercase tracking-wide',
|
|
79
|
-
labelCls,
|
|
80
|
-
)}
|
|
81
|
-
>
|
|
82
|
-
{language}
|
|
83
|
-
</span>
|
|
84
|
-
|
|
85
|
-
<div className="flex shrink-0 items-center gap-0.5">
|
|
86
|
-
{actions}
|
|
87
|
-
{onExpand ? (
|
|
88
|
-
<button
|
|
89
|
-
type="button"
|
|
90
|
-
onClick={onExpand}
|
|
91
|
-
aria-label={expandLabel}
|
|
92
|
-
title={expandLabel}
|
|
93
|
-
className={cn(
|
|
94
|
-
'inline-flex size-5 items-center justify-center rounded-md transition-colors focus-visible:outline-none focus-visible:ring-1',
|
|
95
|
-
btnCls,
|
|
96
|
-
)}
|
|
97
|
-
>
|
|
98
|
-
<Maximize2 className="size-3" aria-hidden />
|
|
99
|
-
</button>
|
|
100
|
-
) : null}
|
|
101
|
-
{copyValue != null ? (
|
|
102
|
-
<button
|
|
103
|
-
type="button"
|
|
104
|
-
onClick={onCopy}
|
|
105
|
-
aria-label={copyLabel}
|
|
106
|
-
className={cn(
|
|
107
|
-
'inline-flex size-5 items-center justify-center rounded-md transition-colors focus-visible:outline-none focus-visible:ring-1',
|
|
108
|
-
btnCls,
|
|
109
|
-
)}
|
|
110
|
-
>
|
|
111
|
-
{copied ? (
|
|
112
|
-
<Check className="size-3" aria-hidden />
|
|
113
|
-
) : (
|
|
114
|
-
<Copy className="size-3" aria-hidden />
|
|
115
|
-
)}
|
|
116
|
-
</button>
|
|
117
|
-
) : null}
|
|
118
|
-
</div>
|
|
119
|
-
</div>
|
|
120
|
-
);
|
|
121
|
-
}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export { CodePanelHeader, type CodePanelHeaderProps } from './CodePanelHeader';
|