@growgroup/visual-editor 0.2.1 → 0.2.2
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/package.json +1 -1
- package/src/editor/parts.ts +38 -12
package/package.json
CHANGED
package/src/editor/parts.ts
CHANGED
|
@@ -148,15 +148,21 @@ export function stripEditorAttrs(root: Element): void {
|
|
|
148
148
|
/** スロットを推定して付ける対象(文字や画像を持ちうる要素) */
|
|
149
149
|
const SLOT_TAGS: Record<string, string> = {
|
|
150
150
|
h1: 'heading', h2: 'heading', h3: 'heading', h4: 'heading', h5: 'heading', h6: 'heading',
|
|
151
|
-
p: 'body',
|
|
152
|
-
figcaption: 'caption', blockquote: 'quote',
|
|
153
|
-
td: 'cell', th: 'cell', label: 'label', span: 'text', time: 'time', small: 'note',
|
|
151
|
+
p: 'body', a: 'link', button: 'button', img: 'image',
|
|
152
|
+
figcaption: 'caption', blockquote: 'quote', label: 'label', span: 'text', time: 'time', small: 'note',
|
|
154
153
|
};
|
|
155
154
|
|
|
155
|
+
/** タグだけの骨格(「同じ形の兄弟」の判定用。文字と属性は見ない) */
|
|
156
|
+
const tagSkeleton = (el: Element): string =>
|
|
157
|
+
`${el.tagName.toLowerCase()}${el.children.length ? `(${Array.from(el.children).map(tagSkeleton).join(',')})` : ''}`;
|
|
158
|
+
|
|
156
159
|
/**
|
|
157
|
-
* スロットの推定。既に `data-slot`
|
|
158
|
-
*
|
|
159
|
-
*
|
|
160
|
+
* スロットの推定。既に `data-slot` があるものはそのまま。祖先が既にスロットなら付けない(入れ子のスロットを作らない)。
|
|
161
|
+
*
|
|
162
|
+
* 1. 繰り返し(ul/ol・dl・table、同じ形の子が 2 つ以上並ぶ器)は**器ごと 1 つのスロット**(list / items)。
|
|
163
|
+
* カードが 3 枚か 4 枚かはページの内容であって部品の構造ではないので、項目ごとにスロットを切らない
|
|
164
|
+
* (切ると、定義より多い項目が sync で捨てられる)。ルート自身が繰り返しの器ならルートがスロットになる
|
|
165
|
+
* 2. 残りはタグで(見出し→heading、段落→body、リンク→link …)。span は文字だけを直接持つときだけ。svg の中は見ない
|
|
160
166
|
* 戻り値は付けた数。
|
|
161
167
|
*/
|
|
162
168
|
export function inferSlots(root: Element): number {
|
|
@@ -172,12 +178,32 @@ export function inferSlots(root: Element): number {
|
|
|
172
178
|
const base = v.replace(/-\d+$/, '');
|
|
173
179
|
if (base) used.set(base, Math.max(used.get(base) ?? 0, 1));
|
|
174
180
|
}
|
|
175
|
-
const
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
+
const inSlot = (el: Element) => {
|
|
182
|
+
const a = el.parentElement?.closest(`[${SLOT_ATTR}]`);
|
|
183
|
+
return !!(a && root.contains(a));
|
|
184
|
+
};
|
|
185
|
+
const skip = (el: Element) => !!el.closest('svg') || el.hasAttribute(SLOT_ATTR) || inSlot(el);
|
|
186
|
+
const isRepeatContainer = (el: Element) => {
|
|
187
|
+
const kids = Array.from(el.children);
|
|
188
|
+
const tag = el.tagName.toLowerCase();
|
|
189
|
+
if ((tag === 'ul' || tag === 'ol') && kids.some((k) => k.tagName.toLowerCase() === 'li')) return 'list';
|
|
190
|
+
if (tag === 'dl' || tag === 'table') return 'list';
|
|
191
|
+
if (kids.length >= 2 && kids.every((k) => tagSkeleton(k) === tagSkeleton(kids[0]))) return 'items';
|
|
192
|
+
return null;
|
|
193
|
+
};
|
|
194
|
+
// 1. 繰り返しの器(ルート自身も含む。文書順なので外側が先に決まり、中の器は祖先がスロットになって飛ばされる)
|
|
195
|
+
for (const el of [root, ...Array.from(root.querySelectorAll('*'))]) {
|
|
196
|
+
if (el !== root && skip(el)) continue;
|
|
197
|
+
if (el === root && el.hasAttribute(SLOT_ATTR)) continue;
|
|
198
|
+
const kind = isRepeatContainer(el);
|
|
199
|
+
if (!kind) continue;
|
|
200
|
+
el.setAttribute(SLOT_ATTR, nameFor(kind));
|
|
201
|
+
count++;
|
|
202
|
+
}
|
|
203
|
+
if (root.hasAttribute(SLOT_ATTR)) return count;
|
|
204
|
+
// 2. 残りはタグで
|
|
205
|
+
for (const el of Array.from(root.querySelectorAll('*'))) {
|
|
206
|
+
if (skip(el)) continue;
|
|
181
207
|
const tag = el.tagName.toLowerCase();
|
|
182
208
|
const base = SLOT_TAGS[tag];
|
|
183
209
|
if (!base) continue;
|