draply-dev 1.2.0 → 1.2.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.
@@ -1,14 +1,12 @@
1
1
  /**
2
- * Draply Advanced Features
3
- * - Visual Diff Panel
4
- * - Code Generation (Export)
5
- * - Source Map Integration
2
+ * Draply Features
3
+ * - Diff Panel (view changes)
4
+ * - Export Panel (copy CSS / JSX / Tailwind)
6
5
  */
7
6
  (function () {
8
7
  if (window.__draply_features__) return;
9
8
  window.__draply_features__ = true;
10
9
 
11
- // Wait for overlay to initialize
12
10
  const waitForOverlay = setInterval(() => {
13
11
  if (!window.__pixelshift__) return;
14
12
  clearInterval(waitForOverlay);
@@ -17,891 +15,341 @@
17
15
 
18
16
  function initFeatures() {
19
17
  // ══════════════════════════════════════════
20
- // INJECT ADDITIONAL STYLES
18
+ // STYLES
21
19
  // ══════════════════════════════════════════
22
- const featureStyles = document.createElement('style');
23
- featureStyles.textContent = `
24
- /* ── FEATURE TABS ──────────────────────────────── */
25
- .ps-feature-tabs {
20
+ const s = document.createElement('style');
21
+ s.textContent = `
22
+ /* ── FEATURE TABS ────────────────────────── */
23
+ .ps-ftabs {
26
24
  display: flex;
27
25
  gap: 0;
28
- margin: 0 -12px;
29
- padding: 0 12px;
30
26
  border-bottom: 1px solid #1e1e3a;
31
- margin-bottom: 10px;
27
+ margin-bottom: 8px;
32
28
  }
33
- .ps-feature-tab {
29
+ .ps-ftab {
34
30
  flex: 1;
35
31
  background: none;
36
32
  border: none;
37
33
  color: #555577;
38
34
  font-family: 'Space Mono', monospace;
39
35
  font-size: 9px;
40
- padding: 8px 4px;
36
+ padding: 6px 4px;
41
37
  cursor: pointer;
42
38
  border-bottom: 2px solid transparent;
43
39
  transition: all .2s;
44
40
  text-transform: uppercase;
45
41
  letter-spacing: 0.5px;
46
42
  }
47
- .ps-feature-tab:hover { color: #aaaacc; }
48
- .ps-feature-tab.active {
43
+ .ps-ftab:hover { color: #aaaacc; }
44
+ .ps-ftab.active {
49
45
  color: #7fff6e;
50
46
  border-bottom-color: #7fff6e;
51
47
  }
52
48
 
53
- /* ── DIFF PANEL ────────────────────────────────── */
54
- #__ps_diff_panel__ {
55
- display: none;
56
- flex-direction: column;
57
- gap: 8px;
58
- padding: 8px 0;
59
- max-height: 250px;
60
- overflow-y: auto;
61
- }
62
- #__ps_diff_panel__::-webkit-scrollbar { width: 4px; }
63
- #__ps_diff_panel__::-webkit-scrollbar-track { background: transparent; }
64
- #__ps_diff_panel__::-webkit-scrollbar-thumb { background: #2a2a44; border-radius: 2px; }
65
- #__ps_diff_panel__.v { display: flex; }
66
-
67
- .ps-diff-empty {
68
- color: #555577;
69
- font-size: 10px;
70
- text-align: center;
71
- padding: 20px 0;
72
- }
73
-
74
- .ps-diff-item {
75
- background: #0d0d1a;
76
- border: 1px solid #1e1e3a;
77
- border-radius: 6px;
78
- padding: 8px 10px;
79
- font-size: 9px;
80
- }
81
- .ps-diff-item:hover {
82
- border-color: #2a2a5a;
83
- }
84
- .ps-diff-selector {
85
- color: #7fff6e;
86
- font-weight: 600;
87
- margin-bottom: 6px;
88
- white-space: nowrap;
89
- overflow: hidden;
90
- text-overflow: ellipsis;
91
- }
92
- .ps-diff-row {
49
+ /* ── FEATURES CONTAINER ──────────────────── */
50
+ #__ps_feat__ {
93
51
  display: flex;
94
- align-items: center;
95
- gap: 6px;
96
- margin: 3px 0;
97
- font-size: 9px;
98
- }
99
- .ps-diff-prop {
100
- color: #8888aa;
101
- min-width: 80px;
102
- flex-shrink: 0;
103
- }
104
- .ps-diff-old {
105
- color: #ff6b6b;
106
- text-decoration: line-through;
107
- opacity: 0.7;
108
- flex: 1;
109
- overflow: hidden;
110
- text-overflow: ellipsis;
111
- white-space: nowrap;
112
- }
113
- .ps-diff-arrow {
114
- color: #555577;
115
- flex-shrink: 0;
116
- }
117
- .ps-diff-new {
118
- color: #7fff6e;
119
- flex: 1;
120
- overflow: hidden;
121
- text-overflow: ellipsis;
122
- white-space: nowrap;
123
- }
124
-
125
- .ps-diff-actions {
126
- display: flex;
127
- gap: 6px;
128
- margin-top: 6px;
129
- }
130
- .ps-diff-btn {
131
- background: none;
132
- border: 1px solid #2a2a44;
133
- color: #8888aa;
134
- border-radius: 4px;
135
- padding: 3px 8px;
136
- font-size: 8px;
137
- cursor: pointer;
138
- font-family: 'Space Mono', monospace;
139
- transition: all .15s;
140
- }
141
- .ps-diff-btn:hover {
142
- border-color: #7fff6e;
143
- color: #7fff6e;
144
- }
145
- .ps-diff-btn.revert:hover {
146
- border-color: #ff6b6b;
147
- color: #ff6b6b;
148
- }
149
-
150
- /* ── SPLIT VIEW OVERLAY ───────────────────────── */
151
- #__ps_splitview__ {
152
- display: none;
153
- position: fixed;
154
- top: 0; left: 0;
155
- width: 100%; height: 100%;
156
- z-index: 999998;
157
- pointer-events: none;
158
- }
159
- #__ps_splitview__.v { display: block; }
160
- #__ps_splitview__::after {
161
- content: '';
162
- position: absolute;
163
- top: 0;
164
- left: 50%;
165
- width: 3px;
166
- height: 100%;
167
- background: #7fff6e;
168
- transform: translateX(-50%);
169
- box-shadow: 0 0 12px rgba(127,255,110,0.4);
170
- }
171
- .ps-split-label {
172
- position: absolute;
173
- top: 8px;
174
- padding: 4px 12px;
175
- background: rgba(10,10,26,0.9);
176
- border: 1px solid #1e1e3a;
177
- border-radius: 4px;
178
- font-family: 'Space Mono', monospace;
179
- font-size: 10px;
180
- color: #aaaacc;
181
- pointer-events: none;
52
+ flex-direction: column;
53
+ padding: 0 12px 8px;
54
+ border-top: 1px solid #1e1e3a;
55
+ margin-top: 8px;
182
56
  }
183
- .ps-split-label.before { left: 12px; }
184
- .ps-split-label.after { right: 12px; }
185
57
 
186
- /* ── EXPORT PANEL ──────────────────────────────── */
187
- #__ps_export_panel__ {
58
+ /* ── DIFF PANEL ──────────────────────────── */
59
+ .ps-dp {
188
60
  display: none;
189
61
  flex-direction: column;
190
- gap: 8px;
191
- padding: 8px 0;
192
- max-height: 250px;
62
+ gap: 6px;
63
+ max-height: 200px;
193
64
  overflow-y: auto;
194
65
  }
195
- #__ps_export_panel__::-webkit-scrollbar { width: 4px; }
196
- #__ps_export_panel__::-webkit-scrollbar-track { background: transparent; }
197
- #__ps_export_panel__::-webkit-scrollbar-thumb { background: #2a2a44; border-radius: 2px; }
198
- #__ps_export_panel__.v { display: flex; }
66
+ .ps-dp::-webkit-scrollbar { width: 3px; }
67
+ .ps-dp::-webkit-scrollbar-track { background: transparent; }
68
+ .ps-dp::-webkit-scrollbar-thumb { background: #2a2a44; border-radius: 2px; }
69
+ .ps-dp.v { display: flex; }
199
70
 
200
- .ps-export-format-tabs {
201
- display: flex;
202
- flex-wrap: wrap;
203
- gap: 4px;
204
- margin-bottom: 6px;
205
- }
206
- .ps-export-format-btn {
71
+ .ps-di {
207
72
  background: #0d0d1a;
208
73
  border: 1px solid #1e1e3a;
209
- color: #8888aa;
210
- border-radius: 4px;
211
- padding: 4px 8px;
212
- font-size: 8px;
213
- cursor: pointer;
214
- font-family: 'Space Mono', monospace;
215
- transition: all .15s;
216
- }
217
- .ps-export-format-btn:hover { border-color: #555577; }
218
- .ps-export-format-btn.active {
219
- border-color: #7fff6e;
220
- color: #7fff6e;
221
- background: rgba(127,255,110,0.05);
222
- }
223
-
224
- .ps-export-code {
225
- background: #0d0d1a;
226
- border: 1px solid #1e1e3a;
227
- border-radius: 6px;
228
- padding: 10px;
229
- font-family: 'Space Mono', monospace;
230
- font-size: 9px;
231
- color: #ccccee;
232
- white-space: pre-wrap;
233
- word-break: break-all;
234
- max-height: 300px;
235
- overflow-y: auto;
236
- line-height: 1.6;
237
- position: relative;
238
- }
239
- .ps-export-code::-webkit-scrollbar { width: 4px; }
240
- .ps-export-code::-webkit-scrollbar-track { background: transparent; }
241
- .ps-export-code::-webkit-scrollbar-thumb { background: #2a2a44; border-radius: 2px; }
242
-
243
- .ps-export-copy {
244
- background: linear-gradient(135deg, #7fff6e22, #7fff6e11);
245
- border: 1px solid #7fff6e44;
246
- color: #7fff6e;
247
74
  border-radius: 5px;
248
- padding: 6px 16px;
249
- font-size: 10px;
250
- cursor: pointer;
251
- font-family: 'Space Mono', monospace;
252
- transition: all .15s;
253
- text-align: center;
254
- margin-top: 4px;
255
- }
256
- .ps-export-copy:hover {
257
- background: linear-gradient(135deg, #7fff6e33, #7fff6e22);
258
- border-color: #7fff6e;
259
- }
260
-
261
- .ps-export-empty {
262
- color: #555577;
263
- font-size: 10px;
264
- text-align: center;
265
- padding: 20px 0;
75
+ padding: 6px 8px;
76
+ font-size: 9px;
266
77
  }
267
-
268
- /* ── SOURCE PANEL ──────────────────────────────── */
269
- #__ps_source_panel__ {
78
+ .ps-di:hover { border-color: #2a2a5a; }
79
+ .ps-ds { color: #7fff6e; font-weight: 600; margin-bottom: 4px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
80
+ .ps-dr { display: flex; align-items: center; gap: 4px; margin: 2px 0; font-size: 9px; }
81
+ .ps-dk { color: #8888aa; min-width: 70px; flex-shrink: 0; }
82
+ .ps-da { color: #555577; flex-shrink: 0; }
83
+ .ps-dv { color: #7fff6e; flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
84
+ .ps-db {
85
+ background: none; border: 1px solid #2a2a44; color: #8888aa;
86
+ border-radius: 3px; padding: 2px 6px; font-size: 8px; cursor: pointer;
87
+ font-family: 'Space Mono', monospace; transition: all .15s; margin-top: 4px;
88
+ }
89
+ .ps-db:hover { border-color: #ff6b6b; color: #ff6b6b; }
90
+ .ps-de { color: #555577; font-size: 9px; text-align: center; padding: 12px 0; }
91
+
92
+ /* ── EXPORT PANEL ────────────────────────── */
93
+ .ps-ep {
270
94
  display: none;
271
95
  flex-direction: column;
272
- gap: 8px;
273
- padding: 8px 0;
96
+ gap: 6px;
97
+ max-height: 200px;
98
+ overflow-y: auto;
274
99
  }
275
- #__ps_source_panel__.v { display: flex; }
100
+ .ps-ep::-webkit-scrollbar { width: 3px; }
101
+ .ps-ep::-webkit-scrollbar-track { background: transparent; }
102
+ .ps-ep::-webkit-scrollbar-thumb { background: #2a2a44; border-radius: 2px; }
103
+ .ps-ep.v { display: flex; }
276
104
 
277
- .ps-source-info {
278
- background: #0d0d1a;
279
- border: 1px solid #1e1e3a;
280
- border-radius: 6px;
281
- padding: 10px;
282
- font-size: 9px;
283
- }
284
- .ps-source-label {
285
- color: #555577;
286
- font-size: 8px;
287
- text-transform: uppercase;
288
- letter-spacing: 0.5px;
289
- margin-bottom: 4px;
290
- }
291
- .ps-source-value {
292
- color: #ccccee;
293
- font-size: 10px;
294
- word-break: break-all;
295
- }
296
- .ps-source-value.highlight {
297
- color: #7fff6e;
298
- }
299
- .ps-source-hint {
300
- background: rgba(127,255,110,0.05);
301
- border: 1px solid #7fff6e33;
302
- border-radius: 6px;
303
- padding: 8px 10px;
304
- font-size: 9px;
305
- color: #aaddaa;
306
- line-height: 1.5;
105
+ .ps-ef { display: flex; flex-wrap: wrap; gap: 3px; margin-bottom: 4px; }
106
+ .ps-eb {
107
+ background: #0d0d1a; border: 1px solid #1e1e3a; color: #8888aa;
108
+ border-radius: 3px; padding: 3px 6px; font-size: 8px; cursor: pointer;
109
+ font-family: 'Space Mono', monospace; transition: all .15s;
307
110
  }
308
- .ps-source-hint-icon {
309
- color: #7fff6e;
310
- margin-right: 4px;
311
- }
312
- .ps-source-loading {
313
- color: #555577;
314
- font-size: 10px;
315
- text-align: center;
316
- padding: 20px 0;
317
- }
318
- .ps-source-empty {
319
- color: #555577;
320
- font-size: 10px;
321
- text-align: center;
322
- padding: 20px 0;
111
+ .ps-eb:hover { border-color: #555577; }
112
+ .ps-eb.active { border-color: #7fff6e; color: #7fff6e; background: rgba(127,255,110,0.05); }
113
+
114
+ .ps-ec {
115
+ background: #0d0d1a; border: 1px solid #1e1e3a; border-radius: 5px;
116
+ padding: 8px; font-family: 'Space Mono', monospace; font-size: 9px;
117
+ color: #ccccee; white-space: pre-wrap; word-break: break-all;
118
+ max-height: 140px; overflow-y: auto; line-height: 1.5;
323
119
  }
120
+ .ps-ec::-webkit-scrollbar { width: 3px; }
121
+ .ps-ec::-webkit-scrollbar-track { background: transparent; }
122
+ .ps-ec::-webkit-scrollbar-thumb { background: #2a2a44; border-radius: 2px; }
324
123
 
325
- /* ── FEATURE PANEL CONTAINER ───────────────────── */
326
- #__ps_features__ {
327
- display: flex;
328
- flex-direction: column;
329
- padding: 0 12px 12px;
330
- border-top: 1px solid #1e1e3a;
331
- margin-top: 10px;
124
+ .ps-cp {
125
+ background: linear-gradient(135deg, #7fff6e22, #7fff6e11);
126
+ border: 1px solid #7fff6e44; color: #7fff6e; border-radius: 4px;
127
+ padding: 5px 12px; font-size: 9px; cursor: pointer;
128
+ font-family: 'Space Mono', monospace; transition: all .15s; text-align: center;
332
129
  }
130
+ .ps-cp:hover { background: linear-gradient(135deg, #7fff6e33, #7fff6e22); border-color: #7fff6e; }
333
131
  `;
334
- document.head.appendChild(featureStyles);
132
+ document.head.appendChild(s);
335
133
 
336
134
  // ══════════════════════════════════════════
337
- // FIND SIDEBAR & INJECT UI
135
+ // BUILD UI
338
136
  // ══════════════════════════════════════════
339
137
  const sidebar = document.getElementById('__ps_sidebar__');
340
138
  if (!sidebar) return;
341
139
 
342
- // Create feature panel container
343
- const featuresDiv = document.createElement('div');
344
- featuresDiv.id = '__ps_features__';
345
- featuresDiv.innerHTML = `
346
- <div class="ps-feature-tabs">
347
- <button class="ps-feature-tab active" data-ftab="diff">📊 Diff</button>
348
- <button class="ps-feature-tab" data-ftab="export">📦 Export</button>
349
- <button class="ps-feature-tab" data-ftab="source">🔍 Source</button>
140
+ const feat = document.createElement('div');
141
+ feat.id = '__ps_feat__';
142
+ feat.innerHTML = `
143
+ <div class="ps-ftabs">
144
+ <button class="ps-ftab active" data-ft="diff">📊 Diff</button>
145
+ <button class="ps-ftab" data-ft="export">📦 Export</button>
350
146
  </div>
351
-
352
- <!-- DIFF PANEL -->
353
- <div id="__ps_diff_panel__" class="v">
354
- <div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:4px;">
355
- <span style="color:#8888aa;font-size:9px;">Changes</span>
356
- <button id="__ps_split_toggle__" class="ps-diff-btn" style="font-size:8px;">⊞ Split View</button>
357
- </div>
358
- <div id="__ps_diff_list__">
359
- <div class="ps-diff-empty">No changes yet. Edit elements to see diffs.</div>
360
- </div>
361
- </div>
362
-
363
- <!-- EXPORT PANEL -->
364
- <div id="__ps_export_panel__">
365
- <div class="ps-export-format-tabs">
366
- <button class="ps-export-format-btn active" data-format="css">CSS</button>
367
- <button class="ps-export-format-btn" data-format="jsx">JSX Inline</button>
368
- <button class="ps-export-format-btn" data-format="tailwind">Tailwind</button>
369
- <button class="ps-export-format-btn" data-format="modules">CSS Modules</button>
370
- <button class="ps-export-format-btn" data-format="styled">styled-components</button>
371
- </div>
372
- <div id="__ps_export_code__" class="ps-export-code">
373
- <span class="ps-export-empty">No changes to export.</span>
374
- </div>
375
- <button id="__ps_export_copy__" class="ps-export-copy">📋 Copy to Clipboard</button>
147
+ <div class="ps-dp v" id="__ps_dp__">
148
+ <div class="ps-de">No changes yet</div>
376
149
  </div>
377
-
378
- <!-- SOURCE PANEL -->
379
- <div id="__ps_source_panel__">
380
- <div id="__ps_source_project__" class="ps-source-info">
381
- <div class="ps-source-label">Project Framework</div>
382
- <div class="ps-source-value ps-source-loading">Detecting...</div>
383
- </div>
384
- <div id="__ps_source_element__" class="ps-source-info" style="display:none;">
385
- <div class="ps-source-label">Selected Element</div>
386
- <div id="__ps_source_el_info__" class="ps-source-value"></div>
387
- </div>
388
- <div id="__ps_source_file__" class="ps-source-info" style="display:none;">
389
- <div class="ps-source-label">Source File</div>
390
- <div id="__ps_source_file_info__" class="ps-source-value highlight"></div>
391
- </div>
392
- <div id="__ps_source_hint__" class="ps-source-hint" style="display:none;">
393
- <span class="ps-source-hint-icon">💡</span>
394
- <span id="__ps_source_hint_text__"></span>
150
+ <div class="ps-ep" id="__ps_ep__">
151
+ <div class="ps-ef">
152
+ <button class="ps-eb active" data-fmt="css">CSS</button>
153
+ <button class="ps-eb" data-fmt="jsx">JSX</button>
154
+ <button class="ps-eb" data-fmt="tailwind">Tailwind</button>
395
155
  </div>
156
+ <div class="ps-ec" id="__ps_ec__">/* No changes */</div>
157
+ <button class="ps-cp" id="__ps_copy__">📋 Copy</button>
396
158
  </div>
397
159
  `;
398
160
 
399
- // Split view overlay
400
- const splitView = document.createElement('div');
401
- splitView.id = '__ps_splitview__';
402
- splitView.innerHTML = `
403
- <div class="ps-split-label before">BEFORE</div>
404
- <div class="ps-split-label after">AFTER</div>
405
- `;
406
- document.body.appendChild(splitView);
407
-
408
- // Insert features panel before the footer (SAVE CHANGES button)
161
+ // Insert before footer
409
162
  const foot = sidebar.querySelector('.ps-foot');
410
- if (foot) {
411
- sidebar.insertBefore(featuresDiv, foot);
412
- } else {
413
- sidebar.appendChild(featuresDiv);
414
- }
163
+ if (foot) sidebar.insertBefore(feat, foot);
164
+ else sidebar.appendChild(feat);
415
165
 
416
166
  // ══════════════════════════════════════════
417
- // FEATURE TABS
167
+ // TAB SWITCHING
418
168
  // ══════════════════════════════════════════
419
- const ftabs = featuresDiv.querySelectorAll('.ps-feature-tab');
420
- const diffPanel = document.getElementById('__ps_diff_panel__');
421
- const exportPanel = document.getElementById('__ps_export_panel__');
422
- const sourcePanel = document.getElementById('__ps_source_panel__');
169
+ const dp = document.getElementById('__ps_dp__');
170
+ const ep = document.getElementById('__ps_ep__');
171
+ const tabs = feat.querySelectorAll('.ps-ftab');
423
172
 
424
- ftabs.forEach(tab => {
425
- tab.addEventListener('click', () => {
426
- ftabs.forEach(t => t.classList.remove('active'));
173
+ tabs.forEach(tab => {
174
+ tab.onclick = () => {
175
+ tabs.forEach(t => t.classList.remove('active'));
427
176
  tab.classList.add('active');
428
- const which = tab.dataset.ftab;
429
- diffPanel.classList.toggle('v', which === 'diff');
430
- exportPanel.classList.toggle('v', which === 'export');
431
- sourcePanel.classList.toggle('v', which === 'source');
432
- if (which === 'diff') refreshDiffPanel();
433
- if (which === 'export') refreshExportPanel();
434
- if (which === 'source') refreshSourcePanel();
435
- });
177
+ const w = tab.dataset.ft;
178
+ dp.classList.toggle('v', w === 'diff');
179
+ ep.classList.toggle('v', w === 'export');
180
+ if (w === 'diff') refreshDiff();
181
+ if (w === 'export') refreshExport();
182
+ };
436
183
  });
437
184
 
438
185
  // ══════════════════════════════════════════
439
- // VISUAL DIFF
186
+ // DIFF PANEL
440
187
  // ══════════════════════════════════════════
441
- const diffList = document.getElementById('__ps_diff_list__');
442
-
443
- // Hook into history changes by polling
444
- let lastHistoryLength = 0;
188
+ let lastLen = 0;
445
189
  setInterval(() => {
446
- // Access overlay's history via DOM observation
447
- const historyRows = document.querySelectorAll('#__ps__ [data-hid]');
448
- if (historyRows.length !== lastHistoryLength) {
449
- lastHistoryLength = historyRows.length;
450
- refreshDiffPanel();
190
+ const rows = document.querySelectorAll('#__ps__ [data-hid]');
191
+ if (rows.length !== lastLen) {
192
+ lastLen = rows.length;
193
+ refreshDiff();
451
194
  }
452
- }, 300);
195
+ }, 400);
453
196
 
454
- function refreshDiffPanel() {
455
- // Read changes from the unsaved list entries
456
- const historyRows = document.querySelectorAll('#__ps__ [data-hid]');
457
-
458
- if (historyRows.length === 0) {
459
- diffList.innerHTML = '<div class="ps-diff-empty">No changes yet. Edit elements to see diffs.</div>';
197
+ function refreshDiff() {
198
+ const rows = document.querySelectorAll('#__ps__ [data-hid]');
199
+ if (rows.length === 0) {
200
+ dp.innerHTML = '<div class="ps-de">No changes yet</div>';
460
201
  return;
461
202
  }
462
203
 
463
- // Collect change data from the DOM
464
204
  const changes = [];
465
- historyRows.forEach(btn => {
205
+ rows.forEach(btn => {
466
206
  const row = btn.closest('div[style]');
467
207
  if (!row) return;
468
- const selectorDiv = row.querySelector('div[style*="color:#7fff6e"]');
469
- const propsDiv = row.querySelector('div[style*="color:#555577"]');
470
- if (selectorDiv && propsDiv) {
471
- changes.push({
472
- hid: btn.dataset.hid,
473
- selector: selectorDiv.textContent,
474
- propsStr: propsDiv.textContent
475
- });
208
+ const selDiv = row.querySelector('div[style*="color:#7fff6e"]');
209
+ const propDiv = row.querySelector('div[style*="color:#555577"]');
210
+ if (selDiv && propDiv) {
211
+ changes.push({ hid: btn.dataset.hid, sel: selDiv.textContent, props: propDiv.textContent });
476
212
  }
477
213
  });
478
214
 
479
- diffList.innerHTML = '';
215
+ dp.innerHTML = '';
480
216
  changes.forEach(ch => {
481
217
  const item = document.createElement('div');
482
- item.className = 'ps-diff-item';
483
-
484
- // Parse props string
485
- const propPairs = ch.propsStr.split(',').map(p => p.trim()).filter(Boolean);
486
- let rowsHtml = '';
487
- propPairs.forEach(pair => {
488
- const colonIdx = pair.indexOf(':');
489
- if (colonIdx < 0) return;
490
- const prop = pair.substring(0, colonIdx).trim();
491
- const val = pair.substring(colonIdx + 1).trim();
492
- rowsHtml += `
493
- <div class="ps-diff-row">
494
- <span class="ps-diff-prop">${prop}</span>
495
- <span class="ps-diff-arrow">→</span>
496
- <span class="ps-diff-new">${val}</span>
497
- </div>
498
- `;
499
- });
500
-
501
- item.innerHTML = `
502
- <div class="ps-diff-selector">${ch.selector}</div>
503
- ${rowsHtml}
504
- <div class="ps-diff-actions">
505
- <button class="ps-diff-btn revert" data-hid="${ch.hid}">↩ Revert</button>
506
- </div>
507
- `;
508
-
509
- // Revert button
510
- item.querySelector('.revert').addEventListener('click', () => {
511
- const origBtn = document.querySelector(`#__ps__ [data-hid="${ch.hid}"]`);
512
- if (origBtn) origBtn.click();
513
- setTimeout(refreshDiffPanel, 100);
218
+ item.className = 'ps-di';
219
+
220
+ const pairs = ch.props.split(',').map(p => p.trim()).filter(Boolean);
221
+ let html = `<div class="ps-ds">${ch.sel}</div>`;
222
+ pairs.forEach(pair => {
223
+ const ci = pair.indexOf(':');
224
+ if (ci < 0) return;
225
+ html += `<div class="ps-dr"><span class="ps-dk">${pair.substring(0, ci).trim()}</span><span class="ps-da">→</span><span class="ps-dv">${pair.substring(ci + 1).trim()}</span></div>`;
514
226
  });
515
-
516
- diffList.appendChild(item);
227
+ html += `<button class="ps-db" data-hid="${ch.hid}">↩ Revert</button>`;
228
+ item.innerHTML = html;
229
+
230
+ item.querySelector('.ps-db').onclick = () => {
231
+ const orig = document.querySelector(`#__ps__ [data-hid="${ch.hid}"]`);
232
+ if (orig) orig.click();
233
+ setTimeout(refreshDiff, 100);
234
+ };
235
+ dp.appendChild(item);
517
236
  });
518
237
  }
519
238
 
520
- // Split view toggle
521
- const splitToggle = document.getElementById('__ps_split_toggle__');
522
- let splitActive = false;
523
- splitToggle.addEventListener('click', () => {
524
- splitActive = !splitActive;
525
- splitView.classList.toggle('v', splitActive);
526
- splitToggle.textContent = splitActive ? '⊟ Close Split' : '⊞ Split View';
527
- splitToggle.style.borderColor = splitActive ? '#7fff6e' : '';
528
- splitToggle.style.color = splitActive ? '#7fff6e' : '';
529
- });
530
-
531
239
  // ══════════════════════════════════════════
532
- // CODE GENERATION
240
+ // EXPORT PANEL
533
241
  // ══════════════════════════════════════════
534
- const exportCode = document.getElementById('__ps_export_code__');
535
- const exportCopy = document.getElementById('__ps_export_copy__');
536
- const formatBtns = featuresDiv.querySelectorAll('.ps-export-format-btn');
537
- let currentFormat = 'css';
538
-
539
- // CSS to Tailwind mapping (basic properties)
540
- const cssToTailwind = {
541
- 'background-color': (v) => {
542
- if (v === 'transparent') return 'bg-transparent';
543
- return `bg-[${v}]`;
544
- },
545
- 'color': (v) => `text-[${v}]`,
546
- 'font-size': (v) => {
547
- const px = parseInt(v);
548
- const map = { 12: 'text-xs', 14: 'text-sm', 16: 'text-base', 18: 'text-lg', 20: 'text-xl', 24: 'text-2xl', 30: 'text-3xl', 36: 'text-4xl' };
549
- return map[px] || `text-[${v}]`;
550
- },
551
- 'font-weight': (v) => {
552
- const map = { '400': 'font-normal', '500': 'font-medium', '600': 'font-semibold', '700': 'font-bold' };
553
- return map[v] || `font-[${v}]`;
554
- },
555
- 'font-style': (v) => v === 'italic' ? 'italic' : 'not-italic',
556
- 'text-decoration': (v) => {
557
- if (v.includes('underline')) return 'underline';
558
- if (v.includes('line-through')) return 'line-through';
559
- return 'no-underline';
560
- },
561
- 'text-transform': (v) => {
562
- const map = { 'uppercase': 'uppercase', 'lowercase': 'lowercase', 'capitalize': 'capitalize', 'none': 'normal-case' };
563
- return map[v] || '';
564
- },
565
- 'line-height': (v) => `leading-[${v}]`,
566
- 'letter-spacing': (v) => `tracking-[${v}]`,
567
- 'width': (v) => `w-[${v}]`,
568
- 'height': (v) => `h-[${v}]`,
569
- 'left': (v) => `left-[${v}]`,
570
- 'top': (v) => `top-[${v}]`,
571
- 'border': (v) => {
572
- if (v === 'none') return 'border-0';
573
- return `border-[${v}]`;
574
- },
575
- 'border-color': (v) => `border-[${v}]`,
576
- 'z-index': (v) => `z-[${v}]`,
577
- };
578
-
579
- function camelCase(str) {
580
- return str.replace(/-([a-z])/g, (_, c) => c.toUpperCase());
581
- }
582
-
583
- formatBtns.forEach(btn => {
584
- btn.addEventListener('click', () => {
585
- formatBtns.forEach(b => b.classList.remove('active'));
586
- btn.classList.add('active');
587
- currentFormat = btn.dataset.format;
588
- refreshExportPanel();
589
- });
242
+ const ec = document.getElementById('__ps_ec__');
243
+ const copyBtn = document.getElementById('__ps_copy__');
244
+ const fmtBtns = feat.querySelectorAll('.ps-eb');
245
+ let fmt = 'css';
246
+
247
+ fmtBtns.forEach(b => {
248
+ b.onclick = () => {
249
+ fmtBtns.forEach(x => x.classList.remove('active'));
250
+ b.classList.add('active');
251
+ fmt = b.dataset.fmt;
252
+ refreshExport();
253
+ };
590
254
  });
591
255
 
592
- function getChangesData() {
593
- // Collect changes from history rows
594
- const historyRows = document.querySelectorAll('#__ps__ [data-hid]');
595
- const changesMap = new Map();
596
-
597
- historyRows.forEach(btn => {
256
+ function getChanges() {
257
+ const rows = document.querySelectorAll('#__ps__ [data-hid]');
258
+ const map = new Map();
259
+ rows.forEach(btn => {
598
260
  const row = btn.closest('div[style]');
599
261
  if (!row) return;
600
- const selectorDiv = row.querySelector('div[style*="color:#7fff6e"]');
601
- const propsDiv = row.querySelector('div[style*="color:#555577"]');
602
- if (!selectorDiv || !propsDiv) return;
603
-
604
- const selector = selectorDiv.textContent.trim();
605
- const propsStr = propsDiv.textContent.trim();
606
-
607
- if (!changesMap.has(selector)) {
608
- changesMap.set(selector, {});
609
- }
610
-
611
- const props = changesMap.get(selector);
612
- propsStr.split(',').forEach(pair => {
613
- const colonIdx = pair.indexOf(':');
614
- if (colonIdx < 0) return;
615
- const prop = pair.substring(0, colonIdx).trim();
616
- const val = pair.substring(colonIdx + 1).trim();
617
- props[prop] = val;
618
- });
619
- });
620
-
621
- return changesMap;
622
- }
623
-
624
- function generateCSS(changesMap) {
625
- if (changesMap.size === 0) return '/* No changes to export */';
626
- let output = '/* Generated by Draply */\n\n';
627
- changesMap.forEach((props, selector) => {
628
- output += `${selector} {\n`;
629
- Object.entries(props).forEach(([prop, val]) => {
630
- output += ` ${prop}: ${val};\n`;
262
+ const selDiv = row.querySelector('div[style*="color:#7fff6e"]');
263
+ const propDiv = row.querySelector('div[style*="color:#555577"]');
264
+ if (!selDiv || !propDiv) return;
265
+ const sel = selDiv.textContent.trim();
266
+ if (!map.has(sel)) map.set(sel, {});
267
+ const obj = map.get(sel);
268
+ propDiv.textContent.split(',').forEach(pair => {
269
+ const ci = pair.indexOf(':');
270
+ if (ci < 0) return;
271
+ obj[pair.substring(0, ci).trim()] = pair.substring(ci + 1).trim();
631
272
  });
632
- output += '}\n\n';
633
273
  });
634
- return output.trim();
274
+ return map;
635
275
  }
636
276
 
637
- function generateJSX(changesMap) {
638
- if (changesMap.size === 0) return '// No changes to export';
639
- let output = '// Generated by Draply JSX Inline Styles\n\n';
640
- changesMap.forEach((props, selector) => {
641
- output += `// ${selector}\n`;
642
- output += 'const style = {\n';
643
- Object.entries(props).forEach(([prop, val]) => {
644
- const camel = camelCase(prop);
645
- // Numbers for certain props
646
- if (['fontSize', 'lineHeight', 'letterSpacing', 'width', 'height', 'left', 'top', 'zIndex'].includes(camel) && val.endsWith('px')) {
647
- if (camel === 'zIndex') {
648
- output += ` ${camel}: ${parseInt(val)},\n`;
649
- } else {
650
- output += ` ${camel}: '${val}',\n`;
651
- }
652
- } else {
653
- output += ` ${camel}: '${val}',\n`;
654
- }
655
- });
656
- output += '};\n\n';
657
- });
658
- return output.trim();
277
+ function refreshExport() {
278
+ const m = getChanges();
279
+ if (m.size === 0) { ec.textContent = '/* No changes */'; return; }
280
+ switch (fmt) {
281
+ case 'css': ec.textContent = genCSS(m); break;
282
+ case 'jsx': ec.textContent = genJSX(m); break;
283
+ case 'tailwind': ec.textContent = genTW(m); break;
284
+ }
659
285
  }
660
286
 
661
- function generateTailwind(changesMap) {
662
- if (changesMap.size === 0) return '<!-- No changes to export -->';
663
- let output = '<!-- Generated by Draply — Tailwind Classes -->\n\n';
664
- changesMap.forEach((props, selector) => {
665
- const classes = [];
666
- Object.entries(props).forEach(([prop, val]) => {
667
- const mapper = cssToTailwind[prop];
668
- if (mapper) {
669
- const cls = mapper(val);
670
- if (cls) classes.push(cls);
671
- } else {
672
- classes.push(`[${prop}:${val}]`);
673
- }
674
- });
675
- output += `/* ${selector} */\n`;
676
- output += `class="${classes.join(' ')}"\n\n`;
287
+ function genCSS(m) {
288
+ let o = '';
289
+ m.forEach((p, s) => {
290
+ o += `${s} {\n`;
291
+ Object.entries(p).forEach(([k, v]) => o += ` ${k}: ${v};\n`);
292
+ o += '}\n\n';
677
293
  });
678
- return output.trim();
294
+ return o.trim();
679
295
  }
680
296
 
681
- function generateModules(changesMap) {
682
- if (changesMap.size === 0) return '/* No changes to export */';
683
- let output = '/* Generated by Draply — CSS Modules */\n\n';
684
- let counter = 0;
685
- changesMap.forEach((props, selector) => {
686
- const className = selector.replace(/[^a-zA-Z0-9]/g, '').substring(0, 20) || `element${counter++}`;
687
- output += `.${className} {\n`;
688
- Object.entries(props).forEach(([prop, val]) => {
689
- output += ` ${prop}: ${val};\n`;
297
+ function genJSX(m) {
298
+ let o = '';
299
+ m.forEach((p, s) => {
300
+ o += `// ${s}\nconst style = {\n`;
301
+ Object.entries(p).forEach(([k, v]) => {
302
+ const c = k.replace(/-([a-z])/g, (_, l) => l.toUpperCase());
303
+ o += ` ${c}: '${v}',\n`;
690
304
  });
691
- output += '}\n\n';
692
- output += `/* Usage: import styles from './module.module.css'; */\n`;
693
- output += `/* <div className={styles.${className}}> */\n\n`;
305
+ o += '};\n\n';
694
306
  });
695
- return output.trim();
307
+ return o.trim();
696
308
  }
697
309
 
698
- function generateStyled(changesMap) {
699
- if (changesMap.size === 0) return '// No changes to export';
700
- let output = "// Generated by Draply — styled-components\nimport styled from 'styled-components';\n\n";
701
- let counter = 0;
702
- changesMap.forEach((props, selector) => {
703
- const name = 'Styled' + (selector.replace(/[^a-zA-Z0-9]/g, '').substring(0, 15) || `Element${counter++}`);
704
- output += `const ${name} = styled.div\`\n`;
705
- Object.entries(props).forEach(([prop, val]) => {
706
- output += ` ${prop}: ${val};\n`;
310
+ function genTW(m) {
311
+ const twMap = {
312
+ 'background-color': v => `bg-[${v}]`,
313
+ 'color': v => `text-[${v}]`,
314
+ 'font-size': v => `text-[${v}]`,
315
+ 'font-weight': v => { const n = { '400': 'font-normal', '500': 'font-medium', '600': 'font-semibold', '700': 'font-bold' }; return n[v] || `font-[${v}]`; },
316
+ 'width': v => `w-[${v}]`,
317
+ 'height': v => `h-[${v}]`,
318
+ 'left': v => `left-[${v}]`,
319
+ 'top': v => `top-[${v}]`,
320
+ 'border': v => v === 'none' ? 'border-0' : `border-[${v}]`,
321
+ 'z-index': v => `z-[${v}]`,
322
+ };
323
+ let o = '';
324
+ m.forEach((p, s) => {
325
+ const cls = Object.entries(p).map(([k, v]) => {
326
+ const fn = twMap[k];
327
+ return fn ? fn(v) : `[${k}:${v}]`;
707
328
  });
708
- output += '`;\n\n';
329
+ o += `/* ${s} */\nclass="${cls.join(' ')}"\n\n`;
709
330
  });
710
- return output.trim();
711
- }
712
-
713
- function refreshExportPanel() {
714
- const changesMap = getChangesData();
715
-
716
- let code = '';
717
- switch (currentFormat) {
718
- case 'css': code = generateCSS(changesMap); break;
719
- case 'jsx': code = generateJSX(changesMap); break;
720
- case 'tailwind': code = generateTailwind(changesMap); break;
721
- case 'modules': code = generateModules(changesMap); break;
722
- case 'styled': code = generateStyled(changesMap); break;
723
- }
724
-
725
- exportCode.textContent = code;
331
+ return o.trim();
726
332
  }
727
333
 
728
- exportCopy.addEventListener('click', async () => {
334
+ copyBtn.onclick = async () => {
729
335
  try {
730
- await navigator.clipboard.writeText(exportCode.textContent);
731
- showToast('📋 Copied to clipboard!');
732
- exportCopy.textContent = '✓ Copied!';
733
- setTimeout(() => { exportCopy.textContent = '📋 Copy to Clipboard'; }, 1500);
336
+ await navigator.clipboard.writeText(ec.textContent);
734
337
  } catch {
735
- // Fallback
736
338
  const ta = document.createElement('textarea');
737
- ta.value = exportCode.textContent;
339
+ ta.value = ec.textContent;
738
340
  document.body.appendChild(ta);
739
341
  ta.select();
740
342
  document.execCommand('copy');
741
343
  document.body.removeChild(ta);
742
- showToast('📋 Copied to clipboard!');
743
- exportCopy.textContent = '✓ Copied!';
744
- setTimeout(() => { exportCopy.textContent = '📋 Copy to Clipboard'; }, 1500);
745
- }
746
- });
747
-
748
- // ══════════════════════════════════════════
749
- // SOURCE MAP INTEGRATION
750
- // ══════════════════════════════════════════
751
- let projectInfo = null;
752
-
753
- async function detectProject() {
754
- try {
755
- const resp = await fetch('/draply-project-info');
756
- if (resp.ok) {
757
- projectInfo = await resp.json();
758
- updateProjectUI();
759
- }
760
- } catch {
761
- projectInfo = { framework: 'unknown', cssStrategy: 'external' };
762
- updateProjectUI();
763
- }
764
- }
765
-
766
- function updateProjectUI() {
767
- const projEl = document.querySelector('#__ps_source_project__ .ps-source-value');
768
- if (!projEl || !projectInfo) return;
769
-
770
- const fwNames = {
771
- 'react': '⚛️ React',
772
- 'next': '▲ Next.js',
773
- 'vue': '💚 Vue.js',
774
- 'nuxt': '💚 Nuxt',
775
- 'angular': '🅰️ Angular',
776
- 'svelte': '🔶 Svelte',
777
- 'vite': '⚡ Vite',
778
- 'unknown': '📁 Static/Unknown'
779
- };
780
-
781
- const cssNames = {
782
- 'tailwind': 'Tailwind CSS',
783
- 'css-modules': 'CSS Modules',
784
- 'styled-components': 'styled-components',
785
- 'emotion': 'Emotion',
786
- 'sass': 'SASS/SCSS',
787
- 'external': 'External CSS',
788
- 'unknown': 'Unknown'
789
- };
790
-
791
- projEl.classList.remove('ps-source-loading');
792
- projEl.classList.add('highlight');
793
- projEl.textContent = `${fwNames[projectInfo.framework] || projectInfo.framework}`;
794
-
795
- if (projectInfo.cssStrategy && projectInfo.cssStrategy !== 'unknown') {
796
- projEl.textContent += ` + ${cssNames[projectInfo.cssStrategy] || projectInfo.cssStrategy}`;
797
- }
798
- }
799
-
800
- function refreshSourcePanel() {
801
- if (!projectInfo) detectProject();
802
-
803
- // Check for selected element
804
- const selectedEl = document.querySelector('.__ps__');
805
- const elInfo = document.getElementById('__ps_source_el_info__');
806
- const elSection = document.getElementById('__ps_source_element__');
807
- const fileSection = document.getElementById('__ps_source_file__');
808
- const fileInfo = document.getElementById('__ps_source_file_info__');
809
- const hintSection = document.getElementById('__ps_source_hint__');
810
- const hintText = document.getElementById('__ps_source_hint_text__');
811
-
812
- if (!selectedEl) {
813
- if (elSection) elSection.style.display = 'none';
814
- if (fileSection) fileSection.style.display = 'none';
815
- if (hintSection) hintSection.style.display = 'none';
816
- return;
817
- }
818
-
819
- if (elSection) {
820
- elSection.style.display = '';
821
- const tag = selectedEl.tagName.toLowerCase();
822
- const cls = [...selectedEl.classList].filter(c => !c.startsWith('__')).join('.');
823
- const id = selectedEl.id ? `#${selectedEl.id}` : '';
824
- elInfo.textContent = `<${tag}${id}${cls ? '.' + cls : ''}>`;
825
- }
826
-
827
- // Try to find source via data attributes
828
- const sourceFile = selectedEl.dataset?.source ||
829
- selectedEl.getAttribute('data-source') ||
830
- findReactSource(selectedEl);
831
-
832
- if (sourceFile && fileSection) {
833
- fileSection.style.display = '';
834
- fileInfo.textContent = sourceFile;
835
- } else if (fileSection) {
836
- fileSection.style.display = 'none';
837
344
  }
345
+ copyBtn.textContent = '✓ Copied!';
346
+ setTimeout(() => { copyBtn.textContent = '📋 Copy'; }, 1500);
347
+ };
838
348
 
839
- // Generate insertion hint
840
- if (hintSection && hintText && projectInfo) {
841
- hintSection.style.display = '';
842
- const tag = selectedEl.tagName.toLowerCase();
843
- const cls = [...selectedEl.classList].filter(c => !c.startsWith('__'))[0];
844
-
845
- if (projectInfo.framework === 'react' || projectInfo.framework === 'next') {
846
- if (projectInfo.cssStrategy === 'tailwind') {
847
- hintText.textContent = `Add Tailwind classes directly to the ${cls ? '.' + cls : tag} JSX element's className prop.`;
848
- } else if (projectInfo.cssStrategy === 'css-modules') {
849
- hintText.textContent = `Add styles to the corresponding .module.css file for ${cls ? '.' + cls : tag}.`;
850
- } else if (projectInfo.cssStrategy === 'styled-components') {
851
- hintText.textContent = `Modify the styled() definition for ${cls ? '.' + cls : tag} in the component file.`;
852
- } else {
853
- hintText.textContent = `Add the CSS rules for ${cls ? '.' + cls : tag} to your stylesheet or use inline styles in JSX.`;
854
- }
855
- } else if (projectInfo.framework === 'vue' || projectInfo.framework === 'nuxt') {
856
- hintText.textContent = `Add styles in the <style scoped> section of the ${cls ? '.' + cls : tag} component.`;
857
- } else {
858
- hintText.textContent = `Add the generated CSS to your stylesheet targeting ${cls ? '.' + cls : tag}.`;
859
- }
860
- }
861
- }
862
-
863
- function findReactSource(el) {
864
- // Try to find React fiber for source info
865
- const fiberKey = Object.keys(el).find(k => k.startsWith('__reactFiber') || k.startsWith('__reactInternalInstance'));
866
- if (!fiberKey) return null;
867
-
868
- try {
869
- let fiber = el[fiberKey];
870
- while (fiber) {
871
- if (fiber._debugSource) {
872
- return `${fiber._debugSource.fileName}:${fiber._debugSource.lineNumber}`;
873
- }
874
- if (fiber._debugOwner && fiber._debugOwner._debugSource) {
875
- return `${fiber._debugOwner._debugSource.fileName}:${fiber._debugOwner._debugSource.lineNumber}`;
876
- }
877
- fiber = fiber.return;
878
- }
879
- } catch { /* ignore */ }
880
- return null;
881
- }
882
-
883
- // ══════════════════════════════════════════
884
- // UTILS
885
- // ══════════════════════════════════════════
349
+ // Toast helper
886
350
  function showToast(msg) {
887
- const tst = document.getElementById('__tst__');
888
- if (tst) {
889
- tst.textContent = msg;
890
- tst.classList.add('v');
891
- clearTimeout(tst._t);
892
- tst._t = setTimeout(() => tst.classList.remove('v'), 2800);
893
- }
351
+ const t = document.getElementById('__ps_tst__');
352
+ if (t) { t.textContent = msg; t.classList.add('v'); clearTimeout(t._t); t._t = setTimeout(() => t.classList.remove('v'), 2800); }
894
353
  }
895
-
896
- // Auto-detect project on load
897
- setTimeout(detectProject, 500);
898
-
899
- // Listen for element selection changes
900
- setInterval(() => {
901
- const activeTab = featuresDiv.querySelector('.ps-feature-tab.active');
902
- if (activeTab && activeTab.dataset.ftab === 'source') {
903
- refreshSourcePanel();
904
- }
905
- }, 1000);
906
354
  }
907
355
  })();