@growgroup/visual-editor 0.3.1 → 0.4.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 +6 -1
- package/dist/editor.css +1 -1
- package/package.json +1 -1
- package/src/editor/EditorContext.tsx +3 -2
- package/src/editor/EditorToolbar.tsx +8 -2
- package/src/editor/FrontendVisualEditor.tsx +114 -44
- package/src/editor/components/EditorCanvas.tsx +73 -15
- package/src/editor/components/EditorFooter.tsx +2 -0
- package/src/editor/components/EditorHeader.tsx +3 -2
- package/src/editor/components/FigmaColorPicker.tsx +2 -1
- package/src/editor/components/JsEditorDialog.tsx +1 -0
- package/src/editor/components/PageSettingsDialog.tsx +1 -0
- package/src/editor/components/comments/CommentBoard.tsx +9 -6
- package/src/editor/components/multi-page/CanvasFrameDecor.tsx +172 -0
- package/src/editor/components/multi-page/MultiPageCanvasView.tsx +173 -77
- package/src/editor/components/multi-page/PageFramePreview.tsx +96 -20
- package/src/editor/components/multi-page/index.ts +1 -0
- package/src/editor/components/ppt/PptChrome.tsx +6 -2
- package/src/editor/components/ppt/PptComments.tsx +411 -71
- package/src/editor/constants.ts +16 -1
- package/src/editor/contexts/EditorArtboardContext.tsx +8 -0
- package/src/editor/contexts/EditorComponentsContext.tsx +31 -27
- package/src/editor/contexts/EditorUIStateContext.tsx +4 -3
- package/src/editor/contexts/EditorVariablesContext.tsx +5 -4
- package/src/editor/contexts/MultiPageCanvasContext.tsx +26 -2
- package/src/editor/hooks/useAiReplace.ts +4 -3
- package/src/editor/hooks/useBrowserZoomPrevention.ts +2 -1
- package/src/editor/hooks/useComponentEditMode.ts +27 -26
- package/src/editor/hooks/useEditorMessages.ts +3 -2
- package/src/editor/hooks/useElementActions.ts +27 -26
- package/src/editor/hooks/useElementSelection.ts +19 -16
- package/src/editor/hooks/useFocusManagement.ts +7 -6
- package/src/editor/hooks/useGoogleFonts.ts +3 -2
- package/src/editor/hooks/useIframeInitializer.ts +6 -3
- package/src/editor/hooks/useIframeSetup.ts +4 -3
- package/src/editor/hooks/useImageUpload.ts +4 -3
- package/src/editor/hooks/useMarqueeSelection.ts +2 -1
- package/src/editor/hooks/usePageSettingsManager.ts +24 -23
- package/src/editor/hooks/useRichPaste.ts +11 -10
- package/src/editor/parts.ts +7 -25
- package/src/editor/utils/component-renderer.ts +8 -7
- package/src/editor/utils/component-sync.ts +16 -15
- package/src/editor/utils/debug.ts +52 -0
- package/src/editor/utils/dom-utils.ts +11 -10
- package/src/editor/utils/drop-target.ts +135 -0
- package/src/editor/utils/eyedropper.ts +3 -1
- package/src/editor/utils/figma-export.ts +6 -5
- package/src/editor/utils/figma-kiwi-decoder.ts +28 -27
- package/src/editor/utils/figma-kiwi-encoder.ts +9 -8
- package/src/editor/utils/figma-paste.ts +57 -56
- package/src/editor/utils/flex-utils.ts +12 -10
- package/src/editor/utils/override-apply.ts +3 -2
- package/src/editor/utils/override-detection.ts +11 -10
- package/src/editor/utils/tailwind-utils.ts +4 -3
- package/src/io.ts +22 -1
- package/src/lib/firebase/css-variables.ts +4 -3
- package/src/lib/firebase/storage.ts +3 -2
- package/src/styles/skin.css +9 -4
- package/src/types/editor.ts +6 -2
- package/src/version.ts +1 -1
|
@@ -6,6 +6,7 @@ import type { PageSettings, ProjectPageSettings } from '../types/page-settings';
|
|
|
6
6
|
import type { HtmlImportResult } from '../components';
|
|
7
7
|
import { buildDomTree, getArtboardContent } from '../utils/dom-utils';
|
|
8
8
|
import { makeChildrenEditable } from '../utils/html-utils';
|
|
9
|
+
import { debugLog } from '../utils/debug';
|
|
9
10
|
|
|
10
11
|
interface UsePageSettingsManagerProps {
|
|
11
12
|
parentId?: string;
|
|
@@ -116,12 +117,12 @@ export function usePageSettingsManager({
|
|
|
116
117
|
|
|
117
118
|
// CSSがStorageにある場合
|
|
118
119
|
if (pageData.settings.customCssUrl) {
|
|
119
|
-
|
|
120
|
+
debugLog('[loadPageSettings] Fetching CSS from Storage:', pageData.settings.customCssUrl);
|
|
120
121
|
try {
|
|
121
122
|
const response = await fetch(pageData.settings.customCssUrl);
|
|
122
123
|
if (response.ok) {
|
|
123
124
|
customCss = await response.text();
|
|
124
|
-
|
|
125
|
+
debugLog(`[loadPageSettings] CSS fetched: ${customCss.length} chars`);
|
|
125
126
|
}
|
|
126
127
|
} catch (e) {
|
|
127
128
|
console.error('[loadPageSettings] Failed to fetch CSS from Storage:', e);
|
|
@@ -130,12 +131,12 @@ export function usePageSettingsManager({
|
|
|
130
131
|
|
|
131
132
|
// JSがStorageにある場合
|
|
132
133
|
if (pageData.settings.customJsUrl) {
|
|
133
|
-
|
|
134
|
+
debugLog('[loadPageSettings] Fetching JS from Storage:', pageData.settings.customJsUrl);
|
|
134
135
|
try {
|
|
135
136
|
const response = await fetch(pageData.settings.customJsUrl);
|
|
136
137
|
if (response.ok) {
|
|
137
138
|
customJs = await response.text();
|
|
138
|
-
|
|
139
|
+
debugLog(`[loadPageSettings] JS fetched: ${customJs.length} chars`);
|
|
139
140
|
}
|
|
140
141
|
} catch (e) {
|
|
141
142
|
console.error('[loadPageSettings] Failed to fetch JS from Storage:', e);
|
|
@@ -177,7 +178,7 @@ export function usePageSettingsManager({
|
|
|
177
178
|
setImportedJs(customJs);
|
|
178
179
|
}
|
|
179
180
|
|
|
180
|
-
|
|
181
|
+
debugLog('[loadPageSettings] Loaded settings:', {
|
|
181
182
|
...loadedSettings,
|
|
182
183
|
customCss: customCss.length + ' chars',
|
|
183
184
|
customJs: customJs.length + ' chars',
|
|
@@ -219,7 +220,7 @@ export function usePageSettingsManager({
|
|
|
219
220
|
}
|
|
220
221
|
if (styleElement.textContent !== importedCss) {
|
|
221
222
|
styleElement.textContent = importedCss;
|
|
222
|
-
|
|
223
|
+
debugLog('[applyStylesToIframe] Applied CSS:', importedCss.length, 'chars');
|
|
223
224
|
}
|
|
224
225
|
}
|
|
225
226
|
|
|
@@ -231,7 +232,7 @@ export function usePageSettingsManager({
|
|
|
231
232
|
const safeJs = `;try{${importedJs}}catch(e){console.error('[imported-scripts] Error:',e);}`;
|
|
232
233
|
// 既にスクリプトが存在し、同じ内容であればスキップ
|
|
233
234
|
if (existingScript?.textContent === safeJs) {
|
|
234
|
-
|
|
235
|
+
debugLog('[applyStylesToIframe] JS already applied, skipping');
|
|
235
236
|
return;
|
|
236
237
|
}
|
|
237
238
|
// 既存のスクリプトを削除
|
|
@@ -243,7 +244,7 @@ export function usePageSettingsManager({
|
|
|
243
244
|
scriptElement.id = 'imported-scripts';
|
|
244
245
|
scriptElement.textContent = safeJs;
|
|
245
246
|
iframeDoc.body.appendChild(scriptElement);
|
|
246
|
-
|
|
247
|
+
debugLog('[applyStylesToIframe] Applied JS:', importedJs.length, 'chars');
|
|
247
248
|
}
|
|
248
249
|
};
|
|
249
250
|
|
|
@@ -275,11 +276,11 @@ export function usePageSettingsManager({
|
|
|
275
276
|
iframeDoc.head.appendChild(styleElement);
|
|
276
277
|
}
|
|
277
278
|
styleElement.textContent = css;
|
|
278
|
-
|
|
279
|
+
debugLog('[handleSaveCss] Updated CSS:', css.length, 'chars');
|
|
279
280
|
} else if (styleElement) {
|
|
280
281
|
// CSSが空の場合はスタイル要素を削除
|
|
281
282
|
styleElement.remove();
|
|
282
|
-
|
|
283
|
+
debugLog('[handleSaveCss] Removed empty CSS');
|
|
283
284
|
}
|
|
284
285
|
|
|
285
286
|
// 状態を更新
|
|
@@ -298,7 +299,7 @@ export function usePageSettingsManager({
|
|
|
298
299
|
const styleElement = iframeDoc.getElementById('imported-styles');
|
|
299
300
|
if (styleElement) {
|
|
300
301
|
styleElement.remove();
|
|
301
|
-
|
|
302
|
+
debugLog('[handleClearCss] Cleared imported CSS');
|
|
302
303
|
}
|
|
303
304
|
}
|
|
304
305
|
setImportedCss('');
|
|
@@ -338,11 +339,11 @@ export function usePageSettingsManager({
|
|
|
338
339
|
const safeJs = `;try{${js}}catch(e){console.error('[imported-scripts] Error:',e);}`;
|
|
339
340
|
newScriptElement.textContent = safeJs;
|
|
340
341
|
iframeDoc.body.appendChild(newScriptElement);
|
|
341
|
-
|
|
342
|
+
debugLog('[handleSaveJs] Updated JS:', js.length, 'chars');
|
|
342
343
|
} else if (scriptElement) {
|
|
343
344
|
// JSが空の場合はスクリプト要素を削除
|
|
344
345
|
scriptElement.remove();
|
|
345
|
-
|
|
346
|
+
debugLog('[handleSaveJs] Removed empty JS');
|
|
346
347
|
}
|
|
347
348
|
|
|
348
349
|
// 状態を更新
|
|
@@ -361,7 +362,7 @@ export function usePageSettingsManager({
|
|
|
361
362
|
const scriptElement = iframeDoc.getElementById('imported-scripts');
|
|
362
363
|
if (scriptElement) {
|
|
363
364
|
scriptElement.remove();
|
|
364
|
-
|
|
365
|
+
debugLog('[handleClearJs] Cleared imported JS');
|
|
365
366
|
}
|
|
366
367
|
}
|
|
367
368
|
setImportedJs('');
|
|
@@ -461,13 +462,13 @@ export function usePageSettingsManager({
|
|
|
461
462
|
if (settings.customJs) firestoreSettings.customJs = settings.customJs;
|
|
462
463
|
|
|
463
464
|
await api.patch(apiPath, { settings: firestoreSettings });
|
|
464
|
-
|
|
465
|
+
debugLog('[handleSavePageSettings] Saved settings to Firestore:', firestoreSettings);
|
|
465
466
|
} catch (error) {
|
|
466
467
|
console.error('[handleSavePageSettings] Failed to save settings to Firestore:', error);
|
|
467
468
|
}
|
|
468
469
|
}
|
|
469
470
|
|
|
470
|
-
|
|
471
|
+
debugLog('[handleSavePageSettings] Saved settings:', settings);
|
|
471
472
|
}, [getIframeDoc, notifyIframeChange, parentId, contentId, editorMode, getIdToken]);
|
|
472
473
|
|
|
473
474
|
// 現在のページ設定を保存(メイン保存ボタンから呼ばれる)
|
|
@@ -492,7 +493,7 @@ export function usePageSettingsManager({
|
|
|
492
493
|
|
|
493
494
|
// CSS/JSが空の場合は保存不要
|
|
494
495
|
if (!currentCss && !currentJs && !pageSettings.title && !pageSettings.description) {
|
|
495
|
-
|
|
496
|
+
debugLog('[handleSaveCurrentSettings] No settings to save');
|
|
496
497
|
return;
|
|
497
498
|
}
|
|
498
499
|
|
|
@@ -525,7 +526,7 @@ export function usePageSettingsManager({
|
|
|
525
526
|
if (currentJs) firestoreSettings.customJs = currentJs;
|
|
526
527
|
|
|
527
528
|
await api.patch(apiPath, { settings: firestoreSettings });
|
|
528
|
-
|
|
529
|
+
debugLog('[handleSaveCurrentSettings] Saved settings to Firestore:', {
|
|
529
530
|
customCss: currentCss.length + ' chars',
|
|
530
531
|
customJs: currentJs.length + ' chars',
|
|
531
532
|
});
|
|
@@ -581,7 +582,7 @@ export function usePageSettingsManager({
|
|
|
581
582
|
document.body.removeChild(a);
|
|
582
583
|
URL.revokeObjectURL(url);
|
|
583
584
|
|
|
584
|
-
|
|
585
|
+
debugLog('[handleExport] Downloaded:', filename, 'format:', format);
|
|
585
586
|
} catch (error) {
|
|
586
587
|
console.error('[handleExport] Export failed:', error);
|
|
587
588
|
}
|
|
@@ -643,7 +644,7 @@ export function usePageSettingsManager({
|
|
|
643
644
|
styleElement.id = 'imported-styles';
|
|
644
645
|
styleElement.textContent = result.css;
|
|
645
646
|
iframeDoc.head.appendChild(styleElement);
|
|
646
|
-
|
|
647
|
+
debugLog('[handleHtmlImport] Injected CSS:', result.css.length, 'chars');
|
|
647
648
|
|
|
648
649
|
// CSS状態を保存(編集用)
|
|
649
650
|
setImportedCss(result.css);
|
|
@@ -667,7 +668,7 @@ export function usePageSettingsManager({
|
|
|
667
668
|
const safeJs = `;try{${result.js}}catch(e){console.error('[imported-scripts] Error:',e);}`;
|
|
668
669
|
scriptElement.textContent = safeJs;
|
|
669
670
|
iframeDoc.body.appendChild(scriptElement);
|
|
670
|
-
|
|
671
|
+
debugLog('[handleHtmlImport] Injected JS:', result.js.length, 'chars');
|
|
671
672
|
|
|
672
673
|
// JS状態を保存(編集用)- 元のJSを保存
|
|
673
674
|
setImportedJs(result.js);
|
|
@@ -677,11 +678,11 @@ export function usePageSettingsManager({
|
|
|
677
678
|
|
|
678
679
|
// インポートされた要素に data-editable と data-element-id を付与
|
|
679
680
|
const count = makeChildrenEditable(artboard);
|
|
680
|
-
|
|
681
|
+
debugLog('[handleHtmlImport] Made', count, 'elements editable');
|
|
681
682
|
|
|
682
683
|
// リソース情報をログ
|
|
683
684
|
if (result.resources) {
|
|
684
|
-
|
|
685
|
+
debugLog('[handleHtmlImport] Resources:', {
|
|
685
686
|
images: result.resources.images?.length || 0,
|
|
686
687
|
stylesheets: result.resources.stylesheets?.length || 0,
|
|
687
688
|
scripts: result.resources.scripts?.length || 0,
|
|
@@ -18,6 +18,7 @@ import {
|
|
|
18
18
|
PasteProcessResult,
|
|
19
19
|
} from '../utils/paste-processors';
|
|
20
20
|
import { fetchAndApplyFigmaImages } from '../utils/figma-paste';
|
|
21
|
+
import { debugLog } from '../utils/debug';
|
|
21
22
|
|
|
22
23
|
/**
|
|
23
24
|
* Result of a rich paste operation
|
|
@@ -95,7 +96,7 @@ export function useRichPaste() {
|
|
|
95
96
|
|
|
96
97
|
// Detect content type (or use forced type)
|
|
97
98
|
const contentType = forceContentType || detectContentType(clipboardData);
|
|
98
|
-
|
|
99
|
+
debugLog('[useRichPaste] Detected content type:', contentType, forceContentType ? '(forced)' : '');
|
|
99
100
|
|
|
100
101
|
// Skip images (handled by useImageUpload)
|
|
101
102
|
if (contentType === 'image') {
|
|
@@ -176,7 +177,7 @@ export function useRichPaste() {
|
|
|
176
177
|
}
|
|
177
178
|
}
|
|
178
179
|
|
|
179
|
-
|
|
180
|
+
debugLog('[useRichPaste] Successfully pasted', pastedIds.length, 'elements');
|
|
180
181
|
|
|
181
182
|
// For Figma content, fetch and apply images asynchronously
|
|
182
183
|
let figmaImagesApplied: number | undefined;
|
|
@@ -190,7 +191,7 @@ export function useRichPaste() {
|
|
|
190
191
|
result.figmaFileKey &&
|
|
191
192
|
(hasImageHashes || hasImageNodeIds)
|
|
192
193
|
) {
|
|
193
|
-
|
|
194
|
+
debugLog('[useRichPaste] Fetching Figma images...',
|
|
194
195
|
hasImageHashes ? `${result.figmaImageHashes!.length} hashes` : 'no hashes',
|
|
195
196
|
hasImageNodeIds ? `${result.figmaImageNodeIds!.length} nodeIds` : 'no nodeIds',
|
|
196
197
|
);
|
|
@@ -211,7 +212,7 @@ export function useRichPaste() {
|
|
|
211
212
|
if (imageResult.applied > 0) {
|
|
212
213
|
// Notify change again since images were applied
|
|
213
214
|
notifyIframeChange();
|
|
214
|
-
|
|
215
|
+
debugLog(`[useRichPaste] Applied ${imageResult.applied} Figma images`);
|
|
215
216
|
}
|
|
216
217
|
}
|
|
217
218
|
|
|
@@ -228,21 +229,21 @@ export function useRichPaste() {
|
|
|
228
229
|
* Debug helper: Log clipboard contents
|
|
229
230
|
*/
|
|
230
231
|
const debugClipboard = useCallback((clipboardData: DataTransfer): void => {
|
|
231
|
-
|
|
232
|
-
|
|
232
|
+
debugLog('[useRichPaste] === Clipboard Debug ===');
|
|
233
|
+
debugLog('Available types:', Array.from(clipboardData.types));
|
|
233
234
|
|
|
234
235
|
clipboardData.types.forEach((type) => {
|
|
235
236
|
const data = clipboardData.getData(type);
|
|
236
|
-
|
|
237
|
+
debugLog(`[${type}]:`, data.substring(0, 500) + (data.length > 500 ? '...' : ''));
|
|
237
238
|
});
|
|
238
239
|
|
|
239
|
-
|
|
240
|
+
debugLog('Items:', Array.from(clipboardData.items).map(item => ({
|
|
240
241
|
kind: item.kind,
|
|
241
242
|
type: item.type,
|
|
242
243
|
})));
|
|
243
244
|
|
|
244
|
-
|
|
245
|
-
|
|
245
|
+
debugLog('Detected type:', detectContentType(clipboardData));
|
|
246
|
+
debugLog('[useRichPaste] === End Debug ===');
|
|
246
247
|
}, []);
|
|
247
248
|
|
|
248
249
|
return {
|
package/src/editor/parts.ts
CHANGED
|
@@ -15,6 +15,7 @@
|
|
|
15
15
|
* (移動・削除・並び替え)。スロットの外を直したいときは「部品から切り離す」。
|
|
16
16
|
*/
|
|
17
17
|
import type { EditorPartDef } from '../io';
|
|
18
|
+
import { findFlowInsertion } from './utils/drop-target';
|
|
18
19
|
|
|
19
20
|
export const PART_ATTR = 'data-part';
|
|
20
21
|
export const PART_VERSION_ATTR = 'data-part-v';
|
|
@@ -281,34 +282,15 @@ export function detachPart(el: HTMLElement): void {
|
|
|
281
282
|
|
|
282
283
|
/**
|
|
283
284
|
* フロー(流し込み)への挿入位置を決める。
|
|
284
|
-
*
|
|
285
|
+
* ドロップ位置の要素から、「セクションの器」の直下にある祖先まで上がり、その前後に入れる。
|
|
285
286
|
* 器 = `[data-wf-body]` / `main` / `#artboard` / body。何も当たらなければ器の末尾。
|
|
287
|
+
*
|
|
288
|
+
* 位置の計算そのものは drop-target.ts に置いてある。ドラッグ中に出す印
|
|
289
|
+
* (どこに入るかの横棒)と同じ関数を使わないと、印と実際の挿入位置がずれるため。
|
|
286
290
|
*/
|
|
287
291
|
export function insertIntoFlow(doc: Document, el: HTMLElement, x: number, y: number): void {
|
|
288
|
-
const
|
|
289
|
-
|
|
290
|
-
(node === doc.body ||
|
|
291
|
-
node.id === 'artboard' ||
|
|
292
|
-
node.hasAttribute('data-wf-body') ||
|
|
293
|
-
node.tagName === 'MAIN');
|
|
294
|
-
const fallback = (): Element =>
|
|
295
|
-
doc.querySelector('[data-wf-body]') ?? doc.querySelector('main') ?? doc.getElementById('artboard') ?? doc.body;
|
|
296
|
-
|
|
297
|
-
let hit: Element | null = doc.elementFromPoint(x, y);
|
|
298
|
-
if (!hit || hit === doc.body || hit.id === 'artboard' || hit.closest('.selection-box')) {
|
|
299
|
-
fallback().appendChild(el);
|
|
300
|
-
return;
|
|
301
|
-
}
|
|
302
|
-
// 器の直下にあたる祖先まで上がる
|
|
303
|
-
let anchor: Element = hit;
|
|
304
|
-
while (anchor.parentElement && !isContainer(anchor.parentElement)) {
|
|
305
|
-
anchor = anchor.parentElement;
|
|
306
|
-
}
|
|
307
|
-
if (!anchor.parentElement) {
|
|
308
|
-
fallback().appendChild(el);
|
|
309
|
-
return;
|
|
310
|
-
}
|
|
311
|
-
anchor.after(el);
|
|
292
|
+
const { parent, before } = findFlowInsertion(doc, x, y);
|
|
293
|
+
parent.insertBefore(el, before);
|
|
312
294
|
}
|
|
313
295
|
|
|
314
296
|
/**
|
|
@@ -14,6 +14,7 @@ import type {
|
|
|
14
14
|
OverrideImageValue,
|
|
15
15
|
} from '../../types/editor-components';
|
|
16
16
|
import { generateElementId } from './dom-utils';
|
|
17
|
+
import { debugLog } from './debug';
|
|
17
18
|
|
|
18
19
|
// ============================================================
|
|
19
20
|
// Constants
|
|
@@ -56,7 +57,7 @@ export function createDOMElement(
|
|
|
56
57
|
doc: Document = document
|
|
57
58
|
): HTMLElement {
|
|
58
59
|
// Debug logging
|
|
59
|
-
|
|
60
|
+
debugLog('[createDOMElement] Creating:', {
|
|
60
61
|
tagName: element.tagName,
|
|
61
62
|
className: element.className,
|
|
62
63
|
hasTextContent: !!element.textContent,
|
|
@@ -110,13 +111,13 @@ export function createDOMElement(
|
|
|
110
111
|
if (element.innerHTML !== undefined) {
|
|
111
112
|
// Use innerHTML for mixed content (text + elements)
|
|
112
113
|
el.innerHTML = element.innerHTML;
|
|
113
|
-
|
|
114
|
+
debugLog('[createDOMElement] Using innerHTML for mixed content');
|
|
114
115
|
} else if (element.textContent !== undefined) {
|
|
115
116
|
// textContent is explicitly set (possibly via override) - use it
|
|
116
117
|
// This takes precedence over children, which is important for text overrides
|
|
117
118
|
el.textContent = element.textContent;
|
|
118
119
|
if (element.children.length > 0) {
|
|
119
|
-
|
|
120
|
+
debugLog('[createDOMElement] textContent override replacing children structure');
|
|
120
121
|
}
|
|
121
122
|
} else {
|
|
122
123
|
// Recursively create and append children
|
|
@@ -155,7 +156,7 @@ export function createDOMElement(
|
|
|
155
156
|
}
|
|
156
157
|
}
|
|
157
158
|
|
|
158
|
-
|
|
159
|
+
debugLog('[createDOMElement] Created element:', el.tagName, 'with innerHTML length:', el.innerHTML.length);
|
|
159
160
|
|
|
160
161
|
return el;
|
|
161
162
|
}
|
|
@@ -417,7 +418,7 @@ export function renderInstance(
|
|
|
417
418
|
instance: ComponentInstance,
|
|
418
419
|
doc: Document = document
|
|
419
420
|
): HTMLElement {
|
|
420
|
-
|
|
421
|
+
debugLog('[renderInstance] Starting render:', {
|
|
421
422
|
masterId: master.id,
|
|
422
423
|
masterName: master.name,
|
|
423
424
|
instanceId: instance.id,
|
|
@@ -428,7 +429,7 @@ export function renderInstance(
|
|
|
428
429
|
// Log the master component's root element structure
|
|
429
430
|
const defaultVariant = master.variants.find(v => v.id === instance.variantId);
|
|
430
431
|
if (defaultVariant) {
|
|
431
|
-
|
|
432
|
+
debugLog('[renderInstance] Variant rootElement:', {
|
|
432
433
|
tagName: defaultVariant.rootElement.tagName,
|
|
433
434
|
className: defaultVariant.rootElement.className,
|
|
434
435
|
hasTextContent: !!defaultVariant.rootElement.textContent,
|
|
@@ -440,7 +441,7 @@ export function renderInstance(
|
|
|
440
441
|
|
|
441
442
|
// Resolve the instance to a ComponentElement
|
|
442
443
|
const resolved = resolveInstance(master, instance);
|
|
443
|
-
|
|
444
|
+
debugLog('[renderInstance] Resolved element:', {
|
|
444
445
|
tagName: resolved.tagName,
|
|
445
446
|
className: resolved.className,
|
|
446
447
|
hasTextContent: !!resolved.textContent,
|
|
@@ -19,6 +19,7 @@ import {
|
|
|
19
19
|
resolveInstance,
|
|
20
20
|
} from './component-renderer';
|
|
21
21
|
import { detectOverrides } from './override-detection';
|
|
22
|
+
import { debugLog } from './debug';
|
|
22
23
|
|
|
23
24
|
// ============================================================
|
|
24
25
|
// Types
|
|
@@ -360,7 +361,7 @@ export function propagateMasterChanges(
|
|
|
360
361
|
// Find all DOM elements for this master
|
|
361
362
|
const domElements = findInstancesByMaster(iframeDoc, master.id);
|
|
362
363
|
|
|
363
|
-
|
|
364
|
+
debugLog('[propagateMasterChanges] Found DOM elements for master:', {
|
|
364
365
|
masterId: master.id,
|
|
365
366
|
domElementsCount: domElements.length,
|
|
366
367
|
});
|
|
@@ -369,13 +370,13 @@ export function propagateMasterChanges(
|
|
|
369
370
|
const domElementMap = new Map<string, HTMLElement>();
|
|
370
371
|
for (const el of domElements) {
|
|
371
372
|
const instanceId = getInstanceIdFromElement(el);
|
|
372
|
-
|
|
373
|
+
debugLog('[propagateMasterChanges] DOM element instanceId:', instanceId);
|
|
373
374
|
if (instanceId) {
|
|
374
375
|
domElementMap.set(instanceId, el);
|
|
375
376
|
}
|
|
376
377
|
}
|
|
377
378
|
|
|
378
|
-
|
|
379
|
+
debugLog('[propagateMasterChanges] Processing instances:', {
|
|
379
380
|
totalInstances: instances.length,
|
|
380
381
|
instanceIds: instances.map(i => i.id),
|
|
381
382
|
domMapKeys: Array.from(domElementMap.keys()),
|
|
@@ -385,13 +386,13 @@ export function propagateMasterChanges(
|
|
|
385
386
|
for (const instance of instances) {
|
|
386
387
|
// Skip detached instances
|
|
387
388
|
if (instance.isDetached) {
|
|
388
|
-
|
|
389
|
+
debugLog('[propagateMasterChanges] Skipping detached instance:', instance.id);
|
|
389
390
|
continue;
|
|
390
391
|
}
|
|
391
392
|
|
|
392
393
|
// Skip instances for other masters
|
|
393
394
|
if (instance.masterComponentId !== master.id) {
|
|
394
|
-
|
|
395
|
+
debugLog('[propagateMasterChanges] Skipping instance for other master:', instance.id);
|
|
395
396
|
continue;
|
|
396
397
|
}
|
|
397
398
|
|
|
@@ -400,11 +401,11 @@ export function propagateMasterChanges(
|
|
|
400
401
|
const domEl = domElementMap.get(instance.id);
|
|
401
402
|
if (!domEl) {
|
|
402
403
|
// Instance not in DOM, skip
|
|
403
|
-
|
|
404
|
+
debugLog('[propagateMasterChanges] DOM element not found for instance:', instance.id);
|
|
404
405
|
continue;
|
|
405
406
|
}
|
|
406
407
|
|
|
407
|
-
|
|
408
|
+
debugLog('[propagateMasterChanges] Found DOM element for instance:', instance.id);
|
|
408
409
|
|
|
409
410
|
// Validate variant still exists
|
|
410
411
|
let variantId = instance.variantId;
|
|
@@ -423,15 +424,15 @@ export function propagateMasterChanges(
|
|
|
423
424
|
let detectedOverrides: ComponentOverride[] = [];
|
|
424
425
|
if (effectiveVariant) {
|
|
425
426
|
// Debug: Log what we're comparing
|
|
426
|
-
|
|
427
|
-
|
|
427
|
+
debugLog('[propagateMasterChanges] === Override Detection Debug ===');
|
|
428
|
+
debugLog('[propagateMasterChanges] DOM element:', {
|
|
428
429
|
tagName: domEl.tagName,
|
|
429
430
|
elementId: domEl.getAttribute('data-element-id'),
|
|
430
431
|
textContent: domEl.textContent?.substring(0, 100),
|
|
431
432
|
innerHTML: domEl.innerHTML?.substring(0, 200),
|
|
432
433
|
childrenCount: domEl.children.length,
|
|
433
434
|
});
|
|
434
|
-
|
|
435
|
+
debugLog('[propagateMasterChanges] Master rootElement:', {
|
|
435
436
|
tagName: effectiveVariant.rootElement.tagName,
|
|
436
437
|
id: effectiveVariant.rootElement.id,
|
|
437
438
|
textContent: effectiveVariant.rootElement.textContent?.substring(0, 100),
|
|
@@ -442,7 +443,7 @@ export function propagateMasterChanges(
|
|
|
442
443
|
|
|
443
444
|
try {
|
|
444
445
|
detectedOverrides = detectOverrides(domEl, effectiveVariant.rootElement);
|
|
445
|
-
|
|
446
|
+
debugLog('[propagateMasterChanges] Detected DOM overrides:', {
|
|
446
447
|
instanceId: instance.id,
|
|
447
448
|
count: detectedOverrides.length,
|
|
448
449
|
overrides: detectedOverrides.map(o => ({
|
|
@@ -477,7 +478,7 @@ export function propagateMasterChanges(
|
|
|
477
478
|
}
|
|
478
479
|
const mergedOverrides = Array.from(overrideMap.values());
|
|
479
480
|
|
|
480
|
-
|
|
481
|
+
debugLog('[propagateMasterChanges] Merged overrides:', {
|
|
481
482
|
instanceId: instance.id,
|
|
482
483
|
existing: existingOverrides.length,
|
|
483
484
|
detected: detectedOverrides.length,
|
|
@@ -536,7 +537,7 @@ export function propagateMasterChanges(
|
|
|
536
537
|
});
|
|
537
538
|
};
|
|
538
539
|
applyIdMappings(newEl);
|
|
539
|
-
|
|
540
|
+
debugLog('[propagateMasterChanges] Applied ID mappings for duplicated instance:', {
|
|
540
541
|
instanceId: instance.id,
|
|
541
542
|
mappingsCount: idMappings.size,
|
|
542
543
|
});
|
|
@@ -563,7 +564,7 @@ export function propagateMasterChanges(
|
|
|
563
564
|
newEl.style.setProperty(prop, value);
|
|
564
565
|
}
|
|
565
566
|
|
|
566
|
-
|
|
567
|
+
debugLog('[propagateMasterChanges] Preserved position styles:', {
|
|
567
568
|
instanceId: instance.id,
|
|
568
569
|
preservedStyles: styleObj,
|
|
569
570
|
});
|
|
@@ -585,7 +586,7 @@ export function propagateMasterChanges(
|
|
|
585
586
|
}
|
|
586
587
|
}
|
|
587
588
|
|
|
588
|
-
|
|
589
|
+
debugLog(
|
|
589
590
|
`[propagateMasterChanges] Updated ${result.updatedCount} instances, failed ${result.failedCount}`
|
|
590
591
|
);
|
|
591
592
|
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 開発時だけ出すログ。
|
|
3
|
+
*
|
|
4
|
+
* 【なぜ必要か】
|
|
5
|
+
* 構成ラフ 26 ページをキャンバス表示で開くと、部品 89 個の読み込みだけで
|
|
6
|
+
* console.log が 1,000 件以上出ていた(実測 2,327 件)。console.log は
|
|
7
|
+
* 引数のオブジェクトを DevTools が握り続けるため GC されず、DevTools を
|
|
8
|
+
* 開いていると読み込みが目に見えて遅くなる。利用者に意味のない内部ログは
|
|
9
|
+
* 既定で黙らせ、調べたいときだけ出す。
|
|
10
|
+
*
|
|
11
|
+
* 【出し方】
|
|
12
|
+
* localStorage.setItem('gg-editor:debug', '1') // 出る
|
|
13
|
+
* localStorage.removeItem('gg-editor:debug') // 止まる
|
|
14
|
+
* 値は毎回読む。DevTools でフラグを立てた直後から効かせるため
|
|
15
|
+
* (キャッシュすると読み込み直後の 1 回しか反映されない)。
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
const DEBUG_KEY = 'gg-editor:debug';
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* デバッグログを出す設定か。
|
|
22
|
+
* localStorage は srcdoc / sandbox の文脈や「サイトデータをブロック」設定で
|
|
23
|
+
* 参照そのものが例外を投げるので、必ず握りつぶして false に倒す。
|
|
24
|
+
*/
|
|
25
|
+
export function isDebugEnabled(): boolean {
|
|
26
|
+
try {
|
|
27
|
+
return typeof localStorage !== 'undefined' && localStorage.getItem(DEBUG_KEY) === '1';
|
|
28
|
+
} catch {
|
|
29
|
+
return false;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/** 内部の様子を見るためのログ。既定では出ない */
|
|
34
|
+
export function debugLog(...args: unknown[]): void {
|
|
35
|
+
if (isDebugEnabled()) console.log(...args);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* 開発者向けの警告。既定では出ない。
|
|
40
|
+
* 利用者が対処できる警告(保存に失敗した等)は console.warn のまま残すこと。
|
|
41
|
+
*/
|
|
42
|
+
export function debugWarn(...args: unknown[]): void {
|
|
43
|
+
if (isDebugEnabled()) console.warn(...args);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* 開発時だけ握りつぶしたい失敗の記録。既定では出ない。
|
|
48
|
+
* 利用者に伝える必要のある失敗は console.error のまま残すこと。
|
|
49
|
+
*/
|
|
50
|
+
export function debugError(...args: unknown[]): void {
|
|
51
|
+
if (isDebugEnabled()) console.error(...args);
|
|
52
|
+
}
|
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
|
|
8
8
|
import type { ElementCapture, DOMTreeNode, MarqueeState } from '../types';
|
|
9
9
|
import { removeConflictingClasses } from './tailwind-utils';
|
|
10
|
+
import { debugLog } from './debug';
|
|
10
11
|
|
|
11
12
|
// ========================================
|
|
12
13
|
// buildDomTree キャッシュ(Phase 2 最適化)
|
|
@@ -481,7 +482,7 @@ export function convertSingleElementToAbsolute(iframeDoc: Document, element: HTM
|
|
|
481
482
|
|
|
482
483
|
// インライン要素はスキップ
|
|
483
484
|
if (isInlineElement(element, computedStyle)) {
|
|
484
|
-
|
|
485
|
+
debugLog('[convertSingleElementToAbsolute] Skipping inline element:', element.tagName);
|
|
485
486
|
return false;
|
|
486
487
|
}
|
|
487
488
|
|
|
@@ -566,7 +567,7 @@ export function convertSingleElementToAbsolute(iframeDoc: Document, element: HTM
|
|
|
566
567
|
element.style.transform = preservedTransform;
|
|
567
568
|
}
|
|
568
569
|
|
|
569
|
-
|
|
570
|
+
debugLog('[convertSingleElementToAbsolute] Converted:', {
|
|
570
571
|
tagName: element.tagName,
|
|
571
572
|
left: Math.round(left * 100) / 100,
|
|
572
573
|
top: Math.round(top * 100) / 100,
|
|
@@ -601,7 +602,7 @@ export function convertToAbsolutePositioning(iframeDoc: Document): number {
|
|
|
601
602
|
|
|
602
603
|
// ズームスケールを取得
|
|
603
604
|
const scale = getArtboardScale(iframeDoc);
|
|
604
|
-
|
|
605
|
+
debugLog('[convertToAbsolutePositioning] Scale:', scale);
|
|
605
606
|
|
|
606
607
|
// artboardの現在の高さをキャプチャして固定(絶対配置後も高さを維持するため)
|
|
607
608
|
const artboard = iframeDoc.getElementById('artboard');
|
|
@@ -613,7 +614,7 @@ export function convertToAbsolutePositioning(iframeDoc: Document): number {
|
|
|
613
614
|
artboard.setAttribute('data-original-height', artboard.style.height || 'auto');
|
|
614
615
|
}
|
|
615
616
|
artboard.style.height = `${Math.round(artboardHeight)}px`;
|
|
616
|
-
|
|
617
|
+
debugLog('[convertToAbsolutePositioning] Set artboard height:', artboardHeight);
|
|
617
618
|
}
|
|
618
619
|
|
|
619
620
|
// 1. まず全要素の現在の位置・サイズをキャプチャ(インライン要素は除外)
|
|
@@ -864,7 +865,7 @@ export function convertToAbsolutePositioning(iframeDoc: Document): number {
|
|
|
864
865
|
}
|
|
865
866
|
|
|
866
867
|
captures.forEach(({ element }) => element.removeAttribute('data-gg-pre-overflow'));
|
|
867
|
-
|
|
868
|
+
debugLog('[convertToAbsolutePositioning] Converted', captures.length, 'elements');
|
|
868
869
|
return captures.length;
|
|
869
870
|
}
|
|
870
871
|
|
|
@@ -885,11 +886,11 @@ export function restoreArtboardAutoHeight(iframeDoc: Document): void {
|
|
|
885
886
|
artboard.style.height = originalHeight;
|
|
886
887
|
}
|
|
887
888
|
artboard.removeAttribute('data-original-height');
|
|
888
|
-
|
|
889
|
+
debugLog('[restoreArtboardAutoHeight] Restored artboard height to:', originalHeight);
|
|
889
890
|
} else {
|
|
890
891
|
// data-original-heightがない場合は単純に高さを削除
|
|
891
892
|
artboard.style.removeProperty('height');
|
|
892
|
-
|
|
893
|
+
debugLog('[restoreArtboardAutoHeight] Removed artboard height style');
|
|
893
894
|
}
|
|
894
895
|
}
|
|
895
896
|
|
|
@@ -980,20 +981,20 @@ export function buildDomTree(
|
|
|
980
981
|
|
|
981
982
|
// HTMLが変わっていなければキャッシュを返す
|
|
982
983
|
if (cached && cached.html === currentHtml) {
|
|
983
|
-
|
|
984
|
+
debugLog('[buildDomTree] Cache hit, returning cached tree with', cached.tree.length, 'root nodes');
|
|
984
985
|
return cached.tree;
|
|
985
986
|
}
|
|
986
987
|
|
|
987
988
|
// 新規計算
|
|
988
989
|
const tree = buildDomTreeInternal(iframeDoc, root);
|
|
989
990
|
domTreeCache.set(iframeDoc, { html: currentHtml, tree });
|
|
990
|
-
|
|
991
|
+
debugLog('[buildDomTree] Cache miss, built tree with', tree.length, 'root nodes from', root.id || root.tagName);
|
|
991
992
|
return tree;
|
|
992
993
|
}
|
|
993
994
|
|
|
994
995
|
// キャッシュを使用しない場合
|
|
995
996
|
const nodes = buildDomTreeInternal(iframeDoc, root);
|
|
996
|
-
|
|
997
|
+
debugLog('[buildDomTree] Built tree (no cache) with', nodes.length, 'root nodes from', root.id || root.tagName);
|
|
997
998
|
return nodes;
|
|
998
999
|
}
|
|
999
1000
|
|