@iyulab/chat-components 0.1.1 → 0.2.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.
Files changed (109) hide show
  1. package/CHANGELOG.md +24 -1
  2. package/dist/components/blocks/UCodeBlock.component.d.ts +7 -8
  3. package/dist/components/blocks/UCodeBlock.component.js +32 -21
  4. package/dist/components/blocks/UCodeBlock.styles.js +7 -6
  5. package/dist/components/{json-viewer/UJsonViewer.component.d.ts → blocks/UJsonBlock.component.d.ts} +5 -4
  6. package/dist/components/{json-viewer/UJsonViewer.component.js → blocks/UJsonBlock.component.js} +20 -17
  7. package/dist/components/blocks/UJsonBlock.d.ts +7 -0
  8. package/dist/components/blocks/UJsonBlock.js +5 -0
  9. package/dist/components/blocks/UMarkedBlock.component.d.ts +30 -9
  10. package/dist/components/blocks/UMarkedBlock.component.js +75 -26
  11. package/dist/components/blocks/UMarkedBlock.styles.js +288 -949
  12. package/dist/components/blocks/UThinkBlock.component.d.ts +3 -8
  13. package/dist/components/blocks/UThinkBlock.component.js +28 -22
  14. package/dist/components/blocks/UThinkBlock.styles.js +35 -54
  15. package/dist/components/blocks/UToolBlock.component.d.ts +13 -9
  16. package/dist/components/blocks/UToolBlock.component.js +30 -76
  17. package/dist/components/blocks/UToolBlock.styles.js +21 -62
  18. package/dist/components/buttons/UAttachButton.component.d.ts +3 -5
  19. package/dist/components/buttons/UAttachButton.component.js +30 -19
  20. package/dist/components/buttons/UAttachButton.styles.js +5 -12
  21. package/dist/components/buttons/UCopyButton.component.d.ts +3 -8
  22. package/dist/components/buttons/UCopyButton.component.js +29 -53
  23. package/dist/components/buttons/UCopyButton.styles.js +8 -37
  24. package/dist/components/buttons/UReportButton.component.d.ts +9 -0
  25. package/dist/components/buttons/UReportButton.component.js +36 -0
  26. package/dist/components/buttons/UReportButton.d.ts +7 -0
  27. package/dist/components/buttons/UReportButton.js +5 -0
  28. package/dist/components/buttons/UReportButton.styles.js +14 -0
  29. package/dist/components/buttons/URetryButton.component.d.ts +11 -0
  30. package/dist/components/buttons/URetryButton.component.js +53 -0
  31. package/dist/components/buttons/URetryButton.d.ts +7 -0
  32. package/dist/components/buttons/URetryButton.js +5 -0
  33. package/dist/components/buttons/URetryButton.styles.js +26 -0
  34. package/dist/components/buttons/UShareButton.component.d.ts +9 -0
  35. package/dist/components/buttons/UShareButton.component.js +36 -0
  36. package/dist/components/buttons/UShareButton.d.ts +7 -0
  37. package/dist/components/buttons/UShareButton.js +5 -0
  38. package/dist/components/buttons/UShareButton.styles.d.ts +1 -0
  39. package/dist/components/buttons/UShareButton.styles.js +14 -0
  40. package/dist/components/buttons/UVoteButton.component.d.ts +15 -0
  41. package/dist/components/buttons/UVoteButton.component.js +70 -0
  42. package/dist/components/buttons/UVoteButton.d.ts +8 -0
  43. package/dist/components/buttons/UVoteButton.js +5 -0
  44. package/dist/components/buttons/UVoteButton.styles.d.ts +1 -0
  45. package/dist/components/buttons/UVoteButton.styles.js +19 -0
  46. package/dist/components/loaders/UDotLoader.component.d.ts +9 -0
  47. package/dist/components/loaders/UDotLoader.component.js +23 -0
  48. package/dist/components/loaders/UDotLoader.d.ts +7 -0
  49. package/dist/components/loaders/UDotLoader.js +5 -0
  50. package/dist/components/loaders/UDotLoader.styles.d.ts +1 -0
  51. package/dist/components/loaders/UDotLoader.styles.js +50 -0
  52. package/dist/components/message/UMessage.component.d.ts +12 -6
  53. package/dist/components/message/UMessage.component.js +39 -59
  54. package/dist/components/message/UMessage.d.ts +0 -1
  55. package/dist/components/message/UMessage.styles.js +30 -49
  56. package/dist/components/prompt/UPrompt.component.d.ts +25 -0
  57. package/dist/components/prompt/UPrompt.component.js +113 -0
  58. package/dist/components/prompt/UPrompt.d.ts +7 -0
  59. package/dist/components/prompt/UPrompt.js +5 -0
  60. package/dist/components/prompt/UPrompt.styles.d.ts +1 -0
  61. package/dist/components/prompt/UPrompt.styles.js +42 -0
  62. package/dist/components/references/URefCard.component.d.ts +22 -0
  63. package/dist/components/references/URefCard.component.js +96 -0
  64. package/dist/components/references/URefCard.d.ts +7 -0
  65. package/dist/components/references/URefCard.js +5 -0
  66. package/dist/components/references/URefCard.styles.d.ts +1 -0
  67. package/dist/components/references/URefCard.styles.js +103 -0
  68. package/dist/components/references/URefCardGroup.component.d.ts +25 -0
  69. package/dist/components/references/URefCardGroup.component.js +88 -0
  70. package/dist/components/references/URefCardGroup.d.ts +7 -0
  71. package/dist/components/references/URefCardGroup.js +5 -0
  72. package/dist/components/references/URefCardGroup.styles.d.ts +1 -0
  73. package/dist/components/references/URefCardGroup.styles.js +69 -0
  74. package/dist/components/references/URefTag.component.d.ts +11 -0
  75. package/dist/components/{buttons/USendButton.component.js → references/URefTag.component.js} +17 -14
  76. package/dist/components/references/URefTag.d.ts +7 -0
  77. package/dist/components/references/URefTag.js +5 -0
  78. package/dist/components/references/URefTag.styles.d.ts +1 -0
  79. package/dist/components/references/URefTag.styles.js +51 -0
  80. package/dist/events/UCancelEvent.d.ts +6 -0
  81. package/dist/index.d.ts +15 -5
  82. package/dist/index.js +20 -6
  83. package/dist/types/BlockItem.d.ts +36 -0
  84. package/dist/types/BlockReference.d.ts +32 -0
  85. package/dist/types/JsonNode.d.ts +20 -0
  86. package/dist/utilities/converters.d.ts +9 -0
  87. package/dist/utilities/converters.js +19 -0
  88. package/package.json +9 -10
  89. package/dist/components/buttons/USendButton.component.d.ts +0 -13
  90. package/dist/components/buttons/USendButton.d.ts +0 -7
  91. package/dist/components/buttons/USendButton.js +0 -5
  92. package/dist/components/buttons/USendButton.styles.js +0 -23
  93. package/dist/components/buttons/UThinkButton.component.d.ts +0 -19
  94. package/dist/components/buttons/UThinkButton.component.js +0 -73
  95. package/dist/components/buttons/UThinkButton.d.ts +0 -7
  96. package/dist/components/buttons/UThinkButton.js +0 -5
  97. package/dist/components/buttons/UThinkButton.styles.js +0 -72
  98. package/dist/components/json-viewer/UJsonViewer.d.ts +0 -7
  99. package/dist/components/json-viewer/UJsonViewer.js +0 -5
  100. package/dist/components/json-viewer/UJsonViewer.lib.d.ts +0 -16
  101. package/dist/components/json-viewer/UJsonViewer.lib.js +0 -28
  102. package/dist/components/message/UMessage.types.d.ts +0 -21
  103. package/dist/events/UStopEvent.d.ts +0 -6
  104. package/dist/internals/date-helpers.d.ts +0 -8
  105. package/dist/internals/date-helpers.js +0 -27
  106. /package/dist/components/{buttons/USendButton.styles.d.ts → blocks/UJsonBlock.styles.d.ts} +0 -0
  107. /package/dist/components/{json-viewer/UJsonViewer.styles.js → blocks/UJsonBlock.styles.js} +0 -0
  108. /package/dist/components/buttons/{UThinkButton.styles.d.ts → UReportButton.styles.d.ts} +0 -0
  109. /package/dist/components/{json-viewer/UJsonViewer.styles.d.ts → buttons/URetryButton.styles.d.ts} +0 -0
