sunny-html-editor 1.2.5 → 1.3.1
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 +1 -0
- package/html-editor.css +17 -1
- package/html-editor.js +306 -260
- 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.1
|
|
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,41 +523,43 @@ 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保存 ==========
|
|
542
|
+
var EDITOR_CDN_BASE = 'https://cdn.jsdelivr.net/npm/sunny-html-editor@1.3.1';
|
|
543
|
+
|
|
506
544
|
if (saveHtmlBtn) saveHtmlBtn.addEventListener('click', function() {
|
|
507
545
|
var wasEditMode = isEditMode;
|
|
508
546
|
if (isEditMode && editModeBtn) editModeBtn.click();
|
|
509
547
|
|
|
510
|
-
//
|
|
511
|
-
var
|
|
512
|
-
var
|
|
513
|
-
|
|
514
|
-
if (toolbar) toolbar.style.display = 'none';
|
|
515
|
-
contextMenus.forEach(function(menu) { menu.style.display = 'none'; });
|
|
516
|
-
|
|
517
|
-
// HTML取得(ツールバーとコンテキストメニューを除外したクリーンなHTML)
|
|
518
|
-
var docClone = document.documentElement.cloneNode(true);
|
|
519
|
-
var cloneToolbar = docClone.querySelector('.toolbar');
|
|
520
|
-
var cloneMenus = docClone.querySelectorAll('.context-menu');
|
|
548
|
+
// コンテンツHTML取得(クリーン版)
|
|
549
|
+
var contentClone = container.cloneNode(true);
|
|
550
|
+
var cloneToolbar = contentClone.querySelector('.toolbar');
|
|
551
|
+
var cloneMenus = contentClone.querySelectorAll('.context-menu');
|
|
521
552
|
if (cloneToolbar) cloneToolbar.remove();
|
|
522
553
|
cloneMenus.forEach(function(menu) { menu.remove(); });
|
|
554
|
+
restoreMermaidToPre(contentClone);
|
|
555
|
+
var contentHtml = contentClone.innerHTML;
|
|
523
556
|
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
// 表示を復元
|
|
527
|
-
if (toolbar) toolbar.style.display = '';
|
|
528
|
-
contextMenus.forEach(function(menu) { menu.style.display = ''; });
|
|
529
|
-
|
|
530
|
-
if (wasEditMode && editModeBtn) editModeBtn.click();
|
|
531
|
-
|
|
532
|
-
var blob = new Blob([html], { type: 'text/html; charset=utf-8' });
|
|
533
|
-
var url = URL.createObjectURL(blob);
|
|
534
|
-
var a = document.createElement('a');
|
|
535
|
-
a.href = url;
|
|
536
|
-
|
|
557
|
+
// タイトル・ファイル名取得
|
|
537
558
|
var h1Element = container.querySelector('h1');
|
|
559
|
+
var title = 'Design Spec';
|
|
538
560
|
var fileName = 'design_spec';
|
|
539
561
|
if (h1Element) {
|
|
562
|
+
title = h1Element.textContent.trim();
|
|
540
563
|
fileName = h1Element.innerHTML
|
|
541
564
|
.replace(/<br\s*\/?>/gi, '-')
|
|
542
565
|
.replace(/<\/p>\s*<p>/gi, '-')
|
|
@@ -544,9 +567,36 @@ document.addEventListener('DOMContentLoaded', function() {
|
|
|
544
567
|
.replace(/\s+/g, '')
|
|
545
568
|
.trim();
|
|
546
569
|
}
|
|
570
|
+
|
|
571
|
+
// CDN参照の単一HTMLファイル組み立て
|
|
572
|
+
var html = '<!DOCTYPE html>\n' +
|
|
573
|
+
'<html lang="ja">\n' +
|
|
574
|
+
'<head>\n' +
|
|
575
|
+
'<meta charset="UTF-8">\n' +
|
|
576
|
+
'<title>' + title.replace(/</g, '<') + '</title>\n' +
|
|
577
|
+
'<link rel="stylesheet" href="' + EDITOR_CDN_BASE + '/html-editor.css">\n' +
|
|
578
|
+
'</head>\n' +
|
|
579
|
+
'<body>\n' +
|
|
580
|
+
'<div class="layout">\n' +
|
|
581
|
+
' <main class="main">\n' +
|
|
582
|
+
' <div class="container" id="editorContainer">\n' +
|
|
583
|
+
contentHtml + '\n' +
|
|
584
|
+
' </div>\n' +
|
|
585
|
+
' </main>\n' +
|
|
586
|
+
'</div>\n' +
|
|
587
|
+
'<script src="' + EDITOR_CDN_BASE + '/html-editor.js"><\/script>\n' +
|
|
588
|
+
'</body>\n' +
|
|
589
|
+
'</html>';
|
|
590
|
+
|
|
591
|
+
var blob = new Blob([html], { type: 'text/html; charset=utf-8' });
|
|
592
|
+
var url = URL.createObjectURL(blob);
|
|
593
|
+
var a = document.createElement('a');
|
|
594
|
+
a.href = url;
|
|
547
595
|
a.download = fileName + '.html';
|
|
548
596
|
a.click();
|
|
549
597
|
URL.revokeObjectURL(url);
|
|
598
|
+
|
|
599
|
+
if (wasEditMode && editModeBtn) editModeBtn.click();
|
|
550
600
|
});
|
|
551
601
|
|
|
552
602
|
// ========== Markdown出力 ==========
|
|
@@ -595,6 +645,9 @@ document.addEventListener('DOMContentLoaded', function() {
|
|
|
595
645
|
// edit-modeクラスを除去
|
|
596
646
|
content.classList.remove('edit-mode');
|
|
597
647
|
|
|
648
|
+
// Mermaidコンテナをpre/codeに復元
|
|
649
|
+
restoreMermaidToPre(content);
|
|
650
|
+
|
|
598
651
|
// id属性を除去(containerのid)
|
|
599
652
|
content.removeAttribute('id');
|
|
600
653
|
|
|
@@ -726,7 +779,7 @@ document.addEventListener('DOMContentLoaded', function() {
|
|
|
726
779
|
if (!content) content = container;
|
|
727
780
|
|
|
728
781
|
// 対象要素を拡張(ul, ol, pre, blockquote を追加)
|
|
729
|
-
var elements = content.querySelectorAll('h1, h2, h3, h4, p, table, hr, ul, ol, pre, blockquote');
|
|
782
|
+
var elements = content.querySelectorAll('h1, h2, h3, h4, p, table, hr, ul, ol, pre, blockquote, .mermaid-container');
|
|
730
783
|
|
|
731
784
|
// 処理済みの要素を追跡(ネストされたリストの重複処理を防ぐ)
|
|
732
785
|
var processed = new Set();
|
|
@@ -779,34 +832,26 @@ document.addEventListener('DOMContentLoaded', function() {
|
|
|
779
832
|
lines = lines.concat(bqLines);
|
|
780
833
|
lines.push('');
|
|
781
834
|
processed.add(el);
|
|
835
|
+
} else if (el.classList && el.classList.contains('mermaid-container')) {
|
|
836
|
+
var mermaidSource = el.dataset.mermaidSource;
|
|
837
|
+
if (mermaidSource) {
|
|
838
|
+
lines.push('```mermaid');
|
|
839
|
+
lines.push(mermaidSource);
|
|
840
|
+
lines.push('```');
|
|
841
|
+
lines.push('');
|
|
842
|
+
}
|
|
843
|
+
processed.add(el);
|
|
782
844
|
} else if (tag === 'TABLE') {
|
|
783
845
|
var rows = el.querySelectorAll('tr');
|
|
784
846
|
|
|
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
847
|
rows.forEach(function(row, rowIndex) {
|
|
797
848
|
var cells = row.querySelectorAll('th, td');
|
|
798
849
|
var cellTexts = [];
|
|
799
850
|
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
|
-
}
|
|
851
|
+
var tempDiv = document.createElement('div');
|
|
852
|
+
tempDiv.innerHTML = cell.innerHTML;
|
|
853
|
+
tempDiv.innerHTML = tempDiv.innerHTML.replace(/<br\s*\/?>/gi, ' / ');
|
|
854
|
+
var text = tempDiv.textContent.trim();
|
|
810
855
|
cellTexts.push(text);
|
|
811
856
|
});
|
|
812
857
|
lines.push('| ' + cellTexts.join(' | ') + ' |');
|
|
@@ -1829,222 +1874,156 @@ document.addEventListener('DOMContentLoaded', function() {
|
|
|
1829
1874
|
}
|
|
1830
1875
|
}
|
|
1831
1876
|
|
|
1832
|
-
|
|
1833
|
-
|
|
1834
|
-
|
|
1835
|
-
|
|
1836
|
-
|
|
1837
|
-
|
|
1838
|
-
|
|
1839
|
-
|
|
1840
|
-
|
|
1841
|
-
|
|
1842
|
-
|
|
1843
|
-
|
|
1844
|
-
|
|
1845
|
-
|
|
1846
|
-
|
|
1847
|
-
format: 'a4',
|
|
1848
|
-
orientation: 'portrait'
|
|
1849
|
-
});
|
|
1850
|
-
|
|
1851
|
-
var pageWidth = 210;
|
|
1852
|
-
var pageHeight = 297;
|
|
1853
|
-
var margin = 15;
|
|
1854
|
-
var contentWidth = pageWidth - margin * 2;
|
|
1855
|
-
var contentHeight = pageHeight - margin * 2;
|
|
1856
|
-
var currentY = margin;
|
|
1857
|
-
var currentPage = 1;
|
|
1858
|
-
|
|
1859
|
-
// しおり情報
|
|
1860
|
-
var bookmarks = [];
|
|
1861
|
-
|
|
1862
|
-
// コンテンツを複製してPDF用に整形
|
|
1863
|
-
var content = container.cloneNode(true);
|
|
1864
|
-
|
|
1865
|
-
// ツールバーと右クリックメニューを除去
|
|
1866
|
-
var toolbar = content.querySelector('.toolbar');
|
|
1867
|
-
if (toolbar) toolbar.remove();
|
|
1868
|
-
content.querySelectorAll('.context-menu').forEach(function(menu) {
|
|
1869
|
-
menu.remove();
|
|
1870
|
-
});
|
|
1871
|
-
content.querySelectorAll('[contenteditable]').forEach(function(el) {
|
|
1872
|
-
el.removeAttribute('contenteditable');
|
|
1873
|
-
});
|
|
1874
|
-
|
|
1875
|
-
// 一時的にDOMに追加(html2canvas用)
|
|
1876
|
-
content.style.position = 'absolute';
|
|
1877
|
-
content.style.left = '-9999px';
|
|
1878
|
-
content.style.width = contentWidth * 3.78 + 'px'; // mm to px
|
|
1879
|
-
content.style.background = 'white';
|
|
1880
|
-
content.style.padding = '0';
|
|
1881
|
-
document.body.appendChild(content);
|
|
1882
|
-
|
|
1883
|
-
// h2で分割してセクションごとに処理
|
|
1884
|
-
var allElements = content.querySelectorAll('h1, h2, h3, h4, p, ul, ol, pre, blockquote, table, hr');
|
|
1885
|
-
var sections = [];
|
|
1886
|
-
var currentSection = { elements: [], h2Text: null, h3List: [] };
|
|
1887
|
-
|
|
1888
|
-
allElements.forEach(function(el) {
|
|
1889
|
-
// ネストされたリストはスキップ(親リストで処理される)
|
|
1890
|
-
if ((el.tagName === 'UL' || el.tagName === 'OL') && el.parentElement.closest('ul, ol')) {
|
|
1891
|
-
return;
|
|
1892
|
-
}
|
|
1893
|
-
|
|
1894
|
-
if (el.tagName === 'H2') {
|
|
1895
|
-
if (currentSection.elements.length > 0 || currentSection.h2Text) {
|
|
1896
|
-
sections.push(currentSection);
|
|
1897
|
-
}
|
|
1898
|
-
currentSection = { elements: [el], h2Text: el.textContent.trim(), h3List: [] };
|
|
1899
|
-
} else if (el.tagName === 'H3') {
|
|
1900
|
-
currentSection.elements.push(el);
|
|
1901
|
-
currentSection.h3List.push({ text: el.textContent.trim(), pageNum: null });
|
|
1902
|
-
} else {
|
|
1903
|
-
currentSection.elements.push(el);
|
|
1904
|
-
}
|
|
1905
|
-
});
|
|
1906
|
-
if (currentSection.elements.length > 0) {
|
|
1907
|
-
sections.push(currentSection);
|
|
1908
|
-
}
|
|
1909
|
-
|
|
1910
|
-
// 各セクションを処理
|
|
1911
|
-
for (var i = 0; i < sections.length; i++) {
|
|
1912
|
-
var section = sections[i];
|
|
1913
|
-
|
|
1914
|
-
// セクション内の要素を一つのdivにまとめる
|
|
1915
|
-
var sectionDiv = document.createElement('div');
|
|
1916
|
-
sectionDiv.style.background = 'white';
|
|
1917
|
-
sectionDiv.style.padding = '10px';
|
|
1918
|
-
section.elements.forEach(function(el) {
|
|
1919
|
-
sectionDiv.appendChild(el.cloneNode(true));
|
|
1920
|
-
});
|
|
1921
|
-
content.innerHTML = '';
|
|
1922
|
-
content.appendChild(sectionDiv);
|
|
1923
|
-
|
|
1924
|
-
// html2canvasでキャプチャ
|
|
1925
|
-
var canvas = await html2canvas(sectionDiv, {
|
|
1926
|
-
scale: 2,
|
|
1927
|
-
useCORS: true,
|
|
1928
|
-
backgroundColor: '#ffffff'
|
|
1929
|
-
});
|
|
1930
|
-
|
|
1931
|
-
var imgData = canvas.toDataURL('image/jpeg', 0.95);
|
|
1932
|
-
var imgWidth = contentWidth;
|
|
1933
|
-
var imgHeight = (canvas.height * imgWidth) / canvas.width;
|
|
1934
|
-
|
|
1935
|
-
// ページに収まるかチェック
|
|
1936
|
-
if (currentY + imgHeight > pageHeight - margin) {
|
|
1937
|
-
// 新しいページを追加
|
|
1938
|
-
pdf.addPage();
|
|
1939
|
-
currentPage++;
|
|
1940
|
-
currentY = margin;
|
|
1941
|
-
}
|
|
1877
|
+
|
|
1878
|
+
// ========== Excel出力 ==========
|
|
1879
|
+
var fontSizes = {
|
|
1880
|
+
heading: 14,
|
|
1881
|
+
tableHeader: 13,
|
|
1882
|
+
body: 12
|
|
1883
|
+
};
|
|
1884
|
+
|
|
1885
|
+
function svgToPngBase64(svgElement) {
|
|
1886
|
+
return new Promise(function(resolve, reject) {
|
|
1887
|
+
try {
|
|
1888
|
+
// SVGの実サイズを取得
|
|
1889
|
+
var rect = svgElement.getBoundingClientRect();
|
|
1890
|
+
var svgWidth = rect.width || parseInt(svgElement.getAttribute('width')) || 800;
|
|
1891
|
+
var svgHeight = rect.height || parseInt(svgElement.getAttribute('height')) || 400;
|
|
1942
1892
|
|
|
1943
|
-
//
|
|
1944
|
-
|
|
1945
|
-
|
|
1946
|
-
|
|
1947
|
-
|
|
1948
|
-
|
|
1949
|
-
children: []
|
|
1950
|
-
});
|
|
1893
|
+
// SVGクローンにサイズを明示設定(Image読み込み時に必要)
|
|
1894
|
+
var cloneSvg = svgElement.cloneNode(true);
|
|
1895
|
+
cloneSvg.setAttribute('width', svgWidth);
|
|
1896
|
+
cloneSvg.setAttribute('height', svgHeight);
|
|
1897
|
+
if (!cloneSvg.getAttribute('xmlns')) {
|
|
1898
|
+
cloneSvg.setAttribute('xmlns', 'http://www.w3.org/2000/svg');
|
|
1951
1899
|
}
|
|
1952
1900
|
|
|
1953
|
-
|
|
1954
|
-
|
|
1955
|
-
|
|
1956
|
-
section.h3List.forEach(function(h3) {
|
|
1957
|
-
parentBookmark.children.push({
|
|
1958
|
-
text: h3.text,
|
|
1959
|
-
page: currentPage,
|
|
1960
|
-
level: 2
|
|
1961
|
-
});
|
|
1962
|
-
});
|
|
1963
|
-
}
|
|
1901
|
+
var svgData = new XMLSerializer().serializeToString(cloneSvg);
|
|
1902
|
+
var svgBlob = new Blob([svgData], { type: 'image/svg+xml;charset=utf-8' });
|
|
1903
|
+
var url = URL.createObjectURL(svgBlob);
|
|
1964
1904
|
|
|
1965
|
-
|
|
1966
|
-
|
|
1967
|
-
var
|
|
1968
|
-
var
|
|
1905
|
+
var img = new Image();
|
|
1906
|
+
img.onload = function() {
|
|
1907
|
+
var scale = 2;
|
|
1908
|
+
var w = img.naturalWidth || svgWidth;
|
|
1909
|
+
var h = img.naturalHeight || svgHeight;
|
|
1910
|
+
var canvas = document.createElement('canvas');
|
|
1911
|
+
canvas.width = w * scale;
|
|
1912
|
+
canvas.height = h * scale;
|
|
1913
|
+
var ctx = canvas.getContext('2d');
|
|
1914
|
+
ctx.fillStyle = '#ffffff';
|
|
1915
|
+
ctx.fillRect(0, 0, canvas.width, canvas.height);
|
|
1916
|
+
ctx.drawImage(img, 0, 0, canvas.width, canvas.height);
|
|
1969
1917
|
|
|
1970
|
-
|
|
1971
|
-
|
|
1972
|
-
|
|
1973
|
-
|
|
1974
|
-
|
|
1975
|
-
|
|
1976
|
-
|
|
1977
|
-
|
|
1978
|
-
|
|
1979
|
-
|
|
1980
|
-
|
|
1981
|
-
|
|
1982
|
-
|
|
1983
|
-
|
|
1984
|
-
sourceY += drawHeightPx;
|
|
1985
|
-
remainingHeight -= drawHeight;
|
|
1986
|
-
|
|
1987
|
-
if (remainingHeight > 0) {
|
|
1988
|
-
pdf.addPage();
|
|
1989
|
-
currentPage++;
|
|
1990
|
-
currentY = margin;
|
|
1991
|
-
} else {
|
|
1992
|
-
currentY += drawHeight + 5;
|
|
1993
|
-
}
|
|
1994
|
-
}
|
|
1995
|
-
} else {
|
|
1996
|
-
pdf.addImage(imgData, 'JPEG', margin, currentY, imgWidth, imgHeight);
|
|
1997
|
-
currentY += imgHeight + 5;
|
|
1998
|
-
}
|
|
1918
|
+
URL.revokeObjectURL(url);
|
|
1919
|
+
|
|
1920
|
+
var dataUrl = canvas.toDataURL('image/png');
|
|
1921
|
+
var base64 = dataUrl.replace(/^data:image\/png;base64,/, '');
|
|
1922
|
+
resolve(base64);
|
|
1923
|
+
};
|
|
1924
|
+
img.onerror = function() {
|
|
1925
|
+
URL.revokeObjectURL(url);
|
|
1926
|
+
reject(new Error('SVG→PNG変換に失敗'));
|
|
1927
|
+
};
|
|
1928
|
+
img.src = url;
|
|
1929
|
+
} catch (err) {
|
|
1930
|
+
reject(err);
|
|
1999
1931
|
}
|
|
1932
|
+
});
|
|
1933
|
+
}
|
|
1934
|
+
|
|
1935
|
+
// Excel用のデータシリアライズ(Chrome拡張sandbox用)
|
|
1936
|
+
async function serializeContentForExcel(sections, fileName) {
|
|
1937
|
+
var data = { fileName: fileName, sections: [] };
|
|
1938
|
+
|
|
1939
|
+
for (var s = 0; s < sections.length; s++) {
|
|
1940
|
+
var section = sections[s];
|
|
1941
|
+
var sectionData = { title: section.title, elements: [] };
|
|
2000
1942
|
|
|
2001
|
-
|
|
2002
|
-
|
|
2003
|
-
|
|
2004
|
-
|
|
2005
|
-
bookmarks.forEach(function(bm) {
|
|
2006
|
-
var parent = pdf.outline.add(null, bm.text, { pageNumber: bm.page });
|
|
2007
|
-
bm.children.forEach(function(child) {
|
|
2008
|
-
pdf.outline.add(parent, child.text, { pageNumber: child.page });
|
|
2009
|
-
});
|
|
2010
|
-
});
|
|
2011
|
-
|
|
2012
|
-
// タイトル取得
|
|
2013
|
-
var h1 = container.querySelector('h1');
|
|
2014
|
-
var fileName = h1 ? h1.textContent.trim().replace(/[\\/:*?"<>|]/g, '_') : 'document';
|
|
2015
|
-
|
|
2016
|
-
// PDF保存
|
|
2017
|
-
pdf.save(fileName + '.pdf');
|
|
1943
|
+
for (var e = 0; e < section.elements.length; e++) {
|
|
1944
|
+
var elData = await serializeElement(section.elements[e]);
|
|
1945
|
+
if (elData) sectionData.elements.push(elData);
|
|
1946
|
+
}
|
|
2018
1947
|
|
|
2019
|
-
|
|
2020
|
-
console.error('PDF生成エラー:', error);
|
|
2021
|
-
alert('PDF生成中にエラーが発生しました: ' + error.message);
|
|
1948
|
+
data.sections.push(sectionData);
|
|
2022
1949
|
}
|
|
2023
1950
|
|
|
2024
|
-
|
|
2025
|
-
|
|
2026
|
-
});
|
|
1951
|
+
return data;
|
|
1952
|
+
}
|
|
2027
1953
|
|
|
2028
|
-
|
|
2029
|
-
|
|
2030
|
-
|
|
2031
|
-
|
|
2032
|
-
|
|
2033
|
-
|
|
1954
|
+
async function serializeElement(element) {
|
|
1955
|
+
if (element.classList && element.classList.contains('mermaid-container')) {
|
|
1956
|
+
var svg = element.querySelector('svg');
|
|
1957
|
+
if (svg) {
|
|
1958
|
+
try {
|
|
1959
|
+
var pngBase64 = await svgToPngBase64(svg);
|
|
1960
|
+
var svgRect = svg.getBoundingClientRect();
|
|
1961
|
+
return {
|
|
1962
|
+
tag: 'MERMAID', pngBase64: pngBase64,
|
|
1963
|
+
width: svgRect.width || 800, height: svgRect.height || 400
|
|
1964
|
+
};
|
|
1965
|
+
} catch (err) {
|
|
1966
|
+
return { tag: 'MERMAID_FALLBACK' };
|
|
1967
|
+
}
|
|
1968
|
+
}
|
|
1969
|
+
return null;
|
|
1970
|
+
}
|
|
1971
|
+
|
|
1972
|
+
var tag = element.tagName;
|
|
1973
|
+
switch (tag) {
|
|
1974
|
+
case 'H1': return { tag: 'H1', text: element.textContent.trim() };
|
|
1975
|
+
case 'H2': return { tag: 'H2', text: element.textContent.trim(), styles: getElementStyles(element) };
|
|
1976
|
+
case 'H3': return { tag: 'H3', text: element.textContent.trim(), styles: getElementStyles(element) };
|
|
1977
|
+
case 'H4': return { tag: 'H4', text: element.textContent.trim(), styles: getElementStyles(element) };
|
|
1978
|
+
case 'P':
|
|
1979
|
+
var pText = element.textContent.trim();
|
|
1980
|
+
return pText ? { tag: 'P', text: pText } : null;
|
|
1981
|
+
case 'TABLE':
|
|
1982
|
+
return serializeTable(element);
|
|
1983
|
+
case 'UL':
|
|
1984
|
+
case 'OL':
|
|
1985
|
+
return { tag: tag, lines: processListToExcelText(element, '') };
|
|
1986
|
+
case 'PRE':
|
|
1987
|
+
var codeEl = element.querySelector('code');
|
|
1988
|
+
var codeText = codeEl ? codeEl.textContent : element.textContent;
|
|
1989
|
+
return codeText ? { tag: 'PRE', text: codeText } : null;
|
|
1990
|
+
case 'BLOCKQUOTE':
|
|
1991
|
+
var bqText = element.textContent.trim();
|
|
1992
|
+
return bqText ? { tag: 'BLOCKQUOTE', text: bqText } : null;
|
|
1993
|
+
case 'HR': return { tag: 'HR' };
|
|
1994
|
+
default: return null;
|
|
1995
|
+
}
|
|
1996
|
+
}
|
|
1997
|
+
|
|
1998
|
+
function serializeTable(table) {
|
|
1999
|
+
var rows = table.querySelectorAll('tr');
|
|
2000
|
+
var tableData = { tag: 'TABLE', rows: [] };
|
|
2001
|
+
rows.forEach(function(tr) {
|
|
2002
|
+
var cells = tr.querySelectorAll('th, td');
|
|
2003
|
+
var rowData = [];
|
|
2004
|
+
cells.forEach(function(cell) {
|
|
2005
|
+
rowData.push({
|
|
2006
|
+
text: cell.textContent.trim(),
|
|
2007
|
+
isHeader: cell.tagName === 'TH',
|
|
2008
|
+
colspan: parseInt(cell.getAttribute('colspan')) || 1
|
|
2009
|
+
});
|
|
2010
|
+
});
|
|
2011
|
+
tableData.rows.push(rowData);
|
|
2012
|
+
});
|
|
2013
|
+
return tableData;
|
|
2014
|
+
}
|
|
2034
2015
|
|
|
2035
2016
|
if (exportExcelBtn) exportExcelBtn.addEventListener('click', async function() {
|
|
2036
2017
|
try {
|
|
2037
2018
|
await convertToExcel();
|
|
2038
2019
|
} catch (error) {
|
|
2039
2020
|
console.error('Excel変換エラー:', error);
|
|
2040
|
-
|
|
2021
|
+
var errMsg = error ? (error.message || error.toString()) : 'unknown';
|
|
2022
|
+
alert('Excel変換中にエラーが発生しました: ' + errMsg);
|
|
2041
2023
|
}
|
|
2042
2024
|
});
|
|
2043
2025
|
|
|
2044
2026
|
async function convertToExcel() {
|
|
2045
|
-
// ライブラリ遅延読み込み
|
|
2046
|
-
await ensureExcelLibrary();
|
|
2047
|
-
|
|
2048
2027
|
var content = container.querySelector('.content');
|
|
2049
2028
|
if (!content) content = container;
|
|
2050
2029
|
|
|
@@ -2055,6 +2034,33 @@ document.addEventListener('DOMContentLoaded', function() {
|
|
|
2055
2034
|
return;
|
|
2056
2035
|
}
|
|
2057
2036
|
|
|
2037
|
+
// ファイル名取得
|
|
2038
|
+
var h1Element = container.querySelector('h1');
|
|
2039
|
+
var fileName = 'design_spec';
|
|
2040
|
+
if (h1Element) {
|
|
2041
|
+
fileName = h1Element.innerHTML
|
|
2042
|
+
.replace(/<br\s*\/?>/gi, '-')
|
|
2043
|
+
.replace(/<\/p>\s*<p>/gi, '-')
|
|
2044
|
+
.replace(/<[^>]*>/g, '')
|
|
2045
|
+
.replace(/\s+/g, '')
|
|
2046
|
+
.trim();
|
|
2047
|
+
}
|
|
2048
|
+
|
|
2049
|
+
// Chrome拡張のsandboxモード(window.__excelSandboxが設定されている場合)
|
|
2050
|
+
if (window.__excelSandbox) {
|
|
2051
|
+
var data = await serializeContentForExcel(sections, fileName);
|
|
2052
|
+
await window.__excelSandbox(data);
|
|
2053
|
+
return;
|
|
2054
|
+
}
|
|
2055
|
+
|
|
2056
|
+
// 通常モード(Web版 - CDNからExcelJS読み込み)
|
|
2057
|
+
await ensureExcelLibrary();
|
|
2058
|
+
|
|
2059
|
+
if (!window.ExcelJS) {
|
|
2060
|
+
alert('ExcelJSライブラリが読み込めませんでした');
|
|
2061
|
+
return;
|
|
2062
|
+
}
|
|
2063
|
+
|
|
2058
2064
|
var workbook = new ExcelJS.Workbook();
|
|
2059
2065
|
workbook.creator = 'Design Editor';
|
|
2060
2066
|
workbook.created = new Date();
|
|
@@ -2110,7 +2116,7 @@ document.addEventListener('DOMContentLoaded', function() {
|
|
|
2110
2116
|
if (h2Elements.length === 0) {
|
|
2111
2117
|
var h1 = content.querySelector('h1');
|
|
2112
2118
|
var title = h1 ? h1.textContent.trim() : 'Sheet1';
|
|
2113
|
-
var elements = content.querySelectorAll('h1, h2, h3, h4, p, table, hr, ul, ol, pre, blockquote');
|
|
2119
|
+
var elements = content.querySelectorAll('h1, h2, h3, h4, p, table, hr, ul, ol, pre, blockquote, .mermaid-container');
|
|
2114
2120
|
sections.push({
|
|
2115
2121
|
title: title,
|
|
2116
2122
|
elements: Array.from(elements),
|
|
@@ -2126,7 +2132,8 @@ document.addEventListener('DOMContentLoaded', function() {
|
|
|
2126
2132
|
var next = h2.nextElementSibling;
|
|
2127
2133
|
|
|
2128
2134
|
while (next && next.tagName !== 'H2' && next.tagName !== 'HR') {
|
|
2129
|
-
if (['H3', 'H4', 'P', 'TABLE', 'UL', 'OL', 'PRE', 'BLOCKQUOTE'].includes(next.tagName)
|
|
2135
|
+
if (['H3', 'H4', 'P', 'TABLE', 'UL', 'OL', 'PRE', 'BLOCKQUOTE'].includes(next.tagName) ||
|
|
2136
|
+
(next.classList && next.classList.contains('mermaid-container'))) {
|
|
2130
2137
|
elements.push(next);
|
|
2131
2138
|
}
|
|
2132
2139
|
next = next.nextElementSibling;
|
|
@@ -2273,6 +2280,45 @@ document.addEventListener('DOMContentLoaded', function() {
|
|
|
2273
2280
|
}
|
|
2274
2281
|
|
|
2275
2282
|
async function processElement(worksheet, element, currentRow, maxColumns) {
|
|
2283
|
+
// Mermaidコンテナの場合はDIVなのでclassで判定
|
|
2284
|
+
if (element.classList && element.classList.contains('mermaid-container')) {
|
|
2285
|
+
var svg = element.querySelector('svg');
|
|
2286
|
+
if (svg) {
|
|
2287
|
+
try {
|
|
2288
|
+
var pngBase64 = await svgToPngBase64(svg);
|
|
2289
|
+
var imageId = worksheet.workbook.addImage({
|
|
2290
|
+
base64: pngBase64,
|
|
2291
|
+
extension: 'png'
|
|
2292
|
+
});
|
|
2293
|
+
|
|
2294
|
+
// SVGの実サイズからExcel上のサイズを計算(縦横比保持)
|
|
2295
|
+
var svgRect = svg.getBoundingClientRect();
|
|
2296
|
+
var svgW = svgRect.width || 800;
|
|
2297
|
+
var svgH = svgRect.height || 400;
|
|
2298
|
+
|
|
2299
|
+
// Excel上の表示幅を600pxに制限し、縦横比を保持
|
|
2300
|
+
var maxWidthPx = 600;
|
|
2301
|
+
var displayW = Math.min(svgW, maxWidthPx);
|
|
2302
|
+
var displayH = svgH * (displayW / svgW);
|
|
2303
|
+
var rowsNeeded = Math.ceil(displayH / 20);
|
|
2304
|
+
|
|
2305
|
+
worksheet.addImage(imageId, {
|
|
2306
|
+
tl: { col: 0, row: currentRow - 1 },
|
|
2307
|
+
ext: { width: displayW, height: displayH }
|
|
2308
|
+
});
|
|
2309
|
+
|
|
2310
|
+
currentRow += rowsNeeded + 1;
|
|
2311
|
+
} catch (err) {
|
|
2312
|
+
// 画像変換失敗時はテキストで代替
|
|
2313
|
+
var fallbackCell = worksheet.getCell(currentRow, 1);
|
|
2314
|
+
fallbackCell.value = '[Mermaid図]';
|
|
2315
|
+
fallbackCell.font = { name: 'メイリオ', size: fontSizes.body, italic: true, color: { argb: 'FF999999' } };
|
|
2316
|
+
currentRow++;
|
|
2317
|
+
}
|
|
2318
|
+
}
|
|
2319
|
+
return currentRow;
|
|
2320
|
+
}
|
|
2321
|
+
|
|
2276
2322
|
var tag = element.tagName;
|
|
2277
2323
|
|
|
2278
2324
|
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.1",
|
|
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"
|