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.
- package/bin/cli.js +11 -481
- package/package.json +1 -1
- package/src/draply-features.js +223 -775
- package/src/overlay.js +3 -15
package/src/draply-features.js
CHANGED
|
@@ -1,14 +1,12 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Draply
|
|
3
|
-
* -
|
|
4
|
-
* -
|
|
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
|
-
//
|
|
18
|
+
// STYLES
|
|
21
19
|
// ══════════════════════════════════════════
|
|
22
|
-
const
|
|
23
|
-
|
|
24
|
-
/* ── FEATURE TABS
|
|
25
|
-
.ps-
|
|
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:
|
|
27
|
+
margin-bottom: 8px;
|
|
32
28
|
}
|
|
33
|
-
.ps-
|
|
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:
|
|
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-
|
|
48
|
-
.ps-
|
|
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
|
-
/* ──
|
|
54
|
-
#
|
|
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
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
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
|
-
/* ──
|
|
187
|
-
|
|
58
|
+
/* ── DIFF PANEL ──────────────────────────── */
|
|
59
|
+
.ps-dp {
|
|
188
60
|
display: none;
|
|
189
61
|
flex-direction: column;
|
|
190
|
-
gap:
|
|
191
|
-
|
|
192
|
-
max-height: 250px;
|
|
62
|
+
gap: 6px;
|
|
63
|
+
max-height: 200px;
|
|
193
64
|
overflow-y: auto;
|
|
194
65
|
}
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
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-
|
|
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
|
|
249
|
-
font-size:
|
|
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
|
-
|
|
269
|
-
|
|
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:
|
|
273
|
-
|
|
96
|
+
gap: 6px;
|
|
97
|
+
max-height: 200px;
|
|
98
|
+
overflow-y: auto;
|
|
274
99
|
}
|
|
275
|
-
|
|
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-
|
|
278
|
-
|
|
279
|
-
border: 1px solid #1e1e3a;
|
|
280
|
-
border-radius: 6px;
|
|
281
|
-
|
|
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-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
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
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
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(
|
|
132
|
+
document.head.appendChild(s);
|
|
335
133
|
|
|
336
134
|
// ══════════════════════════════════════════
|
|
337
|
-
//
|
|
135
|
+
// BUILD UI
|
|
338
136
|
// ══════════════════════════════════════════
|
|
339
137
|
const sidebar = document.getElementById('__ps_sidebar__');
|
|
340
138
|
if (!sidebar) return;
|
|
341
139
|
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
<button class="ps-
|
|
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
|
-
|
|
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
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
<
|
|
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
|
-
//
|
|
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
|
-
|
|
412
|
-
} else {
|
|
413
|
-
sidebar.appendChild(featuresDiv);
|
|
414
|
-
}
|
|
163
|
+
if (foot) sidebar.insertBefore(feat, foot);
|
|
164
|
+
else sidebar.appendChild(feat);
|
|
415
165
|
|
|
416
166
|
// ══════════════════════════════════════════
|
|
417
|
-
//
|
|
167
|
+
// TAB SWITCHING
|
|
418
168
|
// ══════════════════════════════════════════
|
|
419
|
-
const
|
|
420
|
-
const
|
|
421
|
-
const
|
|
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
|
-
|
|
425
|
-
tab.
|
|
426
|
-
|
|
173
|
+
tabs.forEach(tab => {
|
|
174
|
+
tab.onclick = () => {
|
|
175
|
+
tabs.forEach(t => t.classList.remove('active'));
|
|
427
176
|
tab.classList.add('active');
|
|
428
|
-
const
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
if (
|
|
433
|
-
|
|
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
|
-
//
|
|
186
|
+
// DIFF PANEL
|
|
440
187
|
// ══════════════════════════════════════════
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
// Hook into history changes by polling
|
|
444
|
-
let lastHistoryLength = 0;
|
|
188
|
+
let lastLen = 0;
|
|
445
189
|
setInterval(() => {
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
refreshDiffPanel();
|
|
190
|
+
const rows = document.querySelectorAll('#__ps__ [data-hid]');
|
|
191
|
+
if (rows.length !== lastLen) {
|
|
192
|
+
lastLen = rows.length;
|
|
193
|
+
refreshDiff();
|
|
451
194
|
}
|
|
452
|
-
},
|
|
195
|
+
}, 400);
|
|
453
196
|
|
|
454
|
-
function
|
|
455
|
-
|
|
456
|
-
|
|
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
|
-
|
|
205
|
+
rows.forEach(btn => {
|
|
466
206
|
const row = btn.closest('div[style]');
|
|
467
207
|
if (!row) return;
|
|
468
|
-
const
|
|
469
|
-
const
|
|
470
|
-
if (
|
|
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
|
-
|
|
215
|
+
dp.innerHTML = '';
|
|
480
216
|
changes.forEach(ch => {
|
|
481
217
|
const item = document.createElement('div');
|
|
482
|
-
item.className = 'ps-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
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
|
-
|
|
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
|
-
//
|
|
240
|
+
// EXPORT PANEL
|
|
533
241
|
// ══════════════════════════════════════════
|
|
534
|
-
const
|
|
535
|
-
const
|
|
536
|
-
const
|
|
537
|
-
let
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
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
|
|
593
|
-
|
|
594
|
-
const
|
|
595
|
-
|
|
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
|
|
601
|
-
const
|
|
602
|
-
if (!
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
const
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
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
|
|
274
|
+
return map;
|
|
635
275
|
}
|
|
636
276
|
|
|
637
|
-
function
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
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
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
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
|
|
294
|
+
return o.trim();
|
|
679
295
|
}
|
|
680
296
|
|
|
681
|
-
function
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
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
|
-
|
|
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
|
|
307
|
+
return o.trim();
|
|
696
308
|
}
|
|
697
309
|
|
|
698
|
-
function
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
const
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
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
|
-
|
|
329
|
+
o += `/* ${s} */\nclass="${cls.join(' ')}"\n\n`;
|
|
709
330
|
});
|
|
710
|
-
return
|
|
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
|
-
|
|
334
|
+
copyBtn.onclick = async () => {
|
|
729
335
|
try {
|
|
730
|
-
await navigator.clipboard.writeText(
|
|
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 =
|
|
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
|
-
|
|
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
|
|
888
|
-
if (
|
|
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
|
})();
|