@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.
Files changed (60) hide show
  1. package/README.md +6 -1
  2. package/dist/editor.css +1 -1
  3. package/package.json +1 -1
  4. package/src/editor/EditorContext.tsx +3 -2
  5. package/src/editor/EditorToolbar.tsx +8 -2
  6. package/src/editor/FrontendVisualEditor.tsx +114 -44
  7. package/src/editor/components/EditorCanvas.tsx +73 -15
  8. package/src/editor/components/EditorFooter.tsx +2 -0
  9. package/src/editor/components/EditorHeader.tsx +3 -2
  10. package/src/editor/components/FigmaColorPicker.tsx +2 -1
  11. package/src/editor/components/JsEditorDialog.tsx +1 -0
  12. package/src/editor/components/PageSettingsDialog.tsx +1 -0
  13. package/src/editor/components/comments/CommentBoard.tsx +9 -6
  14. package/src/editor/components/multi-page/CanvasFrameDecor.tsx +172 -0
  15. package/src/editor/components/multi-page/MultiPageCanvasView.tsx +173 -77
  16. package/src/editor/components/multi-page/PageFramePreview.tsx +96 -20
  17. package/src/editor/components/multi-page/index.ts +1 -0
  18. package/src/editor/components/ppt/PptChrome.tsx +6 -2
  19. package/src/editor/components/ppt/PptComments.tsx +411 -71
  20. package/src/editor/constants.ts +16 -1
  21. package/src/editor/contexts/EditorArtboardContext.tsx +8 -0
  22. package/src/editor/contexts/EditorComponentsContext.tsx +31 -27
  23. package/src/editor/contexts/EditorUIStateContext.tsx +4 -3
  24. package/src/editor/contexts/EditorVariablesContext.tsx +5 -4
  25. package/src/editor/contexts/MultiPageCanvasContext.tsx +26 -2
  26. package/src/editor/hooks/useAiReplace.ts +4 -3
  27. package/src/editor/hooks/useBrowserZoomPrevention.ts +2 -1
  28. package/src/editor/hooks/useComponentEditMode.ts +27 -26
  29. package/src/editor/hooks/useEditorMessages.ts +3 -2
  30. package/src/editor/hooks/useElementActions.ts +27 -26
  31. package/src/editor/hooks/useElementSelection.ts +19 -16
  32. package/src/editor/hooks/useFocusManagement.ts +7 -6
  33. package/src/editor/hooks/useGoogleFonts.ts +3 -2
  34. package/src/editor/hooks/useIframeInitializer.ts +6 -3
  35. package/src/editor/hooks/useIframeSetup.ts +4 -3
  36. package/src/editor/hooks/useImageUpload.ts +4 -3
  37. package/src/editor/hooks/useMarqueeSelection.ts +2 -1
  38. package/src/editor/hooks/usePageSettingsManager.ts +24 -23
  39. package/src/editor/hooks/useRichPaste.ts +11 -10
  40. package/src/editor/parts.ts +7 -25
  41. package/src/editor/utils/component-renderer.ts +8 -7
  42. package/src/editor/utils/component-sync.ts +16 -15
  43. package/src/editor/utils/debug.ts +52 -0
  44. package/src/editor/utils/dom-utils.ts +11 -10
  45. package/src/editor/utils/drop-target.ts +135 -0
  46. package/src/editor/utils/eyedropper.ts +3 -1
  47. package/src/editor/utils/figma-export.ts +6 -5
  48. package/src/editor/utils/figma-kiwi-decoder.ts +28 -27
  49. package/src/editor/utils/figma-kiwi-encoder.ts +9 -8
  50. package/src/editor/utils/figma-paste.ts +57 -56
  51. package/src/editor/utils/flex-utils.ts +12 -10
  52. package/src/editor/utils/override-apply.ts +3 -2
  53. package/src/editor/utils/override-detection.ts +11 -10
  54. package/src/editor/utils/tailwind-utils.ts +4 -3
  55. package/src/io.ts +22 -1
  56. package/src/lib/firebase/css-variables.ts +4 -3
  57. package/src/lib/firebase/storage.ts +3 -2
  58. package/src/styles/skin.css +9 -4
  59. package/src/types/editor.ts +6 -2
  60. package/src/version.ts +1 -1
