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 +2 -0
- package/html-editor.css +17 -1
- package/html-editor.js +174 -238
- package/package.json +3 -2
package/README.md
CHANGED
package/html-editor.css
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Sunny HTML Editor - スタイルシート
|
|
3
|
-
* @version 1.
|
|
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.
|
|
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
|
|
114
|
-
if (!window.
|
|
115
|
-
await loadScript('https://cdnjs.cloudflare.com/ajax/libs/
|
|
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
|
-
|
|
118
|
-
|
|
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
|
|
123
|
-
|
|
124
|
-
|
|
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
|
-
|
|
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
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
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)
|
|
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)
|
|
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.
|
|
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"
|