podo-ui 0.7.0 → 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.
@@ -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,4CAovIb,CAAC;AAEF,eAAe,MAAM,CAAC"}
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 && codeContent !== undefined) {
597
- editorRef.current.innerHTML = codeContent;
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
- // 현재 HTML 포맷팅
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
- // 기본적인 HTML 포맷팅
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 indentedLines = lines.map(line => {
633
- const trimmed = line.trim();
634
- if (!trimmed)
635
- return '';
636
- // 닫는 태그인 경우 들여쓰기 레벨 감소
637
- if (trimmed.startsWith('</')) {
638
- indentLevel = Math.max(0, indentLevel - 1);
639
- const indented = ' '.repeat(indentLevel) + trimmed;
640
- return indented;
641
- }
642
- // 자체 닫는 태그가 아닌 여는 태그인 경우
643
- const indented = ' '.repeat(indentLevel) + trimmed;
644
- if (trimmed.startsWith('<') && !trimmed.startsWith('<!') &&
645
- !trimmed.endsWith('/>') && trimmed.includes('>') &&
646
- !trimmed.includes('</')) {
647
- indentLevel++;
648
- }
649
- return indented;
650
- });
651
- return indentedLines.filter(line => line !== '').join('\n');
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
- setCodeContent(e.target.value);
724
+ const newContent = e.target.value;
725
+ setCodeContent(newContent);
726
+ // 사용자가 코드를 수정하면 원본 HTML도 업데이트
727
+ setOriginalHtml(newContent);
656
728
  };
657
729
  const applyParagraphStyle = (value) => {
658
730
  // 빈 값이면 본문으로 설정
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "podo-ui",
3
- "version": "0.7.0",
3
+ "version": "0.7.2",
4
4
  "type": "module",
5
5
  "author": "hada0127 <work@tarucy.net>",
6
6
  "license": "MIT",