@mk-kit/ui 0.47.0 → 0.49.0

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.
@@ -5,6 +5,8 @@ import { mkUniqueId, MK_I18N, MkFieldContext, mkInjectFieldTouched, mkHighlight
5
5
  import { MkIcon } from '@mk-kit/ui/icon';
6
6
  import { NG_VALUE_ACCESSOR } from '@angular/forms';
7
7
  import { MkCopyToClipboard } from '@mk-kit/ui/directives';
8
+ import { mkComputeDiff, mkDiffStats, mkStripAnsi, mkParseAnsi, mkRenderMarkdown, mkParseMarkdown } from '@mk-kit/core';
9
+ export { mkComputeDiff, mkDiffStats, mkParseAnsi, mkParseMarkdown, mkRenderMarkdown, mkStripAnsi } from '@mk-kit/core';
8
10
  import { mkMoveItemInArray, MkDropList, MkDrag } from '@mk-kit/ui/dnd';
9
11
 
10
12
  /**
@@ -4781,187 +4783,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.7", ngImpor
4781
4783
  args: [{ selector: 'mk-carousel', changeDetection: ChangeDetectionStrategy.OnPush, host: { class: 'mk-carousel' }, template: "<div\n class=\"mk-carousel__viewport\"\n role=\"region\"\n aria-roledescription=\"carousel\"\n [attr.aria-label]=\"ariaLabel()\"\n tabindex=\"0\"\n (keydown)=\"onKeydown($event)\"\n (mouseenter)=\"pause()\"\n (mouseleave)=\"resume()\"\n (focusin)=\"pause()\"\n (focusout)=\"resume()\"\n (pointerdown)=\"onPointerDown($event)\"\n (pointermove)=\"onPointerMove($event)\"\n (pointerup)=\"onPointerUp($event)\"\n (pointercancel)=\"onPointerUp($event)\"\n>\n @if (autoplay()) {\n <button\n type=\"button\"\n class=\"mk-carousel__playpause\"\n [attr.aria-label]=\"userPaused() ? i18n.playSlideshow : i18n.pauseSlideshow\"\n [attr.aria-pressed]=\"userPaused()\"\n (click)=\"toggleUserPause()\"\n >\n <span aria-hidden=\"true\">{{ userPaused() ? '\u25B6' : '\u23F8' }}</span>\n </button>\n }\n <div\n class=\"mk-carousel__track\"\n [class.mk-carousel__track--dragging]=\"dragging()\"\n [style.transform]=\"trackTransform()\"\n >\n <ng-content />\n </div>\n\n @if (showArrows() && count() > 1) {\n <button\n type=\"button\"\n class=\"mk-carousel__arrow mk-carousel__arrow--prev\"\n [attr.aria-label]=\"i18n.previousSlide\"\n (click)=\"prev()\"\n >\n <span aria-hidden=\"true\">\u2039</span>\n </button>\n <button\n type=\"button\"\n class=\"mk-carousel__arrow mk-carousel__arrow--next\"\n [attr.aria-label]=\"i18n.nextSlide\"\n (click)=\"next()\"\n >\n <span aria-hidden=\"true\">\u203A</span>\n </button>\n }\n</div>\n\n@if (showDots() && count() > 1) {\n <div class=\"mk-carousel__dots\">\n @for (dot of dots(); track dot) {\n <button\n type=\"button\"\n class=\"mk-carousel__dot\"\n [class.mk-carousel__dot--active]=\"dot === index()\"\n [attr.aria-current]=\"dot === index() || null\"\n [attr.aria-label]=\"i18n.goToSlide(dot + 1)\"\n (click)=\"goTo(dot)\"\n ></button>\n }\n </div>\n}\n\n<div class=\"mk-visually-hidden\" [attr.aria-live]=\"liveMode()\">{{ liveText() }}</div>\n", styles: ["@charset \"UTF-8\";:host{display:block;font-family:var(--mk-font-sans)}.mk-carousel__viewport{position:relative;overflow:hidden;border-radius:var(--mk-radius-md);touch-action:pan-y}.mk-carousel__viewport:focus-visible{outline:var(--mk-focus-ring-width) solid var(--mk-focus-ring);outline-offset:var(--mk-focus-ring-offset)}.mk-carousel__track{display:flex;transition:transform var(--mk-duration-normal, .24s) var(--mk-ease-standard)}.mk-carousel__track--dragging{transition:none}:host ::ng-deep .mk-carousel__slide{flex:0 0 100%;min-width:0;box-sizing:border-box}.mk-carousel__playpause{position:absolute;top:var(--mk-space-2);inset-inline-start:var(--mk-space-2);z-index:1;display:grid;place-items:center;width:2rem;height:2rem;padding:0;font-size:var(--mk-font-size-sm);line-height:1;color:var(--mk-text);background-color:var(--mk-surface);border:var(--mk-border-width) solid var(--mk-border);border-radius:var(--mk-radius-circle);box-shadow:var(--mk-shadow-sm);cursor:pointer;transition:background-color var(--mk-duration-fast) var(--mk-ease-standard)}.mk-carousel__playpause:hover{background-color:var(--mk-surface-2)}.mk-carousel__playpause:focus-visible{outline:var(--mk-focus-ring-width) solid var(--mk-focus-ring);outline-offset:var(--mk-focus-ring-offset)}.mk-carousel__arrow{position:absolute;top:50%;transform:translateY(-50%);display:grid;place-items:center;width:2rem;height:2rem;font-size:var(--mk-font-size-lg);line-height:1;color:var(--mk-text);background-color:var(--mk-surface);border:var(--mk-border-width) solid var(--mk-border);border-radius:var(--mk-radius-circle);box-shadow:var(--mk-shadow-sm);cursor:pointer;transition:background-color var(--mk-duration-fast) var(--mk-ease-standard)}.mk-carousel__arrow:hover{background-color:var(--mk-surface-2)}.mk-carousel__arrow:focus-visible{outline:var(--mk-focus-ring-width) solid var(--mk-focus-ring);outline-offset:var(--mk-focus-ring-offset)}.mk-carousel__arrow--prev{inset-inline-start:var(--mk-space-2)}.mk-carousel__arrow--next{inset-inline-end:var(--mk-space-2)}.mk-carousel__arrow:dir(rtl) span{display:inline-block;transform:scaleX(-1)}.mk-carousel__dots{display:flex;justify-content:center;gap:var(--mk-space-2);margin-top:var(--mk-space-2)}.mk-carousel__dot{width:.5rem;height:.5rem;padding:0;background-color:var(--mk-border-strong);border:none;border-radius:var(--mk-radius-circle);cursor:pointer;transition:background-color var(--mk-duration-fast) var(--mk-ease-standard),transform var(--mk-duration-fast) var(--mk-ease-standard)}.mk-carousel__dot:hover{background-color:var(--mk-text-muted)}.mk-carousel__dot:focus-visible{outline:var(--mk-focus-ring-width) solid var(--mk-focus-ring);outline-offset:var(--mk-focus-ring-offset)}.mk-carousel__dot--active{background-color:var(--mk-primary);transform:scale(1.25)}@media(prefers-reduced-motion:reduce){.mk-carousel__track,.mk-carousel__arrow,.mk-carousel__dot{transition:none}}\n"] }]
4782
4784
  }], ctorParameters: () => [], propDecorators: { slides: [{ type: i0.ContentChildren, args: [i0.forwardRef(() => MkCarouselSlide), { isSignal: true }] }], index: [{ type: i0.Input, args: [{ isSignal: true, alias: "index", required: false }] }, { type: i0.Output, args: ["indexChange"] }], ariaLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "ariaLabel", required: false }] }], loop: [{ type: i0.Input, args: [{ isSignal: true, alias: "loop", required: false }] }], showDots: [{ type: i0.Input, args: [{ isSignal: true, alias: "showDots", required: false }] }], showArrows: [{ type: i0.Input, args: [{ isSignal: true, alias: "showArrows", required: false }] }], autoplay: [{ type: i0.Input, args: [{ isSignal: true, alias: "autoplay", required: false }] }], interval: [{ type: i0.Input, args: [{ isSignal: true, alias: "interval", required: false }] }], userPaused: [{ type: i0.Input, args: [{ isSignal: true, alias: "userPaused", required: false }] }, { type: i0.Output, args: ["userPausedChange"] }] } });
4783
4785
 
4784
- /**
4785
- * Dependency-free text diff for {@link MkDiff} — an LCS line diff with optional
4786
- * intra-line word highlighting. Pure and SSR-safe (no DOM). Suited to revision
4787
- * comparison ("what changed between save N and N+1").
4788
- */
4789
- /**
4790
- * The largest DP matrix lcsOps will allocate (cells). Above this the diff
4791
- * falls back to a plain delete-all/insert-all block instead of freezing the
4792
- * tab on huge inputs (an n×m matrix of two 10k-line files is 100M cells).
4793
- */
4794
- const MAX_LCS_CELLS = 4_000_000;
4795
- /** LCS diff of two sequences (dynamic programming + backtrack). */
4796
- function lcsOps(a, b, eq) {
4797
- // Trim the common prefix/suffix first — the usual case for revisions is a
4798
- // small edit inside a large document, which shrinks the matrix massively.
4799
- let lead = 0;
4800
- const maxLead = Math.min(a.length, b.length);
4801
- while (lead < maxLead && eq(a[lead], b[lead]))
4802
- lead++;
4803
- let trail = 0;
4804
- while (trail < maxLead - lead &&
4805
- eq(a[a.length - 1 - trail], b[b.length - 1 - trail])) {
4806
- trail++;
4807
- }
4808
- if (lead > 0 || trail > 0) {
4809
- const head = a.slice(0, lead).map((x, k) => ({
4810
- op: 'equal',
4811
- a: x,
4812
- b: b[k],
4813
- }));
4814
- const tail = a.slice(a.length - trail).map((x, k) => ({
4815
- op: 'equal',
4816
- a: x,
4817
- b: b[b.length - trail + k],
4818
- }));
4819
- return [
4820
- ...head,
4821
- ...lcsCore(a.slice(lead, a.length - trail), b.slice(lead, b.length - trail), eq),
4822
- ...tail,
4823
- ];
4824
- }
4825
- return lcsCore(a, b, eq);
4826
- }
4827
- function lcsCore(a, b, eq) {
4828
- const n = a.length;
4829
- const m = b.length;
4830
- if (n === 0 && m === 0)
4831
- return [];
4832
- // Guard against quadratic blow-up on huge, mostly-different inputs.
4833
- if ((n + 1) * (m + 1) > MAX_LCS_CELLS) {
4834
- return [
4835
- ...a.map((x) => ({ op: 'delete', a: x })),
4836
- ...b.map((x) => ({ op: 'insert', b: x })),
4837
- ];
4838
- }
4839
- // dp[i][j] = LCS length of a[i:] and b[j:].
4840
- const dp = Array.from({ length: n + 1 }, () => new Array(m + 1).fill(0));
4841
- for (let i = n - 1; i >= 0; i--) {
4842
- for (let j = m - 1; j >= 0; j--) {
4843
- dp[i][j] = eq(a[i], b[j])
4844
- ? dp[i + 1][j + 1] + 1
4845
- : Math.max(dp[i + 1][j], dp[i][j + 1]);
4846
- }
4847
- }
4848
- const ops = [];
4849
- let i = 0;
4850
- let j = 0;
4851
- while (i < n && j < m) {
4852
- if (eq(a[i], b[j])) {
4853
- ops.push({ op: 'equal', a: a[i], b: b[j] });
4854
- i++;
4855
- j++;
4856
- }
4857
- else if (dp[i + 1][j] >= dp[i][j + 1]) {
4858
- ops.push({ op: 'delete', a: a[i] });
4859
- i++;
4860
- }
4861
- else {
4862
- ops.push({ op: 'insert', b: b[j] });
4863
- j++;
4864
- }
4865
- }
4866
- while (i < n)
4867
- ops.push({ op: 'delete', a: a[i++] });
4868
- while (j < m)
4869
- ops.push({ op: 'insert', b: b[j++] });
4870
- return ops;
4871
- }
4872
- /** Split a line into word/whitespace tokens (separators preserved). */
4873
- function tokenize(line) {
4874
- return line.split(/(\s+)/).filter((t) => t.length > 0);
4875
- }
4876
- /** Word-level segments for a paired (deleted, inserted) line. */
4877
- function wordSegments(before, after) {
4878
- const ops = lcsOps(tokenize(before), tokenize(after), (x, y) => x === y);
4879
- const bSeg = [];
4880
- const aSeg = [];
4881
- for (const o of ops) {
4882
- if (o.op === 'equal') {
4883
- bSeg.push({ text: o.a, changed: false });
4884
- aSeg.push({ text: o.b, changed: false });
4885
- }
4886
- else if (o.op === 'delete') {
4887
- bSeg.push({ text: o.a, changed: true });
4888
- }
4889
- else {
4890
- aSeg.push({ text: o.b, changed: true });
4891
- }
4892
- }
4893
- return { before: bSeg, after: aSeg };
4894
- }
4895
- /** Pair adjacent delete/insert runs and attach word segments to each row. */
4896
- function addWordHighlights(rows) {
4897
- let i = 0;
4898
- while (i < rows.length) {
4899
- if (rows[i].op !== 'delete') {
4900
- i++;
4901
- continue;
4902
- }
4903
- let d = i;
4904
- while (d < rows.length && rows[d].op === 'delete')
4905
- d++;
4906
- let s = d;
4907
- while (s < rows.length && rows[s].op === 'insert')
4908
- s++;
4909
- const deletes = rows.slice(i, d);
4910
- const inserts = rows.slice(d, s);
4911
- const pairs = Math.min(deletes.length, inserts.length);
4912
- for (let k = 0; k < pairs; k++) {
4913
- const seg = wordSegments(deletes[k].text, inserts[k].text);
4914
- deletes[k].segments = seg.before;
4915
- inserts[k].segments = seg.after;
4916
- }
4917
- i = s;
4918
- }
4919
- }
4920
- /**
4921
- * Compute a line diff between `before` and `after`. Returns one {@link MkDiffRow}
4922
- * per line in document order, with `equal` / `insert` / `delete` ops and 1-based
4923
- * before/after line numbers. With `wordHighlight` (default true) changed lines
4924
- * also carry word-level `segments`.
4925
- */
4926
- function mkComputeDiff(before, after, options = {}) {
4927
- const { wordHighlight = true, ignoreTrailingWhitespace = false } = options;
4928
- const norm = (s) => ignoreTrailingWhitespace ? s.replace(/[ \t]+$/, '') : s;
4929
- const beforeLines = String(before ?? '').split('\n');
4930
- const afterLines = String(after ?? '').split('\n');
4931
- const ops = lcsOps(beforeLines, afterLines, (x, y) => norm(x) === norm(y));
4932
- const rows = [];
4933
- let beforeNo = 0;
4934
- let afterNo = 0;
4935
- for (const o of ops) {
4936
- if (o.op === 'equal') {
4937
- rows.push({ op: 'equal', text: o.a, beforeNo: ++beforeNo, afterNo: ++afterNo });
4938
- }
4939
- else if (o.op === 'delete') {
4940
- rows.push({ op: 'delete', text: o.a, beforeNo: ++beforeNo, afterNo: null });
4941
- }
4942
- else {
4943
- rows.push({ op: 'insert', text: o.b, beforeNo: null, afterNo: ++afterNo });
4944
- }
4945
- }
4946
- if (wordHighlight)
4947
- addWordHighlights(rows);
4948
- return rows;
4949
- }
4950
- /** Summary counts (added / removed / unchanged lines) for a diff. */
4951
- function mkDiffStats(rows) {
4952
- let added = 0;
4953
- let removed = 0;
4954
- let unchanged = 0;
4955
- for (const r of rows) {
4956
- if (r.op === 'insert')
4957
- added++;
4958
- else if (r.op === 'delete')
4959
- removed++;
4960
- else
4961
- unchanged++;
4962
- }
4963
- return { added, removed, unchanged };
4964
- }
4786
+ /** Delegates to `@mk-kit/core` — the exported names are unchanged. */
4965
4787
 
4966
4788
  /**
4967
4789
  * Diff — a text comparison view for revisions (before ⇄ after). Computes an LCS
@@ -5939,153 +5761,7 @@ function collectToDepth(root, maxDepth) {
5939
5761
  return paths;
5940
5762
  }
5941
5763
 
5942
- /**
5943
- * Minimal ANSI SGR parser for {@link MkLogViewer}.
5944
- *
5945
- * Understands the SGR (`ESC[…m`) subset that matters for log output — reset
5946
- * (0), bold (1), italic (3), underline (4), the 22/23/24 "off" codes, the 8
5947
- * standard + 8 bright foreground colours (30–37 / 90–97, 39 reset) and the
5948
- * matching backgrounds (40–47 / 100–107, 49 reset). Extended-colour
5949
- * introducers (38/48;5;… and 38/48;2;r;g;b) are consumed so their arguments
5950
- * are never misread as other codes, but map to "no colour" (the theme has no
5951
- * 256-colour palette). Every other escape sequence — cursor movement, erase,
5952
- * OSC titles, single-character escapes — is stripped rather than rendered as
5953
- * garbage.
5954
- *
5955
- * Colours are emitted as CSS class names (`mk-ansi--fg-red`,
5956
- * `mk-ansi--bg-bright-blue`, `mk-ansi--bold`, …) themed in
5957
- * `log-viewer.scss`, not as raw terminal colours.
5958
- */
5959
- /** SGR colour slots 0–7, shared by fg/bg and their bright variants. */
5960
- const COLOR_NAMES = [
5961
- 'black',
5962
- 'red',
5963
- 'green',
5964
- 'yellow',
5965
- 'blue',
5966
- 'magenta',
5967
- 'cyan',
5968
- 'white',
5969
- ];
5970
- /**
5971
- * Any ANSI escape sequence. Alternatives, in order: SGR (params captured),
5972
- * any other CSI sequence, OSC (terminated by BEL or ST), and two-character
5973
- * `ESC <letter>` escapes.
5974
- */
5975
- const ANSI_SOURCE = '\\x1b(?:\\[([0-9;]*)m|\\[[0-9;?]*[ -/]*[@-~]|\\][^\\x07\\x1b]*(?:\\x07|\\x1b\\\\)?|[@-Z\\\\-_])';
5976
- const EMPTY_CLASSES = [];
5977
- function applySgr(params, s) {
5978
- const codes = params.split(';').map((p) => (p === '' ? 0 : Number(p)));
5979
- for (let i = 0; i < codes.length; i++) {
5980
- const c = codes[i];
5981
- if (c === 0) {
5982
- s.bold = s.italic = s.underline = false;
5983
- s.fg = s.bg = null;
5984
- }
5985
- else if (c === 1)
5986
- s.bold = true;
5987
- else if (c === 3)
5988
- s.italic = true;
5989
- else if (c === 4)
5990
- s.underline = true;
5991
- else if (c === 22)
5992
- s.bold = false;
5993
- else if (c === 23)
5994
- s.italic = false;
5995
- else if (c === 24)
5996
- s.underline = false;
5997
- else if (c >= 30 && c <= 37)
5998
- s.fg = COLOR_NAMES[c - 30];
5999
- else if (c >= 90 && c <= 97)
6000
- s.fg = `bright-${COLOR_NAMES[c - 90]}`;
6001
- else if (c === 39)
6002
- s.fg = null;
6003
- else if (c >= 40 && c <= 47)
6004
- s.bg = COLOR_NAMES[c - 40];
6005
- else if (c >= 100 && c <= 107)
6006
- s.bg = `bright-${COLOR_NAMES[c - 100]}`;
6007
- else if (c === 49)
6008
- s.bg = null;
6009
- else if (c === 38 || c === 48) {
6010
- // Extended colour: consume the arguments, render as "no colour".
6011
- const mode = codes[i + 1];
6012
- if (mode === 5)
6013
- i += 2;
6014
- else if (mode === 2)
6015
- i += 4;
6016
- else
6017
- i = codes.length;
6018
- if (c === 38)
6019
- s.fg = null;
6020
- else
6021
- s.bg = null;
6022
- }
6023
- // Any other SGR parameter is ignored.
6024
- }
6025
- }
6026
- function classesFor(s) {
6027
- if (!s.bold && !s.italic && !s.underline && !s.fg && !s.bg) {
6028
- return EMPTY_CLASSES;
6029
- }
6030
- const classes = [];
6031
- if (s.bold)
6032
- classes.push('mk-ansi--bold');
6033
- if (s.italic)
6034
- classes.push('mk-ansi--italic');
6035
- if (s.underline)
6036
- classes.push('mk-ansi--underline');
6037
- if (s.fg)
6038
- classes.push(`mk-ansi--fg-${s.fg}`);
6039
- if (s.bg)
6040
- classes.push(`mk-ansi--bg-${s.bg}`);
6041
- return classes;
6042
- }
6043
- function sameClasses(a, b) {
6044
- return a.length === b.length && a.every((cls, i) => cls === b[i]);
6045
- }
6046
- /**
6047
- * Parse one log line into styled spans. Pure — same input, same output.
6048
- * Adjacent runs with identical styling are merged, so a plain line yields a
6049
- * single span (or none when empty).
6050
- */
6051
- function mkParseAnsi(line) {
6052
- const spans = [];
6053
- const state = {
6054
- bold: false,
6055
- italic: false,
6056
- underline: false,
6057
- fg: null,
6058
- bg: null,
6059
- };
6060
- const re = new RegExp(ANSI_SOURCE, 'g');
6061
- let last = 0;
6062
- const flush = (end) => {
6063
- if (end <= last)
6064
- return;
6065
- const text = line.slice(last, end);
6066
- const classes = classesFor(state);
6067
- const prev = spans[spans.length - 1];
6068
- if (prev && sameClasses(prev.classes, classes)) {
6069
- spans[spans.length - 1] = { text: prev.text + text, classes: prev.classes };
6070
- }
6071
- else {
6072
- spans.push({ text, classes });
6073
- }
6074
- };
6075
- let m;
6076
- while ((m = re.exec(line))) {
6077
- flush(m.index);
6078
- last = m.index + m[0].length;
6079
- if (m[1] !== undefined)
6080
- applySgr(m[1], state);
6081
- }
6082
- flush(line.length);
6083
- return spans;
6084
- }
6085
- /** Remove every ANSI escape sequence from a line (for copy / search). */
6086
- function mkStripAnsi(line) {
6087
- return line.replace(new RegExp(ANSI_SOURCE, 'g'), '');
6088
- }
5764
+ /** Delegates to `@mk-kit/core` — the exported names are unchanged. */
6089
5765
 
6090
5766
  /** Rows rendered above/below the viewport to smooth fast scrolls. */
6091
5767
  const OVERSCAN = 6;
@@ -6359,519 +6035,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.7", ngImpor
6359
6035
  args: [{ selector: 'mk-log-viewer', exportAs: 'mkLogViewer', changeDetection: ChangeDetectionStrategy.OnPush, imports: [MkCopyToClipboard], host: { class: 'mk-log-viewer' }, template: "@if (!hideToolbar()) {\n <div class=\"mk-log-viewer__toolbar\">\n <ng-content />\n <span class=\"mk-log-viewer__toolbar-spacer\"></span>\n <button\n type=\"button\"\n class=\"mk-log-viewer__tool\"\n [class.mk-log-viewer__tool--active]=\"wrap()\"\n [attr.aria-pressed]=\"wrap()\"\n (click)=\"wrap.set(!wrap())\"\n >\n {{ i18n.logWrapLines }}\n </button>\n <button\n type=\"button\"\n class=\"mk-log-viewer__tool\"\n [mkCopyToClipboard]=\"allText()\"\n #copier=\"mkCopyToClipboard\"\n >\n {{ copier.justCopied() ? '\u2713 ' + i18n.logCopyAll : i18n.logCopyAll }}\n </button>\n </div>\n}\n\n<div class=\"mk-log-viewer__body\">\n <!--\n role=\"log\" makes additions politely announced without re-reading the whole\n buffer; tabindex=\"0\" keeps the overflowing region keyboard-scrollable.\n Tokens are emitted on one line: the rows render `white-space: pre`, so any\n inter-element template whitespace would corrupt log alignment.\n -->\n <div\n #viewport\n class=\"mk-log-viewer__viewport\"\n role=\"log\"\n tabindex=\"0\"\n [attr.aria-label]=\"ariaLabel()\"\n (scroll)=\"onScroll($event)\"\n >\n @if (wrap()) {\n <div class=\"mk-log-viewer__lines mk-log-viewer__lines--wrap\">\n @for (line of wrappedLines(); track line.index) {\n <div class=\"mk-log-viewer__line\">@for (t of line.tokens; track $index) {@if (t.mark) {<mark class=\"mk-log-viewer__match {{ t.cls }}\">{{ t.text }}</mark>} @else {<span class=\"{{ t.cls }}\">{{ t.text }}</span>}}</div>\n }\n </div>\n } @else {\n <div class=\"mk-log-viewer__spacer\" [style.height.px]=\"totalHeight()\">\n <div\n class=\"mk-log-viewer__window\"\n [style.transform]=\"'translateY(' + offset() + 'px)'\"\n >\n @for (line of visibleLines(); track line.index) {\n <div\n class=\"mk-log-viewer__line\"\n [style.height.px]=\"itemHeight()\"\n [style.line-height.px]=\"itemHeight()\"\n >@for (t of line.tokens; track $index) {@if (t.mark) {<mark class=\"mk-log-viewer__match {{ t.cls }}\">{{ t.text }}</mark>} @else {<span class=\"{{ t.cls }}\">{{ t.text }}</span>}}</div>\n }\n </div>\n </div>\n }\n </div>\n\n @if (!follow()) {\n <button\n type=\"button\"\n class=\"mk-log-viewer__follow\"\n (click)=\"resumeFollow()\"\n >\n {{ i18n.logFollow }} <span aria-hidden=\"true\">\u2193</span>\n </button>\n }\n</div>\n\n@if (query()) {\n <span class=\"mk-visually-hidden\" aria-live=\"polite\">\n {{ i18n.resultsCount(matchCount()) }}\n </span>\n}\n", styles: ["@charset \"UTF-8\";:host{--_fs: var(--mk-font-size-sm);display:flex;flex-direction:column;height:var(--mk-log-viewer-height, 20rem);overflow:hidden;background-color:var(--mk-log-viewer-bg, var(--mk-code-bg, var(--mk-surface-2)));border:var(--mk-border-width) solid var(--mk-border);border-radius:var(--mk-radius-md);font-family:var(--mk-font-mono)}.mk-log-viewer__toolbar{display:flex;align-items:center;gap:var(--mk-space-2);flex:none;padding:var(--mk-space-1) var(--mk-space-2);background-color:var(--mk-surface-2);border-bottom:var(--mk-border-width) solid var(--mk-border-subtle);font-family:var(--mk-font-sans)}.mk-log-viewer__toolbar-spacer{flex:1 1 auto}.mk-log-viewer__tool{flex:none;padding:var(--mk-space-1) var(--mk-space-2);font-family:var(--mk-font-sans);font-size:var(--mk-font-size-xs);color:var(--mk-text-muted);background:transparent;border:var(--mk-border-width) solid var(--mk-border);border-radius:var(--mk-radius-sm);cursor:pointer;transition:background-color var(--mk-duration-fast) var(--mk-ease-standard),color var(--mk-duration-fast) var(--mk-ease-standard)}.mk-log-viewer__tool:hover{background-color:var(--mk-hover-overlay);color:var(--mk-text)}.mk-log-viewer__tool:focus-visible{outline:var(--mk-focus-ring-width) solid var(--mk-focus-ring);outline-offset:var(--mk-focus-ring-offset)}.mk-log-viewer__tool--active{color:var(--mk-primary);border-color:var(--mk-primary)}.mk-log-viewer__body{position:relative;flex:1 1 auto;min-height:0}.mk-log-viewer__viewport{height:100%;overflow:auto;direction:ltr;padding:var(--mk-space-2) var(--mk-space-3);font-size:var(--_fs);color:var(--mk-text)}.mk-log-viewer__viewport:focus-visible{outline:var(--mk-focus-ring-width) solid var(--mk-focus-ring);outline-offset:calc(-1 * var(--mk-focus-ring-width))}.mk-log-viewer__spacer{position:relative}.mk-log-viewer__window{position:absolute;top:0;left:0;min-width:100%;width:max-content;will-change:transform}.mk-log-viewer__line{white-space:pre}.mk-log-viewer__lines--wrap .mk-log-viewer__line{white-space:pre-wrap;overflow-wrap:break-word;word-break:break-word;line-height:1.5}.mk-log-viewer__match{background-color:var(--mk-warning);color:var(--mk-warning-contrast);border-radius:2px}.mk-log-viewer__follow{position:absolute;bottom:var(--mk-space-3);left:50%;transform:translate(-50%);padding:var(--mk-space-1) var(--mk-space-3);font-family:var(--mk-font-sans);font-size:var(--mk-font-size-xs);font-weight:var(--mk-font-weight-medium);color:var(--mk-primary-contrast);background-color:var(--mk-primary);border:none;border-radius:var(--mk-radius-pill);box-shadow:var(--mk-shadow-md);cursor:pointer}.mk-log-viewer__follow:hover{background-color:var(--mk-primary-hover)}.mk-log-viewer__follow:focus-visible{outline:var(--mk-focus-ring-width) solid var(--mk-focus-ring);outline-offset:var(--mk-focus-ring-offset)}.mk-ansi--bold{font-weight:var(--mk-font-weight-bold)}.mk-ansi--italic{font-style:italic}.mk-ansi--underline{text-decoration:underline}.mk-ansi--fg-black,.mk-ansi--fg-bright-black{color:var(--mk-text-muted)}.mk-ansi--fg-red,.mk-ansi--fg-bright-red{color:var(--mk-danger-text)}.mk-ansi--fg-green,.mk-ansi--fg-bright-green{color:var(--mk-success)}.mk-ansi--fg-yellow,.mk-ansi--fg-bright-yellow{color:var(--mk-warning)}.mk-ansi--fg-blue,.mk-ansi--fg-bright-blue{color:var(--mk-info)}.mk-ansi--fg-magenta,.mk-ansi--fg-bright-magenta{color:var(--mk-chart-4)}.mk-ansi--fg-cyan,.mk-ansi--fg-bright-cyan{color:var(--mk-chart-6)}.mk-ansi--fg-white,.mk-ansi--fg-bright-white{color:var(--mk-text)}.mk-ansi--bg-black,.mk-ansi--bg-bright-black{background-color:var(--mk-surface-3)}.mk-ansi--bg-red,.mk-ansi--bg-bright-red{background-color:var(--mk-danger-subtle)}.mk-ansi--bg-green,.mk-ansi--bg-bright-green{background-color:var(--mk-success-subtle)}.mk-ansi--bg-yellow,.mk-ansi--bg-bright-yellow{background-color:var(--mk-warning-subtle)}.mk-ansi--bg-blue,.mk-ansi--bg-bright-blue{background-color:var(--mk-info-subtle)}.mk-ansi--bg-magenta,.mk-ansi--bg-bright-magenta{background-color:color-mix(in srgb,var(--mk-chart-4) 18%,transparent)}.mk-ansi--bg-cyan,.mk-ansi--bg-bright-cyan{background-color:color-mix(in srgb,var(--mk-chart-6) 18%,transparent)}.mk-ansi--bg-white,.mk-ansi--bg-bright-white{background-color:var(--mk-surface)}@media(prefers-reduced-motion:reduce){.mk-log-viewer__tool{transition:none}}\n"] }]
6360
6036
  }], ctorParameters: () => [], propDecorators: { viewportRef: [{ type: i0.ViewChild, args: ['viewport', { isSignal: true }] }], lines: [{ type: i0.Input, args: [{ isSignal: true, alias: "lines", required: false }] }], follow: [{ type: i0.Input, args: [{ isSignal: true, alias: "follow", required: false }] }, { type: i0.Output, args: ["followChange"] }], wrap: [{ type: i0.Input, args: [{ isSignal: true, alias: "wrap", required: false }] }, { type: i0.Output, args: ["wrapChange"] }], ansi: [{ type: i0.Input, args: [{ isSignal: true, alias: "ansi", required: false }] }], query: [{ type: i0.Input, args: [{ isSignal: true, alias: "query", required: false }] }], maxLines: [{ type: i0.Input, args: [{ isSignal: true, alias: "maxLines", required: false }] }], itemHeight: [{ type: i0.Input, args: [{ isSignal: true, alias: "itemHeight", required: false }] }], hideToolbar: [{ type: i0.Input, args: [{ isSignal: true, alias: "hideToolbar", required: false }] }], ariaLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "aria-label", required: false }] }], matches: [{ type: i0.Output, args: ["matches"] }] } });
