@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
|
@@ -35,7 +35,7 @@ import { registerAutoSaveFlush, flushAutoSave, beginContentSwitch, endContentSwi
|
|
|
35
35
|
import { getCleanHtml } from './utils/html-utils';
|
|
36
36
|
import { EditorAppearanceContext, useEditorTheme } from './contexts/EditorAppearanceContext';
|
|
37
37
|
import { CanvasAppearance } from './components/shell/CanvasAppearance';
|
|
38
|
-
import { can, io } from '../io';
|
|
38
|
+
import { can, io, type CommentRect } from '../io';
|
|
39
39
|
import { insertIntoFlow } from './parts';
|
|
40
40
|
import type { Slide } from '../types/slide';
|
|
41
41
|
import {
|
|
@@ -93,6 +93,8 @@ import {
|
|
|
93
93
|
import { Input } from '../components/ui/input';
|
|
94
94
|
import { Button } from '../components/ui/button';
|
|
95
95
|
import { cn } from '../lib/utils';
|
|
96
|
+
import { clearPartDropIndicator } from './utils/drop-target';
|
|
97
|
+
import { debugLog } from './utils/debug';
|
|
96
98
|
|
|
97
99
|
/**
|
|
98
100
|
* 自動保存のデバウンス(ms)。
|
|
@@ -374,6 +376,11 @@ function FrontendVisualEditorInner({
|
|
|
374
376
|
const [pptFormatPaneOpen, setPptFormatPaneOpen] = useState(false);
|
|
375
377
|
const [pptCommentFocus, setPptCommentFocus] = useState(0);
|
|
376
378
|
const [pptActiveThread, setPptActiveThread] = useState<string | null>(null);
|
|
379
|
+
// コメントツール(C)で指定した範囲の下書き。投稿・解除・Esc・ページ切替で消える
|
|
380
|
+
const [commentDraftRect, setCommentDraftRect] = useState<CommentRect | null>(null);
|
|
381
|
+
const commentDraftRectRef = useRef<CommentRect | null>(null);
|
|
382
|
+
commentDraftRectRef.current = commentDraftRect;
|
|
383
|
+
useEffect(() => { setCommentDraftRect(null); }, [currentContentId]);
|
|
377
384
|
|
|
378
385
|
// CSS変数パネル状態
|
|
379
386
|
const [isVariablesPanelOpen, setIsVariablesPanelOpen] = useState(false);
|
|
@@ -411,7 +418,7 @@ function FrontendVisualEditorInner({
|
|
|
411
418
|
useEffect(() => {
|
|
412
419
|
// iframeが準備できていない場合は何もしない
|
|
413
420
|
if (!iframeReady) {
|
|
414
|
-
|
|
421
|
+
debugLog('[applyCssVariablesToIframe] Waiting for iframe to be ready...');
|
|
415
422
|
return;
|
|
416
423
|
}
|
|
417
424
|
if (!cssString) return;
|
|
@@ -433,9 +440,9 @@ function FrontendVisualEditorInner({
|
|
|
433
440
|
|
|
434
441
|
if (styleElement.textContent !== cssString) {
|
|
435
442
|
styleElement.textContent = cssString;
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
443
|
+
debugLog('[applyCssVariablesToIframe] Applied CSS variables:', variables.length, 'variables');
|
|
444
|
+
debugLog('[applyCssVariablesToIframe] CSS String:', cssString);
|
|
445
|
+
debugLog('[applyCssVariablesToIframe] Variables:', variables.map(v => ({ name: v.name, cssName: v.cssName, value: v.value })));
|
|
439
446
|
}
|
|
440
447
|
}, [cssString, variables.length, getIframeDoc, iframeReady]);
|
|
441
448
|
|
|
@@ -462,7 +469,7 @@ function FrontendVisualEditorInner({
|
|
|
462
469
|
? `/api/websites/${parentId}/pages/${contentId}`
|
|
463
470
|
: `/api/presentations/${parentId}/slides/${contentId}`;
|
|
464
471
|
await api.patch(apiPath, { layoutMode });
|
|
465
|
-
|
|
472
|
+
debugLog('[FrontendVisualEditor] Saved layout mode:', layoutMode);
|
|
466
473
|
} catch (error) {
|
|
467
474
|
console.error('[FrontendVisualEditor] Failed to save layout mode:', error);
|
|
468
475
|
}
|
|
@@ -1118,7 +1125,7 @@ function FrontendVisualEditorInner({
|
|
|
1118
1125
|
}
|
|
1119
1126
|
} else if (event.data?.type === 'IFRAME_PASTE_IMAGE') {
|
|
1120
1127
|
// iframe内でペーストされた画像を処理
|
|
1121
|
-
|
|
1128
|
+
debugLog('[FrontendVisualEditor] Received IFRAME_PASTE_IMAGE message', event.data);
|
|
1122
1129
|
const { dataUrl, fileName, fileType } = event.data;
|
|
1123
1130
|
if (dataUrl && fileType?.startsWith('image/')) {
|
|
1124
1131
|
try {
|
|
@@ -1133,12 +1140,14 @@ function FrontendVisualEditorInner({
|
|
|
1133
1140
|
} else if (event.data?.type === 'IFRAME_COMPONENT_DROP') {
|
|
1134
1141
|
// iframe内にドロップされたコンポーネントを処理
|
|
1135
1142
|
setIsDraggingOver(false);
|
|
1143
|
+
// 印は挿入より先に消す。残したまま挿入すると履歴と保存HTMLに混ざる
|
|
1144
|
+
clearPartDropIndicator(iframeRef.current?.contentDocument);
|
|
1136
1145
|
const { componentData, x, y } = event.data;
|
|
1137
|
-
|
|
1146
|
+
debugLog('[FrontendVisualEditor] Received IFRAME_COMPONENT_DROP:', componentData, 'at', x, y);
|
|
1138
1147
|
if (componentData && contentId) {
|
|
1139
1148
|
try {
|
|
1140
1149
|
const { componentId } = JSON.parse(componentData);
|
|
1141
|
-
|
|
1150
|
+
debugLog('[FrontendVisualEditor] Parsed componentId:', componentId);
|
|
1142
1151
|
if (componentId && partsMode) {
|
|
1143
1152
|
// 部品モード: 実体化してフローへ(iframe 内の座標がそのまま来る)
|
|
1144
1153
|
const iframeDoc = iframeRef.current?.contentDocument;
|
|
@@ -1170,11 +1179,11 @@ function FrontendVisualEditorInner({
|
|
|
1170
1179
|
} else if (componentId) {
|
|
1171
1180
|
// インスタンスを作成
|
|
1172
1181
|
const instance = createInstance(componentId, undefined, contentId);
|
|
1173
|
-
|
|
1182
|
+
debugLog('[FrontendVisualEditor] Created instance:', instance);
|
|
1174
1183
|
if (instance) {
|
|
1175
1184
|
// マスターコンポーネントを取得
|
|
1176
1185
|
const master = getMasterComponent(componentId);
|
|
1177
|
-
|
|
1186
|
+
debugLog('[FrontendVisualEditor] Master component:', master);
|
|
1178
1187
|
if (master && iframeRef.current) {
|
|
1179
1188
|
const iframeDoc = iframeRef.current.contentDocument;
|
|
1180
1189
|
if (iframeDoc) {
|
|
@@ -1202,7 +1211,7 @@ function FrontendVisualEditorInner({
|
|
|
1202
1211
|
element.classList.add('selected');
|
|
1203
1212
|
}
|
|
1204
1213
|
|
|
1205
|
-
|
|
1214
|
+
debugLog('[FrontendVisualEditor] Component instance created via iframe message:', instance.id);
|
|
1206
1215
|
}
|
|
1207
1216
|
} else {
|
|
1208
1217
|
console.error('[FrontendVisualEditor] Master component not found for id:', componentId);
|
|
@@ -1223,17 +1232,24 @@ function FrontendVisualEditorInner({
|
|
|
1223
1232
|
}, [uploadFromFile, contentId, createInstance, getMasterComponent, iframeRef, notifyIframeChange, setSelectedElement, setSelectedElementIds, partsMode, materializePartInstance, editorMode]);
|
|
1224
1233
|
|
|
1225
1234
|
// ドラッグ&ドロップハンドラ
|
|
1235
|
+
//
|
|
1236
|
+
// [不具合の修正] 以前は「部品のドラッグ」と「ファイルのドラッグ」を同じ条件で扱い、
|
|
1237
|
+
// 部品をパネルからキャンバスへ運ぶ間じゅう「ここに画像をドロップ」の青いオーバーレイが
|
|
1238
|
+
// 紙面を覆っていた。落とせる場所が分からなくなるうえ、画像を入れる操作だと誤解させる。
|
|
1239
|
+
// 画像用のオーバーレイは Files のときだけ出し、部品は紙面の中に出す挿入位置の横棒
|
|
1240
|
+
// (drop-target.ts の印)で示す。
|
|
1226
1241
|
const handleDragEnter = useCallback((e: React.DragEvent) => {
|
|
1227
1242
|
e.preventDefault();
|
|
1228
1243
|
e.stopPropagation();
|
|
1229
1244
|
|
|
1230
1245
|
dragCounterRef.current++;
|
|
1231
1246
|
|
|
1232
|
-
|
|
1233
|
-
if (e.dataTransfer.types.includes('application/x-editor-component') ||
|
|
1234
|
-
e.dataTransfer.types.includes('Files')) {
|
|
1247
|
+
if (e.dataTransfer.types.includes('Files')) {
|
|
1235
1248
|
setIsDraggingOver(true);
|
|
1236
1249
|
e.dataTransfer.dropEffect = 'copy';
|
|
1250
|
+
} else if (e.dataTransfer.types.includes('application/x-editor-component')) {
|
|
1251
|
+
// 落とせることは dropEffect で示すだけ。オーバーレイは出さない
|
|
1252
|
+
e.dataTransfer.dropEffect = 'copy';
|
|
1237
1253
|
}
|
|
1238
1254
|
}, []);
|
|
1239
1255
|
|
|
@@ -1241,7 +1257,6 @@ function FrontendVisualEditorInner({
|
|
|
1241
1257
|
e.preventDefault();
|
|
1242
1258
|
e.stopPropagation();
|
|
1243
1259
|
|
|
1244
|
-
// コンポーネントまたはファイルがドラッグされているか確認
|
|
1245
1260
|
if (e.dataTransfer.types.includes('application/x-editor-component') ||
|
|
1246
1261
|
e.dataTransfer.types.includes('Files')) {
|
|
1247
1262
|
e.dataTransfer.dropEffect = 'copy';
|
|
@@ -1251,14 +1266,17 @@ function FrontendVisualEditorInner({
|
|
|
1251
1266
|
const handleDragLeave = useCallback((e: React.DragEvent) => {
|
|
1252
1267
|
e.preventDefault();
|
|
1253
1268
|
e.stopPropagation();
|
|
1254
|
-
|
|
1269
|
+
|
|
1255
1270
|
dragCounterRef.current--;
|
|
1256
|
-
|
|
1271
|
+
|
|
1257
1272
|
// カウンターが0になったら本当に外に出た
|
|
1258
1273
|
if (dragCounterRef.current === 0) {
|
|
1259
1274
|
setIsDraggingOver(false);
|
|
1275
|
+
// 紙面の外に出たら挿入位置の印も消す(iframe 側の dragleave が
|
|
1276
|
+
// 来ないまま外へ抜ける経路があるため、ここでも必ず落とす)
|
|
1277
|
+
clearPartDropIndicator(iframeRef.current?.contentDocument);
|
|
1260
1278
|
}
|
|
1261
|
-
}, []);
|
|
1279
|
+
}, [iframeRef]);
|
|
1262
1280
|
|
|
1263
1281
|
const handleDrop = useCallback(async (e: React.DragEvent) => {
|
|
1264
1282
|
e.preventDefault();
|
|
@@ -1267,14 +1285,16 @@ function FrontendVisualEditorInner({
|
|
|
1267
1285
|
// ドラッグ状態をリセット
|
|
1268
1286
|
dragCounterRef.current = 0;
|
|
1269
1287
|
setIsDraggingOver(false);
|
|
1288
|
+
// 印は履歴に載る前に消す(notifyIframeChange より必ず先)
|
|
1289
|
+
clearPartDropIndicator(iframeRef.current?.contentDocument);
|
|
1270
1290
|
|
|
1271
1291
|
// コンポーネントのドロップをチェック
|
|
1272
1292
|
const componentData = e.dataTransfer.getData('application/x-editor-component');
|
|
1273
|
-
|
|
1293
|
+
debugLog('[FrontendVisualEditor] Drop event - componentData:', componentData);
|
|
1274
1294
|
if (componentData) {
|
|
1275
1295
|
try {
|
|
1276
1296
|
const { componentId, variantId } = JSON.parse(componentData);
|
|
1277
|
-
|
|
1297
|
+
debugLog('[FrontendVisualEditor] Parsed componentId:', componentId, 'variantId:', variantId, 'contentId:', contentId, 'iframeRef:', !!iframeRef.current);
|
|
1278
1298
|
if (componentId && iframeRef.current && contentId) {
|
|
1279
1299
|
const iframeDoc = iframeRef.current.contentDocument;
|
|
1280
1300
|
if (iframeDoc) {
|
|
@@ -1283,6 +1303,28 @@ function FrontendVisualEditorInner({
|
|
|
1283
1303
|
// 縮小表示されている。画面座標をそのまま使うとスライド左上を原点にできないので、
|
|
1284
1304
|
// スライドのルート要素の実測矩形を基準に、スライド内座標へ換算する。
|
|
1285
1305
|
const iframeRect = iframeRef.current.getBoundingClientRect();
|
|
1306
|
+
|
|
1307
|
+
// [不具合の修正] いま編集していないページの紙面に落としたときに、
|
|
1308
|
+
// 黙って現在ページの末尾へ入れてしまうのを止める。
|
|
1309
|
+
//
|
|
1310
|
+
// キャンバス表示では、紙面の枠(`[data-page-preview]`)の中の iframe に
|
|
1311
|
+
// `pointer-events: none` が掛かっている。そのため別ページの紙面に落としても
|
|
1312
|
+
// drop はその iframe ではなく親ドキュメント側に来て、この関数が受ける。
|
|
1313
|
+
// ここは「現在ページの iframe」を基準に座標を出すので、別ページの上で
|
|
1314
|
+
// 落とすと換算結果が紙面の外になり、挿入先が見つからず末尾送りになっていた。
|
|
1315
|
+
// 落とした場所と違うページに黙って入るのは気づけないので、入れずに捨てる。
|
|
1316
|
+
// (どのページに落としたかを見て、そのページを開いてから入れるのが本筋だが、
|
|
1317
|
+
// それはマルチフレーム側の担当なので、ここでは誤挿入を防ぐに留める)
|
|
1318
|
+
const insideLiveFrame =
|
|
1319
|
+
e.clientX >= iframeRect.left &&
|
|
1320
|
+
e.clientX <= iframeRect.right &&
|
|
1321
|
+
e.clientY >= iframeRect.top &&
|
|
1322
|
+
e.clientY <= iframeRect.bottom;
|
|
1323
|
+
if (!insideLiveFrame) {
|
|
1324
|
+
console.warn('[FrontendVisualEditor] いま編集しているページの外に落とされたため、部品を入れませんでした');
|
|
1325
|
+
return;
|
|
1326
|
+
}
|
|
1327
|
+
|
|
1286
1328
|
// 親の座標 → iframe の座標。iframe が外側の transform で縮んでいる
|
|
1287
1329
|
// (マルチフレーム)ときは実測の倍率で割る。単独表示では 1
|
|
1288
1330
|
const outerScale = iframeRect.width / (iframeRef.current.clientWidth || iframeRect.width) || 1;
|
|
@@ -1336,7 +1378,7 @@ function FrontendVisualEditorInner({
|
|
|
1336
1378
|
|
|
1337
1379
|
// インスタンスを作成(バリアントIDが指定されていれば使用)
|
|
1338
1380
|
// Pass position directly to createInstance to avoid React state batching race condition
|
|
1339
|
-
|
|
1381
|
+
debugLog('[FrontendVisualEditor] Creating instance for component:', componentId, 'variant:', variantId);
|
|
1340
1382
|
const instance = createInstance(
|
|
1341
1383
|
componentId,
|
|
1342
1384
|
variantId,
|
|
@@ -1347,11 +1389,11 @@ function FrontendVisualEditorInner({
|
|
|
1347
1389
|
undefined, // initialPropertyValues
|
|
1348
1390
|
{ x, y } // initialPosition
|
|
1349
1391
|
);
|
|
1350
|
-
|
|
1392
|
+
debugLog('[FrontendVisualEditor] Created instance:', instance);
|
|
1351
1393
|
if (instance) {
|
|
1352
1394
|
// マスターコンポーネントを取得
|
|
1353
1395
|
const master = getMasterComponent(componentId);
|
|
1354
|
-
|
|
1396
|
+
debugLog('[FrontendVisualEditor] Master component:', master);
|
|
1355
1397
|
if (master) {
|
|
1356
1398
|
// インスタンスをHTMLにレンダリング (position will be applied from instance.position)
|
|
1357
1399
|
const { renderInstance } = await import('./utils/component-renderer');
|
|
@@ -1374,7 +1416,7 @@ function FrontendVisualEditorInner({
|
|
|
1374
1416
|
element.classList.add('selected');
|
|
1375
1417
|
}
|
|
1376
1418
|
|
|
1377
|
-
|
|
1419
|
+
debugLog('[FrontendVisualEditor] Component instance created:', instance.id);
|
|
1378
1420
|
} else {
|
|
1379
1421
|
console.error('[FrontendVisualEditor] Master component not found for id:', componentId);
|
|
1380
1422
|
}
|
|
@@ -1422,7 +1464,7 @@ function FrontendVisualEditorInner({
|
|
|
1422
1464
|
const handlePaste = useCallback(async (e: Event) => {
|
|
1423
1465
|
if (handledPasteRef.current.has(e)) return;
|
|
1424
1466
|
handledPasteRef.current.add(e);
|
|
1425
|
-
|
|
1467
|
+
debugLog('[FrontendVisualEditor] handlePaste triggered');
|
|
1426
1468
|
const clipboardEvent = e as ClipboardEvent;
|
|
1427
1469
|
|
|
1428
1470
|
// テキスト編集中は通常のペーストを許可
|
|
@@ -1473,14 +1515,14 @@ function FrontendVisualEditorInner({
|
|
|
1473
1515
|
}
|
|
1474
1516
|
|
|
1475
1517
|
const items = Array.from(clipboardEvent.clipboardData.items);
|
|
1476
|
-
|
|
1518
|
+
debugLog('[FrontendVisualEditor] Clipboard items:', items.map(i => ({ type: i.type, kind: i.kind })));
|
|
1477
1519
|
|
|
1478
1520
|
// デバッグ: クリップボードの内容を詳細ログ
|
|
1479
1521
|
debugClipboard(clipboardEvent.clipboardData);
|
|
1480
1522
|
|
|
1481
1523
|
// コンテンツタイプを最初に判定(Excel/Wordは画像より優先)
|
|
1482
1524
|
const contentType = detectContentType(clipboardEvent.clipboardData);
|
|
1483
|
-
|
|
1525
|
+
debugLog('[FrontendVisualEditor] Detected content type:', contentType);
|
|
1484
1526
|
|
|
1485
1527
|
// 画像の場合(純粋な画像ペーストのみ、Excel/Wordは除外済み)
|
|
1486
1528
|
if (contentType === 'image') {
|
|
@@ -1512,7 +1554,7 @@ function FrontendVisualEditorInner({
|
|
|
1512
1554
|
}
|
|
1513
1555
|
}
|
|
1514
1556
|
|
|
1515
|
-
|
|
1557
|
+
debugLog('[FrontendVisualEditor] Calling uploadFromClipboard for image');
|
|
1516
1558
|
await uploadFromClipboard(clipboardEvent.clipboardData, { x: 100, y: 100 });
|
|
1517
1559
|
return;
|
|
1518
1560
|
}
|
|
@@ -1521,44 +1563,44 @@ function FrontendVisualEditorInner({
|
|
|
1521
1563
|
if (canHandleRichPaste(clipboardEvent.clipboardData)) {
|
|
1522
1564
|
e.preventDefault();
|
|
1523
1565
|
e.stopPropagation();
|
|
1524
|
-
|
|
1566
|
+
debugLog('[FrontendVisualEditor] Calling pasteRichContent for:', contentType);
|
|
1525
1567
|
const result = await pasteRichContent(clipboardEvent.clipboardData);
|
|
1526
|
-
|
|
1568
|
+
debugLog('[FrontendVisualEditor] Rich paste result:', result);
|
|
1527
1569
|
|
|
1528
1570
|
// Figmaなどでデコード失敗した場合は画像としてフォールバック
|
|
1529
1571
|
if (result.shouldFallbackToImage) {
|
|
1530
|
-
|
|
1572
|
+
debugLog('[FrontendVisualEditor] Rich paste failed, falling back to image upload');
|
|
1531
1573
|
const imageResult = await uploadFromClipboard(clipboardEvent.clipboardData, { x: 100, y: 100 });
|
|
1532
1574
|
|
|
1533
1575
|
// 画像もない場合はプレーンテキストとしてペースト
|
|
1534
1576
|
if (!imageResult) {
|
|
1535
|
-
|
|
1577
|
+
debugLog('[FrontendVisualEditor] No image found, falling back to plain text');
|
|
1536
1578
|
const plainText = clipboardEvent.clipboardData.getData('text/plain');
|
|
1537
1579
|
if (plainText) {
|
|
1538
1580
|
// プレーンテキストとして強制的に処理
|
|
1539
1581
|
const textResult = await pasteRichContent(clipboardEvent.clipboardData, 'plain-text');
|
|
1540
|
-
|
|
1582
|
+
debugLog('[FrontendVisualEditor] Plain text paste result:', textResult);
|
|
1541
1583
|
}
|
|
1542
1584
|
}
|
|
1543
1585
|
}
|
|
1544
1586
|
return;
|
|
1545
1587
|
}
|
|
1546
1588
|
} else {
|
|
1547
|
-
|
|
1589
|
+
debugLog('[FrontendVisualEditor] No clipboardData available');
|
|
1548
1590
|
}
|
|
1549
1591
|
}, [getIframeDoc, uploadFromClipboard, canHandleRichPaste, pasteRichContent, debugClipboard, detectContentType]);
|
|
1550
1592
|
|
|
1551
1593
|
// メインドキュメントとiframe両方にペーストイベントを登録
|
|
1552
1594
|
useEffect(() => {
|
|
1553
|
-
|
|
1595
|
+
debugLog('[FrontendVisualEditor] Registering paste event listeners');
|
|
1554
1596
|
|
|
1555
1597
|
// デバッグ用:ウィンドウレベルでキャプチャフェーズでリスナー追加
|
|
1556
1598
|
const debugPasteHandler = (e: ClipboardEvent) => {
|
|
1557
|
-
|
|
1558
|
-
|
|
1559
|
-
|
|
1599
|
+
debugLog('[FrontendVisualEditor] Window paste event captured (capture phase)', e);
|
|
1600
|
+
debugLog('[FrontendVisualEditor] Target:', e.target);
|
|
1601
|
+
debugLog('[FrontendVisualEditor] ClipboardData:', e.clipboardData);
|
|
1560
1602
|
if (e.clipboardData) {
|
|
1561
|
-
|
|
1603
|
+
debugLog('[FrontendVisualEditor] Items:', Array.from(e.clipboardData.items).map(i => ({ type: i.type, kind: i.kind })));
|
|
1562
1604
|
}
|
|
1563
1605
|
};
|
|
1564
1606
|
window.addEventListener('paste', debugPasteHandler, true); // capture phase
|
|
@@ -1672,6 +1714,14 @@ function FrontendVisualEditorInner({
|
|
|
1672
1714
|
// Scaleツールなど特定のツールを使用中の場合、選択ツールに戻す
|
|
1673
1715
|
if (activeTool !== 'select') {
|
|
1674
1716
|
setActiveTool('select');
|
|
1717
|
+
// コメントツールを抜けるだけ(下書きは次の Esc で消す = 1 段ずつ戻る)
|
|
1718
|
+
if (activeTool === 'comment') return;
|
|
1719
|
+
}
|
|
1720
|
+
|
|
1721
|
+
// コメントの範囲の下書きがあれば、それを解除して終わり
|
|
1722
|
+
if (commentDraftRectRef.current) {
|
|
1723
|
+
setCommentDraftRect(null);
|
|
1724
|
+
return;
|
|
1675
1725
|
}
|
|
1676
1726
|
|
|
1677
1727
|
if (iframeDoc) {
|
|
@@ -1863,6 +1913,8 @@ function FrontendVisualEditorInner({
|
|
|
1863
1913
|
eraser: () => setActiveTool('eraser'),
|
|
1864
1914
|
text: () => setActiveTool('text'),
|
|
1865
1915
|
frame: () => setActiveTool('frame'),
|
|
1916
|
+
// コメントの範囲指定(コメント機能があるホストだけ)
|
|
1917
|
+
comment: can('commentAction') ? () => setActiveTool('comment') : undefined,
|
|
1866
1918
|
// 編集操作
|
|
1867
1919
|
// undo/redo は常に生やす(履歴が無いときは内部で何もしない)。
|
|
1868
1920
|
// 条件付きで undefined にするとブラウザ既定の取り消しが走ってしまう。
|
|
@@ -2022,6 +2074,7 @@ function FrontendVisualEditorInner({
|
|
|
2022
2074
|
setPptCommentsOpen(true);
|
|
2023
2075
|
setPptCommentFocus((n) => n + 1);
|
|
2024
2076
|
},
|
|
2077
|
+
pickRegion: () => setActiveTool('comment'),
|
|
2025
2078
|
}}
|
|
2026
2079
|
/>
|
|
2027
2080
|
</>
|
|
@@ -2172,6 +2225,7 @@ function FrontendVisualEditorInner({
|
|
|
2172
2225
|
onImageUpload={() => openFilePicker({ x: 100, y: 100 })}
|
|
2173
2226
|
onOpenMediaLibrary={can('apiFetch') ? () => setIsMediaLibraryOpen(true) : undefined}
|
|
2174
2227
|
isMediaReplaceMode={isImageSelected}
|
|
2228
|
+
canComment={can('commentAction')}
|
|
2175
2229
|
onAiRegenerate={can('apiFetch') && selectedElement && selectedElementIds.length <= 1 ? openAiPrompt : undefined}
|
|
2176
2230
|
onOpenVariables={() => setIsVariablesPanelOpen(true)}
|
|
2177
2231
|
hasVariables={hasVariables}
|
|
@@ -2218,9 +2272,22 @@ function FrontendVisualEditorInner({
|
|
|
2218
2272
|
<PptCommentMarkers
|
|
2219
2273
|
page={Number(currentContentId ?? contentId) || 1}
|
|
2220
2274
|
onOpenThread={(id) => {
|
|
2275
|
+
setPptFormatPaneOpen(false);
|
|
2221
2276
|
setPptCommentsOpen(true);
|
|
2222
2277
|
setPptActiveThread(id);
|
|
2223
2278
|
}}
|
|
2279
|
+
activeThreadId={pptCommentsOpen ? pptActiveThread : null}
|
|
2280
|
+
draftRect={commentDraftRect}
|
|
2281
|
+
regionActive={activeTool === 'comment'}
|
|
2282
|
+
onRegion={(rect) => {
|
|
2283
|
+
// 範囲が決まったら選択ツールに戻し、パネルを開いて本文へフォーカス
|
|
2284
|
+
setCommentDraftRect(rect);
|
|
2285
|
+
setActiveTool('select');
|
|
2286
|
+
setPptFormatPaneOpen(false);
|
|
2287
|
+
setPptCommentsOpen(true);
|
|
2288
|
+
setPptActiveThread(null);
|
|
2289
|
+
setPptCommentFocus((n) => n + 1);
|
|
2290
|
+
}}
|
|
2224
2291
|
/>
|
|
2225
2292
|
)}
|
|
2226
2293
|
{isPpt && pptFormatPaneOpen && (
|
|
@@ -2235,6 +2302,9 @@ function FrontendVisualEditorInner({
|
|
|
2235
2302
|
focusSignal={pptCommentFocus}
|
|
2236
2303
|
activeThreadId={pptActiveThread}
|
|
2237
2304
|
onActiveThread={setPptActiveThread}
|
|
2305
|
+
pendingRect={commentDraftRect}
|
|
2306
|
+
onClearPendingRect={() => setCommentDraftRect(null)}
|
|
2307
|
+
onPickRegion={() => setActiveTool('comment')}
|
|
2238
2308
|
/>
|
|
2239
2309
|
</div>
|
|
2240
2310
|
)}
|
|
@@ -2512,7 +2582,7 @@ function FrontendVisualEditorInner({
|
|
|
2512
2582
|
|
|
2513
2583
|
// 絶対配置に変換(トップレベルでimport済み)
|
|
2514
2584
|
const count = convertToAbsolutePositioning(iframeDoc);
|
|
2515
|
-
|
|
2585
|
+
debugLog('[Toast] Converted', count, 'elements to absolute positioning');
|
|
2516
2586
|
|
|
2517
2587
|
// DOMツリーを再構築
|
|
2518
2588
|
const tree = buildDomTree(iframeDoc);
|
|
@@ -2628,7 +2698,7 @@ export function FrontendVisualEditor({
|
|
|
2628
2698
|
? await fromIo()
|
|
2629
2699
|
: await getCSSVariablesList(effectiveParentId!, scope);
|
|
2630
2700
|
setInitialVariables(variables);
|
|
2631
|
-
|
|
2701
|
+
debugLog(`[FrontendVisualEditor] Loaded CSS variables (${fromIo ? 'io' : scope}):`, variables.length);
|
|
2632
2702
|
} catch (error) {
|
|
2633
2703
|
console.error('[FrontendVisualEditor] Failed to load CSS variables:', error);
|
|
2634
2704
|
} finally {
|
|
@@ -2644,7 +2714,7 @@ export function FrontendVisualEditor({
|
|
|
2644
2714
|
const toIo = io().saveVariables;
|
|
2645
2715
|
if (toIo) {
|
|
2646
2716
|
await toIo(variables);
|
|
2647
|
-
|
|
2717
|
+
debugLog('[FrontendVisualEditor] Saved CSS variables (io):', variables.length);
|
|
2648
2718
|
return;
|
|
2649
2719
|
}
|
|
2650
2720
|
if (!effectiveParentId) {
|
|
@@ -2653,7 +2723,7 @@ export function FrontendVisualEditor({
|
|
|
2653
2723
|
// editorModeに応じてスコープを決定
|
|
2654
2724
|
const scope: CSSVariableScope = editorMode === 'webpage' ? 'website' : 'presentation';
|
|
2655
2725
|
await saveCSSVariables(effectiveParentId, variables, { scope });
|
|
2656
|
-
|
|
2726
|
+
debugLog(`[FrontendVisualEditor] Saved CSS variables (${scope}):`, variables.length);
|
|
2657
2727
|
}, [effectiveParentId, editorMode]);
|
|
2658
2728
|
|
|
2659
2729
|
// CSS変数をロードするコールバック
|