@pedroab0/skills 1.0.1 → 1.2.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 (29) hide show
  1. package/README.md +19 -3
  2. package/bin/cli.js +175 -35
  3. package/figma-extractor/README.md +14 -6
  4. package/figma-extractor/SKILL.md +18 -16
  5. package/figma-extractor/figma-plugin/README.md +10 -5
  6. package/figma-extractor/figma-plugin/code.js +461 -87
  7. package/figma-extractor/figma-plugin/ui.html +76 -23
  8. package/figma-extractor/scripts/fetch_figma.js +465 -61
  9. package/figma-node-builder/README.md +1 -1
  10. package/figma-node-builder/SKILL.md +7 -8
  11. package/figma-node-builder/scripts/inspect_spec.js +15 -2
  12. package/figma-to-code/README.md +10 -4
  13. package/figma-to-code/SKILL.md +18 -16
  14. package/figma-to-code/scripts/fetch_figma.js +448 -53
  15. package/package.json +12 -3
  16. package/figma-extractor/demo/desktop_home.json +0 -2434
  17. package/figma-extractor/demo/dt_money_desktop_home.png +0 -0
  18. package/figma-extractor/demo/dt_money_preview.png +0 -0
  19. package/figma-extractor/demo/dt_money_selection.json +0 -472
  20. package/figma-extractor/demo/index.html +0 -188
  21. package/figma-extractor/demo/style.css +0 -360
  22. package/figma-extractor/figma-plugin/assets/cover-1920.png +0 -0
  23. package/figma-extractor/figma-plugin/assets/cover.jpg +0 -0
  24. package/figma-extractor/figma-plugin/assets/cover.png +0 -0
  25. package/figma-extractor/figma-plugin/assets/icon-128.png +0 -0
  26. package/figma-extractor/figma-plugin/assets/icon.png +0 -0
  27. package/figma-extractor/scripts/test_fetch_figma.js +0 -601
  28. package/figma-node-builder/scripts/test_inspect_spec.js +0 -148
  29. package/figma-to-code/scripts/test_fetch_figma.js +0 -581
@@ -261,14 +261,72 @@
261
261
  setTimeout(() => URL.revokeObjectURL(url), 5000);
262
262
  }
263
263
 
264
- function flashButton(btn, text) {
265
- const original = btn.innerHTML;
264
+ function flashButton(btn, text, bgOverride = '') {
265
+ if (!btn.dataset.originalHtml) {
266
+ btn.dataset.originalHtml = btn.innerHTML;
267
+ }
268
+ if (btn._flashTimer) {
269
+ clearTimeout(btn._flashTimer);
270
+ }
266
271
  btn.innerHTML = `<span>${text}</span>`;
267
- btn.style.borderColor = '#10b981';
268
- setTimeout(() => {
269
- btn.innerHTML = original;
272
+ if (bgOverride) {
273
+ btn.style.background = bgOverride;
274
+ } else {
275
+ btn.style.borderColor = '#10b981';
276
+ }
277
+ btn._flashTimer = setTimeout(() => {
278
+ btn.innerHTML = btn.dataset.originalHtml;
279
+ btn.style.background = '';
270
280
  btn.style.borderColor = '';
271
- }, 1500);
281
+ btn._flashTimer = null;
282
+ }, 1800);
283
+ }
284
+
285
+ // Figma plugin UI runs inside a null-origin sandboxed iframe where navigator.clipboard
286
+ // is blocked by Permissions-Policy. Synchronous execCommand('copy') is the official
287
+ // cross-browser / Figma Desktop clipboard mechanism, with Async Clipboard as fallback.
288
+ function copyUsingExecCommand(text) {
289
+ const textarea = document.createElement('textarea');
290
+ textarea.value = text;
291
+ textarea.setAttribute('readonly', '');
292
+ textarea.style.position = 'fixed';
293
+ textarea.style.top = '0';
294
+ textarea.style.left = '0';
295
+ textarea.style.width = '1px';
296
+ textarea.style.height = '1px';
297
+ textarea.style.padding = '0';
298
+ textarea.style.border = 'none';
299
+ textarea.style.outline = 'none';
300
+ textarea.style.boxShadow = 'none';
301
+ textarea.style.background = 'transparent';
302
+ textarea.style.opacity = '0';
303
+ textarea.style.pointerEvents = 'none';
304
+ document.body.appendChild(textarea);
305
+ textarea.focus({ preventScroll: true });
306
+ textarea.select();
307
+ textarea.setSelectionRange(0, textarea.value.length);
308
+ let copied = false;
309
+ try {
310
+ copied = document.execCommand('copy');
311
+ } catch (_) {
312
+ copied = false;
313
+ } finally {
314
+ document.body.removeChild(textarea);
315
+ }
316
+ return copied;
317
+ }
318
+
319
+ async function copyTextToClipboard(text) {
320
+ if (copyUsingExecCommand(text)) {
321
+ return true;
322
+ }
323
+ if (navigator.clipboard && typeof navigator.clipboard.writeText === 'function') {
324
+ try {
325
+ await navigator.clipboard.writeText(text);
326
+ return true;
327
+ } catch (_) {}
328
+ }
329
+ return false;
272
330
  }
273
331
 
274
332
  // Pure vanilla zero-dependency ZIP archive creator (Store format)
@@ -369,7 +427,7 @@
369
427
  document.getElementById('node-bounds').textContent = `${w} × ${h} px`;
370
428
  document.getElementById('hint-name').textContent = msg.name || 'component';
371
429
 
372
- const jsonStr = JSON.stringify(msg.spec || {}, null, 2);
430
+ const jsonStr = JSON.stringify(msg.spec || {});
373
431
  const sizeKb = (new Blob([jsonStr]).size / 1024).toFixed(1);
374
432
  document.getElementById('node-size').textContent = `${sizeKb} KB`;
375
433
 
@@ -414,30 +472,25 @@
414
472
  }
