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.
Files changed (2) hide show
  1. package/html-editor.js +149 -32
  2. 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.0
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 toolbar = container.querySelector('.toolbar');
548
- var contextMenus = document.querySelectorAll('.context-menu');
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
- // Mermaidコンテナをpre/codeに復元(再読み込み時に再描画可能にする)
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, '&lt;') + '</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
- alert('Excel変換中にエラーが発生しました: ' + error.message);
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();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sunny-html-editor",
3
- "version": "1.3.0",
3
+ "version": "1.3.1",
4
4
  "description": "軽量WYSIWYGエディタ - 既存のHTMLに編集機能を後付けで追加、Markdown変換対応",
5
5
  "main": "html-editor.js",
6
6
  "files": [