@burger-editor/utils 4.0.0-alpha.6 → 4.0.0-alpha.60
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 +249 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.js +2 -0
- package/dist/merge-items.d.ts +35 -0
- package/dist/merge-items.js +57 -0
- package/dist/normalize-html.d.ts +8 -0
- package/dist/normalize-html.js +109 -0
- package/dist/normalize-indent.d.ts +8 -0
- package/dist/normalize-indent.js +18 -0
- package/package.json +6 -6
package/README.md
CHANGED
|
@@ -1,3 +1,251 @@
|
|
|
1
|
-
# utils
|
|
1
|
+
# @burger-editor/utils
|
|
2
2
|
|
|
3
3
|
[](https://badge.fury.io/js/@burger-editor%2Futils)
|
|
4
|
+
|
|
5
|
+
BurgerEditorで使用する共通ユーティリティ関数集です。
|
|
6
|
+
|
|
7
|
+
## 概要
|
|
8
|
+
|
|
9
|
+
`@burger-editor/utils`は、BurgerEditorのコアパッケージや関連パッケージで共有される汎用ユーティリティ関数を提供します。文字列変換、マークダウン処理、日付フォーマット、HTML操作などの機能を含みます。
|
|
10
|
+
|
|
11
|
+
## インストール
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
npm install @burger-editor/utils
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
または
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
yarn add @burger-editor/utils
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
## 提供される関数
|
|
24
|
+
|
|
25
|
+
### 文字列ケース変換
|
|
26
|
+
|
|
27
|
+
#### `camelCase(str: string): string`
|
|
28
|
+
|
|
29
|
+
ケバブケース文字列をキャメルケースに変換します。
|
|
30
|
+
|
|
31
|
+
```typescript
|
|
32
|
+
import { camelCase } from '@burger-editor/utils';
|
|
33
|
+
|
|
34
|
+
camelCase('my-property-name'); // => 'myPropertyName'
|
|
35
|
+
camelCase('background-color'); // => 'backgroundColor'
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
#### `kebabCase(str: string): string`
|
|
39
|
+
|
|
40
|
+
キャメルケース文字列をケバブケースに変換します。
|
|
41
|
+
|
|
42
|
+
```typescript
|
|
43
|
+
import { kebabCase } from '@burger-editor/utils';
|
|
44
|
+
|
|
45
|
+
kebabCase('myPropertyName'); // => 'my-property-name'
|
|
46
|
+
kebabCase('backgroundColor'); // => 'background-color'
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
### マークダウン変換
|
|
50
|
+
|
|
51
|
+
#### `markdownToHtml(markdown: string): string`
|
|
52
|
+
|
|
53
|
+
MarkdownテキストをHTMLに変換します。
|
|
54
|
+
|
|
55
|
+
```typescript
|
|
56
|
+
import { markdownToHtml } from '@burger-editor/utils';
|
|
57
|
+
|
|
58
|
+
const html = markdownToHtml('# 見出し\n\n**太字**のテキスト');
|
|
59
|
+
// => '<h1>見出し</h1>\n<p><strong>太字</strong>のテキスト</p>'
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
#### `htmlToMarkdown(html: string): string`
|
|
63
|
+
|
|
64
|
+
HTMLをMarkdownテキストに変換します。
|
|
65
|
+
|
|
66
|
+
```typescript
|
|
67
|
+
import { htmlToMarkdown } from '@burger-editor/utils';
|
|
68
|
+
|
|
69
|
+
const markdown = htmlToMarkdown('<h1>見出し</h1><p><strong>太字</strong></p>');
|
|
70
|
+
// => '# 見出し\n\n**太字**'
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
### テキストフォーマット
|
|
74
|
+
|
|
75
|
+
#### `nl2br(text: string): string`
|
|
76
|
+
|
|
77
|
+
改行コードを`<br />`タグに変換します。
|
|
78
|
+
|
|
79
|
+
```typescript
|
|
80
|
+
import { nl2br } from '@burger-editor/utils';
|
|
81
|
+
|
|
82
|
+
nl2br('行1\n行2\r\n行3');
|
|
83
|
+
// => '行1<br />行2<br />行3'
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
#### `br2nl(html: string): string`
|
|
87
|
+
|
|
88
|
+
`<br>`タグを改行コード(`\r\n`)に変換します。
|
|
89
|
+
|
|
90
|
+
```typescript
|
|
91
|
+
import { br2nl } from '@burger-editor/utils';
|
|
92
|
+
|
|
93
|
+
br2nl('行1<br>行2<br />行3');
|
|
94
|
+
// => '行1\r\n行2\r\n行3'
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
### 数値・日付フォーマット
|
|
98
|
+
|
|
99
|
+
#### `formatByteSize(byteSize: number, digits?: number, autoFormat?: boolean): string`
|
|
100
|
+
|
|
101
|
+
バイト数を人間が読みやすい形式にフォーマットします。
|
|
102
|
+
|
|
103
|
+
```typescript
|
|
104
|
+
import { formatByteSize } from '@burger-editor/utils';
|
|
105
|
+
|
|
106
|
+
formatByteSize(1024); // => '1.00kB'
|
|
107
|
+
formatByteSize(1048576); // => '1.00MB'
|
|
108
|
+
formatByteSize(1536, 1); // => '1.5kB'
|
|
109
|
+
formatByteSize(500, 2, false); // => '500byte'
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
#### `formatDate(timestamp: number, format: string): string`
|
|
113
|
+
|
|
114
|
+
Unixタイムスタンプを指定したフォーマットの日付文字列に変換します。
|
|
115
|
+
|
|
116
|
+
```typescript
|
|
117
|
+
import { formatDate } from '@burger-editor/utils';
|
|
118
|
+
|
|
119
|
+
formatDate(1704067200, 'YYYY-MM-DD'); // => '2024-01-01'
|
|
120
|
+
formatDate(1704067200, 'YYYY年MM月DD日'); // => '2024年01月01日'
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
### URL・パス処理
|
|
124
|
+
|
|
125
|
+
#### `origin(): string`
|
|
126
|
+
|
|
127
|
+
現在のURLのオリジン(プロトコル + ホスト + ポート)を取得します。
|
|
128
|
+
|
|
129
|
+
```typescript
|
|
130
|
+
import { origin } from '@burger-editor/utils';
|
|
131
|
+
|
|
132
|
+
// ブラウザで http://localhost:3000/page にアクセスしている場合
|
|
133
|
+
origin(); // => 'http://localhost:3000'
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
#### `parseYTId(idOrUrl: string): string`
|
|
137
|
+
|
|
138
|
+
YouTube URLから動画IDを抽出します。
|
|
139
|
+
|
|
140
|
+
```typescript
|
|
141
|
+
import { parseYTId } from '@burger-editor/utils';
|
|
142
|
+
|
|
143
|
+
parseYTId('https://www.youtube.com/watch?v=dQw4w9WgXcQ');
|
|
144
|
+
// => 'dQw4w9WgXcQ'
|
|
145
|
+
|
|
146
|
+
parseYTId('https://youtu.be/dQw4w9WgXcQ');
|
|
147
|
+
// => 'dQw4w9WgXcQ'
|
|
148
|
+
|
|
149
|
+
parseYTId('dQw4w9WgXcQ');
|
|
150
|
+
// => 'dQw4w9WgXcQ'
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
#### `getBackgroundImagePath(value: string): string`
|
|
154
|
+
|
|
155
|
+
CSS `background-image` プロパティ値からパスを抽出します。
|
|
156
|
+
|
|
157
|
+
```typescript
|
|
158
|
+
import { getBackgroundImagePath } from '@burger-editor/utils';
|
|
159
|
+
|
|
160
|
+
getBackgroundImagePath('url("/images/bg.jpg")');
|
|
161
|
+
// => '/images/bg.jpg'
|
|
162
|
+
|
|
163
|
+
getBackgroundImagePath("url('https://example.com/bg.png')");
|
|
164
|
+
// => 'https://example.com/bg.png'
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
### バリデーション
|
|
168
|
+
|
|
169
|
+
#### `isValidAsClassName(className: string): boolean`
|
|
170
|
+
|
|
171
|
+
文字列が有効なCSSクラス名かどうかをチェックします。
|
|
172
|
+
|
|
173
|
+
```typescript
|
|
174
|
+
import { isValidAsClassName } from '@burger-editor/utils';
|
|
175
|
+
|
|
176
|
+
isValidAsClassName('my-class'); // => true
|
|
177
|
+
isValidAsClassName('_private'); // => true
|
|
178
|
+
isValidAsClassName('123invalid'); // => false
|
|
179
|
+
isValidAsClassName('my class'); // => false
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
### HTML操作
|
|
183
|
+
|
|
184
|
+
#### `strToDOM(html: string): HTMLElement`
|
|
185
|
+
|
|
186
|
+
HTML文字列をDOM要素に変換します。
|
|
187
|
+
|
|
188
|
+
```typescript
|
|
189
|
+
import { strToDOM } from '@burger-editor/utils';
|
|
190
|
+
|
|
191
|
+
const element = strToDOM('<div class="container">コンテンツ</div>');
|
|
192
|
+
// => HTMLDivElement
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
#### `normalizeHtml(html: string): string`
|
|
196
|
+
|
|
197
|
+
HTMLを正規化します(空白の調整など)。
|
|
198
|
+
|
|
199
|
+
```typescript
|
|
200
|
+
import { normalizeHtml } from '@burger-editor/utils';
|
|
201
|
+
|
|
202
|
+
const normalized = normalizeHtml('<div> content </div>');
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
#### `replaceCommentWithHTML(template: string, items: Record<string, string>, replacer: Function): string`
|
|
206
|
+
|
|
207
|
+
テンプレート内のコメントマーカーをHTMLに置き換えます。
|
|
208
|
+
|
|
209
|
+
```typescript
|
|
210
|
+
import { replaceCommentWithHTML } from '@burger-editor/utils';
|
|
211
|
+
|
|
212
|
+
const template = '<div><!-- item:text --></div>';
|
|
213
|
+
const items = { text: '<p>テキスト</p>' };
|
|
214
|
+
const result = replaceCommentWithHTML(template, items, (_, itemHtml) => itemHtml);
|
|
215
|
+
// => '<div><p>テキスト</p></div>'
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
### アイテム操作
|
|
219
|
+
|
|
220
|
+
#### `mergeItems(itemSeeds: Record<string, ItemSeed>, customItems?: Record<string, ItemSeed>): Record<string, ItemSeed>`
|
|
221
|
+
|
|
222
|
+
アイテム定義をマージします。
|
|
223
|
+
|
|
224
|
+
```typescript
|
|
225
|
+
import { mergeItems } from '@burger-editor/utils';
|
|
226
|
+
|
|
227
|
+
const defaultItems = { text: textItemSeed };
|
|
228
|
+
const customItems = { custom: customItemSeed };
|
|
229
|
+
const merged = mergeItems(defaultItems, customItems);
|
|
230
|
+
// => { text: textItemSeed, custom: customItemSeed }
|
|
231
|
+
```
|
|
232
|
+
|
|
233
|
+
## 依存関係
|
|
234
|
+
|
|
235
|
+
- **dayjs** - 日付フォーマット処理
|
|
236
|
+
- **marked** - Markdown to HTML変換
|
|
237
|
+
- **turndown** - HTML to Markdown変換
|
|
238
|
+
|
|
239
|
+
## 使用パッケージ
|
|
240
|
+
|
|
241
|
+
このパッケージは以下のBurgerEditorパッケージで使用されています:
|
|
242
|
+
|
|
243
|
+
- [@burger-editor/core](../core/)
|
|
244
|
+
- [@burger-editor/blocks](../blocks/)
|
|
245
|
+
- [@burger-editor/frozen-patty](../frozen-patty/)
|
|
246
|
+
- [@burger-editor/migrator](../migrator/)
|
|
247
|
+
- [@burger-editor/mcp-server](../mcp-server/)
|
|
248
|
+
|
|
249
|
+
## ライセンス
|
|
250
|
+
|
|
251
|
+
Dual Licensed under MIT OR Apache-2.0
|
package/dist/index.d.ts
CHANGED
package/dist/index.js
CHANGED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* マージ操作が可能な汎用型
|
|
3
|
+
* 任意のオブジェクト型にdeleteプロパティを追加する
|
|
4
|
+
*/
|
|
5
|
+
export type Mergeable<T extends Record<string, unknown>> = T & {
|
|
6
|
+
readonly delete?: boolean;
|
|
7
|
+
};
|
|
8
|
+
/**
|
|
9
|
+
* 汎用的なマージ関数
|
|
10
|
+
* 指定されたキーフィールドを基準にアイテムのマージを行う
|
|
11
|
+
* @param defaultItems デフォルトのアイテム配列
|
|
12
|
+
* @param mergeItems マージ設定配列
|
|
13
|
+
* @param keyField キーとなるフィールド名
|
|
14
|
+
* @param isValidForAdd 新規追加時のバリデーション関数
|
|
15
|
+
* @returns マージ後のアイテム配列
|
|
16
|
+
* @example
|
|
17
|
+
* ```typescript
|
|
18
|
+
* // SelectableValue型での使用例
|
|
19
|
+
* const result = mergeItems(
|
|
20
|
+
* [{ value: 'a', label: 'A' }],
|
|
21
|
+
* [{ value: 'b', label: 'B' }],
|
|
22
|
+
* 'value',
|
|
23
|
+
* (item) => Boolean(item.label)
|
|
24
|
+
* );
|
|
25
|
+
*
|
|
26
|
+
* // 独自型での使用例
|
|
27
|
+
* interface User { id: string; name: string; }
|
|
28
|
+
* const users = mergeItems(
|
|
29
|
+
* [{ id: '1', name: 'Alice' }],
|
|
30
|
+
* [{ id: '2', name: 'Bob' }],
|
|
31
|
+
* 'id'
|
|
32
|
+
* );
|
|
33
|
+
* ```
|
|
34
|
+
*/
|
|
35
|
+
export declare function mergeItems<T extends Record<string, unknown>, K extends keyof T & string>(defaultItems: readonly T[], mergeItems: readonly Mergeable<Partial<T> & Pick<T, K>>[] | undefined, keyField: K, isValidForAdd?: (item: Mergeable<Partial<T> & Pick<T, K>>) => boolean): T[];
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 汎用的なマージ関数
|
|
3
|
+
* 指定されたキーフィールドを基準にアイテムのマージを行う
|
|
4
|
+
* @param defaultItems デフォルトのアイテム配列
|
|
5
|
+
* @param mergeItems マージ設定配列
|
|
6
|
+
* @param keyField キーとなるフィールド名
|
|
7
|
+
* @param isValidForAdd 新規追加時のバリデーション関数
|
|
8
|
+
* @returns マージ後のアイテム配列
|
|
9
|
+
* @example
|
|
10
|
+
* ```typescript
|
|
11
|
+
* // SelectableValue型での使用例
|
|
12
|
+
* const result = mergeItems(
|
|
13
|
+
* [{ value: 'a', label: 'A' }],
|
|
14
|
+
* [{ value: 'b', label: 'B' }],
|
|
15
|
+
* 'value',
|
|
16
|
+
* (item) => Boolean(item.label)
|
|
17
|
+
* );
|
|
18
|
+
*
|
|
19
|
+
* // 独自型での使用例
|
|
20
|
+
* interface User { id: string; name: string; }
|
|
21
|
+
* const users = mergeItems(
|
|
22
|
+
* [{ id: '1', name: 'Alice' }],
|
|
23
|
+
* [{ id: '2', name: 'Bob' }],
|
|
24
|
+
* 'id'
|
|
25
|
+
* );
|
|
26
|
+
* ```
|
|
27
|
+
*/
|
|
28
|
+
export function mergeItems(defaultItems, mergeItems, keyField, isValidForAdd) {
|
|
29
|
+
// マージ設定がない場合はデフォルトをそのまま返す
|
|
30
|
+
if (!mergeItems) {
|
|
31
|
+
return [...defaultItems];
|
|
32
|
+
}
|
|
33
|
+
let result = [...defaultItems];
|
|
34
|
+
for (const mergeItem of mergeItems) {
|
|
35
|
+
if (mergeItem.delete) {
|
|
36
|
+
// 削除: キーが一致するものを除去
|
|
37
|
+
const keyValue = mergeItem[keyField];
|
|
38
|
+
result = result.filter((item) => item[keyField] !== keyValue);
|
|
39
|
+
}
|
|
40
|
+
else {
|
|
41
|
+
// 追加または更新
|
|
42
|
+
const keyValue = mergeItem[keyField];
|
|
43
|
+
const existingIndex = result.findIndex((item) => item[keyField] === keyValue);
|
|
44
|
+
if (existingIndex === -1) {
|
|
45
|
+
// 新規追加: バリデーション通過が必要
|
|
46
|
+
if (!isValidForAdd || isValidForAdd(mergeItem)) {
|
|
47
|
+
result.push(mergeItem);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
else {
|
|
51
|
+
// 既存の更新: マージする
|
|
52
|
+
result[existingIndex] = { ...result[existingIndex], ...mergeItem };
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
return result;
|
|
57
|
+
}
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* HTMLの構造を正規化して比較する
|
|
3
|
+
* 空白や属性順序の違いを無視し、タグ構造のみを比較する
|
|
4
|
+
* @param html1 比較対象のHTML文字列1
|
|
5
|
+
* @param html2 比較対象のHTML文字列2
|
|
6
|
+
* @returns 構造が同じ場合true、異なる場合false
|
|
7
|
+
*/
|
|
8
|
+
export function normalizeHtmlStructure(html1, html2) {
|
|
9
|
+
const doc1 = new Range().createContextualFragment(html1);
|
|
10
|
+
const doc2 = new Range().createContextualFragment(html2);
|
|
11
|
+
return compareElements(doc1, doc2);
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* 2つの要素の構造を比較する
|
|
15
|
+
* @param el1 比較対象の要素1
|
|
16
|
+
* @param el2 比較対象の要素2
|
|
17
|
+
* @returns 構造が同じ場合true、異なる場合false
|
|
18
|
+
*/
|
|
19
|
+
function compareElements(el1, el2) {
|
|
20
|
+
// 子要素の数を取得(テキストノードは無視)
|
|
21
|
+
const children1 = [...el1.children];
|
|
22
|
+
const children2 = [...el2.children];
|
|
23
|
+
// 子要素の数が異なる場合は構造が異なる
|
|
24
|
+
if (children1.length !== children2.length) {
|
|
25
|
+
return false;
|
|
26
|
+
}
|
|
27
|
+
// 子要素がない場合(テキストのみまたは空要素)
|
|
28
|
+
if (children1.length === 0) {
|
|
29
|
+
// テキスト内容を正規化して比較
|
|
30
|
+
const text1 = normalizeText(el1.textContent ?? '');
|
|
31
|
+
const text2 = normalizeText(el2.textContent ?? '');
|
|
32
|
+
return text1 === text2;
|
|
33
|
+
}
|
|
34
|
+
// 各子要素を比較
|
|
35
|
+
for (const [i, child1] of children1.entries()) {
|
|
36
|
+
const child2 = children2[i];
|
|
37
|
+
if (!child1 || !child2 || !compareElementStructure(child1, child2)) {
|
|
38
|
+
return false;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
return true;
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* 2つの要素の構造を詳細に比較する
|
|
45
|
+
* @param el1 比較対象の要素1
|
|
46
|
+
* @param el2 比較対象の要素2
|
|
47
|
+
* @returns 構造が同じ場合true、異なる場合false
|
|
48
|
+
*/
|
|
49
|
+
function compareElementStructure(el1, el2) {
|
|
50
|
+
// タグ名が異なる場合は構造が異なる
|
|
51
|
+
if (el1.tagName !== el2.tagName) {
|
|
52
|
+
return false;
|
|
53
|
+
}
|
|
54
|
+
// 属性を比較(順序は無視)
|
|
55
|
+
const attrs1 = getNormalizedAttributes(el1);
|
|
56
|
+
const attrs2 = getNormalizedAttributes(el2);
|
|
57
|
+
if (!compareAttributes(attrs1, attrs2)) {
|
|
58
|
+
return false;
|
|
59
|
+
}
|
|
60
|
+
// 子要素を再帰的に比較
|
|
61
|
+
return compareElements(el1, el2);
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* 要素の属性を正規化して取得する
|
|
65
|
+
* @param el 対象の要素
|
|
66
|
+
* @returns 属性名をキー、属性値を値とするオブジェクト
|
|
67
|
+
*/
|
|
68
|
+
function getNormalizedAttributes(el) {
|
|
69
|
+
const attrs = {};
|
|
70
|
+
for (const attr of el.attributes) {
|
|
71
|
+
attrs[attr.name] = attr.value;
|
|
72
|
+
}
|
|
73
|
+
return attrs;
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* 2つの属性オブジェクトを比較する
|
|
77
|
+
* @param attrs1 比較対象の属性1
|
|
78
|
+
* @param attrs2 比較対象の属性2
|
|
79
|
+
* @returns 属性が同じ場合true、異なる場合false
|
|
80
|
+
*/
|
|
81
|
+
function compareAttributes(attrs1, attrs2) {
|
|
82
|
+
const keys1 = Object.keys(attrs1).toSorted();
|
|
83
|
+
const keys2 = Object.keys(attrs2).toSorted();
|
|
84
|
+
// 属性名の数が異なる場合は異なる
|
|
85
|
+
if (keys1.length !== keys2.length) {
|
|
86
|
+
return false;
|
|
87
|
+
}
|
|
88
|
+
// 各属性を比較
|
|
89
|
+
for (const [i, key1] of keys1.entries()) {
|
|
90
|
+
const key2 = keys2[i];
|
|
91
|
+
// 属性名が異なる場合は異なる
|
|
92
|
+
if (key1 !== key2) {
|
|
93
|
+
return false;
|
|
94
|
+
}
|
|
95
|
+
// 属性値が異なる場合は異なる
|
|
96
|
+
if (attrs1[key1] !== attrs2[key2]) {
|
|
97
|
+
return false;
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
return true;
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* テキストを正規化する(空白文字を統一)
|
|
104
|
+
* @param text 対象のテキスト
|
|
105
|
+
* @returns 正規化されたテキスト
|
|
106
|
+
*/
|
|
107
|
+
function normalizeText(text) {
|
|
108
|
+
return text.replaceAll(/\s+/g, ' ').trim();
|
|
109
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* HTMLの先頭行のインデントを検出し、全行から同じインデントを削除する
|
|
3
|
+
*
|
|
4
|
+
* CRLF (\r\n) と LF (\n) の両方に対応
|
|
5
|
+
* @param html - 正規化するHTML文字列
|
|
6
|
+
* @returns インデントが正規化され、前後の空白行が削除されたHTML文字列
|
|
7
|
+
*/
|
|
8
|
+
export function normalizeIndent(html) {
|
|
9
|
+
// 先頭の改行を削除
|
|
10
|
+
const trimmedHtml = html.replace(/^(?:\r?\n)+/, '');
|
|
11
|
+
const lines = trimmedHtml.split(/\r?\n/);
|
|
12
|
+
const firstLineIndent = lines[0]?.match(/^\s+/)?.[0] ?? '';
|
|
13
|
+
if (firstLineIndent.length === 0) {
|
|
14
|
+
return trimmedHtml;
|
|
15
|
+
}
|
|
16
|
+
const normalizedLines = lines.map((line) => line.replace(firstLineIndent, ''));
|
|
17
|
+
return normalizedLines.join('\n');
|
|
18
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@burger-editor/utils",
|
|
3
|
-
"version": "4.0.0-alpha.
|
|
3
|
+
"version": "4.0.0-alpha.60",
|
|
4
4
|
"description": "BurgerEditor Editor Utility Functions",
|
|
5
5
|
"author": "D-ZERO",
|
|
6
6
|
"license": "(MIT OR Apache-2.0)",
|
|
@@ -26,12 +26,12 @@
|
|
|
26
26
|
"clean": "tsc --build --clean"
|
|
27
27
|
},
|
|
28
28
|
"dependencies": {
|
|
29
|
-
"dayjs": "1.11.
|
|
30
|
-
"marked": "
|
|
31
|
-
"turndown": "7.2.
|
|
29
|
+
"dayjs": "1.11.19",
|
|
30
|
+
"marked": "17.0.2",
|
|
31
|
+
"turndown": "7.2.2"
|
|
32
32
|
},
|
|
33
33
|
"devDependencies": {
|
|
34
|
-
"@types/turndown": "5.0.
|
|
34
|
+
"@types/turndown": "5.0.6"
|
|
35
35
|
},
|
|
36
|
-
"gitHead": "
|
|
36
|
+
"gitHead": "edd68ff1a6fc82da87aec4c0b1150085dfdb80a4"
|
|
37
37
|
}
|