@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
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 部品をキャンバスへドラッグしているときの「どこに入るか」の判定と、その印。
|
|
3
|
+
*
|
|
4
|
+
* 【なぜ別ファイルか】
|
|
5
|
+
* 挿入位置の計算(parts.ts の insertIntoFlow)と、ドラッグ中に見せる印は
|
|
6
|
+
* 必ず同じ答えでなければならない。別々に書くと「印の場所と違うところに入る」
|
|
7
|
+
* という一番たちの悪いズレ方をするので、計算は 1 つにして両方から呼ぶ。
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
/** ドラッグ中の印につけるクラス(この名前で探して消す) */
|
|
11
|
+
export const PART_DROP_INDICATOR_CLASS = 'gg-part-drop-indicator';
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* 印に付けるクラス。
|
|
15
|
+
* `selection-box` を併記しているのは、保存時のサニタイズ(html-utils の
|
|
16
|
+
* getCleanHtml)と履歴の除外(EditorHistoryContext の EDITOR_CHROME_SELECTOR)が
|
|
17
|
+
* どちらも `.selection-box` を見ているため。既存の除外機構にそのまま乗せることで、
|
|
18
|
+
* 印が保存 HTML や Undo 履歴に混ざるのを防ぐ。
|
|
19
|
+
* (同じ手は dom-utils の群バウンディングボックスでも使っている)
|
|
20
|
+
*/
|
|
21
|
+
const INDICATOR_CLASS = `selection-box ${PART_DROP_INDICATOR_CLASS}`;
|
|
22
|
+
|
|
23
|
+
/** 紙面の中身ではなく、エディタが差し込んでいる表示専用のノード */
|
|
24
|
+
const EDITOR_CHROME =
|
|
25
|
+
'.selection-box,.marquee-selection-box,.gg-comment-layer,.gg-crop-ui,' +
|
|
26
|
+
'#gg-measure-layer,#gg-smart-guides,.flex-drop-indicator,.nesting-drop-indicator,' +
|
|
27
|
+
'.auto-layout-drop-indicator,.drag-ghost';
|
|
28
|
+
|
|
29
|
+
/** 挿入先。before が null なら parent の末尾 */
|
|
30
|
+
export interface FlowInsertion {
|
|
31
|
+
parent: Element;
|
|
32
|
+
before: Element | null;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* 「セクションの器」か。この直下に部品を並べる。
|
|
37
|
+
* 器 = body / #artboard / [data-wf-body] / main
|
|
38
|
+
*/
|
|
39
|
+
function isContainer(doc: Document, node: Element | null): node is Element {
|
|
40
|
+
return (
|
|
41
|
+
!!node &&
|
|
42
|
+
(node === doc.body ||
|
|
43
|
+
node.id === 'artboard' ||
|
|
44
|
+
node.hasAttribute('data-wf-body') ||
|
|
45
|
+
node.tagName === 'MAIN')
|
|
46
|
+
);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/** 器が見つからないときの落とし先 */
|
|
50
|
+
function fallbackContainer(doc: Document): Element {
|
|
51
|
+
return (
|
|
52
|
+
doc.querySelector('main') ??
|
|
53
|
+
doc.querySelector('[data-wf-body]') ??
|
|
54
|
+
doc.getElementById('artboard') ??
|
|
55
|
+
doc.body
|
|
56
|
+
);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/** 部品を並べる相手になる子要素(表示専用のノードと高さ 0 のものは外す) */
|
|
60
|
+
function placeableChildren(parent: Element): Element[] {
|
|
61
|
+
return Array.from(parent.children).filter((el) => {
|
|
62
|
+
if (el.tagName === 'SCRIPT' || el.tagName === 'STYLE' || el.tagName === 'LINK') return false;
|
|
63
|
+
if (el.matches(EDITOR_CHROME)) return false;
|
|
64
|
+
return el.getBoundingClientRect().height > 0;
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* iframe 内の座標 (x, y) に部品を落としたとき、どこに入るかを返す。
|
|
70
|
+
*
|
|
71
|
+
* 【なぜ elementsFromPoint で一番深い器を探すか】
|
|
72
|
+
* 以前は「当たった要素から器の直下の祖先まで上る」方式だった。これだと
|
|
73
|
+
* セクションとセクションの隙間(= main の地の部分)に落としたとき、上る先が
|
|
74
|
+
* main ではなく main を包む div になり、部品がページの先頭か末尾に飛んでいた
|
|
75
|
+
* (実測: キャンバス表示で紙面の中ほどに落として先頭に入った)。
|
|
76
|
+
* 重なり順を全部見て「一番内側の器」を親に決めれば、隙間に落としても
|
|
77
|
+
* そのセクションの列に入る。
|
|
78
|
+
*/
|
|
79
|
+
export function findFlowInsertion(doc: Document, x: number, y: number): FlowInsertion {
|
|
80
|
+
const stack = (doc.elementsFromPoint(x, y) as Element[]) ?? [];
|
|
81
|
+
const parent = stack.find((el) => isContainer(doc, el)) ?? fallbackContainer(doc);
|
|
82
|
+
|
|
83
|
+
// 親の子を上から見て、ドロップ位置より下に中心がある最初の子の前に入れる。
|
|
84
|
+
// 中心で区切るので「つかんでいる位置のすぐ上か、すぐ下か」が見立てと合う
|
|
85
|
+
const kids = placeableChildren(parent);
|
|
86
|
+
const before = kids.find((el) => {
|
|
87
|
+
const r = el.getBoundingClientRect();
|
|
88
|
+
return y < r.top + r.height / 2;
|
|
89
|
+
});
|
|
90
|
+
return { parent, before: before ?? null };
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/** 既にある印を消す */
|
|
94
|
+
export function clearPartDropIndicator(doc: Document | null | undefined): void {
|
|
95
|
+
if (!doc) return;
|
|
96
|
+
doc.querySelectorAll(`.${PART_DROP_INDICATOR_CLASS}`).forEach((el) => el.remove());
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* 挿入位置に横棒を出す。
|
|
101
|
+
* 紙面(iframe)の中に置くので、キャンバス表示で外側の transform に
|
|
102
|
+
* 縮められていても紙面と一緒に縮み、位置がずれない。
|
|
103
|
+
*/
|
|
104
|
+
export function showPartDropIndicator(doc: Document, x: number, y: number): FlowInsertion {
|
|
105
|
+
// 前回の印を先に消す。残したまま座標判定をすると印そのものに当たる
|
|
106
|
+
clearPartDropIndicator(doc);
|
|
107
|
+
|
|
108
|
+
const target = findFlowInsertion(doc, x, y);
|
|
109
|
+
const kids = placeableChildren(target.parent);
|
|
110
|
+
// before があればその上辺、無ければ最後の子の下辺。子が無ければ親の内側の上辺
|
|
111
|
+
const ref = target.before ?? kids[kids.length - 1] ?? target.parent;
|
|
112
|
+
const atTop = !!target.before || ref === target.parent;
|
|
113
|
+
const rect = ref.getBoundingClientRect();
|
|
114
|
+
|
|
115
|
+
const view = doc.defaultView;
|
|
116
|
+
const scrollX = view?.scrollX ?? 0;
|
|
117
|
+
const scrollY = view?.scrollY ?? 0;
|
|
118
|
+
|
|
119
|
+
const bar = doc.createElement('div');
|
|
120
|
+
bar.className = INDICATOR_CLASS;
|
|
121
|
+
// .selection-box が position:absolute / pointer-events:none / z-index:10000 を
|
|
122
|
+
// 既に持っているので、ここでは見た目と位置だけ足す
|
|
123
|
+
bar.style.cssText = [
|
|
124
|
+
`left: ${rect.left + scrollX}px`,
|
|
125
|
+
`top: ${(atTop ? rect.top : rect.bottom) + scrollY - 2}px`,
|
|
126
|
+
`width: ${Math.max(rect.width, 24)}px`,
|
|
127
|
+
'height: 4px',
|
|
128
|
+
'background: #0d99ff',
|
|
129
|
+
'border-radius: 2px',
|
|
130
|
+
'box-shadow: 0 0 0 1px rgba(255,255,255,0.9), 0 0 8px rgba(13,153,255,0.6)',
|
|
131
|
+
].join(';');
|
|
132
|
+
|
|
133
|
+
doc.body.appendChild(bar);
|
|
134
|
+
return target;
|
|
135
|
+
}
|
|
@@ -10,6 +10,8 @@
|
|
|
10
10
|
* (押せるのに何も起きないボタンを置かない)。
|
|
11
11
|
*/
|
|
12
12
|
|
|
13
|
+
import { debugLog } from './debug';
|
|
14
|
+
|
|
13
15
|
/** この環境でスポイトが使えるか */
|
|
14
16
|
export function isEyeDropperSupported(): boolean {
|
|
15
17
|
return typeof window !== 'undefined' && 'EyeDropper' in window;
|
|
@@ -29,7 +31,7 @@ export async function pickScreenColor(): Promise<string | null> {
|
|
|
29
31
|
return result?.sRGBHex || null;
|
|
30
32
|
} catch (e) {
|
|
31
33
|
// Escでの取り消しもここに来るので、握りつぶしてよい
|
|
32
|
-
|
|
34
|
+
debugLog('EyeDropper canceled or failed', e);
|
|
33
35
|
return null;
|
|
34
36
|
}
|
|
35
37
|
}
|
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
|
|
8
8
|
import { readHTMLMessage, writeHTMLMessage } from 'fig-kiwi';
|
|
9
9
|
import type { Schema } from 'kiwi-schema';
|
|
10
|
+
import { debugLog } from './debug';
|
|
10
11
|
|
|
11
12
|
/**
|
|
12
13
|
* Cached schema from Figma paste (proper kiwi-schema Schema type)
|
|
@@ -18,7 +19,7 @@ let cachedFigKiwiSchema: Schema | null = null;
|
|
|
18
19
|
*/
|
|
19
20
|
export function cacheFigKiwiSchema(schema: Schema): void {
|
|
20
21
|
cachedFigKiwiSchema = schema;
|
|
21
|
-
|
|
22
|
+
debugLog('[figma-export] Cached fig-kiwi schema with', schema.definitions.length, 'definitions');
|
|
22
23
|
}
|
|
23
24
|
|
|
24
25
|
/**
|
|
@@ -505,8 +506,8 @@ export async function copyElementsToFigma(elements: HTMLElement[]): Promise<{
|
|
|
505
506
|
allNodes.push(...nodes);
|
|
506
507
|
}
|
|
507
508
|
|
|
508
|
-
|
|
509
|
-
|
|
509
|
+
debugLog('[figma-export] Converted', allNodes.length, 'nodes');
|
|
510
|
+
debugLog('[figma-export] First node:', JSON.stringify(allNodes[0], null, 2));
|
|
510
511
|
|
|
511
512
|
// Create message using fig-kiwi's expected format
|
|
512
513
|
const message = {
|
|
@@ -527,7 +528,7 @@ export async function copyElementsToFigma(elements: HTMLElement[]): Promise<{
|
|
|
527
528
|
message: message as any, // Type assertion needed due to fig-kiwi's strict types
|
|
528
529
|
});
|
|
529
530
|
|
|
530
|
-
|
|
531
|
+
debugLog('[figma-export] Generated clipboard HTML length:', clipboardHtml.length);
|
|
531
532
|
|
|
532
533
|
// Copy to clipboard
|
|
533
534
|
await navigator.clipboard.write([
|
|
@@ -537,7 +538,7 @@ export async function copyElementsToFigma(elements: HTMLElement[]): Promise<{
|
|
|
537
538
|
}),
|
|
538
539
|
]);
|
|
539
540
|
|
|
540
|
-
|
|
541
|
+
debugLog('[figma-export] Copied to clipboard successfully');
|
|
541
542
|
|
|
542
543
|
return {
|
|
543
544
|
success: true,
|
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
|
|
8
8
|
import * as pako from 'pako';
|
|
9
9
|
import { cacheSchema } from './figma-kiwi-encoder';
|
|
10
|
+
import { debugLog } from './debug';
|
|
10
11
|
|
|
11
12
|
// Extended types array
|
|
12
13
|
const EXTENDED_TYPES = [
|
|
@@ -196,7 +197,7 @@ function customDecodeBinarySchema(buffer: Uint8Array, types: string[]): Schema {
|
|
|
196
197
|
const definitionCount = bb.readVarUint();
|
|
197
198
|
const definitions: Definition[] = [];
|
|
198
199
|
|
|
199
|
-
|
|
200
|
+
debugLog('[figma-kiwi-decoder] Custom decode: definition count =', definitionCount);
|
|
200
201
|
|
|
201
202
|
// Read in the schema
|
|
202
203
|
for (let i = 0; i < definitionCount; i++) {
|
|
@@ -281,7 +282,7 @@ function parseArchive(buffer: Uint8Array): { schema: Uint8Array; data: Uint8Arra
|
|
|
281
282
|
// Read version (4 bytes, little endian)
|
|
282
283
|
const view = new DataView(buffer.buffer, buffer.byteOffset);
|
|
283
284
|
const version = view.getUint32(offset, true);
|
|
284
|
-
|
|
285
|
+
debugLog('[figma-kiwi-decoder] Archive version:', version);
|
|
285
286
|
offset += 4;
|
|
286
287
|
|
|
287
288
|
const files: Uint8Array[] = [];
|
|
@@ -330,7 +331,7 @@ export function parseFigmaClipboardToJson(html: string): FigmaClipboardData {
|
|
|
330
331
|
throw new Error('Figma metadata not found');
|
|
331
332
|
}
|
|
332
333
|
const meta = JSON.parse(atob(metaMatch[1]));
|
|
333
|
-
|
|
334
|
+
debugLog('[figma-kiwi-decoder] Meta:', meta);
|
|
334
335
|
|
|
335
336
|
// Extract binary data
|
|
336
337
|
const dataMatch = html.match(/\(figma\)([A-Za-z0-9+/=]+)\(\/figma\)/);
|
|
@@ -344,22 +345,22 @@ export function parseFigmaClipboardToJson(html: string): FigmaClipboardData {
|
|
|
344
345
|
for (let i = 0; i < binaryString.length; i++) {
|
|
345
346
|
bytes[i] = binaryString.charCodeAt(i);
|
|
346
347
|
}
|
|
347
|
-
|
|
348
|
+
debugLog('[figma-kiwi-decoder] Binary data size:', bytes.length);
|
|
348
349
|
|
|
349
350
|
// Parse archive
|
|
350
351
|
const archive = parseArchive(bytes);
|
|
351
|
-
|
|
352
|
-
|
|
352
|
+
debugLog('[figma-kiwi-decoder] Schema chunk size:', archive.schema.length);
|
|
353
|
+
debugLog('[figma-kiwi-decoder] Data chunk size:', archive.data.length);
|
|
353
354
|
|
|
354
355
|
// Decompress schema and data
|
|
355
356
|
const schemaDecompressed = pako.inflateRaw(archive.schema);
|
|
356
357
|
const dataDecompressed = pako.inflateRaw(archive.data);
|
|
357
|
-
|
|
358
|
-
|
|
358
|
+
debugLog('[figma-kiwi-decoder] Schema decompressed size:', schemaDecompressed.length);
|
|
359
|
+
debugLog('[figma-kiwi-decoder] Data decompressed size:', dataDecompressed.length);
|
|
359
360
|
|
|
360
361
|
// Decode schema with extended types
|
|
361
362
|
const schema = customDecodeBinarySchema(schemaDecompressed, EXTENDED_TYPES);
|
|
362
|
-
|
|
363
|
+
debugLog('[figma-kiwi-decoder] Schema definitions:', schema.definitions.length);
|
|
363
364
|
|
|
364
365
|
// Cache schema for later use in encoding (Figma export)
|
|
365
366
|
cacheSchema(schema, archive.schema);
|
|
@@ -367,8 +368,8 @@ export function parseFigmaClipboardToJson(html: string): FigmaClipboardData {
|
|
|
367
368
|
// Export raw schema for bundling (one-time capture)
|
|
368
369
|
if (typeof window !== 'undefined') {
|
|
369
370
|
const schemaBase64 = btoa(String.fromCharCode(...archive.schema));
|
|
370
|
-
|
|
371
|
-
|
|
371
|
+
debugLog('[figma-kiwi-decoder] RAW SCHEMA BASE64 (copy this for bundling):');
|
|
372
|
+
debugLog(schemaBase64);
|
|
372
373
|
}
|
|
373
374
|
|
|
374
375
|
// Find Message definition
|
|
@@ -376,18 +377,18 @@ export function parseFigmaClipboardToJson(html: string): FigmaClipboardData {
|
|
|
376
377
|
if (messageDefIndex === -1) {
|
|
377
378
|
throw new Error('Message definition not found in schema');
|
|
378
379
|
}
|
|
379
|
-
|
|
380
|
+
debugLog('[figma-kiwi-decoder] Message definition found at index:', messageDefIndex);
|
|
380
381
|
|
|
381
382
|
// Find and log NodeChange definition to see available fields
|
|
382
383
|
const nodeChangeDef = schema.definitions.find(d => d.name === 'NodeChange');
|
|
383
384
|
if (nodeChangeDef) {
|
|
384
|
-
|
|
385
|
+
debugLog('[figma-kiwi-decoder] NodeChange fields:',
|
|
385
386
|
nodeChangeDef.fields.map(f => `${f.name}(${f.value})`).join(', '));
|
|
386
387
|
// Look for parent-related fields
|
|
387
388
|
const parentFields = nodeChangeDef.fields.filter(f =>
|
|
388
389
|
f.name.toLowerCase().includes('parent') || f.name.toLowerCase().includes('index'));
|
|
389
390
|
if (parentFields.length > 0) {
|
|
390
|
-
|
|
391
|
+
debugLog('[figma-kiwi-decoder] Parent-related fields:', parentFields.map(f => f.name));
|
|
391
392
|
}
|
|
392
393
|
}
|
|
393
394
|
|
|
@@ -395,19 +396,19 @@ export function parseFigmaClipboardToJson(html: string): FigmaClipboardData {
|
|
|
395
396
|
const paintDefs = schema.definitions.filter(d =>
|
|
396
397
|
d.name.toLowerCase().includes('paint') || d.name.toLowerCase().includes('image'));
|
|
397
398
|
for (const def of paintDefs) {
|
|
398
|
-
|
|
399
|
+
debugLog(`[figma-kiwi-decoder] ${def.name} (${def.kind}): ${def.fields.map(f => `${f.name}:${f.type}(${f.value})`).join(', ')}`);
|
|
399
400
|
}
|
|
400
401
|
|
|
401
402
|
// Log PaintType enum if it exists
|
|
402
403
|
const paintTypeEnum = schema.definitions.find(d => d.name === 'PaintType');
|
|
403
404
|
if (paintTypeEnum) {
|
|
404
|
-
|
|
405
|
+
debugLog('[figma-kiwi-decoder] PaintType enum values:', paintTypeEnum.fields.map(f => `${f.name}=${f.value}`).join(', '));
|
|
405
406
|
}
|
|
406
407
|
|
|
407
408
|
// Compile schema and decode message using our custom implementation
|
|
408
409
|
const message = decodeMessage(dataDecompressed, schema);
|
|
409
410
|
|
|
410
|
-
|
|
411
|
+
debugLog('[figma-kiwi-decoder] Message decoded, type:', message.type);
|
|
411
412
|
|
|
412
413
|
return { meta, message, schema };
|
|
413
414
|
}
|
|
@@ -668,44 +669,44 @@ export function extractNodes(message: Record<string, unknown>): FigmaNode[] {
|
|
|
668
669
|
// Try to extract node changes from message
|
|
669
670
|
const nodeChanges = message.nodeChanges as unknown[];
|
|
670
671
|
if (Array.isArray(nodeChanges)) {
|
|
671
|
-
|
|
672
|
+
debugLog('[figma-kiwi-decoder] === NODE CHANGES DEBUG ===');
|
|
672
673
|
for (let i = 0; i < nodeChanges.length; i++) {
|
|
673
674
|
const change = nodeChanges[i];
|
|
674
675
|
if (change && typeof change === 'object') {
|
|
675
676
|
const nodeObj = change as Record<string, unknown>;
|
|
676
677
|
// Log all keys to see what fields are available
|
|
677
|
-
|
|
678
|
-
|
|
678
|
+
debugLog(`[figma-kiwi-decoder] Node ${i}: ${nodeObj.type} "${nodeObj.name}"`);
|
|
679
|
+
debugLog(`[figma-kiwi-decoder] Keys: ${Object.keys(nodeObj).join(', ')}`);
|
|
679
680
|
// Look for parent-related fields - parentIndex has nested guid structure
|
|
680
681
|
if ('parentIndex' in nodeObj && nodeObj.parentIndex) {
|
|
681
682
|
const pi = nodeObj.parentIndex as { guid?: { sessionID?: number; localID?: number } };
|
|
682
683
|
if (pi.guid) {
|
|
683
|
-
|
|
684
|
+
debugLog(`[figma-kiwi-decoder] parentIndex.guid: ${pi.guid.sessionID}:${pi.guid.localID}`);
|
|
684
685
|
}
|
|
685
686
|
}
|
|
686
687
|
if ('guid' in nodeObj && nodeObj.guid) {
|
|
687
688
|
const g = nodeObj.guid as { sessionID?: number; localID?: number };
|
|
688
|
-
|
|
689
|
+
debugLog(`[figma-kiwi-decoder] guid: ${g.sessionID}:${g.localID}`);
|
|
689
690
|
}
|
|
690
691
|
// Log auto-layout properties if present
|
|
691
692
|
if ('stackMode' in nodeObj) {
|
|
692
|
-
|
|
693
|
+
debugLog(`[figma-kiwi-decoder] auto-layout: mode=${nodeObj.stackMode} spacing=${nodeObj.stackSpacing} padding=${nodeObj.stackPadding}`);
|
|
693
694
|
}
|
|
694
695
|
// Log text properties if present
|
|
695
696
|
if ('textAlignHorizontal' in nodeObj || 'lineHeight' in nodeObj) {
|
|
696
697
|
const lh = nodeObj.lineHeight as { value?: number; units?: string } | undefined;
|
|
697
698
|
const ls = nodeObj.letterSpacing as { value?: number; units?: string } | undefined;
|
|
698
|
-
|
|
699
|
+
debugLog(`[figma-kiwi-decoder] text: align=${nodeObj.textAlignHorizontal} lineHeight=${lh?.value}${lh?.units} letterSpacing=${ls?.value}${ls?.units}`);
|
|
699
700
|
}
|
|
700
701
|
// Log fill paints details for debugging image detection
|
|
701
702
|
if ('fillPaints' in nodeObj && Array.isArray(nodeObj.fillPaints)) {
|
|
702
703
|
for (let j = 0; j < nodeObj.fillPaints.length; j++) {
|
|
703
704
|
const paint = nodeObj.fillPaints[j] as Record<string, unknown>;
|
|
704
705
|
const paintKeys = Object.keys(paint);
|
|
705
|
-
|
|
706
|
+
debugLog(`[figma-kiwi-decoder] fillPaint[${j}]: type=${paint.type} keys=[${paintKeys.join(', ')}]`);
|
|
706
707
|
// Log all values for IMAGE type or unknown types
|
|
707
708
|
if (paint.type === 'IMAGE' || paintKeys.includes('imageHash') || paintKeys.includes('imageRef')) {
|
|
708
|
-
|
|
709
|
+
debugLog(`[figma-kiwi-decoder] IMAGE paint detail:`, JSON.stringify(paint, (_, v) => {
|
|
709
710
|
if (v instanceof Uint8Array) return `<Uint8Array(${v.length})>`;
|
|
710
711
|
return v;
|
|
711
712
|
}, 2).substring(0, 500));
|
|
@@ -715,7 +716,7 @@ export function extractNodes(message: Record<string, unknown>): FigmaNode[] {
|
|
|
715
716
|
nodes.push(nodeObj as FigmaNode);
|
|
716
717
|
}
|
|
717
718
|
}
|
|
718
|
-
|
|
719
|
+
debugLog('[figma-kiwi-decoder] === END NODE CHANGES DEBUG ===');
|
|
719
720
|
}
|
|
720
721
|
|
|
721
722
|
return nodes;
|
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
8
|
import * as pako from 'pako';
|
|
9
|
+
import { debugLog } from './debug';
|
|
9
10
|
|
|
10
11
|
/**
|
|
11
12
|
* WriteBuffer for encoding binary data in kiwi format
|
|
@@ -193,7 +194,7 @@ export function cacheSchema(schema: Schema, rawSchemaBytes?: Uint8Array): void {
|
|
|
193
194
|
if (rawSchemaBytes) {
|
|
194
195
|
cachedSchemaRaw = rawSchemaBytes;
|
|
195
196
|
}
|
|
196
|
-
|
|
197
|
+
debugLog('[figma-kiwi-encoder] Schema cached with', schema.definitions.length, 'definitions');
|
|
197
198
|
}
|
|
198
199
|
|
|
199
200
|
/**
|
|
@@ -402,7 +403,7 @@ export function encodeMessage(
|
|
|
402
403
|
nodeChanges: nodeChanges,
|
|
403
404
|
};
|
|
404
405
|
|
|
405
|
-
|
|
406
|
+
debugLog('[figma-kiwi-encoder] Message:', { type: 'NODE_CHANGES', sessionID, pasteID, nodeChangesCount: nodeChanges.length });
|
|
406
407
|
|
|
407
408
|
// Encode the message
|
|
408
409
|
encodeStruct(bb, message, messageDef, defMap);
|
|
@@ -537,28 +538,28 @@ export function generateFigmaClipboardHtml(
|
|
|
537
538
|
|
|
538
539
|
// Encode message
|
|
539
540
|
const messageData = encodeMessage(nodeChanges, schemaToUse);
|
|
540
|
-
|
|
541
|
+
debugLog('[figma-kiwi-encoder] Encoded message size:', messageData.length);
|
|
541
542
|
|
|
542
543
|
// Compress data
|
|
543
544
|
const dataCompressed = pako.deflateRaw(messageData);
|
|
544
|
-
|
|
545
|
+
debugLog('[figma-kiwi-encoder] Compressed data size:', dataCompressed.length);
|
|
545
546
|
|
|
546
547
|
// Get or encode schema
|
|
547
548
|
let schemaCompressed: Uint8Array;
|
|
548
549
|
if (cachedSchemaRaw) {
|
|
549
550
|
// Re-use cached raw schema (already in correct format)
|
|
550
551
|
schemaCompressed = cachedSchemaRaw;
|
|
551
|
-
|
|
552
|
+
debugLog('[figma-kiwi-encoder] Using cached schema bytes');
|
|
552
553
|
} else {
|
|
553
554
|
// Encode schema
|
|
554
555
|
const schemaData = encodeBinarySchema(schemaToUse);
|
|
555
556
|
schemaCompressed = pako.deflateRaw(schemaData);
|
|
556
|
-
|
|
557
|
+
debugLog('[figma-kiwi-encoder] Encoded schema size:', schemaCompressed.length);
|
|
557
558
|
}
|
|
558
559
|
|
|
559
560
|
// Create archive
|
|
560
561
|
const archive = createArchive(schemaCompressed, dataCompressed);
|
|
561
|
-
|
|
562
|
+
debugLog('[figma-kiwi-encoder] Archive size:', archive.length);
|
|
562
563
|
|
|
563
564
|
// Encode to base64
|
|
564
565
|
const binaryString = Array.from(archive).map(b => String.fromCharCode(b)).join('');
|
|
@@ -578,6 +579,6 @@ export function generateFigmaClipboardHtml(
|
|
|
578
579
|
: '';
|
|
579
580
|
const html = `<meta charset="utf-8"><span data-metadata="<!--(figmeta)${base64Meta}(/figmeta)-->"></span><span data-buffer="<!--(figma)${base64Data}(/figma)-->"></span>${previewSpan}`;
|
|
580
581
|
|
|
581
|
-
|
|
582
|
+
debugLog('[figma-kiwi-encoder] Generated clipboard HTML length:', html.length);
|
|
582
583
|
return html;
|
|
583
584
|
}
|