@@ -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
- console.log('EyeDropper canceled or failed', e);
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
- console.log('[figma-export] Cached fig-kiwi schema with', schema.definitions.length, 'definitions');
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
- console.log('[figma-export] Converted', allNodes.length, 'nodes');
509
- console.log('[figma-export] First node:', JSON.stringify(allNodes[0], null, 2));
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
- console.log('[figma-export] Generated clipboard HTML length:', clipboardHtml.length);
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
- console.log('[figma-export] Copied to clipboard successfully');
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
- console.log('[figma-kiwi-decoder] Custom decode: definition count =', definitionCount);
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
- console.log('[figma-kiwi-decoder] Archive version:', version);
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
- console.log('[figma-kiwi-decoder] Meta:', meta);
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
- console.log('[figma-kiwi-decoder] Binary data size:', bytes.length);
348
+ debugLog('[figma-kiwi-decoder] Binary data size:', bytes.length);
348
349
 
349
350
  // Parse archive
350
351
  const archive = parseArchive(bytes);
351
- console.log('[figma-kiwi-decoder] Schema chunk size:', archive.schema.length);
352
- console.log('[figma-kiwi-decoder] Data chunk size:', archive.data.length);
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
- console.log('[figma-kiwi-decoder] Schema decompressed size:', schemaDecompressed.length);
358
- console.log('[figma-kiwi-decoder] Data decompressed size:', dataDecompressed.length);
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
- console.log('[figma-kiwi-decoder] Schema definitions:', schema.definitions.length);
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
- console.log('[figma-kiwi-decoder] RAW SCHEMA BASE64 (copy this for bundling):');
371
- console.log(schemaBase64);
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
- console.log('[figma-kiwi-decoder] Message definition found at index:', messageDefIndex);
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
- console.log('[figma-kiwi-decoder] NodeChange fields:',
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
- console.log('[figma-kiwi-decoder] Parent-related fields:', parentFields.map(f => f.name));
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
- console.log(`[figma-kiwi-decoder] ${def.name} (${def.kind}): ${def.fields.map(f => `${f.name}:${f.type}(${f.value})`).join(', ')}`);
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
- console.log('[figma-kiwi-decoder] PaintType enum values:', paintTypeEnum.fields.map(f => `${f.name}=${f.value}`).join(', '));
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
- console.log('[figma-kiwi-decoder] Message decoded, type:', message.type);
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
- console.log('[figma-kiwi-decoder] === NODE CHANGES DEBUG ===');
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
- console.log(`[figma-kiwi-decoder] Node ${i}: ${nodeObj.type} "${nodeObj.name}"`);
678
- console.log(`[figma-kiwi-decoder] Keys: ${Object.keys(nodeObj).join(', ')}`);
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
- console.log(`[figma-kiwi-decoder] parentIndex.guid: ${pi.guid.sessionID}:${pi.guid.localID}`);
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
- console.log(`[figma-kiwi-decoder] guid: ${g.sessionID}:${g.localID}`);
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
- console.log(`[figma-kiwi-decoder] auto-layout: mode=${nodeObj.stackMode} spacing=${nodeObj.stackSpacing} padding=${nodeObj.stackPadding}`);
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
- console.log(`[figma-kiwi-decoder] text: align=${nodeObj.textAlignHorizontal} lineHeight=${lh?.value}${lh?.units} letterSpacing=${ls?.value}${ls?.units}`);
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
- console.log(`[figma-kiwi-decoder] fillPaint[${j}]: type=${paint.type} keys=[${paintKeys.join(', ')}]`);
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
- console.log(`[figma-kiwi-decoder] IMAGE paint detail:`, JSON.stringify(paint, (_, v) => {
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
- console.log('[figma-kiwi-decoder] === END NODE CHANGES DEBUG ===');
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
- console.log('[figma-kiwi-encoder] Schema cached with', schema.definitions.length, 'definitions');
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
- console.log('[figma-kiwi-encoder] Message:', { type: 'NODE_CHANGES', sessionID, pasteID, nodeChangesCount: nodeChanges.length });
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
- console.log('[figma-kiwi-encoder] Encoded message size:', messageData.length);
541
+ debugLog('[figma-kiwi-encoder] Encoded message size:', messageData.length);
541
542
 
542
543
  // Compress data
543
544
  const dataCompressed = pako.deflateRaw(messageData);
544
- console.log('[figma-kiwi-encoder] Compressed data size:', dataCompressed.length);
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
- console.log('[figma-kiwi-encoder] Using cached schema bytes');
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
- console.log('[figma-kiwi-encoder] Encoded schema size:', schemaCompressed.length);
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
- console.log('[figma-kiwi-encoder] Archive size:', archive.length);
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
- console.log('[figma-kiwi-encoder] Generated clipboard HTML length:', html.length);
582
+ debugLog('[figma-kiwi-encoder] Generated clipboard HTML length:', html.length);
582
583
  return html;
583
584
  }