draply-dev 1.4.1 → 1.4.3

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 CHANGED
@@ -80,6 +80,19 @@ const server = http.createServer((req, res) => {
80
80
  });
81
81
  return;
82
82
  }
83
+ // ── Serve assets_draply ───────────────────────────────────────────────────
84
+ if (req.url.startsWith('/assets_draply/')) {
85
+ const filePath = path.join(process.cwd(), req.url.split('?')[0]);
86
+ if (fs.existsSync(filePath)) {
87
+ const ext = path.extname(filePath).toLowerCase();
88
+ const mime = { '.png': 'image/png', '.jpg': 'image/jpeg', '.jpeg': 'image/jpeg', '.gif': 'image/gif', '.svg': 'image/svg+xml', '.webp': 'image/webp' };
89
+ res.writeHead(200, { 'Content-Type': mime[ext] || 'application/octet-stream' });
90
+ fs.createReadStream(filePath).pipe(res);
91
+ } else {
92
+ res.writeHead(404); res.end();
93
+ }
94
+ return;
95
+ }
83
96
 
84
97
  // ── Config endpoint (get/set API key) ───────────────────────────────────────
85
98
  const configPath = path.join(process.cwd(), 'draply.config.json');
@@ -198,13 +211,23 @@ const server = http.createServer((req, res) => {
198
211
  if (ctxEnd === -1 || e > ctxEnd) ctxEnd = e;
199
212
  }
200
213
  }
214
+ if (ctxStart === -1 && items.some(c => c.type === 'create')) {
215
+ for (let i = lines.length - 1; i >= 0; i--) {
216
+ if (lines[i].includes('</body>')) {
217
+ ctxStart = Math.max(0, i - 20);
218
+ ctxEnd = Math.min(lines.length - 1, i + 20);
219
+ break;
220
+ }
221
+ }
222
+ }
201
223
  if (ctxStart === -1) { ctxStart = 0; ctxEnd = Math.min(lines.length - 1, 150); }
202
224
 
203
225
  const snippet = lines.slice(ctxStart, ctxEnd + 1).join('\n');
204
226
 
205
227
  items.forEach(item => {
206
228
  const propsStr = Object.entries(item.props).map(([k, v]) => ` ${k}: ${v}`).join('\n');
207
- changesBlock += `\nTarget Selector: ${item.selector}\nChanges:\n${propsStr}\n`;
229
+ changesBlock += `\nTarget Selector: ${item.selector}\nType: ${item.type}\nChanges:\n${propsStr}\n`;
230
+ if (item.type === 'create') changesBlock += `HTML to Insert: ${item.outerHTML}\n`;
208
231
  });
209
232
 
