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.
- package/README.md +47 -104
- package/html-editor.js +222 -85
- 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
|
-
|
|
16
|
+
### CDN(推奨)
|
|
28
17
|
|
|
29
18
|
```html
|
|
30
|
-
|
|
31
|
-
<script src="https://
|
|
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
|
-
|
|
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
|
-
|
|
71
|
-
|
|
72
|
-
<
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
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
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
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
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
92
|
+
| ボタン | 機能 |
|
|
93
|
+
|--------|------|
|
|
94
|
+
| ↔️ | サイドバー表示/非表示 |
|
|
95
|
+
| 📝 | 編集モード切替 |
|
|
96
|
+
| Ⓜ️ | Markdownコピー |
|
|
97
|
+
| 🌐 | HTMLコピー |
|
|
98
|
+
| 💾 | HTML保存(ダウンロード) |
|
|
99
|
+
| 📊 | Excel出力 |
|
|
100
|
+
| 📕 | PDF出力 |
|
|
124
101
|
|
|
125
|
-
|
|
102
|
+
## バージョン履歴
|
|
126
103
|
|
|
127
|
-
|
|
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
|
|
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;
|
|
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
|
-
|
|
181
|
+
// ツールバーとコンテキストメニューを一時的に非表示
|
|
182
|
+
var toolbar = container.querySelector('.toolbar');
|
|
183
|
+
var contextMenus = document.querySelectorAll('.context-menu');
|
|
51
184
|
|
|
52
|
-
if (
|
|
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
|
-
'
|
|
157
|
-
'
|
|
158
|
-
'<
|
|
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
|
-
'
|
|
163
|
-
'
|
|
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
|
|