ide-assi 0.330.0 → 0.333.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.
@@ -1,12 +1,31 @@
1
1
  import ninegrid from "ninegrid2";
2
- import {diff_match_patch} from 'diff-match-patch';
3
2
 
4
- export class IdeDiff extends HTMLElement
5
- {
6
- #asisSrc;
7
- #tobeSrc;
8
- #asisPre; // <pre> 요소 참조 저장
9
- #tobePre; // <pre> 요소 참조 저장
3
+ // CodeMirror 6 핵심 및 확장 임포트
4
+ import { EditorView, lineNumbers, highlightSpecialChars, drawSelection, dropCursor, keymap, highlightActiveLine, highlightActiveLineGutter } from "@codemirror/view";
5
+ import { EditorState, Compartment } from "@codemirror/state";
6
+ import { history, historyKeymap, indentWithTab } from "@codemirror/commands";
7
+ // ⭐️ 여기서 searchKeymap을 제거합니다.
8
+ import { defaultKeymap, selectAll } from "@codemirror/commands"; // selectAll은 commands에 남아있습니다.
9
+ // ⭐️ searchKeymap은 이제 @codemirror/search 에서 가져옵니다.
10
+ import { searchKeymap, highlightSelectionMatches } from "@codemirror/search";
11
+ import { bracketMatching } from "@codemirror/language";
12
+ import { javascript } from "@codemirror/lang-javascript"; // 예시 언어 (필요에 따라 다른 언어 팩 추가)
13
+ import { indentOnInput, syntaxHighlighting, defaultHighlightStyle } from "@codemirror/language"; // syntaxHighlighting 추가
14
+ import { lintKeymap } from "@codemirror/lint";
15
+ import { autocompletion, completionKeymap } from "@codemirror/autocomplete";
16
+
17
+ // Diff 로직을 위해 diff-match-patch 사용
18
+ import { diff_match_patch } from 'diff-match-patch';
19
+
20
+ // ... (나머지 IdeDiff 클래스 코드는 동일) ...
21
+
22
+ export class IdeDiff extends HTMLElement {
23
+ #asisEditorView; // asis 패널의 CodeMirror EditorView 인스턴스
24
+ #tobeEditorView; // tobe 패널의 CodeMirror EditorView 인스턴스
25
+ #asisEditorEl; // asis 에디터를 렌더링할 DOM 요소
26
+ #tobeEditorEl; // tobe 에디터를 렌더링할 DOM 요소
27
+
28
+ #languageCompartment = new Compartment(); // 언어 확장을 동적으로 변경하기 위한 Compartment
10
29
 
11
30
  constructor() {
12
31
  super();
@@ -15,127 +34,227 @@ export class IdeDiff extends HTMLElement
15
34
 
16
35
  connectedCallback() {
17
36
  this.shadowRoot.innerHTML = `
18
- <style>
19
- /* 전역 또는 컴포넌트 스코프에서 box-sizing: border-box; 적용 권장 */
20
- * {
21
- box-sizing: border-box;
22
- }
23
- @import "https://cdn.jsdelivr.net/npm/ninegrid@${ninegrid.version}/dist/css/ideDiff.css";
24
- ${ninegrid.getCustomPath(this,"ideDiff.css")}
25
-
26
- /* 추가 CSS (필요 시) */
27
- .wrapper {
28
- display: flex; /* 스플리터와 함께 작동하도록 flex 컨테이너 */
29
- height: 100%; /* 부모의 높이를 채우도록 설정 */
30
- overflow: hidden; /* 내부 스크롤을 위해 overflow 처리 */
31
- }
32
- .panel {
33
- flex: 1; /* 패널들이 남은 공간을 채우도록 */
34
- overflow: auto; /* 패널 내부에서 스크롤 가능하게 */
35
- position: relative; /* 자식 요소의 absolute 포지셔닝 기준 */
36
- }
37
- .panel pre {
38
- margin: 0; /* pre 태그의 기본 마진 제거 */
39
- padding: 10px; /* 코드 가독성을 위한 내부 패딩 */
40
- white-space: pre-wrap; /* 자동 줄바꿈 */
41
- word-break: break-all; /* 단어가 길어도 강제 줄바꿈 */
42
- /* white-space: pre-wrap; 사용하면 가로 스크롤 없이 줄바꿈됩니다.
43
- 만약 가로 스크롤을 원하면 white-space: pre; 로 두고 overflow-x: auto; 를 panel에 추가 */
44
- }
45
- ins {
46
- background-color: #d4edda; /* 삽입된 텍스트 배경색 (예시) */
47
- text-decoration: none; /* 밑줄 제거 (일반적으로) */
48
- }
49
- del {
50
- background-color: #f8d7da; /* 삭제된 텍스트 배경색 (예시) */
51
- text-decoration: none; /* 취소선 제거 (일반적으로) */
52
- }
53
- </style>
54
-
55
- <div class="wrapper">
56
- <div class="panel asis"><pre></pre></div>
57
- <nx-splitter></nx-splitter>
58
- <div class="panel tobe"><pre></pre></div>
59
- </div>
60
- `;
37
+ <style>
38
+ /* ninegrid CSS 필요한 기본 스타일 */
39
+ @import "https://cdn.jsdelivr.net/npm/ninegrid@${ninegrid.version}/dist/css/ideDiff.css";
40
+ ${ninegrid.getCustomPath(this, "ideDiff.css")}
41
+
42
+ /* CodeMirror를 위한 기본적인 Flexbox 레이아웃 */
43
+ .wrapper {
44
+ display: flex;
45
+ width: 100%;
46
+ height: 100%; /* 부모의 높이를 채우도록 설정 */
47
+ overflow: hidden; /* 내부 스크롤을 위해 overflow 처리 */
48
+ }
49
+ .panel {
50
+ flex: 1; /* 패널들이 남은 공간을 채우도록 */
51
+ overflow: hidden; /* CodeMirror EditorView 자체가 스크롤을 처리 */
52
+ /* position: relative; */ /* CodeMirror가 자체적으로 관리하므로 필요 없을 수 있음 */
53
+ min-width: 0; /* Flexbox 아이템이 content 때문에 늘어나는 것을 방지 */
54
+ }
55
+ .cm-editor {
56
+ height: 100%; /* EditorView가 부모 div의 높이를 채우도록 */
57
+ }
58
+
59
+ /* Diff 시각화를 위한 기본 스타일 (나중에 데코레이션으로 더 정교하게) */
60
+ .cm-line.cm-deleted-line {
61
+ background-color: #ffeef0; /* 삭제된 배경 */
62
+ }
63
+ .cm-line.cm-inserted-line {
64
+ background-color: #e6ffed; /* 삽입된 줄 배경 */
65
+ }
66
+ .cm-deleted-inline {
67
+ background-color: #ffcccc; /* 인라인 삭제 */
68
+ text-decoration: line-through;
69
+ }
70
+ .cm-inserted-inline {
71
+ background-color: #ccffcc; /* 인라인 삽입 */
72
+ }
73
+ </style>
74
+
75
+ <div class="wrapper">
76
+ <div class="panel asis"></div>
77
+ <nx-splitter></nx-splitter>
78
+ <div class="panel tobe"></div>
79
+ </div>
80
+ `;
61
81
 
62
82
  requestAnimationFrame(() => {
63
- this.#init();
83
+ this.#initCodeMirror();
64
84
  });
65
- };
85
+ }
66
86
 
67
- #init = () => {
68
- // 패널 pre 요소에 대한 참조를 저장
69
- this.#asisPre = this.shadowRoot.querySelector('.asis pre');
70
- this.#tobePre = this.shadowRoot.querySelector('.tobe pre');
87
+ #initCodeMirror = () => {
88
+ this.#asisEditorEl = this.shadowRoot.querySelector('.panel.asis');
89
+ this.#tobeEditorEl = this.shadowRoot.querySelector('.panel.tobe');
71
90
 
72
- // 스크롤 동기화 이벤트 리스너 추가
73
- this.#asisPre.addEventListener('scroll', this.#syncScroll);
74
- this.#tobePre.addEventListener('scroll', this.#syncScroll);
91
+ if (!this.#asisEditorEl || !this.#tobeEditorEl) {
92
+ console.error('CodeMirror panel containers not found!');
93
+ return;
94
+ }
95
+
96
+ // CodeMirror 확장 기본 설정
97
+ const basicExtensions = [
98
+ lineNumbers(), // 줄 번호
99
+ highlightSpecialChars(), // 특수 문자 강조 (예: 공백)
100
+ history(), // 실행 취소/다시 실행
101
+ drawSelection(), // 선택 영역 표시
102
+ dropCursor(), // 드래그 시 커서 위치
103
+ EditorState.allowMultipleSelections.of(true), // 다중 선택 허용
104
+ indentOnInput(), // 입력 시 자동 들여쓰기
105
+ bracketMatching(), // 괄호 짝 맞추기
106
+ highlightActiveLine(), // 현재 줄 강조
107
+ highlightSelectionMatches(), // 선택된 텍스트와 일치하는 부분 강조
108
+ keymap.of([
109
+ ...defaultKeymap, // 기본 키맵 (Enter, Backspace 등)
110
+ ...searchKeymap, // 검색 관련 키맵
111
+ ...historyKeymap, // undo/redo 키맵
112
+ ...lintKeymap, // 린트 관련 키맵
113
+ ...completionKeymap, // 자동 완성 키맵
114
+ indentWithTab // 탭으로 들여쓰기 (필요시)
115
+ ]),
116
+ syntaxHighlighting(defaultHighlightStyle, { fallback: true }), // 기본 구문 하이라이팅
117
+ autocompletion(), // 자동 완성
118
+ // EditorState.readOnly.of(true) // 기본적으로 읽기 전용으로 설정
119
+ ];
120
+
121
+ // asis 에디터 생성 (읽기 전용)
122
+ this.#asisEditorView = new EditorView({
123
+ state: EditorState.create({
124
+ doc: '', // 초기 내용 비워둠
125
+ extensions: [
126
+ basicExtensions,
127
+ javascript(), // JS 언어 모드 (필요에 따라 변경)
128
+ EditorState.readOnly.of(true), // asis는 읽기 전용
129
+ this.#languageCompartment.of(javascript()) // 언어 변경을 위한 compartment
130
+ ]
131
+ }),
132
+ parent: this.#asisEditorEl
133
+ });
134
+
135
+ // tobe 에디터 생성 (읽기 전용, 또는 편집 가능하게 할 수도 있음)
136
+ this.#tobeEditorView = new EditorView({
137
+ state: EditorState.create({
138
+ doc: '', // 초기 내용 비워둠
139
+ extensions: [
140
+ basicExtensions,
141
+ javascript(), // JS 언어 모드 (필요에 따라 변경)
142
+ EditorState.readOnly.of(true), // tobe도 읽기 전용으로 설정 (필요시 false)
143
+ this.#languageCompartment.of(javascript()) // 언어 변경을 위한 compartment
144
+ ]
145
+ }),
146
+ parent: this.#tobeEditorEl
147
+ });
148
+
149
+ // 스크롤 동기화 설정
150
+ this.#setupScrollSync();
75
151
  };
76
152
 
77
- #syncScroll = (e) => {
78
- // 스크롤 이벤트 발생 시 다른 패널의 스크롤 위치를 동기화
79
- // `e.target`이 스크롤 이벤트를 발생시킨 요소
80
- if (e.target === this.#asisPre) {
81
- this.#tobePre.scrollTop = this.#asisPre.scrollTop;
82
- } else if (e.target === this.#tobePre) {
83
- this.#asisPre.scrollTop = this.#tobePre.scrollTop;
84
- }
153
+ #setupScrollSync = () => {
154
+ let scrollingA = false;
155
+ let scrollingB = false;
156
+
157
+ this.#asisEditorView.scrollDOM.addEventListener('scroll', () => {
158
+ if (!scrollingB) {
159
+ scrollingA = true;
160
+ this.#tobeEditorView.scrollDOM.scrollTop = this.#asisEditorView.scrollDOM.scrollTop;
161
+ this.#tobeEditorView.scrollDOM.scrollLeft = this.#asisEditorView.scrollDOM.scrollLeft;
162
+ }
163
+ scrollingB = false;
164
+ });
165
+
166
+ this.#tobeEditorView.scrollDOM.addEventListener('scroll', () => {
167
+ if (!scrollingA) {
168
+ scrollingB = true;
169
+ this.#asisEditorView.scrollDOM.scrollTop = this.#tobeEditorView.scrollDOM.scrollTop;
170
+ this.#asisEditorView.scrollDOM.scrollLeft = this.#tobeEditorView.scrollDOM.scrollLeft;
171
+ }
172
+ scrollingA = false;
173
+ });
85
174
  };
86
175
 
87
- #renderDiff = () => {
176
+ // Diff를 계산하고 에디터에 적용하는 메서드
177
+ #renderDiff = (asisSrc, tobeSrc) => {
88
178
  const dmp = new diff_match_patch();
89
- // 공백 문자를 정규화하여 diff 정확도 향상 (선택 사항)
90
- const cleanedAsisSrc = this.#asisSrc.replace(/\r\n/g, '\n');
91
- const cleanedTobeSrc = this.#tobeSrc.replace(/\r\n/g, '\n');
92
-
93
- const diffs = dmp.diff_main(cleanedAsisSrc, cleanedTobeSrc);
179
+ const diffs = dmp.diff_main(asisSrc, tobeSrc);
94
180
  dmp.diff_cleanupSemantic(diffs);
95
181
 
96
- const asisHtml = [];
97
- const tobeHtml = [];
98
-
99
- for (const [op, text] of diffs) {
100
- // HTML 이스케이프 처리: <, >, & 등 특수문자 변환
101
- const escapedText = text
102
- .replace(/&/g, '&amp;')
103
- .replace(/</g, '&lt;')
104
- .replace(/>/g, '&gt;');
105
-
106
- switch (op) {
107
- case diff_match_patch.DIFF_INSERT:
108
- tobeHtml.push(`<ins>${escapedText}</ins>`);
109
- // 삭제된 내용이 없으므로 asis에는 공백을 유지 (줄 맞춤 고려)
110
- // 만약 단위로 완벽하게 매칭하려면 여기에 다른 로직 필요
111
- break;
112
- case diff_match_patch.DIFF_DELETE:
113
- asisHtml.push(`<del>${escapedText}</del>`);
114
- // 추가된 내용이 없으므로 tobe에는 공백을 유지
115
- break;
116
- case diff_match_patch.DIFF_EQUAL:
117
- asisHtml.push(`<span>${escapedText}</span>`);
118
- tobeHtml.push(`<span>${escapedText}</span>`);
119
- break;
120
- }
121
- }
182
+ // TODO: diffs 결과를 바탕으로 CodeMirror 데코레이션(Decoration)을 생성하고 에디터에 적용하는 로직 구현
183
+ // 부분은 CodeMirror의 Decoration, StateField, ViewPlugin 등을 이해하고 구현해야 합니다.
184
+ // 각 줄에 대한 변경 상태(삽입/삭제/동일)를 판단하고, 인라인 변경(단어 단위)도 처리해야 합니다.
185
+ // 예를 들어:
186
+ // const asisDecorations = [];
187
+ // const tobeDecorations = [];
188
+ // let asisCursor = 0;
189
+ // let tobeCursor = 0;
190
+ // for (const [op, text] of diffs) {
191
+ // const len = text.length;
192
+ // if (op === diff_match_patch.DIFF_INSERT) {
193
+ // // tobe 쪽에 삽입된 부분 데코레이션 추가
194
+ // tobeDecorations.push(Decoration.mark({class: "cm-inserted-inline"}).range(tobeCursor, tobeCursor + len));
195
+ // tobeCursor += len;
196
+ // } else if (op === diff_match_patch.DIFF_DELETE) {
197
+ // // asis 쪽에 삭제된 부분 데코레이션 추가
198
+ // asisDecorations.push(Decoration.mark({class: "cm-deleted-inline"}).range(asisCursor, asisCursor + len));
199
+ // asisCursor += len;
200
+ // } else { // DIFF_EQUAL
201
+ // asisCursor += len;
202
+ // tobeCursor += len;
203
+ // }
204
+ // }
205
+ // 에디터 상태를 업데이트하고 데코레이션을 적용하는 복잡한 로직이 필요합니다.
206
+ // 이 예시에서는 생략하지만, 실제 구현 시 핵심이 됩니다.
122
207
 
123
- // 중요한 부분: `innerHTML` 대신 `textContent`를 사용하면
124
- // `diff-match-patch`의 출력에 따라 HTML 태그가 그대로 보이므로,
125
- // 여기서는 `innerHTML`을 사용하되, 텍스트는 `escapedText`로 처리하여 XSS 방지.
126
- this.#asisPre.innerHTML = asisHtml.join('');
127
- this.#tobePre.innerHTML = tobeHtml.join('');
208
+ // 현재는 단순히 텍스트만 업데이트
209
+ this.#asisEditorView.dispatch({
210
+ changes: { from: 0, to: this.#asisEditorView.state.doc.length, insert: asisSrc }
211
+ });
212
+ this.#tobeEditorView.dispatch({
213
+ changes: { from: 0, to: this.#tobeEditorView.state.doc.length, insert: tobeSrc }
214
+ });
128
215
  };
129
216
 
217
+ initialize = (src1, src2, language = 'javascript') => {
218
+ if (!this.#asisEditorView || !this.#tobeEditorView) {
219
+ console.warn('CodeMirror Editors not initialized yet.');
220
+ return;
221
+ }
130
222
 
131
- initialize = (src1, src2) => {
132
- console.log("Initializing IdeDiff with sources:", src1, src2);
223
+ // 언어 변경 (선택 사항, 필요 시 활성화)
224
+ let langExtension;
225
+ switch(language) {
226
+ case 'javascript':
227
+ langExtension = javascript();
228
+ break;
229
+ // case 'html':
230
+ // langExtension = html(); // @codemirror/lang-html 임포트 필요
231
+ // break;
232
+ // case 'css':
233
+ // langExtension = css(); // @codemirror/lang-css 임포트 필요
234
+ // break;
235
+ default:
236
+ langExtension = javascript(); // 기본값
237
+ }
133
238
 
134
- this.#asisSrc = src1;
135
- this.#tobeSrc = src2;
239
+ // 언어 확장 업데이트
240
+ this.#asisEditorView.dispatch({
241
+ effects: this.#languageCompartment.reconfigure(langExtension)
242
+ });
243
+ this.#tobeEditorView.dispatch({
244
+ effects: this.#languageCompartment.reconfigure(langExtension)
245
+ });
136
246
 
137
- this.#renderDiff();
247
+ this.#renderDiff(src1, src2);
138
248
  };
139
- }
140
249
 
250
+ disconnectedCallback() {
251
+ // 컴포넌트가 DOM에서 제거될 때 에디터 인스턴스 정리
252
+ if (this.#asisEditorView) {
253
+ this.#asisEditorView.destroy();
254
+ }
255
+ if (this.#tobeEditorView) {
256
+ this.#tobeEditorView.destroy();
257
+ }
258
+ }
259
+ }
141
260
  customElements.define("ide-diff", IdeDiff);
@@ -0,0 +1,141 @@
1
+ import ninegrid from "ninegrid2";
2
+ import {diff_match_patch} from 'diff-match-patch';
3
+
4
+ export class IdeDiff extends HTMLElement
5
+ {
6
+ #asisSrc;
7
+ #tobeSrc;
8
+ #asisPre; // <pre> 요소 참조 저장
9
+ #tobePre; // <pre> 요소 참조 저장
10
+
11
+ constructor() {
12
+ super();
13
+ this.attachShadow({ mode: 'open' });
14
+ }
15
+
16
+ connectedCallback() {
17
+ this.shadowRoot.innerHTML = `
18
+ <style>
19
+ /* 전역 또는 컴포넌트 스코프에서 box-sizing: border-box; 적용 권장 */
20
+ * {
21
+ box-sizing: border-box;
22
+ }
23
+ @import "https://cdn.jsdelivr.net/npm/ninegrid@${ninegrid.version}/dist/css/ideDiff.css";
24
+ ${ninegrid.getCustomPath(this,"ideDiff.css")}
25
+
26
+ /* 추가 CSS (필요 시) */
27
+ .wrapper {
28
+ display: flex; /* 스플리터와 함께 작동하도록 flex 컨테이너 */
29
+ height: 100%; /* 부모의 높이를 채우도록 설정 */
30
+ overflow: hidden; /* 내부 스크롤을 위해 overflow 처리 */
31
+ }
32
+ .panel {
33
+ flex: 1; /* 패널들이 남은 공간을 채우도록 */
34
+ overflow: auto; /* 각 패널 내부에서 스크롤 가능하게 */
35
+ position: relative; /* 자식 요소의 absolute 포지셔닝 기준 */
36
+ }
37
+ .panel pre {
38
+ margin: 0; /* pre 태그의 기본 마진 제거 */
39
+ padding: 10px; /* 코드 가독성을 위한 내부 패딩 */
40
+ white-space: pre-wrap; /* 긴 줄 자동 줄바꿈 */
41
+ word-break: break-all; /* 단어가 길어도 강제 줄바꿈 */
42
+ /* white-space: pre-wrap; 을 사용하면 가로 스크롤 없이 줄바꿈됩니다.
43
+ 만약 가로 스크롤을 원하면 white-space: pre; 로 두고 overflow-x: auto; 를 panel에 추가 */
44
+ }
45
+ ins {
46
+ background-color: #d4edda; /* 삽입된 텍스트 배경색 (예시) */
47
+ text-decoration: none; /* 밑줄 제거 (일반적으로) */
48
+ }
49
+ del {
50
+ background-color: #f8d7da; /* 삭제된 텍스트 배경색 (예시) */
51
+ text-decoration: none; /* 취소선 제거 (일반적으로) */
52
+ }
53
+ </style>
54
+
55
+ <div class="wrapper">
56
+ <div class="panel asis"><pre></pre></div>
57
+ <nx-splitter></nx-splitter>
58
+ <div class="panel tobe"><pre></pre></div>
59
+ </div>
60
+ `;
61
+
62
+ requestAnimationFrame(() => {
63
+ this.#init();
64
+ });
65
+ };
66
+
67
+ #init = () => {
68
+ // 패널 pre 요소에 대한 참조를 저장
69
+ this.#asisPre = this.shadowRoot.querySelector('.asis pre');
70
+ this.#tobePre = this.shadowRoot.querySelector('.tobe pre');
71
+
72
+ // 스크롤 동기화 이벤트 리스너 추가
73
+ this.#asisPre.addEventListener('scroll', this.#syncScroll);
74
+ this.#tobePre.addEventListener('scroll', this.#syncScroll);
75
+ };
76
+
77
+ #syncScroll = (e) => {
78
+ // 스크롤 이벤트 발생 시 다른 패널의 스크롤 위치를 동기화
79
+ // `e.target`이 스크롤 이벤트를 발생시킨 요소
80
+ if (e.target === this.#asisPre) {
81
+ this.#tobePre.scrollTop = this.#asisPre.scrollTop;
82
+ } else if (e.target === this.#tobePre) {
83
+ this.#asisPre.scrollTop = this.#tobePre.scrollTop;
84
+ }
85
+ };
86
+
87
+ #renderDiff = () => {
88
+ const dmp = new diff_match_patch();
89
+ // 공백 문자를 정규화하여 diff 정확도 향상 (선택 사항)
90
+ const cleanedAsisSrc = this.#asisSrc.replace(/\r\n/g, '\n');
91
+ const cleanedTobeSrc = this.#tobeSrc.replace(/\r\n/g, '\n');
92
+
93
+ const diffs = dmp.diff_main(cleanedAsisSrc, cleanedTobeSrc);
94
+ dmp.diff_cleanupSemantic(diffs);
95
+
96
+ const asisHtml = [];
97
+ const tobeHtml = [];
98
+
99
+ for (const [op, text] of diffs) {
100
+ // HTML 이스케이프 처리: <, >, & 등 특수문자 변환
101
+ const escapedText = text
102
+ .replace(/&/g, '&amp;')
103
+ .replace(/</g, '&lt;')
104
+ .replace(/>/g, '&gt;');
105
+
106
+ switch (op) {
107
+ case diff_match_patch.DIFF_INSERT:
108
+ tobeHtml.push(`<ins>${escapedText}</ins>`);
109
+ // 삭제된 내용이 없으므로 asis에는 공백을 유지 (줄 맞춤 고려)
110
+ // 만약 줄 단위로 완벽하게 매칭하려면 여기에 다른 로직 필요
111
+ break;
112
+ case diff_match_patch.DIFF_DELETE:
113
+ asisHtml.push(`<del>${escapedText}</del>`);
114
+ // 추가된 내용이 없으므로 tobe에는 공백을 유지
115
+ break;
116
+ case diff_match_patch.DIFF_EQUAL:
117
+ asisHtml.push(`<span>${escapedText}</span>`);
118
+ tobeHtml.push(`<span>${escapedText}</span>`);
119
+ break;
120
+ }
121
+ }
122
+
123
+ // 중요한 부분: `innerHTML` 대신 `textContent`를 사용하면
124
+ // `diff-match-patch`의 출력에 따라 HTML 태그가 그대로 보이므로,
125
+ // 여기서는 `innerHTML`을 사용하되, 텍스트는 `escapedText`로 처리하여 XSS 방지.
126
+ this.#asisPre.innerHTML = asisHtml.join('');
127
+ this.#tobePre.innerHTML = tobeHtml.join('');
128
+ };
129
+
130
+
131
+ initialize = (src1, src2) => {
132
+ console.log("Initializing IdeDiff with sources:", src1, src2);
133
+
134
+ this.#asisSrc = src1;
135
+ this.#tobeSrc = src2;
136
+
137
+ this.#renderDiff();
138
+ };
139
+ }
140
+
141
+ customElements.define("ide-diff", IdeDiff);