package/CHANGELOG.md CHANGED
@@ -1,7 +1,30 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.2.0 (2026-01-16)
4
+
5
+ ### Added
6
+ - New components: `UPrompt`, `UDotLoader` for enhanced chat UI
7
+ - Reference components: `URefCard`, `URefCardGroup`, `URefTag` for displaying references
8
+ - New button components: `UReportButton`, `URetryButton`, `UShareButton`, `UVoteButton`
9
+ - `UCancelEvent` to replace `UStopEvent`
10
+ - Type definitions: `BlockItem`, `BlockReference`, `JsonNode`
11
+ - Utility converters for data transformation
12
+ - Test utilities: generator.ts, messages.ts
13
+
14
+ ### Changed
15
+ - Renamed UJsonViewer to UJsonBlock for consistency
16
+ - Enhanced styling and functionality across multiple block components
17
+ - Updated button components styling and behavior (UAttachButton, UCopyButton)
18
+ - Improved UMessage component structure and styling
19
+
20
+ ### Removed
21
+ - Deprecated buttons: USendButton, UThinkButton
22
+ - UStopEvent (replaced by UCancelEvent)
23
+ - UMessage.types.ts (types moved to dedicated types directory)
24
+ - Internal date-helpers utility
25
+
3
26
  ## 0.1.1 (2025-12-19)