415
473
  };
416
474
 
417
- // Copy to clipboard
475
+ // Copy to clipboard (compact pruned JSON matching CLI default)
418
476
  document.getElementById('btn-copy').onclick = async () => {
419
477
  if (!currentData) return;
420
- const jsonStr = JSON.stringify(currentData.spec, null, 2);
421
- try {
422
- await navigator.clipboard.writeText(jsonStr);
423
- const btn = document.getElementById('btn-copy');
424
- const originalText = btn.innerHTML;
425
- btn.innerHTML = '<span>✔ Copied Node JSON!</span>';
426
- btn.style.background = '#16a34a';
478
+ const jsonStr = JSON.stringify(currentData.spec);
479
+ const btn = document.getElementById('btn-copy');
480
+ const ok = await copyTextToClipboard(jsonStr);
481
+ if (ok) {
482
+ flashButton(btn, '✔ Copied Node JSON!', '#16a34a');
427
483
  parent.postMessage({ pluginMessage: { type: 'NOTIFY', message: `Copied "${currentData.name}.json" to clipboard!` } }, '*');
428
- setTimeout(() => {
429
- btn.innerHTML = originalText;
430
- btn.style.background = '';
431
- }, 2000);
432
- } catch (err) {
433
- console.error('Clipboard failed', err);
484
+ } else {
485
+ flashButton(btn, '✖ Copy failed — use .json', '#dc2626');
486
+ parent.postMessage({ pluginMessage: { type: 'NOTIFY', message: 'Failed to access clipboard. Please download .json instead.', error: true } }, '*');
434
487
  }
435
488
  };
436
489
 
437
490
  // Download JSON
438
491
  document.getElementById('btn-download-json').onclick = () => {
439
492
  if (!currentData) return;
440
- const jsonStr = JSON.stringify(currentData.spec, null, 2);
493
+ const jsonStr = JSON.stringify(currentData.spec);
441
494
  const blob = new Blob([jsonStr], { type: 'application/json' });
442
495
  triggerDownload(blob, `${currentData.name}.json`);
443
496
  flashButton(document.getElementById('btn-download-json'), '✔ Saved .json');
@@ -453,7 +506,7 @@
453
506
  // Download Both (.specs.zip)
454
507
  document.getElementById('btn-download-both').onclick = () => {
455
508
  if (!currentData) return;
456
- const jsonStr = JSON.stringify(currentData.spec, null, 2);
509
+ const jsonStr = JSON.stringify(currentData.spec);
457
510
  const enc = new TextEncoder();
458
511
  const files = [
459
512
  { name: `${currentData.name}.json`, data: enc.encode(jsonStr) }