@bitovi/vybit 0.5.0 → 0.7.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.
- package/overlay/dist/overlay.js +805 -670
- package/package.json +1 -1
- package/panel/dist/assets/{DesignMode-BAyjCFC8.js → DesignMode-nOHHHxgv.js} +2 -2
- package/panel/dist/assets/index-BYIDlGa3.js +53 -0
- package/panel/dist/assets/index-BqcsQk03.css +1 -0
- package/panel/dist/index.html +4 -4
- package/server/tailwind-v3.ts +476 -146
- package/server/tailwind-v4.ts +228 -115
- package/shared/types.ts +29 -1
- package/panel/dist/assets/index-DVWn5Mcj.js +0 -49
- package/panel/dist/assets/index-_AiRfYt4.css +0 -1
package/overlay/dist/overlay.js
CHANGED
|
@@ -1121,607 +1121,205 @@
|
|
|
1121
1121
|
});
|
|
1122
1122
|
};
|
|
1123
1123
|
|
|
1124
|
-
// overlay/src/
|
|
1125
|
-
var
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
connected = true;
|
|
1132
|
-
send({ type: "REGISTER", role: "overlay" });
|
|
1133
|
-
window.dispatchEvent(new CustomEvent("overlay-ws-connected"));
|
|
1134
|
-
});
|
|
1135
|
-
socket.addEventListener("close", () => {
|
|
1136
|
-
connected = false;
|
|
1137
|
-
socket = null;
|
|
1138
|
-
window.dispatchEvent(new CustomEvent("overlay-ws-disconnected"));
|
|
1139
|
-
setTimeout(() => connect(url), 3e3);
|
|
1140
|
-
});
|
|
1141
|
-
socket.addEventListener("message", (event) => {
|
|
1142
|
-
try {
|
|
1143
|
-
const data = JSON.parse(event.data);
|
|
1144
|
-
for (const handler of handlers) {
|
|
1145
|
-
handler(data);
|
|
1146
|
-
}
|
|
1147
|
-
} catch (err) {
|
|
1148
|
-
console.error("[tw-overlay] Failed to parse message:", err);
|
|
1149
|
-
}
|
|
1150
|
-
});
|
|
1151
|
-
socket.addEventListener("error", (err) => {
|
|
1152
|
-
console.error("[tw-overlay] WebSocket error:", err);
|
|
1153
|
-
});
|
|
1154
|
-
}
|
|
1155
|
-
function send(data) {
|
|
1156
|
-
if (connected && socket) {
|
|
1157
|
-
socket.send(JSON.stringify(data));
|
|
1158
|
-
} else {
|
|
1159
|
-
console.warn("[tw-overlay] Cannot send \u2014 not connected");
|
|
1124
|
+
// overlay/src/containers/ModalContainer.ts
|
|
1125
|
+
var STORAGE_KEY = "tw-modal-bounds";
|
|
1126
|
+
function loadBounds() {
|
|
1127
|
+
try {
|
|
1128
|
+
const stored = localStorage.getItem(STORAGE_KEY);
|
|
1129
|
+
if (stored) return JSON.parse(stored);
|
|
1130
|
+
} catch {
|
|
1160
1131
|
}
|
|
1132
|
+
return { top: 80, left: Math.max(0, window.innerWidth - 440), width: 400, height: 600 };
|
|
1161
1133
|
}
|
|
1162
|
-
function
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
send({ ...data, to: role });
|
|
1167
|
-
}
|
|
1168
|
-
|
|
1169
|
-
// overlay/src/fiber.ts
|
|
1170
|
-
function getFiber(domNode) {
|
|
1171
|
-
const key = Object.keys(domNode).find((k) => k.startsWith("__reactFiber$"));
|
|
1172
|
-
return key ? domNode[key] : null;
|
|
1173
|
-
}
|
|
1174
|
-
function findComponentBoundary(fiber) {
|
|
1175
|
-
let current = fiber.return;
|
|
1176
|
-
while (current) {
|
|
1177
|
-
if (typeof current.type === "function") {
|
|
1178
|
-
return {
|
|
1179
|
-
componentType: current.type,
|
|
1180
|
-
componentName: current.type.displayName || current.type.name || "Unknown",
|
|
1181
|
-
componentFiber: current
|
|
1182
|
-
};
|
|
1183
|
-
}
|
|
1184
|
-
current = current.return;
|
|
1134
|
+
function saveBounds(bounds) {
|
|
1135
|
+
try {
|
|
1136
|
+
localStorage.setItem(STORAGE_KEY, JSON.stringify(bounds));
|
|
1137
|
+
} catch {
|
|
1185
1138
|
}
|
|
1186
|
-
return null;
|
|
1187
1139
|
}
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
const el = document.getElementById(id);
|
|
1192
|
-
if (!el) continue;
|
|
1193
|
-
const key = Object.keys(el).find((k) => k.startsWith("__reactContainer$"));
|
|
1194
|
-
if (key) {
|
|
1195
|
-
const container = el[key];
|
|
1196
|
-
if (container?.stateNode?.current) {
|
|
1197
|
-
return container.stateNode.current;
|
|
1198
|
-
}
|
|
1199
|
-
return container;
|
|
1200
|
-
}
|
|
1140
|
+
var ModalContainer = class {
|
|
1141
|
+
constructor(shadowRoot2) {
|
|
1142
|
+
this.shadowRoot = shadowRoot2;
|
|
1201
1143
|
}
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1144
|
+
name = "modal";
|
|
1145
|
+
host = null;
|
|
1146
|
+
bounds = loadBounds();
|
|
1147
|
+
open(panelUrl) {
|
|
1148
|
+
if (this.host) return;
|
|
1149
|
+
this.bounds = loadBounds();
|
|
1150
|
+
const host = document.createElement("div");
|
|
1151
|
+
host.className = "container-modal";
|
|
1152
|
+
this.applyBounds(host);
|
|
1153
|
+
host.style.position = "fixed";
|
|
1154
|
+
host.style.zIndex = "999999";
|
|
1155
|
+
host.style.background = "#1e1e2e";
|
|
1156
|
+
host.style.borderRadius = "8px";
|
|
1157
|
+
host.style.boxShadow = "0 8px 32px rgba(0,0,0,0.4)";
|
|
1158
|
+
host.style.display = "flex";
|
|
1159
|
+
host.style.flexDirection = "column";
|
|
1160
|
+
host.style.overflow = "hidden";
|
|
1161
|
+
host.style.pointerEvents = "auto";
|
|
1162
|
+
const handle = document.createElement("div");
|
|
1163
|
+
handle.style.cssText = `
|
|
1164
|
+
height: 28px;
|
|
1165
|
+
background: #181825;
|
|
1166
|
+
cursor: move;
|
|
1167
|
+
display: flex;
|
|
1168
|
+
align-items: center;
|
|
1169
|
+
justify-content: center;
|
|
1170
|
+
flex-shrink: 0;
|
|
1171
|
+
user-select: none;
|
|
1172
|
+
`;
|
|
1173
|
+
handle.innerHTML = `<svg width="32" height="6" viewBox="0 0 32 6" fill="none"><rect x="0" y="0" width="32" height="2" rx="1" fill="#585b70"/><rect x="0" y="4" width="32" height="2" rx="1" fill="#585b70"/></svg>`;
|
|
1174
|
+
this.setupDrag(handle, host);
|
|
1175
|
+
host.appendChild(handle);
|
|
1176
|
+
const iframe = document.createElement("iframe");
|
|
1177
|
+
iframe.src = panelUrl;
|
|
1178
|
+
iframe.allow = "microphone";
|
|
1179
|
+
iframe.style.cssText = "flex:1; border:none; width:100%;";
|
|
1180
|
+
host.appendChild(iframe);
|
|
1181
|
+
const gripper = document.createElement("div");
|
|
1182
|
+
gripper.style.cssText = `
|
|
1183
|
+
position: absolute;
|
|
1184
|
+
bottom: 0;
|
|
1185
|
+
right: 0;
|
|
1186
|
+
width: 16px;
|
|
1187
|
+
height: 16px;
|
|
1188
|
+
cursor: nwse-resize;
|
|
1189
|
+
`;
|
|
1190
|
+
gripper.innerHTML = '<span style="position:absolute;bottom:2px;right:4px;color:#585b70;font-size:10px;">\u25E2</span>';
|
|
1191
|
+
this.setupResize(gripper, host, iframe);
|
|
1192
|
+
host.appendChild(gripper);
|
|
1193
|
+
this.shadowRoot.appendChild(host);
|
|
1194
|
+
this.host = host;
|
|
1205
1195
|
}
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
function walk(fiber) {
|
|
1211
|
-
if (!fiber) return;
|
|
1212
|
-
if (fiber.type === componentType) {
|
|
1213
|
-
results.push(fiber);
|
|
1196
|
+
close() {
|
|
1197
|
+
if (this.host) {
|
|
1198
|
+
this.host.remove();
|
|
1199
|
+
this.host = null;
|
|
1214
1200
|
}
|
|
1215
|
-
walk(fiber.child);
|
|
1216
|
-
walk(fiber.sibling);
|
|
1217
|
-
}
|
|
1218
|
-
walk(rootFiber);
|
|
1219
|
-
return results;
|
|
1220
|
-
}
|
|
1221
|
-
|
|
1222
|
-
// overlay/src/grouping.ts
|
|
1223
|
-
function cssEscape(cls) {
|
|
1224
|
-
if (typeof CSS !== "undefined" && CSS.escape) return CSS.escape(cls);
|
|
1225
|
-
return cls.replace(/([^\w-])/g, "\\$1");
|
|
1226
|
-
}
|
|
1227
|
-
function buildSelector(tag, classes) {
|
|
1228
|
-
return tag.toLowerCase() + classes.map((c) => `.${cssEscape(c)}`).join("");
|
|
1229
|
-
}
|
|
1230
|
-
function parseClassList(className) {
|
|
1231
|
-
if (typeof className !== "string") return [];
|
|
1232
|
-
return className.trim().split(/\s+/).filter(Boolean).sort();
|
|
1233
|
-
}
|
|
1234
|
-
function classDiff(refClasses, candidateClasses) {
|
|
1235
|
-
const added = [];
|
|
1236
|
-
const removed = [];
|
|
1237
|
-
for (const c of candidateClasses) {
|
|
1238
|
-
if (!refClasses.has(c)) added.push(c);
|
|
1239
1201
|
}
|
|
1240
|
-
|
|
1241
|
-
|
|
1202
|
+
isOpen() {
|
|
1203
|
+
return this.host !== null;
|
|
1242
1204
|
}
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
const parts = [];
|
|
1249
|
-
for (const a of added) parts.push(`+${a}`);
|
|
1250
|
-
for (const r of removed) parts.push(`-${r}`);
|
|
1251
|
-
return parts.join(" ");
|
|
1252
|
-
}
|
|
1253
|
-
var MAX_DIFF = 10;
|
|
1254
|
-
var MAX_CANDIDATES = 200;
|
|
1255
|
-
function findExactMatches(clickedEl, shadowHost2) {
|
|
1256
|
-
const classes = parseClassList(typeof clickedEl.className === "string" ? clickedEl.className : "");
|
|
1257
|
-
const tag = clickedEl.tagName;
|
|
1258
|
-
const fiber = getFiber(clickedEl);
|
|
1259
|
-
const boundary = fiber ? findComponentBoundary(fiber) : null;
|
|
1260
|
-
const componentName = boundary?.componentName ?? null;
|
|
1261
|
-
let exactMatches;
|
|
1262
|
-
if (boundary) {
|
|
1263
|
-
const rootFiber = getRootFiber();
|
|
1264
|
-
const allNodes = rootFiber ? collectComponentDOMNodes(rootFiber, boundary.componentType, tag) : [];
|
|
1265
|
-
exactMatches = allNodes.filter(
|
|
1266
|
-
(n) => n.tagName === tag && n.className === clickedEl.className
|
|
1267
|
-
);
|
|
1268
|
-
} else {
|
|
1269
|
-
if (classes.length === 0) {
|
|
1270
|
-
exactMatches = Array.from(
|
|
1271
|
-
document.querySelectorAll(tag.toLowerCase())
|
|
1272
|
-
).filter(
|
|
1273
|
-
(n) => (typeof n.className === "string" ? n.className.trim() : "") === "" && !isInShadowHost(n, shadowHost2)
|
|
1274
|
-
);
|
|
1275
|
-
} else {
|
|
1276
|
-
const selector = buildSelector(tag, classes);
|
|
1277
|
-
exactMatches = Array.from(
|
|
1278
|
-
document.querySelectorAll(selector)
|
|
1279
|
-
).filter(
|
|
1280
|
-
(n) => n.className === clickedEl.className && !isInShadowHost(n, shadowHost2)
|
|
1281
|
-
);
|
|
1282
|
-
}
|
|
1205
|
+
applyBounds(el) {
|
|
1206
|
+
el.style.top = `${this.bounds.top}px`;
|
|
1207
|
+
el.style.left = `${this.bounds.left}px`;
|
|
1208
|
+
el.style.width = `${this.bounds.width}px`;
|
|
1209
|
+
el.style.height = `${this.bounds.height}px`;
|
|
1283
1210
|
}
|
|
1284
|
-
|
|
1285
|
-
|
|
1211
|
+
setupDrag(handle, host) {
|
|
1212
|
+
let startX = 0, startY = 0, startLeft = 0, startTop = 0;
|
|
1213
|
+
const onMove = (e) => {
|
|
1214
|
+
this.bounds.left = startLeft + (e.clientX - startX);
|
|
1215
|
+
this.bounds.top = startTop + (e.clientY - startY);
|
|
1216
|
+
host.style.left = `${this.bounds.left}px`;
|
|
1217
|
+
host.style.top = `${this.bounds.top}px`;
|
|
1218
|
+
};
|
|
1219
|
+
const onUp = () => {
|
|
1220
|
+
document.removeEventListener("mousemove", onMove);
|
|
1221
|
+
document.removeEventListener("mouseup", onUp);
|
|
1222
|
+
saveBounds(this.bounds);
|
|
1223
|
+
};
|
|
1224
|
+
handle.addEventListener("mousedown", (e) => {
|
|
1225
|
+
e.preventDefault();
|
|
1226
|
+
startX = e.clientX;
|
|
1227
|
+
startY = e.clientY;
|
|
1228
|
+
startLeft = this.bounds.left;
|
|
1229
|
+
startTop = this.bounds.top;
|
|
1230
|
+
document.addEventListener("mousemove", onMove);
|
|
1231
|
+
document.addEventListener("mouseup", onUp);
|
|
1232
|
+
});
|
|
1286
1233
|
}
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
|
|
1305
|
-
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
for (const cls of classes) {
|
|
1311
|
-
const sel = `${tag.toLowerCase()}.${cssEscape(cls)}`;
|
|
1312
|
-
for (const n of document.querySelectorAll(sel)) {
|
|
1313
|
-
if (!seen.has(n) && !isInShadowHost(n, shadowHost2)) {
|
|
1314
|
-
seen.add(n);
|
|
1315
|
-
candidates.push(n);
|
|
1316
|
-
if (candidates.length >= MAX_CANDIDATES) break;
|
|
1317
|
-
}
|
|
1318
|
-
}
|
|
1319
|
-
if (candidates.length >= MAX_CANDIDATES) break;
|
|
1320
|
-
}
|
|
1321
|
-
}
|
|
1322
|
-
const groupMap = /* @__PURE__ */ new Map();
|
|
1323
|
-
for (const el of candidates) {
|
|
1324
|
-
const candidateClasses = new Set(parseClassList(typeof el.className === "string" ? el.className : ""));
|
|
1325
|
-
const { added, removed } = classDiff(refSet, candidateClasses);
|
|
1326
|
-
const totalDiff = added.length + removed.length;
|
|
1327
|
-
if (totalDiff === 0 || totalDiff > MAX_DIFF) continue;
|
|
1328
|
-
const key = `+${added.join(",")}|-${removed.join(",")}`;
|
|
1329
|
-
const existing = groupMap.get(key);
|
|
1330
|
-
if (existing) {
|
|
1331
|
-
existing.elements.push(el);
|
|
1332
|
-
} else {
|
|
1333
|
-
groupMap.set(key, { added, removed, elements: [el] });
|
|
1334
|
-
}
|
|
1335
|
-
}
|
|
1336
|
-
const groups = [];
|
|
1337
|
-
for (const [, g] of groupMap) {
|
|
1338
|
-
groups.push({
|
|
1339
|
-
label: diffLabel(g.added, g.removed),
|
|
1340
|
-
added: g.added,
|
|
1341
|
-
removed: g.removed,
|
|
1342
|
-
elements: g.elements
|
|
1234
|
+
setupResize(gripper, host, iframe) {
|
|
1235
|
+
let startX = 0, startY = 0, startW = 0, startH = 0;
|
|
1236
|
+
const onMove = (e) => {
|
|
1237
|
+
this.bounds.width = Math.max(300, startW + (e.clientX - startX));
|
|
1238
|
+
this.bounds.height = Math.max(200, startH + (e.clientY - startY));
|
|
1239
|
+
host.style.width = `${this.bounds.width}px`;
|
|
1240
|
+
host.style.height = `${this.bounds.height}px`;
|
|
1241
|
+
};
|
|
1242
|
+
const onUp = () => {
|
|
1243
|
+
iframe.style.pointerEvents = "";
|
|
1244
|
+
document.removeEventListener("mousemove", onMove);
|
|
1245
|
+
document.removeEventListener("mouseup", onUp);
|
|
1246
|
+
saveBounds(this.bounds);
|
|
1247
|
+
};
|
|
1248
|
+
gripper.addEventListener("mousedown", (e) => {
|
|
1249
|
+
e.preventDefault();
|
|
1250
|
+
iframe.style.pointerEvents = "none";
|
|
1251
|
+
startX = e.clientX;
|
|
1252
|
+
startY = e.clientY;
|
|
1253
|
+
startW = this.bounds.width;
|
|
1254
|
+
startH = this.bounds.height;
|
|
1255
|
+
document.addEventListener("mousemove", onMove);
|
|
1256
|
+
document.addEventListener("mouseup", onUp);
|
|
1343
1257
|
});
|
|
1344
1258
|
}
|
|
1345
|
-
|
|
1346
|
-
const diffA = a.added.length + a.removed.length;
|
|
1347
|
-
const diffB = b.added.length + b.removed.length;
|
|
1348
|
-
if (diffA !== diffB) return diffA - diffB;
|
|
1349
|
-
return b.elements.length - a.elements.length;
|
|
1350
|
-
});
|
|
1351
|
-
return groups;
|
|
1352
|
-
}
|
|
1353
|
-
function collectComponentDOMNodes(rootFiber, componentType, tagName) {
|
|
1354
|
-
const instances = findAllInstances(rootFiber, componentType);
|
|
1355
|
-
const results = [];
|
|
1356
|
-
for (const inst of instances) {
|
|
1357
|
-
collectHostNodes(inst.child, tagName, results);
|
|
1358
|
-
}
|
|
1359
|
-
return results;
|
|
1360
|
-
}
|
|
1361
|
-
function collectHostNodes(fiber, tagName, out) {
|
|
1362
|
-
if (!fiber) return;
|
|
1363
|
-
if (fiber.tag === 5 && fiber.stateNode instanceof HTMLElement) {
|
|
1364
|
-
if (fiber.stateNode.tagName === tagName) {
|
|
1365
|
-
out.push(fiber.stateNode);
|
|
1366
|
-
}
|
|
1367
|
-
}
|
|
1368
|
-
collectHostNodes(fiber.child, tagName, out);
|
|
1369
|
-
collectHostNodes(fiber.sibling, tagName, out);
|
|
1370
|
-
}
|
|
1371
|
-
function isInShadowHost(el, shadowHost2) {
|
|
1372
|
-
if (!shadowHost2) return false;
|
|
1373
|
-
return shadowHost2.contains(el);
|
|
1374
|
-
}
|
|
1259
|
+
};
|
|
1375
1260
|
|
|
1376
|
-
// overlay/src/
|
|
1377
|
-
|
|
1378
|
-
|
|
1379
|
-
|
|
1380
|
-
while (current && current !== document.documentElement) {
|
|
1381
|
-
ancestors.push(current);
|
|
1382
|
-
current = current.parentElement;
|
|
1383
|
-
}
|
|
1384
|
-
ancestors.reverse();
|
|
1385
|
-
return buildLevel(ancestors, 0, target, oldClass, newClass, originalClassMap, 0);
|
|
1386
|
-
}
|
|
1387
|
-
function buildLevel(ancestors, ancestorIndex, target, oldClass, newClass, originalClassMap, indent) {
|
|
1388
|
-
const el = ancestors[ancestorIndex];
|
|
1389
|
-
const pad = " ".repeat(indent);
|
|
1390
|
-
const tag = el.tagName.toLowerCase();
|
|
1391
|
-
let attrs = "";
|
|
1392
|
-
if (el.id) attrs += ` id="${el.id}"`;
|
|
1393
|
-
const originalClass = originalClassMap.get(el);
|
|
1394
|
-
const classStr = originalClass != null ? originalClass.trim() : typeof el.className === "string" ? el.className.trim() : "";
|
|
1395
|
-
if (classStr) {
|
|
1396
|
-
attrs += ` class="${classStr}"`;
|
|
1397
|
-
}
|
|
1398
|
-
const isTarget = el === target;
|
|
1399
|
-
if (isTarget) {
|
|
1400
|
-
const text = getInnerText(el);
|
|
1401
|
-
const textNode = text ? `
|
|
1402
|
-
${pad} ${text}` : "";
|
|
1403
|
-
return `${pad}<${tag}${attrs}> <!-- TARGET: change ${oldClass} \u2192 ${newClass} -->${textNode}
|
|
1404
|
-
${pad}</${tag}>`;
|
|
1261
|
+
// overlay/src/containers/PopoverContainer.ts
|
|
1262
|
+
var PopoverContainer = class {
|
|
1263
|
+
constructor(shadowRoot2) {
|
|
1264
|
+
this.shadowRoot = shadowRoot2;
|
|
1405
1265
|
}
|
|
1406
|
-
|
|
1407
|
-
|
|
1266
|
+
name = "popover";
|
|
1267
|
+
host = null;
|
|
1268
|
+
open(panelUrl) {
|
|
1269
|
+
if (this.host) return;
|
|
1270
|
+
const host = document.createElement("div");
|
|
1271
|
+
host.className = "container-popover";
|
|
1272
|
+
host.style.cssText = `
|
|
1273
|
+
position: fixed;
|
|
1274
|
+
top: 0;
|
|
1275
|
+
right: 0;
|
|
1276
|
+
width: 400px;
|
|
1277
|
+
height: 100vh;
|
|
1278
|
+
z-index: 999999;
|
|
1279
|
+
background: #1e1e2e;
|
|
1280
|
+
box-shadow: -4px 0 24px rgba(0,0,0,0.3);
|
|
1281
|
+
pointer-events: auto;
|
|
1282
|
+
`;
|
|
1283
|
+
const iframe = document.createElement("iframe");
|
|
1284
|
+
iframe.src = panelUrl;
|
|
1285
|
+
iframe.allow = "microphone";
|
|
1286
|
+
iframe.style.cssText = "width:100%; height:100%; border:none;";
|
|
1287
|
+
host.appendChild(iframe);
|
|
1288
|
+
this.shadowRoot.appendChild(host);
|
|
1289
|
+
this.host = host;
|
|
1408
1290
|
}
|
|
1409
|
-
|
|
1410
|
-
|
|
1411
|
-
|
|
1412
|
-
|
|
1413
|
-
if (relevantIndex === -1) {
|
|
1414
|
-
inner = buildLevel(ancestors, ancestorIndex + 1, target, oldClass, newClass, originalClassMap, indent + 1);
|
|
1415
|
-
} else {
|
|
1416
|
-
const start = Math.max(0, relevantIndex - 3);
|
|
1417
|
-
const end = Math.min(children.length - 1, relevantIndex + 3);
|
|
1418
|
-
if (start > 0) {
|
|
1419
|
-
inner += `${pad} ...
|
|
1420
|
-
`;
|
|
1421
|
-
}
|
|
1422
|
-
for (let i = start; i <= end; i++) {
|
|
1423
|
-
if (i === relevantIndex) {
|
|
1424
|
-
inner += buildLevel(ancestors, ancestorIndex + 1, target, oldClass, newClass, originalClassMap, indent + 1) + "\n";
|
|
1425
|
-
} else {
|
|
1426
|
-
inner += renderSiblingNode(children[i], indent + 1, originalClassMap) + "\n";
|
|
1427
|
-
}
|
|
1428
|
-
}
|
|
1429
|
-
if (end < children.length - 1) {
|
|
1430
|
-
inner += `${pad} ...
|
|
1431
|
-
`;
|
|
1291
|
+
close() {
|
|
1292
|
+
if (this.host) {
|
|
1293
|
+
this.host.remove();
|
|
1294
|
+
this.host = null;
|
|
1432
1295
|
}
|
|
1433
1296
|
}
|
|
1434
|
-
|
|
1435
|
-
|
|
1436
|
-
}
|
|
1437
|
-
function renderSiblingNode(el, indent, originalClassMap) {
|
|
1438
|
-
const pad = " ".repeat(indent);
|
|
1439
|
-
const tag = el.tagName.toLowerCase();
|
|
1440
|
-
let attrs = "";
|
|
1441
|
-
if (el.id) attrs += ` id="${el.id}"`;
|
|
1442
|
-
const originalClass = originalClassMap.get(el);
|
|
1443
|
-
const classStr = originalClass != null ? originalClass.trim() : typeof el.className === "string" ? el.className.trim() : "";
|
|
1444
|
-
if (classStr) {
|
|
1445
|
-
attrs += ` class="${classStr}"`;
|
|
1446
|
-
}
|
|
1447
|
-
const text = getInnerText(el);
|
|
1448
|
-
if (!el.id && (!el.className || !el.className.trim()) && !text) {
|
|
1449
|
-
return `${pad}<${tag}>...</${tag}>`;
|
|
1450
|
-
}
|
|
1451
|
-
if (text) {
|
|
1452
|
-
return `${pad}<${tag}${attrs}>
|
|
1453
|
-
${pad} ${text}
|
|
1454
|
-
${pad}</${tag}>`;
|
|
1297
|
+
isOpen() {
|
|
1298
|
+
return this.host !== null;
|
|
1455
1299
|
}
|
|
1456
|
-
|
|
1457
|
-
|
|
1458
|
-
|
|
1459
|
-
|
|
1300
|
+
};
|
|
1301
|
+
|
|
1302
|
+
// overlay/src/containers/PopupContainer.ts
|
|
1303
|
+
var PopupContainer = class {
|
|
1304
|
+
name = "popup";
|
|
1305
|
+
popup = null;
|
|
1306
|
+
open(panelUrl) {
|
|
1307
|
+
if (this.popup && !this.popup.closed) {
|
|
1308
|
+
this.popup.focus();
|
|
1309
|
+
return;
|
|
1310
|
+
}
|
|
1311
|
+
this.popup = window.open(panelUrl, "tw-panel", "popup,width=420,height=700");
|
|
1460
1312
|
}
|
|
1461
|
-
|
|
1462
|
-
|
|
1463
|
-
|
|
1464
|
-
let text = "";
|
|
1465
|
-
for (const node of Array.from(el.childNodes)) {
|
|
1466
|
-
if (node.nodeType === Node.TEXT_NODE) {
|
|
1467
|
-
text += node.textContent || "";
|
|
1313
|
+
close() {
|
|
1314
|
+
if (this.popup && !this.popup.closed) {
|
|
1315
|
+
this.popup.close();
|
|
1468
1316
|
}
|
|
1317
|
+
this.popup = null;
|
|
1469
1318
|
}
|
|
1470
|
-
|
|
1471
|
-
|
|
1472
|
-
|
|
1473
|
-
}
|
|
1474
|
-
|
|
1475
|
-
// overlay/src/patcher.ts
|
|
1476
|
-
var previewState = null;
|
|
1477
|
-
var previewStyleEl = null;
|
|
1478
|
-
var committedStyleEl = null;
|
|
1479
|
-
var previewGeneration = 0;
|
|
1480
|
-
async function applyPreview(elements, oldClass, newClass, serverOrigin) {
|
|
1481
|
-
const gen = ++previewGeneration;
|
|
1482
|
-
if (!previewState) {
|
|
1483
|
-
previewState = {
|
|
1484
|
-
elements,
|
|
1485
|
-
originalClasses: elements.map((n) => n.className)
|
|
1486
|
-
};
|
|
1487
|
-
}
|
|
1488
|
-
if (newClass) {
|
|
1489
|
-
try {
|
|
1490
|
-
const res = await fetch(`${serverOrigin}/css`, {
|
|
1491
|
-
method: "POST",
|
|
1492
|
-
headers: { "Content-Type": "application/json" },
|
|
1493
|
-
body: JSON.stringify({ classes: [newClass] })
|
|
1494
|
-
});
|
|
1495
|
-
if (gen !== previewGeneration) return;
|
|
1496
|
-
const { css } = await res.json();
|
|
1497
|
-
if (gen !== previewGeneration) return;
|
|
1498
|
-
if (!previewStyleEl) {
|
|
1499
|
-
previewStyleEl = document.createElement("style");
|
|
1500
|
-
previewStyleEl.setAttribute("data-tw-preview", "");
|
|
1501
|
-
document.head.appendChild(previewStyleEl);
|
|
1502
|
-
}
|
|
1503
|
-
previewStyleEl.textContent = css;
|
|
1504
|
-
} catch {
|
|
1505
|
-
}
|
|
1506
|
-
}
|
|
1507
|
-
if (gen !== previewGeneration) return;
|
|
1508
|
-
if (previewState) {
|
|
1509
|
-
for (let i = 0; i < previewState.elements.length; i++) {
|
|
1510
|
-
previewState.elements[i].className = previewState.originalClasses[i];
|
|
1511
|
-
}
|
|
1512
|
-
}
|
|
1513
|
-
for (const node of elements) {
|
|
1514
|
-
if (oldClass) node.classList.remove(oldClass);
|
|
1515
|
-
if (newClass) node.classList.add(newClass);
|
|
1516
|
-
}
|
|
1517
|
-
}
|
|
1518
|
-
function revertPreview() {
|
|
1519
|
-
previewGeneration++;
|
|
1520
|
-
if (previewState) {
|
|
1521
|
-
for (let i = 0; i < previewState.elements.length; i++) {
|
|
1522
|
-
previewState.elements[i].className = previewState.originalClasses[i];
|
|
1523
|
-
}
|
|
1524
|
-
previewState = null;
|
|
1525
|
-
}
|
|
1526
|
-
previewStyleEl?.remove();
|
|
1527
|
-
previewStyleEl = null;
|
|
1528
|
-
}
|
|
1529
|
-
function getPreviewState() {
|
|
1530
|
-
return previewState;
|
|
1531
|
-
}
|
|
1532
|
-
function commitPreview() {
|
|
1533
|
-
previewGeneration++;
|
|
1534
|
-
previewState = null;
|
|
1535
|
-
if (previewStyleEl) {
|
|
1536
|
-
const css = previewStyleEl.textContent || "";
|
|
1537
|
-
if (css) {
|
|
1538
|
-
if (!committedStyleEl) {
|
|
1539
|
-
committedStyleEl = document.createElement("style");
|
|
1540
|
-
committedStyleEl.setAttribute("data-tw-committed", "");
|
|
1541
|
-
document.head.appendChild(committedStyleEl);
|
|
1542
|
-
}
|
|
1543
|
-
committedStyleEl.textContent += css;
|
|
1544
|
-
}
|
|
1545
|
-
previewStyleEl.remove();
|
|
1546
|
-
previewStyleEl = null;
|
|
1547
|
-
}
|
|
1548
|
-
}
|
|
1549
|
-
|
|
1550
|
-
// overlay/src/containers/PopoverContainer.ts
|
|
1551
|
-
var PopoverContainer = class {
|
|
1552
|
-
constructor(shadowRoot2) {
|
|
1553
|
-
this.shadowRoot = shadowRoot2;
|
|
1554
|
-
}
|
|
1555
|
-
name = "popover";
|
|
1556
|
-
host = null;
|
|
1557
|
-
open(panelUrl) {
|
|
1558
|
-
if (this.host) return;
|
|
1559
|
-
const host = document.createElement("div");
|
|
1560
|
-
host.className = "container-popover";
|
|
1561
|
-
host.style.cssText = `
|
|
1562
|
-
position: fixed;
|
|
1563
|
-
top: 0;
|
|
1564
|
-
right: 0;
|
|
1565
|
-
width: 400px;
|
|
1566
|
-
height: 100vh;
|
|
1567
|
-
z-index: 999999;
|
|
1568
|
-
background: #1e1e2e;
|
|
1569
|
-
box-shadow: -4px 0 24px rgba(0,0,0,0.3);
|
|
1570
|
-
pointer-events: auto;
|
|
1571
|
-
`;
|
|
1572
|
-
const iframe = document.createElement("iframe");
|
|
1573
|
-
iframe.src = panelUrl;
|
|
1574
|
-
iframe.style.cssText = "width:100%; height:100%; border:none;";
|
|
1575
|
-
host.appendChild(iframe);
|
|
1576
|
-
this.shadowRoot.appendChild(host);
|
|
1577
|
-
this.host = host;
|
|
1578
|
-
}
|
|
1579
|
-
close() {
|
|
1580
|
-
if (this.host) {
|
|
1581
|
-
this.host.remove();
|
|
1582
|
-
this.host = null;
|
|
1583
|
-
}
|
|
1584
|
-
}
|
|
1585
|
-
isOpen() {
|
|
1586
|
-
return this.host !== null;
|
|
1587
|
-
}
|
|
1588
|
-
};
|
|
1589
|
-
|
|
1590
|
-
// overlay/src/containers/ModalContainer.ts
|
|
1591
|
-
var STORAGE_KEY = "tw-modal-bounds";
|
|
1592
|
-
function loadBounds() {
|
|
1593
|
-
try {
|
|
1594
|
-
const stored = localStorage.getItem(STORAGE_KEY);
|
|
1595
|
-
if (stored) return JSON.parse(stored);
|
|
1596
|
-
} catch {
|
|
1597
|
-
}
|
|
1598
|
-
return { top: 80, left: Math.max(0, window.innerWidth - 440), width: 400, height: 600 };
|
|
1599
|
-
}
|
|
1600
|
-
function saveBounds(bounds) {
|
|
1601
|
-
try {
|
|
1602
|
-
localStorage.setItem(STORAGE_KEY, JSON.stringify(bounds));
|
|
1603
|
-
} catch {
|
|
1604
|
-
}
|
|
1605
|
-
}
|
|
1606
|
-
var ModalContainer = class {
|
|
1607
|
-
constructor(shadowRoot2) {
|
|
1608
|
-
this.shadowRoot = shadowRoot2;
|
|
1609
|
-
}
|
|
1610
|
-
name = "modal";
|
|
1611
|
-
host = null;
|
|
1612
|
-
bounds = loadBounds();
|
|
1613
|
-
open(panelUrl) {
|
|
1614
|
-
if (this.host) return;
|
|
1615
|
-
this.bounds = loadBounds();
|
|
1616
|
-
const host = document.createElement("div");
|
|
1617
|
-
host.className = "container-modal";
|
|
1618
|
-
this.applyBounds(host);
|
|
1619
|
-
host.style.position = "fixed";
|
|
1620
|
-
host.style.zIndex = "999999";
|
|
1621
|
-
host.style.background = "#1e1e2e";
|
|
1622
|
-
host.style.borderRadius = "8px";
|
|
1623
|
-
host.style.boxShadow = "0 8px 32px rgba(0,0,0,0.4)";
|
|
1624
|
-
host.style.display = "flex";
|
|
1625
|
-
host.style.flexDirection = "column";
|
|
1626
|
-
host.style.overflow = "hidden";
|
|
1627
|
-
host.style.pointerEvents = "auto";
|
|
1628
|
-
const handle = document.createElement("div");
|
|
1629
|
-
handle.style.cssText = `
|
|
1630
|
-
height: 28px;
|
|
1631
|
-
background: #181825;
|
|
1632
|
-
cursor: move;
|
|
1633
|
-
display: flex;
|
|
1634
|
-
align-items: center;
|
|
1635
|
-
justify-content: center;
|
|
1636
|
-
flex-shrink: 0;
|
|
1637
|
-
user-select: none;
|
|
1638
|
-
`;
|
|
1639
|
-
handle.innerHTML = '<span style="color:#585b70;font-size:11px;letter-spacing:2px;">\u22EF\u22EF\u22EF</span>';
|
|
1640
|
-
this.setupDrag(handle, host);
|
|
1641
|
-
host.appendChild(handle);
|
|
1642
|
-
const iframe = document.createElement("iframe");
|
|
1643
|
-
iframe.src = panelUrl;
|
|
1644
|
-
iframe.style.cssText = "flex:1; border:none; width:100%;";
|
|
1645
|
-
host.appendChild(iframe);
|
|
1646
|
-
const gripper = document.createElement("div");
|
|
1647
|
-
gripper.style.cssText = `
|
|
1648
|
-
position: absolute;
|
|
1649
|
-
bottom: 0;
|
|
1650
|
-
right: 0;
|
|
1651
|
-
width: 16px;
|
|
1652
|
-
height: 16px;
|
|
1653
|
-
cursor: nwse-resize;
|
|
1654
|
-
`;
|
|
1655
|
-
gripper.innerHTML = '<span style="position:absolute;bottom:2px;right:4px;color:#585b70;font-size:10px;">\u25E2</span>';
|
|
1656
|
-
this.setupResize(gripper, host, iframe);
|
|
1657
|
-
host.appendChild(gripper);
|
|
1658
|
-
this.shadowRoot.appendChild(host);
|
|
1659
|
-
this.host = host;
|
|
1660
|
-
}
|
|
1661
|
-
close() {
|
|
1662
|
-
if (this.host) {
|
|
1663
|
-
this.host.remove();
|
|
1664
|
-
this.host = null;
|
|
1665
|
-
}
|
|
1666
|
-
}
|
|
1667
|
-
isOpen() {
|
|
1668
|
-
return this.host !== null;
|
|
1669
|
-
}
|
|
1670
|
-
applyBounds(el) {
|
|
1671
|
-
el.style.top = `${this.bounds.top}px`;
|
|
1672
|
-
el.style.left = `${this.bounds.left}px`;
|
|
1673
|
-
el.style.width = `${this.bounds.width}px`;
|
|
1674
|
-
el.style.height = `${this.bounds.height}px`;
|
|
1675
|
-
}
|
|
1676
|
-
setupDrag(handle, host) {
|
|
1677
|
-
let startX = 0, startY = 0, startLeft = 0, startTop = 0;
|
|
1678
|
-
const onMove = (e) => {
|
|
1679
|
-
this.bounds.left = startLeft + (e.clientX - startX);
|
|
1680
|
-
this.bounds.top = startTop + (e.clientY - startY);
|
|
1681
|
-
host.style.left = `${this.bounds.left}px`;
|
|
1682
|
-
host.style.top = `${this.bounds.top}px`;
|
|
1683
|
-
};
|
|
1684
|
-
const onUp = () => {
|
|
1685
|
-
document.removeEventListener("mousemove", onMove);
|
|
1686
|
-
document.removeEventListener("mouseup", onUp);
|
|
1687
|
-
saveBounds(this.bounds);
|
|
1688
|
-
};
|
|
1689
|
-
handle.addEventListener("mousedown", (e) => {
|
|
1690
|
-
e.preventDefault();
|
|
1691
|
-
startX = e.clientX;
|
|
1692
|
-
startY = e.clientY;
|
|
1693
|
-
startLeft = this.bounds.left;
|
|
1694
|
-
startTop = this.bounds.top;
|
|
1695
|
-
document.addEventListener("mousemove", onMove);
|
|
1696
|
-
document.addEventListener("mouseup", onUp);
|
|
1697
|
-
});
|
|
1698
|
-
}
|
|
1699
|
-
setupResize(gripper, host, iframe) {
|
|
1700
|
-
let startX = 0, startY = 0, startW = 0, startH = 0;
|
|
1701
|
-
const onMove = (e) => {
|
|
1702
|
-
this.bounds.width = Math.max(300, startW + (e.clientX - startX));
|
|
1703
|
-
this.bounds.height = Math.max(200, startH + (e.clientY - startY));
|
|
1704
|
-
host.style.width = `${this.bounds.width}px`;
|
|
1705
|
-
host.style.height = `${this.bounds.height}px`;
|
|
1706
|
-
};
|
|
1707
|
-
const onUp = () => {
|
|
1708
|
-
iframe.style.pointerEvents = "";
|
|
1709
|
-
document.removeEventListener("mousemove", onMove);
|
|
1710
|
-
document.removeEventListener("mouseup", onUp);
|
|
1711
|
-
saveBounds(this.bounds);
|
|
1712
|
-
};
|
|
1713
|
-
gripper.addEventListener("mousedown", (e) => {
|
|
1714
|
-
e.preventDefault();
|
|
1715
|
-
iframe.style.pointerEvents = "none";
|
|
1716
|
-
startX = e.clientX;
|
|
1717
|
-
startY = e.clientY;
|
|
1718
|
-
startW = this.bounds.width;
|
|
1719
|
-
startH = this.bounds.height;
|
|
1720
|
-
document.addEventListener("mousemove", onMove);
|
|
1721
|
-
document.addEventListener("mouseup", onUp);
|
|
1722
|
-
});
|
|
1723
|
-
}
|
|
1724
|
-
};
|
|
1319
|
+
isOpen() {
|
|
1320
|
+
return this.popup !== null && !this.popup.closed;
|
|
1321
|
+
}
|
|
1322
|
+
};
|
|
1725
1323
|
|
|
1726
1324
|
// overlay/src/containers/SidebarContainer.ts
|
|
1727
1325
|
var SidebarContainer = class {
|
|
@@ -1794,76 +1392,477 @@ ${pad}</${tag}>`;
|
|
|
1794
1392
|
host.appendChild(resizeHandle);
|
|
1795
1393
|
const iframe = document.createElement("iframe");
|
|
1796
1394
|
iframe.src = panelUrl;
|
|
1395
|
+
iframe.allow = "microphone";
|
|
1797
1396
|
iframe.style.cssText = "flex:1; border:none; height:100%;";
|
|
1798
1397
|
host.appendChild(iframe);
|
|
1799
1398
|
this.shadowRoot.appendChild(host);
|
|
1800
1399
|
this.host = host;
|
|
1801
1400
|
}
|
|
1802
|
-
close() {
|
|
1803
|
-
if (this.host) {
|
|
1804
|
-
this.host.remove();
|
|
1805
|
-
this.host = null;
|
|
1806
|
-
if (this.pageWrapper) {
|
|
1807
|
-
const children = Array.from(this.pageWrapper.childNodes);
|
|
1808
|
-
const shadowHost2 = document.getElementById("tw-visual-editor-host");
|
|
1809
|
-
for (const node of children) {
|
|
1810
|
-
if (shadowHost2 && shadowHost2.parentNode) {
|
|
1811
|
-
shadowHost2.parentNode.insertBefore(node, shadowHost2);
|
|
1812
|
-
} else {
|
|
1813
|
-
document.body.appendChild(node);
|
|
1814
|
-
}
|
|
1815
|
-
}
|
|
1816
|
-
this.pageWrapper.remove();
|
|
1817
|
-
this.pageWrapper = null;
|
|
1401
|
+
close() {
|
|
1402
|
+
if (this.host) {
|
|
1403
|
+
this.host.remove();
|
|
1404
|
+
this.host = null;
|
|
1405
|
+
if (this.pageWrapper) {
|
|
1406
|
+
const children = Array.from(this.pageWrapper.childNodes);
|
|
1407
|
+
const shadowHost2 = document.getElementById("tw-visual-editor-host");
|
|
1408
|
+
for (const node of children) {
|
|
1409
|
+
if (shadowHost2 && shadowHost2.parentNode) {
|
|
1410
|
+
shadowHost2.parentNode.insertBefore(node, shadowHost2);
|
|
1411
|
+
} else {
|
|
1412
|
+
document.body.appendChild(node);
|
|
1413
|
+
}
|
|
1414
|
+
}
|
|
1415
|
+
this.pageWrapper.remove();
|
|
1416
|
+
this.pageWrapper = null;
|
|
1417
|
+
}
|
|
1418
|
+
document.body.style.overflow = this.originalBodyOverflow || "";
|
|
1419
|
+
}
|
|
1420
|
+
}
|
|
1421
|
+
isOpen() {
|
|
1422
|
+
return this.host !== null;
|
|
1423
|
+
}
|
|
1424
|
+
setupResize(handle, host) {
|
|
1425
|
+
let startX = 0, startW = 0;
|
|
1426
|
+
const onMove = (e) => {
|
|
1427
|
+
this.width = Math.max(280, startW - (e.clientX - startX));
|
|
1428
|
+
host.style.width = `${this.width}px`;
|
|
1429
|
+
document.documentElement.style.paddingRight = `${this.width}px`;
|
|
1430
|
+
};
|
|
1431
|
+
const onUp = () => {
|
|
1432
|
+
document.removeEventListener("mousemove", onMove);
|
|
1433
|
+
document.removeEventListener("mouseup", onUp);
|
|
1434
|
+
};
|
|
1435
|
+
handle.addEventListener("mousedown", (e) => {
|
|
1436
|
+
e.preventDefault();
|
|
1437
|
+
startX = e.clientX;
|
|
1438
|
+
startW = this.width;
|
|
1439
|
+
document.addEventListener("mousemove", onMove);
|
|
1440
|
+
document.addEventListener("mouseup", onUp);
|
|
1441
|
+
});
|
|
1442
|
+
}
|
|
1443
|
+
};
|
|
1444
|
+
|
|
1445
|
+
// overlay/src/context.ts
|
|
1446
|
+
function buildContext(target, oldClass, newClass, originalClassMap) {
|
|
1447
|
+
const ancestors = [];
|
|
1448
|
+
let current = target;
|
|
1449
|
+
while (current && current !== document.documentElement) {
|
|
1450
|
+
ancestors.push(current);
|
|
1451
|
+
current = current.parentElement;
|
|
1452
|
+
}
|
|
1453
|
+
ancestors.reverse();
|
|
1454
|
+
return buildLevel(ancestors, 0, target, oldClass, newClass, originalClassMap, 0);
|
|
1455
|
+
}
|
|
1456
|
+
function buildLevel(ancestors, ancestorIndex, target, oldClass, newClass, originalClassMap, indent) {
|
|
1457
|
+
const el = ancestors[ancestorIndex];
|
|
1458
|
+
const pad = " ".repeat(indent);
|
|
1459
|
+
const tag = el.tagName.toLowerCase();
|
|
1460
|
+
let attrs = "";
|
|
1461
|
+
if (el.id) attrs += ` id="${el.id}"`;
|
|
1462
|
+
const originalClass = originalClassMap.get(el);
|
|
1463
|
+
const classStr = originalClass != null ? originalClass.trim() : typeof el.className === "string" ? el.className.trim() : "";
|
|
1464
|
+
if (classStr) {
|
|
1465
|
+
attrs += ` class="${classStr}"`;
|
|
1466
|
+
}
|
|
1467
|
+
const isTarget = el === target;
|
|
1468
|
+
if (isTarget) {
|
|
1469
|
+
const text = getInnerText(el);
|
|
1470
|
+
const textNode = text ? `
|
|
1471
|
+
${pad} ${text}` : "";
|
|
1472
|
+
return `${pad}<${tag}${attrs}> <!-- TARGET: change ${oldClass} \u2192 ${newClass} -->${textNode}
|
|
1473
|
+
${pad}</${tag}>`;
|
|
1474
|
+
}
|
|
1475
|
+
if (ancestorIndex >= ancestors.length - 1) {
|
|
1476
|
+
return `${pad}<${tag}${attrs} />`;
|
|
1477
|
+
}
|
|
1478
|
+
const nextAncestor = ancestors[ancestorIndex + 1];
|
|
1479
|
+
const children = Array.from(el.children);
|
|
1480
|
+
const relevantIndex = children.indexOf(nextAncestor);
|
|
1481
|
+
let inner = "";
|
|
1482
|
+
if (relevantIndex === -1) {
|
|
1483
|
+
inner = buildLevel(ancestors, ancestorIndex + 1, target, oldClass, newClass, originalClassMap, indent + 1);
|
|
1484
|
+
} else {
|
|
1485
|
+
const start = Math.max(0, relevantIndex - 3);
|
|
1486
|
+
const end = Math.min(children.length - 1, relevantIndex + 3);
|
|
1487
|
+
if (start > 0) {
|
|
1488
|
+
inner += `${pad} ...
|
|
1489
|
+
`;
|
|
1490
|
+
}
|
|
1491
|
+
for (let i = start; i <= end; i++) {
|
|
1492
|
+
if (i === relevantIndex) {
|
|
1493
|
+
inner += buildLevel(ancestors, ancestorIndex + 1, target, oldClass, newClass, originalClassMap, indent + 1) + "\n";
|
|
1494
|
+
} else {
|
|
1495
|
+
inner += renderSiblingNode(children[i], indent + 1, originalClassMap) + "\n";
|
|
1496
|
+
}
|
|
1497
|
+
}
|
|
1498
|
+
if (end < children.length - 1) {
|
|
1499
|
+
inner += `${pad} ...
|
|
1500
|
+
`;
|
|
1501
|
+
}
|
|
1502
|
+
}
|
|
1503
|
+
return `${pad}<${tag}${attrs}>
|
|
1504
|
+
${inner}${pad}</${tag}>`;
|
|
1505
|
+
}
|
|
1506
|
+
function renderSiblingNode(el, indent, originalClassMap) {
|
|
1507
|
+
const pad = " ".repeat(indent);
|
|
1508
|
+
const tag = el.tagName.toLowerCase();
|
|
1509
|
+
let attrs = "";
|
|
1510
|
+
if (el.id) attrs += ` id="${el.id}"`;
|
|
1511
|
+
const originalClass = originalClassMap.get(el);
|
|
1512
|
+
const classStr = originalClass != null ? originalClass.trim() : typeof el.className === "string" ? el.className.trim() : "";
|
|
1513
|
+
if (classStr) {
|
|
1514
|
+
attrs += ` class="${classStr}"`;
|
|
1515
|
+
}
|
|
1516
|
+
const text = getInnerText(el);
|
|
1517
|
+
if (!el.id && (!el.className || !el.className.trim()) && !text) {
|
|
1518
|
+
return `${pad}<${tag}>...</${tag}>`;
|
|
1519
|
+
}
|
|
1520
|
+
if (text) {
|
|
1521
|
+
return `${pad}<${tag}${attrs}>
|
|
1522
|
+
${pad} ${text}
|
|
1523
|
+
${pad}</${tag}>`;
|
|
1524
|
+
}
|
|
1525
|
+
if (el.children.length > 0) {
|
|
1526
|
+
return `${pad}<${tag}${attrs}>
|
|
1527
|
+
${pad} ...
|
|
1528
|
+
${pad}</${tag}>`;
|
|
1529
|
+
}
|
|
1530
|
+
return `${pad}<${tag}${attrs} />`;
|
|
1531
|
+
}
|
|
1532
|
+
function getInnerText(el) {
|
|
1533
|
+
let text = "";
|
|
1534
|
+
for (const node of Array.from(el.childNodes)) {
|
|
1535
|
+
if (node.nodeType === Node.TEXT_NODE) {
|
|
1536
|
+
text += node.textContent || "";
|
|
1537
|
+
}
|
|
1538
|
+
}
|
|
1539
|
+
text = text.trim();
|
|
1540
|
+
if (text.length > 60) text = text.slice(0, 57) + "...";
|
|
1541
|
+
return text;
|
|
1542
|
+
}
|
|
1543
|
+
|
|
1544
|
+
// overlay/src/fiber.ts
|
|
1545
|
+
function getFiber(domNode) {
|
|
1546
|
+
const key = Object.keys(domNode).find((k) => k.startsWith("__reactFiber$"));
|
|
1547
|
+
return key ? domNode[key] : null;
|
|
1548
|
+
}
|
|
1549
|
+
function findComponentBoundary(fiber) {
|
|
1550
|
+
let current = fiber.return;
|
|
1551
|
+
while (current) {
|
|
1552
|
+
if (typeof current.type === "function") {
|
|
1553
|
+
return {
|
|
1554
|
+
componentType: current.type,
|
|
1555
|
+
componentName: current.type.displayName || current.type.name || "Unknown",
|
|
1556
|
+
componentFiber: current
|
|
1557
|
+
};
|
|
1558
|
+
}
|
|
1559
|
+
current = current.return;
|
|
1560
|
+
}
|
|
1561
|
+
return null;
|
|
1562
|
+
}
|
|
1563
|
+
function getRootFiber() {
|
|
1564
|
+
const candidateIds = ["root", "app", "__next"];
|
|
1565
|
+
for (const id of candidateIds) {
|
|
1566
|
+
const el = document.getElementById(id);
|
|
1567
|
+
if (!el) continue;
|
|
1568
|
+
const key = Object.keys(el).find((k) => k.startsWith("__reactContainer$"));
|
|
1569
|
+
if (key) {
|
|
1570
|
+
const container = el[key];
|
|
1571
|
+
if (container?.stateNode?.current) {
|
|
1572
|
+
return container.stateNode.current;
|
|
1573
|
+
}
|
|
1574
|
+
return container;
|
|
1575
|
+
}
|
|
1576
|
+
}
|
|
1577
|
+
const reactRoot = document.querySelector("[data-reactroot]");
|
|
1578
|
+
if (reactRoot) {
|
|
1579
|
+
return getFiber(reactRoot);
|
|
1580
|
+
}
|
|
1581
|
+
return null;
|
|
1582
|
+
}
|
|
1583
|
+
function findAllInstances(rootFiber, componentType) {
|
|
1584
|
+
const results = [];
|
|
1585
|
+
function walk(fiber) {
|
|
1586
|
+
if (!fiber) return;
|
|
1587
|
+
if (fiber.type === componentType) {
|
|
1588
|
+
results.push(fiber);
|
|
1589
|
+
}
|
|
1590
|
+
walk(fiber.child);
|
|
1591
|
+
walk(fiber.sibling);
|
|
1592
|
+
}
|
|
1593
|
+
walk(rootFiber);
|
|
1594
|
+
return results;
|
|
1595
|
+
}
|
|
1596
|
+
|
|
1597
|
+
// overlay/src/grouping.ts
|
|
1598
|
+
function cssEscape(cls) {
|
|
1599
|
+
if (typeof CSS !== "undefined" && CSS.escape) return CSS.escape(cls);
|
|
1600
|
+
return cls.replace(/([^\w-])/g, "\\$1");
|
|
1601
|
+
}
|
|
1602
|
+
function buildSelector(tag, classes) {
|
|
1603
|
+
return tag.toLowerCase() + classes.map((c) => `.${cssEscape(c)}`).join("");
|
|
1604
|
+
}
|
|
1605
|
+
function parseClassList(className) {
|
|
1606
|
+
if (typeof className !== "string") return [];
|
|
1607
|
+
return className.trim().split(/\s+/).filter(Boolean).sort();
|
|
1608
|
+
}
|
|
1609
|
+
function classDiff(refClasses, candidateClasses) {
|
|
1610
|
+
const added = [];
|
|
1611
|
+
const removed = [];
|
|
1612
|
+
for (const c of candidateClasses) {
|
|
1613
|
+
if (!refClasses.has(c)) added.push(c);
|
|
1614
|
+
}
|
|
1615
|
+
for (const c of refClasses) {
|
|
1616
|
+
if (!candidateClasses.has(c)) removed.push(c);
|
|
1617
|
+
}
|
|
1618
|
+
added.sort();
|
|
1619
|
+
removed.sort();
|
|
1620
|
+
return { added, removed };
|
|
1621
|
+
}
|
|
1622
|
+
function diffLabel(added, removed) {
|
|
1623
|
+
const parts = [];
|
|
1624
|
+
for (const a of added) parts.push(`+${a}`);
|
|
1625
|
+
for (const r of removed) parts.push(`-${r}`);
|
|
1626
|
+
return parts.join(" ");
|
|
1627
|
+
}
|
|
1628
|
+
var MAX_DIFF = 10;
|
|
1629
|
+
var MAX_CANDIDATES = 200;
|
|
1630
|
+
function findExactMatches(clickedEl, shadowHost2) {
|
|
1631
|
+
const classes = parseClassList(typeof clickedEl.className === "string" ? clickedEl.className : "");
|
|
1632
|
+
const tag = clickedEl.tagName;
|
|
1633
|
+
const fiber = getFiber(clickedEl);
|
|
1634
|
+
const boundary = fiber ? findComponentBoundary(fiber) : null;
|
|
1635
|
+
const componentName = boundary?.componentName ?? null;
|
|
1636
|
+
let exactMatches;
|
|
1637
|
+
if (boundary) {
|
|
1638
|
+
const rootFiber = getRootFiber();
|
|
1639
|
+
const allNodes = rootFiber ? collectComponentDOMNodes(rootFiber, boundary.componentType, tag) : [];
|
|
1640
|
+
exactMatches = allNodes.filter(
|
|
1641
|
+
(n) => n.tagName === tag && n.className === clickedEl.className
|
|
1642
|
+
);
|
|
1643
|
+
} else {
|
|
1644
|
+
if (classes.length === 0) {
|
|
1645
|
+
exactMatches = Array.from(
|
|
1646
|
+
document.querySelectorAll(tag.toLowerCase())
|
|
1647
|
+
).filter(
|
|
1648
|
+
(n) => (typeof n.className === "string" ? n.className.trim() : "") === "" && !isInShadowHost(n, shadowHost2)
|
|
1649
|
+
);
|
|
1650
|
+
} else {
|
|
1651
|
+
const selector = buildSelector(tag, classes);
|
|
1652
|
+
exactMatches = Array.from(
|
|
1653
|
+
document.querySelectorAll(selector)
|
|
1654
|
+
).filter(
|
|
1655
|
+
(n) => n.className === clickedEl.className && !isInShadowHost(n, shadowHost2)
|
|
1656
|
+
);
|
|
1657
|
+
}
|
|
1658
|
+
}
|
|
1659
|
+
if (!exactMatches.includes(clickedEl)) {
|
|
1660
|
+
exactMatches.unshift(clickedEl);
|
|
1661
|
+
}
|
|
1662
|
+
return {
|
|
1663
|
+
exactMatch: exactMatches,
|
|
1664
|
+
nearGroups: [],
|
|
1665
|
+
// Not computed yet — lazy
|
|
1666
|
+
componentName
|
|
1667
|
+
};
|
|
1668
|
+
}
|
|
1669
|
+
function computeNearGroups(clickedEl, exactMatchSet, shadowHost2) {
|
|
1670
|
+
const rawClassName = typeof clickedEl.className === "string" ? clickedEl.className : "";
|
|
1671
|
+
const classes = parseClassList(rawClassName);
|
|
1672
|
+
const tag = clickedEl.tagName;
|
|
1673
|
+
const refSet = new Set(classes);
|
|
1674
|
+
if (classes.length === 0) return [];
|
|
1675
|
+
const fiber = getFiber(clickedEl);
|
|
1676
|
+
const boundary = fiber ? findComponentBoundary(fiber) : null;
|
|
1677
|
+
let candidates;
|
|
1678
|
+
if (boundary) {
|
|
1679
|
+
const rootFiber = getRootFiber();
|
|
1680
|
+
candidates = rootFiber ? collectComponentDOMNodes(rootFiber, boundary.componentType, tag) : [];
|
|
1681
|
+
candidates = candidates.filter((n) => !exactMatchSet.has(n));
|
|
1682
|
+
} else {
|
|
1683
|
+
const seen = new Set(exactMatchSet);
|
|
1684
|
+
candidates = [];
|
|
1685
|
+
for (const cls of classes) {
|
|
1686
|
+
const sel = `${tag.toLowerCase()}.${cssEscape(cls)}`;
|
|
1687
|
+
for (const n of document.querySelectorAll(sel)) {
|
|
1688
|
+
if (!seen.has(n) && !isInShadowHost(n, shadowHost2)) {
|
|
1689
|
+
seen.add(n);
|
|
1690
|
+
candidates.push(n);
|
|
1691
|
+
if (candidates.length >= MAX_CANDIDATES) break;
|
|
1692
|
+
}
|
|
1693
|
+
}
|
|
1694
|
+
if (candidates.length >= MAX_CANDIDATES) break;
|
|
1695
|
+
}
|
|
1696
|
+
}
|
|
1697
|
+
const groupMap = /* @__PURE__ */ new Map();
|
|
1698
|
+
for (const el of candidates) {
|
|
1699
|
+
const candidateClasses = new Set(parseClassList(typeof el.className === "string" ? el.className : ""));
|
|
1700
|
+
const { added, removed } = classDiff(refSet, candidateClasses);
|
|
1701
|
+
const totalDiff = added.length + removed.length;
|
|
1702
|
+
if (totalDiff === 0 || totalDiff > MAX_DIFF) continue;
|
|
1703
|
+
const key = `+${added.join(",")}|-${removed.join(",")}`;
|
|
1704
|
+
const existing = groupMap.get(key);
|
|
1705
|
+
if (existing) {
|
|
1706
|
+
existing.elements.push(el);
|
|
1707
|
+
} else {
|
|
1708
|
+
groupMap.set(key, { added, removed, elements: [el] });
|
|
1709
|
+
}
|
|
1710
|
+
}
|
|
1711
|
+
const groups = [];
|
|
1712
|
+
for (const [, g] of groupMap) {
|
|
1713
|
+
groups.push({
|
|
1714
|
+
label: diffLabel(g.added, g.removed),
|
|
1715
|
+
added: g.added,
|
|
1716
|
+
removed: g.removed,
|
|
1717
|
+
elements: g.elements
|
|
1718
|
+
});
|
|
1719
|
+
}
|
|
1720
|
+
groups.sort((a, b) => {
|
|
1721
|
+
const diffA = a.added.length + a.removed.length;
|
|
1722
|
+
const diffB = b.added.length + b.removed.length;
|
|
1723
|
+
if (diffA !== diffB) return diffA - diffB;
|
|
1724
|
+
return b.elements.length - a.elements.length;
|
|
1725
|
+
});
|
|
1726
|
+
return groups;
|
|
1727
|
+
}
|
|
1728
|
+
function collectComponentDOMNodes(rootFiber, componentType, tagName) {
|
|
1729
|
+
const instances = findAllInstances(rootFiber, componentType);
|
|
1730
|
+
const results = [];
|
|
1731
|
+
for (const inst of instances) {
|
|
1732
|
+
collectHostNodes(inst.child, tagName, results);
|
|
1733
|
+
}
|
|
1734
|
+
return results;
|
|
1735
|
+
}
|
|
1736
|
+
function collectHostNodes(fiber, tagName, out) {
|
|
1737
|
+
if (!fiber) return;
|
|
1738
|
+
if (fiber.tag === 5 && fiber.stateNode instanceof HTMLElement) {
|
|
1739
|
+
if (fiber.stateNode.tagName === tagName) {
|
|
1740
|
+
out.push(fiber.stateNode);
|
|
1741
|
+
}
|
|
1742
|
+
}
|
|
1743
|
+
collectHostNodes(fiber.child, tagName, out);
|
|
1744
|
+
collectHostNodes(fiber.sibling, tagName, out);
|
|
1745
|
+
}
|
|
1746
|
+
function isInShadowHost(el, shadowHost2) {
|
|
1747
|
+
if (!shadowHost2) return false;
|
|
1748
|
+
return shadowHost2.contains(el);
|
|
1749
|
+
}
|
|
1750
|
+
|
|
1751
|
+
// overlay/src/patcher.ts
|
|
1752
|
+
var previewState = null;
|
|
1753
|
+
var previewStyleEl = null;
|
|
1754
|
+
var committedStyleEl = null;
|
|
1755
|
+
var previewGeneration = 0;
|
|
1756
|
+
async function applyPreview(elements, oldClass, newClass, serverOrigin) {
|
|
1757
|
+
const gen = ++previewGeneration;
|
|
1758
|
+
if (!previewState) {
|
|
1759
|
+
previewState = {
|
|
1760
|
+
elements,
|
|
1761
|
+
originalClasses: elements.map((n) => n.className)
|
|
1762
|
+
};
|
|
1763
|
+
}
|
|
1764
|
+
if (newClass) {
|
|
1765
|
+
try {
|
|
1766
|
+
const res = await fetch(`${serverOrigin}/css`, {
|
|
1767
|
+
method: "POST",
|
|
1768
|
+
headers: { "Content-Type": "application/json" },
|
|
1769
|
+
body: JSON.stringify({ classes: [newClass] })
|
|
1770
|
+
});
|
|
1771
|
+
if (gen !== previewGeneration) return;
|
|
1772
|
+
const { css } = await res.json();
|
|
1773
|
+
if (gen !== previewGeneration) return;
|
|
1774
|
+
if (!previewStyleEl) {
|
|
1775
|
+
previewStyleEl = document.createElement("style");
|
|
1776
|
+
previewStyleEl.setAttribute("data-tw-preview", "");
|
|
1777
|
+
document.head.appendChild(previewStyleEl);
|
|
1818
1778
|
}
|
|
1819
|
-
|
|
1779
|
+
previewStyleEl.textContent = css;
|
|
1780
|
+
} catch {
|
|
1820
1781
|
}
|
|
1821
1782
|
}
|
|
1822
|
-
|
|
1823
|
-
|
|
1783
|
+
if (gen !== previewGeneration) return;
|
|
1784
|
+
if (previewState) {
|
|
1785
|
+
for (let i = 0; i < previewState.elements.length; i++) {
|
|
1786
|
+
previewState.elements[i].className = previewState.originalClasses[i];
|
|
1787
|
+
}
|
|
1824
1788
|
}
|
|
1825
|
-
|
|
1826
|
-
|
|
1827
|
-
|
|
1828
|
-
|
|
1829
|
-
|
|
1830
|
-
|
|
1831
|
-
|
|
1832
|
-
|
|
1833
|
-
|
|
1834
|
-
|
|
1789
|
+
for (const node of elements) {
|
|
1790
|
+
if (oldClass) node.classList.remove(oldClass);
|
|
1791
|
+
if (newClass) node.classList.add(newClass);
|
|
1792
|
+
}
|
|
1793
|
+
}
|
|
1794
|
+
async function applyPreviewBatch(elements, pairs, serverOrigin) {
|
|
1795
|
+
const gen = ++previewGeneration;
|
|
1796
|
+
if (!previewState) {
|
|
1797
|
+
previewState = {
|
|
1798
|
+
elements,
|
|
1799
|
+
originalClasses: elements.map((n) => n.className)
|
|
1835
1800
|
};
|
|
1836
|
-
handle.addEventListener("mousedown", (e) => {
|
|
1837
|
-
e.preventDefault();
|
|
1838
|
-
startX = e.clientX;
|
|
1839
|
-
startW = this.width;
|
|
1840
|
-
document.addEventListener("mousemove", onMove);
|
|
1841
|
-
document.addEventListener("mouseup", onUp);
|
|
1842
|
-
});
|
|
1843
1801
|
}
|
|
1844
|
-
|
|
1845
|
-
|
|
1846
|
-
|
|
1847
|
-
|
|
1848
|
-
|
|
1849
|
-
|
|
1850
|
-
|
|
1851
|
-
|
|
1852
|
-
|
|
1853
|
-
|
|
1802
|
+
const newClasses = pairs.map((p) => p.newClass).filter(Boolean);
|
|
1803
|
+
if (newClasses.length > 0) {
|
|
1804
|
+
try {
|
|
1805
|
+
const res = await fetch(`${serverOrigin}/css`, {
|
|
1806
|
+
method: "POST",
|
|
1807
|
+
headers: { "Content-Type": "application/json" },
|
|
1808
|
+
body: JSON.stringify({ classes: newClasses })
|
|
1809
|
+
});
|
|
1810
|
+
if (gen !== previewGeneration) return;
|
|
1811
|
+
const { css } = await res.json();
|
|
1812
|
+
if (gen !== previewGeneration) return;
|
|
1813
|
+
if (!previewStyleEl) {
|
|
1814
|
+
previewStyleEl = document.createElement("style");
|
|
1815
|
+
previewStyleEl.setAttribute("data-tw-preview", "");
|
|
1816
|
+
document.head.appendChild(previewStyleEl);
|
|
1817
|
+
}
|
|
1818
|
+
previewStyleEl.textContent = css;
|
|
1819
|
+
} catch {
|
|
1854
1820
|
}
|
|
1855
|
-
this.popup = window.open(panelUrl, "tw-panel", "popup,width=420,height=700");
|
|
1856
1821
|
}
|
|
1857
|
-
|
|
1858
|
-
|
|
1859
|
-
|
|
1822
|
+
if (gen !== previewGeneration) return;
|
|
1823
|
+
if (previewState) {
|
|
1824
|
+
for (let i = 0; i < previewState.elements.length; i++) {
|
|
1825
|
+
previewState.elements[i].className = previewState.originalClasses[i];
|
|
1860
1826
|
}
|
|
1861
|
-
this.popup = null;
|
|
1862
1827
|
}
|
|
1863
|
-
|
|
1864
|
-
|
|
1828
|
+
for (const node of elements) {
|
|
1829
|
+
for (const { oldClass, newClass } of pairs) {
|
|
1830
|
+
if (oldClass) node.classList.remove(oldClass);
|
|
1831
|
+
if (newClass) node.classList.add(newClass);
|
|
1832
|
+
}
|
|
1865
1833
|
}
|
|
1866
|
-
}
|
|
1834
|
+
}
|
|
1835
|
+
function revertPreview() {
|
|
1836
|
+
previewGeneration++;
|
|
1837
|
+
if (previewState) {
|
|
1838
|
+
for (let i = 0; i < previewState.elements.length; i++) {
|
|
1839
|
+
previewState.elements[i].className = previewState.originalClasses[i];
|
|
1840
|
+
}
|
|
1841
|
+
previewState = null;
|
|
1842
|
+
}
|
|
1843
|
+
previewStyleEl?.remove();
|
|
1844
|
+
previewStyleEl = null;
|
|
1845
|
+
}
|
|
1846
|
+
function getPreviewState() {
|
|
1847
|
+
return previewState;
|
|
1848
|
+
}
|
|
1849
|
+
function commitPreview() {
|
|
1850
|
+
previewGeneration++;
|
|
1851
|
+
previewState = null;
|
|
1852
|
+
if (previewStyleEl) {
|
|
1853
|
+
const css = previewStyleEl.textContent || "";
|
|
1854
|
+
if (css) {
|
|
1855
|
+
if (!committedStyleEl) {
|
|
1856
|
+
committedStyleEl = document.createElement("style");
|
|
1857
|
+
committedStyleEl.setAttribute("data-tw-committed", "");
|
|
1858
|
+
document.head.appendChild(committedStyleEl);
|
|
1859
|
+
}
|
|
1860
|
+
committedStyleEl.textContent += css;
|
|
1861
|
+
}
|
|
1862
|
+
previewStyleEl.remove();
|
|
1863
|
+
previewStyleEl = null;
|
|
1864
|
+
}
|
|
1865
|
+
}
|
|
1867
1866
|
|
|
1868
1867
|
// node_modules/html-to-image/es/util.js
|
|
1869
1868
|
function resolveUrl(url, baseUrl) {
|
|
@@ -2704,6 +2703,51 @@ ${pad}</${tag}>`;
|
|
|
2704
2703
|
}
|
|
2705
2704
|
}
|
|
2706
2705
|
|
|
2706
|
+
// overlay/src/ws.ts
|
|
2707
|
+
var socket = null;
|
|
2708
|
+
var connected = false;
|
|
2709
|
+
var handlers = [];
|
|
2710
|
+
function connect(url = "ws://localhost:3333") {
|
|
2711
|
+
socket = new WebSocket(url);
|
|
2712
|
+
socket.addEventListener("open", () => {
|
|
2713
|
+
connected = true;
|
|
2714
|
+
send({ type: "REGISTER", role: "overlay" });
|
|
2715
|
+
window.dispatchEvent(new CustomEvent("overlay-ws-connected"));
|
|
2716
|
+
});
|
|
2717
|
+
socket.addEventListener("close", () => {
|
|
2718
|
+
connected = false;
|
|
2719
|
+
socket = null;
|
|
2720
|
+
window.dispatchEvent(new CustomEvent("overlay-ws-disconnected"));
|
|
2721
|
+
setTimeout(() => connect(url), 3e3);
|
|
2722
|
+
});
|
|
2723
|
+
socket.addEventListener("message", (event) => {
|
|
2724
|
+
try {
|
|
2725
|
+
const data = JSON.parse(event.data);
|
|
2726
|
+
for (const handler of handlers) {
|
|
2727
|
+
handler(data);
|
|
2728
|
+
}
|
|
2729
|
+
} catch (err) {
|
|
2730
|
+
console.error("[tw-overlay] Failed to parse message:", err);
|
|
2731
|
+
}
|
|
2732
|
+
});
|
|
2733
|
+
socket.addEventListener("error", (err) => {
|
|
2734
|
+
console.error("[tw-overlay] WebSocket error:", err);
|
|
2735
|
+
});
|
|
2736
|
+
}
|
|
2737
|
+
function send(data) {
|
|
2738
|
+
if (connected && socket) {
|
|
2739
|
+
socket.send(JSON.stringify(data));
|
|
2740
|
+
} else {
|
|
2741
|
+
console.warn("[tw-overlay] Cannot send \u2014 not connected");
|
|
2742
|
+
}
|
|
2743
|
+
}
|
|
2744
|
+
function onMessage(handler) {
|
|
2745
|
+
handlers.push(handler);
|
|
2746
|
+
}
|
|
2747
|
+
function sendTo(role, data) {
|
|
2748
|
+
send({ ...data, to: role });
|
|
2749
|
+
}
|
|
2750
|
+
|
|
2707
2751
|
// overlay/src/index.ts
|
|
2708
2752
|
var shadowRoot;
|
|
2709
2753
|
var shadowHost;
|
|
@@ -2886,11 +2930,40 @@ ${pad}</${tag}>`;
|
|
|
2886
2930
|
z-index: 999998;
|
|
2887
2931
|
}
|
|
2888
2932
|
/* \u2500\u2500 Group picker popover (replaces instance picker) \u2500\u2500 */
|
|
2933
|
+
.el-group-exact {
|
|
2934
|
+
display: flex;
|
|
2935
|
+
align-items: center;
|
|
2936
|
+
gap: 8px;
|
|
2937
|
+
padding: 8px 12px;
|
|
2938
|
+
font-size: 11px;
|
|
2939
|
+
color: #A0ABAB;
|
|
2940
|
+
}
|
|
2941
|
+
.el-group-exact .el-count-chip {
|
|
2942
|
+
display: inline-flex;
|
|
2943
|
+
align-items: center;
|
|
2944
|
+
justify-content: center;
|
|
2945
|
+
min-width: 20px;
|
|
2946
|
+
padding: 1px 6px;
|
|
2947
|
+
font-size: 10px;
|
|
2948
|
+
font-weight: 600;
|
|
2949
|
+
color: #fff;
|
|
2950
|
+
background: #00848B;
|
|
2951
|
+
border-radius: 9999px;
|
|
2952
|
+
}
|
|
2953
|
+
.el-group-divider {
|
|
2954
|
+
padding: 6px 12px 4px;
|
|
2955
|
+
font-size: 10px;
|
|
2956
|
+
font-weight: 700;
|
|
2957
|
+
text-transform: uppercase;
|
|
2958
|
+
letter-spacing: 0.05em;
|
|
2959
|
+
color: #687879;
|
|
2960
|
+
border-top: 1px solid #DFE2E2;
|
|
2961
|
+
}
|
|
2889
2962
|
.el-group-empty {
|
|
2890
2963
|
padding: 12px 14px;
|
|
2891
2964
|
font-size: 11px;
|
|
2892
2965
|
color: #687879;
|
|
2893
|
-
text-align:
|
|
2966
|
+
text-align: left;
|
|
2894
2967
|
}
|
|
2895
2968
|
.el-group-row {
|
|
2896
2969
|
display: flex;
|
|
@@ -3187,11 +3260,8 @@ ${pad}</${tag}>`;
|
|
|
3187
3260
|
const label = boundary?.componentName ?? target.tagName.toLowerCase();
|
|
3188
3261
|
showHoverPreview(target, label);
|
|
3189
3262
|
}
|
|
3190
|
-
var PENCIL_SVG = `<svg width="14" height="14" viewBox="0 0
|
|
3191
|
-
|
|
3192
|
-
<path d="M18.5 2.5a2.121 2.121 0 0 1 3 3L12 15l-4 1 1-4 9.5-9.5z"/>
|
|
3193
|
-
</svg>`;
|
|
3194
|
-
var RESELECT_SVG = `<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="3" width="18" height="18" rx="2"/><path d="M8 12l2.5 6 1.5-3 3-1.5z"/></svg>`;
|
|
3263
|
+
var PENCIL_SVG = `<svg width="14" height="14" viewBox="0 0 16 16" fill="currentColor"><path d="M15,0H1C0.448,0,0,0.448,0,1v9c0,0.552,0.448,1,1,1h2.882l-1.776,3.553c-0.247,0.494-0.047,1.095,0.447,1.342C2.696,15.966,2.849,16,2.999,16c0.367,0,0.72-0.202,0.896-0.553L4.618,14h6.764l0.724,1.447C12.281,15.798,12.634,16,13.001,16c0.15,0,0.303-0.034,0.446-0.105c0.494-0.247,0.694-0.848,0.447-1.342L12.118,11H15c0.552,0,1-0.448,1-1V1C16,0.448,15.552,0,15,0z M5.618,12l0.5-1h3.764l0.5,1H5.618z M14,9H2V2h12V9z"/></svg>`;
|
|
3264
|
+
var RESELECT_SVG = `<svg width="14" height="14" viewBox="0 0 16 16" fill="currentColor"><path d="M14,0H2C.895,0,0,.895,0,2V14c0,1.105,.895,2,2,2H6c.552,0,1-.448,1-1h0c0-.552-.448-1-1-1H2V2H14V6c0,.552,.448,1,1,1h0c.552,0,1-.448,1-1V2c0-1.105-.895-2-2-2Z"/><path d="M12.043,10.629l2.578-.644c.268-.068,.43-.339,.362-.607-.043-.172-.175-.308-.345-.358l-7-2c-.175-.051-.363-.002-.492,.126-.128,.129-.177,.317-.126,.492l2,7c.061,.214,.257,.362,.48,.362h.009c.226-.004,.421-.16,.476-.379l.644-2.578,3.664,3.664c.397,.384,1.03,.373,1.414-.025,.374-.388,.374-1.002,0-1.389l-3.664-3.664Z"/></svg>`;
|
|
3195
3265
|
async function positionWithFlip(anchor, floating, placement = "top-start") {
|
|
3196
3266
|
const { x, y } = await computePosition2(anchor, floating, {
|
|
3197
3267
|
placement,
|
|
@@ -3267,7 +3337,9 @@ ${pad}</${tag}>`;
|
|
|
3267
3337
|
}
|
|
3268
3338
|
function showGroupPicker(anchorBtn, onClose, onCountChange) {
|
|
3269
3339
|
if (pickerCloseHandler) {
|
|
3270
|
-
document.removeEventListener("click", pickerCloseHandler, {
|
|
3340
|
+
document.removeEventListener("click", pickerCloseHandler, {
|
|
3341
|
+
capture: true
|
|
3342
|
+
});
|
|
3271
3343
|
pickerCloseHandler = null;
|
|
3272
3344
|
}
|
|
3273
3345
|
pickerEl?.remove();
|
|
@@ -3286,13 +3358,28 @@ ${pad}</${tag}>`;
|
|
|
3286
3358
|
header.className = "el-picker-header";
|
|
3287
3359
|
const title = document.createElement("span");
|
|
3288
3360
|
title.className = "el-picker-title";
|
|
3289
|
-
title.textContent = "
|
|
3361
|
+
title.textContent = "Selection";
|
|
3290
3362
|
header.appendChild(title);
|
|
3291
3363
|
picker.appendChild(header);
|
|
3364
|
+
const exactCount = currentEquivalentNodes.length;
|
|
3365
|
+
const exactRow = document.createElement("div");
|
|
3366
|
+
exactRow.className = "el-group-exact";
|
|
3367
|
+
const chip = document.createElement("span");
|
|
3368
|
+
chip.className = "el-count-chip";
|
|
3369
|
+
chip.textContent = String(exactCount);
|
|
3370
|
+
exactRow.appendChild(chip);
|
|
3371
|
+
const exactLabel = document.createElement("span");
|
|
3372
|
+
exactLabel.textContent = `exact match${exactCount !== 1 ? "es" : ""} selected`;
|
|
3373
|
+
exactRow.appendChild(exactLabel);
|
|
3374
|
+
picker.appendChild(exactRow);
|
|
3375
|
+
const divider = document.createElement("div");
|
|
3376
|
+
divider.className = "el-group-divider";
|
|
3377
|
+
divider.textContent = "Similar";
|
|
3378
|
+
picker.appendChild(divider);
|
|
3292
3379
|
if (groups.length === 0) {
|
|
3293
3380
|
const empty = document.createElement("div");
|
|
3294
3381
|
empty.className = "el-group-empty";
|
|
3295
|
-
empty.textContent = "No similar elements found";
|
|
3382
|
+
empty.textContent = "No additional similar elements found";
|
|
3296
3383
|
picker.appendChild(empty);
|
|
3297
3384
|
} else {
|
|
3298
3385
|
let clearPreviewHighlights2 = function() {
|
|
@@ -3341,8 +3428,10 @@ ${pad}</${tag}>`;
|
|
|
3341
3428
|
const diff = document.createElement("span");
|
|
3342
3429
|
diff.className = "el-group-diff";
|
|
3343
3430
|
const parts = [];
|
|
3344
|
-
for (const a of group.added)
|
|
3345
|
-
|
|
3431
|
+
for (const a of group.added)
|
|
3432
|
+
parts.push(`<span class="diff-add">+${a}</span>`);
|
|
3433
|
+
for (const r of group.removed)
|
|
3434
|
+
parts.push(`<span class="diff-rem">-${r}</span>`);
|
|
3346
3435
|
diff.innerHTML = parts.join(" ");
|
|
3347
3436
|
row.appendChild(cb);
|
|
3348
3437
|
row.appendChild(count);
|
|
@@ -3370,7 +3459,9 @@ ${pad}</${tag}>`;
|
|
|
3370
3459
|
const removePicker = () => {
|
|
3371
3460
|
shadowRoot.querySelectorAll(".highlight-preview").forEach((el) => el.remove());
|
|
3372
3461
|
if (pickerCloseHandler) {
|
|
3373
|
-
document.removeEventListener("click", pickerCloseHandler, {
|
|
3462
|
+
document.removeEventListener("click", pickerCloseHandler, {
|
|
3463
|
+
capture: true
|
|
3464
|
+
});
|
|
3374
3465
|
pickerCloseHandler = null;
|
|
3375
3466
|
}
|
|
3376
3467
|
pickerEl?.remove();
|
|
@@ -3429,7 +3520,7 @@ ${pad}</${tag}>`;
|
|
|
3429
3520
|
const screenshotRow = document.createElement("button");
|
|
3430
3521
|
screenshotRow.className = "draw-popover-item";
|
|
3431
3522
|
screenshotRow.innerHTML = `
|
|
3432
|
-
<span class="draw-popover-icon"
|
|
3523
|
+
<span class="draw-popover-icon"><svg width="14" height="14" viewBox="0 0 16 16" fill="currentColor"><rect x="4" y="4" width="8" height="8" rx="1" ry="1"/><path d="M2,2H6V0H2C.895,0,0,.895,0,2V6H2V2Z"/><path d="M14,0h-4V2h4V6h2V2c0-1.105-.895-2-2-2Z"/><path d="M14,14h-4v2h4c1.105,0,2-.895,2-2v-4h-2v4Z"/><path d="M2,10H0v4c0,1.105,.895,2,2,2H6v-2H2v-4Z"/></svg></span>
|
|
3433
3524
|
<span class="draw-popover-label">Screenshot & Annotate</span>
|
|
3434
3525
|
`;
|
|
3435
3526
|
screenshotRow.addEventListener("click", (e) => {
|
|
@@ -3483,7 +3574,10 @@ ${pad}</${tag}>`;
|
|
|
3483
3574
|
async function clickHandler(e) {
|
|
3484
3575
|
const composed = e.composedPath();
|
|
3485
3576
|
if (composed.some((el) => el === shadowHost)) return;
|
|
3486
|
-
if (composed.some(
|
|
3577
|
+
if (composed.some(
|
|
3578
|
+
(el) => el instanceof HTMLElement && el.hasAttribute("data-tw-design-canvas")
|
|
3579
|
+
))
|
|
3580
|
+
return;
|
|
3487
3581
|
e.preventDefault();
|
|
3488
3582
|
e.stopPropagation();
|
|
3489
3583
|
const target = e.target;
|
|
@@ -3495,7 +3589,6 @@ ${pad}</${tag}>`;
|
|
|
3495
3589
|
for (const node of result.exactMatch) {
|
|
3496
3590
|
highlightElement(node);
|
|
3497
3591
|
}
|
|
3498
|
-
console.log(`[overlay] ${componentName} \u2014 ${result.exactMatch.length} exact matches highlighted`);
|
|
3499
3592
|
const config = await fetchTailwindConfig();
|
|
3500
3593
|
currentEquivalentNodes = result.exactMatch;
|
|
3501
3594
|
currentTargetEl = targetEl;
|
|
@@ -3590,6 +3683,7 @@ ${pad}</${tag}>`;
|
|
|
3590
3683
|
`;
|
|
3591
3684
|
const iframe = document.createElement("iframe");
|
|
3592
3685
|
iframe.src = `${SERVER_ORIGIN}/panel/?mode=design`;
|
|
3686
|
+
iframe.allow = "microphone";
|
|
3593
3687
|
iframe.style.cssText = `
|
|
3594
3688
|
width: 100%;
|
|
3595
3689
|
height: 100%;
|
|
@@ -3702,7 +3796,12 @@ ${pad}</${tag}>`;
|
|
|
3702
3796
|
default:
|
|
3703
3797
|
targetEl.insertAdjacentElement("beforebegin", wrapper);
|
|
3704
3798
|
}
|
|
3705
|
-
designCanvasWrappers.push({
|
|
3799
|
+
designCanvasWrappers.push({
|
|
3800
|
+
wrapper,
|
|
3801
|
+
replacedNodes: null,
|
|
3802
|
+
parent: null,
|
|
3803
|
+
anchor: null
|
|
3804
|
+
});
|
|
3706
3805
|
iframe.addEventListener("load", () => {
|
|
3707
3806
|
const contextMsg = {
|
|
3708
3807
|
type: "ELEMENT_CONTEXT",
|
|
@@ -3731,23 +3830,36 @@ ${pad}</${tag}>`;
|
|
|
3731
3830
|
return;
|
|
3732
3831
|
}
|
|
3733
3832
|
if (!areSiblings(currentEquivalentNodes)) {
|
|
3734
|
-
showToast(
|
|
3833
|
+
showToast(
|
|
3834
|
+
"Screenshot & Annotate requires all selected elements to be siblings in the DOM."
|
|
3835
|
+
);
|
|
3735
3836
|
return;
|
|
3736
3837
|
}
|
|
3737
3838
|
let screenshot;
|
|
3738
3839
|
let screenshotWidth;
|
|
3739
3840
|
let screenshotHeight;
|
|
3740
3841
|
try {
|
|
3741
|
-
({
|
|
3842
|
+
({
|
|
3843
|
+
dataUrl: screenshot,
|
|
3844
|
+
width: screenshotWidth,
|
|
3845
|
+
height: screenshotHeight
|
|
3846
|
+
} = await captureRegion(currentEquivalentNodes));
|
|
3742
3847
|
} catch (err) {
|
|
3743
3848
|
showToast("Screenshot capture failed");
|
|
3744
3849
|
console.error("[overlay] captureRegion error:", err);
|
|
3745
3850
|
return;
|
|
3746
3851
|
}
|
|
3747
3852
|
const parent = currentEquivalentNodes[0].parentElement;
|
|
3748
|
-
|
|
3853
|
+
if (!parent) {
|
|
3854
|
+
showToast("Cannot find parent element");
|
|
3855
|
+
return;
|
|
3856
|
+
}
|
|
3857
|
+
const marker = document.createComment("tw-placeholder");
|
|
3858
|
+
parent.insertBefore(marker, currentEquivalentNodes[0]);
|
|
3749
3859
|
const firstStyle = getComputedStyle(currentEquivalentNodes[0]);
|
|
3750
|
-
const lastStyle = getComputedStyle(
|
|
3860
|
+
const lastStyle = getComputedStyle(
|
|
3861
|
+
currentEquivalentNodes[currentEquivalentNodes.length - 1]
|
|
3862
|
+
);
|
|
3751
3863
|
const marginTop = firstStyle.marginTop;
|
|
3752
3864
|
const marginBottom = lastStyle.marginBottom;
|
|
3753
3865
|
const marginLeft = firstStyle.marginLeft;
|
|
@@ -3789,12 +3901,9 @@ ${pad}</${tag}>`;
|
|
|
3789
3901
|
display: block;
|
|
3790
3902
|
`;
|
|
3791
3903
|
wrapper.appendChild(iframe);
|
|
3792
|
-
|
|
3793
|
-
|
|
3794
|
-
|
|
3795
|
-
parent.appendChild(wrapper);
|
|
3796
|
-
}
|
|
3797
|
-
designCanvasWrappers.push({ wrapper, replacedNodes, parent, anchor });
|
|
3904
|
+
parent.insertBefore(wrapper, marker);
|
|
3905
|
+
marker.remove();
|
|
3906
|
+
designCanvasWrappers.push({ wrapper, replacedNodes, parent, anchor: wrapper.nextSibling });
|
|
3798
3907
|
iframe.addEventListener("load", () => {
|
|
3799
3908
|
const contextMsg = {
|
|
3800
3909
|
type: "ELEMENT_CONTEXT",
|
|
@@ -3867,9 +3976,18 @@ ${pad}</${tag}>`;
|
|
|
3867
3976
|
setSelectMode(false);
|
|
3868
3977
|
}
|
|
3869
3978
|
} else if (msg.type === "PATCH_PREVIEW" && currentEquivalentNodes.length > 0) {
|
|
3870
|
-
applyPreview(
|
|
3979
|
+
applyPreview(
|
|
3980
|
+
currentEquivalentNodes,
|
|
3981
|
+
msg.oldClass,
|
|
3982
|
+
msg.newClass,
|
|
3983
|
+
SERVER_ORIGIN
|
|
3984
|
+
);
|
|
3985
|
+
} else if (msg.type === "PATCH_PREVIEW_BATCH" && currentEquivalentNodes.length > 0) {
|
|
3986
|
+
applyPreviewBatch(currentEquivalentNodes, msg.pairs, SERVER_ORIGIN);
|
|
3871
3987
|
} else if (msg.type === "PATCH_REVERT") {
|
|
3872
3988
|
revertPreview();
|
|
3989
|
+
} else if (msg.type === "PATCH_REVERT_STAGED" && currentEquivalentNodes.length > 0) {
|
|
3990
|
+
applyPreview(currentEquivalentNodes, msg.oldClass, msg.newClass, SERVER_ORIGIN).then(() => commitPreview());
|
|
3873
3991
|
} else if (msg.type === "PATCH_STAGE" && currentTargetEl && currentBoundary) {
|
|
3874
3992
|
const state = getPreviewState();
|
|
3875
3993
|
const originalClassMap = /* @__PURE__ */ new Map();
|
|
@@ -3880,7 +3998,12 @@ ${pad}</${tag}>`;
|
|
|
3880
3998
|
}
|
|
3881
3999
|
const targetElIndex = currentEquivalentNodes.indexOf(currentTargetEl);
|
|
3882
4000
|
const originalClassString = state && targetElIndex !== -1 ? state.originalClasses[targetElIndex] : currentTargetEl.className;
|
|
3883
|
-
const context = buildContext(
|
|
4001
|
+
const context = buildContext(
|
|
4002
|
+
currentTargetEl,
|
|
4003
|
+
msg.oldClass,
|
|
4004
|
+
msg.newClass,
|
|
4005
|
+
originalClassMap
|
|
4006
|
+
);
|
|
3884
4007
|
send({
|
|
3885
4008
|
type: "PATCH_STAGED",
|
|
3886
4009
|
patch: {
|
|
@@ -3940,6 +4063,8 @@ ${pad}</${tag}>`;
|
|
|
3940
4063
|
last.appendChild(img);
|
|
3941
4064
|
}
|
|
3942
4065
|
}
|
|
4066
|
+
} else if (msg.type === "CLOSE_PANEL") {
|
|
4067
|
+
if (active) toggleInspect(btn);
|
|
3943
4068
|
} else if (msg.type === "DESIGN_CLOSE") {
|
|
3944
4069
|
const last = designCanvasWrappers.pop();
|
|
3945
4070
|
if (last) {
|
|
@@ -3953,6 +4078,12 @@ ${pad}</${tag}>`;
|
|
|
3953
4078
|
}
|
|
3954
4079
|
}
|
|
3955
4080
|
last.wrapper.remove();
|
|
4081
|
+
if (currentTargetEl && currentEquivalentNodes.length > 0) {
|
|
4082
|
+
for (const n of currentEquivalentNodes) {
|
|
4083
|
+
highlightElement(n);
|
|
4084
|
+
}
|
|
4085
|
+
showDrawButton(currentTargetEl);
|
|
4086
|
+
}
|
|
3956
4087
|
}
|
|
3957
4088
|
}
|
|
3958
4089
|
});
|
|
@@ -3965,15 +4096,19 @@ ${pad}</${tag}>`;
|
|
|
3965
4096
|
positionWithFlip(currentTargetEl, toolbarEl);
|
|
3966
4097
|
}
|
|
3967
4098
|
});
|
|
3968
|
-
window.addEventListener(
|
|
3969
|
-
|
|
3970
|
-
|
|
3971
|
-
currentEquivalentNodes.
|
|
3972
|
-
|
|
3973
|
-
|
|
3974
|
-
|
|
3975
|
-
|
|
3976
|
-
|
|
4099
|
+
window.addEventListener(
|
|
4100
|
+
"scroll",
|
|
4101
|
+
() => {
|
|
4102
|
+
if (currentEquivalentNodes.length > 0) {
|
|
4103
|
+
shadowRoot.querySelectorAll(".highlight-overlay").forEach((el) => el.remove());
|
|
4104
|
+
currentEquivalentNodes.forEach((n) => highlightElement(n));
|
|
4105
|
+
}
|
|
4106
|
+
if (toolbarEl && currentTargetEl) {
|
|
4107
|
+
positionWithFlip(currentTargetEl, toolbarEl);
|
|
4108
|
+
}
|
|
4109
|
+
},
|
|
4110
|
+
{ capture: true, passive: true }
|
|
4111
|
+
);
|
|
3977
4112
|
if (sessionStorage.getItem(PANEL_OPEN_KEY) === "1") {
|
|
3978
4113
|
active = true;
|
|
3979
4114
|
btn.classList.add("active");
|