4
- Update package.json exports field with correct paths
27
+ - Update package.json exports field with correct paths
5
28
 
6
29
  ## 0.1.0 (2025-12-19)
7
30
  - Initial library version release
@@ -1,4 +1,3 @@
1
- import { nothing } from 'lit';
2
1
  import { BaseElement } from '@iyulab/components/dist/components/BaseElement.js';
3
2
  /**
4
3
  * 코드 블록을 렌더링하는 컴포넌트입니다.
@@ -7,18 +6,18 @@ import { BaseElement } from '@iyulab/components/dist/components/BaseElement.js';
7
6
  export declare class UCodeBlock extends BaseElement {
8
7
  static styles: import('lit').CSSResultGroup[];
9
8
  static dependencies: Record<string, typeof BaseElement>;
9
+ /** 클립보드 복사 상태를 나타내는 플래그입니다. */
10
+ isCopied: boolean;
10
11
  /** 코드 블록의 헤더를 숨길지 여부를 지정합니다. */
11
12
  headless: boolean;
12
13
  /** 코드 언어를 지정합니다. */
13
- language: string;
14
+ lang: string;
14
15
  /** 표시할 코드 내용입니다. */
15
16
  value?: string;
16
- render(): import('lit-html').TemplateResult<1> | typeof nothing;
17
+ render(): import('lit-html').TemplateResult<1>;
17
18
  /**
18
- * 코드에 구문 강조를 적용합니다.
19
- * @param value 코드 문자열
20
- * @param lang 코드 언어
21
- * @returns 구문 강조가 적용된 HTML 문자열
19
+ * slot에 할당된 코드를 value로 설정합니다.
20
+ * 기능을 통해 HTML 내에 직접 코드를 작성할 수 있습니다.
22
21
  */
23
- private parse;
22
+ private handleSlotChange;
24
23
  }
@@ -1,5 +1,5 @@
1
- import { nothing, html } from 'lit';
2
- import { property } from 'lit/decorators.js';
1
+ import { html } from 'lit';
2
+ import { state, property } from 'lit/decorators.js';
3
3
  import { unsafeHTML } from 'lit/directives/unsafe-html.js';
4
4
  import hljs from 'highlight.js';
5
5
  import { BaseElement } from '@iyulab/components/dist/components/BaseElement.js';
