sunny-html-editor 1.0.2 → 1.1.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.
Files changed (3) hide show
  1. package/README.md +47 -104
  2. package/html-editor.js +222 -85
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -4,90 +4,64 @@
4
4
 
5
5
  ## 特徴
6
6
 
7
- - **自己完結型** - 基本機能は外部依存なし
7
+ - **自己完結型** - ツールバー・コンテキストメニューを自動生成
8
+ - **遅延読み込み** - Excel/PDF出力用ライブラリは使用時に自動読み込み
8
9
  - **WYSIWYG編集** - 見たまま編集、右クリックで要素追加
9
10
  - **キーボード操作** - ↑↓で要素間移動、Shift/Ctrl+Enterで項目追加
10
11
  - **多彩なエクスポート** - Markdown、HTML、Excel、PDF
11
12
  - **サイドバーナビ** - 目次自動生成
12
13
 
13
- ## 外部ライブラリ依存
14
-
15
- ### 必須
16
-
17
- なし(基本機能のみ使用する場合)
18
-
19
- ### オプション(Excel/PDF出力に必要)
20
-
21
- | ライブラリ | バージョン | 用途 | CDN |
22
- |-----------|----------|------|-----|
23
- | ExcelJS | 4.3.0+ | Excel出力 | https://cdnjs.cloudflare.com/ajax/libs/exceljs/4.3.0/exceljs.min.js |
24
- | jsPDF | 2.5.1+ | PDF出力 | https://cdnjs.cloudflare.com/ajax/libs/jspdf/2.5.1/jspdf.umd.min.js |
25
- | html2canvas | 1.4.1+ | PDF出力(HTML→Canvas変換) | https://cdnjs.cloudflare.com/ajax/libs/html2canvas/1.4.1/html2canvas.min.js |
14
+ ## インストール
26
15
 
27
- Excel/PDF出力を使う場合は、html-editor.jsより**前に**読み込んでください:
16
+ ### CDN(推奨)
28
17
 
29
18
  ```html
30
- <!-- 外部ライブラリ(Excel/PDF出力に必要) -->
31
- <script src="https://cdnjs.cloudflare.com/ajax/libs/exceljs/4.3.0/exceljs.min.js"></script>
32
- <script src="https://cdnjs.cloudflare.com/ajax/libs/jspdf/2.5.1/jspdf.umd.min.js"></script>
33
- <script src="https://cdnjs.cloudflare.com/ajax/libs/html2canvas/1.4.1/html2canvas.min.js"></script>
34
-
35
- <!-- HTML Editor -->
36
- <link rel="stylesheet" href="html-editor.css">
37
- <script src="html-editor.js"></script>
19
+ <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/sunny-html-editor/html-editor.css">
20
+ <script src="https://cdn.jsdelivr.net/npm/sunny-html-editor/html-editor.js"></script>
38
21
  ```
39
22
 
40
- ## インストール
41
-
42
23
  ### npm
43
24
 
44
25
  ```bash
45
26
  npm install sunny-html-editor
46
27
  ```
47
28
 
48
- ### CDN
49
-
50
- ```html
51
- <link rel="stylesheet" href="https://unpkg.com/sunny-html-editor/html-editor.css">
52
- <script src="https://unpkg.com/sunny-html-editor/html-editor.js"></script>
53
- ```
54
-
55
- ### ダウンロード
56
-
57
- `html-editor.css` と `html-editor.js` をダウンロードして読み込み。
58
-
59
29
  ## 使い方
60
30
 
61
- ### 基本
31
+ `id="editorContainer"` を持つコンテナを用意するだけ。ツールバーとコンテキストメニューは自動生成されます。
62
32
 
63
33
  ```html
64
34
  <!DOCTYPE html>
65
- <html>
35
+ <html lang="ja">
66
36
  <head>
67
- <link rel="stylesheet" href="html-editor.css">
37
+ <meta charset="UTF-8">
38
+ <title>ドキュメントタイトル</title>
39
+ <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/sunny-html-editor/html-editor.css">
68
40
  </head>
69
41
  <body>
70
- <div id="content">
71
- <h1>タイトル</h1>
72
- <p>既存のHTMLコンテンツ</p>
73
- </div>
74
-
75
- <script src="html-editor.js"></script>
76
- <script>
77
- HtmlEditor.init('#content');
78
- </script>
42
+ <div class="layout">
43
+ <main class="main">
44
+ <div class="container" id="editorContainer">
45
+ <h1>タイトル</h1>
46
+ <h2 id="section1">見出し</h2>
47
+ <p>本文...</p>
48
+ </div>
49
+ </main>
50
+ </div>
51
+ <script src="https://cdn.jsdelivr.net/npm/sunny-html-editor/html-editor.js"></script>
79
52
  </body>
80
53
  </html>
81
54
  ```
