ide-assi 0.329.0 → 0.331.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 +23600 -2881
- package/dist/bundle.esm.js +23600 -2881
- package/dist/components/ideDiff.js +63 -57
- package/dist/components/ideDiff.js.bak +141 -0
- package/package.json +10 -1
- package/src/components/ideDiff.js +63 -57
- package/src/components/ideDiff.js.bak +141 -0
|
@@ -1,76 +1,82 @@
|
|
|
1
|
-
import
|
|
2
|
-
|
|
3
|
-
import {
|
|
1
|
+
import { EditorView } from "@codemirror/view";
|
|
2
|
+
import { EditorState } from "@codemirror/state";
|
|
3
|
+
import { javascript } from "@codemirror/lang-javascript"; // 예시 언어
|
|
4
|
+
import { lineNumbers } from "@codemirror/view";
|
|
5
|
+
import { DiffEditor } from "codemirror-editor-diff"; // Diff 기능 라이브러리 (예시)
|
|
4
6
|
|
|
5
|
-
export class IdeDiff extends HTMLElement
|
|
6
|
-
|
|
7
|
-
#asisSrc;
|
|
8
|
-
#tobeSrc;
|
|
7
|
+
export class IdeDiff extends HTMLElement {
|
|
8
|
+
#diffEditorInstance; // DiffEditor 인스턴스
|
|
9
9
|
|
|
10
10
|
constructor() {
|
|
11
11
|
super();
|
|
12
12
|
this.attachShadow({ mode: 'open' });
|
|
13
13
|
}
|
|
14
|
-
|
|
15
|
-
connectedCallback() {
|
|
16
14
|
|
|
15
|
+
connectedCallback() {
|
|
17
16
|
this.shadowRoot.innerHTML = `
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
17
|
+
<style>
|
|
18
|
+
/* ninegrid CSS 및 필요한 기본 스타일 */
|
|
19
|
+
@import "https://cdn.jsdelivr.net/npm/ninegrid@${ninegrid.version}/dist/css/ideDiff.css";
|
|
20
|
+
${ninegrid.getCustomPath(this, "ideDiff.css")}
|
|
21
|
+
.wrapper {
|
|
22
|
+
width: 100%;
|
|
23
|
+
height: 100%;
|
|
24
|
+
display: flex;
|
|
25
|
+
}
|
|
26
|
+
#editor-container {
|
|
27
|
+
flex: 1;
|
|
28
|
+
min-height: 0;
|
|
29
|
+
}
|
|
30
|
+
/* CodeMirror 기본 스타일 */
|
|
31
|
+
.cm-editor {
|
|
32
|
+
height: 100%;
|
|
33
|
+
/* border: 1px solid #eee; */
|
|
34
|
+
}
|
|
35
|
+
</style>
|
|
36
|
+
<div class="wrapper">
|
|
37
|
+
<div id="editor-container"></div>
|
|
38
|
+
</div>
|
|
39
|
+
`;
|
|
29
40
|
|
|
30
41
|
requestAnimationFrame(() => {
|
|
31
|
-
this.#
|
|
42
|
+
this.#initCodeMirror();
|
|
32
43
|
});
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
#init = () => {
|
|
36
|
-
};
|
|
37
|
-
|
|
38
|
-
#renderDiff = () => {
|
|
39
|
-
const dmp = new diff_match_patch();
|
|
40
|
-
const diffs = dmp.diff_main(this.#asisSrc, this.#tobeSrc);
|
|
41
|
-
dmp.diff_cleanupSemantic(diffs);
|
|
42
|
-
|
|
43
|
-
const asisHtml = [];
|
|
44
|
-
const tobeHtml = [];
|
|
44
|
+
}
|
|
45
45
|
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
case diff_match_patch.DIFF_DELETE:
|
|
52
|
-
asisHtml.push(`<del>${text}</del>`);
|
|
53
|
-
break;
|
|
54
|
-
case diff_match_patch.DIFF_EQUAL:
|
|
55
|
-
asisHtml.push(`<span>${text}</span>`);
|
|
56
|
-
tobeHtml.push(`<span>${text}</span>`);
|
|
57
|
-
break;
|
|
58
|
-
}
|
|
46
|
+
#initCodeMirror = () => {
|
|
47
|
+
const container = this.shadowRoot.getElementById('editor-container');
|
|
48
|
+
if (!container) {
|
|
49
|
+
console.error('CodeMirror container not found!');
|
|
50
|
+
return;
|
|
59
51
|
}
|
|
60
52
|
|
|
61
|
-
|
|
62
|
-
this
|
|
53
|
+
// DiffEditor 인스턴스 생성
|
|
54
|
+
this.#diffEditorInstance = new DiffEditor(container, {
|
|
55
|
+
// CodeMirror 확장을 이곳에 배열로 전달
|
|
56
|
+
extensions: [
|
|
57
|
+
lineNumbers(), // 줄 번호
|
|
58
|
+
javascript(), // 구문 하이라이팅 (JavaScript 예시)
|
|
59
|
+
EditorState.readOnly.of(true) // 읽기 전용
|
|
60
|
+
// ... 기타 CodeMirror 확장
|
|
61
|
+
],
|
|
62
|
+
// DiffEditor 자체 옵션 (만약 있다면)
|
|
63
|
+
});
|
|
64
|
+
// 이 라이브러리가 내부적으로 두 개의 EditorView를 만들고 관리합니다.
|
|
65
|
+
// 스크롤 동기화, Diff 시각화 등은 이 라이브러리가 처리합니다.
|
|
63
66
|
};
|
|
64
67
|
|
|
65
|
-
|
|
66
68
|
initialize = (src1, src2) => {
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
this.#renderDiff();
|
|
69
|
+
if (!this.#diffEditorInstance) {
|
|
70
|
+
console.warn('CodeMirror Diff Editor not initialized yet.');
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
this.#diffEditorInstance.setValues(src1, src2);
|
|
73
74
|
};
|
|
74
|
-
}
|
|
75
75
|
|
|
76
|
-
|
|
76
|
+
disconnectedCallback() {
|
|
77
|
+
if (this.#diffEditorInstance) {
|
|
78
|
+
this.#diffEditorInstance.destroy(); // 인스턴스 정리 메서드 호출 (라이브러리마다 다름)
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
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, '&')
|
|
103
|
+
.replace(/</g, '<')
|
|
104
|
+
.replace(/>/g, '>');
|
|
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);
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ide-assi",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "0.
|
|
4
|
+
"version": "0.331.0",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
7
7
|
"exports": {
|
|
@@ -24,6 +24,15 @@
|
|
|
24
24
|
"rollup": "^4.44.1"
|
|
25
25
|
},
|
|
26
26
|
"dependencies": {
|
|
27
|
+
"@codemirror/autocomplete": "^6.x.x",
|
|
28
|
+
"@codemirror/commands": "^6.x.x",
|
|
29
|
+
"@codemirror/highlight": "^0.x.x",
|
|
30
|
+
"@codemirror/lang-javascript": "^6.x.x",
|
|
31
|
+
"@codemirror/language": "^6.x.x",
|
|
32
|
+
"@codemirror/lint": "^6.x.x",
|
|
33
|
+
"@codemirror/search": "^6.x.x",
|
|
34
|
+
"@codemirror/state": "^6.x.x",
|
|
35
|
+
"@codemirror/view": "^6.x.x",
|
|
27
36
|
"@langchain/core": "^0.x.x",
|
|
28
37
|
"@langchain/google-genai": "^0.x.x",
|
|
29
38
|
"@langchain/ollama": "^0.x.x",
|
|
@@ -1,76 +1,82 @@
|
|
|
1
|
-
import
|
|
2
|
-
|
|
3
|
-
import {
|
|
1
|
+
import { EditorView } from "@codemirror/view";
|
|
2
|
+
import { EditorState } from "@codemirror/state";
|
|
3
|
+
import { javascript } from "@codemirror/lang-javascript"; // 예시 언어
|
|
4
|
+
import { lineNumbers } from "@codemirror/view";
|
|
5
|
+
import { DiffEditor } from "codemirror-editor-diff"; // Diff 기능 라이브러리 (예시)
|
|
4
6
|
|
|
5
|
-
export class IdeDiff extends HTMLElement
|
|
6
|
-
|
|
7
|
-
#asisSrc;
|
|
8
|
-
#tobeSrc;
|
|
7
|
+
export class IdeDiff extends HTMLElement {
|
|
8
|
+
#diffEditorInstance; // DiffEditor 인스턴스
|
|
9
9
|
|
|
10
10
|
constructor() {
|
|
11
11
|
super();
|
|
12
12
|
this.attachShadow({ mode: 'open' });
|
|
13
13
|
}
|
|
14
|
-
|
|
15
|
-
connectedCallback() {
|
|
16
14
|
|
|
15
|
+
connectedCallback() {
|
|
17
16
|
this.shadowRoot.innerHTML = `
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
17
|
+
<style>
|
|
18
|
+
/* ninegrid CSS 및 필요한 기본 스타일 */
|
|
19
|
+
@import "https://cdn.jsdelivr.net/npm/ninegrid@${ninegrid.version}/dist/css/ideDiff.css";
|
|
20
|
+
${ninegrid.getCustomPath(this, "ideDiff.css")}
|
|
21
|
+
.wrapper {
|
|
22
|
+
width: 100%;
|
|
23
|
+
height: 100%;
|
|
24
|
+
display: flex;
|
|
25
|
+
}
|
|
26
|
+
#editor-container {
|
|
27
|
+
flex: 1;
|
|
28
|
+
min-height: 0;
|
|
29
|
+
}
|
|
30
|
+
/* CodeMirror 기본 스타일 */
|
|
31
|
+
.cm-editor {
|
|
32
|
+
height: 100%;
|
|
33
|
+
/* border: 1px solid #eee; */
|
|
34
|
+
}
|
|
35
|
+
</style>
|
|
36
|
+
<div class="wrapper">
|
|
37
|
+
<div id="editor-container"></div>
|
|
38
|
+
</div>
|
|
39
|
+
`;
|
|
29
40
|
|
|
30
41
|
requestAnimationFrame(() => {
|
|
31
|
-
this.#
|
|
42
|
+
this.#initCodeMirror();
|
|
32
43
|
});
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
#init = () => {
|
|
36
|
-
};
|
|
37
|
-
|
|
38
|
-
#renderDiff = () => {
|
|
39
|
-
const dmp = new diff_match_patch();
|
|
40
|
-
const diffs = dmp.diff_main(this.#asisSrc, this.#tobeSrc);
|
|
41
|
-
dmp.diff_cleanupSemantic(diffs);
|
|
42
|
-
|
|
43
|
-
const asisHtml = [];
|
|
44
|
-
const tobeHtml = [];
|
|
44
|
+
}
|
|
45
45
|
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
case diff_match_patch.DIFF_DELETE:
|
|
52
|
-
asisHtml.push(`<del>${text}</del>`);
|
|
53
|
-
break;
|
|
54
|
-
case diff_match_patch.DIFF_EQUAL:
|
|
55
|
-
asisHtml.push(`<span>${text}</span>`);
|
|
56
|
-
tobeHtml.push(`<span>${text}</span>`);
|
|
57
|
-
break;
|
|
58
|
-
}
|
|
46
|
+
#initCodeMirror = () => {
|
|
47
|
+
const container = this.shadowRoot.getElementById('editor-container');
|
|
48
|
+
if (!container) {
|
|
49
|
+
console.error('CodeMirror container not found!');
|
|
50
|
+
return;
|
|
59
51
|
}
|
|
60
52
|
|
|
61
|
-
|
|
62
|
-
this
|
|
53
|
+
// DiffEditor 인스턴스 생성
|
|
54
|
+
this.#diffEditorInstance = new DiffEditor(container, {
|
|
55
|
+
// CodeMirror 확장을 이곳에 배열로 전달
|
|
56
|
+
extensions: [
|
|
57
|
+
lineNumbers(), // 줄 번호
|
|
58
|
+
javascript(), // 구문 하이라이팅 (JavaScript 예시)
|
|
59
|
+
EditorState.readOnly.of(true) // 읽기 전용
|
|
60
|
+
// ... 기타 CodeMirror 확장
|
|
61
|
+
],
|
|
62
|
+
// DiffEditor 자체 옵션 (만약 있다면)
|
|
63
|
+
});
|
|
64
|
+
// 이 라이브러리가 내부적으로 두 개의 EditorView를 만들고 관리합니다.
|
|
65
|
+
// 스크롤 동기화, Diff 시각화 등은 이 라이브러리가 처리합니다.
|
|
63
66
|
};
|
|
64
67
|
|
|
65
|
-
|
|
66
68
|
initialize = (src1, src2) => {
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
this.#renderDiff();
|
|
69
|
+
if (!this.#diffEditorInstance) {
|
|
70
|
+
console.warn('CodeMirror Diff Editor not initialized yet.');
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
this.#diffEditorInstance.setValues(src1, src2);
|
|
73
74
|
};
|
|
74
|
-
}
|
|
75
75
|
|
|
76
|
-
|
|
76
|
+
disconnectedCallback() {
|
|
77
|
+
if (this.#diffEditorInstance) {
|
|
78
|
+
this.#diffEditorInstance.destroy(); // 인스턴스 정리 메서드 호출 (라이브러리마다 다름)
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
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, '&')
|
|
103
|
+
.replace(/</g, '<')
|
|
104
|
+
.replace(/>/g, '>');
|
|
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);
|