@@ -18,8 +18,18 @@ var __decorateClass = (decorators, target, key, kind) => {
18
18
  class UCodeBlock extends BaseElement {
19
19
  constructor() {
20
20
  super(...arguments);
21
+ this.isCopied = false;
21
22
  this.headless = false;
22
- this.language = "plaintext";
23
+ this.lang = "plaintext";
24
+ /**
25
+ * slot에 할당된 코드를 value로 설정합니다.
26
+ * 이 기능을 통해 HTML 내에 직접 코드를 작성할 수 있습니다.
27
+ */
28
+ this.handleSlotChange = (e) => {
29
+ const slot = e.target;
30
+ const nodes = slot.assignedNodes({ flatten: true });
31
+ this.value = nodes.map((node) => node.textContent).join("\n\n");
32
+ };
23
33
  }
24
34
  static {
25
35
  this.styles = [super.styles, styles];
@@ -30,34 +40,35 @@ class UCodeBlock extends BaseElement {
30
40
  };
31
41
  }
32
42
  render() {
33
- if (!this.value) return nothing;
34
- const lang = hljs.getLanguage(this.language) ? this.language : "plaintext";
43
+ const lang = hljs.getLanguage(this.lang) ? this.lang : "plaintext";
44
+ const value = this.value || "";
35
45
  return html`
36
46
  <div class="header" ?hidden=${this.headless}>
37
- <span class="language">${lang}</span>
38
- <u-copy-button mode="badge" .value="${this.value}"></u-copy-button>
47
+ <span class="lang">
48
+ ${lang}
49
+ </span>
50
+ <u-copy-button
51
+ .value=${value}
52
+ ></u-copy-button>
39
53
  </div>
40
- ${unsafeHTML(`<pre class="hljs">${this.parse(this.value, lang)}</pre>`)}
41
- `;
42
- }
43
- /**
44
- * 코드에 구문 강조를 적용합니다.
45
- * @param value 코드 문자열
46
- * @param lang 코드 언어
47
- * @returns 구문 강조가 적용된 HTML 문자열
48
- */
49
- parse(value, lang) {
50
- return hljs.highlight(value, {
54
+
55
+ <pre class="hljs">${unsafeHTML(hljs.highlight(value, {
51
56
  language: lang
52
- }).value;
57
+ }).value)}</pre>
58
+
59
+ <slot hidden @slotchange=${this.handleSlotChange}></slot>
60
+ `;
53
61
  }
54
62
  }
63
+ __decorateClass([
64
+ state()
65
+ ], UCodeBlock.prototype, "isCopied");
55
66
  __decorateClass([
56
67
  property({ type: Boolean, reflect: true })
57
68
  ], UCodeBlock.prototype, "headless");
58
69
  __decorateClass([
59
- property({ type: String })
60
- ], UCodeBlock.prototype, "language");
70
+ property({ type: String, reflect: true })
71
+ ], UCodeBlock.prototype, "lang");
61
72
  __decorateClass([
62
73
  property({ type: String })
63
74
  ], UCodeBlock.prototype, "value");
@@ -39,10 +39,10 @@ const styles = css`
39
39
  :host {
40
40
  display: block;
41
41
  width: 100%;
42
+ padding: 8px 16px;
42
43
  border: 1px solid var(--u-border-color);
43
44
  border-radius: 8px;
44
45
  background-color: var(--u-neutral-100);
45
- padding: 8px 16px;
46
46
  }
47
47
 
48
48
  .header {
@@ -51,28 +51,29 @@ const styles = css`
51
51
  flex-direction: row;
52
52
  align-items: center;
53
53
  justify-content: space-between;
54
+ margin-bottom: 12px;
55
+ user-select: none;
56
+ }
57
+ .header .lang {
54
58
  font-family: Arial, Helvetica, sans-serif;
55
59
  font-size: 12px;
56
60
  font-weight: 300;
57
61
  color: var(--u-txt-color-strong);
58
- margin-bottom: 12px;
59
- user-select: none;
60
62
  }
61
63
 
62
64
  /* highlight.js styles */
63
65
  .hljs {
64
66
  display: block;
65
- overflow-x: auto;
66
67
  margin: 0;
67
68
  padding: 0;
68
69
  color: var(--hljs-text-color);
69
- font-family: var(--fontStack-monospace, ui-monospace, SFMono-Regular, SF Mono, Menlo, Consolas, Liberation Mono, monospace);
70
+ font-family: ui-monospace, SFMono-Regular, SF Mono, Menlo, Consolas, Liberation Mono, monospace;
70
71
  font-size: 14px;
71
72
  line-height: 1.45;
72
73
  white-space: pre;
74
+ overflow: auto;
73
75
  scrollbar-width: thin;
74
76
  scrollbar-color: var(--u-scrollbar-color) transparent;
75
- scrollbar-gutter: stable;
76
77
  }
77
78
 
78
79
  .hljs-doctag,
@@ -1,10 +1,10 @@
1
1
  import { PropertyValues, TemplateResult } from 'lit';
2
2
  import { BaseElement } from '@iyulab/components/dist/components/BaseElement.js';
3
- import { JsonNode } from './UJsonViewer.lib.js';
3
+ import { JsonNode } from '../../types/JsonNode.js';
4
4
  /**
5
5
  * json 데이터를 트리 형태로 시각화하는 컴포넌트입니다.
6
6
  */
7
- export declare class UJsonViewer extends BaseElement {
7
+ export declare class UJsonBlock extends BaseElement {
8
8
  static styles: import('lit').CSSResultGroup[];
9
9
  static dependencies: Record<string, typeof BaseElement>;
10
10
  state: Record<string, boolean>;
@@ -37,8 +37,9 @@ export declare class UJsonViewer extends BaseElement {
37
37
  * @param node - JsonObject 또는 JsonArray
38
38
  */
39
39
  private renderPreview;
40
- /** 노드 확장/축소 클릭 핸들러 */
41
- private toggle;
42
40
  /** JSON 데이터를 순회하며 각 경로의 확장 상태를 설정합니다. */
43
41
  private setState;
42
+ private isValueType;
43
+ /** 노드 확장/축소 클릭 핸들러 */
44
+ private handlePropertyKeyClick;
44
45
  }
@@ -3,8 +3,8 @@ import { state, property } from 'lit/decorators.js';
3
3
  import { map } from 'lit/directives/map.js';
4
4
  import { when } from 'lit/directives/when.js';
5
5
  import { BaseElement } from '@iyulab/components/dist/components/BaseElement.js';
6
- import { isValueType, getNodeName, jsonConverter } from './UJsonViewer.lib.js';
7
- import { styles } from './UJsonViewer.styles.js';
6
+ import { jsonAttrConverter } from '../../utilities/converters.js';
7
+ import { styles } from './UJsonBlock.styles.js';
8
8
 
9
9
  var __defProp = Object.defineProperty;
10
10
  var __decorateClass = (decorators, target, key, kind) => {
@@ -15,12 +15,15 @@ var __decorateClass = (decorators, target, key, kind) => {
15
15
  if (result) __defProp(target, key, result);
16
16
  return result;
17
17
  };
18
- class UJsonViewer extends BaseElement {
18
+ class UJsonBlock extends BaseElement {
19
19
  constructor() {
20
20
  super(...arguments);
21
21
  this.state = {};
22
22
  this.expanded = true;
23
23
  this.value = {};
24
+ this.isValueType = (value) => {
25
+ return value !== Object(value);
26
+ };
24
27
  }
25
28
  static {
26
29
  this.styles = [super.styles, styles];
@@ -51,14 +54,14 @@ class UJsonViewer extends BaseElement {
51
54
  * @param parent - 부모 노드의 경로
52
55
  */
53
56
  renderNode(value, parent = "") {
54
- return isValueType(value) ? this.renderValue(value) : this.renderObject(value, parent);
57
+ return this.isValueType(value) ? this.renderValue(value) : this.renderObject(value, parent);
55
58
  }
56
59
  /**
57
60
  * JsonValue를 렌더링합니다.
58
61
  * @param node - JsonValue (string, number, boolean, null 등)
59
62
  */
60
63
  renderValue(node) {
61
- const type = getNodeName(node);
64
+ const type = node === null ? "null" : Array.isArray(node) ? "array" : typeof node === "object" ? "object" : typeof node;
62
65
  return html`
63
66
  <span part="${type}" class="${type}" role="treeitem">
64
67
  ${JSON.stringify(node)}
@@ -75,7 +78,7 @@ class UJsonViewer extends BaseElement {
75
78
  <ul part="object" role="group">
76
79
  ${map(Object.entries(node), ([key, value]) => {
77
80
  const path = parent ? `${parent}.${key}` : key;
78
- const isValue = isValueType(value);
81
+ const isValue = this.isValueType(value);
79
82
  const isExpanded = this.state[path] ?? this.expanded;
80
83
  return html`
81
84
  <li part="property" role="treeitem"
@@ -85,7 +88,7 @@ class UJsonViewer extends BaseElement {
85
88
  class="key"
86
89
  ?collapsable="${!isValue}"
87
90
  ?collapsed="${!isValue && !isExpanded}"
88
- @click=${!isValue ? () => this.toggle(path) : null}>
91
+ @click=${!isValue ? () => this.handlePropertyKeyClick(path) : null}>
89
92
  ${key}:
90
93
  ${when(!isValue && !isExpanded, () => this.renderPreview(value))}
91
94
  </span>
@@ -107,11 +110,6 @@ class UJsonViewer extends BaseElement {
107
110
  </span>
108
111
  `;
109
112
  }
110
- /** 노드 확장/축소 클릭 핸들러 */
111
- toggle(path) {
112
- const isExpanded = this.state[path] ?? false;
113
- this.state = { ...this.state, [path]: !isExpanded };
114
- }
115
113
  /** JSON 데이터를 순회하며 각 경로의 확장 상태를 설정합니다. */
116
114
  setState(value, parent = "") {
117
115
  if (typeof value !== "object" || value === null) return {};
@@ -123,15 +121,20 @@ class UJsonViewer extends BaseElement {
123
121
  });
124
122
  return state2;
125
123
  }
124
+ /** 노드 확장/축소 클릭 핸들러 */
125
+ handlePropertyKeyClick(path) {
126
+ const isExpanded = this.state[path] ?? false;
127
+ this.state = { ...this.state, [path]: !isExpanded };
128
+ }
126
129
  }
127
130
  __decorateClass([
128
131
  state()
129
- ], UJsonViewer.prototype, "state");
132
+ ], UJsonBlock.prototype, "state");
130
133
  __decorateClass([
131
134
  property({ type: Boolean })
132
- ], UJsonViewer.prototype, "expanded");
135
+ ], UJsonBlock.prototype, "expanded");
133
136
  __decorateClass([
134
- property({ type: Object, converter: jsonConverter })
135
- ], UJsonViewer.prototype, "value");
137
+ property({ type: Object, converter: jsonAttrConverter })
138
+ ], UJsonBlock.prototype, "value");
136
139
 
