podo-ui 0.7.1 → 0.7.2
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/react/atom/editor.d.ts.map +1 -1
- package/dist/react/atom/editor.js +129 -57
- package/package.json +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"editor.d.ts","sourceRoot":"","sources":["../../../react/atom/editor.tsx"],"names":[],"mappings":"AAEA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAGxB,MAAM,MAAM,WAAW,GACnB,WAAW,GACX,WAAW,GACX,YAAY,GACZ,OAAO,GACP,OAAO,GACP,MAAM,GACN,OAAO,GACP,MAAM,GACN,OAAO,GACP,SAAS,GACT,IAAI,GACJ,QAAQ,GACR,MAAM,CAAC;AAEX,MAAM,WAAW,WAAW;IAC1B,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,MAAM,GAAG,UAAU,CAAC;IAC7B,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,QAAQ,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;IACpC,SAAS,CAAC,EAAE,CAAC,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC;IAC/B,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,OAAO,CAAC,EAAE,WAAW,EAAE,CAAC;CACzB;AAED,QAAA,MAAM,MAAM,0GAWT,WAAW,
|
|
1
|
+
{"version":3,"file":"editor.d.ts","sourceRoot":"","sources":["../../../react/atom/editor.tsx"],"names":[],"mappings":"AAEA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAGxB,MAAM,MAAM,WAAW,GACnB,WAAW,GACX,WAAW,GACX,YAAY,GACZ,OAAO,GACP,OAAO,GACP,MAAM,GACN,OAAO,GACP,MAAM,GACN,OAAO,GACP,SAAS,GACT,IAAI,GACJ,QAAQ,GACR,MAAM,CAAC;AAEX,MAAM,WAAW,WAAW;IAC1B,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,MAAM,GAAG,UAAU,CAAC;IAC7B,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,QAAQ,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;IACpC,SAAS,CAAC,EAAE,CAAC,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC;IAC/B,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,OAAO,CAAC,EAAE,WAAW,EAAE,CAAC;CACzB;AAED,QAAA,MAAM,MAAM,0GAWT,WAAW,4CAi0Ib,CAAC;AAEF,eAAe,MAAM,CAAC"}
|
|
@@ -47,7 +47,8 @@ const Editor = ({ value = '', width = '100%', height = '400px', minHeight, maxHe
|
|
|
47
47
|
const [editYoutubeWidth, setEditYoutubeWidth] = useState('100%'); // 편집 중인 유튜브 크기
|
|
48
48
|
const [editYoutubeAlign, setEditYoutubeAlign] = useState('center'); // 편집 중인 유튜브 정렬
|
|
49
49
|
const [isCodeView, setIsCodeView] = useState(false); // 코드보기 모드
|
|
50
|
-
const [codeContent, setCodeContent] = useState(''); // 코드보기 내용
|
|
50
|
+
const [codeContent, setCodeContent] = useState(''); // 코드보기 내용 (포맷팅된 버전)
|
|
51
|
+
const [originalHtml, setOriginalHtml] = useState(''); // 원본 HTML (포맷팅 없음)
|
|
51
52
|
const [savedEditorHeight, setSavedEditorHeight] = useState(null); // 위지윅 에디터 높이 저장
|
|
52
53
|
// Undo/Redo 히스토리 관리
|
|
53
54
|
const [history, setHistory] = useState([value]);
|
|
@@ -380,12 +381,11 @@ const Editor = ({ value = '', width = '100%', height = '400px', minHeight, maxHe
|
|
|
380
381
|
// 임시 div를 만들어서 HTML 파싱
|
|
381
382
|
const tempDiv = document.createElement('div');
|
|
382
383
|
tempDiv.innerHTML = html;
|
|
383
|
-
// 지원하는
|
|
384
|
+
// 지원하는 태그 정의
|
|
384
385
|
const allowedTags = ['P', 'BR', 'STRONG', 'B', 'EM', 'I', 'U', 'S', 'STRIKE', 'DEL',
|
|
385
386
|
'H1', 'H2', 'H3', 'H4', 'H5', 'H6', 'BLOCKQUOTE', 'PRE',
|
|
386
387
|
'UL', 'OL', 'LI', 'A', 'IMG', 'SPAN', 'DIV', 'HR',
|
|
387
388
|
'TABLE', 'THEAD', 'TBODY', 'TFOOT', 'TR', 'TH', 'TD'];
|
|
388
|
-
const allowedStyles = ['color', 'background-color', 'text-align'];
|
|
389
389
|
// 모든 요소를 순회하면서 정리
|
|
390
390
|
const cleanElement = (element) => {
|
|
391
391
|
const tagName = element.tagName;
|
|
@@ -439,28 +439,7 @@ const Editor = ({ value = '', width = '100%', height = '400px', minHeight, maxHe
|
|
|
439
439
|
if (tagName === 'TH') {
|
|
440
440
|
newElement.style.fontWeight = 'bold';
|
|
441
441
|
}
|
|
442
|
-
//
|
|
443
|
-
if (element instanceof HTMLElement && element.style) {
|
|
444
|
-
// 테이블 셀의 경우 배경색과 정렬만 허용
|
|
445
|
-
if (tagName === 'TD' || tagName === 'TH') {
|
|
446
|
-
const cellAllowedStyles = ['background-color', 'text-align'];
|
|
447
|
-
cellAllowedStyles.forEach(styleName => {
|
|
448
|
-
const value = element.style.getPropertyValue(styleName);
|
|
449
|
-
if (value) {
|
|
450
|
-
newElement.style.setProperty(styleName, value);
|
|
451
|
-
}
|
|
452
|
-
});
|
|
453
|
-
}
|
|
454
|
-
else {
|
|
455
|
-
// 일반 요소는 허용된 스타일만
|
|
456
|
-
allowedStyles.forEach(styleName => {
|
|
457
|
-
const value = element.style.getPropertyValue(styleName);
|
|
458
|
-
if (value) {
|
|
459
|
-
newElement.style.setProperty(styleName, value);
|
|
460
|
-
}
|
|
461
|
-
});
|
|
462
|
-
}
|
|
463
|
-
}
|
|
442
|
+
// 붙여넣기된 요소의 style 속성은 모두 제거 (위 테이블 기본 스타일만 유지)
|
|
464
443
|
// 자식 요소 처리
|
|
465
444
|
Array.from(element.childNodes).forEach(child => {
|
|
466
445
|
if (child.nodeType === Node.ELEMENT_NODE) {
|
|
@@ -593,8 +572,9 @@ const Editor = ({ value = '', width = '100%', height = '400px', minHeight, maxHe
|
|
|
593
572
|
setSavedEditorHeight(null); // 저장된 높이 초기화
|
|
594
573
|
// 다음 렌더링 사이클에서 에디터 내용 업데이트
|
|
595
574
|
setTimeout(() => {
|
|
596
|
-
if (editorRef.current &&
|
|
597
|
-
|
|
575
|
+
if (editorRef.current && originalHtml !== undefined) {
|
|
576
|
+
// 원본 HTML을 사용 (포맷팅되지 않은 버전)
|
|
577
|
+
editorRef.current.innerHTML = originalHtml;
|
|
598
578
|
handleInput();
|
|
599
579
|
}
|
|
600
580
|
}, 0);
|
|
@@ -611,48 +591,140 @@ const Editor = ({ value = '', width = '100%', height = '400px', minHeight, maxHe
|
|
|
611
591
|
const currentHeight = editorRef.current.scrollHeight;
|
|
612
592
|
setSavedEditorHeight(currentHeight);
|
|
613
593
|
}
|
|
614
|
-
//
|
|
594
|
+
// 원본 HTML 저장 (포맷팅 없음)
|
|
615
595
|
const html = editorRef.current.innerHTML;
|
|
596
|
+
setOriginalHtml(html);
|
|
597
|
+
// 표시용으로 포맷팅된 HTML 생성
|
|
616
598
|
const formattedHtml = formatHtml(html);
|
|
617
599
|
setCodeContent(formattedHtml);
|
|
618
600
|
setIsCodeView(true);
|
|
619
601
|
}
|
|
620
602
|
}
|
|
621
603
|
};
|
|
622
|
-
// HTML 포맷팅 함수
|
|
604
|
+
// HTML 포맷팅 함수 (beautify)
|
|
623
605
|
const formatHtml = (html) => {
|
|
624
|
-
|
|
625
|
-
const formatted = html
|
|
626
|
-
.replace(/></g, '>\n<') // 태그 사이에 줄바꿈 추가
|
|
627
|
-
.replace(/(<div|<p|<h[1-6]|<ul|<ol|<li|<blockquote)/gi, '\n$1') // 블록 요소 앞에 줄바꿈
|
|
628
|
-
.replace(/(<\/div>|<\/p>|<\/h[1-6]>|<\/ul>|<\/ol>|<\/li>|<\/blockquote>)/gi, '$1\n'); // 블록 요소 뒤에 줄바꿈
|
|
629
|
-
// 들여쓰기 추가
|
|
630
|
-
const lines = formatted.split('\n');
|
|
606
|
+
let formatted = '';
|
|
631
607
|
let indentLevel = 0;
|
|
632
|
-
const
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
608
|
+
const indentSize = 2;
|
|
609
|
+
// 블록 레벨 요소 정의
|
|
610
|
+
const blockElements = [
|
|
611
|
+
'div', 'p', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6',
|
|
612
|
+
'ul', 'ol', 'li', 'blockquote', 'pre', 'table',
|
|
613
|
+
'thead', 'tbody', 'tr', 'td', 'th', 'section',
|
|
614
|
+
'article', 'header', 'footer', 'nav', 'aside', 'main'
|
|
615
|
+
];
|
|
616
|
+
// 인라인 요소 정의
|
|
617
|
+
const inlineElements = ['span', 'a', 'strong', 'b', 'em', 'i', 'u', 'code', 'small', 'sub', 'sup', 'mark'];
|
|
618
|
+
// HTML을 토큰으로 분리
|
|
619
|
+
const tokens = [];
|
|
620
|
+
let current = '';
|
|
621
|
+
let inTag = false;
|
|
622
|
+
for (let i = 0; i < html.length; i++) {
|
|
623
|
+
const char = html[i];
|
|
624
|
+
if (char === '<') {
|
|
625
|
+
if (current.trim()) {
|
|
626
|
+
tokens.push(current);
|
|
627
|
+
}
|
|
628
|
+
current = char;
|
|
629
|
+
inTag = true;
|
|
630
|
+
}
|
|
631
|
+
else if (char === '>' && inTag) {
|
|
632
|
+
current += char;
|
|
633
|
+
tokens.push(current);
|
|
634
|
+
current = '';
|
|
635
|
+
inTag = false;
|
|
636
|
+
}
|
|
637
|
+
else {
|
|
638
|
+
current += char;
|
|
639
|
+
}
|
|
640
|
+
}
|
|
641
|
+
if (current.trim()) {
|
|
642
|
+
tokens.push(current);
|
|
643
|
+
}
|
|
644
|
+
// 토큰을 순회하며 포맷팅
|
|
645
|
+
for (let i = 0; i < tokens.length; i++) {
|
|
646
|
+
const token = tokens[i];
|
|
647
|
+
const trimmedToken = token.trim();
|
|
648
|
+
if (!trimmedToken)
|
|
649
|
+
continue;
|
|
650
|
+
const prevToken = i > 0 ? tokens[i - 1]?.trim() : '';
|
|
651
|
+
// 태그인 경우
|
|
652
|
+
if (trimmedToken.startsWith('<')) {
|
|
653
|
+
// 닫는 태그
|
|
654
|
+
if (trimmedToken.startsWith('</')) {
|
|
655
|
+
const tagName = trimmedToken.match(/<\/(\w+)/)?.[1]?.toLowerCase();
|
|
656
|
+
if (tagName && blockElements.includes(tagName)) {
|
|
657
|
+
indentLevel = Math.max(0, indentLevel - 1);
|
|
658
|
+
formatted += '\n' + ' '.repeat(indentLevel * indentSize) + trimmedToken;
|
|
659
|
+
}
|
|
660
|
+
else {
|
|
661
|
+
formatted += trimmedToken;
|
|
662
|
+
}
|
|
663
|
+
}
|
|
664
|
+
// 자체 닫는 태그 (self-closing)
|
|
665
|
+
else if (trimmedToken.endsWith('/>')) {
|
|
666
|
+
const tagName = trimmedToken.match(/<(\w+)/)?.[1]?.toLowerCase();
|
|
667
|
+
if (tagName && blockElements.includes(tagName)) {
|
|
668
|
+
formatted += '\n' + ' '.repeat(indentLevel * indentSize) + trimmedToken;
|
|
669
|
+
}
|
|
670
|
+
else {
|
|
671
|
+
formatted += trimmedToken;
|
|
672
|
+
}
|
|
673
|
+
}
|
|
674
|
+
// 여는 태그
|
|
675
|
+
else {
|
|
676
|
+
const tagName = trimmedToken.match(/<(\w+)/)?.[1]?.toLowerCase();
|
|
677
|
+
// br 태그 특별 처리: 줄바꿈 후 다음 요소는 현재 레벨 유지
|
|
678
|
+
if (tagName === 'br') {
|
|
679
|
+
formatted += trimmedToken;
|
|
680
|
+
}
|
|
681
|
+
else if (tagName && blockElements.includes(tagName)) {
|
|
682
|
+
formatted += '\n' + ' '.repeat(indentLevel * indentSize) + trimmedToken;
|
|
683
|
+
// 여는 태그와 닫는 태그가 같은 줄에 있지 않으면 들여쓰기 증가
|
|
684
|
+
const nextToken = tokens[i + 1];
|
|
685
|
+
const closingTag = `</${tagName}>`;
|
|
686
|
+
if (!nextToken || !nextToken.includes(closingTag)) {
|
|
687
|
+
indentLevel++;
|
|
688
|
+
}
|
|
689
|
+
}
|
|
690
|
+
else {
|
|
691
|
+
// 인라인 요소
|
|
692
|
+
// br 태그 직후인 경우 줄바꿈과 들여쓰기 추가
|
|
693
|
+
if (prevToken === '<br>' || prevToken === '<br/>') {
|
|
694
|
+
formatted += '\n' + ' '.repeat(indentLevel * indentSize) + trimmedToken;
|
|
695
|
+
}
|
|
696
|
+
else {
|
|
697
|
+
formatted += trimmedToken;
|
|
698
|
+
}
|
|
699
|
+
}
|
|
700
|
+
}
|
|
701
|
+
}
|
|
702
|
+
// 텍스트 노드
|
|
703
|
+
else {
|
|
704
|
+
const nextToken = tokens[i + 1];
|
|
705
|
+
// br 태그 직후인 경우, 이미 줄바꿈과 들여쓰기가 추가되어 있음
|
|
706
|
+
if (prevToken === '<br>' || prevToken === '<br/>') {
|
|
707
|
+
formatted += trimmedToken;
|
|
708
|
+
}
|
|
709
|
+
// 이전이나 다음 토큰이 인라인 요소면 공백 없이 추가
|
|
710
|
+
else if (prevToken && inlineElements.some(tag => prevToken.includes(`<${tag}`)) ||
|
|
711
|
+
nextToken && inlineElements.some(tag => nextToken.includes(`</${tag}`))) {
|
|
712
|
+
formatted += trimmedToken;
|
|
713
|
+
}
|
|
714
|
+
else {
|
|
715
|
+
formatted += trimmedToken;
|
|
716
|
+
}
|
|
717
|
+
}
|
|
718
|
+
}
|
|
719
|
+
// 앞뒤 공백 제거 및 연속된 빈 줄 정리
|
|
720
|
+
return formatted.trim().replace(/\n\s*\n\s*\n/g, '\n\n');
|
|
652
721
|
};
|
|
653
722
|
// 코드 에디터 내용 변경 처리
|
|
654
723
|
const handleCodeChange = (e) => {
|
|
655
|
-
|
|
724
|
+
const newContent = e.target.value;
|
|
725
|
+
setCodeContent(newContent);
|
|
726
|
+
// 사용자가 코드를 수정하면 원본 HTML도 업데이트
|
|
727
|
+
setOriginalHtml(newContent);
|
|
656
728
|
};
|
|
657
729
|
const applyParagraphStyle = (value) => {
|
|
658
730
|
// 빈 값이면 본문으로 설정
|