6361
6037
 
6362
- /**
6363
- * Dependency-free markdown parsing and rendering for `<mk-markdown>` — a small
6364
- * hand-written CommonMark subset aimed at admin-app content (changelogs, AI
6365
- * output, user notes). {@link mkParseMarkdown} produces a typed AST and
6366
- * {@link mkRenderMarkdown} serialises it to an HTML string that is safe by
6367
- * construction: every character of source text is HTML-escaped and the only
6368
- * tags in the output are the ones the renderer itself generates.
6369
- *
6370
- * SUPPORTED SYNTAX
6371
- * - ATX headings `#` … `######` (optional closing hashes are stripped)
6372
- * - Paragraphs; hard line breaks via two trailing spaces
6373
- * - Emphasis: bold `**x**`, italic `*x*` / `_x_` (word-bounded for `_`),
6374
- * strikethrough `~~x~~`, and `***x***` for bold-italic
6375
- * - Inline code `` `x` `` (multi-backtick fences for literal backticks)
6376
- * - Links `[text](url)` — http/https/mailto/relative URLs only; anything else
6377
- * (`javascript:` etc.) has its href dropped and renders as plain text
6378
- * - Images `![alt](src)` — same URL policy; unsafe sources render as alt text
6379
- * - Fenced code blocks ``` with an optional language (highlighted through
6380
- * `mkHighlight` for its known languages, plain escaped code otherwise)
6381
- * - Tight unordered (`-`, `*`, `+`) and ordered (`1.`, `1)`) lists, nested by
6382
- * 2-space indent; a blank line ends the list (loose lists unsupported)
6383
- * - Blockquotes `>` (nestable; no lazy continuation)
6384
- * - Horizontal rules (`---`, `***`, `___`)
6385
- * - GitHub pipe tables with `:---:` alignment colons and `\|` cell escapes
6386
- * - Backslash escapes for ASCII punctuation (`\*`, `\|`, …)
6387
- * - Bare `http(s)://` URLs autolink only when the `autolink` option is on
6388
- *
6389
- * NOT SUPPORTED (by design)
6390
- * - Raw inline HTML — always escaped and shown as text. This is the whole
6391
- * sanitisation story: with no HTML passthrough there is no XSS surface.
6392
- * - Footnotes, setext headings (`===` / `---` underlines — a `---` line is
6393
- * always a horizontal rule here), reference-style links, loose lists.
6394
- */
6395
- /* ------------------------------------------------------------------ *
6396
- * Escaping & URL safety
6397
- * ------------------------------------------------------------------ */
6398
- /** Escape text for safe placement in HTML content or a quoted attribute. */
6399
- function escapeHtml(src) {
6400
- return src
6401
- .replace(/&/g, '&amp;')
6402
- .replace(/</g, '&lt;')
6403
- .replace(/>/g, '&gt;')
6404
- .replace(/"/g, '&quot;');
6405
- }
6406
- /**
6407
- * True for URLs safe in `href`/`src`: http(s), mailto and relative paths /
6408
- * anchors (same policy as rich-text's sanitiser, minus tel/data). Any other
6409
- * scheme — `javascript:`, `data:`, `vbscript:`, … — is rejected.
6410
- */
6411
- function isSafeUrl(url) {
6412
- const trimmed = url.trim();
6413
- if (trimmed === '')
6414
- return false;
6415
- if (/^(https?:|mailto:)/i.test(trimmed))
6416
- return true;
6417
- if (/^[a-z][a-z0-9+.-]*:/i.test(trimmed))
6418
- return false; // any other scheme
6419
- return true; // relative path, anchor, query
6420
- }
6421
- /* ------------------------------------------------------------------ *
6422
- * Inline parsing
6423
- * ------------------------------------------------------------------ */
6424
- /** Backslash escapes: any ASCII punctuation character. */
6425
- const ESCAPE_RE = /^\\([!"#$%&'()*+,\-./:;<=>?@[\\\]^_`{|}~])/;
6426
- /** Inline code span: N backticks, content, N backticks (not followed by `). */
6427
- const CODE_SPAN_RE = /^(`+)([\s\S]+?)\1(?!`)/;
6428
- /** URL part of a link/image: bare of whitespace, one balanced-paren level. */
6429
- const URL_PART = String.raw `((?:[^()\s]|\([^()\s]*\))*)`;
6430
- /** Image: `![alt](src "ignored title")`. */
6431
- const IMAGE_RE = new RegExp(String.raw `^!\[([^\]]*)\]\(\s*${URL_PART}(?:\s+[^)]*)?\s*\)`);
6432
- /** Link: `[label](url "ignored title")` — label may hold one `[…]` level. */
6433
- const LINK_RE = new RegExp(String.raw `^\[((?:[^[\]\\]|\\[\s\S]|\[[^\]]*\])*)\]\(\s*${URL_PART}(?:\s+[^)]*)?\s*\)`);
6434
- const STRONG_EM_RE = /^\*\*\*(?!\s)([\s\S]+?(?<=\S))\*\*\*(?!\*)/;
6435
- const STRONG_RE = /^\*\*(?!\s)([\s\S]+?(?<=\S))\*\*(?!\*)/;
6436
- const EM_STAR_RE = /^\*(?![\s*])([\s\S]+?(?<=[^\s*]))\*(?!\*)/;
6437
- const EM_UNDERSCORE_RE = /^_(?!\s)([\s\S]+?(?<=\S))_(?![0-9A-Za-z_])/;
6438
- const DEL_RE = /^~~(?!\s)([\s\S]+?(?<=\S))~~/;
6439
- const AUTOLINK_RE = /^https?:\/\/[^\s<>]+/i;
6440
- /** Parse a run of inline markdown into AST nodes. */
6441
- function parseInlines(src, autolink) {
6442
- const out = [];
6443
- let text = '';
6444
- const flush = () => {
6445
- if (text !== '') {
6446
- out.push({ kind: 'text', text });
6447
- text = '';
6448
- }
6449
- };
6450
- let i = 0;
6451
- while (i < src.length) {
6452
- const rest = src.slice(i);
6453
- const ch = rest[0];
6454
- let m;
6455
- // Line breaks (paragraph lines are joined with '\n'; two or more trailing
6456
- // spaces before the newline mark a hard break).
6457
- if ((m = rest.match(/^[ \t]*\n/))) {
6458
- flush();
6459
- out.push(/ {2,}\n$/.test(m[0]) ? { kind: 'break' } : { kind: 'softbreak' });
6460
- i += m[0].length;
6461
- continue;
6462
- }
6463
- if (ch === '\\' && (m = rest.match(ESCAPE_RE))) {
6464
- text += m[1];
6465
- i += m[0].length;
6466
- continue;
6467
- }
6468
- if (ch === '`' && (m = rest.match(CODE_SPAN_RE))) {
6469
- let code = m[2].replace(/\n/g, ' ');
6470
- // CommonMark: strip one space from both ends when both exist
6471
- // (enables `` ` `` for a literal backtick).
6472
- if (code.length > 2 && code.startsWith(' ') && code.endsWith(' ') && code.trim() !== '') {
6473
- code = code.slice(1, -1);
6474
- }
6475
- flush();
6476
- out.push({ kind: 'code', code });
6477
- i += m[0].length;
6478
- continue;
6479
- }
6480
- if (ch === '!' && (m = rest.match(IMAGE_RE))) {
6481
- flush();
6482
- if (isSafeUrl(m[2])) {
6483
- out.push({ kind: 'image', src: m[2].trim(), alt: m[1] });
6484
- }
6485
- else if (m[1] !== '') {
6486
- out.push({ kind: 'text', text: m[1] }); // unsafe source → alt text
6487
- }
6488
- i += m[0].length;
6489
- continue;
6490
- }
6491
- if (ch === '[' && (m = rest.match(LINK_RE))) {
6492
- flush();
6493
- const children = parseInlines(m[1], autolink);
6494
- if (isSafeUrl(m[2])) {
6495
- out.push({ kind: 'link', href: m[2].trim(), children });
6496
- }
6497
- else {
6498
- out.push(...children); // unsafe URL → href dropped, text kept
6499
- }
6500
- i += m[0].length;
6501
- continue;
6502
- }
6503
- if (ch === '*') {
6504
- if ((m = rest.match(STRONG_EM_RE))) {
6505
- flush();
6506
- out.push({
6507
- kind: 'strong',
6508
- children: [{ kind: 'em', children: parseInlines(m[1], autolink) }],
6509
- });
6510
- i += m[0].length;
6511
- continue;
6512
- }
6513
- if ((m = rest.match(STRONG_RE))) {
6514
- flush();
6515
- out.push({ kind: 'strong', children: parseInlines(m[1], autolink) });
6516
- i += m[0].length;
6517
- continue;
6518
- }
6519
- if ((m = rest.match(EM_STAR_RE))) {
6520
- flush();
6521
- out.push({ kind: 'em', children: parseInlines(m[1], autolink) });
6522
- i += m[0].length;
6523
- continue;
6524
- }
6525
- }
6526
- // `_` emphasis only at a word boundary (no intra-word `snake_case`).
6527
- if (ch === '_' && (i === 0 || !/[0-9A-Za-z_]/.test(src[i - 1])) && (m = rest.match(EM_UNDERSCORE_RE))) {
6528
- flush();
6529
- out.push({ kind: 'em', children: parseInlines(m[1], autolink) });
6530
- i += m[0].length;
6531
- continue;
6532
- }
6533
- if (ch === '~' && (m = rest.match(DEL_RE))) {
6534
- flush();
6535
- out.push({ kind: 'del', children: parseInlines(m[1], autolink) });
6536
- i += m[0].length;
6537
- continue;
6538
- }
6539
- if (autolink && (ch === 'h' || ch === 'H') && (i === 0 || /[\s([]/.test(src[i - 1])) && (m = rest.match(AUTOLINK_RE))) {
6540
- // Trim trailing punctuation that usually belongs to the sentence.
6541
- const url = m[0].replace(/[.,;:!?'")\]]+$/, '');
6542
- if (url.length > 'https://'.length) {
6543
- flush();
6544
- out.push({ kind: 'link', href: url, children: [{ kind: 'text', text: url }] });
6545
- i += url.length;
6546
- continue;
6547
- }
6548
- }
6549
- text += ch;
6550
- i += 1;
6551
- }
6552
- flush();
6553
- return out;
6554
- }
6555
- /* ------------------------------------------------------------------ *
6556
- * Block parsing
6557
- * ------------------------------------------------------------------ */
6558
- const FENCE_OPEN_RE = /^ {0,3}(`{3,})[ \t]*(\S*)[^`]*$/;
6559
- const HEADING_RE = /^ {0,3}(#{1,6})(?:[ \t]+(.*))?$/;
6560
- const HR_RE = /^ {0,3}([-*_])(?:[ \t]*\1){2,}[ \t]*$/;
6561
- const QUOTE_RE = /^ {0,3}>/;
6562
- const BULLET_ITEM_RE = /^([ \t]*)([-*+])[ \t]+(.*)$/;
6563
- const ORDERED_ITEM_RE = /^([ \t]*)(\d{1,9})[.)][ \t]+(.*)$/;
6564
- const TABLE_DELIM_RE = /^\s*\|?\s*:?-+:?\s*(?:\|\s*:?-+:?\s*)*\|?\s*$/;
6565
- function isBlank(line) {
6566
- return line.trim() === '';
6567
- }
6568
- /** True when `line` starts a construct that interrupts a paragraph. */
6569
- function interruptsParagraph(line, next) {
6570
- return (isBlank(line) ||
6571
- FENCE_OPEN_RE.test(line) ||
6572
- HEADING_RE.test(line) ||
6573
- HR_RE.test(line) ||
6574
- QUOTE_RE.test(line) ||
6575
- BULLET_ITEM_RE.test(line) ||
6576
- ORDERED_ITEM_RE.test(line) ||
6577
- isTableStart(line, next));
6578
- }
6579
- /** True when `line` + `next` open a pipe table (header row + delimiter row). */
6580
- function isTableStart(line, next) {
6581
- if (next === undefined || !line.includes('|') || !TABLE_DELIM_RE.test(next))
6582
- return false;
6583
- if (!next.includes('|'))
6584
- return false; // require at least one pipe in the delimiter
6585
- return splitTableRow(line).length === splitTableRow(next).length;
6586
- }
6587
- /** Split a pipe-table row into raw cell strings, honouring `\|` escapes. */
6588
- function splitTableRow(line) {
6589
- let row = line.trim();
6590
- if (row.startsWith('|'))
6591
- row = row.slice(1);
6592
- // Strip a trailing unescaped pipe.
6593
- if (/(?<!\\)\|$/.test(row))
6594
- row = row.slice(0, -1);
6595
- const cells = [];
6596
- let cell = '';
6597
- for (let i = 0; i < row.length; i++) {
6598
- const ch = row[i];
6599
- if (ch === '\\' && i + 1 < row.length) {
6600
- cell += ch + row[i + 1];
6601
- i++;
6602
- }
6603
- else if (ch === '|') {
6604
- cells.push(cell.trim());
6605
- cell = '';
6606
- }
6607
- else {
6608
- cell += ch;
6609
- }
6610
- }
6611
- cells.push(cell.trim());
6612
- return cells;
6613
- }
6614
- function parseAlign(cell) {
6615
- const left = cell.startsWith(':');
6616
- const right = cell.endsWith(':');
6617
- if (left && right)
6618
- return 'center';
6619
- if (right)
6620
- return 'right';
6621
- if (left)
6622
- return 'left';
6623
- return null;
6624
- }
6625
- /** Parse consecutive list lines starting at `lines[start]` into a (nested) list. */
6626
- function parseList(lines, start, autolink) {
6627
- const stack = [];
6628
- let i = start;
6629
- while (i < lines.length) {
6630
- const m = lines[i].match(BULLET_ITEM_RE) ?? lines[i].match(ORDERED_ITEM_RE);
6631
- if (!m)
6632
- break;
6633
- const ordered = /\d/.test(m[2]);
6634
- const indent = m[1].replace(/\t/g, ' ').length;
6635
- let depth = Math.min(Math.floor(indent / 2), stack.length);
6636
- if (stack.length === 0)
6637
- depth = 0;
6638
- if (depth === stack.length && depth > 0) {
6639
- // One level deeper: open a sublist on the previous item.
6640
- const parent = stack[depth - 1];
6641
- const lastItem = parent.items[parent.items.length - 1];
6642
- const sublist = {
6643
- kind: 'list',
6644
- ordered,
6645
- start: ordered ? Number(m[2]) : 1,
6646
- items: [],
6647
- };
6648
- lastItem.sublist = sublist;
6649
- stack.push(sublist);
6650
- }
6651
- else {
6652
- while (stack.length - 1 > depth)
6653
- stack.pop();
6654
- if (stack.length === 0) {
6655
- stack.push({ kind: 'list', ordered, start: ordered ? Number(m[2]) : 1, items: [] });
6656
- }
6657
- }
6658
- stack[stack.length - 1].items.push({
6659
- children: parseInlines(m[3].replace(/[ \t]+$/, ''), autolink),
6660
- sublist: null,
6661
- });
6662
- i++;
6663
- }
6664
- return { list: stack[0], next: i };
6665
- }
6666
- /** Parse an array of source lines into blocks (recursive for blockquotes). */
6667
- function parseBlockLines(lines, autolink) {
6668
- const blocks = [];
6669
- let i = 0;
6670
- while (i < lines.length) {
6671
- const line = lines[i];
6672
- if (isBlank(line)) {
6673
- i++;
6674
- continue;
6675
- }
6676
- // Fenced code block.
6677
- let m = line.match(FENCE_OPEN_RE);
6678
- if (m) {
6679
- const fence = m[1];
6680
- const language = (m[2].match(/^[A-Za-z0-9#+.-]*/)?.[0] ?? '').toLowerCase();
6681
- const body = [];
6682
- i++;
6683
- while (i < lines.length) {
6684
- const closing = lines[i].match(/^ {0,3}(`{3,})[ \t]*$/);
6685
- if (closing && closing[1].length >= fence.length) {
6686
- i++;
6687
- break;
6688
- }
6689
- body.push(lines[i]);
6690
- i++;
6691
- }
6692
- blocks.push({ kind: 'code', language, code: body.join('\n') });
6693
- continue;
6694
- }
6695
- // Horizontal rule (before list: `- - -` is an hr, `- x` a list).
6696
- if (HR_RE.test(line)) {
6697
- blocks.push({ kind: 'hr' });
6698
- i++;
6699
- continue;
6700
- }
6701
- // ATX heading.
6702
- m = line.match(HEADING_RE);
6703
- if (m) {
6704
- const content = (m[2] ?? '').replace(/[ \t]+#+[ \t]*$/, '').trim();
6705
- blocks.push({
6706
- kind: 'heading',
6707
- level: m[1].length,
6708
- children: parseInlines(content, autolink),
6709
- });
6710
- i++;
6711
- continue;
6712
- }
6713
- // Blockquote: consecutive `>` lines, one marker level stripped, recursed.
6714
- if (QUOTE_RE.test(line)) {
6715
- const inner = [];
6716
- while (i < lines.length && QUOTE_RE.test(lines[i])) {
6717
- inner.push(lines[i].replace(/^ {0,3}> ?/, ''));
6718
- i++;
6719
- }
6720
- blocks.push({ kind: 'blockquote', children: parseBlockLines(inner, autolink) });
6721
- continue;
6722
- }
6723
- // List (tight; ends at the first non-item line).
6724
- if (BULLET_ITEM_RE.test(line) || ORDERED_ITEM_RE.test(line)) {
6725
- const { list, next } = parseList(lines, i, autolink);
6726
- blocks.push(list);
6727
- i = next;
6728
- continue;
6729
- }
6730
- // Pipe table.
6731
- if (isTableStart(line, lines[i + 1])) {
6732
- const header = splitTableRow(line).map((c) => parseInlines(c, autolink));
6733
- const align = splitTableRow(lines[i + 1]).map(parseAlign);
6734
- const rows = [];
6735
- i += 2;
6736
- while (i < lines.length && !isBlank(lines[i]) && lines[i].includes('|')) {
6737
- const cells = splitTableRow(lines[i]);
6738
- rows.push(header.map((_, col) => parseInlines(cells[col] ?? '', autolink)));
6739
- i++;
6740
- }
6741
- blocks.push({ kind: 'table', align, header, rows });
6742
- continue;
6743
- }
6744
- // Paragraph: gather until a blank line or another block construct.
6745
- const para = [line];
6746
- i++;
6747
- while (i < lines.length && !interruptsParagraph(lines[i], lines[i + 1])) {
6748
- para.push(lines[i]);
6749
- i++;
6750
- }
6751
- const content = para.map((l) => l.replace(/^[ \t]+/, '')).join('\n').replace(/[ \t]+$/, '');
6752
- blocks.push({ kind: 'paragraph', children: parseInlines(content, autolink) });
6753
- }
6754
- return blocks;
6755
- }
6756
- /**
6757
- * Parse markdown source into a typed AST. Pure and dependency-free — safe to
6758
- * run on a server. Unsafe link/image URLs are already dropped at this stage,
6759
- * so the AST never carries a `javascript:` (or other non-allow-listed) URL.
6760
- */
6761
- function mkParseMarkdown(src, options = {}) {
6762
- const lines = String(src ?? '').replace(/\r\n?/g, '\n').split('\n');
6763
- return parseBlockLines(lines, options.autolink ?? false);
6764
- }
6765
- /* ------------------------------------------------------------------ *
6766
- * Rendering
6767
- * ------------------------------------------------------------------ */
6768
- const ALIGN_CLASS = {
6769
- left: '', // left is the document default — no class needed
6770
- center: ' class="mk-markdown--center"',
6771
- right: ' class="mk-markdown--right"',
6772
- };
6773
- function renderInlines(nodes, options) {
6774
- let out = '';
6775
- for (const node of nodes) {
6776
- switch (node.kind) {
6777
- case 'text':
6778
- out += escapeHtml(node.text);
6779
- break;
6780
- case 'softbreak':
6781
- out += '\n';
6782
- break;
6783
- case 'break':
6784
- out += '<br>';
6785
- break;
6786
- case 'code':
6787
- out += `<code>${escapeHtml(node.code)}</code>`;
6788
- break;
6789
- case 'strong':
6790
- out += `<strong>${renderInlines(node.children, options)}</strong>`;
6791
- break;
6792
- case 'em':
6793
- out += `<em>${renderInlines(node.children, options)}</em>`;
6794
- break;
6795
- case 'del':
6796
- out += `<del>${renderInlines(node.children, options)}</del>`;
6797
- break;
6798
- case 'link': {
6799
- const target = options.linkTarget === '_blank'
6800
- ? ' target="_blank" rel="noopener noreferrer"'
6801
- : '';
6802
- out += `<a href="${escapeHtml(node.href)}"${target}>${renderInlines(node.children, options)}</a>`;
6803
- break;
6804
- }
6805
- case 'image':
6806
- out += `<img src="${escapeHtml(node.src)}" alt="${escapeHtml(node.alt)}">`;
6807
- break;
6808
- }
6809
- }
6810
- return out;
6811
- }
6812
- function renderList(list, options) {
6813
- const tag = list.ordered ? 'ol' : 'ul';
6814
- const start = list.ordered && list.start !== 1 ? ` start="${list.start}"` : '';
6815
- const items = list.items
6816
- .map((item) => {
6817
- const sub = item.sublist ? renderList(item.sublist, options) : '';
6818
- return `<li>${renderInlines(item.children, options)}${sub}</li>`;
6819
- })
6820
- .join('');
6821
- return `<${tag}${start}>${items}</${tag}>`;
6822
- }
6823
- /**
6824
- * Render a parsed AST to an HTML string. The output contains only tags this
6825
- * renderer generates (all source text is escaped), so it is safe to bind to
6826
- * `[innerHTML]` — Angular's sanitizer keeps every element/attribute used here.
6827
- * Fenced code is highlighted via `mkHighlight` for its known languages.
6828
- */
6829
- function mkRenderMarkdown(blocks, options = {}) {
6830
- let out = '';
6831
- for (const block of blocks) {
6832
- switch (block.kind) {
6833
- case 'heading':
6834
- out += `<h${block.level}>${renderInlines(block.children, options)}</h${block.level}>`;
6835
- break;
6836
- case 'paragraph':
6837
- out += `<p>${renderInlines(block.children, options)}</p>`;
6838
- break;
6839
- case 'code': {
6840
- const lang = block.language
6841
- ? ` class="language-${escapeHtml(block.language)}"`
6842
- : '';
6843
- out += `<pre class="mk-markdown__code"><code${lang}>${mkHighlight(block.code, block.language)}</code></pre>`;
6844
- break;
6845
- }
6846
- case 'blockquote':
6847
- out += `<blockquote>${mkRenderMarkdown(block.children, options)}</blockquote>`;
6848
- break;
6849
- case 'hr':
6850
- out += '<hr>';
6851
- break;
6852
- case 'list':
6853
- out += renderList(block, options);
6854
- break;
6855
- case 'table': {
6856
- const alignAttr = (col) => {
6857
- const align = block.align[col];
6858
- return align ? ALIGN_CLASS[align] : '';
6859
- };
6860
- const head = block.header
6861
- .map((cell, c) => `<th${alignAttr(c)}>${renderInlines(cell, options)}</th>`)
6862
- .join('');
6863
- const body = block.rows
6864
- .map((row) => `<tr>${row
6865
- .map((cell, c) => `<td${alignAttr(c)}>${renderInlines(cell, options)}</td>`)
6866
- .join('')}</tr>`)
6867
- .join('');
6868
- out += `<table><thead><tr>${head}</tr></thead>${body ? `<tbody>${body}</tbody>` : ''}</table>`;
6869
- break;
6870
- }
6871
- }
6872
- }
6873
- return out;
6874
- }
6038
+ /** Delegates to `@mk-kit/core` — the exported names are unchanged. */
6875
6039
 
6876
6040
  /**
6877
6041
  * Markdown — renders a CommonMark subset (see {@link mkParseMarkdown} for the
@@ -6922,5 +6086,5 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.7", ngImpor
6922
6086
  * Generated bundle index. Do not edit.
6923
6087
  */
6924
6088
 
6925
- export { MK_CHART_PALETTE_SIZE, MK_HEATMAP_MIN_CONTRAST, MkAvatar, MkAvatarGroup, MkBadge, MkBadgeOverlay, MkBarChart, MkCalendarHeatmap, MkCard, MkCardFooter, MkCardHeader, MkCardTitle, MkCarousel, MkCarouselSlide, MkCode, MkCountdown, MkDescItem, MkDescriptionList, MkDiff, MkDivider, MkDonutChart, MkEmptyState, MkFunnelChart, MkGauge, MkHeatmap, MkInlineEdit, MkJsonViewer, MkKanban, MkLineChart, MkList, MkListItem, MkLogViewer, MkMarkdown, MkOrgChart, MkOrgChartNodeDef, MkProfileActions, MkProfileCard, MkProfileMeta, MkProgressBar, MkProgressRing, MkQrCode, MkRadarChart, MkScatterChart, MkSkeleton, MkSkeletonPreset, MkSparkline, MkSpinner, MkStatCard, MkTag, MkTimeline, MkTimelineItem, MkTreemap, MkVirtualScroll, mkArcPath, mkAreaPath, mkBuildJsonTree, mkChartColor, mkComputeDiff, mkContrastRatio, mkDiffStats, mkEncodeQr, mkFormatCompact, mkHeatmapCellColors, mkLinePath, mkLinearScale, mkMixRgb, mkNiceTicks, mkOrgChartFromFlat, mkParseAnsi, mkParseMarkdown, mkParseRgb, mkRelativeLuminance, mkRenderMarkdown, mkSplitDuration, mkSquarify, mkStripAnsi };
6089
+ export { MK_CHART_PALETTE_SIZE, MK_HEATMAP_MIN_CONTRAST, MkAvatar, MkAvatarGroup, MkBadge, MkBadgeOverlay, MkBarChart, MkCalendarHeatmap, MkCard, MkCardFooter, MkCardHeader, MkCardTitle, MkCarousel, MkCarouselSlide, MkCode, MkCountdown, MkDescItem, MkDescriptionList, MkDiff, MkDivider, MkDonutChart, MkEmptyState, MkFunnelChart, MkGauge, MkHeatmap, MkInlineEdit, MkJsonViewer, MkKanban, MkLineChart, MkList, MkListItem, MkLogViewer, MkMarkdown, MkOrgChart, MkOrgChartNodeDef, MkProfileActions, MkProfileCard, MkProfileMeta, MkProgressBar, MkProgressRing, MkQrCode, MkRadarChart, MkScatterChart, MkSkeleton, MkSkeletonPreset, MkSparkline, MkSpinner, MkStatCard, MkTag, MkTimeline, MkTimelineItem, MkTreemap, MkVirtualScroll, mkArcPath, mkAreaPath, mkBuildJsonTree, mkChartColor, mkContrastRatio, mkEncodeQr, mkFormatCompact, mkHeatmapCellColors, mkLinePath, mkLinearScale, mkMixRgb, mkNiceTicks, mkOrgChartFromFlat, mkParseRgb, mkRelativeLuminance, mkSplitDuration, mkSquarify };
6926
6090
  //# sourceMappingURL=mk-kit-ui-data.mjs.map