137
- export { UJsonViewer };
140
+ export { UJsonBlock };
@@ -0,0 +1,7 @@
1
+ import { UJsonBlock } from './UJsonBlock.component.js';
2
+ declare global {
3
+ interface HTMLElementTagNameMap {
4
+ 'u-json-block': UJsonBlock;
5
+ }
6
+ }
7
+ export { UJsonBlock };
@@ -0,0 +1,5 @@
1
+ import { UJsonBlock } from './UJsonBlock.component.js';
2
+
3
+ UJsonBlock.define("u-json-block");
4
+
5
+ export { UJsonBlock };
@@ -1,7 +1,12 @@
1
- import { nothing } from 'lit';
2
1
  import { BaseElement } from '@iyulab/components/dist/components/BaseElement.js';
2
+ import { TextBlockReference } from '../../types/BlockReference';
3
3
  /**
4
4
  * 마크다운 컨텐츠를 렌더링하는 컴포넌트입니다.
5
+ *
6
+ * 주의:
7
+ * - refs로 삽입되는 커스텀 태그(u-ref-*)는 속성/내용을 모두 escape하여 XSS를 방지합니다.
8
+ * - 코드블록 내부는 HTML을 전부 텍스트로 취급(escape)하며, refs 태그는 통째 제거합니다.
9
+ * - 인덱스 기반 삽입(ref.endIndex)이 깨지지 않도록 "normalize(제로폭 제거)"를 insert 전에 1회 수행합니다.
5
10
  */
