ide-assi 0.329.0 → 0.330.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 +87 -21
- package/dist/bundle.esm.js +87 -21
- package/dist/components/ideDiff.js +88 -23
- package/package.json +1 -1
- package/src/components/ideDiff.js +88 -23
package/dist/bundle.cjs.js
CHANGED
|
@@ -196896,26 +196896,59 @@ class IdeDiff extends HTMLElement
|
|
|
196896
196896
|
{
|
|
196897
196897
|
#asisSrc;
|
|
196898
196898
|
#tobeSrc;
|
|
196899
|
+
#asisPre; // <pre> 요소 참조 저장
|
|
196900
|
+
#tobePre; // <pre> 요소 참조 저장
|
|
196899
196901
|
|
|
196900
196902
|
constructor() {
|
|
196901
196903
|
super();
|
|
196902
196904
|
this.attachShadow({ mode: 'open' });
|
|
196903
196905
|
}
|
|
196904
|
-
|
|
196905
|
-
connectedCallback() {
|
|
196906
196906
|
|
|
196907
|
+
connectedCallback() {
|
|
196907
196908
|
this.shadowRoot.innerHTML = `
|
|
196908
|
-
|
|
196909
|
-
|
|
196910
|
-
|
|
196911
|
-
|
|
196912
|
-
|
|
196913
|
-
|
|
196914
|
-
|
|
196915
|
-
|
|
196916
|
-
|
|
196917
|
-
|
|
196918
|
-
|
|
196909
|
+
<style>
|
|
196910
|
+
/* 전역 또는 컴포넌트 스코프에서 box-sizing: border-box; 적용 권장 */
|
|
196911
|
+
* {
|
|
196912
|
+
box-sizing: border-box;
|
|
196913
|
+
}
|
|
196914
|
+
@import "https://cdn.jsdelivr.net/npm/ninegrid@${ninegrid.version}/dist/css/ideDiff.css";
|
|
196915
|
+
${ninegrid.getCustomPath(this,"ideDiff.css")}
|
|
196916
|
+
|
|
196917
|
+
/* 추가 CSS (필요 시) */
|
|
196918
|
+
.wrapper {
|
|
196919
|
+
display: flex; /* 스플리터와 함께 작동하도록 flex 컨테이너 */
|
|
196920
|
+
height: 100%; /* 부모의 높이를 채우도록 설정 */
|
|
196921
|
+
overflow: hidden; /* 내부 스크롤을 위해 overflow 처리 */
|
|
196922
|
+
}
|
|
196923
|
+
.panel {
|
|
196924
|
+
flex: 1; /* 패널들이 남은 공간을 채우도록 */
|
|
196925
|
+
overflow: auto; /* 각 패널 내부에서 스크롤 가능하게 */
|
|
196926
|
+
position: relative; /* 자식 요소의 absolute 포지셔닝 기준 */
|
|
196927
|
+
}
|
|
196928
|
+
.panel pre {
|
|
196929
|
+
margin: 0; /* pre 태그의 기본 마진 제거 */
|
|
196930
|
+
padding: 10px; /* 코드 가독성을 위한 내부 패딩 */
|
|
196931
|
+
white-space: pre-wrap; /* 긴 줄 자동 줄바꿈 */
|
|
196932
|
+
word-break: break-all; /* 단어가 길어도 강제 줄바꿈 */
|
|
196933
|
+
/* white-space: pre-wrap; 을 사용하면 가로 스크롤 없이 줄바꿈됩니다.
|
|
196934
|
+
만약 가로 스크롤을 원하면 white-space: pre; 로 두고 overflow-x: auto; 를 panel에 추가 */
|
|
196935
|
+
}
|
|
196936
|
+
ins {
|
|
196937
|
+
background-color: #d4edda; /* 삽입된 텍스트 배경색 (예시) */
|
|
196938
|
+
text-decoration: none; /* 밑줄 제거 (일반적으로) */
|
|
196939
|
+
}
|
|
196940
|
+
del {
|
|
196941
|
+
background-color: #f8d7da; /* 삭제된 텍스트 배경색 (예시) */
|
|
196942
|
+
text-decoration: none; /* 취소선 제거 (일반적으로) */
|
|
196943
|
+
}
|
|
196944
|
+
</style>
|
|
196945
|
+
|
|
196946
|
+
<div class="wrapper">
|
|
196947
|
+
<div class="panel asis"><pre></pre></div>
|
|
196948
|
+
<nx-splitter></nx-splitter>
|
|
196949
|
+
<div class="panel tobe"><pre></pre></div>
|
|
196950
|
+
</div>
|
|
196951
|
+
`;
|
|
196919
196952
|
|
|
196920
196953
|
requestAnimationFrame(() => {
|
|
196921
196954
|
this.#init();
|
|
@@ -196923,38 +196956,71 @@ class IdeDiff extends HTMLElement
|
|
|
196923
196956
|
};
|
|
196924
196957
|
|
|
196925
196958
|
#init = () => {
|
|
196959
|
+
// 패널 pre 요소에 대한 참조를 저장
|
|
196960
|
+
this.#asisPre = this.shadowRoot.querySelector('.asis pre');
|
|
196961
|
+
this.#tobePre = this.shadowRoot.querySelector('.tobe pre');
|
|
196962
|
+
|
|
196963
|
+
// 스크롤 동기화 이벤트 리스너 추가
|
|
196964
|
+
this.#asisPre.addEventListener('scroll', this.#syncScroll);
|
|
196965
|
+
this.#tobePre.addEventListener('scroll', this.#syncScroll);
|
|
196966
|
+
};
|
|
196967
|
+
|
|
196968
|
+
#syncScroll = (e) => {
|
|
196969
|
+
// 스크롤 이벤트 발생 시 다른 패널의 스크롤 위치를 동기화
|
|
196970
|
+
// `e.target`이 스크롤 이벤트를 발생시킨 요소
|
|
196971
|
+
if (e.target === this.#asisPre) {
|
|
196972
|
+
this.#tobePre.scrollTop = this.#asisPre.scrollTop;
|
|
196973
|
+
} else if (e.target === this.#tobePre) {
|
|
196974
|
+
this.#asisPre.scrollTop = this.#tobePre.scrollTop;
|
|
196975
|
+
}
|
|
196926
196976
|
};
|
|
196927
196977
|
|
|
196928
196978
|
#renderDiff = () => {
|
|
196929
196979
|
const dmp = new diffMatchPatchExports.diff_match_patch();
|
|
196930
|
-
|
|
196980
|
+
// 공백 문자를 정규화하여 diff 정확도 향상 (선택 사항)
|
|
196981
|
+
const cleanedAsisSrc = this.#asisSrc.replace(/\r\n/g, '\n');
|
|
196982
|
+
const cleanedTobeSrc = this.#tobeSrc.replace(/\r\n/g, '\n');
|
|
196983
|
+
|
|
196984
|
+
const diffs = dmp.diff_main(cleanedAsisSrc, cleanedTobeSrc);
|
|
196931
196985
|
dmp.diff_cleanupSemantic(diffs);
|
|
196932
196986
|
|
|
196933
196987
|
const asisHtml = [];
|
|
196934
196988
|
const tobeHtml = [];
|
|
196935
196989
|
|
|
196936
196990
|
for (const [op, text] of diffs) {
|
|
196991
|
+
// HTML 이스케이프 처리: <, >, & 등 특수문자 변환
|
|
196992
|
+
const escapedText = text
|
|
196993
|
+
.replace(/&/g, '&')
|
|
196994
|
+
.replace(/</g, '<')
|
|
196995
|
+
.replace(/>/g, '>');
|
|
196996
|
+
|
|
196937
196997
|
switch (op) {
|
|
196938
196998
|
case diffMatchPatchExports.diff_match_patch.DIFF_INSERT:
|
|
196939
|
-
tobeHtml.push(`<ins>${
|
|
196999
|
+
tobeHtml.push(`<ins>${escapedText}</ins>`);
|
|
197000
|
+
// 삭제된 내용이 없으므로 asis에는 공백을 유지 (줄 맞춤 고려)
|
|
197001
|
+
// 만약 줄 단위로 완벽하게 매칭하려면 여기에 다른 로직 필요
|
|
196940
197002
|
break;
|
|
196941
197003
|
case diffMatchPatchExports.diff_match_patch.DIFF_DELETE:
|
|
196942
|
-
asisHtml.push(`<del>${
|
|
197004
|
+
asisHtml.push(`<del>${escapedText}</del>`);
|
|
197005
|
+
// 추가된 내용이 없으므로 tobe에는 공백을 유지
|
|
196943
197006
|
break;
|
|
196944
197007
|
case diffMatchPatchExports.diff_match_patch.DIFF_EQUAL:
|
|
196945
|
-
asisHtml.push(`<span>${
|
|
196946
|
-
tobeHtml.push(`<span>${
|
|
197008
|
+
asisHtml.push(`<span>${escapedText}</span>`);
|
|
197009
|
+
tobeHtml.push(`<span>${escapedText}</span>`);
|
|
196947
197010
|
break;
|
|
196948
197011
|
}
|
|
196949
197012
|
}
|
|
196950
197013
|
|
|
196951
|
-
|
|
196952
|
-
|
|
197014
|
+
// 중요한 부분: `innerHTML` 대신 `textContent`를 사용하면
|
|
197015
|
+
// `diff-match-patch`의 출력에 따라 HTML 태그가 그대로 보이므로,
|
|
197016
|
+
// 여기서는 `innerHTML`을 사용하되, 텍스트는 `escapedText`로 처리하여 XSS 방지.
|
|
197017
|
+
this.#asisPre.innerHTML = asisHtml.join('');
|
|
197018
|
+
this.#tobePre.innerHTML = tobeHtml.join('');
|
|
196953
197019
|
};
|
|
196954
197020
|
|
|
196955
197021
|
|
|
196956
197022
|
initialize = (src1, src2) => {
|
|
196957
|
-
console.log(src1, src2);
|
|
197023
|
+
console.log("Initializing IdeDiff with sources:", src1, src2);
|
|
196958
197024
|
|
|
196959
197025
|
this.#asisSrc = src1;
|
|
196960
197026
|
this.#tobeSrc = src2;
|
package/dist/bundle.esm.js
CHANGED
|
@@ -196892,26 +196892,59 @@ class IdeDiff extends HTMLElement
|
|
|
196892
196892
|
{
|
|
196893
196893
|
#asisSrc;
|
|
196894
196894
|
#tobeSrc;
|
|
196895
|
+
#asisPre; // <pre> 요소 참조 저장
|
|
196896
|
+
#tobePre; // <pre> 요소 참조 저장
|
|
196895
196897
|
|
|
196896
196898
|
constructor() {
|
|
196897
196899
|
super();
|
|
196898
196900
|
this.attachShadow({ mode: 'open' });
|
|
196899
196901
|
}
|
|
196900
|
-
|
|
196901
|
-
connectedCallback() {
|
|
196902
196902
|
|
|
196903
|
+
connectedCallback() {
|
|
196903
196904
|
this.shadowRoot.innerHTML = `
|
|
196904
|
-
|
|
196905
|
-
|
|
196906
|
-
|
|
196907
|
-
|
|
196908
|
-
|
|
196909
|
-
|
|
196910
|
-
|
|
196911
|
-
|
|
196912
|
-
|
|
196913
|
-
|
|
196914
|
-
|
|
196905
|
+
<style>
|
|
196906
|
+
/* 전역 또는 컴포넌트 스코프에서 box-sizing: border-box; 적용 권장 */
|
|
196907
|
+
* {
|
|
196908
|
+
box-sizing: border-box;
|
|
196909
|
+
}
|
|
196910
|
+
@import "https://cdn.jsdelivr.net/npm/ninegrid@${ninegrid.version}/dist/css/ideDiff.css";
|
|
196911
|
+
${ninegrid.getCustomPath(this,"ideDiff.css")}
|
|
196912
|
+
|
|
196913
|
+
/* 추가 CSS (필요 시) */
|
|
196914
|
+
.wrapper {
|
|
196915
|
+
display: flex; /* 스플리터와 함께 작동하도록 flex 컨테이너 */
|
|
196916
|
+
height: 100%; /* 부모의 높이를 채우도록 설정 */
|
|
196917
|
+
overflow: hidden; /* 내부 스크롤을 위해 overflow 처리 */
|
|
196918
|
+
}
|
|
196919
|
+
.panel {
|
|
196920
|
+
flex: 1; /* 패널들이 남은 공간을 채우도록 */
|
|
196921
|
+
overflow: auto; /* 각 패널 내부에서 스크롤 가능하게 */
|
|
196922
|
+
position: relative; /* 자식 요소의 absolute 포지셔닝 기준 */
|
|
196923
|
+
}
|
|
196924
|
+
.panel pre {
|
|
196925
|
+
margin: 0; /* pre 태그의 기본 마진 제거 */
|
|
196926
|
+
padding: 10px; /* 코드 가독성을 위한 내부 패딩 */
|
|
196927
|
+
white-space: pre-wrap; /* 긴 줄 자동 줄바꿈 */
|
|
196928
|
+
word-break: break-all; /* 단어가 길어도 강제 줄바꿈 */
|
|
196929
|
+
/* white-space: pre-wrap; 을 사용하면 가로 스크롤 없이 줄바꿈됩니다.
|
|
196930
|
+
만약 가로 스크롤을 원하면 white-space: pre; 로 두고 overflow-x: auto; 를 panel에 추가 */
|
|
196931
|
+
}
|
|
196932
|
+
ins {
|
|
196933
|
+
background-color: #d4edda; /* 삽입된 텍스트 배경색 (예시) */
|
|
196934
|
+
text-decoration: none; /* 밑줄 제거 (일반적으로) */
|
|
196935
|
+
}
|
|
196936
|
+
del {
|
|
196937
|
+
background-color: #f8d7da; /* 삭제된 텍스트 배경색 (예시) */
|
|
196938
|
+
text-decoration: none; /* 취소선 제거 (일반적으로) */
|
|
196939
|
+
}
|
|
196940
|
+
</style>
|
|
196941
|
+
|
|
196942
|
+
<div class="wrapper">
|
|
196943
|
+
<div class="panel asis"><pre></pre></div>
|
|
196944
|
+
<nx-splitter></nx-splitter>
|
|
196945
|
+
<div class="panel tobe"><pre></pre></div>
|
|
196946
|
+
</div>
|
|
196947
|
+
`;
|
|
196915
196948
|
|
|
196916
196949
|
requestAnimationFrame(() => {
|
|
196917
196950
|
this.#init();
|
|
@@ -196919,38 +196952,71 @@ class IdeDiff extends HTMLElement
|
|
|
196919
196952
|
};
|
|
196920
196953
|
|
|
196921
196954
|
#init = () => {
|
|
196955
|
+
// 패널 pre 요소에 대한 참조를 저장
|
|
196956
|
+
this.#asisPre = this.shadowRoot.querySelector('.asis pre');
|
|
196957
|
+
this.#tobePre = this.shadowRoot.querySelector('.tobe pre');
|
|
196958
|
+
|
|
196959
|
+
// 스크롤 동기화 이벤트 리스너 추가
|
|
196960
|
+
this.#asisPre.addEventListener('scroll', this.#syncScroll);
|
|
196961
|
+
this.#tobePre.addEventListener('scroll', this.#syncScroll);
|
|
196962
|
+
};
|
|
196963
|
+
|
|
196964
|
+
#syncScroll = (e) => {
|
|
196965
|
+
// 스크롤 이벤트 발생 시 다른 패널의 스크롤 위치를 동기화
|
|
196966
|
+
// `e.target`이 스크롤 이벤트를 발생시킨 요소
|
|
196967
|
+
if (e.target === this.#asisPre) {
|
|
196968
|
+
this.#tobePre.scrollTop = this.#asisPre.scrollTop;
|
|
196969
|
+
} else if (e.target === this.#tobePre) {
|
|
196970
|
+
this.#asisPre.scrollTop = this.#tobePre.scrollTop;
|
|
196971
|
+
}
|
|
196922
196972
|
};
|
|
196923
196973
|
|
|
196924
196974
|
#renderDiff = () => {
|
|
196925
196975
|
const dmp = new diffMatchPatchExports.diff_match_patch();
|
|
196926
|
-
|
|
196976
|
+
// 공백 문자를 정규화하여 diff 정확도 향상 (선택 사항)
|
|
196977
|
+
const cleanedAsisSrc = this.#asisSrc.replace(/\r\n/g, '\n');
|
|
196978
|
+
const cleanedTobeSrc = this.#tobeSrc.replace(/\r\n/g, '\n');
|
|
196979
|
+
|
|
196980
|
+
const diffs = dmp.diff_main(cleanedAsisSrc, cleanedTobeSrc);
|
|
196927
196981
|
dmp.diff_cleanupSemantic(diffs);
|
|
196928
196982
|
|
|
196929
196983
|
const asisHtml = [];
|
|
196930
196984
|
const tobeHtml = [];
|
|
196931
196985
|
|
|
196932
196986
|
for (const [op, text] of diffs) {
|
|
196987
|
+
// HTML 이스케이프 처리: <, >, & 등 특수문자 변환
|
|
196988
|
+
const escapedText = text
|
|
196989
|
+
.replace(/&/g, '&')
|
|
196990
|
+
.replace(/</g, '<')
|
|
196991
|
+
.replace(/>/g, '>');
|
|
196992
|
+
|
|
196933
196993
|
switch (op) {
|
|
196934
196994
|
case diffMatchPatchExports.diff_match_patch.DIFF_INSERT:
|
|
196935
|
-
tobeHtml.push(`<ins>${
|
|
196995
|
+
tobeHtml.push(`<ins>${escapedText}</ins>`);
|
|
196996
|
+
// 삭제된 내용이 없으므로 asis에는 공백을 유지 (줄 맞춤 고려)
|
|
196997
|
+
// 만약 줄 단위로 완벽하게 매칭하려면 여기에 다른 로직 필요
|
|
196936
196998
|
break;
|
|
196937
196999
|
case diffMatchPatchExports.diff_match_patch.DIFF_DELETE:
|
|
196938
|
-
asisHtml.push(`<del>${
|
|
197000
|
+
asisHtml.push(`<del>${escapedText}</del>`);
|
|
197001
|
+
// 추가된 내용이 없으므로 tobe에는 공백을 유지
|
|
196939
197002
|
break;
|
|
196940
197003
|
case diffMatchPatchExports.diff_match_patch.DIFF_EQUAL:
|
|
196941
|
-
asisHtml.push(`<span>${
|
|
196942
|
-
tobeHtml.push(`<span>${
|
|
197004
|
+
asisHtml.push(`<span>${escapedText}</span>`);
|
|
197005
|
+
tobeHtml.push(`<span>${escapedText}</span>`);
|
|
196943
197006
|
break;
|
|
196944
197007
|
}
|
|
196945
197008
|
}
|
|
196946
197009
|
|
|
196947
|
-
|
|
196948
|
-
|
|
197010
|
+
// 중요한 부분: `innerHTML` 대신 `textContent`를 사용하면
|
|
197011
|
+
// `diff-match-patch`의 출력에 따라 HTML 태그가 그대로 보이므로,
|
|
197012
|
+
// 여기서는 `innerHTML`을 사용하되, 텍스트는 `escapedText`로 처리하여 XSS 방지.
|
|
197013
|
+
this.#asisPre.innerHTML = asisHtml.join('');
|
|
197014
|
+
this.#tobePre.innerHTML = tobeHtml.join('');
|
|
196949
197015
|
};
|
|
196950
197016
|
|
|
196951
197017
|
|
|
196952
197018
|
initialize = (src1, src2) => {
|
|
196953
|
-
console.log(src1, src2);
|
|
197019
|
+
console.log("Initializing IdeDiff with sources:", src1, src2);
|
|
196954
197020
|
|
|
196955
197021
|
this.#asisSrc = src1;
|
|
196956
197022
|
this.#tobeSrc = src2;
|
|
@@ -1,31 +1,63 @@
|
|
|
1
1
|
import ninegrid from "ninegrid2";
|
|
2
|
-
//import { IdeAi } from "./ideAi.js";
|
|
3
2
|
import {diff_match_patch} from 'diff-match-patch';
|
|
4
3
|
|
|
5
4
|
export class IdeDiff extends HTMLElement
|
|
6
5
|
{
|
|
7
6
|
#asisSrc;
|
|
8
7
|
#tobeSrc;
|
|
8
|
+
#asisPre; // <pre> 요소 참조 저장
|
|
9
|
+
#tobePre; // <pre> 요소 참조 저장
|
|
9
10
|
|
|
10
11
|
constructor() {
|
|
11
12
|
super();
|
|
12
13
|
this.attachShadow({ mode: 'open' });
|
|
13
14
|
}
|
|
14
|
-
|
|
15
|
-
connectedCallback() {
|
|
16
15
|
|
|
16
|
+
connectedCallback() {
|
|
17
17
|
this.shadowRoot.innerHTML = `
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
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
|
+
`;
|
|
29
61
|
|
|
30
62
|
requestAnimationFrame(() => {
|
|
31
63
|
this.#init();
|
|
@@ -33,38 +65,71 @@ export class IdeDiff extends HTMLElement
|
|
|
33
65
|
};
|
|
34
66
|
|
|
35
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
|
+
}
|
|
36
85
|
};
|
|
37
86
|
|
|
38
87
|
#renderDiff = () => {
|
|
39
88
|
const dmp = new diff_match_patch();
|
|
40
|
-
|
|
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);
|
|
41
94
|
dmp.diff_cleanupSemantic(diffs);
|
|
42
95
|
|
|
43
96
|
const asisHtml = [];
|
|
44
97
|
const tobeHtml = [];
|
|
45
98
|
|
|
46
99
|
for (const [op, text] of diffs) {
|
|
100
|
+
// HTML 이스케이프 처리: <, >, & 등 특수문자 변환
|
|
101
|
+
const escapedText = text
|
|
102
|
+
.replace(/&/g, '&')
|
|
103
|
+
.replace(/</g, '<')
|
|
104
|
+
.replace(/>/g, '>');
|
|
105
|
+
|
|
47
106
|
switch (op) {
|
|
48
107
|
case diff_match_patch.DIFF_INSERT:
|
|
49
|
-
tobeHtml.push(`<ins>${
|
|
108
|
+
tobeHtml.push(`<ins>${escapedText}</ins>`);
|
|
109
|
+
// 삭제된 내용이 없으므로 asis에는 공백을 유지 (줄 맞춤 고려)
|
|
110
|
+
// 만약 줄 단위로 완벽하게 매칭하려면 여기에 다른 로직 필요
|
|
50
111
|
break;
|
|
51
112
|
case diff_match_patch.DIFF_DELETE:
|
|
52
|
-
asisHtml.push(`<del>${
|
|
113
|
+
asisHtml.push(`<del>${escapedText}</del>`);
|
|
114
|
+
// 추가된 내용이 없으므로 tobe에는 공백을 유지
|
|
53
115
|
break;
|
|
54
116
|
case diff_match_patch.DIFF_EQUAL:
|
|
55
|
-
asisHtml.push(`<span>${
|
|
56
|
-
tobeHtml.push(`<span>${
|
|
117
|
+
asisHtml.push(`<span>${escapedText}</span>`);
|
|
118
|
+
tobeHtml.push(`<span>${escapedText}</span>`);
|
|
57
119
|
break;
|
|
58
120
|
}
|
|
59
121
|
}
|
|
60
122
|
|
|
61
|
-
|
|
62
|
-
|
|
123
|
+
// 중요한 부분: `innerHTML` 대신 `textContent`를 사용하면
|
|
124
|
+
// `diff-match-patch`의 출력에 따라 HTML 태그가 그대로 보이므로,
|
|
125
|
+
// 여기서는 `innerHTML`을 사용하되, 텍스트는 `escapedText`로 처리하여 XSS 방지.
|
|
126
|
+
this.#asisPre.innerHTML = asisHtml.join('');
|
|
127
|
+
this.#tobePre.innerHTML = tobeHtml.join('');
|
|
63
128
|
};
|
|
64
129
|
|
|
65
130
|
|
|
66
131
|
initialize = (src1, src2) => {
|
|
67
|
-
console.log(src1, src2);
|
|
132
|
+
console.log("Initializing IdeDiff with sources:", src1, src2);
|
|
68
133
|
|
|
69
134
|
this.#asisSrc = src1;
|
|
70
135
|
this.#tobeSrc = src2;
|
|
@@ -73,4 +138,4 @@ export class IdeDiff extends HTMLElement
|
|
|
73
138
|
};
|
|
74
139
|
}
|
|
75
140
|
|
|
76
|
-
customElements.define("ide-diff", IdeDiff);
|
|
141
|
+
customElements.define("ide-diff", IdeDiff);
|
package/package.json
CHANGED
|
@@ -1,31 +1,63 @@
|
|
|
1
1
|
import ninegrid from "ninegrid2";
|
|
2
|
-
//import { IdeAi } from "./ideAi.js";
|
|
3
2
|
import {diff_match_patch} from 'diff-match-patch';
|
|
4
3
|
|
|
5
4
|
export class IdeDiff extends HTMLElement
|
|
6
5
|
{
|
|
7
6
|
#asisSrc;
|
|
8
7
|
#tobeSrc;
|
|
8
|
+
#asisPre; // <pre> 요소 참조 저장
|
|
9
|
+
#tobePre; // <pre> 요소 참조 저장
|
|
9
10
|
|
|
10
11
|
constructor() {
|
|
11
12
|
super();
|
|
12
13
|
this.attachShadow({ mode: 'open' });
|
|
13
14
|
}
|
|
14
|
-
|
|
15
|
-
connectedCallback() {
|
|
16
15
|
|
|
16
|
+
connectedCallback() {
|
|
17
17
|
this.shadowRoot.innerHTML = `
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
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
|
+
`;
|
|
29
61
|
|
|
30
62
|
requestAnimationFrame(() => {
|
|
31
63
|
this.#init();
|
|
@@ -33,38 +65,71 @@ export class IdeDiff extends HTMLElement
|
|
|
33
65
|
};
|
|
34
66
|
|
|
35
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
|
+
}
|
|
36
85
|
};
|
|
37
86
|
|
|
38
87
|
#renderDiff = () => {
|
|
39
88
|
const dmp = new diff_match_patch();
|
|
40
|
-
|
|
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);
|
|
41
94
|
dmp.diff_cleanupSemantic(diffs);
|
|
42
95
|
|
|
43
96
|
const asisHtml = [];
|
|
44
97
|
const tobeHtml = [];
|
|
45
98
|
|
|
46
99
|
for (const [op, text] of diffs) {
|
|
100
|
+
// HTML 이스케이프 처리: <, >, & 등 특수문자 변환
|
|
101
|
+
const escapedText = text
|
|
102
|
+
.replace(/&/g, '&')
|
|
103
|
+
.replace(/</g, '<')
|
|
104
|
+
.replace(/>/g, '>');
|
|
105
|
+
|
|
47
106
|
switch (op) {
|
|
48
107
|
case diff_match_patch.DIFF_INSERT:
|
|
49
|
-
tobeHtml.push(`<ins>${
|
|
108
|
+
tobeHtml.push(`<ins>${escapedText}</ins>`);
|
|
109
|
+
// 삭제된 내용이 없으므로 asis에는 공백을 유지 (줄 맞춤 고려)
|
|
110
|
+
// 만약 줄 단위로 완벽하게 매칭하려면 여기에 다른 로직 필요
|
|
50
111
|
break;
|
|
51
112
|
case diff_match_patch.DIFF_DELETE:
|
|
52
|
-
asisHtml.push(`<del>${
|
|
113
|
+
asisHtml.push(`<del>${escapedText}</del>`);
|
|
114
|
+
// 추가된 내용이 없으므로 tobe에는 공백을 유지
|
|
53
115
|
break;
|
|
54
116
|
case diff_match_patch.DIFF_EQUAL:
|
|
55
|
-
asisHtml.push(`<span>${
|
|
56
|
-
tobeHtml.push(`<span>${
|
|
117
|
+
asisHtml.push(`<span>${escapedText}</span>`);
|
|
118
|
+
tobeHtml.push(`<span>${escapedText}</span>`);
|
|
57
119
|
break;
|
|
58
120
|
}
|
|
59
121
|
}
|
|
60
122
|
|
|
61
|
-
|
|
62
|
-
|
|
123
|
+
// 중요한 부분: `innerHTML` 대신 `textContent`를 사용하면
|
|
124
|
+
// `diff-match-patch`의 출력에 따라 HTML 태그가 그대로 보이므로,
|
|
125
|
+
// 여기서는 `innerHTML`을 사용하되, 텍스트는 `escapedText`로 처리하여 XSS 방지.
|
|
126
|
+
this.#asisPre.innerHTML = asisHtml.join('');
|
|
127
|
+
this.#tobePre.innerHTML = tobeHtml.join('');
|
|
63
128
|
};
|
|
64
129
|
|
|
65
130
|
|
|
66
131
|
initialize = (src1, src2) => {
|
|
67
|
-
console.log(src1, src2);
|
|
132
|
+
console.log("Initializing IdeDiff with sources:", src1, src2);
|
|
68
133
|
|
|
69
134
|
this.#asisSrc = src1;
|
|
70
135
|
this.#tobeSrc = src2;
|
|
@@ -73,4 +138,4 @@ export class IdeDiff extends HTMLElement
|
|
|
73
138
|
};
|
|
74
139
|
}
|
|
75
140
|
|
|
76
|
-
customElements.define("ide-diff", IdeDiff);
|
|
141
|
+
customElements.define("ide-diff", IdeDiff);
|