@burger-editor/local 4.0.0-alpha.46 → 4.0.0-alpha.47
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 +46 -0
- package/dist/client.js +427 -24
- package/dist/client.js.map +1 -1
- package/package.json +5 -5
- package/server/view/app.d.ts +9 -9
- package/server/view/app.d.ts.map +1 -1
- package/server/view/app.js +10 -14
- package/style/app.css +277 -0
package/README.md
CHANGED
|
@@ -120,6 +120,52 @@ title: 'New Page'
|
|
|
120
120
|
- `interval` (number): チェック間隔(ミリ秒)(デフォルト: 10000)
|
|
121
121
|
- `retryCount` (number): リトライ回数(デフォルト: 3)
|
|
122
122
|
|
|
123
|
+
## Front Matter編集機能
|
|
124
|
+
|
|
125
|
+
BurgerEditorは、HTMLファイルのFront Matter(YAMLメタデータ)を編集するUIを提供します。編集ボックスはBurgerEditor編集領域の上部に表示されます。
|
|
126
|
+
|
|
127
|
+
### 対応するデータ型
|
|
128
|
+
|
|
129
|
+
Front Matterエディターは、値の型を自動検出して適切な入力UIを表示します:
|
|
130
|
+
|
|
131
|
+
| 型 | 入力UI | 例 |
|
|
132
|
+
| ----------------- | ------------------ | ------------------------ |
|
|
133
|
+
| 文字列 | テキスト入力 | `title: 'Hello World'` |
|
|
134
|
+
| 数値 | 数値入力 | `order: 1` |
|
|
135
|
+
| 真偽値 | チェックボックス | `published: true` |
|
|
136
|
+
| 日付 | 日付ピッカー | `date: '2025-01-06'` |
|
|
137
|
+
| 配列/オブジェクト | JSONテキストエリア | `tags: ['blog', 'news']` |
|
|
138
|
+
|
|
139
|
+
### 使用方法
|
|
140
|
+
|
|
141
|
+
1. HTMLファイルの先頭にFront Matterを記述します:
|
|
142
|
+
|
|
143
|
+
```html
|
|
144
|
+
---
|
|
145
|
+
title: 'ページタイトル'
|
|
146
|
+
date: '2025-01-06'
|
|
147
|
+
published: true
|
|
148
|
+
---
|
|
149
|
+
|
|
150
|
+
<div class="my-editor">
|
|
151
|
+
<!-- コンテンツ -->
|
|
152
|
+
</div>
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
2. BurgerEditorでファイルを開くと、編集領域の上にFront Matterエディターが表示されます
|
|
156
|
+
|
|
157
|
+
3. フィールドの追加・削除、値の編集が可能です
|
|
158
|
+
|
|
159
|
+
4. 変更は自動的に保存されます(500msのデバウンス付き)
|
|
160
|
+
|
|
161
|
+
### 新規フィールドの追加
|
|
162
|
+
|
|
163
|
+
「+ 追加」ボタンをクリックすると、新しいフィールドを追加できます:
|
|
164
|
+
|
|
165
|
+
1. キー名を入力(例: `author`, `category`)
|
|
166
|
+
2. 型を選択(テキスト、数値、真偽値、日付、JSON)
|
|
167
|
+
3. 「追加」ボタンをクリック
|
|
168
|
+
|
|
123
169
|
## カスタムブロックカタログの追加
|
|
124
170
|
|
|
125
171
|
既存のブロックカタログにカスタムブロックを追加できます:
|
package/dist/client.js
CHANGED
|
@@ -1294,7 +1294,7 @@ var style$b = "/* No Styling */\n";
|
|
|
1294
1294
|
var template$b = "<a href=\"\" data-kind=\"primary\" data-before-icon=\"none\" data-after-icon=\"none\" data-bge=\"link:href, target:target, kind:data-kind, before-icon:data-before-icon, after-icon:data-after-icon\">\n\t<div>\n\t\t<span data-bge=\"text\">ボタン</span>\n\t\t<span data-bge=\"subtext\">サブテキスト</span>\n\t</div>\n</a>\n";
|
|
1295
1295
|
|
|
1296
1296
|
var button = createItem$1({
|
|
1297
|
-
version: "4.0.0-alpha.
|
|
1297
|
+
version: "4.0.0-alpha.46",
|
|
1298
1298
|
name: "button",
|
|
1299
1299
|
template: template$b,
|
|
1300
1300
|
style: style$b,
|
|
@@ -1350,7 +1350,7 @@ var style$a = "/* No Styling */\n";
|
|
|
1350
1350
|
var template$a = "<details data-bge=\"open:open\">\n\t<summary data-bge=\"summary\">折りたたみコンテンツ</summary>\n\t<div data-bge=\"content\"><p>内容を入力してください</p></div>\n</details>\n";
|
|
1351
1351
|
|
|
1352
1352
|
var details = createItem$1({
|
|
1353
|
-
version: "4.0.0-alpha.
|
|
1353
|
+
version: "4.0.0-alpha.46",
|
|
1354
1354
|
name: "details",
|
|
1355
1355
|
template: template$a,
|
|
1356
1356
|
style: style$a,
|
|
@@ -1364,7 +1364,7 @@ var style$9 = "[data-bgi='download-file'] {\n\t[data-bge*='size'] {\n\t\t&::befo
|
|
|
1364
1364
|
var template$9 = "<a href=\"./files/bgeditor/bg-sample.pdf\" target=\"_blank\" data-bge=\"path:href, download:download\">\n\t<div>\n\t\t<span data-bge=\"name\">サンプルダウンロードファイル</span>\n\t\t<span data-bge=\"formated-size, size:data-size\" data-size=\"138158\">134.92kB</span>\n\t</div>\n</a>\n";
|
|
1365
1365
|
|
|
1366
1366
|
var downloadFile = createItem$1({
|
|
1367
|
-
version: "4.0.0-alpha.
|
|
1367
|
+
version: "4.0.0-alpha.46",
|
|
1368
1368
|
name: "download-file",
|
|
1369
1369
|
template: template$9,
|
|
1370
1370
|
style: style$9,
|
|
@@ -1403,7 +1403,7 @@ var style$8 = "[data-bgi='google-maps'] {\n\tdiv {\n\t\tinline-size: 100%;\n\t\t
|
|
|
1403
1403
|
var template$8 = "<div data-lat=\"35.681382\" data-lng=\"139.766084\" data-zoom=\"16\" data-bge=\"lat:data-lat, lng:data-lng, zoom:data-zoom\">\n\t<img data-bge=\"img:src\" src=\"https://maps.google.com/maps/api/staticmap?center=35.681382,139.766084&zoom=16&size=640x400&markers=color:red|color:red|35.681382,139.766084&scale=2&key=%googleMapsApiKey%\" width=\"8\" height=\"5\" alt=\"Google Maps\" />\n</div>\n<a href=\"https://maps.apple.com/?q=35.681382,139.766084\" data-bge=\"url:href\" target=\"_blank\"><span>アプリで開く</span></a>\n";
|
|
1404
1404
|
|
|
1405
1405
|
var googleMaps = createItem$1({
|
|
1406
|
-
version: "4.0.0-alpha.
|
|
1406
|
+
version: "4.0.0-alpha.46",
|
|
1407
1407
|
name: "google-maps",
|
|
1408
1408
|
template: template$8,
|
|
1409
1409
|
style: style$8,
|
|
@@ -1538,7 +1538,7 @@ var style$7 = "[data-bgi='hr'] {\n\t--inline-size: 100%;\n\t--border-color: #000
|
|
|
1538
1538
|
var template$7 = "<div data-bgi-hr-kind=\"primary\" data-bge=\"kind:data-bgi-hr-kind\">\n\t<hr />\n</div>\n";
|
|
1539
1539
|
|
|
1540
1540
|
var hr = createItem$1({
|
|
1541
|
-
version: "4.0.0-alpha.
|
|
1541
|
+
version: "4.0.0-alpha.46",
|
|
1542
1542
|
name: "hr",
|
|
1543
1543
|
template: template$7,
|
|
1544
1544
|
style: style$7,
|
|
@@ -1628,7 +1628,7 @@ function createWidthState() {
|
|
|
1628
1628
|
|
|
1629
1629
|
const ORIGIN = "__org";
|
|
1630
1630
|
var image = createItem$1({
|
|
1631
|
-
version: "4.0.0-alpha.
|
|
1631
|
+
version: "4.0.0-alpha.46",
|
|
1632
1632
|
name: "image",
|
|
1633
1633
|
template: template$6,
|
|
1634
1634
|
style: style$6,
|
|
@@ -1826,7 +1826,7 @@ var style$5 = "[data-bgi='import'] {\n\tbge-import {\n\t\t&::before {\n\t\t\tfon
|
|
|
1826
1826
|
var template$5 = "<bge-import data-bge=\":src\" src=\"\"></bge-import>\n";
|
|
1827
1827
|
|
|
1828
1828
|
var importItem = createItem$1({
|
|
1829
|
-
version: "4.0.0-alpha.
|
|
1829
|
+
version: "4.0.0-alpha.46",
|
|
1830
1830
|
name: "import",
|
|
1831
1831
|
template: template$5,
|
|
1832
1832
|
style: style$5,
|
|
@@ -1840,7 +1840,7 @@ var style$4 = "/* No Styling */\n";
|
|
|
1840
1840
|
var template$4 = "<div data-bge=\":scrollable\" data-bge-scrollable=\"false\">\n\t<table>\n\t\t<caption data-bge=\"caption\">\n\t\t\tキャプションを入力してください\n\t\t</caption>\n\t\t<tbody data-bge-list>\n\t\t\t<tr>\n\t\t\t\t<th data-bge=\"th\">表組の見出し</th>\n\t\t\t\t<td data-bge=\"td\">表組の内容を入力してください</td>\n\t\t\t</tr>\n\t\t</tbody>\n\t</table>\n</div>\n";
|
|
1841
1841
|
|
|
1842
1842
|
var table = createItem$1({
|
|
1843
|
-
version: "4.0.0-alpha.
|
|
1843
|
+
version: "4.0.0-alpha.46",
|
|
1844
1844
|
name: "table",
|
|
1845
1845
|
template: template$4,
|
|
1846
1846
|
style: style$4,
|
|
@@ -1868,7 +1868,7 @@ var style$3 = "/* No Styling */\n";
|
|
|
1868
1868
|
var template$3 = "<h2 data-bge=\"title-h2\">見出しを入力してください</h2>\n";
|
|
1869
1869
|
|
|
1870
1870
|
var titleH2 = createItem$1({
|
|
1871
|
-
version: "4.0.0-alpha.
|
|
1871
|
+
version: "4.0.0-alpha.46",
|
|
1872
1872
|
name: "title-h2",
|
|
1873
1873
|
template: template$3,
|
|
1874
1874
|
style: style$3,
|
|
@@ -1882,7 +1882,7 @@ var style$2 = "/* No Styling */\n";
|
|
|
1882
1882
|
var template$2 = "<h3 data-bge=\"title-h3\">見出しを入力してください</h3>\n";
|
|
1883
1883
|
|
|
1884
1884
|
var titleH3 = createItem$1({
|
|
1885
|
-
version: "4.0.0-alpha.
|
|
1885
|
+
version: "4.0.0-alpha.46",
|
|
1886
1886
|
name: "title-h3",
|
|
1887
1887
|
template: template$2,
|
|
1888
1888
|
style: style$2,
|
|
@@ -1896,7 +1896,7 @@ var style$1 = "/* No Styling */\n";
|
|
|
1896
1896
|
var template$1 = "<div data-bge=\"wysiwyg\"><p>本文を入力してください</p></div>\n";
|
|
1897
1897
|
|
|
1898
1898
|
var wysiwyg = createItem$1({
|
|
1899
|
-
version: "4.0.0-alpha.
|
|
1899
|
+
version: "4.0.0-alpha.46",
|
|
1900
1900
|
name: "wysiwyg",
|
|
1901
1901
|
template: template$1,
|
|
1902
1902
|
style: style$1,
|
|
@@ -1911,7 +1911,7 @@ var template = "<div data-id=\"3KtWfp0UopM\" data-title=\"YouTube動画\" data-w
|
|
|
1911
1911
|
|
|
1912
1912
|
const FALLBACK_TITLE = "YouTube\u52D5\u753B";
|
|
1913
1913
|
var youtube = createItem$1({
|
|
1914
|
-
version: "4.0.0-alpha.
|
|
1914
|
+
version: "4.0.0-alpha.46",
|
|
1915
1915
|
name: "youtube",
|
|
1916
1916
|
template,
|
|
1917
1917
|
style: style$c,
|
|
@@ -40232,7 +40232,368 @@ function $upload(request) {
|
|
|
40232
40232
|
};
|
|
40233
40233
|
}
|
|
40234
40234
|
|
|
40235
|
+
class FrontMatterEditor {
|
|
40236
|
+
#container;
|
|
40237
|
+
#fields = [];
|
|
40238
|
+
#hasFrontMatter;
|
|
40239
|
+
#isCollapsed = false;
|
|
40240
|
+
#onUpdated;
|
|
40241
|
+
#originalFrontMatter;
|
|
40242
|
+
constructor(options) {
|
|
40243
|
+
this.#container = options.container;
|
|
40244
|
+
this.#onUpdated = options.onUpdated;
|
|
40245
|
+
this.#hasFrontMatter = options.hasFrontMatter;
|
|
40246
|
+
this.#fields = this.#parseInitialData(options.initialData);
|
|
40247
|
+
if (options.hasFrontMatter) {
|
|
40248
|
+
this.#originalFrontMatter = JSON.stringify(options.initialData);
|
|
40249
|
+
}
|
|
40250
|
+
this.#render();
|
|
40251
|
+
}
|
|
40252
|
+
/**
|
|
40253
|
+
* Get current Front Matter data
|
|
40254
|
+
*/
|
|
40255
|
+
getData() {
|
|
40256
|
+
const data = {};
|
|
40257
|
+
for (const field of this.#fields) {
|
|
40258
|
+
data[field.key] = field.value;
|
|
40259
|
+
}
|
|
40260
|
+
return data;
|
|
40261
|
+
}
|
|
40262
|
+
/**
|
|
40263
|
+
* Get original Front Matter string for format preservation
|
|
40264
|
+
*/
|
|
40265
|
+
getOriginalFrontMatter() {
|
|
40266
|
+
return this.#originalFrontMatter;
|
|
40267
|
+
}
|
|
40268
|
+
/**
|
|
40269
|
+
* Check if Front Matter has been modified
|
|
40270
|
+
*/
|
|
40271
|
+
hasChanges() {
|
|
40272
|
+
if (!this.#hasFrontMatter && this.#fields.length === 0) {
|
|
40273
|
+
return false;
|
|
40274
|
+
}
|
|
40275
|
+
const currentData = JSON.stringify(this.getData());
|
|
40276
|
+
return currentData !== this.#originalFrontMatter;
|
|
40277
|
+
}
|
|
40278
|
+
/**
|
|
40279
|
+
* Add a new field
|
|
40280
|
+
* @param key
|
|
40281
|
+
* @param type
|
|
40282
|
+
*/
|
|
40283
|
+
#addField(key, type) {
|
|
40284
|
+
const defaultValue = this.#getDefaultValue(type);
|
|
40285
|
+
this.#fields.push({ key, type, value: defaultValue });
|
|
40286
|
+
this.#hasFrontMatter = true;
|
|
40287
|
+
this.#render();
|
|
40288
|
+
this.#notifyUpdate();
|
|
40289
|
+
}
|
|
40290
|
+
/**
|
|
40291
|
+
* Create a field element
|
|
40292
|
+
* @param field
|
|
40293
|
+
* @param index
|
|
40294
|
+
*/
|
|
40295
|
+
#createFieldElement(field, index) {
|
|
40296
|
+
const fieldEl = document.createElement("div");
|
|
40297
|
+
fieldEl.className = "fm-editor-field";
|
|
40298
|
+
fieldEl.dataset.type = field.type;
|
|
40299
|
+
const labelEl = document.createElement("label");
|
|
40300
|
+
labelEl.className = "fm-editor-field-label";
|
|
40301
|
+
labelEl.textContent = field.key;
|
|
40302
|
+
fieldEl.append(labelEl);
|
|
40303
|
+
const inputWrapper = document.createElement("div");
|
|
40304
|
+
inputWrapper.className = "fm-editor-field-input";
|
|
40305
|
+
const inputEl = this.#createInputElement(field, index);
|
|
40306
|
+
inputWrapper.append(inputEl);
|
|
40307
|
+
fieldEl.append(inputWrapper);
|
|
40308
|
+
const deleteBtn = document.createElement("button");
|
|
40309
|
+
deleteBtn.type = "button";
|
|
40310
|
+
deleteBtn.className = "fm-editor-field-delete";
|
|
40311
|
+
deleteBtn.title = "フィールドを削除";
|
|
40312
|
+
deleteBtn.textContent = "×";
|
|
40313
|
+
deleteBtn.addEventListener("click", () => {
|
|
40314
|
+
this.#deleteField(index);
|
|
40315
|
+
});
|
|
40316
|
+
fieldEl.append(deleteBtn);
|
|
40317
|
+
return fieldEl;
|
|
40318
|
+
}
|
|
40319
|
+
/**
|
|
40320
|
+
* Create input element based on field type
|
|
40321
|
+
* @param field
|
|
40322
|
+
* @param index
|
|
40323
|
+
*/
|
|
40324
|
+
#createInputElement(field, index) {
|
|
40325
|
+
switch (field.type) {
|
|
40326
|
+
case "boolean": {
|
|
40327
|
+
const checkbox = document.createElement("input");
|
|
40328
|
+
checkbox.type = "checkbox";
|
|
40329
|
+
checkbox.checked = Boolean(field.value);
|
|
40330
|
+
checkbox.addEventListener("change", () => {
|
|
40331
|
+
this.#updateFieldValue(index, checkbox.checked);
|
|
40332
|
+
});
|
|
40333
|
+
return checkbox;
|
|
40334
|
+
}
|
|
40335
|
+
case "number": {
|
|
40336
|
+
const input = document.createElement("input");
|
|
40337
|
+
input.type = "number";
|
|
40338
|
+
input.value = String(field.value ?? "");
|
|
40339
|
+
input.addEventListener("input", () => {
|
|
40340
|
+
const numValue = input.value === "" ? null : Number(input.value);
|
|
40341
|
+
this.#updateFieldValue(index, numValue);
|
|
40342
|
+
});
|
|
40343
|
+
return input;
|
|
40344
|
+
}
|
|
40345
|
+
case "date": {
|
|
40346
|
+
const input = document.createElement("input");
|
|
40347
|
+
input.type = "date";
|
|
40348
|
+
if (field.value) {
|
|
40349
|
+
const dateValue = field.value instanceof Date ? field.value : new Date(String(field.value));
|
|
40350
|
+
if (!Number.isNaN(dateValue.getTime())) {
|
|
40351
|
+
const isoDate = dateValue.toISOString().split("T")[0] ?? "";
|
|
40352
|
+
input.value = isoDate;
|
|
40353
|
+
}
|
|
40354
|
+
}
|
|
40355
|
+
input.addEventListener("input", () => {
|
|
40356
|
+
this.#updateFieldValue(index, input.value);
|
|
40357
|
+
});
|
|
40358
|
+
return input;
|
|
40359
|
+
}
|
|
40360
|
+
case "json": {
|
|
40361
|
+
const textarea = document.createElement("textarea");
|
|
40362
|
+
textarea.rows = 3;
|
|
40363
|
+
try {
|
|
40364
|
+
textarea.value = JSON.stringify(field.value, null, 2);
|
|
40365
|
+
} catch {
|
|
40366
|
+
textarea.value = String(field.value);
|
|
40367
|
+
}
|
|
40368
|
+
textarea.addEventListener("input", () => {
|
|
40369
|
+
try {
|
|
40370
|
+
const parsed = JSON.parse(textarea.value);
|
|
40371
|
+
textarea.classList.remove("fm-editor-error");
|
|
40372
|
+
this.#updateFieldValue(index, parsed);
|
|
40373
|
+
} catch {
|
|
40374
|
+
textarea.classList.add("fm-editor-error");
|
|
40375
|
+
}
|
|
40376
|
+
});
|
|
40377
|
+
return textarea;
|
|
40378
|
+
}
|
|
40379
|
+
default: {
|
|
40380
|
+
const input = document.createElement("input");
|
|
40381
|
+
input.type = "text";
|
|
40382
|
+
input.value = String(field.value ?? "");
|
|
40383
|
+
input.addEventListener("input", () => {
|
|
40384
|
+
this.#updateFieldValue(index, input.value);
|
|
40385
|
+
});
|
|
40386
|
+
return input;
|
|
40387
|
+
}
|
|
40388
|
+
}
|
|
40389
|
+
}
|
|
40390
|
+
/**
|
|
40391
|
+
* Delete a field
|
|
40392
|
+
* @param index
|
|
40393
|
+
*/
|
|
40394
|
+
#deleteField(index) {
|
|
40395
|
+
this.#fields = this.#fields.filter((_, i) => i !== index);
|
|
40396
|
+
this.#render();
|
|
40397
|
+
this.#notifyUpdate();
|
|
40398
|
+
}
|
|
40399
|
+
/**
|
|
40400
|
+
* Detect the type of a value
|
|
40401
|
+
* @param value
|
|
40402
|
+
*/
|
|
40403
|
+
#detectType(value) {
|
|
40404
|
+
if (typeof value === "boolean") {
|
|
40405
|
+
return "boolean";
|
|
40406
|
+
}
|
|
40407
|
+
if (typeof value === "number") {
|
|
40408
|
+
return "number";
|
|
40409
|
+
}
|
|
40410
|
+
if (typeof value === "string") {
|
|
40411
|
+
if (/^\d{4}-\d{2}-\d{2}(?:T\d{2}:\d{2}:\d{2})?/.test(value)) {
|
|
40412
|
+
const parsed = Date.parse(value);
|
|
40413
|
+
if (!Number.isNaN(parsed)) {
|
|
40414
|
+
return "date";
|
|
40415
|
+
}
|
|
40416
|
+
}
|
|
40417
|
+
return "text";
|
|
40418
|
+
}
|
|
40419
|
+
if (value instanceof Date) {
|
|
40420
|
+
return "date";
|
|
40421
|
+
}
|
|
40422
|
+
if (typeof value === "object" && value !== null) {
|
|
40423
|
+
return "json";
|
|
40424
|
+
}
|
|
40425
|
+
return "text";
|
|
40426
|
+
}
|
|
40427
|
+
/**
|
|
40428
|
+
* Get default value for a type
|
|
40429
|
+
* @param type
|
|
40430
|
+
*/
|
|
40431
|
+
#getDefaultValue(type) {
|
|
40432
|
+
switch (type) {
|
|
40433
|
+
case "boolean": {
|
|
40434
|
+
return false;
|
|
40435
|
+
}
|
|
40436
|
+
case "number": {
|
|
40437
|
+
return 0;
|
|
40438
|
+
}
|
|
40439
|
+
case "date": {
|
|
40440
|
+
return (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
|
|
40441
|
+
}
|
|
40442
|
+
case "json": {
|
|
40443
|
+
return [];
|
|
40444
|
+
}
|
|
40445
|
+
default: {
|
|
40446
|
+
return "";
|
|
40447
|
+
}
|
|
40448
|
+
}
|
|
40449
|
+
}
|
|
40450
|
+
/**
|
|
40451
|
+
* Notify update callback
|
|
40452
|
+
*/
|
|
40453
|
+
#notifyUpdate() {
|
|
40454
|
+
if (this.#onUpdated) {
|
|
40455
|
+
this.#onUpdated(this.getData());
|
|
40456
|
+
}
|
|
40457
|
+
}
|
|
40458
|
+
/**
|
|
40459
|
+
* Parse initial data and detect field types
|
|
40460
|
+
* @param data
|
|
40461
|
+
*/
|
|
40462
|
+
#parseInitialData(data) {
|
|
40463
|
+
const fields = [];
|
|
40464
|
+
for (const [key, value] of Object.entries(data)) {
|
|
40465
|
+
fields.push({
|
|
40466
|
+
key,
|
|
40467
|
+
type: this.#detectType(value),
|
|
40468
|
+
value
|
|
40469
|
+
});
|
|
40470
|
+
}
|
|
40471
|
+
return fields;
|
|
40472
|
+
}
|
|
40473
|
+
/**
|
|
40474
|
+
* Render the editor UI
|
|
40475
|
+
*/
|
|
40476
|
+
#render() {
|
|
40477
|
+
this.#container.innerHTML = "";
|
|
40478
|
+
this.#container.classList.add("fm-editor");
|
|
40479
|
+
const header = document.createElement("div");
|
|
40480
|
+
header.className = "fm-editor-header";
|
|
40481
|
+
header.innerHTML = `
|
|
40482
|
+
<button type="button" class="fm-editor-toggle" aria-expanded="${!this.#isCollapsed}">
|
|
40483
|
+
<span class="fm-editor-toggle-icon">${this.#isCollapsed ? "▶" : "▼"}</span>
|
|
40484
|
+
<span>Front Matter</span>
|
|
40485
|
+
</button>
|
|
40486
|
+
<button type="button" class="fm-editor-add" title="フィールドを追加">+ 追加</button>
|
|
40487
|
+
`;
|
|
40488
|
+
this.#container.append(header);
|
|
40489
|
+
const toggleBtn = header.querySelector(".fm-editor-toggle");
|
|
40490
|
+
toggleBtn?.addEventListener("click", () => {
|
|
40491
|
+
this.#isCollapsed = !this.#isCollapsed;
|
|
40492
|
+
this.#render();
|
|
40493
|
+
});
|
|
40494
|
+
const addBtn = header.querySelector(".fm-editor-add");
|
|
40495
|
+
addBtn?.addEventListener("click", () => {
|
|
40496
|
+
this.#showAddFieldDialog();
|
|
40497
|
+
});
|
|
40498
|
+
if (!this.#isCollapsed) {
|
|
40499
|
+
const fieldsContainer = document.createElement("div");
|
|
40500
|
+
fieldsContainer.className = "fm-editor-fields";
|
|
40501
|
+
if (this.#fields.length === 0) {
|
|
40502
|
+
const emptyMessage = document.createElement("div");
|
|
40503
|
+
emptyMessage.className = "fm-editor-empty";
|
|
40504
|
+
emptyMessage.textContent = "フィールドがありません。「+ 追加」ボタンでフィールドを追加してください。";
|
|
40505
|
+
fieldsContainer.append(emptyMessage);
|
|
40506
|
+
} else {
|
|
40507
|
+
for (const [index, field] of this.#fields.entries()) {
|
|
40508
|
+
const fieldEl = this.#createFieldElement(field, index);
|
|
40509
|
+
fieldsContainer.append(fieldEl);
|
|
40510
|
+
}
|
|
40511
|
+
}
|
|
40512
|
+
this.#container.append(fieldsContainer);
|
|
40513
|
+
}
|
|
40514
|
+
}
|
|
40515
|
+
/**
|
|
40516
|
+
* Show dialog to add a new field
|
|
40517
|
+
*/
|
|
40518
|
+
#showAddFieldDialog() {
|
|
40519
|
+
const dialog = document.createElement("dialog");
|
|
40520
|
+
dialog.className = "fm-editor-dialog";
|
|
40521
|
+
dialog.innerHTML = `
|
|
40522
|
+
<form method="dialog">
|
|
40523
|
+
<h3>フィールドを追加</h3>
|
|
40524
|
+
<div class="fm-editor-dialog-field">
|
|
40525
|
+
<label for="fm-new-key">キー名</label>
|
|
40526
|
+
<input type="text" id="fm-new-key" name="key" required placeholder="例: title, author, date" />
|
|
40527
|
+
</div>
|
|
40528
|
+
<div class="fm-editor-dialog-field">
|
|
40529
|
+
<label for="fm-new-type">型</label>
|
|
40530
|
+
<select id="fm-new-type" name="type">
|
|
40531
|
+
<option value="text">テキスト</option>
|
|
40532
|
+
<option value="number">数値</option>
|
|
40533
|
+
<option value="boolean">真偽値</option>
|
|
40534
|
+
<option value="date">日付</option>
|
|
40535
|
+
<option value="json">JSON(配列/オブジェクト)</option>
|
|
40536
|
+
</select>
|
|
40537
|
+
</div>
|
|
40538
|
+
<div class="fm-editor-dialog-actions">
|
|
40539
|
+
<button type="button" class="fm-editor-dialog-cancel">キャンセル</button>
|
|
40540
|
+
<button type="submit" class="fm-editor-dialog-submit">追加</button>
|
|
40541
|
+
</div>
|
|
40542
|
+
</form>
|
|
40543
|
+
`;
|
|
40544
|
+
const cancelBtn = dialog.querySelector(".fm-editor-dialog-cancel");
|
|
40545
|
+
cancelBtn?.addEventListener("click", () => {
|
|
40546
|
+
dialog.close();
|
|
40547
|
+
dialog.remove();
|
|
40548
|
+
});
|
|
40549
|
+
const form = dialog.querySelector("form");
|
|
40550
|
+
form?.addEventListener("submit", (e) => {
|
|
40551
|
+
e.preventDefault();
|
|
40552
|
+
const formData = new FormData(form);
|
|
40553
|
+
const key = formData.get("key");
|
|
40554
|
+
const type = formData.get("type");
|
|
40555
|
+
if (key && !this.#fields.some((f) => f.key === key)) {
|
|
40556
|
+
this.#addField(key, type);
|
|
40557
|
+
}
|
|
40558
|
+
dialog.close();
|
|
40559
|
+
dialog.remove();
|
|
40560
|
+
});
|
|
40561
|
+
document.body.append(dialog);
|
|
40562
|
+
dialog.showModal();
|
|
40563
|
+
const keyInput = dialog.querySelector("#fm-new-key");
|
|
40564
|
+
keyInput?.focus();
|
|
40565
|
+
}
|
|
40566
|
+
/**
|
|
40567
|
+
* Update field value
|
|
40568
|
+
* @param index
|
|
40569
|
+
* @param value
|
|
40570
|
+
*/
|
|
40571
|
+
#updateFieldValue(index, value) {
|
|
40572
|
+
const field = this.#fields[index];
|
|
40573
|
+
if (field) {
|
|
40574
|
+
this.#fields[index] = { ...field, value };
|
|
40575
|
+
this.#notifyUpdate();
|
|
40576
|
+
}
|
|
40577
|
+
}
|
|
40578
|
+
}
|
|
40579
|
+
function createFrontMatterEditor(options) {
|
|
40580
|
+
return new FrontMatterEditor(options);
|
|
40581
|
+
}
|
|
40582
|
+
|
|
40235
40583
|
const client = hc(location.origin);
|
|
40584
|
+
const FRONT_MATTER_SAVE_DEBOUNCE_DELAY = 500;
|
|
40585
|
+
function debounce(fn, delay) {
|
|
40586
|
+
let timeoutId = null;
|
|
40587
|
+
return (...args) => {
|
|
40588
|
+
if (timeoutId !== null) {
|
|
40589
|
+
clearTimeout(timeoutId);
|
|
40590
|
+
}
|
|
40591
|
+
timeoutId = setTimeout(() => {
|
|
40592
|
+
fn(...args);
|
|
40593
|
+
timeoutId = null;
|
|
40594
|
+
}, delay);
|
|
40595
|
+
};
|
|
40596
|
+
}
|
|
40236
40597
|
async function createEditor() {
|
|
40237
40598
|
const configRes = await client["config.json"].$get();
|
|
40238
40599
|
const config = await configRes.json();
|
|
@@ -40244,6 +40605,57 @@ async function createEditor() {
|
|
|
40244
40605
|
console.warn("Editable area not found");
|
|
40245
40606
|
return;
|
|
40246
40607
|
}
|
|
40608
|
+
const frontMatterContainer = document.querySelector(".front-matter-editor");
|
|
40609
|
+
const frontMatterInput = document.getElementById(
|
|
40610
|
+
"front-matter"
|
|
40611
|
+
);
|
|
40612
|
+
const hasFrontMatterInput = document.getElementById(
|
|
40613
|
+
"has-front-matter"
|
|
40614
|
+
);
|
|
40615
|
+
let frontMatterEditor = null;
|
|
40616
|
+
async function saveContent(content, frontMatterData, originalFrontMatter) {
|
|
40617
|
+
const res = await client.api.content.$post({
|
|
40618
|
+
json: {
|
|
40619
|
+
path: location.pathname,
|
|
40620
|
+
content,
|
|
40621
|
+
frontMatter: frontMatterData,
|
|
40622
|
+
originalFrontMatter
|
|
40623
|
+
}
|
|
40624
|
+
});
|
|
40625
|
+
const json = await res.json();
|
|
40626
|
+
if (!json.saved) {
|
|
40627
|
+
console.error(`Failed to save: ${json.path}`);
|
|
40628
|
+
return;
|
|
40629
|
+
}
|
|
40630
|
+
console.log(
|
|
40631
|
+
`Saved: ${json.path}${json.hasFrontMatter ? " (with Front Matter)" : ""}`
|
|
40632
|
+
);
|
|
40633
|
+
}
|
|
40634
|
+
const debouncedSaveFrontMatter = debounce(() => {
|
|
40635
|
+
if (!frontMatterEditor) {
|
|
40636
|
+
return;
|
|
40637
|
+
}
|
|
40638
|
+
const content = mainInput.value;
|
|
40639
|
+
const frontMatterData = frontMatterEditor.getData();
|
|
40640
|
+
const originalFrontMatter = frontMatterEditor.getOriginalFrontMatter();
|
|
40641
|
+
void saveContent(content, frontMatterData, originalFrontMatter);
|
|
40642
|
+
}, FRONT_MATTER_SAVE_DEBOUNCE_DELAY);
|
|
40643
|
+
if (frontMatterContainer && frontMatterInput) {
|
|
40644
|
+
const initialData = JSON.parse(frontMatterInput.value || "{}");
|
|
40645
|
+
const hasFrontMatter = hasFrontMatterInput?.value === "true";
|
|
40646
|
+
frontMatterEditor = createFrontMatterEditor({
|
|
40647
|
+
container: frontMatterContainer,
|
|
40648
|
+
initialData,
|
|
40649
|
+
hasFrontMatter,
|
|
40650
|
+
onUpdated: debouncedSaveFrontMatter
|
|
40651
|
+
});
|
|
40652
|
+
{
|
|
40653
|
+
console.log("Front Matter editor initialized:", {
|
|
40654
|
+
initialData,
|
|
40655
|
+
hasFrontMatter
|
|
40656
|
+
});
|
|
40657
|
+
}
|
|
40658
|
+
}
|
|
40247
40659
|
const catalog = config.enableImportBlock ? {
|
|
40248
40660
|
...config.catalog,
|
|
40249
40661
|
import: [
|
|
@@ -40293,18 +40705,9 @@ async function createEditor() {
|
|
|
40293
40705
|
return;
|
|
40294
40706
|
}
|
|
40295
40707
|
mainInput.value = content;
|
|
40296
|
-
const
|
|
40297
|
-
|
|
40298
|
-
|
|
40299
|
-
content
|
|
40300
|
-
}
|
|
40301
|
-
});
|
|
40302
|
-
const json = await res.json();
|
|
40303
|
-
if (!json.saved) {
|
|
40304
|
-
console.error(`Failed to save: ${json.path}`);
|
|
40305
|
-
return;
|
|
40306
|
-
}
|
|
40307
|
-
console.log(`Saved: ${json.path}`);
|
|
40708
|
+
const frontMatterData = frontMatterEditor?.getData();
|
|
40709
|
+
const originalFrontMatter = frontMatterEditor?.getOriginalFrontMatter();
|
|
40710
|
+
await saveContent(content, frontMatterData, originalFrontMatter);
|
|
40308
40711
|
},
|
|
40309
40712
|
fileIO: {
|
|
40310
40713
|
async getFileList(fileType, options) {
|