6
11
  export declare class UMarkedBlock extends BaseElement {
7
12
  static styles: import('lit').CSSResultGroup[];
@@ -9,16 +14,32 @@ export declare class UMarkedBlock extends BaseElement {
9
14
  private parser;
10
15
  /** 마크다운 컨텐츠를 렌더링할 때 사용할 값입니다. */
11
16
  value?: string;
12
- render(): import('lit-html').TemplateResult<1> | typeof nothing;
17
+ /** 컨텐츠의 인용 출처들입니다. */
18
+ refs?: TextBlockReference[];
19
+ render(): import('lit-html/directive.js').DirectiveResult<typeof import('lit-html/directives/unsafe-html.js').UnsafeHTMLDirective>;
13
20
  /**
14
- * 마크다운 문자열을 HTML로 변환합니다.
15
- * @param value 변환할 마크다운 문자열
16
- * @returns 변환된 HTML 문자열
21
+ * 인용/출처 참조객체가 있을때 문자열에 출처태그를 삽입합니다.
22
+ * @param value 삽입할 마크다운 문자열
23
+ * @return 삽입된 마크다운 문자열
17
24
  */
18
- private parse;
25
+ private insert;
19
26
  /**
20
- * HTML 속성에 안전하게 삽입하기 위해 특수문자를 이스케이프합니다.
21
- * @param value 원본 문자열
27
+ * 참조 카드 엘리먼트를 안전하게 빌드합니다.
22
28
  */
23
- private sanitizeCode;
29
+ private buildCard;
30
+ /**
31
+ * Zero Width Space, LTR/RTL marks, BOM 등 특수 제어 문자를 제거합니다.
32
+ * (ref.endIndex 기준을 흔들지 않게 insert 전에 1회 수행)
33
+ */
34
+ private normalizeText;
35
+ /**
36
+ * 코드블록 내부 텍스트를 안전하게 처리합니다.
37
+ * - u-ref-tag는 통째로 제거(코드블록 안에서 UI 태그가 살아남는 것 방지)
38
+ * - 나머지는 전부 HTML escape
39
+ */
40
+ private sanitizeText;
41
+ /**
42
+ * HTML 텍스트 노드로 안전하게 삽입하기 위해 escape 합니다.
43
+ */
44
+ private escapeHTML;
24
45
  }
@@ -1,12 +1,14 @@
1
- import { nothing, html } from 'lit';
1
+ import { nothing } from 'lit';
2
2
  import { property } from 'lit/decorators.js';
3
3
  import { unsafeHTML } from 'lit/directives/unsafe-html.js';
4
4
  import { Marked } from 'marked';
5
- import markedAlert from 'marked-alert';
6
- import markedFootnote from 'marked-footnote';
7
5
  import markedKatex from 'marked-katex-extension';
8
6
  import { BaseElement } from '@iyulab/components/dist/components/BaseElement.js';
7
+ import { UTooltip } from '@iyulab/components/dist/components/tooltip/UTooltip.component.js';
9
8
  import { UCodeBlock } from './UCodeBlock.component.js';
9
+ import { URefTag } from '../references/URefTag.component.js';
10
+ import { URefCard } from '../references/URefCard.component.js';
11
+ import { URefCardGroup } from '../references/URefCardGroup.component.js';
10
12
  import { styles } from './UMarkedBlock.styles.js';
11
13
 
12
14
  var __defProp = Object.defineProperty;
@@ -23,58 +25,105 @@ class UMarkedBlock extends BaseElement {
23
25
  super(...arguments);
24
26
  this.parser = new Marked({
25
27
  pedantic: false,
26
- // 엄격한 마크다운 규격 준수하지 않음 (markdown.pl을 따르지 않음)
27
28
  gfm: true,
28
- // GitHub Flavored Markdown 사용
29
29
  breaks: true,
30
- // 단순 줄바꿈 또한 <br> 태그로 변환
31
30
  silent: true,
32
- // 파싱 오류 발생 시 예외를 발생시키지 않고 메시지를 출력
33
31
  renderer: {
34
32
  code: ({ text, lang }) => {
35
33
  lang ||= "plaintext";
36
- text = this.sanitizeCode(text);
37
- return `<u-code-block language="${lang}" value="${text}"></u-code-block>`;
34
+ const safeLang = this.escapeHTML(lang);
35
+ const safeText = this.sanitizeText(text);
36
+ return `<u-code-block lang="${safeLang}">${safeText}</u-code-block>`;
38
37
  }
39
38
  }
40
- }).use(markedAlert()).use(markedFootnote()).use(markedKatex({ output: "mathml" }));
39
+ }).use(markedKatex({ output: "mathml" }));
41
40
  }
