@growgroup/visual-editor 0.3.0 → 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 +10 -1
- package/dist/editor.css +1 -1
- package/package.json +3 -2
- package/src/editor/EditorContext.tsx +11 -2
- package/src/editor/EditorToolbar.tsx +8 -2
- package/src/editor/FrontendVisualEditor.tsx +128 -45
- package/src/editor/components/EditorCanvas.tsx +87 -15
- package/src/editor/components/EditorContextMenu.tsx +12 -3
- package/src/editor/components/EditorFooter.tsx +2 -0
- package/src/editor/components/EditorHeader.tsx +3 -2
- package/src/editor/components/EditorLayerPanel.tsx +28 -2
- package/src/editor/components/EditorPropertyPanel.tsx +54 -25
- 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 +204 -58
- package/src/editor/components/multi-page/PageFramePreview.tsx +131 -22
- 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 +44 -1
- package/src/editor/contexts/EditorArtboardContext.tsx +60 -1
- package/src/editor/contexts/EditorComponentsContext.tsx +48 -31
- package/src/editor/contexts/EditorUIStateContext.tsx +4 -3
- package/src/editor/contexts/EditorVariablesContext.tsx +5 -4
- package/src/editor/contexts/MultiPageCanvasContext.tsx +211 -34
- 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 +27 -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/html-utils.ts +19 -6
- 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/index.ts +5 -0
- 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 +63 -4
- package/src/types/editor.ts +6 -2
- package/src/version.ts +9 -0
|
@@ -30,6 +30,7 @@ import {
|
|
|
30
30
|
} from '../utils/viewport-utils';
|
|
31
31
|
import type { SerializedElement, BoundingBox, StyleClipboard, ClipboardData } from '../types';
|
|
32
32
|
import type { globalLastUsedStylesRef } from '../contexts/EditorRefsContext';
|
|
33
|
+
import { debugLog } from '../utils/debug';
|
|
33
34
|
|
|
34
35
|
/**
|
|
35
36
|
* 要素の種類を判定(shape / text / line)
|
|
@@ -202,7 +203,7 @@ function captureLastUsedStyles(
|
|
|
202
203
|
lastUsedStylesRef: typeof globalLastUsedStylesRef
|
|
203
204
|
) {
|
|
204
205
|
const category = getElementCategory(el);
|
|
205
|
-
|
|
206
|
+
debugLog('[captureLastUsedStyles] Category:', category, 'Styles:', styles);
|
|
206
207
|
|
|
207
208
|
if (category === 'shape') {
|
|
208
209
|
const shapeStyles: Partial<typeof lastUsedStylesRef.current.shape> = {};
|
|
@@ -214,7 +215,7 @@ function captureLastUsedStyles(
|
|
|
214
215
|
if (styles.opacity) shapeStyles.opacity = styles.opacity;
|
|
215
216
|
if (Object.keys(shapeStyles).length > 0) {
|
|
216
217
|
lastUsedStylesRef.updateShape(shapeStyles);
|
|
217
|
-
|
|
218
|
+
debugLog('[captureLastUsedStyles] Updated shape styles:', shapeStyles);
|
|
218
219
|
}
|
|
219
220
|
} else if (category === 'text') {
|
|
220
221
|
const textStyles: Partial<typeof lastUsedStylesRef.current.text> = {};
|
|
@@ -227,7 +228,7 @@ function captureLastUsedStyles(
|
|
|
227
228
|
if (styles.textAlign) textStyles.textAlign = styles.textAlign;
|
|
228
229
|
if (Object.keys(textStyles).length > 0) {
|
|
229
230
|
lastUsedStylesRef.updateText(textStyles);
|
|
230
|
-
|
|
231
|
+
debugLog('[captureLastUsedStyles] Updated text styles:', textStyles);
|
|
231
232
|
}
|
|
232
233
|
} else if (category === 'line') {
|
|
233
234
|
const lineStyles: Partial<typeof lastUsedStylesRef.current.line> = {};
|
|
@@ -235,7 +236,7 @@ function captureLastUsedStyles(
|
|
|
235
236
|
if (styles.strokeWidth) lineStyles.strokeWidth = styles.strokeWidth;
|
|
236
237
|
if (Object.keys(lineStyles).length > 0) {
|
|
237
238
|
lastUsedStylesRef.updateLine(lineStyles);
|
|
238
|
-
|
|
239
|
+
debugLog('[captureLastUsedStyles] Updated line styles:', lineStyles);
|
|
239
240
|
}
|
|
240
241
|
}
|
|
241
242
|
}
|
|
@@ -533,11 +534,11 @@ export function useElementActions() {
|
|
|
533
534
|
|
|
534
535
|
// 複数選択されている場合は全て更新
|
|
535
536
|
if (selectedElementIds.length > 0) {
|
|
536
|
-
|
|
537
|
+
debugLog('[DEBUG useElementActions] Applying styles to', selectedElementIds.length, 'elements');
|
|
537
538
|
selectedElementIds.forEach(id => {
|
|
538
539
|
const el = getIframeElement(iframeDoc, id);
|
|
539
540
|
if (el) {
|
|
540
|
-
|
|
541
|
+
debugLog('[DEBUG useElementActions] Applying to element:', id);
|
|
541
542
|
applyStylesWithViewportHandling(el, styles);
|
|
542
543
|
}
|
|
543
544
|
});
|
|
@@ -699,7 +700,7 @@ export function useElementActions() {
|
|
|
699
700
|
if (newInstance) {
|
|
700
701
|
// 新しいインスタンスIDで属性を更新
|
|
701
702
|
clone.setAttribute('data-component-instance', newInstance.id);
|
|
702
|
-
|
|
703
|
+
debugLog('[duplicateElement] Created new component instance:', newInstance.id, 'from:', instanceId);
|
|
703
704
|
}
|
|
704
705
|
} catch (error) {
|
|
705
706
|
// インスタンス作成に失敗した場合、コンポーネント属性を削除(通常要素として複製)
|
|
@@ -1159,7 +1160,7 @@ export function useElementActions() {
|
|
|
1159
1160
|
const convertChildrenToAbsolute = async () => {
|
|
1160
1161
|
// オートレイアウトモードの場合は変換をスキップ
|
|
1161
1162
|
if (layoutMode === 'auto') {
|
|
1162
|
-
|
|
1163
|
+
debugLog('[groupElements] Auto layout mode - skipping absolute positioning conversion');
|
|
1163
1164
|
return;
|
|
1164
1165
|
}
|
|
1165
1166
|
|
|
@@ -1175,7 +1176,7 @@ export function useElementActions() {
|
|
|
1175
1176
|
convertSingleElementToAbsolute(iframeDoc, el);
|
|
1176
1177
|
});
|
|
1177
1178
|
|
|
1178
|
-
|
|
1179
|
+
debugLog('[groupElements] Converted', elements.length, 'child elements to absolute positioning');
|
|
1179
1180
|
notifyIframeChange();
|
|
1180
1181
|
} catch (err) {
|
|
1181
1182
|
console.error('[groupElements] Error converting children:', err);
|
|
@@ -1206,7 +1207,7 @@ export function useElementActions() {
|
|
|
1206
1207
|
|
|
1207
1208
|
// グループ解除可能かどうかを判定(詳細な条件をチェック)
|
|
1208
1209
|
if (!canUngroup(parent)) {
|
|
1209
|
-
|
|
1210
|
+
debugLog('[ungroupElements] Element cannot be ungrouped:', parent.tagName);
|
|
1210
1211
|
return;
|
|
1211
1212
|
}
|
|
1212
1213
|
|
|
@@ -1226,7 +1227,7 @@ export function useElementActions() {
|
|
|
1226
1227
|
});
|
|
1227
1228
|
|
|
1228
1229
|
if (children.length === 0) {
|
|
1229
|
-
|
|
1230
|
+
debugLog('[ungroupElements] No editable children to ungroup');
|
|
1230
1231
|
return;
|
|
1231
1232
|
}
|
|
1232
1233
|
|
|
@@ -1307,7 +1308,7 @@ export function useElementActions() {
|
|
|
1307
1308
|
const finalizePositioning = async () => {
|
|
1308
1309
|
// オートレイアウトモードの場合は変換をスキップ
|
|
1309
1310
|
if (layoutMode === 'auto') {
|
|
1310
|
-
|
|
1311
|
+
debugLog('[ungroupElements] Auto layout mode - skipping absolute positioning conversion');
|
|
1311
1312
|
return;
|
|
1312
1313
|
}
|
|
1313
1314
|
|
|
@@ -1322,7 +1323,7 @@ export function useElementActions() {
|
|
|
1322
1323
|
convertSingleElementToAbsolute(iframeDoc, el);
|
|
1323
1324
|
});
|
|
1324
1325
|
|
|
1325
|
-
|
|
1326
|
+
debugLog('[ungroupElements] Converted', childElements.length, 'elements to absolute positioning');
|
|
1326
1327
|
notifyIframeChange();
|
|
1327
1328
|
} catch (err) {
|
|
1328
1329
|
console.error('[ungroupElements] Error finalizing positions:', err);
|
|
@@ -1335,7 +1336,7 @@ export function useElementActions() {
|
|
|
1335
1336
|
setSelectedElement(null);
|
|
1336
1337
|
notifyIframeChange();
|
|
1337
1338
|
|
|
1338
|
-
|
|
1339
|
+
debugLog('[ungroupElements] Ungrouped', childIds.length, 'elements from', selectedElement.id);
|
|
1339
1340
|
}, [selectedElement, getIframeDoc, setSelectedElementIds, setSelectedElement, notifyIframeChange]);
|
|
1340
1341
|
|
|
1341
1342
|
/**
|
|
@@ -1525,10 +1526,10 @@ export function useElementActions() {
|
|
|
1525
1526
|
* Ctrl/Cmd + Alt + C
|
|
1526
1527
|
*/
|
|
1527
1528
|
const copyStyle = useCallback(() => {
|
|
1528
|
-
|
|
1529
|
+
debugLog('[copyStyle] Called, selectedElement:', selectedElement?.id);
|
|
1529
1530
|
const iframeDoc = getIframeDoc();
|
|
1530
1531
|
if (!iframeDoc || !selectedElement) {
|
|
1531
|
-
|
|
1532
|
+
debugLog('[copyStyle] No element selected or no iframeDoc');
|
|
1532
1533
|
return false;
|
|
1533
1534
|
}
|
|
1534
1535
|
|
|
@@ -1600,8 +1601,8 @@ export function useElementActions() {
|
|
|
1600
1601
|
) as StyleClipboard;
|
|
1601
1602
|
|
|
1602
1603
|
styleClipboardRef.current = cleanedStyle;
|
|
1603
|
-
|
|
1604
|
-
|
|
1604
|
+
debugLog('[copyStyle] Style copied:', cleanedStyle);
|
|
1605
|
+
debugLog('[copyStyle] Clipboard after setting:', styleClipboardRef.current);
|
|
1605
1606
|
return true;
|
|
1606
1607
|
}, [selectedElement, getIframeDoc, styleClipboardRef]);
|
|
1607
1608
|
|
|
@@ -1610,17 +1611,17 @@ export function useElementActions() {
|
|
|
1610
1611
|
* Ctrl/Cmd + Alt + V
|
|
1611
1612
|
*/
|
|
1612
1613
|
const pasteStyle = useCallback(() => {
|
|
1613
|
-
|
|
1614
|
-
|
|
1614
|
+
debugLog('[pasteStyle] Called');
|
|
1615
|
+
debugLog('[pasteStyle] styleClipboardRef.current:', styleClipboardRef.current);
|
|
1615
1616
|
const iframeDoc = getIframeDoc();
|
|
1616
1617
|
if (!iframeDoc) {
|
|
1617
|
-
|
|
1618
|
+
debugLog('[pasteStyle] No iframeDoc');
|
|
1618
1619
|
return false;
|
|
1619
1620
|
}
|
|
1620
1621
|
|
|
1621
1622
|
const copiedStyle = styleClipboardRef.current;
|
|
1622
1623
|
if (!copiedStyle || Object.keys(copiedStyle).length === 0) {
|
|
1623
|
-
|
|
1624
|
+
debugLog('[pasteStyle] No style in clipboard');
|
|
1624
1625
|
return false;
|
|
1625
1626
|
}
|
|
1626
1627
|
|
|
@@ -1630,7 +1631,7 @@ export function useElementActions() {
|
|
|
1630
1631
|
: selectedElement ? [selectedElement.id] : [];
|
|
1631
1632
|
|
|
1632
1633
|
if (targetIds.length === 0) {
|
|
1633
|
-
|
|
1634
|
+
debugLog('[pasteStyle] No element selected');
|
|
1634
1635
|
return false;
|
|
1635
1636
|
}
|
|
1636
1637
|
|
|
@@ -1707,7 +1708,7 @@ export function useElementActions() {
|
|
|
1707
1708
|
}
|
|
1708
1709
|
}
|
|
1709
1710
|
|
|
1710
|
-
|
|
1711
|
+
debugLog('[pasteStyle] Style pasted to', targetIds.length, 'element(s)');
|
|
1711
1712
|
return true;
|
|
1712
1713
|
}, [selectedElement, selectedElementIds, getIframeDoc, styleClipboardRef, notifyIframeChange, setSelectedElement]);
|
|
1713
1714
|
|
|
@@ -1754,13 +1755,13 @@ export function useElementActions() {
|
|
|
1754
1755
|
return { success: false, error: 'Selected elements not found in document' };
|
|
1755
1756
|
}
|
|
1756
1757
|
|
|
1757
|
-
|
|
1758
|
+
debugLog('[copyToFigma] Copying', elements.length, 'element(s) to Figma format');
|
|
1758
1759
|
|
|
1759
1760
|
try {
|
|
1760
1761
|
const result = await copyElementsToFigma(elements);
|
|
1761
1762
|
|
|
1762
1763
|
if (result.success) {
|
|
1763
|
-
|
|
1764
|
+
debugLog('[copyToFigma] Successfully copied', result.nodeCount, 'nodes to clipboard');
|
|
1764
1765
|
return { success: true, nodeCount: result.nodeCount };
|
|
1765
1766
|
} else {
|
|
1766
1767
|
console.error('[copyToFigma] Failed:', result.error);
|
|
@@ -24,6 +24,7 @@ import {
|
|
|
24
24
|
import { extractElementInfo } from "../utils/style-utils";
|
|
25
25
|
import { MARQUEE_DRAG_THRESHOLD } from "../constants";
|
|
26
26
|
import type { DragState, ResizeState, MarqueeState } from "../types";
|
|
27
|
+
import { debugLog } from '../utils/debug';
|
|
27
28
|
|
|
28
29
|
/**
|
|
29
30
|
* mousedown で保留し、mouseup で意味を確定するクリック情報
|
|
@@ -453,7 +454,7 @@ export function useElementSelection(
|
|
|
453
454
|
|
|
454
455
|
// 真のインライン要素(テキストフロー内)はドラッグしない
|
|
455
456
|
if (isTrueInlineInTextFlow(element, iframeDoc)) {
|
|
456
|
-
|
|
457
|
+
debugLog(
|
|
457
458
|
"[startElementDrag] Skipping true inline element:",
|
|
458
459
|
element.tagName,
|
|
459
460
|
element.getAttribute("data-element-id"),
|
|
@@ -918,14 +919,15 @@ export function useElementSelection(
|
|
|
918
919
|
if (editingElement.classList.contains("selected")) {
|
|
919
920
|
updateSelectionBox(iframeDoc, editingElement);
|
|
920
921
|
}
|
|
921
|
-
|
|
922
|
+
debugLog("[Canvas] Text editing exited via click elsewhere");
|
|
922
923
|
}
|
|
923
924
|
|
|
924
925
|
// 描画モード・テキストモードでは選択しない
|
|
925
926
|
const bodyClasses = iframeDoc.body.classList;
|
|
926
927
|
if (
|
|
927
928
|
bodyClasses.contains("draw-mode") ||
|
|
928
|
-
bodyClasses.contains("text-mode")
|
|
929
|
+
bodyClasses.contains("text-mode") ||
|
|
930
|
+
bodyClasses.contains("comment-mode")
|
|
929
931
|
)
|
|
930
932
|
return;
|
|
931
933
|
|
|
@@ -1030,7 +1032,7 @@ export function useElementSelection(
|
|
|
1030
1032
|
centerY,
|
|
1031
1033
|
};
|
|
1032
1034
|
iframeDoc.body.classList.add("rotating");
|
|
1033
|
-
|
|
1035
|
+
debugLog("[Canvas] Rotation started:", handleType);
|
|
1034
1036
|
} else if (handleType === "radius") {
|
|
1035
1037
|
// 角丸ハンドル
|
|
1036
1038
|
const borderRadius =
|
|
@@ -1057,7 +1059,7 @@ export function useElementSelection(
|
|
|
1057
1059
|
centerX: 0,
|
|
1058
1060
|
centerY: 0,
|
|
1059
1061
|
};
|
|
1060
|
-
|
|
1062
|
+
debugLog("[Canvas] Border radius resize started");
|
|
1061
1063
|
} else {
|
|
1062
1064
|
// 通常のリサイズハンドル
|
|
1063
1065
|
resizeStateRef.current = {
|
|
@@ -1082,7 +1084,7 @@ export function useElementSelection(
|
|
|
1082
1084
|
centerX: 0,
|
|
1083
1085
|
centerY: 0,
|
|
1084
1086
|
};
|
|
1085
|
-
|
|
1087
|
+
debugLog("[Canvas] Resize started:", handleType);
|
|
1086
1088
|
}
|
|
1087
1089
|
}
|
|
1088
1090
|
return;
|
|
@@ -1129,7 +1131,7 @@ export function useElementSelection(
|
|
|
1129
1131
|
e,
|
|
1130
1132
|
iframeDoc,
|
|
1131
1133
|
);
|
|
1132
|
-
|
|
1134
|
+
debugLog(
|
|
1133
1135
|
"[Canvas] Multi-element drag via selection-box:",
|
|
1134
1136
|
existingIds,
|
|
1135
1137
|
started ? "started" : "blocked",
|
|
@@ -1138,7 +1140,7 @@ export function useElementSelection(
|
|
|
1138
1140
|
} else {
|
|
1139
1141
|
// 単一選択時:対象要素をドラッグ
|
|
1140
1142
|
startElementDrag(targetEl, e, iframeDoc);
|
|
1141
|
-
|
|
1143
|
+
debugLog(
|
|
1142
1144
|
"[Canvas] Drag via selection-box for:",
|
|
1143
1145
|
forElementId,
|
|
1144
1146
|
);
|
|
@@ -1212,7 +1214,7 @@ export function useElementSelection(
|
|
|
1212
1214
|
if (marqueeGeomRef) marqueeGeomRef.current = { ...geom };
|
|
1213
1215
|
setMarqueeStateRef.current(geom);
|
|
1214
1216
|
marqueeStartPendingRef.current = true;
|
|
1215
|
-
|
|
1217
|
+
debugLog(
|
|
1216
1218
|
"[Canvas] Marquee pending (blank) at",
|
|
1217
1219
|
e.clientX,
|
|
1218
1220
|
e.clientY,
|
|
@@ -1251,7 +1253,7 @@ export function useElementSelection(
|
|
|
1251
1253
|
}
|
|
1252
1254
|
|
|
1253
1255
|
if (target.getAttribute("contenteditable") === "true") {
|
|
1254
|
-
|
|
1256
|
+
debugLog("[Canvas] Skip - contenteditable active");
|
|
1255
1257
|
return;
|
|
1256
1258
|
}
|
|
1257
1259
|
|
|
@@ -1287,7 +1289,7 @@ export function useElementSelection(
|
|
|
1287
1289
|
.filter((el) => el !== null);
|
|
1288
1290
|
if (selectedElements.length > 0) {
|
|
1289
1291
|
const started = startGroupDrag(selectedElements, e, iframeDoc);
|
|
1290
|
-
|
|
1292
|
+
debugLog(
|
|
1291
1293
|
"[Canvas] Group drag:",
|
|
1292
1294
|
existingIds.length,
|
|
1293
1295
|
started ? "started" : "blocked",
|
|
@@ -1295,21 +1297,21 @@ export function useElementSelection(
|
|
|
1295
1297
|
}
|
|
1296
1298
|
} else {
|
|
1297
1299
|
startElementDrag(target, e, iframeDoc);
|
|
1298
|
-
|
|
1300
|
+
debugLog("[Canvas] Single drag start:", elementId);
|
|
1299
1301
|
}
|
|
1300
1302
|
return;
|
|
1301
1303
|
}
|
|
1302
1304
|
|
|
1303
1305
|
if (e.shiftKey) {
|
|
1304
1306
|
// 未選択 + Shift → 追加するかは mouseup で確定(ここでは動かさない)
|
|
1305
|
-
|
|
1307
|
+
debugLog("[Canvas] Shift add pending:", elementId);
|
|
1306
1308
|
return;
|
|
1307
1309
|
}
|
|
1308
1310
|
|
|
1309
1311
|
// 未選択 + 素クリック → その場で単独選択してドラッグ開始(モード非依存)
|
|
1310
1312
|
selectSingle(iframeDoc, target);
|
|
1311
1313
|
startElementDrag(target, e, iframeDoc);
|
|
1312
|
-
|
|
1314
|
+
debugLog("[Canvas] Select + drag start:", elementId);
|
|
1313
1315
|
};
|
|
1314
1316
|
|
|
1315
1317
|
/**
|
|
@@ -1421,7 +1423,8 @@ export function useElementSelection(
|
|
|
1421
1423
|
if (bodyClasses.contains("gg-cropping")) return;
|
|
1422
1424
|
if (
|
|
1423
1425
|
bodyClasses.contains("draw-mode") ||
|
|
1424
|
-
bodyClasses.contains("text-mode")
|
|
1426
|
+
bodyClasses.contains("text-mode") ||
|
|
1427
|
+
bodyClasses.contains("comment-mode")
|
|
1425
1428
|
)
|
|
1426
1429
|
return;
|
|
1427
1430
|
|
|
@@ -1452,7 +1455,7 @@ export function useElementSelection(
|
|
|
1452
1455
|
deepElement.classList.add("selected");
|
|
1453
1456
|
setSelectedElementIdsRef.current([elementId]);
|
|
1454
1457
|
enableTextEditing(deepElement);
|
|
1455
|
-
|
|
1458
|
+
debugLog(
|
|
1456
1459
|
"[Canvas] Text editing started via double-click:",
|
|
1457
1460
|
elementId,
|
|
1458
1461
|
);
|
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
|
|
8
8
|
import { useCallback, useRef, useEffect } from 'react';
|
|
9
9
|
import { useEditorContext } from '../EditorContext';
|
|
10
|
+
import { debugLog } from '../utils/debug';
|
|
10
11
|
|
|
11
12
|
interface UseFocusManagementReturn {
|
|
12
13
|
/**
|
|
@@ -49,7 +50,7 @@ export function useFocusManagement(): UseFocusManagementReturn {
|
|
|
49
50
|
const iframe = iframeRef.current;
|
|
50
51
|
const iframeDoc = getIframeDoc();
|
|
51
52
|
if (!iframeDoc) {
|
|
52
|
-
|
|
53
|
+
debugLog('[FocusManagement] Cannot restore focus: iframe doc not available');
|
|
53
54
|
return;
|
|
54
55
|
}
|
|
55
56
|
|
|
@@ -59,7 +60,7 @@ export function useFocusManagement(): UseFocusManagementReturn {
|
|
|
59
60
|
// まずiframe要素自体にフォーカス(親ドキュメントの観点から)
|
|
60
61
|
if (iframe) iframe.focus();
|
|
61
62
|
editingElement.focus();
|
|
62
|
-
|
|
63
|
+
debugLog('[FocusManagement] Restored focus to editing element');
|
|
63
64
|
return;
|
|
64
65
|
}
|
|
65
66
|
|
|
@@ -70,7 +71,7 @@ export function useFocusManagement(): UseFocusManagementReturn {
|
|
|
70
71
|
}
|
|
71
72
|
if (iframeDoc.body) {
|
|
72
73
|
iframeDoc.body.focus();
|
|
73
|
-
|
|
74
|
+
debugLog('[FocusManagement] Restored focus to iframe and body');
|
|
74
75
|
}
|
|
75
76
|
}, [iframeRef, getIframeDoc]);
|
|
76
77
|
|
|
@@ -139,7 +140,7 @@ export function useFocusManagement(): UseFocusManagementReturn {
|
|
|
139
140
|
relatedTarget.closest('[data-dropdown-menu]');
|
|
140
141
|
|
|
141
142
|
if (isDialogOrPopover) {
|
|
142
|
-
|
|
143
|
+
debugLog('[FocusManagement] Focus moved to dialog/popover, allowing');
|
|
143
144
|
return;
|
|
144
145
|
}
|
|
145
146
|
|
|
@@ -151,7 +152,7 @@ export function useFocusManagement(): UseFocusManagementReturn {
|
|
|
151
152
|
relatedTarget.getAttribute('contenteditable') === 'true';
|
|
152
153
|
|
|
153
154
|
if (isInputField) {
|
|
154
|
-
|
|
155
|
+
debugLog('[FocusManagement] Focus moved to input field, allowing');
|
|
155
156
|
return;
|
|
156
157
|
}
|
|
157
158
|
|
|
@@ -170,7 +171,7 @@ export function useFocusManagement(): UseFocusManagementReturn {
|
|
|
170
171
|
focusRecoveryTimerRef.current = setTimeout(() => {
|
|
171
172
|
// まだフォーカスがエディタ外にあれば復元
|
|
172
173
|
if (!isEditorFocused()) {
|
|
173
|
-
|
|
174
|
+
debugLog('[FocusManagement] Focus lost, attempting recovery');
|
|
174
175
|
restoreFocus();
|
|
175
176
|
}
|
|
176
177
|
}, 30); // 30msに短縮してフォーカス復元を高速化
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
|
|
3
3
|
import { useState, useEffect, useCallback, useMemo } from 'react';
|
|
4
|
+
import { debugLog } from '../utils/debug';
|
|
4
5
|
|
|
5
6
|
/**
|
|
6
7
|
* Google Fontの情報
|
|
@@ -183,7 +184,7 @@ export function useGoogleFonts() {
|
|
|
183
184
|
|
|
184
185
|
// APIエラーまたは空の場合はフォールバック
|
|
185
186
|
if (data.error || !data.items || data.items.length === 0) {
|
|
186
|
-
|
|
187
|
+
debugLog('[useGoogleFonts] API returned empty, using popular fonts');
|
|
187
188
|
setFonts([...SYSTEM_FONTS, ...POPULAR_GOOGLE_FONTS]);
|
|
188
189
|
return;
|
|
189
190
|
}
|
|
@@ -254,7 +255,7 @@ export function useGoogleFonts() {
|
|
|
254
255
|
// ロード済みセットに追加
|
|
255
256
|
setLoadedFonts(prev => new Set(prev).add(fontKey));
|
|
256
257
|
|
|
257
|
-
|
|
258
|
+
debugLog(`[useGoogleFonts] Loaded font: ${fontFamily}`);
|
|
258
259
|
}, [loadedFonts]);
|
|
259
260
|
|
|
260
261
|
/**
|
|
@@ -17,6 +17,7 @@ import {
|
|
|
17
17
|
} from '../utils/dom-utils';
|
|
18
18
|
import { generateEditableHtml } from '../utils/html-utils';
|
|
19
19
|
import { extractElementInfo } from '../utils/style-utils';
|
|
20
|
+
import { debugLog } from '../utils/debug';
|
|
20
21
|
|
|
21
22
|
/**
|
|
22
23
|
* iframeの初期化とイベントハンドラ設定を行うHook
|
|
@@ -357,7 +358,7 @@ export function useIframeInitializer() {
|
|
|
357
358
|
|
|
358
359
|
element.addEventListener('mousedown', (e) => {
|
|
359
360
|
const bodyClasses = iframeDoc.body.classList;
|
|
360
|
-
if (bodyClasses.contains('draw-mode') || bodyClasses.contains('text-mode')) return;
|
|
361
|
+
if (bodyClasses.contains('draw-mode') || bodyClasses.contains('text-mode') || bodyClasses.contains('comment-mode')) return;
|
|
361
362
|
if (element.getAttribute('contenteditable') === 'true') return;
|
|
362
363
|
|
|
363
364
|
e.preventDefault();
|
|
@@ -506,7 +507,7 @@ export function useIframeInitializer() {
|
|
|
506
507
|
// 別の幅で崩れ、デザインツールへの取り込みで意味を失う。
|
|
507
508
|
if (editorMode !== 'webpage') {
|
|
508
509
|
const count = convertToAbsolutePositioning(iframeDoc);
|
|
509
|
-
|
|
510
|
+
debugLog('[Editor] 絶対配置へ変換:', count, '要素');
|
|
510
511
|
}
|
|
511
512
|
|
|
512
513
|
// 変更を通知
|
|
@@ -567,7 +568,7 @@ export function useIframeInitializer() {
|
|
|
567
568
|
const iframeDoc = getIframeDoc();
|
|
568
569
|
if (iframeDoc?.body) {
|
|
569
570
|
iframeDoc.body.classList.remove(
|
|
570
|
-
'move-mode', 'draw-mode', 'text-mode',
|
|
571
|
+
'move-mode', 'draw-mode', 'text-mode', 'comment-mode',
|
|
571
572
|
'tool-rectangle', 'tool-ellipse', 'tool-line', 'tool-arrow',
|
|
572
573
|
'tool-pen', 'tool-pencil', 'tool-text', 'tool-frame'
|
|
573
574
|
);
|
|
@@ -578,6 +579,8 @@ export function useIframeInitializer() {
|
|
|
578
579
|
iframeDoc.body.classList.add('draw-mode', `tool-${activeTool}`);
|
|
579
580
|
} else if (activeTool === 'text') {
|
|
580
581
|
iframeDoc.body.classList.add('text-mode', 'tool-text');
|
|
582
|
+
} else if (activeTool === 'comment') {
|
|
583
|
+
iframeDoc.body.classList.add('comment-mode');
|
|
581
584
|
}
|
|
582
585
|
}
|
|
583
586
|
}, [activeTool, getIframeDoc]);
|
|
@@ -11,6 +11,7 @@ import { useEditorContext } from '../EditorContext';
|
|
|
11
11
|
import { buildDomTree, isInlineElement } from '../utils/dom-utils';
|
|
12
12
|
import { EDITOR_IFRAME_STYLES } from '../constants';
|
|
13
13
|
import { isLockedInsidePart } from '../parts';
|
|
14
|
+
import { debugLog } from '../utils/debug';
|
|
14
15
|
|
|
15
16
|
interface UseIframeSetupReturn {
|
|
16
17
|
/**
|
|
@@ -145,7 +146,7 @@ export function useIframeSetup(): UseIframeSetupReturn {
|
|
|
145
146
|
*/
|
|
146
147
|
const initializeIframeDocument = useCallback(
|
|
147
148
|
(iframeDoc: Document) => {
|
|
148
|
-
|
|
149
|
+
debugLog('[IframeSetup] Initializing iframe document...');
|
|
149
150
|
|
|
150
151
|
// スタイルを注入
|
|
151
152
|
const style = iframeDoc.createElement('style');
|
|
@@ -158,7 +159,7 @@ export function useIframeSetup(): UseIframeSetupReturn {
|
|
|
158
159
|
|
|
159
160
|
// 全要素にdata-editableとdata-element-idを付与
|
|
160
161
|
const elementCount = initializeEditableElements(iframeDoc);
|
|
161
|
-
|
|
162
|
+
debugLog('[IframeSetup] Initialized', elementCount, 'editable elements');
|
|
162
163
|
|
|
163
164
|
// DOMツリーを構築
|
|
164
165
|
const newTree = buildDomTree(iframeDoc);
|
|
@@ -278,7 +279,7 @@ export function useIframeSetup(): UseIframeSetupReturn {
|
|
|
278
279
|
const handleMouseMove = (e: MouseEvent) => {
|
|
279
280
|
// 描画モードやテキストモードでは無効
|
|
280
281
|
const bodyClasses = iframeDoc.body.classList;
|
|
281
|
-
if (bodyClasses.contains('draw-mode') || bodyClasses.contains('text-mode') || bodyClasses.contains('move-mode')) {
|
|
282
|
+
if (bodyClasses.contains('draw-mode') || bodyClasses.contains('text-mode') || bodyClasses.contains('move-mode') || bodyClasses.contains('comment-mode')) {
|
|
282
283
|
if (currentHoveredTextElement) {
|
|
283
284
|
currentHoveredTextElement.classList.remove('text-editable-hover');
|
|
284
285
|
currentHoveredTextElement = null;
|
|
@@ -17,6 +17,7 @@ import {
|
|
|
17
17
|
} from '../../lib/firebase/storage';
|
|
18
18
|
import { buildDomTree } from '../utils/dom-utils';
|
|
19
19
|
import { findInsertionParent, findSlideRoot } from '../utils/slide-root';
|
|
20
|
+
import { debugLog } from '../utils/debug';
|
|
20
21
|
|
|
21
22
|
interface UploadResult {
|
|
22
23
|
storageUrl: string;
|
|
@@ -177,15 +178,15 @@ export function useImageUpload(options: UseImageUploadOptions = {}) {
|
|
|
177
178
|
clipboardData: DataTransfer,
|
|
178
179
|
insertOptions?: { x?: number; y?: number }
|
|
179
180
|
): Promise<UploadResult | null> => {
|
|
180
|
-
|
|
181
|
+
debugLog('[useImageUpload] uploadFromClipboard called');
|
|
181
182
|
// クリップボードから画像を探す
|
|
182
183
|
const items = Array.from(clipboardData.items);
|
|
183
|
-
|
|
184
|
+
debugLog('[useImageUpload] Clipboard items:', items.map(i => ({ type: i.type, kind: i.kind })));
|
|
184
185
|
|
|
185
186
|
for (const item of items) {
|
|
186
187
|
if (item.type.startsWith('image/')) {
|
|
187
188
|
const file = item.getAsFile();
|
|
188
|
-
|
|
189
|
+
debugLog('[useImageUpload] Got file from clipboard item:', file);
|
|
189
190
|
if (file) {
|
|
190
191
|
return uploadFromFile(file, insertOptions);
|
|
191
192
|
}
|
|
@@ -18,6 +18,7 @@ import {
|
|
|
18
18
|
import { extractElementInfo } from '../utils/style-utils';
|
|
19
19
|
import { MARQUEE_DRAG_THRESHOLD } from '../constants';
|
|
20
20
|
import type { MarqueeState } from '../types';
|
|
21
|
+
import { debugLog } from '../utils/debug';
|
|
21
22
|
|
|
22
23
|
interface UseMarqueeSelectionOptions {
|
|
23
24
|
/** マーキー選択の開始保留フラグ */
|
|
@@ -305,7 +306,7 @@ export function useMarqueeSelection(
|
|
|
305
306
|
window.postMessage({ type: 'ELEMENT_DESELECTED' }, '*');
|
|
306
307
|
}
|
|
307
308
|
|
|
308
|
-
|
|
309
|
+
debugLog(
|
|
309
310
|
'[Canvas] Marquee selected',
|
|
310
311
|
selectedIds.length,
|
|
311
312
|
additive ? '(additive)' : '',
|