ide-assi 0.444.0 → 0.446.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.
- package/dist/bundle.cjs.js +912 -614
- package/dist/bundle.esm.js +912 -614
- package/dist/components/ideAssi.js +46 -3
- package/package.json +1 -1
- package/src/components/ideAssi.js +46 -3
- package/src/components/ideDiff.js.bak +0 -141
- package/src/components/ideDiff.js.bak2 +0 -725
|
@@ -1,725 +0,0 @@
|
|
|
1
|
-
import ninegrid from "ninegrid2";
|
|
2
|
-
|
|
3
|
-
// CodeMirror 6 핵심 및 확장 임포트 (변동 없음)
|
|
4
|
-
import {
|
|
5
|
-
EditorView, lineNumbers, highlightSpecialChars, drawSelection,
|
|
6
|
-
dropCursor, keymap, highlightActiveLine, highlightActiveLineGutter,
|
|
7
|
-
Decoration
|
|
8
|
-
} from "@codemirror/view";
|
|
9
|
-
import {
|
|
10
|
-
EditorState, Compartment, StateField,
|
|
11
|
-
RangeSetBuilder,
|
|
12
|
-
StateEffect,
|
|
13
|
-
Text
|
|
14
|
-
} from "@codemirror/state";
|
|
15
|
-
import { history, historyKeymap, indentWithTab } from "@codemirror/commands";
|
|
16
|
-
import { defaultKeymap, selectAll } from "@codemirror/commands";
|
|
17
|
-
import { searchKeymap, highlightSelectionMatches } from "@codemirror/search";
|
|
18
|
-
import { bracketMatching } from "@codemirror/language";
|
|
19
|
-
import { javascript } from "@codemirror/lang-javascript";
|
|
20
|
-
import { indentOnInput, syntaxHighlighting, defaultHighlightStyle } from "@codemirror/language";
|
|
21
|
-
import { lintKeymap } from "@codemirror/lint";
|
|
22
|
-
import { autocompletion, completionKeymap } from "@codemirror/autocomplete";
|
|
23
|
-
|
|
24
|
-
// Diff 로직을 위해 diff-match-patch 사용 (변동 없음)
|
|
25
|
-
import { diff_match_patch } from 'diff-match-patch';
|
|
26
|
-
|
|
27
|
-
// Diff 데코레이션을 위한 StateField 정의 (변동 없음)
|
|
28
|
-
const asisDiffDecorations = StateField.define({
|
|
29
|
-
create() { return Decoration.none; },
|
|
30
|
-
update(decorations, tr) {
|
|
31
|
-
return tr.effects.reduce((currentDecos, effect) => {
|
|
32
|
-
if (effect.is(setAsisDecorationsEffect)) {
|
|
33
|
-
return effect.value;
|
|
34
|
-
}
|
|
35
|
-
return currentDecos;
|
|
36
|
-
}, decorations);
|
|
37
|
-
},
|
|
38
|
-
provide: f => EditorView.decorations.from(f)
|
|
39
|
-
});
|
|
40
|
-
|
|
41
|
-
const tobeDiffDecorations = StateField.define({
|
|
42
|
-
create() { return Decoration.none; },
|
|
43
|
-
update(decorations, tr) {
|
|
44
|
-
return tr.effects.reduce((currentDecos, effect) => {
|
|
45
|
-
if (effect.is(setTobeDecorationsEffect)) {
|
|
46
|
-
return effect.value;
|
|
47
|
-
}
|
|
48
|
-
return currentDecos;
|
|
49
|
-
}, decorations);
|
|
50
|
-
},
|
|
51
|
-
provide: f => EditorView.decorations.from(f)
|
|
52
|
-
});
|
|
53
|
-
|
|
54
|
-
const setAsisDecorationsEffect = StateEffect.define();
|
|
55
|
-
const setTobeDecorationsEffect = StateEffect.define();
|
|
56
|
-
|
|
57
|
-
export class IdeDiff extends HTMLElement {
|
|
58
|
-
|
|
59
|
-
#asisEditorView;
|
|
60
|
-
#tobeEditorView;
|
|
61
|
-
#asisEditorEl;
|
|
62
|
-
#tobeEditorEl;
|
|
63
|
-
|
|
64
|
-
#languageCompartment = new Compartment();
|
|
65
|
-
|
|
66
|
-
#isScrollSyncActive = false;
|
|
67
|
-
_asisScrollHandler = null;
|
|
68
|
-
_tobeScrollHandler = null;
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
constructor() {
|
|
72
|
-
super();
|
|
73
|
-
this.attachShadow({ mode: 'open' });
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
connectedCallback() {
|
|
77
|
-
this.shadowRoot.innerHTML = `
|
|
78
|
-
<style>
|
|
79
|
-
/* ninegrid CSS 및 필요한 기본 스타일 (변동 없음) */
|
|
80
|
-
@import "https://cdn.jsdelivr.net/npm/ninegrid@${ninegrid.version}/dist/css/ideDiff.css";
|
|
81
|
-
${ninegrid.getCustomPath(this, "ideDiff.css")}
|
|
82
|
-
</style>
|
|
83
|
-
|
|
84
|
-
<div class="wrapper">
|
|
85
|
-
<div class="panel asis"></div>
|
|
86
|
-
<nx-splitter></nx-splitter>
|
|
87
|
-
<div class="panel tobe"></div>
|
|
88
|
-
</div>
|
|
89
|
-
`;
|
|
90
|
-
|
|
91
|
-
requestAnimationFrame(() => {
|
|
92
|
-
this.#initCodeMirror();
|
|
93
|
-
const src1 = `
|
|
94
|
-
import React, { useRef, useEffect } from "react" adfa;
|
|
95
|
-
import { api, ai } from "ide-assi";
|
|
96
|
-
import ninegrid from "ninegrid2";
|
|
97
|
-
|
|
98
|
-
const DocManager = () => {
|
|
99
|
-
const tabRef = useRef(null);
|
|
100
|
-
const gridRef = useRef(null);
|
|
101
|
-
|
|
102
|
-
const selectList = async (params) => {
|
|
103
|
-
if (!gridRef.current) return;
|
|
104
|
-
gridRef.current.classList.add("loading");
|
|
105
|
-
api.post(\`/api/tmpl-a/doc-manager/selectList\`, params).then((res) => {
|
|
106
|
-
gridRef.current.data.source = res.list;
|
|
107
|
-
});
|
|
108
|
-
};
|
|
109
|
-
|
|
110
|
-
const handleNaturalLanguageSearch = async () => {
|
|
111
|
-
const searchTextElement = ninegrid.querySelector("#searchText", tabRef.current);
|
|
112
|
-
const searchText = searchTextElement ? searchTextElement.value : "";
|
|
113
|
-
|
|
114
|
-
if (!gridRef.current) return;
|
|
115
|
-
gridRef.current.classList.add("loading");
|
|
116
|
-
|
|
117
|
-
let params = {};
|
|
118
|
-
if (searchText) {
|
|
119
|
-
params = {
|
|
120
|
-
_whereClause: await ai.generateWhereCause(
|
|
121
|
-
"tmpla/DocManagerMapper.xml",
|
|
122
|
-
"selectList",
|
|
123
|
-
searchText,
|
|
124
|
-
import.meta.env.VITE_GEMINI_API_KEY
|
|
125
|
-
),
|
|
126
|
-
};
|
|
127
|
-
}
|
|
128
|
-
selectList(params);
|
|
129
|
-
};
|
|
130
|
-
|
|
131
|
-
const handleClassicSearch = () => {
|
|
132
|
-
if (!gridRef.current) return;
|
|
133
|
-
gridRef.current.classList.add("loading");
|
|
134
|
-
|
|
135
|
-
const form2Element = ninegrid.querySelector(".form2", tabRef.current);
|
|
136
|
-
const params = form2Element ? form2Element.getData() : {};
|
|
137
|
-
selectList(params);
|
|
138
|
-
};
|
|
139
|
-
|
|
140
|
-
useEffect(() => {
|
|
141
|
-
selectList({});
|
|
142
|
-
|
|
143
|
-
const searchTextElement = ninegrid.querySelector("#searchText", tabRef.current);
|
|
144
|
-
const searchButton = ninegrid.querySelector(".search", tabRef.current);
|
|
145
|
-
|
|
146
|
-
const handleKeyDown = (e) => {
|
|
147
|
-
if (e.key === "Enter" && !e.isComposing) {
|
|
148
|
-
handleNaturalLanguageSearch();
|
|
149
|
-
}
|
|
150
|
-
};
|
|
151
|
-
|
|
152
|
-
const handleClick = () => {
|
|
153
|
-
handleClassicSearch();
|
|
154
|
-
};
|
|
155
|
-
|
|
156
|
-
if (searchTextElement) {
|
|
157
|
-
searchTextElement.addEventListener("keydown", handleKeyDown);
|
|
158
|
-
}
|
|
159
|
-
if (searchButton) {
|
|
160
|
-
searchButton.addEventListener("click", handleClick);
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
return () => {
|
|
164
|
-
if (searchTextElement) {
|
|
165
|
-
searchTextElement.removeEventListener("keydown", handleKeyDown);
|
|
166
|
-
}
|
|
167
|
-
if (searchButton) {
|
|
168
|
-
searchButton.removeEventListener("click", handleClick);
|
|
169
|
-
}
|
|
170
|
-
};
|
|
171
|
-
}, []);
|
|
172
|
-
|
|
173
|
-
return (
|
|
174
|
-
<div className="wrapper">
|
|
175
|
-
<nx-collapse target="nx-tab" className="search-collapse"></nx-collapse>
|
|
176
|
-
<div className="list-wrapper">
|
|
177
|
-
<nx-tab theme="theme-3" ref={tabRef}>
|
|
178
|
-
<nx-tab-page caption="자연어 검색">
|
|
179
|
-
<nx-form className="form1">
|
|
180
|
-
<input
|
|
181
|
-
type="text"
|
|
182
|
-
id="searchText"
|
|
183
|
-
name="searchText"
|
|
184
|
-
placeholder="자연어 검색어를 입력하세요 (ex: 작성자가 홍길동인 데이타를 찾아줘)"
|
|
185
|
-
/>
|
|
186
|
-
</nx-form>
|
|
187
|
-
</nx-tab-page>
|
|
188
|
-
<nx-tab-page caption="클래식 검색">
|
|
189
|
-
<nx-form className="form2">
|
|
190
|
-
<label>문서명: <input type="text" name="docNm" /></label>
|
|
191
|
-
<label>매출액:
|
|
192
|
-
<input type="number" name="minAmt" placeholder="최소" /> ~
|
|
193
|
-
<input type="number" name="maxAmt" placeholder="최대" />
|
|
194
|
-
</label>
|
|
195
|
-
</nx-form>
|
|
196
|
-
<button className="search">검색</button>
|
|
197
|
-
</nx-tab-page>
|
|
198
|
-
</nx-tab>
|
|
199
|
-
|
|
200
|
-
<div className="grid-wrapper">
|
|
201
|
-
<nine-grid
|
|
202
|
-
ref={gridRef}
|
|
203
|
-
caption="문서관리"
|
|
204
|
-
select-type="row"
|
|
205
|
-
show-title-bar="true"
|
|
206
|
-
show-menu-icon="true"
|
|
207
|
-
show-status-bar="true"
|
|
208
|
-
enable-fixed-col="true"
|
|
209
|
-
row-resizable="false"
|
|
210
|
-
col-movable="true"
|
|
211
|
-
>
|
|
212
|
-
<table>
|
|
213
|
-
<colgroup>
|
|
214
|
-
<col width="50" fixed="left" background-color="gray" />
|
|
215
|
-
<col width="100" />
|
|
216
|
-
<col width="100" />
|
|
217
|
-
<col width="200" />
|
|
218
|
-
<col width="120" />
|
|
219
|
-
<col width="100" />
|
|
220
|
-
<col width="150" />
|
|
221
|
-
<col width="150" />
|
|
222
|
-
</colgroup>
|
|
223
|
-
<thead>
|
|
224
|
-
<tr>
|
|
225
|
-
<th>No.</th>
|
|
226
|
-
<th>최종수정자</th>
|
|
227
|
-
<th>문서ID</th>
|
|
228
|
-
<th>문서명</th>
|
|
229
|
-
<th>매출액</th>
|
|
230
|
-
<th>최초등록자</th>
|
|
231
|
-
<th>최초등록일</th>
|
|
232
|
-
<th>최종수정일</th>
|
|
233
|
-
</tr>
|
|
234
|
-
</thead>
|
|
235
|
-
<tbody>
|
|
236
|
-
<tr>
|
|
237
|
-
<th><ng-row-indicator /></th>
|
|
238
|
-
<td data-bind="updateUser" text-align="center"></td>
|
|
239
|
-
<td data-bind="docId" text-align="center"></td>
|
|
240
|
-
<td data-bind="docNm" text-align="left"></td>
|
|
241
|
-
<td
|
|
242
|
-
data-bind="amt"
|
|
243
|
-
data-expr="data.amt.toLocaleString()"
|
|
244
|
-
text-align="right"
|
|
245
|
-
show-icon="true"
|
|
246
|
-
icon-type="sphere"
|
|
247
|
-
icon-color="data.amt >= 2000 ? 'red' : 'gray'"
|
|
248
|
-
></td>
|
|
249
|
-
<td data-bind="insertUser" text-align="center"></td>
|
|
250
|
-
<td data-bind="insertDt" text-align="center"></td>
|
|
251
|
-
<td data-bind="updateDt" text-align="center"></td>
|
|
252
|
-
</tr>
|
|
253
|
-
</tbody>
|
|
254
|
-
</table>
|
|
255
|
-
</nine-grid>
|
|
256
|
-
</div>
|
|
257
|
-
</div>
|
|
258
|
-
</div>
|
|
259
|
-
);
|
|
260
|
-
};
|
|
261
|
-
|
|
262
|
-
export default DocManager;
|
|
263
|
-
`;
|
|
264
|
-
|
|
265
|
-
const src2 = `
|
|
266
|
-
import React, { useRef, useEffect } from "react";
|
|
267
|
-
import { api, ai } from "ide-assi";
|
|
268
|
-
import ninegrid from "ninegrid2";
|
|
269
|
-
|
|
270
|
-
const DocManager = () => {
|
|
271
|
-
const tabRef = useRef(null);
|
|
272
|
-
const gridRef = useRef(null);
|
|
273
|
-
|
|
274
|
-
const selectList = async (params) => {
|
|
275
|
-
if (!gridRef.current) return;
|
|
276
|
-
gridRef.current.classList.add("loading");
|
|
277
|
-
api.post(\`/api/tmpl-a/doc-manager/selectList\`, params).then((res) => {
|
|
278
|
-
gridRef.current.data.source = res.list;
|
|
279
|
-
});
|
|
280
|
-
};
|
|
281
|
-
|
|
282
|
-
const handleNaturalLanguageSearch = async () => {
|
|
283
|
-
const searchTextElement = ninegrid.querySelector("#searchText", tabRef.current);
|
|
284
|
-
const searchText = searchTextElement ? searchTextElement.value : "";
|
|
285
|
-
|
|
286
|
-
if (!gridRef.current) return;
|
|
287
|
-
gridRef.current.classList.add("loading");
|
|
288
|
-
|
|
289
|
-
let params = {};
|
|
290
|
-
if (searchText) {
|
|
291
|
-
params = {
|
|
292
|
-
_whereClause: await ai.generateWhereCause(
|
|
293
|
-
"tmpla/DocManagerMapper.xml",
|
|
294
|
-
"selectList",
|
|
295
|
-
searchText,
|
|
296
|
-
import.meta.env.VITE_GEMINI_API_KEY
|
|
297
|
-
),
|
|
298
|
-
};
|
|
299
|
-
}
|
|
300
|
-
selectList(params);
|
|
301
|
-
};
|
|
302
|
-
|
|
303
|
-
const handleClassicSearch = () => {
|
|
304
|
-
if (!gridRef.current) return;
|
|
305
|
-
gridRef.current.classList.add("loading");
|
|
306
|
-
|
|
307
|
-
const form2Element = ninegrid.querySelector(".form2", tabRef.current);
|
|
308
|
-
const params = form2Element ? form2Element.getData() : {};
|
|
309
|
-
selectList(params);
|
|
310
|
-
};
|
|
311
|
-
|
|
312
|
-
useEffect(() => {
|
|
313
|
-
selectList({});
|
|
314
|
-
|
|
315
|
-
const searchTextElement = ninegrid.querySelector("#searchText", tabRef.current);
|
|
316
|
-
const searchButton = ninegrid.querySelector(".search", tabRef.current);
|
|
317
|
-
|
|
318
|
-
const handleKeyDown = (e) => {
|
|
319
|
-
if (e.key === "Enter" && !e.isComposing) {
|
|
320
|
-
handleNaturalLanguageSearch();
|
|
321
|
-
}
|
|
322
|
-
};
|
|
323
|
-
|
|
324
|
-
const handleClick = () => {
|
|
325
|
-
handleClassicSearch();
|
|
326
|
-
};
|
|
327
|
-
|
|
328
|
-
if (searchTextElement) {
|
|
329
|
-
searchTextElement.addEventListener("keydown", handleKeyDown);
|
|
330
|
-
}
|
|
331
|
-
if (searchButton) {
|
|
332
|
-
searchButton.addEventListener("click", handleClick);
|
|
333
|
-
}
|
|
334
|
-
|
|
335
|
-
return () => {
|
|
336
|
-
if (searchTextElement) {
|
|
337
|
-
searchTextElement.removeEventListener("keydown", handleKeyDown);
|
|
338
|
-
}
|
|
339
|
-
if (searchButton) {
|
|
340
|
-
searchButton.removeEventListener("click", handleClick);
|
|
341
|
-
}
|
|
342
|
-
};
|
|
343
|
-
}, []);
|
|
344
|
-
|
|
345
|
-
return (
|
|
346
|
-
<div className="wrapper">
|
|
347
|
-
<nx-collapse target="nx-tab" className="search-collapse"></nx-collapse>
|
|
348
|
-
<div className="list-wrapper">
|
|
349
|
-
<nx-tab theme="theme-3" ref={tabRef}>
|
|
350
|
-
<nx-tab-page caption="자연어 검색">
|
|
351
|
-
<nx-form className="form1">
|
|
352
|
-
<input
|
|
353
|
-
type="text"
|
|
354
|
-
id="searchText"
|
|
355
|
-
name="searchText"
|
|
356
|
-
placeholder="자연어 검색어를 입력하세요 (ex: 작성자가 홍길동인 데이타를 찾아줘)"
|
|
357
|
-
/>
|
|
358
|
-
</nx-form>
|
|
359
|
-
</nx-tab-page>
|
|
360
|
-
<nx-tab-page caption="클래식 검색">
|
|
361
|
-
<nx-form className="form2">
|
|
362
|
-
<label>문서명: <input type="text" name="docNm" /></label>
|
|
363
|
-
<label>매출액:
|
|
364
|
-
<input type="number" name="minAmt" placeholder="최소" /> ~
|
|
365
|
-
<input type="number" name="maxAmt" placeholder="최대" />
|
|
366
|
-
</label>
|
|
367
|
-
</nx-form>
|
|
368
|
-
<button className="search">검색</button>
|
|
369
|
-
</nx-tab-page>
|
|
370
|
-
</nx-tab>
|
|
371
|
-
|
|
372
|
-
<div className="grid-wrapper">
|
|
373
|
-
<nine-grid
|
|
374
|
-
ref={gridRef}
|
|
375
|
-
caption="매출 문서 관리"
|
|
376
|
-
select-type="row"
|
|
377
|
-
show-title-bar="true"
|
|
378
|
-
show-menu-icon="true"
|
|
379
|
-
show-status-bar="true"
|
|
380
|
-
enable-fixed-col="true"
|
|
381
|
-
row-resizable="false"
|
|
382
|
-
col-movable="true"
|
|
383
|
-
>
|
|
384
|
-
<table>
|
|
385
|
-
<colgroup>
|
|
386
|
-
<col width="50" fixed="left" background-color="gray" />
|
|
387
|
-
<col width="100" />
|
|
388
|
-
<col width="120" />
|
|
389
|
-
<col width="100" />
|
|
390
|
-
<col width="200" />
|
|
391
|
-
<col width="100" />
|
|
392
|
-
<col width="150" />
|
|
393
|
-
<col width="150" />
|
|
394
|
-
</colgroup>
|
|
395
|
-
<thead>
|
|
396
|
-
<tr>
|
|
397
|
-
<th>No.</th>
|
|
398
|
-
<th>문서ID</th>
|
|
399
|
-
<th>매출액</th>
|
|
400
|
-
<th>최종수정자</th>
|
|
401
|
-
<th>문서명</th>
|
|
402
|
-
<th>최초등록자</th>
|
|
403
|
-
<th>최초등록일</th>
|
|
404
|
-
<th>최종수정일</th>
|
|
405
|
-
</tr>
|
|
406
|
-
</thead>
|
|
407
|
-
<tbody>
|
|
408
|
-
<tr>
|
|
409
|
-
<th><ng-row-indicator /></th>
|
|
410
|
-
<td data-bind="docId" text-align="center"></td>
|
|
411
|
-
<td
|
|
412
|
-
data-bind="amt"
|
|
413
|
-
data-expr="data.amt.toLocaleString()"
|
|
414
|
-
text-align="right"
|
|
415
|
-
show-icon="true"
|
|
416
|
-
icon-type="sphere"
|
|
417
|
-
icon-color="data.amt >= 2000 ? 'red' : 'gray'"
|
|
418
|
-
></td>
|
|
419
|
-
<td data-bind="updateUser" text-align="center"></td>
|
|
420
|
-
<td data-bind="docNm" text-align="left"></td>
|
|
421
|
-
<td data-bind="insertUser" text-align="center"></td>
|
|
422
|
-
<td data-bind="insertDt" text-align="center"></td>
|
|
423
|
-
<td data-bind="updateDt" text-align="center"></td>
|
|
424
|
-
</tr>
|
|
425
|
-
</tbody>
|
|
426
|
-
</table>
|
|
427
|
-
</nine-grid>
|
|
428
|
-
</div>
|
|
429
|
-
</div>
|
|
430
|
-
</div>
|
|
431
|
-
);
|
|
432
|
-
};
|
|
433
|
-
|
|
434
|
-
export default DocManager;
|
|
435
|
-
`;
|
|
436
|
-
this.initialize(src1, src2, 'javascript');
|
|
437
|
-
});
|
|
438
|
-
}
|
|
439
|
-
|
|
440
|
-
#initCodeMirror = () => {
|
|
441
|
-
this.#asisEditorEl = this.shadowRoot.querySelector('.panel.asis');
|
|
442
|
-
this.#tobeEditorEl = this.shadowRoot.querySelector('.panel.tobe');
|
|
443
|
-
|
|
444
|
-
if (!this.#asisEditorEl || !this.#tobeEditorEl) {
|
|
445
|
-
console.error('CodeMirror panel containers not found!');
|
|
446
|
-
return;
|
|
447
|
-
}
|
|
448
|
-
|
|
449
|
-
const basicExtensions = [
|
|
450
|
-
lineNumbers(),
|
|
451
|
-
highlightSpecialChars(),
|
|
452
|
-
history(),
|
|
453
|
-
drawSelection(),
|
|
454
|
-
dropCursor(),
|
|
455
|
-
EditorState.allowMultipleSelections.of(true),
|
|
456
|
-
indentOnInput(),
|
|
457
|
-
bracketMatching(),
|
|
458
|
-
highlightActiveLine(),
|
|
459
|
-
highlightSelectionMatches(),
|
|
460
|
-
keymap.of([
|
|
461
|
-
...defaultKeymap,
|
|
462
|
-
...searchKeymap,
|
|
463
|
-
...historyKeymap,
|
|
464
|
-
...lintKeymap,
|
|
465
|
-
...completionKeymap,
|
|
466
|
-
indentWithTab,
|
|
467
|
-
selectAll
|
|
468
|
-
]),
|
|
469
|
-
syntaxHighlighting(defaultHighlightStyle, { fallback: true }),
|
|
470
|
-
autocompletion(),
|
|
471
|
-
];
|
|
472
|
-
|
|
473
|
-
// ASIS 에디터 뷰 초기화
|
|
474
|
-
this.#asisEditorView = new EditorView({
|
|
475
|
-
state: EditorState.create({
|
|
476
|
-
doc: '',
|
|
477
|
-
extensions: [
|
|
478
|
-
basicExtensions,
|
|
479
|
-
javascript(),
|
|
480
|
-
EditorState.readOnly.of(true),
|
|
481
|
-
this.#languageCompartment.of(javascript()),
|
|
482
|
-
asisDiffDecorations,
|
|
483
|
-
// ⭐️ updateListener는 유지: 뷰가 DOM에 완전히 렌더링될 때까지 기다림
|
|
484
|
-
EditorView.updateListener.of((update) => {
|
|
485
|
-
if (update.view.contentDOM.firstChild && !update.view._initialAsisContentLoaded) {
|
|
486
|
-
update.view._initialAsisContentLoaded = true;
|
|
487
|
-
console.log("CodeMirror ASIS view is ready for initial content.");
|
|
488
|
-
}
|
|
489
|
-
})
|
|
490
|
-
]
|
|
491
|
-
}),
|
|
492
|
-
parent: this.#asisEditorEl
|
|
493
|
-
});
|
|
494
|
-
|
|
495
|
-
// TOBE 에디터 뷰 초기화
|
|
496
|
-
this.#tobeEditorView = new EditorView({
|
|
497
|
-
state: EditorState.create({
|
|
498
|
-
doc: '',
|
|
499
|
-
extensions: [
|
|
500
|
-
basicExtensions,
|
|
501
|
-
javascript(),
|
|
502
|
-
EditorState.readOnly.of(true),
|
|
503
|
-
this.#languageCompartment.of(javascript()),
|
|
504
|
-
tobeDiffDecorations,
|
|
505
|
-
// ⭐️ updateListener는 유지: 뷰가 DOM에 완전히 렌더링될 때까지 기다림
|
|
506
|
-
EditorView.updateListener.of((update) => {
|
|
507
|
-
if (update.view.contentDOM.firstChild && !update.view._initialTobeContentLoaded) {
|
|
508
|
-
update.view._initialTobeContentLoaded = true;
|
|
509
|
-
console.log("CodeMirror TOBE view is ready for initial content.");
|
|
510
|
-
}
|
|
511
|
-
})
|
|
512
|
-
]
|
|
513
|
-
}),
|
|
514
|
-
parent: this.#tobeEditorEl
|
|
515
|
-
});
|
|
516
|
-
|
|
517
|
-
this.#setupScrollSync();
|
|
518
|
-
};
|
|
519
|
-
|
|
520
|
-
#setupScrollSync = () => {
|
|
521
|
-
if (this._asisScrollHandler) {
|
|
522
|
-
this.#asisEditorView.scrollDOM.removeEventListener('scroll', this._asisScrollHandler);
|
|
523
|
-
this.#tobeEditorView.scrollDOM.removeEventListener('scroll', this._tobeScrollHandler);
|
|
524
|
-
}
|
|
525
|
-
|
|
526
|
-
let scrollingA = false;
|
|
527
|
-
let scrollingB = false;
|
|
528
|
-
|
|
529
|
-
this._asisScrollHandler = () => {
|
|
530
|
-
if (!this.#isScrollSyncActive) return;
|
|
531
|
-
|
|
532
|
-
if (!scrollingB) {
|
|
533
|
-
scrollingA = true;
|
|
534
|
-
this.#tobeEditorView.scrollDOM.scrollTop = this.#asisEditorView.scrollDOM.scrollTop;
|
|
535
|
-
this.#tobeEditorView.scrollDOM.scrollLeft = this.#asisEditorView.scrollDOM.scrollLeft;
|
|
536
|
-
}
|
|
537
|
-
scrollingB = false;
|
|
538
|
-
};
|
|
539
|
-
|
|
540
|
-
this._tobeScrollHandler = () => {
|
|
541
|
-
if (!this.#isScrollSyncActive) return;
|
|
542
|
-
|
|
543
|
-
if (!scrollingA) {
|
|
544
|
-
scrollingB = true;
|
|
545
|
-
this.#asisEditorView.scrollDOM.scrollTop = this.#tobeEditorView.scrollDOM.scrollTop;
|
|
546
|
-
this.#asisEditorView.scrollDOM.scrollLeft = this.#tobeEditorView.scrollDOM.scrollLeft;
|
|
547
|
-
}
|
|
548
|
-
scrollingA = false;
|
|
549
|
-
};
|
|
550
|
-
|
|
551
|
-
this.#asisEditorView.scrollDOM.addEventListener('scroll', this._asisScrollHandler);
|
|
552
|
-
this.#tobeEditorView.scrollDOM.addEventListener('scroll', this._tobeScrollHandler);
|
|
553
|
-
};
|
|
554
|
-
|
|
555
|
-
// IdeDiff 클래스 내부
|
|
556
|
-
#applyDiffDecorations = (asisSrc, tobeSrc) => {
|
|
557
|
-
const dmp = new diff_match_patch();
|
|
558
|
-
const diffs = dmp.diff_main(asisSrc, tobeSrc);
|
|
559
|
-
dmp.diff_cleanupSemantic(diffs);
|
|
560
|
-
|
|
561
|
-
const asisLineDecos = [];
|
|
562
|
-
const tobeLineDecos = [];
|
|
563
|
-
|
|
564
|
-
let asisCursor = 0;
|
|
565
|
-
let tobeCursor = 0;
|
|
566
|
-
|
|
567
|
-
// 중요: 새로운 문서 내용을 기반으로 임시 Doc 객체를 생성합니다.
|
|
568
|
-
// 이 Doc 객체를 사용하여 lineAt()을 호출합니다.
|
|
569
|
-
const asisDoc = Text.of(asisSrc.split('\n'));
|
|
570
|
-
const tobeDoc = Text.of(tobeSrc.split('\n'));
|
|
571
|
-
|
|
572
|
-
for (const [op, text] of diffs) {
|
|
573
|
-
const len = text.length;
|
|
574
|
-
|
|
575
|
-
switch (op) {
|
|
576
|
-
case diff_match_patch.DIFF_INSERT: // Added text
|
|
577
|
-
let currentTobeLineOffset = tobeCursor;
|
|
578
|
-
const tobeLines = text.split('\n');
|
|
579
|
-
for (let i = 0; i < tobeLines.length; i++) {
|
|
580
|
-
// 줄의 시작 오프셋이 tobeDoc의 길이를 넘지 않는지 확인
|
|
581
|
-
if (currentTobeLineOffset < tobeDoc.length) {
|
|
582
|
-
const line = tobeDoc.lineAt(currentTobeLineOffset);
|
|
583
|
-
tobeLineDecos.push({
|
|
584
|
-
from: line.from,
|
|
585
|
-
to: line.to,
|
|
586
|
-
deco: Decoration.line({ class: "cm-inserted-line-bg" })
|
|
587
|
-
});
|
|
588
|
-
}
|
|
589
|
-
// 다음 줄의 시작 오프셋 계산 (개행 문자 고려)
|
|
590
|
-
currentTobeLineOffset += tobeLines[i].length + (i < tobeLines.length - 1 ? 1 : 0);
|
|
591
|
-
// 마지막 줄이 비어 있고 개행으로 끝나지 않으면 루프 종료 (lineAt 오류 방지)
|
|
592
|
-
if (i === tobeLines.length - 1 && tobeLines[i].length === 0 && text.endsWith('\n') === false) {
|
|
593
|
-
break;
|
|
594
|
-
}
|
|
595
|
-
}
|
|
596
|
-
tobeCursor += len;
|
|
597
|
-
break;
|
|
598
|
-
|
|
599
|
-
case diff_match_patch.DIFF_DELETE: // Deleted text
|
|
600
|
-
let currentAsisLineOffset = asisCursor;
|
|
601
|
-
const asisLines = text.split('\n');
|
|
602
|
-
for (let i = 0; i < asisLines.length; i++) {
|
|
603
|
-
// 줄의 시작 오프셋이 asisDoc의 길이를 넘지 않는지 확인
|
|
604
|
-
if (currentAsisLineOffset < asisDoc.length) {
|
|
605
|
-
const line = asisDoc.lineAt(currentAsisLineOffset);
|
|
606
|
-
asisLineDecos.push({
|
|
607
|
-
from: line.from,
|
|
608
|
-
to: line.to,
|
|
609
|
-
deco: Decoration.line({ class: "cm-deleted-line-bg" })
|
|
610
|
-
});
|
|
611
|
-
}
|
|
612
|
-
// 다음 줄의 시작 오프셋 계산 (개행 문자 고려)
|
|
613
|
-
currentAsisLineOffset += asisLines[i].length + (i < asisLines.length - 1 ? 1 : 0);
|
|
614
|
-
// 마지막 줄이 비어 있고 개행으로 끝나지 않으면 루프 종료 (lineAt 오류 방지)
|
|
615
|
-
if (i === asisLines.length - 1 && asisLines[i].length === 0 && text.endsWith('\n') === false) {
|
|
616
|
-
break;
|
|
617
|
-
}
|
|
618
|
-
}
|
|
619
|
-
asisCursor += len;
|
|
620
|
-
break;
|
|
621
|
-
|
|
622
|
-
case diff_match_patch.DIFF_EQUAL: // Unchanged text
|
|
623
|
-
asisCursor += len;
|
|
624
|
-
tobeCursor += len;
|
|
625
|
-
break;
|
|
626
|
-
}
|
|
627
|
-
}
|
|
628
|
-
|
|
629
|
-
// 데코레이션 중복 및 겹침 방지를 위해 정렬
|
|
630
|
-
asisLineDecos.sort((a, b) => a.from - b.from);
|
|
631
|
-
tobeLineDecos.sort((a, b) => a.from - b.from);
|
|
632
|
-
|
|
633
|
-
// 라인 데코레이션 빌더 생성
|
|
634
|
-
const asisLineBuilder = new RangeSetBuilder();
|
|
635
|
-
for (const { from, to, deco } of asisLineDecos) {
|
|
636
|
-
asisLineBuilder.add(from, to, deco);
|
|
637
|
-
}
|
|
638
|
-
const finalAsisDecorations = asisLineBuilder.finish(); // ⭐️ .update({ add: ... }) 부분 제거
|
|
639
|
-
|
|
640
|
-
const tobeLineBuilder = new RangeSetBuilder();
|
|
641
|
-
for (const { from, to, deco } of tobeLineDecos) {
|
|
642
|
-
tobeLineBuilder.add(from, to, deco);
|
|
643
|
-
}
|
|
644
|
-
const finalTobeDecorations = tobeLineBuilder.finish(); // ⭐️ .update({ add: ... }) 부분 제거
|
|
645
|
-
|
|
646
|
-
return {
|
|
647
|
-
asisEffect: setAsisDecorationsEffect.of(finalAsisDecorations),
|
|
648
|
-
tobeEffect: setTobeDecorationsEffect.of(finalTobeDecorations)
|
|
649
|
-
};
|
|
650
|
-
};
|
|
651
|
-
|
|
652
|
-
initialize = (src1, src2, language = 'javascript') => {
|
|
653
|
-
if (!this.#asisEditorView || !this.#tobeEditorView) {
|
|
654
|
-
console.warn('CodeMirror Editors not initialized yet.');
|
|
655
|
-
return;
|
|
656
|
-
}
|
|
657
|
-
|
|
658
|
-
this.#isScrollSyncActive = false; // 스크롤 동기화 일시 중지
|
|
659
|
-
|
|
660
|
-
let langExtension;
|
|
661
|
-
switch(language) {
|
|
662
|
-
case 'javascript':
|
|
663
|
-
langExtension = javascript();
|
|
664
|
-
break;
|
|
665
|
-
default:
|
|
666
|
-
langExtension = javascript();
|
|
667
|
-
}
|
|
668
|
-
|
|
669
|
-
// 데코레이션 효과는 미리 계산해 둡니다.
|
|
670
|
-
const { asisEffect, tobeEffect } = this.#applyDiffDecorations(src1, src2);
|
|
671
|
-
|
|
672
|
-
// 1단계: 텍스트 내용 변경과 언어 확장을 먼저 디스패치합니다.
|
|
673
|
-
// 'to' 값을 현재 문서 길이 전체로 지정하여 정확한 교체를 보장합니다.
|
|
674
|
-
this.#asisEditorView.dispatch({
|
|
675
|
-
changes: { from: 0, to: this.#asisEditorView.state.doc.length, insert: src1 },
|
|
676
|
-
effects: [
|
|
677
|
-
this.#languageCompartment.reconfigure(langExtension)
|
|
678
|
-
]
|
|
679
|
-
});
|
|
680
|
-
|
|
681
|
-
this.#tobeEditorView.dispatch({
|
|
682
|
-
changes: { from: 0, to: this.#tobeEditorView.state.doc.length, insert: src2 },
|
|
683
|
-
effects: [
|
|
684
|
-
this.#languageCompartment.reconfigure(langExtension)
|
|
685
|
-
]
|
|
686
|
-
});
|
|
687
|
-
|
|
688
|
-
// ⭐️ 2단계: 텍스트 및 언어 변경이 완전히 적용되고 뷰가 안정화될 시간을 줍니다.
|
|
689
|
-
// 다음 프레임에서 데코레이션 효과만 별도로 디스패치합니다.
|
|
690
|
-
requestAnimationFrame(() => {
|
|
691
|
-
this.#asisEditorView.dispatch({
|
|
692
|
-
effects: [asisEffect]
|
|
693
|
-
});
|
|
694
|
-
this.#tobeEditorView.dispatch({
|
|
695
|
-
effects: [tobeEffect]
|
|
696
|
-
});
|
|
697
|
-
|
|
698
|
-
// ⭐️ 3단계: 데코레이션까지 적용된 후 뷰가 다시 안정화될 시간을 한 번 더 줍니다.
|
|
699
|
-
// 그 다음 프레임에서 스크롤 동기화를 활성화합니다.
|
|
700
|
-
requestAnimationFrame(() => {
|
|
701
|
-
this.#isScrollSyncActive = true;
|
|
702
|
-
// 필요하다면 여기서 초기 스크롤 위치를 0으로 리셋하여 정렬을 보장할 수 있습니다.
|
|
703
|
-
// this.#asisEditorView.scrollDOM.scrollTop = 0;
|
|
704
|
-
// this.#tobeEditorView.scrollDOM.scrollTop = 0;
|
|
705
|
-
});
|
|
706
|
-
});
|
|
707
|
-
};
|
|
708
|
-
|
|
709
|
-
disconnectedCallback() {
|
|
710
|
-
// 컴포넌트 해제 시 스크롤 리스너 제거
|
|
711
|
-
if (this._asisScrollHandler) {
|
|
712
|
-
this.#asisEditorView.scrollDOM.removeEventListener('scroll', this._asisScrollHandler);
|
|
713
|
-
this.#tobeEditorView.scrollDOM.removeEventListener('scroll', this._tobeScrollHandler);
|
|
714
|
-
this._asisScrollHandler = null;
|
|
715
|
-
this._tobeScrollHandler = null;
|
|
716
|
-
}
|
|
717
|
-
if (this.#asisEditorView) {
|
|
718
|
-
this.#asisEditorView.destroy();
|
|
719
|
-
}
|
|
720
|
-
if (this.#tobeEditorView) {
|
|
721
|
-
this.#tobeEditorView.destroy();
|
|
722
|
-
}
|
|
723
|
-
}
|
|
724
|
-
}
|
|
725
|
-
customElements.define("ide-diff", IdeDiff);
|