42
41
  static {
43
42
  this.styles = [super.styles, styles];
44
43
  }
45
44
  static {
46
45
  this.dependencies = {
47
- "u-code-block": UCodeBlock
46
+ "u-tooltip": UTooltip,
47
+ "u-code-block": UCodeBlock,
48
+ "u-ref-tag": URefTag,
49
+ "u-ref-card": URefCard,
50
+ "u-ref-card-group": URefCardGroup
48
51
  };
49
52
  }
50
53
  render() {
51
54
  if (!this.value) return nothing;
52
- return html`
53
- <div class="markdown-body">
54
- ${unsafeHTML(this.parse(this.value))}
55
- </div>
56
- `;
55
+ let value = this.normalizeText(this.value);
56
+ if (this.refs && this.refs.length > 0) {
57
+ value = this.insert(value, this.refs);
58
+ }
59
+ const html = this.parser.parse(value, { async: false });
60
+ return unsafeHTML(html);
57
61
  }
58
62
  /**
59
- * 마크다운 문자열을 HTML로 변환합니다.
60
- * @param value 변환할 마크다운 문자열
61
- * @returns 변환된 HTML 문자열
63
+ * 인용/출처 참조객체가 있을때 문자열에 출처태그를 삽입합니다.
64
+ * @param value 삽입할 마크다운 문자열
65
+ * @return 삽입된 마크다운 문자열
62
66
  */