82
55
 
83
- ### オプション
56
+ ## 外部ライブラリ
84
57
 
85
- ```javascript
86
- HtmlEditor.init('#content', {
87
- sidebar: true, // サイドバー表示(デフォルト: true)
88
- toolbar: true // ツールバー表示(デフォルト: true)
89
- });
90
- ```
58
+ Excel/PDF出力に必要なライブラリは**使用時に自動で読み込まれます**。事前の読み込みは不要です。
59
+
60
+ | ライブラリ | 用途 |
61
+ |-----------|------|
62
+ | ExcelJS | Excel出力 |
63
+ | jsPDF | PDF出力 |
64
+ | html2canvas | PDF出力(HTML→Canvas変換) |
91
65
 
92
66
  ## キーボードショートカット
93
67
 
@@ -113,64 +87,33 @@ HtmlEditor.init('#content', {
113
87
 
114
88
  テーブル上では行/列の追加・削除も可能。
115
89
 
116
- ## エクスポート
90
+ ## ツールバーボタン
117
91
 
118
- ### Markdown
119
-
120
- ```javascript
121
- // ツールバーのⓂ️ボタン、または Ctrl+M
122
- // クリップボードにコピーされます
123
- ```
92
+ | ボタン | 機能 |
93
+ |--------|------|
94
+ | ↔️ | サイドバー表示/非表示 |
95
+ | 📝 | 編集モード切替 |
96
+ | Ⓜ️ | Markdownコピー |
97
+ | 🌐 | HTMLコピー |
98
+ | 💾 | HTML保存(ダウンロード) |
99
+ | 📊 | Excel出力 |
100
+ | 📕 | PDF出力 |
124
101
 
125
- ### HTML
102
+ ## バージョン履歴
126
103
 
127
- ```javascript
128
- // ツールバーの🌐ボタン
129
- // クリップボードにコピーされます
130
- ```
131
-
132
- ### HTML保存
133
-
134
- ```javascript
135
- // ツールバーの💾ボタン
136
- // HTMLファイルとしてダウンロード
137
- ```
138
-
139
- ### Excel / PDF
140
-
141
- 上記の外部ライブラリが読み込まれている必要があります。
142
- 読み込まれていない場合、ボタンを押すとアラートが表示されます。
143
-
144
- ## API
145
-
146
- ### HtmlEditor.init(selector, options)
147
-
148
- エディタを初期化します。
149
-
150
- - `selector` - コンテナ要素のセレクタまたはDOM要素
151
- - `options` - オプションオブジェクト
152
-
153
- ### 返り値
154
-
155
- HtmlEditorインスタンスを返します。
156
-
157
- ```javascript
158
- var editor = HtmlEditor.init('#content');
159
-
160
- // 編集モード切替
161
- editor._action_editMode();
162
-
163
- // サイドバー更新
164
- editor._updateSidebar();
165
- ```
104
+ - **1.1.0** - ツールバー・コンテキストメニュー自動生成、外部ライブラリ遅延読み込み
105
+ - **1.0.3** - nullチェック強化
106
+ - **1.0.2** - DOMContentLoaded自動初期化
107
+ - **1.0.1** - メディアクエリ削除
108
+ - **1.0.0** - 初回リリース
166
109
 
167
110
  ## ブラウザ対応
168
111
 
169
- - Chrome (推奨)
112
+ - Chrome(推奨)
170
113
  - Firefox
171
114
  - Safari
172
115
  - Edge
173
116
 
174
117
  ## ライセンス
175
118
 
176
- MIT
119
+ MIT License - OnMax Group
package/html-editor.js CHANGED
@@ -1,11 +1,142 @@
1
1
  /**
2
2
  * Sunny HTML Editor - 軽量WYSIWYGエディタ
3
- * @version 1.0.2
3
+ * @version 1.1.0
4
4
  */
5
+
6
+ // ========== HTML生成関数 ==========
7
+ function getToolbarHtml() {
8
+ return '<div class="toolbar">' +
9
+ '<button id="toggleSidebar" title="サイドメニュー表示/非表示 (Ctrl+B)">↔️</button>' +
10
+ '<button id="editMode" title="編集モード (Ctrl+E)">📝</button>' +
11
+ '<button id="exportMd" title="Markdownコピー (Ctrl+M)">Ⓜ️</button>' +
12
+ '<button id="exportHtml" title="HTMLコピー">🌐</button>' +
13
+ '<button id="saveHtml" title="HTML保存">💾</button>' +
14
+ '<button id="exportExcel" title="Excel出力">📊</button>' +
15
+ '<button id="exportPdf" title="PDF出力">📕</button>' +
16
+ '</div>';
17
+ }
18
+
19
+ function getContextMenusHtml() {
20
+ // テーブル用
21
+ var tableMenu = '<div class="context-menu" id="contextMenu">' +
22
+ '<div class="context-menu-item" data-action="addRowBelow">+ 行を追加</div>' +
23
+ '<div class="context-menu-item" data-action="addColRight">+ 列を追加</div>' +
24
+ '<div class="context-menu-divider"></div>' +
25
+ '<div class="context-menu-item delete" data-action="deleteRow">🗑 行を削除</div>' +
26
+ '<div class="context-menu-item delete" data-action="deleteCol">🗑 列を削除</div>' +
27
+ '<div class="context-menu-item delete" data-action="deleteTable">🗑 テーブルを削除</div>' +
28
+ '<div class="context-menu-divider thick"></div>' +
29
+ '<div class="context-menu-item" data-action="addH2">+ 大見出し</div>' +
30
+ '<div class="context-menu-item" data-action="addH3">+ 中見出し</div>' +
31
+ '<div class="context-menu-item" data-action="addH4">+ 小見出し</div>' +
32
+ '<div class="context-menu-divider"></div>' +
33
+ '<div class="context-menu-item" data-action="addTableBelow">+ テーブル</div>' +
34
+ '<div class="context-menu-divider"></div>' +
35
+ '<div class="context-menu-item" data-action="addParagraph">+ 本文</div>' +
36
+ '<div class="context-menu-divider"></div>' +
37
+ '<div class="context-menu-item" data-action="addUl">+ 箇条書き</div>' +
38
+ '<div class="context-menu-item" data-action="addOl">+ 番号リスト</div>' +
39
+ '<div class="context-menu-divider"></div>' +
40
+ '<div class="context-menu-item" data-action="addPre">+ コードブロック</div>' +
41
+ '<div class="context-menu-item" data-action="addBlockquote">+ 引用</div>' +
42
+ '</div>';
43
+
44
+ // 共通メニュー項目
45
+ var commonItems =
46
+ '<div class="context-menu-item" data-action="addH2">+ 大見出し</div>' +
47
+ '<div class="context-menu-item" data-action="addH3">+ 中見出し</div>' +
48
+ '<div class="context-menu-item" data-action="addH4">+ 小見出し</div>' +
49
+ '<div class="context-menu-divider"></div>' +
50
+ '<div class="context-menu-item" data-action="addTable">+ テーブル</div>' +
51
+ '<div class="context-menu-divider"></div>' +
52
+ '<div class="context-menu-item" data-action="addParagraph">+ 本文</div>' +
53
+ '<div class="context-menu-divider"></div>' +
54
+ '<div class="context-menu-item" data-action="addUl">+ 箇条書き</div>' +
55
+ '<div class="context-menu-item" data-action="addOl">+ 番号リスト</div>' +
56
+ '<div class="context-menu-divider"></div>' +
57
+ '<div class="context-menu-item" data-action="addPre">+ コードブロック</div>' +
58
+ '<div class="context-menu-item" data-action="addBlockquote">+ 引用</div>';
59
+
60
+ // h1用
61
+ var h1Menu = '<div class="context-menu" id="h1ContextMenu">' + commonItems + '</div>';
62
+
63
+ // 見出し用
64
+ var headingMenu = '<div class="context-menu" id="headingContextMenu">' + commonItems +
65
+ '<div class="context-menu-divider"></div>' +
66
+ '<div class="context-menu-item delete" data-action="deleteSection">🗑 セクションを削除</div>' +
67
+ '</div>';
68
+
69
+ // 段落用
70
+ var paragraphMenu = '<div class="context-menu" id="paragraphContextMenu">' + commonItems +
71
+ '<div class="context-menu-divider"></div>' +
72
+ '<div class="context-menu-item delete" data-action="deleteParagraph">🗑 本文を削除</div>' +
73
+ '</div>';
74
+
75
+ // リスト用
76
+ var listMenu = '<div class="context-menu" id="listContextMenu">' + commonItems +
77
+ '<div class="context-menu-divider"></div>' +
78
+ '<div class="context-menu-item delete" data-action="deleteList">🗑 リストを削除</div>' +
79
+ '</div>';
80
+
81
+ // コードブロック用
82
+ var preMenu = '<div class="context-menu" id="preContextMenu">' + commonItems +
83
+ '<div class="context-menu-divider"></div>' +
84
+ '<div class="context-menu-item delete" data-action="deletePre">🗑 コードブロックを削除</div>' +
85
+ '</div>';
86
+
87
+ // 引用用
88
+ var blockquoteMenu = '<div class="context-menu" id="blockquoteContextMenu">' + commonItems +
89
+ '<div class="context-menu-divider"></div>' +
90
+ '<div class="context-menu-item delete" data-action="deleteBlockquote">🗑 引用を削除</div>' +
91
+ '</div>';
92
+
93
+ return tableMenu + h1Menu + headingMenu + paragraphMenu + listMenu + preMenu + blockquoteMenu;
94
+ }
95
+
96
+ // ========== 外部ライブラリ遅延読み込み ==========
97
+ var libraryPromises = {};
98
+
99
+ function loadScript(src) {
100
+ if (libraryPromises[src]) return libraryPromises[src];
101
+
102
+ libraryPromises[src] = new Promise(function(resolve, reject) {
103
+ var script = document.createElement('script');
104
+ script.src = src;
105
+ script.onload = resolve;
106
+ script.onerror = reject;
107
+ document.head.appendChild(script);
108
+ });
109
+
110
+ return libraryPromises[src];
111
+ }
112
+
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');
116
+ }
117
+ if (!window.html2canvas) {
118
+ await loadScript('https://cdnjs.cloudflare.com/ajax/libs/html2canvas/1.4.1/html2canvas.min.js');
119
+ }
120
+ }
121
+
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
+ }
126
+ }
127
+
128
+ // ========== 初期化 ==========
5
129
  document.addEventListener('DOMContentLoaded', function() {
6
130
  var container = document.getElementById('editorContainer');
7
- if (!container) return; // editorContainerがなければ何もしない
131
+ if (!container) return;
132
+
133
+ // ツールバー挿入
134
+ container.insertAdjacentHTML('afterbegin', getToolbarHtml());
135
+
136
+ // コンテキストメニュー挿入(bodyの末尾に配置)
137
+ document.body.insertAdjacentHTML('beforeend', getContextMenusHtml());
8
138
 
139
+ // ボタン参照取得
9
140
  var editModeBtn = document.getElementById('editMode');
10
141
  var toggleSidebarBtn = document.getElementById('toggleSidebar');
11
142
  var saveHtmlBtn = document.getElementById('saveHtml');
@@ -43,13 +174,31 @@ document.addEventListener('DOMContentLoaded', function() {
43
174
  }
44
175
 
45
176
  // ========== HTML保存 ==========
46
- saveHtmlBtn.addEventListener('click', function() {
177
+ if (saveHtmlBtn) saveHtmlBtn.addEventListener('click', function() {
47
178
  var wasEditMode = isEditMode;
48
- if (isEditMode) editModeBtn.click();
179
+ if (isEditMode && editModeBtn) editModeBtn.click();
49
180
 
50
- var html = '<!DOCTYPE html>\n' + document.documentElement.outerHTML;
181
+ // ツールバーとコンテキストメニューを一時的に非表示
182
+ var toolbar = container.querySelector('.toolbar');
183
+ var contextMenus = document.querySelectorAll('.context-menu');
51
184
 
52
- if (wasEditMode) editModeBtn.click();
185
+ if (toolbar) toolbar.style.display = 'none';
186
+ contextMenus.forEach(function(menu) { menu.style.display = 'none'; });
187
+
188
+ // HTML取得(ツールバーとコンテキストメニューを除外したクリーンなHTML)
189
+ var docClone = document.documentElement.cloneNode(true);
190
+ var cloneToolbar = docClone.querySelector('.toolbar');
191
+ var cloneMenus = docClone.querySelectorAll('.context-menu');
192
+ if (cloneToolbar) cloneToolbar.remove();
193
+ cloneMenus.forEach(function(menu) { menu.remove(); });
194
+
195
+ var html = '<!DOCTYPE html>\n' + docClone.outerHTML;
196
+
197
+ // 表示を復元
198
+ if (toolbar) toolbar.style.display = '';
199
+ contextMenus.forEach(function(menu) { menu.style.display = ''; });
200
+
201
+ if (wasEditMode && editModeBtn) editModeBtn.click();
53
202
 
54
203
  var blob = new Blob([html], { type: 'text/html; charset=utf-8' });
55
204
  var url = URL.createObjectURL(blob);
@@ -72,7 +221,7 @@ document.addEventListener('DOMContentLoaded', function() {
72
221
  });
73
222
 
74
223
  // ========== Markdown出力 ==========
75
- exportMdBtn.addEventListener('click', function() {
224
+ if (exportMdBtn) exportMdBtn.addEventListener('click', function() {
76
225
  var md = htmlToMarkdown();
77
226
 
78
227
  navigator.clipboard.writeText(md).then(function() {
@@ -85,7 +234,7 @@ document.addEventListener('DOMContentLoaded', function() {
85
234
  });
86
235
 
87
236
  // ========== HTMLコピー ==========
88
- exportHtmlBtn.addEventListener('click', function() {
237
+ if (exportHtmlBtn) exportHtmlBtn.addEventListener('click', function() {
89
238
  var html = getPlainHtml();
90
239
 
91
240
  navigator.clipboard.writeText(html).then(function() {
@@ -104,7 +253,7 @@ document.addEventListener('DOMContentLoaded', function() {
104
253
  var toolbar = content.querySelector('.toolbar');
105
254
  if (toolbar) toolbar.remove();
106
255
 
107
- // 右クリックメニューを除去
256
+ // 右クリックメニューを除去(念のため)
108
257
  content.querySelectorAll('.context-menu').forEach(function(menu) {
109
258
  menu.remove();
110
259
  });
@@ -130,41 +279,23 @@ document.addEventListener('DOMContentLoaded', function() {
130
279
  var h1 = container.querySelector('h1');
131
280
  var title = h1 ? h1.textContent.trim() : 'Document';
132
281
 
133
- // CSSを取得(サイドバー、リサイザー、ツールバー、右クリックメニュー関連を除外)
134
- var styleEl = document.querySelector('style');
135
- var css = '';
136
- if (styleEl) {
137
- css = styleEl.textContent
138
- .replace(/\.sidebar[\s\S]*?\}\n/g, '')
139
- .replace(/\.sidebar [^{]*\{[\s\S]*?\}\n/g, '')
140
- .replace(/\.resizer[\s\S]*?\}\n/g, '')
141
- .replace(/\.toolbar[\s\S]*?\}\n/g, '')
142
- .replace(/\.toolbar [^{]*\{[\s\S]*?\}\n/g, '')
143
- .replace(/\.context-menu[\s\S]*?\}\n/g, '')
144
- .replace(/\.context-menu[^{]*\{[\s\S]*?\}\n/g, '')
145
- .replace(/\.edit-mode[\s\S]*?\}\n/g, '')
146
- .replace(/\.edit-mode [^{]*\{[\s\S]*?\}\n/g, '')
147
- .replace(/@media[^{]*\{[\s\S]*?\}\s*\}/g, '')
148
- .replace(/\n\s*\n/g, '\n')
149
- .trim();
150
- }
151
-
152
- // 完全なHTML構造(CSS含む、ラッパー構造含む)
282
+ // 完全なHTML構造(外部CSS/JS参照付き)
153
283
  var html = '<!DOCTYPE html>\n' +
154
284
  '<html lang="ja">\n' +
155
285
  '<head>\n' +
156
- ' <meta charset="UTF-8">\n' +
157
- ' <title>' + title + '</title>\n' +
158
- '<style>\n' + css + '\n</style>\n' +
286
+ '<meta charset="UTF-8">\n' +
287
+ '<title>' + title + '</title>\n' +
288
+ '<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/sunny-html-editor/html-editor.css">\n' +
159
289
  '</head>\n' +
160
290
  '<body>\n' +
161
291
  '<div class="layout">\n' +
162
- ' <main class="main">\n' +
163
- ' <div class="container">\n' +
292
+ '<main class="main">\n' +
293
+ '<div class="container" id="editorContainer">\n' +
164
294
  innerHtml + '\n' +
165
- ' </div>\n' +
166
- ' </main>\n' +
167
295
  '</div>\n' +
296
+ '</main>\n' +
297
+ '</div>\n' +
298
+ '<script src="https://cdn.jsdelivr.net/npm/sunny-html-editor/html-editor.js"><\/script>\n' +
168
299
  '</body>\n' +
169
300
  '</html>';
170
301
 
@@ -436,7 +567,7 @@ document.addEventListener('DOMContentLoaded', function() {
436
567
  if (newWidth < 200) newWidth = 200;
437
568
  if (newWidth > 400) newWidth = 400;
438
569
  s.style.width = newWidth + 'px';
439
- main.style.marginLeft = newWidth + 'px';
570
+ if (main) main.style.marginLeft = newWidth + 'px';
440
571
  updateResizerPosition();
441
572
  });
442
573
 
@@ -448,23 +579,23 @@ document.addEventListener('DOMContentLoaded', function() {
448
579
  }
449
580
  });
450
581
 
451
- toggleSidebarBtn.addEventListener('click', function() {
582
+ if (toggleSidebarBtn) toggleSidebarBtn.addEventListener('click', function() {
452
583
  var s = document.querySelector('.sidebar');
453
584
  var r = document.querySelector('.resizer');
454
585
 
455
- if (sidebarExists) {
586
+ if (sidebarExists && s && r) {
456
587
  savedWidth = s.offsetWidth;
457
588
  sidebarHtml = s.outerHTML;
458
589
  resizerHtml = r.outerHTML;
459
590
  s.remove();
460
591
  r.remove();
461
- main.style.marginLeft = '0';
592
+ if (main) main.style.marginLeft = '0';
462
593
  sidebarExists = false;
463
- } else {
594
+ } else if (!sidebarExists && main) {
464
595
  main.insertAdjacentHTML('beforebegin', sidebarHtml);
465
596
  main.insertAdjacentHTML('beforebegin', resizerHtml);
466
597
  var newSidebar = document.querySelector('.sidebar');
467
- newSidebar.style.width = savedWidth + 'px';
598
+ if (newSidebar) newSidebar.style.width = savedWidth + 'px';
468
599
  main.style.marginLeft = savedWidth + 'px';
469
600
  setupResizer();
470
601
  updateResizerPosition();
@@ -514,7 +645,7 @@ document.addEventListener('DOMContentLoaded', function() {
514
645
  }
515
646
 
516
647
  // ========== 編集モード切り替え ==========
517
- editModeBtn.addEventListener('click', function() {
648
+ if (editModeBtn) editModeBtn.addEventListener('click', function() {
518
649
  isEditMode = !isEditMode;
519
650
  if (isEditMode) {
520
651
  container.classList.add('edit-mode');
@@ -625,19 +756,19 @@ document.addEventListener('DOMContentLoaded', function() {
625
756
  container.addEventListener('contextmenu', function(e) {
626
757
  if (!isEditMode) return;
627
758
 
628
- contextMenu.classList.remove('show');
629
- h1ContextMenu.classList.remove('show');
630
- headingContextMenu.classList.remove('show');
631
- paragraphContextMenu.classList.remove('show');
632
- listContextMenu.classList.remove('show');
633
- preContextMenu.classList.remove('show');
634
- blockquoteContextMenu.classList.remove('show');
759
+ if (contextMenu) contextMenu.classList.remove('show');
760
+ if (h1ContextMenu) h1ContextMenu.classList.remove('show');
761
+ if (headingContextMenu) headingContextMenu.classList.remove('show');
762
+ if (paragraphContextMenu) paragraphContextMenu.classList.remove('show');
763
+ if (listContextMenu) listContextMenu.classList.remove('show');
764
+ if (preContextMenu) preContextMenu.classList.remove('show');
765
+ if (blockquoteContextMenu) blockquoteContextMenu.classList.remove('show');
635
766
 
636
767
  var x = e.clientX;
637
768
  var y = e.clientY;
638
769
 
639
770
  var h1 = e.target.closest('h1');
640
- if (h1) {
771
+ if (h1 && h1ContextMenu) {
641
772
  e.preventDefault();
642
773
  contextTargetH1 = h1;
643
774
  showContextMenu(h1ContextMenu, x, y);
@@ -645,7 +776,7 @@ document.addEventListener('DOMContentLoaded', function() {
645
776
  }
646
777
 
647
778
  var heading = e.target.closest('h2, h3, h4');
648
- if (heading) {
779
+ if (heading && headingContextMenu) {
649
780
  e.preventDefault();
650
781
  contextTargetHeading = heading;
651
782
  showContextMenu(headingContextMenu, x, y);
@@ -653,7 +784,7 @@ document.addEventListener('DOMContentLoaded', function() {
653
784
  }
654
785
 
655
786
  var cell = e.target.closest('td, th');
656
- if (cell) {
787
+ if (cell && contextMenu) {
657
788
  e.preventDefault();
658
789
  contextTargetCell = cell;
659
790
  showContextMenu(contextMenu, x, y);
@@ -661,7 +792,7 @@ document.addEventListener('DOMContentLoaded', function() {
661
792
  }
662
793
 
663
794
  var li = e.target.closest('li');
664
- if (li) {
795
+ if (li && listContextMenu) {
665
796
  e.preventDefault();
666
797
  contextTargetLi = li;
667
798
  contextTargetList = li.closest('ul, ol');
@@ -670,7 +801,7 @@ document.addEventListener('DOMContentLoaded', function() {
670
801
  }
671
802
 
672
803
  var pre = e.target.closest('pre');
673
- if (pre) {
804
+ if (pre && preContextMenu) {
674
805
  e.preventDefault();
675
806
  contextTargetPre = pre;
676
807
  showContextMenu(preContextMenu, x, y);
@@ -678,7 +809,7 @@ document.addEventListener('DOMContentLoaded', function() {
678
809
  }
679
810
 
680
811
  var blockquote = e.target.closest('blockquote');
681
- if (blockquote) {
812
+ if (blockquote && blockquoteContextMenu) {
682
813
  e.preventDefault();
683
814
  contextTargetBlockquote = blockquote;
684
815
  showContextMenu(blockquoteContextMenu, x, y);
@@ -686,7 +817,7 @@ document.addEventListener('DOMContentLoaded', function() {
686
817
  }
687
818
 
688
819
  var paragraph = e.target.closest('p');
689
- if (paragraph) {
820
+ if (paragraph && paragraphContextMenu) {
690
821
  e.preventDefault();
691
822
  contextTargetParagraph = paragraph;
692
823
  showContextMenu(paragraphContextMenu, x, y);
@@ -695,24 +826,24 @@ document.addEventListener('DOMContentLoaded', function() {
695
826
  });
696
827
 
697
828
  document.addEventListener('click', function() {
698
- contextMenu.classList.remove('show');
699
- h1ContextMenu.classList.remove('show');
700
- headingContextMenu.classList.remove('show');
701
- paragraphContextMenu.classList.remove('show');
702
- listContextMenu.classList.remove('show');
703
- preContextMenu.classList.remove('show');
704
- blockquoteContextMenu.classList.remove('show');
829
+ if (contextMenu) contextMenu.classList.remove('show');
830
+ if (h1ContextMenu) h1ContextMenu.classList.remove('show');
831
+ if (headingContextMenu) headingContextMenu.classList.remove('show');
832
+ if (paragraphContextMenu) paragraphContextMenu.classList.remove('show');
833
+ if (listContextMenu) listContextMenu.classList.remove('show');
834
+ if (preContextMenu) preContextMenu.classList.remove('show');
835
+ if (blockquoteContextMenu) blockquoteContextMenu.classList.remove('show');
705
836
  });
706
837
 
707
838
  document.addEventListener('keydown', function(e) {
708
839
  if (e.key === 'Escape') {
709
- contextMenu.classList.remove('show');
710
- h1ContextMenu.classList.remove('show');
711
- headingContextMenu.classList.remove('show');
712
- paragraphContextMenu.classList.remove('show');
713
- listContextMenu.classList.remove('show');
714
- preContextMenu.classList.remove('show');
715
- blockquoteContextMenu.classList.remove('show');
840
+ if (contextMenu) contextMenu.classList.remove('show');
841
+ if (h1ContextMenu) h1ContextMenu.classList.remove('show');
842
+ if (headingContextMenu) headingContextMenu.classList.remove('show');
843
+ if (paragraphContextMenu) paragraphContextMenu.classList.remove('show');
844
+ if (listContextMenu) listContextMenu.classList.remove('show');
845
+ if (preContextMenu) preContextMenu.classList.remove('show');
846
+ if (blockquoteContextMenu) blockquoteContextMenu.classList.remove('show');
716
847
  }
717
848
 
718
849
  // ↓↑キーで編集可能要素間を移動(複数行対応)
@@ -785,15 +916,15 @@ document.addEventListener('DOMContentLoaded', function() {
785
916
  switch (e.key.toLowerCase()) {
786
917
  case 'b':
787
918
  e.preventDefault();
788
- toggleSidebarBtn.click();
919
+ if (toggleSidebarBtn) toggleSidebarBtn.click();
789
920
  break;
790
921
  case 'e':
791
922
  e.preventDefault();
792
- editModeBtn.click();
923
+ if (editModeBtn) editModeBtn.click();
793
924
  break;
794
925
  case 'm':
795
926
  e.preventDefault();
796
- exportMdBtn.click();
927
+ if (exportMdBtn) exportMdBtn.click();
797
928
  break;
798
929
  }
799
930
  }
@@ -902,7 +1033,7 @@ document.addEventListener('DOMContentLoaded', function() {
902
1033
  }
903
1034
  });
904
1035
 
905
- contextMenu.addEventListener('click', function(e) {
1036
+ if (contextMenu) contextMenu.addEventListener('click', function(e) {
906
1037
  e.stopPropagation();
907
1038
  var item = e.target.closest('.context-menu-item');
908
1039
  if (!item || !contextTargetCell) return;
@@ -990,7 +1121,7 @@ document.addEventListener('DOMContentLoaded', function() {
990
1121
  contextTargetCell = null;
991
1122
  });
992
1123
 
993
- h1ContextMenu.addEventListener('click', function(e) {
1124
+ if (h1ContextMenu) h1ContextMenu.addEventListener('click', function(e) {
994
1125
  e.stopPropagation();
995
1126
  var item = e.target.closest('.context-menu-item');
996
1127
  if (!item || !contextTargetH1) return;
@@ -1026,7 +1157,7 @@ document.addEventListener('DOMContentLoaded', function() {
1026
1157
  contextTargetH1 = null;
1027
1158
  });
1028
1159
 
1029
- headingContextMenu.addEventListener('click', function(e) {
1160
+ if (headingContextMenu) headingContextMenu.addEventListener('click', function(e) {
1030
1161
  e.stopPropagation();
1031
1162
  var item = e.target.closest('.context-menu-item');
1032
1163
  if (!item || !contextTargetHeading) return;
@@ -1089,7 +1220,7 @@ document.addEventListener('DOMContentLoaded', function() {
1089
1220
  contextTargetHeading = null;
1090
1221
  });
1091
1222
 
1092
- paragraphContextMenu.addEventListener('click', function(e) {
1223
+ if (paragraphContextMenu) paragraphContextMenu.addEventListener('click', function(e) {
1093
1224
  e.stopPropagation();
1094
1225
  var item = e.target.closest('.context-menu-item');
1095
1226
  if (!item || !contextTargetParagraph) return;
@@ -1134,7 +1265,7 @@ document.addEventListener('DOMContentLoaded', function() {
1134
1265
  contextTargetParagraph = null;
1135
1266
  });
1136
1267
 
1137
- listContextMenu.addEventListener('click', function(e) {
1268
+ if (listContextMenu) listContextMenu.addEventListener('click', function(e) {
1138
1269
  e.stopPropagation();
1139
1270
  var item = e.target.closest('.context-menu-item');
1140
1271
  if (!item || !contextTargetList) return;
@@ -1194,7 +1325,7 @@ document.addEventListener('DOMContentLoaded', function() {
1194
1325
  contextTargetLi = null;
1195
1326
  });
1196
1327
 
1197
- preContextMenu.addEventListener('click', function(e) {
1328
+ if (preContextMenu) preContextMenu.addEventListener('click', function(e) {
1198
1329
  e.stopPropagation();
1199
1330
  var item = e.target.closest('.context-menu-item');
1200
1331
  if (!item || !contextTargetPre) return;
@@ -1239,7 +1370,7 @@ document.addEventListener('DOMContentLoaded', function() {
1239
1370
  contextTargetPre = null;
1240
1371
  });
1241
1372
 
1242
- blockquoteContextMenu.addEventListener('click', function(e) {
1373
+ if (blockquoteContextMenu) blockquoteContextMenu.addEventListener('click', function(e) {
1243
1374
  e.stopPropagation();
1244
1375
  var item = e.target.closest('.context-menu-item');
1245
1376
  if (!item || !contextTargetBlockquote) return;
@@ -1364,13 +1495,16 @@ document.addEventListener('DOMContentLoaded', function() {
1364
1495
  }
1365
1496
 
1366
1497
  // ========== PDF出力 ==========
1367
- exportPdfBtn.addEventListener('click', async function() {
1498
+ if (exportPdfBtn) exportPdfBtn.addEventListener('click', async function() {
1368
1499
  var wasEditMode = isEditMode;
1369
- if (isEditMode) editModeBtn.click();
1500
+ if (isEditMode && editModeBtn) editModeBtn.click();
1370
1501
 
1371
1502
  exportPdfBtn.textContent = '...';
1372
1503
 
1373
1504
  try {
1505
+ // ライブラリ遅延読み込み
1506
+ await ensurePdfLibraries();
1507
+
1374
1508
  // jsPDFインスタンス作成
1375
1509
  var { jsPDF } = window.jspdf;
1376
1510
  var pdf = new jsPDF({
@@ -1553,7 +1687,7 @@ document.addEventListener('DOMContentLoaded', function() {
1553
1687
  }
1554
1688
 
1555
1689
  exportPdfBtn.textContent = '📕';
1556
- if (wasEditMode) editModeBtn.click();
1690
+ if (wasEditMode && editModeBtn) editModeBtn.click();
1557
1691
  });
1558
1692
 
1559
1693
  // ========== Excel出力 ==========
@@ -1563,7 +1697,7 @@ document.addEventListener('DOMContentLoaded', function() {
1563
1697
  body: 12
1564
1698
  };
1565
1699
 
1566
- exportExcelBtn.addEventListener('click', async function() {
1700
+ if (exportExcelBtn) exportExcelBtn.addEventListener('click', async function() {
1567
1701
  try {
1568
1702
  await convertToExcel();
1569
1703
  } catch (error) {
@@ -1573,6 +1707,9 @@ document.addEventListener('DOMContentLoaded', function() {
1573
1707
  });
1574
1708
 
1575
1709
  async function convertToExcel() {
1710
+ // ライブラリ遅延読み込み
1711
+ await ensureExcelLibrary();
1712
+
1576
1713
  var content = container.querySelector('.content');
1577
1714
  if (!content) content = container;
1578
1715
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sunny-html-editor",
3
- "version": "1.0.2",
3
+ "version": "1.1.0",
4
4
  "description": "軽量WYSIWYGエディタ - 既存のHTMLに編集機能を後付けで追加",
5
5
  "main": "html-editor.js",
6
6
  "files": [