origam 2.34.0 → 2.35.1
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/CHANGELOG.md +18 -0
- package/dist/src/components/Code/OrigamCode.vue +7 -0
- package/dist/src/composables/Code/code.composable.cjs +19 -11
- package/dist/src/composables/Code/code.composable.js +19 -11
- package/dist/src/interfaces/Code/code.interface.d.ts +29 -8
- package/dist/src/types/Code/code.type.d.ts +14 -0
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -18,6 +18,24 @@ This project follows [Semantic Versioning](https://semver.org).
|
|
|
18
18
|
|
|
19
19
|
## [Unreleased]
|
|
20
20
|
|
|
21
|
+
## [2.35.1] - 2026-10-19
|
|
22
|
+
|
|
23
|
+
### Fixed — `OrigamCode` : un 2e bloc monté pendant un import de `shiki` en échec restait vide (#1189)
|
|
24
|
+
|
|
25
|
+
Quand `shiki` n'est pas résolvable, deux `<origam-code>` montés dans le même tick : le premier s'affichait en texte brut, le second restait **vide** (zone défilante de hauteur 0) avec un rejet de promesse non capturé. `loadHighlighter()` ne renvoyait la promesse munie de son repli qu'au premier appelant ; un appelant concurrent recevait la promesse brute, qui rejetait, et `rebuild()` n'avait qu'un `try/finally`.
|
|
26
|
+
|
|
27
|
+
- `loadHighlighter()` renvoie désormais la même promesse, déjà munie de son repli (`null` → code brut), à tous les appelants.
|
|
28
|
+
- `rebuild()` capture en plus un éventuel rejet et peint le code en clair : aucun bloc ne reste vide.
|
|
29
|
+
- Prouvé par `packages/tests/TU/components/Code/OrigamCode-concurrent-load-failure.spec.ts` (rouge sur le parent, vert après).
|
|
30
|
+
|
|
31
|
+
## [2.35.0] - 2026-10-19
|
|
32
|
+
|
|
33
|
+
### Added — `OrigamCode` : `lang` ouvert, `createOrigam({ code: { langs } })` (#1180)
|
|
34
|
+
|
|
35
|
+
- `lang` n'est plus limité aux 14 grammaires de `SUPPORTED_LANGS` quand un `code.highlighter` est fourni : la valeur est transmise telle quelle au highlighter, qui sait seul ce qu'il peut servir. S'il échoue, le bloc retombe sur `plaintext` (le `try/catch` existant autour de `codeToHtml`), avec un avertissement dev-only, une fois par langage. Avant, `resolveLang()` repliait tout `lang` inconnu sur `plaintext` avant de consulter le highlighter, donc `dockerfile` ne pouvait jamais être servi.
|
|
36
|
+
- Nouvelle option `createOrigam({ code: { langs } })` : liste passée à `createHighlighter` (défaut : `SUPPORTED_LANGS`). Avec le highlighter intégré, un `lang` présent dans `langs` est accepté.
|
|
37
|
+
- Typage : la prop `lang` devient `TCodeLangOpen` (`TCodeLang | (string & {})`), l'autocomplétion des 14 valeurs est conservée. `TCodeLang` reste la liste fermée.
|
|
38
|
+
|
|
21
39
|
## [2.34.0] - 2026-10-19
|
|
22
40
|
|
|
23
41
|
### Added — `OrigamCommandPalette` : prop `filter` pour afficher des résultats classés en externe (#1184)
|
|
@@ -266,6 +266,13 @@ async function rebuild() {
|
|
|
266
266
|
const html = await highlight(formattedCode.value, props.lang);
|
|
267
267
|
if (token !== _renderToken) return;
|
|
268
268
|
paintIntoDom(html);
|
|
269
|
+
} catch (err) {
|
|
270
|
+
console.warn("[origam] OrigamCode: highlighting failed \u2014 rendering plain code.", err);
|
|
271
|
+
if (token === _renderToken) {
|
|
272
|
+
const plain = document.createElement("div");
|
|
273
|
+
plain.textContent = formattedCode.value;
|
|
274
|
+
paintIntoDom(plain.innerHTML);
|
|
275
|
+
}
|
|
269
276
|
} finally {
|
|
270
277
|
if (token === _renderToken) isHighlighting.value = false;
|
|
271
278
|
}
|
|
@@ -34,19 +34,19 @@ function lruSet(key, value) {
|
|
|
34
34
|
}
|
|
35
35
|
_cache.set(key, value);
|
|
36
36
|
}
|
|
37
|
-
async function loadHighlighter(loader) {
|
|
37
|
+
async function loadHighlighter(loader, langs) {
|
|
38
38
|
if (_highlighterUnavailable) return null;
|
|
39
39
|
if (_highlighterPromise) return _highlighterPromise;
|
|
40
|
-
|
|
40
|
+
const raw = (async () => {
|
|
41
41
|
const shiki = loader ? await loader() : await Promise.resolve().then(() => require("shiki"));
|
|
42
42
|
const highlighter = await shiki.createHighlighter({
|
|
43
43
|
themes: [_code.CODE_LIGHT_THEME, _code.CODE_DARK_THEME],
|
|
44
|
-
langs: [..._code.SUPPORTED_LANGS]
|
|
44
|
+
langs: [...(langs ?? _code.SUPPORTED_LANGS)]
|
|
45
45
|
});
|
|
46
46
|
_highlighterReady = true;
|
|
47
47
|
return highlighter;
|
|
48
48
|
})();
|
|
49
|
-
|
|
49
|
+
_highlighterPromise = raw.catch(err => {
|
|
50
50
|
_highlighterUnavailable = true;
|
|
51
51
|
_highlighterPromise = null;
|
|
52
52
|
if (!_unavailableWarned) {
|
|
@@ -55,23 +55,28 @@ async function loadHighlighter(loader) {
|
|
|
55
55
|
}
|
|
56
56
|
return null;
|
|
57
57
|
});
|
|
58
|
+
return _highlighterPromise;
|
|
58
59
|
}
|
|
59
|
-
function resolveLang(lang) {
|
|
60
|
+
function resolveLang(lang, hasCustomHighlighter, extraLangs) {
|
|
61
|
+
if (hasCustomHighlighter) return lang;
|
|
60
62
|
if (_code.SUPPORTED_LANGS.includes(lang)) return lang;
|
|
63
|
+
if (extraLangs?.includes(lang)) return lang;
|
|
61
64
|
if (!_unsupportedLangWarned.has(lang)) {
|
|
62
65
|
_unsupportedLangWarned.add(lang);
|
|
63
|
-
console.warn(`[origam] OrigamCode: lang="${lang}" is not in the bundled grammar set. Falling back to "${_enums.CODE_LANG.PLAINTEXT}". Supported: ${_code.SUPPORTED_LANGS.join(", ")}.`);
|
|
66
|
+
console.warn(`[origam] OrigamCode: lang="${lang}" is not in the bundled grammar set. Falling back to "${_enums.CODE_LANG.PLAINTEXT}". Supported: ${[..._code.SUPPORTED_LANGS, ...(extraLangs ?? [])].join(", ")}. Add it with createOrigam({ code: { langs } }) or supply a code.highlighter.`);
|
|
64
67
|
}
|
|
65
68
|
return _enums.CODE_LANG.PLAINTEXT;
|
|
66
69
|
}
|
|
67
70
|
function useCode() {
|
|
68
|
-
const
|
|
71
|
+
const codeOptions = (0, _vue.inject)(_code.ORIGAM_CODE_KEY);
|
|
72
|
+
const highlighterLoader = codeOptions?.highlighter;
|
|
73
|
+
const customLangs = codeOptions?.langs;
|
|
69
74
|
async function highlight(code, lang) {
|
|
70
|
-
const resolvedLang = resolveLang(lang);
|
|
75
|
+
const resolvedLang = resolveLang(lang, highlighterLoader !== void 0, customLangs);
|
|
71
76
|
const key = makeCacheKey(code, resolvedLang);
|
|
72
77
|
const cached = lruGet(key);
|
|
73
78
|
if (cached !== void 0) return cached;
|
|
74
|
-
const highlighter = await loadHighlighter(highlighterLoader);
|
|
79
|
+
const highlighter = await loadHighlighter(highlighterLoader, customLangs);
|
|
75
80
|
if (!highlighter) {
|
|
76
81
|
const plain = escapeHtml(code);
|
|
77
82
|
lruSet(key, plain);
|
|
@@ -89,7 +94,10 @@ function useCode() {
|
|
|
89
94
|
try {
|
|
90
95
|
html = highlighter.codeToHtml(code, opts);
|
|
91
96
|
} catch (err) {
|
|
92
|
-
|
|
97
|
+
if (process.env?.DEV && !_unsupportedLangWarned.has(resolvedLang)) {
|
|
98
|
+
_unsupportedLangWarned.add(resolvedLang);
|
|
99
|
+
console.warn(`[origam] OrigamCode: shiki could not tokenise lang="${resolvedLang}", falling back to plaintext.`, err);
|
|
100
|
+
}
|
|
93
101
|
html = highlighter.codeToHtml(code, {
|
|
94
102
|
...opts,
|
|
95
103
|
lang: _enums.CODE_LANG.PLAINTEXT
|
|
@@ -99,7 +107,7 @@ function useCode() {
|
|
|
99
107
|
return html;
|
|
100
108
|
}
|
|
101
109
|
async function prime() {
|
|
102
|
-
await loadHighlighter(highlighterLoader);
|
|
110
|
+
await loadHighlighter(highlighterLoader, customLangs);
|
|
103
111
|
}
|
|
104
112
|
function isReady() {
|
|
105
113
|
return _highlighterReady;
|
|
@@ -27,22 +27,22 @@ function lruSet(key, value) {
|
|
|
27
27
|
}
|
|
28
28
|
_cache.set(key, value);
|
|
29
29
|
}
|
|
30
|
-
async function loadHighlighter(loader) {
|
|
30
|
+
async function loadHighlighter(loader, langs) {
|
|
31
31
|
if (_highlighterUnavailable) return null;
|
|
32
32
|
if (_highlighterPromise) return _highlighterPromise;
|
|
33
|
-
|
|
33
|
+
const raw = (async () => {
|
|
34
34
|
const shiki = loader ? await loader() : await import(
|
|
35
35
|
/* @vite-ignore */
|
|
36
36
|
"shiki"
|
|
37
37
|
);
|
|
38
38
|
const highlighter = await shiki.createHighlighter({
|
|
39
39
|
themes: [LIGHT_THEME, DARK_THEME],
|
|
40
|
-
langs: [...SUPPORTED_LANGS]
|
|
40
|
+
langs: [...langs ?? SUPPORTED_LANGS]
|
|
41
41
|
});
|
|
42
42
|
_highlighterReady = true;
|
|
43
43
|
return highlighter;
|
|
44
44
|
})();
|
|
45
|
-
|
|
45
|
+
_highlighterPromise = raw.catch((err) => {
|
|
46
46
|
_highlighterUnavailable = true;
|
|
47
47
|
_highlighterPromise = null;
|
|
48
48
|
if (!_unavailableWarned) {
|
|
@@ -54,25 +54,30 @@ async function loadHighlighter(loader) {
|
|
|
54
54
|
}
|
|
55
55
|
return null;
|
|
56
56
|
});
|
|
57
|
+
return _highlighterPromise;
|
|
57
58
|
}
|
|
58
|
-
function resolveLang(lang) {
|
|
59
|
+
function resolveLang(lang, hasCustomHighlighter, extraLangs) {
|
|
60
|
+
if (hasCustomHighlighter) return lang;
|
|
59
61
|
if (SUPPORTED_LANGS.includes(lang)) return lang;
|
|
62
|
+
if (extraLangs?.includes(lang)) return lang;
|
|
60
63
|
if (!_unsupportedLangWarned.has(lang)) {
|
|
61
64
|
_unsupportedLangWarned.add(lang);
|
|
62
65
|
console.warn(
|
|
63
|
-
`[origam] OrigamCode: lang="${lang}" is not in the bundled grammar set. Falling back to "${CODE_LANG.PLAINTEXT}". Supported: ${SUPPORTED_LANGS.join(", ")}.`
|
|
66
|
+
`[origam] OrigamCode: lang="${lang}" is not in the bundled grammar set. Falling back to "${CODE_LANG.PLAINTEXT}". Supported: ${[...SUPPORTED_LANGS, ...extraLangs ?? []].join(", ")}. Add it with createOrigam({ code: { langs } }) or supply a code.highlighter.`
|
|
64
67
|
);
|
|
65
68
|
}
|
|
66
69
|
return CODE_LANG.PLAINTEXT;
|
|
67
70
|
}
|
|
68
71
|
export function useCode() {
|
|
69
|
-
const
|
|
72
|
+
const codeOptions = inject(ORIGAM_CODE_KEY);
|
|
73
|
+
const highlighterLoader = codeOptions?.highlighter;
|
|
74
|
+
const customLangs = codeOptions?.langs;
|
|
70
75
|
async function highlight(code, lang) {
|
|
71
|
-
const resolvedLang = resolveLang(lang);
|
|
76
|
+
const resolvedLang = resolveLang(lang, highlighterLoader !== void 0, customLangs);
|
|
72
77
|
const key = makeCacheKey(code, resolvedLang);
|
|
73
78
|
const cached = lruGet(key);
|
|
74
79
|
if (cached !== void 0) return cached;
|
|
75
|
-
const highlighter = await loadHighlighter(highlighterLoader);
|
|
80
|
+
const highlighter = await loadHighlighter(highlighterLoader, customLangs);
|
|
76
81
|
if (!highlighter) {
|
|
77
82
|
const plain = escapeHtml(code);
|
|
78
83
|
lruSet(key, plain);
|
|
@@ -87,14 +92,17 @@ export function useCode() {
|
|
|
87
92
|
try {
|
|
88
93
|
html = highlighter.codeToHtml(code, opts);
|
|
89
94
|
} catch (err) {
|
|
90
|
-
|
|
95
|
+
if (import.meta.env?.DEV && !_unsupportedLangWarned.has(resolvedLang)) {
|
|
96
|
+
_unsupportedLangWarned.add(resolvedLang);
|
|
97
|
+
console.warn(`[origam] OrigamCode: shiki could not tokenise lang="${resolvedLang}", falling back to plaintext.`, err);
|
|
98
|
+
}
|
|
91
99
|
html = highlighter.codeToHtml(code, { ...opts, lang: CODE_LANG.PLAINTEXT });
|
|
92
100
|
}
|
|
93
101
|
lruSet(key, html);
|
|
94
102
|
return html;
|
|
95
103
|
}
|
|
96
104
|
async function prime() {
|
|
97
|
-
await loadHighlighter(highlighterLoader);
|
|
105
|
+
await loadHighlighter(highlighterLoader, customLangs);
|
|
98
106
|
}
|
|
99
107
|
function isReady() {
|
|
100
108
|
return _highlighterReady;
|
|
@@ -7,7 +7,7 @@ import type { IMarginProps } from '../Commons/margin.interface';
|
|
|
7
7
|
import type { IPaddingProps } from '../Commons/padding.interface';
|
|
8
8
|
import type { IRoundedProps } from '../Commons/rounded.interface';
|
|
9
9
|
import type { ITypographyProps } from '../Commons/typography.interface';
|
|
10
|
-
import type {
|
|
10
|
+
import type { TCodeLangOpen, TShikiHighlighterLoader } from '../../types/Code/code.type';
|
|
11
11
|
/**
|
|
12
12
|
* Props for `<OrigamCode>` — a shiki-powered code block with line numbers,
|
|
13
13
|
* line highlighting, copy button and theme awareness.
|
|
@@ -26,11 +26,15 @@ export interface ICodeProps extends ICommonsComponentProps, ITagProps, IBorderPr
|
|
|
26
26
|
*/
|
|
27
27
|
code?: string;
|
|
28
28
|
/**
|
|
29
|
-
* Language grammar to apply.
|
|
30
|
-
*
|
|
31
|
-
*
|
|
29
|
+
* Language grammar to apply. Autocompletes the 14 values of
|
|
30
|
+
* `SUPPORTED_LANGS` but accepts any string (#1180). Without a custom
|
|
31
|
+
* `code.highlighter`, a value outside `SUPPORTED_LANGS` and outside
|
|
32
|
+
* `createOrigam({ code: { langs } })` falls back to `plaintext` (one-shot
|
|
33
|
+
* console warning). With a custom `code.highlighter`, the value is handed
|
|
34
|
+
* to it as-is; if it cannot tokenise it, the block falls back to
|
|
35
|
+
* `plaintext` (dev-only warning).
|
|
32
36
|
*/
|
|
33
|
-
lang?:
|
|
37
|
+
lang?: TCodeLangOpen;
|
|
34
38
|
/**
|
|
35
39
|
* Show a left gutter with line numbers. Pure CSS — uses a CSS counter
|
|
36
40
|
* incremented per `.origam-code__row`, no JS layout work.
|
|
@@ -90,7 +94,7 @@ export interface ICodeProps extends ICommonsComponentProps, ITagProps, IBorderPr
|
|
|
90
94
|
*
|
|
91
95
|
* @description
|
|
92
96
|
* App-level config for `<OrigamCode>`, passed via `createOrigam({ code })`.
|
|
93
|
-
* Today it carries
|
|
97
|
+
* Today it carries two knobs — `highlighter` and `langs` — and it is its own
|
|
94
98
|
* interface (rather than a bare function on `IOrigamOptions`) so a future
|
|
95
99
|
* `OrigamCode`-wide setting (e.g. a default theme pair) has somewhere to
|
|
96
100
|
* land without another breaking shape change.
|
|
@@ -116,6 +120,23 @@ export interface ICodeProps extends ICommonsComponentProps, ITagProps, IBorderPr
|
|
|
116
120
|
********************************************************/
|
|
117
121
|
export interface ICodeOptions {
|
|
118
122
|
highlighter?: TShikiHighlighterLoader;
|
|
123
|
+
/*********************************************************
|
|
124
|
+
* langs
|
|
125
|
+
*
|
|
126
|
+
* @description
|
|
127
|
+
* Grammars handed to `createHighlighter({ langs })`. Default:
|
|
128
|
+
* `SUPPORTED_LANGS` (the 14 bundled ones). Replaces the default list when
|
|
129
|
+
* set — include `plaintext` if you rely on it.
|
|
130
|
+
*
|
|
131
|
+
* @description
|
|
132
|
+
* With the built-in highlighter, a `lang` listed here is accepted instead
|
|
133
|
+
* of being folded onto `plaintext`; with a custom `highlighter`, `lang` is
|
|
134
|
+
* never filtered, and this only controls what is loaded up front.
|
|
135
|
+
*
|
|
136
|
+
* @description
|
|
137
|
+
* Like `highlighter`, read once (first `<OrigamCode>` to mount wins).
|
|
138
|
+
********************************************************/
|
|
139
|
+
langs?: ReadonlyArray<string>;
|
|
119
140
|
}
|
|
120
141
|
/** Emits fired by `<OrigamCode>` — copy-to-clipboard confirmation. */
|
|
121
142
|
export interface ICodeEmits {
|
|
@@ -127,7 +148,7 @@ export interface ICodeSlots {
|
|
|
127
148
|
* consumer can rebuild the default filename/lang badge + copy button. */
|
|
128
149
|
header?: (data: {
|
|
129
150
|
filename: string | undefined;
|
|
130
|
-
langName:
|
|
151
|
+
langName: TCodeLangOpen | undefined;
|
|
131
152
|
copy: () => Promise<void>;
|
|
132
153
|
copied: boolean;
|
|
133
154
|
}) => any;
|
|
@@ -146,7 +167,7 @@ export interface IUseCodeReturn {
|
|
|
146
167
|
* underlying shiki highlighter has loaded the requested language /
|
|
147
168
|
* theme (lazy; ~200 ms cold, instant on subsequent calls).
|
|
148
169
|
*/
|
|
149
|
-
highlight(code: string, lang:
|
|
170
|
+
highlight(code: string, lang: TCodeLangOpen): Promise<string>;
|
|
150
171
|
/**
|
|
151
172
|
* Force the highlighter to load. Useful for stories / tests that want
|
|
152
173
|
* to warm the cache before mounting consumers.
|
|
@@ -1,7 +1,21 @@
|
|
|
1
1
|
import { CODE_LANG, CODE_THEME } from '../../enums/Code/code.enum';
|
|
2
2
|
import OrigamCode from '../../components/Code/OrigamCode.vue';
|
|
3
|
+
import type { TAnyString } from '../Commons/commons.type';
|
|
3
4
|
export type TOrigamCode = InstanceType<typeof OrigamCode>;
|
|
4
5
|
export type TCodeLang = `${CODE_LANG}`;
|
|
6
|
+
/*********************************************************
|
|
7
|
+
* TCodeLangOpen
|
|
8
|
+
*
|
|
9
|
+
* @description
|
|
10
|
+
* Open form of `TCodeLang` accepted by the `lang` prop (#1180): the 14 bundled
|
|
11
|
+
* grammars keep their autocompletion, any other string is allowed because a
|
|
12
|
+
* `createOrigam({ code: { highlighter, langs } })` highlighter can serve it.
|
|
13
|
+
*
|
|
14
|
+
* @description
|
|
15
|
+
* `TAnyString` (Commons) stops the union from collapsing to plain `string`,
|
|
16
|
+
* and avoids the empty-object intersection Sonar flags (#771).
|
|
17
|
+
********************************************************/
|
|
18
|
+
export type TCodeLangOpen = TCodeLang | TAnyString;
|
|
5
19
|
export type TCodeTheme = `${CODE_THEME}`;
|
|
6
20
|
/**
|
|
7
21
|
* The slice of shiki's highlighter API that `useCode()` actually calls.
|