63
- parse(value) {
64
- value = value.replace(/\u200B|\u200C|\u200D|\u200E|\u200F|\uFEFF/g, "");
65
- value = this.parser.parse(value, { async: false });
67
+ insert(value, refs) {
68
+ const reversed = [...refs].sort((a, b) => b.endIndex - a.endIndex);
69
+ reversed.forEach((ref) => {
70
+ const sources = ref.sources ?? [];
71
+ const firstSource = sources.at(0);
72
+ const tagName = this.escapeHTML(ref.name ?? "");
73
+ const tagLink = this.escapeHTML(firstSource?.url ?? "#");
74
+ let tooltip = "";
75
+ if (firstSource && sources.length === 1) {
76
+ tooltip = this.buildCard(firstSource, true);
77
+ }
78
+ if (sources.length > 1) {
79
+ const cards = sources.map((s) => this.buildCard(s, false)).join("");
80
+ tooltip = `<u-ref-card-group slot="tooltip">${cards}</u-ref-card-group>`;
81
+ }
82
+ const tag = `<u-ref-tag href="${tagLink}" title="${tagName}">${tagName}${tooltip}</u-ref-tag>`;
83
+ value = value.slice(0, ref.endIndex) + tag + value.slice(ref.endIndex);
84
+ });
66
85
  return value;
67
86
  }
68
87
  /**
69
- * HTML 속성에 안전하게 삽입하기 위해 특수문자를 이스케이프합니다.
70
- * @param value 원본 문자열
88
+ * 참조 카드 엘리먼트를 안전하게 빌드합니다.
89
+ */
90
+ buildCard(source, slot) {
91
+ const type = this.escapeHTML(source.type ?? "");
92
+ const href = this.escapeHTML(source.url ?? "#");
93
+ const heading = this.escapeHTML(source.title ?? "");
94
+ const tags = this.escapeHTML((source.tags ?? []).join(","));
95
+ const snippet = this.escapeHTML(source.snippet ?? "");
96
+ const slotAttr = slot ? ` slot="tooltip"` : "";
97
+ return `<u-ref-card${slotAttr} type="${type}" href="${href}" heading="${heading}" tags="${tags}">${snippet}</u-ref-card>`;
98
+ }
99
+ /**
100
+ * Zero Width Space, LTR/RTL marks, BOM 등 특수 제어 문자를 제거합니다.
101
+ * (ref.endIndex 기준을 흔들지 않게 insert 전에 1회 수행)
102
+ */
103
+ normalizeText(value) {
104
+ return value.replace(/\u200B|\u200C|\u200D|\u200E|\u200F|\uFEFF/g, "");
105
+ }
106
+ /**
107
+ * 코드블록 내부 텍스트를 안전하게 처리합니다.
108
+ * - u-ref-tag는 통째로 제거(코드블록 안에서 UI 태그가 살아남는 것 방지)
109
+ * - 나머지는 전부 HTML escape
110
+ */
111
+ sanitizeText(value) {
112
+ value = value.replace(/<u-ref-tag\b[^>]*>[\s\S]*?<\/u-ref-tag>/gi, "");
113
+ return this.escapeHTML(value);
114
+ }
115
+ /**
116
+ * HTML 텍스트 노드로 안전하게 삽입하기 위해 escape 합니다.
71
117
  */
72
- sanitizeCode(value) {
73
- return value.replace(/&/g, "&amp;").replace(/"/g, "&quot;").replace(/'/g, "&#39;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
118
+ escapeHTML(value) {
119
+ return value.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&#39;");
74
120
  }
75
121
  }
76
122
  __decorateClass([
77
123
  property({ type: String })
78
124
  ], UMarkedBlock.prototype, "value");
125
+ __decorateClass([
126
+ property({ type: Array })
127
+ ], UMarkedBlock.prototype, "refs");
79
128
 
80
129
  export { UMarkedBlock };