210
233
  const prompt = `You are a strict code editor applying style changes to a file.
@@ -223,8 +246,10 @@ Rules:
223
246
  - The content inside <search> must be an EXACT substring from the file snippet above (including indentation).
224
247
  - Update HTML/JSX inline styles or Tailwind classes appropriately.
225
248
  - REPLACE old style values if they exist, DO NOT duplicate keys!
226
- - If 'innerText' is provided in Changes, update the text content inside the target HTML element! Use ONLY the exact text provided in the request. DO NOT add signatures, names, attributions, or "complete" the text based on context.
249
+ - If 'innerText' or 'innerHTML' is provided in Changes, update the text content inside the target HTML element! Use ONLY the exact text provided in the request (preserving internal HTML tags like spans for colors/styles if present). DO NOT add signatures, names, attributions, or "complete" the text based on context.
227
250
  - If 'src' is provided in Changes, update the src attribute of the target HTML element!
251
+ - If a change is marked for a new element (type: create) and you cannot find the target selector, INSERT the provided 'outerHTML' before the closing </body> tag or in an appropriate container.
252
+
228
253
 
229
254
  Example response:
230
255
  <patch>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "draply-dev",
3
- "version": "1.4.1",
3
+ "version": "1.4.3",
4
4
  "description": "Visual overlay for any frontend project — move, resize, restyle live in the browser, save to CSS",
5
5
  "author": "Arman",
6
6
  "type": "commonjs",
package/src/overlay.js CHANGED
@@ -435,7 +435,7 @@
435
435
 
436
436
  /* ── PLACED ASSETS ────────────────────────────────── */
437
437
  .ps-asset-placed {
438
- position: fixed;
438
+ position: absolute;
439
439
  z-index: 999990;
440
440
  cursor: grab;
441
441
  user-select: none;
@@ -1165,14 +1165,14 @@
1165
1165
  // Make text directly editable
1166
1166
  if (!el.isContentEditable) {
1167
1167
  el.contentEditable = 'true';
1168
- el.dataset.origText = el.innerText || '';
1168
+ el.dataset.origText = el.innerHTML || '';
1169
1169
  el.focus();
1170
1170
 
1171
1171
  const finishEdit = () => {
1172
1172
  el.contentEditable = 'false';
1173
1173
  el.removeEventListener('blur', finishEdit);
1174
- if (el.innerText !== el.dataset.origText) {
1175
- rec(el, { innerText: el.innerText }, { innerText: el.dataset.origText });
1174
+ if (el.innerHTML !== el.dataset.origText) {
1175
+ rec(el, { innerHTML: el.innerHTML }, { innerHTML: el.dataset.origText });
1176
1176
  toast('Text updated!');
1177
1177
  }
1178
1178
  };
@@ -1410,7 +1410,7 @@
1410
1410
  width: w + 'px',
1411
1411
  height: h + 'px',
1412
1412
  'z-index': '1',
1413
- });
1413
+ }, null, true); // true = isCreate
1414
1414
 
1415
1415
  toast('✦ Placed — drag to reposition');
1416
1416
  selectPlaced(wrap);
@@ -1494,7 +1494,7 @@
1494
1494
  // Full history — every individual action
1495
1495
  const history = [];
1496
1496
 
1497
- function rec(el, props, prevPropsOverride) {
1497
+ function rec(el, props, prevPropsOverride, isCreate = false) {
1498
1498
  const selector = el.dataset.pixelshiftId ? null : gsel(el);
1499
1499
  const key = el.dataset.pixelshiftId || selector;
1500
1500
 
@@ -1524,19 +1524,22 @@
1524
1524
 
1525
1525
  // Merge into state.changes (for save/apply)
1526
1526
  const ch = {
1527
- type: el.dataset.pixelshiftId ? 'inline' : 'css',
1527
+ type: isCreate ? 'create' : (el.dataset.pixelshiftId ? 'inline' : 'css'),
1528
+ isCreate,
1528
1529
  pixelshiftId: el.dataset.pixelshiftId || null,
1529
1530
  selector,
1530
1531
  exactFile: getReactSource(el),
1531
1532
  file: el.dataset.pixelshiftFile || null,
1532
- props
1533
+ props,
1534
+ tagName: el.tagName.toLowerCase(),
1535
+ outerHTML: el.outerHTML // Send the whole element for 'create' actions
1533
1536
  };
1534
1537
  const i = state.changes.findIndex(c => (c.pixelshiftId || c.selector) === key);
1535
1538
  if (i >= 0) Object.assign(state.changes[i].props, props); else state.changes.push(ch);
1536
1539
 
1537
1540
  // Push to history
1538
1541
  const hid = Date.now() + Math.random();
1539
- history.push({ hid, el, props, prevProps, selector: key });
1542
+ history.push({ hid, el, props, prevProps, selector: key, isCreate });
1540
1543
 
1541
1544
  updateUnsUI();
1542
1545
  }
@@ -1565,11 +1568,18 @@
1565
1568
  }
1566
1569
 
1567
1570
  function revertChange(h) {
1568
- // Re-apply previous inline values (empty string = remove inline style → falls back to stylesheet)
1569
- Object.entries(h.prevProps).forEach(([prop, val]) => {
1570
- const camel = prop.replace(/-([a-z])/g, (_, c) => c.toUpperCase());
1571
- h.el.style[camel] = val; // '' removes inline override, restoring original
1572
- });
1571
+ if (h.isCreate) {
1572
+ h.el.remove();
1573
+ } else {
1574
+ // Re-apply previous inline values
1575
+ Object.entries(h.prevProps).forEach(([prop, val]) => {
1576
+ const camel = prop.replace(/-([a-z])/g, (_, c) => c.toUpperCase());
1577
+ h.el.style[camel] = val;
1578
+ });
1579
+ // Also handle text/html
1580
+ if (h.prevProps.innerHTML !== undefined) h.el.innerHTML = h.prevProps.innerHTML;
1581
+ if (h.prevProps.innerText !== undefined) h.el.innerText = h.prevProps.innerText;
1582
+ }
1573
1583
  // Remove from history
1574
1584
  const idx = history.findIndex(x => x.hid === h.hid);
1575
1585
  if (idx >= 0) history.splice(idx, 1);