sunny-html-editor 1.2.4 → 1.3.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/README.md CHANGED
@@ -4,6 +4,8 @@
4
4
 
5
5
  ## バージョン履歴
6
6
 
7
+ - **1.3.0** - Mermaid図表示対応、Excel出力にMermaid画像埋め込み、テーブル全セルで` / `改行対応、長文字列のword-break対応、PDF出力機能削除
8
+ - **1.2.5** - サイドバー開閉時の横スクロールバー問題を修正
7
9
  - **1.2.4** - h1改行を最初の「-」「_」のみに限定
8
10
  - **1.2.3** - サイドバーh1の改行(br)維持を修正
9
11
  - **1.2.2** - パッケージ修正
package/html-editor.css CHANGED
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * Sunny HTML Editor - スタイルシート
3
- * @version 1.0.0
3
+ * @version 1.3.0
4
4
  */
5
5
 
6
6
  * {
@@ -189,6 +189,7 @@ th, td {
189
189
  padding: 10px;
190
190
  text-align: left;
191
191
  vertical-align: top;
192
+ word-break: break-all;
192
193
  }
193
194
  th {
194
195
  background: linear-gradient(180deg, #f8f8f8 0%, #efefef 100%);
@@ -389,3 +390,18 @@ del {
389
390
  .sidebar {
390
391
  transition: transform 0.3s ease;
391
392
  }
393
+
394
+ /* Mermaid図 */
395
+ .mermaid-container {
396
+ margin: 15px 0 15px 20px;
397
+ padding: 15px;
398
+ background: #fafafa;
399
+ border: 1px solid #e0e0e0;
400
+ border-radius: 4px;
401
+ overflow-x: auto;
402
+ text-align: center;
403
+ }
404
+ .mermaid-container svg {
405
+ max-width: 100%;
406
+ height: auto;
407
+ }
package/html-editor.js CHANGED
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * Sunny HTML Editor - 軽量WYSIWYGエディタ
3
- * @version 1.2.4
3
+ * @version 1.3.0
4
4
  */
5
5
 
6
6
  // ========== HTML生成関数 ==========
@@ -12,7 +12,6 @@ function getToolbarHtml() {
12
12
  '<button id="exportHtml" title="HTMLコピー">🌐</button>' +
13
13
  '<button id="saveHtml" title="HTML保存">💾</button>' +
14
14
  '<button id="exportExcel" title="Excel出力">📊</button>' +
15
- '<button id="exportPdf" title="PDF出力">📕</button>' +
16
15
  '</div>';
17
16
  }
18
17
 
@@ -110,18 +109,43 @@ function loadScript(src) {
110
109
  return libraryPromises[src];
111
110
  }
112
111
 
113
- async function ensurePdfLibraries() {
114
- if (!window.jspdf) {
115
- await loadScript('https://cdnjs.cloudflare.com/ajax/libs/jspdf/2.5.1/jspdf.umd.min.js');
112
+ async function ensureExcelLibrary() {
113
+ if (!window.ExcelJS) {
114
+ await loadScript('https://cdnjs.cloudflare.com/ajax/libs/exceljs/4.3.0/exceljs.min.js');
116
115
  }
117
- if (!window.html2canvas) {
118
- await loadScript('https://cdnjs.cloudflare.com/ajax/libs/html2canvas/1.4.1/html2canvas.min.js');
116
+ }
117
+
118
+ async function ensureMermaidLibrary() {
119
+ if (!window.mermaid) {
120
+ await loadScript('https://cdn.jsdelivr.net/npm/mermaid@10/dist/mermaid.min.js');
121
+ window.mermaid.initialize({ startOnLoad: false, theme: 'default' });
119
122
  }
120
123
  }
121
124
 
122
- async function ensureExcelLibrary() {
123
- if (!window.ExcelJS) {
124
- await loadScript('https://cdnjs.cloudflare.com/ajax/libs/exceljs/4.3.0/exceljs.min.js');
125
+ async function renderMermaidBlocks(root) {
126
+ var codeBlocks = root.querySelectorAll('pre > code.language-mermaid');
127
+ if (codeBlocks.length === 0) return;
128
+
129
+ await ensureMermaidLibrary();
130
+
131
+ for (var i = 0; i < codeBlocks.length; i++) {
132
+ var code = codeBlocks[i];
133
+ var pre = code.parentElement;
134
+ var mermaidText = code.textContent;
135
+
136
+ var wrapper = document.createElement('div');
137
+ wrapper.className = 'mermaid-container';
138
+ wrapper.dataset.mermaidSource = mermaidText;
139
+
140
+ try {
141
+ var id = 'mermaid-' + Date.now() + '-' + i;
142
+ var result = await window.mermaid.render(id, mermaidText);
143
+ wrapper.innerHTML = result.svg;
144
+ } catch (err) {
145
+ wrapper.innerHTML = '<pre style="color:#c00;border:1px solid #c00;padding:10px;border-radius:4px;">Mermaid描画エラー: ' + err.message + '</pre>';
146
+ }
147
+
148
+ pre.replaceWith(wrapper);
125
149
  }
126
150
  }
127
151
 
@@ -250,11 +274,6 @@ function convertMarkdownToHtml(md) {
250
274
  else if (colCount === 3) tableClass = ' class="table-3col"';
251
275
  else tableClass = ' class="table-multi"';
252
276
 
253
- var choiceColIndex = -1;
254
- for (var j = 0; j < t.headers.length; j++) {
255
- if (t.headers[j] === '選択肢') { choiceColIndex = j; break; }
256
- }
257
-
258
277
  var tableHtml = '<table' + tableClass + '>\n<tr>\n';
259
278
  for (var j = 0; j < t.headers.length; j++) {
260
279
  tableHtml += '<th>' + convertMdInline(escapeHtmlForMd(t.headers[j])) + '</th>\n';
@@ -264,7 +283,7 @@ function convertMarkdownToHtml(md) {
264
283
  tableHtml += '<tr>\n';
265
284
  for (var k = 0; k < t.rows[j].length; k++) {
266
285
  var cellContent = convertMdInline(escapeHtmlForMd(t.rows[j][k]));
267
- if (k === choiceColIndex) cellContent = cellContent.replace(/ \/ /g, '<br>');
286
+ cellContent = cellContent.replace(/ \/ /g, '<br>');
268
287
  tableHtml += '<td>' + cellContent + '</td>\n';
269
288
  }
270
289
  tableHtml += '</tr>\n';
@@ -462,6 +481,9 @@ document.addEventListener('DOMContentLoaded', function() {
462
481
  // ツールバー挿入
463
482
  container.insertAdjacentHTML('afterbegin', getToolbarHtml());
464
483
 
484
+ // Mermaidブロックを描画
485
+ renderMermaidBlocks(container);
486
+
465
487
  // コンテキストメニュー挿入(bodyの末尾に配置)
466
488
  document.body.insertAdjacentHTML('beforeend', getContextMenusHtml());
467
489
 
@@ -472,7 +494,6 @@ document.addEventListener('DOMContentLoaded', function() {
472
494
  var exportMdBtn = document.getElementById('exportMd');
473
495
  var exportHtmlBtn = document.getElementById('exportHtml');
474
496
  var exportExcelBtn = document.getElementById('exportExcel');
475
- var exportPdfBtn = document.getElementById('exportPdf');
476
497
  var isEditMode = false;
477
498
  var sectionCounter = { h2: 100, h3: 100, h4: 100 };
478
499
 
@@ -502,6 +523,21 @@ document.addEventListener('DOMContentLoaded', function() {
502
523
  }, 10);
503
524
  }
504
525
 
526
+ // ========== Mermaidコンテナ復元ヘルパー ==========
527
+ function restoreMermaidToPre(root) {
528
+ root.querySelectorAll('.mermaid-container').forEach(function(container) {
529
+ var source = container.dataset.mermaidSource;
530
+ if (source) {
531
+ var pre = document.createElement('pre');
532
+ var code = document.createElement('code');
533
+ code.className = 'language-mermaid';
534
+ code.textContent = source;
535
+ pre.appendChild(code);
536
+ container.replaceWith(pre);
537
+ }
538
+ });
539
+ }
540
+
505
541
  // ========== HTML保存 ==========
506
542
  if (saveHtmlBtn) saveHtmlBtn.addEventListener('click', function() {
507
543
  var wasEditMode = isEditMode;
@@ -521,6 +557,9 @@ document.addEventListener('DOMContentLoaded', function() {
521
557
  if (cloneToolbar) cloneToolbar.remove();
522
558
  cloneMenus.forEach(function(menu) { menu.remove(); });
523
559
 
560
+ // Mermaidコンテナをpre/codeに復元(再読み込み時に再描画可能にする)
561
+ restoreMermaidToPre(docClone);
562
+
524
563
  var html = '<!DOCTYPE html>\n' + docClone.outerHTML;
525
564
 
526
565
  // 表示を復元
@@ -595,6 +634,9 @@ document.addEventListener('DOMContentLoaded', function() {
595
634
  // edit-modeクラスを除去
596
635
  content.classList.remove('edit-mode');
597
636
 
637
+ // Mermaidコンテナをpre/codeに復元
638
+ restoreMermaidToPre(content);
639
+
598
640
  // id属性を除去(containerのid)
599
641
  content.removeAttribute('id');
600
642
 
@@ -726,7 +768,7 @@ document.addEventListener('DOMContentLoaded', function() {
726
768
  if (!content) content = container;
727
769
 
728
770
  // 対象要素を拡張(ul, ol, pre, blockquote を追加)
729
- var elements = content.querySelectorAll('h1, h2, h3, h4, p, table, hr, ul, ol, pre, blockquote');
771
+ var elements = content.querySelectorAll('h1, h2, h3, h4, p, table, hr, ul, ol, pre, blockquote, .mermaid-container');
730
772
 
731
773
  // 処理済みの要素を追跡(ネストされたリストの重複処理を防ぐ)
732
774
  var processed = new Set();
@@ -779,34 +821,26 @@ document.addEventListener('DOMContentLoaded', function() {
779
821
  lines = lines.concat(bqLines);
780
822
  lines.push('');
781
823
  processed.add(el);
824
+ } else if (el.classList && el.classList.contains('mermaid-container')) {
825
+ var mermaidSource = el.dataset.mermaidSource;
826
+ if (mermaidSource) {
827
+ lines.push('```mermaid');
828
+ lines.push(mermaidSource);
829
+ lines.push('```');
830
+ lines.push('');
831
+ }
832
+ processed.add(el);
782
833
  } else if (tag === 'TABLE') {
783
834
  var rows = el.querySelectorAll('tr');
784
835
 
785
- // ヘッダー行から「選択肢」列のインデックスを特定
786
- var choiceColIndex = -1;
787
- if (rows.length > 0) {
788
- var headerCells = rows[0].querySelectorAll('th, td');
789
- headerCells.forEach(function(cell, index) {
790
- if (cell.textContent.trim() === '選択肢') {
791
- choiceColIndex = index;
792
- }
793
- });
794
- }
795
-
796
836
  rows.forEach(function(row, rowIndex) {
797
837
  var cells = row.querySelectorAll('th, td');
798
838
  var cellTexts = [];
799
839
  cells.forEach(function(cell, cellIndex) {
800
- var text;
801
- // 「選択肢」列かつデータ行の場合は<br>を / に変換
802
- if (cellIndex === choiceColIndex && rowIndex > 0) {
803
- var tempDiv = document.createElement('div');
804
- tempDiv.innerHTML = cell.innerHTML;
805
- tempDiv.innerHTML = tempDiv.innerHTML.replace(/<br\s*\/?>/gi, ' / ');
806
- text = tempDiv.textContent.trim();
807
- } else {
808
- text = cell.textContent.trim();
809
- }
840
+ var tempDiv = document.createElement('div');
841
+ tempDiv.innerHTML = cell.innerHTML;
842
+ tempDiv.innerHTML = tempDiv.innerHTML.replace(/<br\s*\/?>/gi, ' / ');
843
+ var text = tempDiv.textContent.trim();
810
844
  cellTexts.push(text);
811
845
  });
812
846
  lines.push('| ' + cellTexts.join(' | ') + ' |');
@@ -895,7 +929,10 @@ document.addEventListener('DOMContentLoaded', function() {
895
929
  if (newWidth < 200) newWidth = 200;
896
930
  if (newWidth > 400) newWidth = 400;
897
931
  s.style.width = newWidth + 'px';
898
- if (main) main.style.marginLeft = newWidth + 'px';
932
+ if (main) {
933
+ main.style.marginLeft = newWidth + 'px';
934
+ main.style.width = 'calc(100% - ' + newWidth + 'px)';
935
+ }
899
936
  updateResizerPosition();
900
937
  });
901
938
 
@@ -917,7 +954,10 @@ document.addEventListener('DOMContentLoaded', function() {
917
954
  resizerHtml = r.outerHTML;
918
955
  s.remove();
919
956
  r.remove();
920
- if (main) main.style.marginLeft = '0';
957
+ if (main) {
958
+ main.style.marginLeft = '0';
959
+ main.style.width = '100%';
960
+ }
921
961
  sidebarExists = false;
922
962
  } else if (!sidebarExists && main) {
923
963
  main.insertAdjacentHTML('beforebegin', sidebarHtml);
@@ -925,6 +965,7 @@ document.addEventListener('DOMContentLoaded', function() {
925
965
  var newSidebar = document.querySelector('.sidebar');
926
966
  if (newSidebar) newSidebar.style.width = savedWidth + 'px';
927
967
  main.style.marginLeft = savedWidth + 'px';
968
+ main.style.width = 'calc(100% - ' + savedWidth + 'px)';
928
969
  setupResizer();
929
970
  updateResizerPosition();
930
971
  updateActiveLink();
@@ -1822,201 +1863,6 @@ document.addEventListener('DOMContentLoaded', function() {
1822
1863
  }
1823
1864
  }
1824
1865
 
1825
- // ========== PDF出力 ==========
1826
- if (exportPdfBtn) exportPdfBtn.addEventListener('click', async function() {
1827
- var wasEditMode = isEditMode;
1828
- if (isEditMode && editModeBtn) editModeBtn.click();
1829
-
1830
- exportPdfBtn.textContent = '...';
1831
-
1832
- try {
1833
- // ライブラリ遅延読み込み
1834
- await ensurePdfLibraries();
1835
-
1836
- // jsPDFインスタンス作成
1837
- var { jsPDF } = window.jspdf;
1838
- var pdf = new jsPDF({
1839
- unit: 'mm',
1840
- format: 'a4',
1841
- orientation: 'portrait'
1842
- });
1843
-
1844
- var pageWidth = 210;
1845
- var pageHeight = 297;
1846
- var margin = 15;
1847
- var contentWidth = pageWidth - margin * 2;
1848
- var contentHeight = pageHeight - margin * 2;
1849
- var currentY = margin;
1850
- var currentPage = 1;
1851
-
1852
- // しおり情報
1853
- var bookmarks = [];
1854
-
1855
- // コンテンツを複製してPDF用に整形
1856
- var content = container.cloneNode(true);
1857
-
1858
- // ツールバーと右クリックメニューを除去
1859
- var toolbar = content.querySelector('.toolbar');
1860
- if (toolbar) toolbar.remove();
1861
- content.querySelectorAll('.context-menu').forEach(function(menu) {
1862
- menu.remove();
1863
- });
1864
- content.querySelectorAll('[contenteditable]').forEach(function(el) {
1865
- el.removeAttribute('contenteditable');
1866
- });
1867
-
1868
- // 一時的にDOMに追加(html2canvas用)
1869
- content.style.position = 'absolute';
1870
- content.style.left = '-9999px';
1871
- content.style.width = contentWidth * 3.78 + 'px'; // mm to px
1872
- content.style.background = 'white';
1873
- content.style.padding = '0';
1874
- document.body.appendChild(content);
1875
-
1876
- // h2で分割してセクションごとに処理
1877
- var allElements = content.querySelectorAll('h1, h2, h3, h4, p, ul, ol, pre, blockquote, table, hr');
1878
- var sections = [];
1879
- var currentSection = { elements: [], h2Text: null, h3List: [] };
1880
-
1881
- allElements.forEach(function(el) {
1882
- // ネストされたリストはスキップ(親リストで処理される)
1883
- if ((el.tagName === 'UL' || el.tagName === 'OL') && el.parentElement.closest('ul, ol')) {
1884
- return;
1885
- }
1886
-
1887
- if (el.tagName === 'H2') {
1888
- if (currentSection.elements.length > 0 || currentSection.h2Text) {
1889
- sections.push(currentSection);
1890
- }
1891
- currentSection = { elements: [el], h2Text: el.textContent.trim(), h3List: [] };
1892
- } else if (el.tagName === 'H3') {
1893
- currentSection.elements.push(el);
1894
- currentSection.h3List.push({ text: el.textContent.trim(), pageNum: null });
1895
- } else {
1896
- currentSection.elements.push(el);
1897
- }
1898
- });
1899
- if (currentSection.elements.length > 0) {
1900
- sections.push(currentSection);
1901
- }
1902
-
1903
- // 各セクションを処理
1904
- for (var i = 0; i < sections.length; i++) {
1905
- var section = sections[i];
1906
-
1907
- // セクション内の要素を一つのdivにまとめる
1908
- var sectionDiv = document.createElement('div');
1909
- sectionDiv.style.background = 'white';
1910
- sectionDiv.style.padding = '10px';
1911
- section.elements.forEach(function(el) {
1912
- sectionDiv.appendChild(el.cloneNode(true));
1913
- });
1914
- content.innerHTML = '';
1915
- content.appendChild(sectionDiv);
1916
-
1917
- // html2canvasでキャプチャ
1918
- var canvas = await html2canvas(sectionDiv, {
1919
- scale: 2,
1920
- useCORS: true,
1921
- backgroundColor: '#ffffff'
1922
- });
1923
-
1924
- var imgData = canvas.toDataURL('image/jpeg', 0.95);
1925
- var imgWidth = contentWidth;
1926
- var imgHeight = (canvas.height * imgWidth) / canvas.width;
1927
-
1928
- // ページに収まるかチェック
1929
- if (currentY + imgHeight > pageHeight - margin) {
1930
- // 新しいページを追加
1931
- pdf.addPage();
1932
- currentPage++;
1933
- currentY = margin;
1934
- }
1935
-
1936
- // セクションのh2をしおりに追加(ページ確定後)
1937
- if (section.h2Text) {
1938
- bookmarks.push({
1939
- text: section.h2Text,
1940
- page: currentPage,
1941
- level: 1,
1942
- children: []
1943
- });
1944
- }
1945
-
1946
- // h3のページ番号を記録
1947
- if (section.h3List.length > 0 && bookmarks.length > 0) {
1948
- var parentBookmark = bookmarks[bookmarks.length - 1];
1949
- section.h3List.forEach(function(h3) {
1950
- parentBookmark.children.push({
1951
- text: h3.text,
1952
- page: currentPage,
1953
- level: 2
1954
- });
1955
- });
1956
- }
1957
-
1958
- // 画像が大きすぎる場合は分割
1959
- if (imgHeight > contentHeight) {
1960
- var remainingHeight = imgHeight;
1961
- var sourceY = 0;
1962
-
1963
- while (remainingHeight > 0) {
1964
- var drawHeight = Math.min(contentHeight - (currentY - margin), remainingHeight);
1965
- var drawHeightPx = (drawHeight / imgHeight) * canvas.height;
1966
-
1967
- // キャンバスの一部を切り出し
1968
- var partCanvas = document.createElement('canvas');
1969
- partCanvas.width = canvas.width;
1970
- partCanvas.height = drawHeightPx;
1971
- var ctx = partCanvas.getContext('2d');
1972
- ctx.drawImage(canvas, 0, sourceY, canvas.width, drawHeightPx, 0, 0, canvas.width, drawHeightPx);
1973
-
1974
- var partImgData = partCanvas.toDataURL('image/jpeg', 0.95);
1975
- pdf.addImage(partImgData, 'JPEG', margin, currentY, imgWidth, drawHeight);
1976
-
1977
- sourceY += drawHeightPx;
1978
- remainingHeight -= drawHeight;
1979
-
1980
- if (remainingHeight > 0) {
1981
- pdf.addPage();
1982
- currentPage++;
1983
- currentY = margin;
1984
- } else {
1985
- currentY += drawHeight + 5;
1986
- }
1987
- }
1988
- } else {
1989
- pdf.addImage(imgData, 'JPEG', margin, currentY, imgWidth, imgHeight);
1990
- currentY += imgHeight + 5;
1991
- }
1992
- }
1993
-
1994
- // 一時要素を削除
1995
- document.body.removeChild(content);
1996
-
1997
- // しおりを追加
1998
- bookmarks.forEach(function(bm) {
1999
- var parent = pdf.outline.add(null, bm.text, { pageNumber: bm.page });
2000
- bm.children.forEach(function(child) {
2001
- pdf.outline.add(parent, child.text, { pageNumber: child.page });
2002
- });
2003
- });
2004
-
2005
- // タイトル取得
2006
- var h1 = container.querySelector('h1');
2007
- var fileName = h1 ? h1.textContent.trim().replace(/[\\/:*?"<>|]/g, '_') : 'document';
2008
-
2009
- // PDF保存
2010
- pdf.save(fileName + '.pdf');
2011
-
2012
- } catch (error) {
2013
- console.error('PDF生成エラー:', error);
2014
- alert('PDF生成中にエラーが発生しました: ' + error.message);
2015
- }
2016
-
2017
- exportPdfBtn.textContent = '📕';
2018
- if (wasEditMode && editModeBtn) editModeBtn.click();
2019
- });
2020
1866
 
2021
1867
  // ========== Excel出力 ==========
2022
1868
  var fontSizes = {
@@ -2025,6 +1871,56 @@ document.addEventListener('DOMContentLoaded', function() {
2025
1871
  body: 12
2026
1872
  };
2027
1873
 
1874
+ function svgToPngBase64(svgElement) {
1875
+ return new Promise(function(resolve, reject) {
1876
+ try {
1877
+ // SVGの実サイズを取得
1878
+ var rect = svgElement.getBoundingClientRect();
1879
+ var svgWidth = rect.width || parseInt(svgElement.getAttribute('width')) || 800;
1880
+ var svgHeight = rect.height || parseInt(svgElement.getAttribute('height')) || 400;
1881
+
1882
+ // SVGクローンにサイズを明示設定(Image読み込み時に必要)
1883
+ var cloneSvg = svgElement.cloneNode(true);
1884
+ cloneSvg.setAttribute('width', svgWidth);
1885
+ cloneSvg.setAttribute('height', svgHeight);
1886
+ if (!cloneSvg.getAttribute('xmlns')) {
1887
+ cloneSvg.setAttribute('xmlns', 'http://www.w3.org/2000/svg');
1888
+ }
1889
+
1890
+ var svgData = new XMLSerializer().serializeToString(cloneSvg);
1891
+ var svgBlob = new Blob([svgData], { type: 'image/svg+xml;charset=utf-8' });
1892
+ var url = URL.createObjectURL(svgBlob);
1893
+
1894
+ var img = new Image();
1895
+ img.onload = function() {
1896
+ var scale = 2;
1897
+ var w = img.naturalWidth || svgWidth;
1898
+ var h = img.naturalHeight || svgHeight;
1899
+ var canvas = document.createElement('canvas');
1900
+ canvas.width = w * scale;
1901
+ canvas.height = h * scale;
1902
+ var ctx = canvas.getContext('2d');
1903
+ ctx.fillStyle = '#ffffff';
1904
+ ctx.fillRect(0, 0, canvas.width, canvas.height);
1905
+ ctx.drawImage(img, 0, 0, canvas.width, canvas.height);
1906
+
1907
+ URL.revokeObjectURL(url);
1908
+
1909
+ var dataUrl = canvas.toDataURL('image/png');
1910
+ var base64 = dataUrl.replace(/^data:image\/png;base64,/, '');
1911
+ resolve(base64);
1912
+ };
1913
+ img.onerror = function() {
1914
+ URL.revokeObjectURL(url);
1915
+ reject(new Error('SVG→PNG変換に失敗'));
1916
+ };
1917
+ img.src = url;
1918
+ } catch (err) {
1919
+ reject(err);
1920
+ }
1921
+ });
1922
+ }
1923
+
2028
1924
  if (exportExcelBtn) exportExcelBtn.addEventListener('click', async function() {
2029
1925
  try {
2030
1926
  await convertToExcel();
@@ -2103,7 +1999,7 @@ document.addEventListener('DOMContentLoaded', function() {
2103
1999
  if (h2Elements.length === 0) {
2104
2000
  var h1 = content.querySelector('h1');
2105
2001
  var title = h1 ? h1.textContent.trim() : 'Sheet1';
2106
- var elements = content.querySelectorAll('h1, h2, h3, h4, p, table, hr, ul, ol, pre, blockquote');
2002
+ var elements = content.querySelectorAll('h1, h2, h3, h4, p, table, hr, ul, ol, pre, blockquote, .mermaid-container');
2107
2003
  sections.push({
2108
2004
  title: title,
2109
2005
  elements: Array.from(elements),
@@ -2119,7 +2015,8 @@ document.addEventListener('DOMContentLoaded', function() {
2119
2015
  var next = h2.nextElementSibling;
2120
2016
 
2121
2017
  while (next && next.tagName !== 'H2' && next.tagName !== 'HR') {
2122
- if (['H3', 'H4', 'P', 'TABLE', 'UL', 'OL', 'PRE', 'BLOCKQUOTE'].includes(next.tagName)) {
2018
+ if (['H3', 'H4', 'P', 'TABLE', 'UL', 'OL', 'PRE', 'BLOCKQUOTE'].includes(next.tagName) ||
2019
+ (next.classList && next.classList.contains('mermaid-container'))) {
2123
2020
  elements.push(next);
2124
2021
  }
2125
2022
  next = next.nextElementSibling;
@@ -2266,6 +2163,45 @@ document.addEventListener('DOMContentLoaded', function() {
2266
2163
  }
2267
2164
 
2268
2165
  async function processElement(worksheet, element, currentRow, maxColumns) {
2166
+ // Mermaidコンテナの場合はDIVなのでclassで判定
2167
+ if (element.classList && element.classList.contains('mermaid-container')) {
2168
+ var svg = element.querySelector('svg');
2169
+ if (svg) {
2170
+ try {
2171
+ var pngBase64 = await svgToPngBase64(svg);
2172
+ var imageId = worksheet.workbook.addImage({
2173
+ base64: pngBase64,
2174
+ extension: 'png'
2175
+ });
2176
+
2177
+ // SVGの実サイズからExcel上のサイズを計算(縦横比保持)
2178
+ var svgRect = svg.getBoundingClientRect();
2179
+ var svgW = svgRect.width || 800;
2180
+ var svgH = svgRect.height || 400;
2181
+
2182
+ // Excel上の表示幅を600pxに制限し、縦横比を保持
2183
+ var maxWidthPx = 600;
2184
+ var displayW = Math.min(svgW, maxWidthPx);
2185
+ var displayH = svgH * (displayW / svgW);
2186
+ var rowsNeeded = Math.ceil(displayH / 20);
2187
+
2188
+ worksheet.addImage(imageId, {
2189
+ tl: { col: 0, row: currentRow - 1 },
2190
+ ext: { width: displayW, height: displayH }
2191
+ });
2192
+
2193
+ currentRow += rowsNeeded + 1;
2194
+ } catch (err) {
2195
+ // 画像変換失敗時はテキストで代替
2196
+ var fallbackCell = worksheet.getCell(currentRow, 1);
2197
+ fallbackCell.value = '[Mermaid図]';
2198
+ fallbackCell.font = { name: 'メイリオ', size: fontSizes.body, italic: true, color: { argb: 'FF999999' } };
2199
+ currentRow++;
2200
+ }
2201
+ }
2202
+ return currentRow;
2203
+ }
2204
+
2269
2205
  var tag = element.tagName;
2270
2206
 
2271
2207
  switch (tag) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sunny-html-editor",
3
- "version": "1.2.4",
3
+ "version": "1.3.0",
4
4
  "description": "軽量WYSIWYGエディタ - 既存のHTMLに編集機能を後付けで追加、Markdown変換対応",
5
5
  "main": "html-editor.js",
6
6
  "files": [
@@ -13,7 +13,8 @@
13
13
  "editor",
14
14
  "html",
15
15
  "contenteditable",
16
- "markdown"
16
+ "markdown",
17
+ "mermaid"
17
18
  ],
18
19
  "author": "",
19
20
  "license": "MIT"