sunny-html-editor 1.3.0 → 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/html-editor.js +149 -32
- package/package.json +1 -1
package/html-editor.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Sunny HTML Editor - 軽量WYSIWYGエディタ
|
|
3
|
-
* @version 1.3.
|
|
3
|
+
* @version 1.3.1
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
// ========== HTML生成関数 ==========
|
|
@@ -539,43 +539,27 @@ document.addEventListener('DOMContentLoaded', function() {
|
|
|
539
539
|
}
|
|
540
540
|
|
|
541
541
|
// ========== HTML保存 ==========
|
|
542
|
+
var EDITOR_CDN_BASE = 'https://cdn.jsdelivr.net/npm/sunny-html-editor@1.3.1';
|
|
543
|
+
|
|
542
544
|
if (saveHtmlBtn) saveHtmlBtn.addEventListener('click', function() {
|
|
543
545
|
var wasEditMode = isEditMode;
|
|
544
546
|
if (isEditMode && editModeBtn) editModeBtn.click();
|
|
545
547
|
|
|
546
|
-
//
|
|
547
|
-
var
|
|
548
|
-
var
|
|
549
|
-
|
|
550
|
-
if (toolbar) toolbar.style.display = 'none';
|
|
551
|
-
contextMenus.forEach(function(menu) { menu.style.display = 'none'; });
|
|
552
|
-
|
|
553
|
-
// HTML取得(ツールバーとコンテキストメニューを除外したクリーンなHTML)
|
|
554
|
-
var docClone = document.documentElement.cloneNode(true);
|
|
555
|
-
var cloneToolbar = docClone.querySelector('.toolbar');
|
|
556
|
-
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');
|
|
557
552
|
if (cloneToolbar) cloneToolbar.remove();
|
|
558
553
|
cloneMenus.forEach(function(menu) { menu.remove(); });
|
|
554
|
+
restoreMermaidToPre(contentClone);
|
|
555
|
+
var contentHtml = contentClone.innerHTML;
|
|
559
556
|
|
|
560
|
-
//
|
|
561
|
-
restoreMermaidToPre(docClone);
|
|
562
|
-
|
|
563
|
-
var html = '<!DOCTYPE html>\n' + docClone.outerHTML;
|
|
564
|
-
|
|
565
|
-
// 表示を復元
|
|
566
|
-
if (toolbar) toolbar.style.display = '';
|
|
567
|
-
contextMenus.forEach(function(menu) { menu.style.display = ''; });
|
|
568
|
-
|
|
569
|
-
if (wasEditMode && editModeBtn) editModeBtn.click();
|
|
570
|
-
|
|
571
|
-
var blob = new Blob([html], { type: 'text/html; charset=utf-8' });
|
|
572
|
-
var url = URL.createObjectURL(blob);
|
|
573
|
-
var a = document.createElement('a');
|
|
574
|
-
a.href = url;
|
|
575
|
-
|
|
557
|
+
// タイトル・ファイル名取得
|
|
576
558
|
var h1Element = container.querySelector('h1');
|
|
559
|
+
var title = 'Design Spec';
|
|
577
560
|
var fileName = 'design_spec';
|
|
578
561
|
if (h1Element) {
|
|
562
|
+
title = h1Element.textContent.trim();
|
|
579
563
|
fileName = h1Element.innerHTML
|
|
580
564
|
.replace(/<br\s*\/?>/gi, '-')
|
|
581
565
|
.replace(/<\/p>\s*<p>/gi, '-')
|
|
@@ -583,9 +567,36 @@ document.addEventListener('DOMContentLoaded', function() {
|
|
|
583
567
|
.replace(/\s+/g, '')
|
|
584
568
|
.trim();
|
|
585
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;
|
|
586
595
|
a.download = fileName + '.html';
|
|
587
596
|
a.click();
|
|
588
597
|
URL.revokeObjectURL(url);
|
|
598
|
+
|
|
599
|
+
if (wasEditMode && editModeBtn) editModeBtn.click();
|
|
589
600
|
});
|
|
590
601
|
|
|
591
602
|
// ========== Markdown出力 ==========
|
|
@@ -1921,19 +1932,98 @@ document.addEventListener('DOMContentLoaded', function() {
|
|
|
1921
1932
|
});
|
|
1922
1933
|
}
|
|
1923
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: [] };
|
|
1942
|
+
|
|
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
|
+
}
|
|
1947
|
+
|
|
1948
|
+
data.sections.push(sectionData);
|
|
1949
|
+
}
|
|
1950
|
+
|
|
1951
|
+
return data;
|
|
1952
|
+
}
|
|
1953
|
+
|
|
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
|
+
}
|
|
2015
|
+
|
|
1924
2016
|
if (exportExcelBtn) exportExcelBtn.addEventListener('click', async function() {
|
|
1925
2017
|
try {
|
|
1926
2018
|
await convertToExcel();
|
|
1927
2019
|
} catch (error) {
|
|
1928
2020
|
console.error('Excel変換エラー:', error);
|
|
1929
|
-
|
|
2021
|
+
var errMsg = error ? (error.message || error.toString()) : 'unknown';
|
|
2022
|
+
alert('Excel変換中にエラーが発生しました: ' + errMsg);
|
|
1930
2023
|
}
|
|
1931
2024
|
});
|
|
1932
2025
|
|
|
1933
2026
|
async function convertToExcel() {
|
|
1934
|
-
// ライブラリ遅延読み込み
|
|
1935
|
-
await ensureExcelLibrary();
|
|
1936
|
-
|
|
1937
2027
|
var content = container.querySelector('.content');
|
|
1938
2028
|
if (!content) content = container;
|
|
1939
2029
|
|
|
@@ -1944,6 +2034,33 @@ document.addEventListener('DOMContentLoaded', function() {
|
|
|
1944
2034
|
return;
|
|
1945
2035
|
}
|
|
1946
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
|
+
|
|
1947
2064
|
var workbook = new ExcelJS.Workbook();
|
|
1948
2065
|
workbook.creator = 'Design Editor';
|
|
1949
2066
|
workbook.created = new Date();
|