@growgroup/visual-editor 0.3.1 → 0.4.1
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 +123 -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 +179 -77
- package/src/editor/components/multi-page/PageFramePreview.tsx +99 -21
- 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 +194 -26
- 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 +43 -11
- 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)。
|
|
@@ -372,8 +374,22 @@ function FrontendVisualEditorInner({
|
|
|
372
374
|
useEffect(() => { if (pptCommentsOpen) setCommentsMounted(true); }, [pptCommentsOpen]);
|
|
373
375
|
// PowerPoint風UIの右ペイン「図の書式設定」(影・反射・光彩・ぼかし)
|
|
374
376
|
const [pptFormatPaneOpen, setPptFormatPaneOpen] = useState(false);
|
|
377
|
+
// コメントツール(C)にした時点で右のコメントパネルも開く。範囲を描き終えるまで開かないと、
|
|
378
|
+
// 書き込む先が見えないまま範囲を引くことになる。入口(ツールバー・校閲タブ・
|
|
379
|
+
// パネルの「範囲を指定」・キー C)はどれも activeTool を変えるだけなので、ここ 1 か所で拾う。
|
|
380
|
+
// ツールを抜けても閉じない(閉じるのは利用者の操作)。開いているときは state が同じ値で止まる
|
|
381
|
+
useEffect(() => {
|
|
382
|
+
if (activeTool !== 'comment' || !can('commentAction')) return;
|
|
383
|
+
setPptFormatPaneOpen(false);
|
|
384
|
+
setPptCommentsOpen(true);
|
|
385
|
+
}, [activeTool]);
|
|
375
386
|
const [pptCommentFocus, setPptCommentFocus] = useState(0);
|
|
376
387
|
const [pptActiveThread, setPptActiveThread] = useState<string | null>(null);
|
|
388
|
+
// コメントツール(C)で指定した範囲の下書き。投稿・解除・Esc・ページ切替で消える
|
|
389
|
+
const [commentDraftRect, setCommentDraftRect] = useState<CommentRect | null>(null);
|
|
390
|
+
const commentDraftRectRef = useRef<CommentRect | null>(null);
|
|
391
|
+
commentDraftRectRef.current = commentDraftRect;
|
|
392
|
+
useEffect(() => { setCommentDraftRect(null); }, [currentContentId]);
|
|
377
393
|
|
|
378
394
|
// CSS変数パネル状態
|
|
379
395
|
const [isVariablesPanelOpen, setIsVariablesPanelOpen] = useState(false);
|
|
@@ -411,7 +427,7 @@ function FrontendVisualEditorInner({
|
|
|
411
427
|
useEffect(() => {
|
|
412
428
|
// iframeが準備できていない場合は何もしない
|
|
413
429
|
if (!iframeReady) {
|
|
414
|
-
|
|
430
|
+
debugLog('[applyCssVariablesToIframe] Waiting for iframe to be ready...');
|
|
415
431
|
return;
|
|
416
432
|
}
|
|
417
433
|
if (!cssString) return;
|
|
@@ -433,9 +449,9 @@ function FrontendVisualEditorInner({
|
|
|
433
449
|
|
|
434
450
|
if (styleElement.textContent !== cssString) {
|
|
435
451
|
styleElement.textContent = cssString;
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
452
|
+
debugLog('[applyCssVariablesToIframe] Applied CSS variables:', variables.length, 'variables');
|
|
453
|
+
debugLog('[applyCssVariablesToIframe] CSS String:', cssString);
|
|
454
|
+
debugLog('[applyCssVariablesToIframe] Variables:', variables.map(v => ({ name: v.name, cssName: v.cssName, value: v.value })));
|
|
439
455
|
}
|
|
440
456
|
}, [cssString, variables.length, getIframeDoc, iframeReady]);
|
|
441
457
|
|
|
@@ -462,7 +478,7 @@ function FrontendVisualEditorInner({
|
|
|
462
478
|
? `/api/websites/${parentId}/pages/${contentId}`
|
|
463
479
|
: `/api/presentations/${parentId}/slides/${contentId}`;
|
|
464
480
|
await api.patch(apiPath, { layoutMode });
|
|
465
|
-
|
|
481
|
+
debugLog('[FrontendVisualEditor] Saved layout mode:', layoutMode);
|
|
466
482
|
} catch (error) {
|
|
467
483
|
console.error('[FrontendVisualEditor] Failed to save layout mode:', error);
|
|
468
484
|
}
|
|
@@ -1118,7 +1134,7 @@ function FrontendVisualEditorInner({
|
|
|
1118
1134
|
}
|
|
1119
1135
|
} else if (event.data?.type === 'IFRAME_PASTE_IMAGE') {
|
|
1120
1136
|
// iframe内でペーストされた画像を処理
|
|
1121
|
-
|
|
1137
|
+
debugLog('[FrontendVisualEditor] Received IFRAME_PASTE_IMAGE message', event.data);
|
|
1122
1138
|
const { dataUrl, fileName, fileType } = event.data;
|
|
1123
1139
|
if (dataUrl && fileType?.startsWith('image/')) {
|
|
1124
1140
|
try {
|
|
@@ -1133,12 +1149,14 @@ function FrontendVisualEditorInner({
|
|
|
1133
1149
|
} else if (event.data?.type === 'IFRAME_COMPONENT_DROP') {
|
|
1134
1150
|
// iframe内にドロップされたコンポーネントを処理
|
|
1135
1151
|
setIsDraggingOver(false);
|
|
1152
|
+
// 印は挿入より先に消す。残したまま挿入すると履歴と保存HTMLに混ざる
|
|
1153
|
+
clearPartDropIndicator(iframeRef.current?.contentDocument);
|
|
1136
1154
|
const { componentData, x, y } = event.data;
|
|
1137
|
-
|
|
1155
|
+
debugLog('[FrontendVisualEditor] Received IFRAME_COMPONENT_DROP:', componentData, 'at', x, y);
|
|
1138
1156
|
if (componentData && contentId) {
|
|
1139
1157
|
try {
|
|
1140
1158
|
const { componentId } = JSON.parse(componentData);
|
|
1141
|
-
|
|
1159
|
+
debugLog('[FrontendVisualEditor] Parsed componentId:', componentId);
|
|
1142
1160
|
if (componentId && partsMode) {
|
|
1143
1161
|
// 部品モード: 実体化してフローへ(iframe 内の座標がそのまま来る)
|
|
1144
1162
|
const iframeDoc = iframeRef.current?.contentDocument;
|
|
@@ -1170,11 +1188,11 @@ function FrontendVisualEditorInner({
|
|
|
1170
1188
|
} else if (componentId) {
|
|
1171
1189
|
// インスタンスを作成
|
|
1172
1190
|
const instance = createInstance(componentId, undefined, contentId);
|
|
1173
|
-
|
|
1191
|
+
debugLog('[FrontendVisualEditor] Created instance:', instance);
|
|
1174
1192
|
if (instance) {
|
|
1175
1193
|
// マスターコンポーネントを取得
|
|
1176
1194
|
const master = getMasterComponent(componentId);
|
|
1177
|
-
|
|
1195
|
+
debugLog('[FrontendVisualEditor] Master component:', master);
|
|
1178
1196
|
if (master && iframeRef.current) {
|
|
1179
1197
|
const iframeDoc = iframeRef.current.contentDocument;
|
|
1180
1198
|
if (iframeDoc) {
|
|
@@ -1202,7 +1220,7 @@ function FrontendVisualEditorInner({
|
|
|
1202
1220
|
element.classList.add('selected');
|
|
1203
1221
|
}
|
|
1204
1222
|
|
|
1205
|
-
|
|
1223
|
+
debugLog('[FrontendVisualEditor] Component instance created via iframe message:', instance.id);
|
|
1206
1224
|
}
|
|
1207
1225
|
} else {
|
|
1208
1226
|
console.error('[FrontendVisualEditor] Master component not found for id:', componentId);
|
|
@@ -1223,17 +1241,24 @@ function FrontendVisualEditorInner({
|
|
|
1223
1241
|
}, [uploadFromFile, contentId, createInstance, getMasterComponent, iframeRef, notifyIframeChange, setSelectedElement, setSelectedElementIds, partsMode, materializePartInstance, editorMode]);
|
|
1224
1242
|
|
|
1225
1243
|
// ドラッグ&ドロップハンドラ
|
|
1244
|
+
//
|
|
1245
|
+
// [不具合の修正] 以前は「部品のドラッグ」と「ファイルのドラッグ」を同じ条件で扱い、
|
|
1246
|
+
// 部品をパネルからキャンバスへ運ぶ間じゅう「ここに画像をドロップ」の青いオーバーレイが
|
|
1247
|
+
// 紙面を覆っていた。落とせる場所が分からなくなるうえ、画像を入れる操作だと誤解させる。
|
|
1248
|
+
// 画像用のオーバーレイは Files のときだけ出し、部品は紙面の中に出す挿入位置の横棒
|
|
1249
|
+
// (drop-target.ts の印)で示す。
|
|
1226
1250
|
const handleDragEnter = useCallback((e: React.DragEvent) => {
|
|
1227
1251
|
e.preventDefault();
|
|
1228
1252
|
e.stopPropagation();
|
|
1229
1253
|
|
|
1230
1254
|
dragCounterRef.current++;
|
|
1231
1255
|
|
|
1232
|
-
|
|
1233
|
-
if (e.dataTransfer.types.includes('application/x-editor-component') ||
|
|
1234
|
-
e.dataTransfer.types.includes('Files')) {
|
|
1256
|
+
if (e.dataTransfer.types.includes('Files')) {
|
|
1235
1257
|
setIsDraggingOver(true);
|
|
1236
1258
|
e.dataTransfer.dropEffect = 'copy';
|
|
1259
|
+
} else if (e.dataTransfer.types.includes('application/x-editor-component')) {
|
|
1260
|
+
// 落とせることは dropEffect で示すだけ。オーバーレイは出さない
|
|
1261
|
+
e.dataTransfer.dropEffect = 'copy';
|
|
1237
1262
|
}
|
|
1238
1263
|
}, []);
|
|
1239
1264
|
|
|
@@ -1241,7 +1266,6 @@ function FrontendVisualEditorInner({
|
|
|
1241
1266
|
e.preventDefault();
|
|
1242
1267
|
e.stopPropagation();
|
|
1243
1268
|
|
|
1244
|
-
// コンポーネントまたはファイルがドラッグされているか確認
|
|
1245
1269
|
if (e.dataTransfer.types.includes('application/x-editor-component') ||
|
|
1246
1270
|
e.dataTransfer.types.includes('Files')) {
|
|
1247
1271
|
e.dataTransfer.dropEffect = 'copy';
|
|
@@ -1251,14 +1275,17 @@ function FrontendVisualEditorInner({
|
|
|
1251
1275
|
const handleDragLeave = useCallback((e: React.DragEvent) => {
|
|
1252
1276
|
e.preventDefault();
|
|
1253
1277
|
e.stopPropagation();
|
|
1254
|
-
|
|
1278
|
+
|
|
1255
1279
|
dragCounterRef.current--;
|
|
1256
|
-
|
|
1280
|
+
|
|
1257
1281
|
// カウンターが0になったら本当に外に出た
|
|
1258
1282
|
if (dragCounterRef.current === 0) {
|
|
1259
1283
|
setIsDraggingOver(false);
|
|
1284
|
+
// 紙面の外に出たら挿入位置の印も消す(iframe 側の dragleave が
|
|
1285
|
+
// 来ないまま外へ抜ける経路があるため、ここでも必ず落とす)
|
|
1286
|
+
clearPartDropIndicator(iframeRef.current?.contentDocument);
|
|
1260
1287
|
}
|
|
1261
|
-
}, []);
|
|
1288
|
+
}, [iframeRef]);
|
|
1262
1289
|
|
|
1263
1290
|
const handleDrop = useCallback(async (e: React.DragEvent) => {
|
|
1264
1291
|
e.preventDefault();
|
|
@@ -1267,14 +1294,16 @@ function FrontendVisualEditorInner({
|
|
|
1267
1294
|
// ドラッグ状態をリセット
|
|
1268
1295
|
dragCounterRef.current = 0;
|
|
1269
1296
|
setIsDraggingOver(false);
|
|
1297
|
+
// 印は履歴に載る前に消す(notifyIframeChange より必ず先)
|
|
1298
|
+
clearPartDropIndicator(iframeRef.current?.contentDocument);
|
|
1270
1299
|
|
|
1271
1300
|
// コンポーネントのドロップをチェック
|
|
1272
1301
|
const componentData = e.dataTransfer.getData('application/x-editor-component');
|
|
1273
|
-
|
|
1302
|
+
debugLog('[FrontendVisualEditor] Drop event - componentData:', componentData);
|
|
1274
1303
|
if (componentData) {
|
|
1275
1304
|
try {
|
|
1276
1305
|
const { componentId, variantId } = JSON.parse(componentData);
|
|
1277
|
-
|
|
1306
|
+
debugLog('[FrontendVisualEditor] Parsed componentId:', componentId, 'variantId:', variantId, 'contentId:', contentId, 'iframeRef:', !!iframeRef.current);
|
|
1278
1307
|
if (componentId && iframeRef.current && contentId) {
|
|
1279
1308
|
const iframeDoc = iframeRef.current.contentDocument;
|
|
1280
1309
|
if (iframeDoc) {
|
|
@@ -1283,6 +1312,28 @@ function FrontendVisualEditorInner({
|
|
|
1283
1312
|
// 縮小表示されている。画面座標をそのまま使うとスライド左上を原点にできないので、
|
|
1284
1313
|
// スライドのルート要素の実測矩形を基準に、スライド内座標へ換算する。
|
|
1285
1314
|
const iframeRect = iframeRef.current.getBoundingClientRect();
|
|
1315
|
+
|
|
1316
|
+
// [不具合の修正] いま編集していないページの紙面に落としたときに、
|
|
1317
|
+
// 黙って現在ページの末尾へ入れてしまうのを止める。
|
|
1318
|
+
//
|
|
1319
|
+
// キャンバス表示では、紙面の枠(`[data-page-preview]`)の中の iframe に
|
|
1320
|
+
// `pointer-events: none` が掛かっている。そのため別ページの紙面に落としても
|
|
1321
|
+
// drop はその iframe ではなく親ドキュメント側に来て、この関数が受ける。
|
|
1322
|
+
// ここは「現在ページの iframe」を基準に座標を出すので、別ページの上で
|
|
1323
|
+
// 落とすと換算結果が紙面の外になり、挿入先が見つからず末尾送りになっていた。
|
|
1324
|
+
// 落とした場所と違うページに黙って入るのは気づけないので、入れずに捨てる。
|
|
1325
|
+
// (どのページに落としたかを見て、そのページを開いてから入れるのが本筋だが、
|
|
1326
|
+
// それはマルチフレーム側の担当なので、ここでは誤挿入を防ぐに留める)
|
|
1327
|
+
const insideLiveFrame =
|
|
1328
|
+
e.clientX >= iframeRect.left &&
|
|
1329
|
+
e.clientX <= iframeRect.right &&
|
|
1330
|
+
e.clientY >= iframeRect.top &&
|
|
1331
|
+
e.clientY <= iframeRect.bottom;
|
|
1332
|
+
if (!insideLiveFrame) {
|
|
1333
|
+
console.warn('[FrontendVisualEditor] いま編集しているページの外に落とされたため、部品を入れませんでした');
|
|
1334
|
+
return;
|
|
1335
|
+
}
|
|
1336
|
+
|
|
1286
1337
|
// 親の座標 → iframe の座標。iframe が外側の transform で縮んでいる
|
|
1287
1338
|
// (マルチフレーム)ときは実測の倍率で割る。単独表示では 1
|
|
1288
1339
|
const outerScale = iframeRect.width / (iframeRef.current.clientWidth || iframeRect.width) || 1;
|
|
@@ -1336,7 +1387,7 @@ function FrontendVisualEditorInner({
|
|
|
1336
1387
|
|
|
1337
1388
|
// インスタンスを作成(バリアントIDが指定されていれば使用)
|
|
1338
1389
|
// Pass position directly to createInstance to avoid React state batching race condition
|
|
1339
|
-
|
|
1390
|
+
debugLog('[FrontendVisualEditor] Creating instance for component:', componentId, 'variant:', variantId);
|
|
1340
1391
|
const instance = createInstance(
|
|
1341
1392
|
componentId,
|
|
1342
1393
|
variantId,
|
|
@@ -1347,11 +1398,11 @@ function FrontendVisualEditorInner({
|
|
|
1347
1398
|
undefined, // initialPropertyValues
|
|
1348
1399
|
{ x, y } // initialPosition
|
|
1349
1400
|
);
|
|
1350
|
-
|
|
1401
|
+
debugLog('[FrontendVisualEditor] Created instance:', instance);
|
|
1351
1402
|
if (instance) {
|
|
1352
1403
|
// マスターコンポーネントを取得
|
|
1353
1404
|
const master = getMasterComponent(componentId);
|
|
1354
|
-
|
|
1405
|
+
debugLog('[FrontendVisualEditor] Master component:', master);
|
|
1355
1406
|
if (master) {
|
|
1356
1407
|
// インスタンスをHTMLにレンダリング (position will be applied from instance.position)
|
|
1357
1408
|
const { renderInstance } = await import('./utils/component-renderer');
|
|
@@ -1374,7 +1425,7 @@ function FrontendVisualEditorInner({
|
|
|
1374
1425
|
element.classList.add('selected');
|
|
1375
1426
|
}
|
|
1376
1427
|
|
|
1377
|
-
|
|
1428
|
+
debugLog('[FrontendVisualEditor] Component instance created:', instance.id);
|
|
1378
1429
|
} else {
|
|
1379
1430
|
console.error('[FrontendVisualEditor] Master component not found for id:', componentId);
|
|
1380
1431
|
}
|
|
@@ -1422,7 +1473,7 @@ function FrontendVisualEditorInner({
|
|
|
1422
1473
|
const handlePaste = useCallback(async (e: Event) => {
|
|
1423
1474
|
if (handledPasteRef.current.has(e)) return;
|
|
1424
1475
|
handledPasteRef.current.add(e);
|
|
1425
|
-
|
|
1476
|
+
debugLog('[FrontendVisualEditor] handlePaste triggered');
|
|
1426
1477
|
const clipboardEvent = e as ClipboardEvent;
|
|
1427
1478
|
|
|
1428
1479
|
// テキスト編集中は通常のペーストを許可
|
|
@@ -1473,14 +1524,14 @@ function FrontendVisualEditorInner({
|
|
|
1473
1524
|
}
|
|
1474
1525
|
|
|
1475
1526
|
const items = Array.from(clipboardEvent.clipboardData.items);
|
|
1476
|
-
|
|
1527
|
+
debugLog('[FrontendVisualEditor] Clipboard items:', items.map(i => ({ type: i.type, kind: i.kind })));
|
|
1477
1528
|
|
|
1478
1529
|
// デバッグ: クリップボードの内容を詳細ログ
|
|
1479
1530
|
debugClipboard(clipboardEvent.clipboardData);
|
|
1480
1531
|
|
|
1481
1532
|
// コンテンツタイプを最初に判定(Excel/Wordは画像より優先)
|
|
1482
1533
|
const contentType = detectContentType(clipboardEvent.clipboardData);
|
|
1483
|
-
|
|
1534
|
+
debugLog('[FrontendVisualEditor] Detected content type:', contentType);
|
|
1484
1535
|
|
|
1485
1536
|
// 画像の場合(純粋な画像ペーストのみ、Excel/Wordは除外済み)
|
|
1486
1537
|
if (contentType === 'image') {
|
|
@@ -1512,7 +1563,7 @@ function FrontendVisualEditorInner({
|
|
|
1512
1563
|
}
|
|
1513
1564
|
}
|
|
1514
1565
|
|
|
1515
|
-
|
|
1566
|
+
debugLog('[FrontendVisualEditor] Calling uploadFromClipboard for image');
|
|
1516
1567
|
await uploadFromClipboard(clipboardEvent.clipboardData, { x: 100, y: 100 });
|
|
1517
1568
|
return;
|
|
1518
1569
|
}
|
|
@@ -1521,44 +1572,44 @@ function FrontendVisualEditorInner({
|
|
|
1521
1572
|
if (canHandleRichPaste(clipboardEvent.clipboardData)) {
|
|
1522
1573
|
e.preventDefault();
|
|
1523
1574
|
e.stopPropagation();
|
|
1524
|
-
|
|
1575
|
+
debugLog('[FrontendVisualEditor] Calling pasteRichContent for:', contentType);
|
|
1525
1576
|
const result = await pasteRichContent(clipboardEvent.clipboardData);
|
|
1526
|
-
|
|
1577
|
+
debugLog('[FrontendVisualEditor] Rich paste result:', result);
|
|
1527
1578
|
|
|
1528
1579
|
// Figmaなどでデコード失敗した場合は画像としてフォールバック
|
|
1529
1580
|
if (result.shouldFallbackToImage) {
|
|
1530
|
-
|
|
1581
|
+
debugLog('[FrontendVisualEditor] Rich paste failed, falling back to image upload');
|
|
1531
1582
|
const imageResult = await uploadFromClipboard(clipboardEvent.clipboardData, { x: 100, y: 100 });
|
|
1532
1583
|
|
|
1533
1584
|
// 画像もない場合はプレーンテキストとしてペースト
|
|
1534
1585
|
if (!imageResult) {
|
|
1535
|
-
|
|
1586
|
+
debugLog('[FrontendVisualEditor] No image found, falling back to plain text');
|
|
1536
1587
|
const plainText = clipboardEvent.clipboardData.getData('text/plain');
|
|
1537
1588
|
if (plainText) {
|
|
1538
1589
|
// プレーンテキストとして強制的に処理
|
|
1539
1590
|
const textResult = await pasteRichContent(clipboardEvent.clipboardData, 'plain-text');
|
|
1540
|
-
|
|
1591
|
+
debugLog('[FrontendVisualEditor] Plain text paste result:', textResult);
|
|
1541
1592
|
}
|
|
1542
1593
|
}
|
|
1543
1594
|
}
|
|
1544
1595
|
return;
|
|
1545
1596
|
}
|
|
1546
1597
|
} else {
|
|
1547
|
-
|
|
1598
|
+
debugLog('[FrontendVisualEditor] No clipboardData available');
|
|
1548
1599
|
}
|
|
1549
1600
|
}, [getIframeDoc, uploadFromClipboard, canHandleRichPaste, pasteRichContent, debugClipboard, detectContentType]);
|
|
1550
1601
|
|
|
1551
1602
|
// メインドキュメントとiframe両方にペーストイベントを登録
|
|
1552
1603
|
useEffect(() => {
|
|
1553
|
-
|
|
1604
|
+
debugLog('[FrontendVisualEditor] Registering paste event listeners');
|
|
1554
1605
|
|
|
1555
1606
|
// デバッグ用:ウィンドウレベルでキャプチャフェーズでリスナー追加
|
|
1556
1607
|
const debugPasteHandler = (e: ClipboardEvent) => {
|
|
1557
|
-
|
|
1558
|
-
|
|
1559
|
-
|
|
1608
|
+
debugLog('[FrontendVisualEditor] Window paste event captured (capture phase)', e);
|
|
1609
|
+
debugLog('[FrontendVisualEditor] Target:', e.target);
|
|
1610
|
+
debugLog('[FrontendVisualEditor] ClipboardData:', e.clipboardData);
|
|
1560
1611
|
if (e.clipboardData) {
|
|
1561
|
-
|
|
1612
|
+
debugLog('[FrontendVisualEditor] Items:', Array.from(e.clipboardData.items).map(i => ({ type: i.type, kind: i.kind })));
|
|
1562
1613
|
}
|
|
1563
1614
|
};
|
|
1564
1615
|
window.addEventListener('paste', debugPasteHandler, true); // capture phase
|
|
@@ -1672,6 +1723,14 @@ function FrontendVisualEditorInner({
|
|
|
1672
1723
|
// Scaleツールなど特定のツールを使用中の場合、選択ツールに戻す
|
|
1673
1724
|
if (activeTool !== 'select') {
|
|
1674
1725
|
setActiveTool('select');
|
|
1726
|
+
// コメントツールを抜けるだけ(下書きは次の Esc で消す = 1 段ずつ戻る)
|
|
1727
|
+
if (activeTool === 'comment') return;
|
|
1728
|
+
}
|
|
1729
|
+
|
|
1730
|
+
// コメントの範囲の下書きがあれば、それを解除して終わり
|
|
1731
|
+
if (commentDraftRectRef.current) {
|
|
1732
|
+
setCommentDraftRect(null);
|
|
1733
|
+
return;
|
|
1675
1734
|
}
|
|
1676
1735
|
|
|
1677
1736
|
if (iframeDoc) {
|
|
@@ -1863,6 +1922,8 @@ function FrontendVisualEditorInner({
|
|
|
1863
1922
|
eraser: () => setActiveTool('eraser'),
|
|
1864
1923
|
text: () => setActiveTool('text'),
|
|
1865
1924
|
frame: () => setActiveTool('frame'),
|
|
1925
|
+
// コメントの範囲指定(コメント機能があるホストだけ)
|
|
1926
|
+
comment: can('commentAction') ? () => setActiveTool('comment') : undefined,
|
|
1866
1927
|
// 編集操作
|
|
1867
1928
|
// undo/redo は常に生やす(履歴が無いときは内部で何もしない)。
|
|
1868
1929
|
// 条件付きで undefined にするとブラウザ既定の取り消しが走ってしまう。
|
|
@@ -2022,6 +2083,7 @@ function FrontendVisualEditorInner({
|
|
|
2022
2083
|
setPptCommentsOpen(true);
|
|
2023
2084
|
setPptCommentFocus((n) => n + 1);
|
|
2024
2085
|
},
|
|
2086
|
+
pickRegion: () => setActiveTool('comment'),
|
|
2025
2087
|
}}
|
|
2026
2088
|
/>
|
|
2027
2089
|
</>
|
|
@@ -2172,6 +2234,7 @@ function FrontendVisualEditorInner({
|
|
|
2172
2234
|
onImageUpload={() => openFilePicker({ x: 100, y: 100 })}
|
|
2173
2235
|
onOpenMediaLibrary={can('apiFetch') ? () => setIsMediaLibraryOpen(true) : undefined}
|
|
2174
2236
|
isMediaReplaceMode={isImageSelected}
|
|
2237
|
+
canComment={can('commentAction')}
|
|
2175
2238
|
onAiRegenerate={can('apiFetch') && selectedElement && selectedElementIds.length <= 1 ? openAiPrompt : undefined}
|
|
2176
2239
|
onOpenVariables={() => setIsVariablesPanelOpen(true)}
|
|
2177
2240
|
hasVariables={hasVariables}
|
|
@@ -2218,9 +2281,22 @@ function FrontendVisualEditorInner({
|
|
|
2218
2281
|
<PptCommentMarkers
|
|
2219
2282
|
page={Number(currentContentId ?? contentId) || 1}
|
|
2220
2283
|
onOpenThread={(id) => {
|
|
2284
|
+
setPptFormatPaneOpen(false);
|
|
2221
2285
|
setPptCommentsOpen(true);
|
|
2222
2286
|
setPptActiveThread(id);
|
|
2223
2287
|
}}
|
|
2288
|
+
activeThreadId={pptCommentsOpen ? pptActiveThread : null}
|
|
2289
|
+
draftRect={commentDraftRect}
|
|
2290
|
+
regionActive={activeTool === 'comment'}
|
|
2291
|
+
onRegion={(rect) => {
|
|
2292
|
+
// 範囲が決まったら選択ツールに戻し、パネルを開いて本文へフォーカス
|
|
2293
|
+
setCommentDraftRect(rect);
|
|
2294
|
+
setActiveTool('select');
|
|
2295
|
+
setPptFormatPaneOpen(false);
|
|
2296
|
+
setPptCommentsOpen(true);
|
|
2297
|
+
setPptActiveThread(null);
|
|
2298
|
+
setPptCommentFocus((n) => n + 1);
|
|
2299
|
+
}}
|
|
2224
2300
|
/>
|
|
2225
2301
|
)}
|
|
2226
2302
|
{isPpt && pptFormatPaneOpen && (
|
|
@@ -2235,6 +2311,9 @@ function FrontendVisualEditorInner({
|
|
|
2235
2311
|
focusSignal={pptCommentFocus}
|
|
2236
2312
|
activeThreadId={pptActiveThread}
|
|
2237
2313
|
onActiveThread={setPptActiveThread}
|
|
2314
|
+
pendingRect={commentDraftRect}
|
|
2315
|
+
onClearPendingRect={() => setCommentDraftRect(null)}
|
|
2316
|
+
onPickRegion={() => setActiveTool('comment')}
|
|
2238
2317
|
/>
|
|
2239
2318
|
</div>
|
|
2240
2319
|
)}
|
|
@@ -2512,7 +2591,7 @@ function FrontendVisualEditorInner({
|
|
|
2512
2591
|
|
|
2513
2592
|
// 絶対配置に変換(トップレベルでimport済み)
|
|
2514
2593
|
const count = convertToAbsolutePositioning(iframeDoc);
|
|
2515
|
-
|
|
2594
|
+
debugLog('[Toast] Converted', count, 'elements to absolute positioning');
|
|
2516
2595
|
|
|
2517
2596
|
// DOMツリーを再構築
|
|
2518
2597
|
const tree = buildDomTree(iframeDoc);
|
|
@@ -2628,7 +2707,7 @@ export function FrontendVisualEditor({
|
|
|
2628
2707
|
? await fromIo()
|
|
2629
2708
|
: await getCSSVariablesList(effectiveParentId!, scope);
|
|
2630
2709
|
setInitialVariables(variables);
|
|
2631
|
-
|
|
2710
|
+
debugLog(`[FrontendVisualEditor] Loaded CSS variables (${fromIo ? 'io' : scope}):`, variables.length);
|
|
2632
2711
|
} catch (error) {
|
|
2633
2712
|
console.error('[FrontendVisualEditor] Failed to load CSS variables:', error);
|
|
2634
2713
|
} finally {
|
|
@@ -2644,7 +2723,7 @@ export function FrontendVisualEditor({
|
|
|
2644
2723
|
const toIo = io().saveVariables;
|
|
2645
2724
|
if (toIo) {
|
|
2646
2725
|
await toIo(variables);
|
|
2647
|
-
|
|
2726
|
+
debugLog('[FrontendVisualEditor] Saved CSS variables (io):', variables.length);
|
|
2648
2727
|
return;
|
|
2649
2728
|
}
|
|
2650
2729
|
if (!effectiveParentId) {
|
|
@@ -2653,7 +2732,7 @@ export function FrontendVisualEditor({
|
|
|
2653
2732
|
// editorModeに応じてスコープを決定
|
|
2654
2733
|
const scope: CSSVariableScope = editorMode === 'webpage' ? 'website' : 'presentation';
|
|
2655
2734
|
await saveCSSVariables(effectiveParentId, variables, { scope });
|
|
2656
|
-
|
|
2735
|
+
debugLog(`[FrontendVisualEditor] Saved CSS variables (${scope}):`, variables.length);
|
|
2657
2736
|
}, [effectiveParentId, editorMode]);
|
|
2658
2737
|
|
|
2659
2738
|
// CSS変数をロードするコールバック
|