@designfever/web-review-kit 0.7.3 → 0.8.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/README.md +7 -4
- package/dist/{chunk-RPVLRULC.js → chunk-4ZP7B7R6.js} +2345 -1786
- package/dist/chunk-4ZP7B7R6.js.map +1 -0
- package/dist/index.cjs +2348 -1793
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +11 -4
- package/dist/index.d.ts +11 -4
- package/dist/index.js +8 -2
- package/dist/index.js.map +1 -1
- package/dist/react-shell.cjs +14508 -11099
- package/dist/react-shell.cjs.map +1 -1
- package/dist/react-shell.d.cts +5 -4
- package/dist/react-shell.d.ts +5 -4
- package/dist/react-shell.js +10634 -7710
- package/dist/react-shell.js.map +1 -1
- package/dist/{types-DT9Z66mV.d.cts → types-BM8E7BFV.d.cts} +63 -3
- package/dist/{types-DT9Z66mV.d.ts → types-BM8E7BFV.d.ts} +63 -3
- package/dist/vite.cjs +344 -48
- package/dist/vite.cjs.map +1 -1
- package/dist/vite.d.cts +2 -0
- package/dist/vite.d.ts +2 -0
- package/dist/vite.js +345 -49
- package/dist/vite.js.map +1 -1
- package/docs/README.md +19 -13
- package/docs/adapters.md +27 -4
- package/docs/adaptor.sample.ts +53 -3
- package/docs/architecture.md +117 -3
- package/docs/figma-overlay.md +51 -0
- package/docs/grid-overlay.md +5 -0
- package/docs/installation.md +23 -8
- package/docs/release-notes-0.8.0.md +175 -0
- package/docs/testing.md +68 -0
- package/package.json +14 -5
- package/dist/chunk-RPVLRULC.js.map +0 -1
- package/docs/code-review-0.6.0.md +0 -232
- package/docs/figma-image-mvp-0.7.0.md +0 -330
|
@@ -99,8 +99,8 @@ function localAdapter(options = {}) {
|
|
|
99
99
|
function normalizeStoredReviewItem(value) {
|
|
100
100
|
if (!value || typeof value !== "object") return void 0;
|
|
101
101
|
const raw = value;
|
|
102
|
-
const kind = raw.kind === "
|
|
103
|
-
if (kind !== "
|
|
102
|
+
const kind = raw.kind === "capture" ? "area" : raw.kind;
|
|
103
|
+
if (kind !== "dom" && kind !== "area") return void 0;
|
|
104
104
|
const { screenshot: _screenshot, reviewNumber: _reviewNumber, ...item } = raw;
|
|
105
105
|
if (kind === raw.kind && _screenshot === void 0 && _reviewNumber === void 0) {
|
|
106
106
|
return raw;
|
|
@@ -125,8 +125,7 @@ var REVIEW_SCOPE_LABELS = {
|
|
|
125
125
|
mobile: "Mobile",
|
|
126
126
|
tablet: "Tablet",
|
|
127
127
|
desktop: "Desktop",
|
|
128
|
-
wide: "Wide"
|
|
129
|
-
dom: "Element"
|
|
128
|
+
wide: "Wide"
|
|
130
129
|
};
|
|
131
130
|
var normalizeReviewItemScope = (value) => {
|
|
132
131
|
if (value === "element") return "dom";
|
|
@@ -172,7 +171,6 @@ function getReviewItemScope(item, presets = DEFAULT_REVIEW_VIEWPORTS) {
|
|
|
172
171
|
}
|
|
173
172
|
function getReviewItemScopeLabel(item, presets = DEFAULT_REVIEW_VIEWPORTS) {
|
|
174
173
|
const scope = getReviewItemScope(item, presets);
|
|
175
|
-
if (scope === "dom") return REVIEW_SCOPE_LABELS.dom;
|
|
176
174
|
const preset = findReviewViewportPreset(item.viewport, presets);
|
|
177
175
|
return preset.label || REVIEW_SCOPE_LABELS[scope];
|
|
178
176
|
}
|
|
@@ -315,26 +313,34 @@ function getPopoverBounds(environment) {
|
|
|
315
313
|
}
|
|
316
314
|
return environment.overlayRect;
|
|
317
315
|
}
|
|
316
|
+
function getEnvironmentScale(environment) {
|
|
317
|
+
const scaleX = typeof environment.scaleX === "number" && Number.isFinite(environment.scaleX) && environment.scaleX > 0 ? environment.scaleX : 1;
|
|
318
|
+
const scaleY = typeof environment.scaleY === "number" && Number.isFinite(environment.scaleY) && environment.scaleY > 0 ? environment.scaleY : 1;
|
|
319
|
+
return { scaleX, scaleY };
|
|
320
|
+
}
|
|
318
321
|
function toHostPoint(point, environment) {
|
|
319
322
|
if (!environment) return point;
|
|
323
|
+
const { scaleX, scaleY } = getEnvironmentScale(environment);
|
|
320
324
|
return {
|
|
321
|
-
x: point.x + environment.viewportRect.left,
|
|
322
|
-
y: point.y + environment.viewportRect.top
|
|
325
|
+
x: point.x * scaleX + environment.viewportRect.left,
|
|
326
|
+
y: point.y * scaleY + environment.viewportRect.top
|
|
323
327
|
};
|
|
324
328
|
}
|
|
325
329
|
function toHostSelection(selection, environment) {
|
|
330
|
+
const { scaleX, scaleY } = getEnvironmentScale(environment);
|
|
326
331
|
return {
|
|
327
|
-
left: selection.left + environment.viewportRect.left,
|
|
328
|
-
top: selection.top + environment.viewportRect.top,
|
|
329
|
-
width: selection.width,
|
|
330
|
-
height: selection.height
|
|
332
|
+
left: selection.left * scaleX + environment.viewportRect.left,
|
|
333
|
+
top: selection.top * scaleY + environment.viewportRect.top,
|
|
334
|
+
width: selection.width * scaleX,
|
|
335
|
+
height: selection.height * scaleY
|
|
331
336
|
};
|
|
332
337
|
}
|
|
333
338
|
function toTargetPoint(point, environment) {
|
|
334
339
|
if (!environment) return point;
|
|
340
|
+
const { scaleX, scaleY } = getEnvironmentScale(environment);
|
|
335
341
|
return {
|
|
336
|
-
x: point.x - environment.viewportRect.left,
|
|
337
|
-
y: point.y - environment.viewportRect.top
|
|
342
|
+
x: (point.x - environment.viewportRect.left) / scaleX,
|
|
343
|
+
y: (point.y - environment.viewportRect.top) / scaleY
|
|
338
344
|
};
|
|
339
345
|
}
|
|
340
346
|
function toTargetPointFromHostEvent(event, environment) {
|
|
@@ -383,11 +389,6 @@ var SEMANTIC_ANCHOR_ATTRIBUTES = [
|
|
|
383
389
|
"name",
|
|
384
390
|
"href"
|
|
385
391
|
];
|
|
386
|
-
function getDomAnchor(selection, configuredAttribute = "data-qa-id", environment) {
|
|
387
|
-
const x = selection.left + selection.width / 2;
|
|
388
|
-
const y = selection.top + selection.height / 2;
|
|
389
|
-
return getDomAnchorFromPoint({ x, y }, configuredAttribute, environment);
|
|
390
|
-
}
|
|
391
392
|
function getDomAnchorFromPoint(point, configuredAttribute = "data-qa-id", environment) {
|
|
392
393
|
const target = environment.document.elementFromPoint(point.x, point.y);
|
|
393
394
|
if (!target) return void 0;
|
|
@@ -937,6 +938,34 @@ function createId() {
|
|
|
937
938
|
}
|
|
938
939
|
|
|
939
940
|
// src/core/hotkey.ts
|
|
941
|
+
var HOTKEY_KEY_ALIASES = {
|
|
942
|
+
q: ["\u3142", "\u3143"],
|
|
943
|
+
w: ["\u3148", "\u3149"],
|
|
944
|
+
e: ["\u3137", "\u3138"],
|
|
945
|
+
r: ["\u3131", "\u3132"],
|
|
946
|
+
t: ["\u3145", "\u3146"],
|
|
947
|
+
y: ["\u315B"],
|
|
948
|
+
u: ["\u3155"],
|
|
949
|
+
i: ["\u3151"],
|
|
950
|
+
o: ["\u3150", "\u3152"],
|
|
951
|
+
p: ["\u3154", "\u3156"],
|
|
952
|
+
a: ["\u3141"],
|
|
953
|
+
s: ["\u3134"],
|
|
954
|
+
d: ["\u3147"],
|
|
955
|
+
f: ["\u3139"],
|
|
956
|
+
g: ["\u314E"],
|
|
957
|
+
h: ["\u3157"],
|
|
958
|
+
j: ["\u3153"],
|
|
959
|
+
k: ["\u314F"],
|
|
960
|
+
l: ["\u3163"],
|
|
961
|
+
z: ["\u314B"],
|
|
962
|
+
x: ["\u314C"],
|
|
963
|
+
c: ["\u314A"],
|
|
964
|
+
v: ["\u314D"],
|
|
965
|
+
b: ["\u3160"],
|
|
966
|
+
n: ["\u315C"],
|
|
967
|
+
m: ["\u3161"]
|
|
968
|
+
};
|
|
940
969
|
function isHotkey(event, hotkey) {
|
|
941
970
|
const parts = hotkey.split("+").map((part) => part.trim().toLowerCase()).filter(Boolean);
|
|
942
971
|
const key = parts.find(
|
|
@@ -953,14 +982,14 @@ function isHotkey(event, hotkey) {
|
|
|
953
982
|
}
|
|
954
983
|
return isHotkeyKey(event, key);
|
|
955
984
|
}
|
|
985
|
+
function getHotkeyActionKey(event, keys) {
|
|
986
|
+
return keys.find((key) => isHotkeyKey(event, key));
|
|
987
|
+
}
|
|
956
988
|
function isHotkeyKey(event, key) {
|
|
957
989
|
const normalizedKey = key.toLowerCase();
|
|
958
990
|
if (event.key.toLowerCase() === normalizedKey) return true;
|
|
959
991
|
if (getHotkeyCode(normalizedKey) === event.code) return true;
|
|
960
|
-
|
|
961
|
-
q: ["\u3142", "\u3143"]
|
|
962
|
-
};
|
|
963
|
-
return aliases[normalizedKey]?.includes(event.key) ?? false;
|
|
992
|
+
return HOTKEY_KEY_ALIASES[normalizedKey]?.includes(event.key) ?? false;
|
|
964
993
|
}
|
|
965
994
|
function getHotkeyCode(key) {
|
|
966
995
|
if (/^[a-z]$/.test(key)) return `Key${key.toUpperCase()}`;
|
|
@@ -995,7 +1024,7 @@ function getPublicSearch(location) {
|
|
|
995
1024
|
function getBoundMarkerPoint(item, environment) {
|
|
996
1025
|
const marker = getItemMarker(item);
|
|
997
1026
|
if (!marker) return void 0;
|
|
998
|
-
if (item.
|
|
1027
|
+
if (item.anchor && marker.relative) {
|
|
999
1028
|
const resolved = resolveAnchorElement(item.anchor, environment);
|
|
1000
1029
|
const element = resolved?.element;
|
|
1001
1030
|
if (element) {
|
|
@@ -1043,27 +1072,25 @@ function getItemHighlightSelection(item, environment) {
|
|
|
1043
1072
|
environment
|
|
1044
1073
|
);
|
|
1045
1074
|
}
|
|
1046
|
-
return
|
|
1047
|
-
[
|
|
1048
|
-
getAnchorHighlightSelection(item, environment),
|
|
1049
|
-
getBoundSelection(item, environment),
|
|
1050
|
-
getPointHighlightSelection(item, environment)
|
|
1051
|
-
],
|
|
1052
|
-
environment
|
|
1053
|
-
);
|
|
1075
|
+
return void 0;
|
|
1054
1076
|
}
|
|
1055
1077
|
function getReviewItemHighlightMode(item) {
|
|
1056
1078
|
if (isDomReviewItem(item)) return "dom";
|
|
1057
|
-
|
|
1058
|
-
return "note";
|
|
1079
|
+
return "area";
|
|
1059
1080
|
}
|
|
1060
1081
|
function getItemMarker(item) {
|
|
1061
1082
|
if (item.marker) return item.marker;
|
|
1062
1083
|
const selection = getItemSelection(item);
|
|
1063
1084
|
if (!selection?.viewport) return void 0;
|
|
1064
1085
|
return {
|
|
1065
|
-
viewport: roundPoint(
|
|
1066
|
-
|
|
1086
|
+
viewport: roundPoint({
|
|
1087
|
+
x: selection.viewport.x,
|
|
1088
|
+
y: selection.viewport.y
|
|
1089
|
+
}),
|
|
1090
|
+
relative: selection.relative ? roundPoint({
|
|
1091
|
+
x: selection.relative.x,
|
|
1092
|
+
y: selection.relative.y
|
|
1093
|
+
}) : void 0
|
|
1067
1094
|
};
|
|
1068
1095
|
}
|
|
1069
1096
|
function getItemSelection(item) {
|
|
@@ -1082,17 +1109,20 @@ function getItemSelection(item) {
|
|
|
1082
1109
|
function shouldShowMarkerForScope(scope, currentScope) {
|
|
1083
1110
|
return scope === currentScope;
|
|
1084
1111
|
}
|
|
1085
|
-
function
|
|
1086
|
-
const
|
|
1112
|
+
function createSelectionStartMarker(selection, anchor, environment) {
|
|
1113
|
+
const startPoint = {
|
|
1114
|
+
x: selection.left,
|
|
1115
|
+
y: selection.top
|
|
1116
|
+
};
|
|
1087
1117
|
return {
|
|
1088
|
-
viewport: roundPoint(
|
|
1089
|
-
relative: anchor ? getRelativePoint(
|
|
1118
|
+
viewport: roundPoint(startPoint),
|
|
1119
|
+
relative: anchor ? getRelativePoint(startPoint, anchor, environment) : void 0
|
|
1090
1120
|
};
|
|
1091
1121
|
}
|
|
1092
1122
|
function getBoundSelection(item, environment) {
|
|
1093
1123
|
const selection = getItemSelection(item);
|
|
1094
1124
|
if (!selection?.viewport) return void 0;
|
|
1095
|
-
if (item.
|
|
1125
|
+
if (item.anchor && selection.relative) {
|
|
1096
1126
|
const resolved = resolveAnchorElement(item.anchor, environment);
|
|
1097
1127
|
const element = resolved?.element;
|
|
1098
1128
|
if (element) {
|
|
@@ -1154,7 +1184,7 @@ function getVisibleHighlightSelection(candidates, environment) {
|
|
|
1154
1184
|
);
|
|
1155
1185
|
}
|
|
1156
1186
|
function isDomReviewItem(item) {
|
|
1157
|
-
return item.
|
|
1187
|
+
return item.kind === "dom" || item.scope === "dom";
|
|
1158
1188
|
}
|
|
1159
1189
|
|
|
1160
1190
|
// src/core/scroll.ts
|
|
@@ -1251,6 +1281,117 @@ function getAdjustedDraftSelection(selection, draft, presets) {
|
|
|
1251
1281
|
};
|
|
1252
1282
|
}
|
|
1253
1283
|
|
|
1284
|
+
// src/core/draft.preview.ts
|
|
1285
|
+
var DraftPreviewController = class {
|
|
1286
|
+
constructor(config) {
|
|
1287
|
+
this.config = config;
|
|
1288
|
+
}
|
|
1289
|
+
clear() {
|
|
1290
|
+
if (!this.snapshot) return;
|
|
1291
|
+
const { element, clone, visibility } = this.snapshot;
|
|
1292
|
+
clone.remove();
|
|
1293
|
+
element.style.visibility = visibility;
|
|
1294
|
+
this.snapshot = void 0;
|
|
1295
|
+
}
|
|
1296
|
+
sync(draft) {
|
|
1297
|
+
const environment = this.config.getEnvironment();
|
|
1298
|
+
if (!draft || !environment || !this.config.hasAdjustment(draft)) {
|
|
1299
|
+
this.clear();
|
|
1300
|
+
return;
|
|
1301
|
+
}
|
|
1302
|
+
const element = this.getStyleableDraftElement(draft, environment);
|
|
1303
|
+
if (!element) {
|
|
1304
|
+
this.clear();
|
|
1305
|
+
return;
|
|
1306
|
+
}
|
|
1307
|
+
if (this.snapshot?.element !== element) {
|
|
1308
|
+
this.clear();
|
|
1309
|
+
}
|
|
1310
|
+
if (!this.snapshot) {
|
|
1311
|
+
const computedStyle = environment.window.getComputedStyle(element);
|
|
1312
|
+
const clone = element.cloneNode(true);
|
|
1313
|
+
removeDuplicateIds(clone);
|
|
1314
|
+
copyComputedStyle(element, clone, environment);
|
|
1315
|
+
positionDraftPreviewClone(clone, element, computedStyle);
|
|
1316
|
+
environment.document.body?.appendChild(clone);
|
|
1317
|
+
this.snapshot = {
|
|
1318
|
+
element,
|
|
1319
|
+
clone,
|
|
1320
|
+
visibility: element.style.visibility
|
|
1321
|
+
};
|
|
1322
|
+
element.style.visibility = "hidden";
|
|
1323
|
+
}
|
|
1324
|
+
const metrics = this.config.getMetrics(draft);
|
|
1325
|
+
const translate = `translate(${toCssNumber(metrics.cssX)}px, ${toCssNumber(
|
|
1326
|
+
metrics.cssY
|
|
1327
|
+
)}px)`;
|
|
1328
|
+
const scale = metrics.scaleFactor === 1 ? "" : `scale(${toCssNumber(metrics.scaleFactor)})`;
|
|
1329
|
+
this.snapshot.clone.style.transform = [translate, scale].filter(Boolean).join(" ");
|
|
1330
|
+
}
|
|
1331
|
+
getStyleableDraftElement(draft, environment) {
|
|
1332
|
+
if (draft.previewElement && draft.previewElement.ownerDocument === environment.document && "style" in draft.previewElement) {
|
|
1333
|
+
return draft.previewElement;
|
|
1334
|
+
}
|
|
1335
|
+
if (!draft.anchor) return void 0;
|
|
1336
|
+
const preferredSelection = draft.selection ? toViewportSelection(draft.selection.viewport) : void 0;
|
|
1337
|
+
const element = resolveAnchorElement(
|
|
1338
|
+
draft.anchor,
|
|
1339
|
+
environment,
|
|
1340
|
+
preferredSelection
|
|
1341
|
+
)?.element;
|
|
1342
|
+
if (!element) return void 0;
|
|
1343
|
+
if ("style" in element) return element;
|
|
1344
|
+
return void 0;
|
|
1345
|
+
}
|
|
1346
|
+
};
|
|
1347
|
+
function positionDraftPreviewClone(clone, element, computedStyle) {
|
|
1348
|
+
const rect = element.getBoundingClientRect();
|
|
1349
|
+
clone.setAttribute("data-dfwr-adjust-preview", "true");
|
|
1350
|
+
clone.setAttribute("aria-hidden", "true");
|
|
1351
|
+
clone.style.position = "fixed";
|
|
1352
|
+
clone.style.left = `${toCssNumber(rect.left)}px`;
|
|
1353
|
+
clone.style.top = `${toCssNumber(rect.top)}px`;
|
|
1354
|
+
clone.style.right = "auto";
|
|
1355
|
+
clone.style.bottom = "auto";
|
|
1356
|
+
clone.style.width = `${toCssNumber(rect.width)}px`;
|
|
1357
|
+
clone.style.height = `${toCssNumber(rect.height)}px`;
|
|
1358
|
+
clone.style.maxWidth = "none";
|
|
1359
|
+
clone.style.maxHeight = "none";
|
|
1360
|
+
clone.style.margin = "0";
|
|
1361
|
+
clone.style.boxSizing = "border-box";
|
|
1362
|
+
clone.style.display = getDraftPreviewDisplay(computedStyle.display);
|
|
1363
|
+
clone.style.zIndex = "2147483646";
|
|
1364
|
+
clone.style.pointerEvents = "none";
|
|
1365
|
+
clone.style.transition = "none";
|
|
1366
|
+
clone.style.willChange = "transform";
|
|
1367
|
+
clone.style.transformOrigin = "top left";
|
|
1368
|
+
clone.style.transform = "none";
|
|
1369
|
+
}
|
|
1370
|
+
function getDraftPreviewDisplay(display) {
|
|
1371
|
+
if (display === "inline" || display === "contents") return "inline-block";
|
|
1372
|
+
return display || "block";
|
|
1373
|
+
}
|
|
1374
|
+
function copyComputedStyle(element, clone, environment) {
|
|
1375
|
+
const computedStyle = environment.window.getComputedStyle(element);
|
|
1376
|
+
for (let index = 0; index < computedStyle.length; index += 1) {
|
|
1377
|
+
const property = computedStyle.item(index);
|
|
1378
|
+
clone.style.setProperty(
|
|
1379
|
+
property,
|
|
1380
|
+
computedStyle.getPropertyValue(property),
|
|
1381
|
+
computedStyle.getPropertyPriority(property)
|
|
1382
|
+
);
|
|
1383
|
+
}
|
|
1384
|
+
}
|
|
1385
|
+
function removeDuplicateIds(element) {
|
|
1386
|
+
element.removeAttribute("id");
|
|
1387
|
+
element.querySelectorAll("[id]").forEach((child) => {
|
|
1388
|
+
child.removeAttribute("id");
|
|
1389
|
+
});
|
|
1390
|
+
}
|
|
1391
|
+
function toCssNumber(value) {
|
|
1392
|
+
return Math.round(value * 1e3) / 1e3;
|
|
1393
|
+
}
|
|
1394
|
+
|
|
1254
1395
|
// src/core/typography.tokens.ts
|
|
1255
1396
|
var reviewTypographyTokens = `
|
|
1256
1397
|
--df-review-font-sans: Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
|
|
@@ -1574,6 +1715,36 @@ function createStyleElement() {
|
|
|
1574
1715
|
--dfwr-item-color-rgb: 255, 143, 97;
|
|
1575
1716
|
}
|
|
1576
1717
|
|
|
1718
|
+
.dfwr-item-target-highlight.is-scope-mobile,
|
|
1719
|
+
.dfwr-item-target-label.is-scope-mobile {
|
|
1720
|
+
--dfwr-item-color: #7cc7ff;
|
|
1721
|
+
--dfwr-item-color-rgb: 124, 199, 255;
|
|
1722
|
+
}
|
|
1723
|
+
|
|
1724
|
+
.dfwr-item-target-highlight.is-scope-tablet,
|
|
1725
|
+
.dfwr-item-target-label.is-scope-tablet {
|
|
1726
|
+
--dfwr-item-color: #63d7c7;
|
|
1727
|
+
--dfwr-item-color-rgb: 99, 215, 199;
|
|
1728
|
+
}
|
|
1729
|
+
|
|
1730
|
+
.dfwr-item-target-highlight.is-scope-desktop,
|
|
1731
|
+
.dfwr-item-target-label.is-scope-desktop {
|
|
1732
|
+
--dfwr-item-color: #f3b75f;
|
|
1733
|
+
--dfwr-item-color-rgb: 243, 183, 95;
|
|
1734
|
+
}
|
|
1735
|
+
|
|
1736
|
+
.dfwr-item-target-highlight.is-scope-wide,
|
|
1737
|
+
.dfwr-item-target-label.is-scope-wide {
|
|
1738
|
+
--dfwr-item-color: #c99cff;
|
|
1739
|
+
--dfwr-item-color-rgb: 201, 156, 255;
|
|
1740
|
+
}
|
|
1741
|
+
|
|
1742
|
+
.dfwr-item-target-highlight.is-scope-dom,
|
|
1743
|
+
.dfwr-item-target-label.is-scope-dom {
|
|
1744
|
+
--dfwr-item-color: #ff8f61;
|
|
1745
|
+
--dfwr-item-color-rgb: 255, 143, 97;
|
|
1746
|
+
}
|
|
1747
|
+
|
|
1577
1748
|
.dfwr-item-target-highlight {
|
|
1578
1749
|
position: fixed;
|
|
1579
1750
|
z-index: 2;
|
|
@@ -1582,7 +1753,6 @@ function createStyleElement() {
|
|
|
1582
1753
|
background: rgba(var(--dfwr-item-color-rgb), 0.08);
|
|
1583
1754
|
box-shadow:
|
|
1584
1755
|
0 0 0 1px rgba(31, 36, 40, 0.78),
|
|
1585
|
-
0 0 0 9999px rgba(0, 0, 0, 0.08),
|
|
1586
1756
|
0 12px 30px rgba(0, 0, 0, 0.24);
|
|
1587
1757
|
pointer-events: none;
|
|
1588
1758
|
}
|
|
@@ -1666,89 +1836,6 @@ function createStyleElement() {
|
|
|
1666
1836
|
border-style: dashed;
|
|
1667
1837
|
}
|
|
1668
1838
|
|
|
1669
|
-
.dfwr-bound-marker.is-note-callout,
|
|
1670
|
-
.dfwr-bound-marker.is-note-callout.is-highlighted {
|
|
1671
|
-
--dfwr-scope: #7cc7ff;
|
|
1672
|
-
--dfwr-scope-rgb: 124, 199, 255;
|
|
1673
|
-
min-width: 0;
|
|
1674
|
-
width: 0;
|
|
1675
|
-
height: 0;
|
|
1676
|
-
padding: 0;
|
|
1677
|
-
transform: none;
|
|
1678
|
-
border: 0;
|
|
1679
|
-
border-radius: 0;
|
|
1680
|
-
background: transparent;
|
|
1681
|
-
box-shadow: none;
|
|
1682
|
-
color: var(--dfwr-scope);
|
|
1683
|
-
animation: none;
|
|
1684
|
-
overflow: visible;
|
|
1685
|
-
}
|
|
1686
|
-
|
|
1687
|
-
.dfwr-bound-marker.is-note-callout::before {
|
|
1688
|
-
content: "";
|
|
1689
|
-
position: absolute;
|
|
1690
|
-
left: 0;
|
|
1691
|
-
top: 0;
|
|
1692
|
-
width: 8px;
|
|
1693
|
-
height: 8px;
|
|
1694
|
-
transform: translate(-50%, -50%);
|
|
1695
|
-
border: 2px solid #111820;
|
|
1696
|
-
border-radius: var(--df-review-radius-pill);
|
|
1697
|
-
background: var(--dfwr-scope);
|
|
1698
|
-
box-shadow:
|
|
1699
|
-
0 0 0 3px rgba(var(--dfwr-scope-rgb), 0.22),
|
|
1700
|
-
0 6px 16px rgba(0, 0, 0, 0.28);
|
|
1701
|
-
}
|
|
1702
|
-
|
|
1703
|
-
.dfwr-bound-marker.is-note-callout.is-highlighted::before {
|
|
1704
|
-
animation: dfwr-note-dot-pulse 1000ms ease-in-out infinite;
|
|
1705
|
-
}
|
|
1706
|
-
|
|
1707
|
-
.dfwr-bound-marker.is-note-callout .dfwr-bound-marker-icon {
|
|
1708
|
-
position: absolute;
|
|
1709
|
-
left: 0;
|
|
1710
|
-
top: 0;
|
|
1711
|
-
width: 31px;
|
|
1712
|
-
height: 2px;
|
|
1713
|
-
transform: rotate(-42deg);
|
|
1714
|
-
transform-origin: left center;
|
|
1715
|
-
border-radius: var(--df-review-radius-pill);
|
|
1716
|
-
background: currentColor;
|
|
1717
|
-
opacity: 1;
|
|
1718
|
-
}
|
|
1719
|
-
|
|
1720
|
-
.dfwr-bound-marker.is-note-callout .dfwr-bound-marker-icon::before,
|
|
1721
|
-
.dfwr-bound-marker.is-note-callout .dfwr-bound-marker-icon::after {
|
|
1722
|
-
display: none;
|
|
1723
|
-
}
|
|
1724
|
-
|
|
1725
|
-
.dfwr-bound-marker.is-note-callout .dfwr-bound-marker-number {
|
|
1726
|
-
position: absolute;
|
|
1727
|
-
left: 24px;
|
|
1728
|
-
top: -41px;
|
|
1729
|
-
display: inline-flex;
|
|
1730
|
-
align-items: center;
|
|
1731
|
-
justify-content: center;
|
|
1732
|
-
min-width: 28px;
|
|
1733
|
-
height: 20px;
|
|
1734
|
-
padding: 0 7px;
|
|
1735
|
-
border: 1px solid var(--dfwr-scope);
|
|
1736
|
-
border-radius: 4px;
|
|
1737
|
-
background: var(--dfwr-scope);
|
|
1738
|
-
box-shadow:
|
|
1739
|
-
0 0 0 3px rgba(var(--dfwr-scope-rgb), 0.18),
|
|
1740
|
-
0 8px 18px rgba(0, 0, 0, 0.28);
|
|
1741
|
-
color: #111820;
|
|
1742
|
-
text-align: center;
|
|
1743
|
-
line-height: 1;
|
|
1744
|
-
white-space: nowrap;
|
|
1745
|
-
}
|
|
1746
|
-
|
|
1747
|
-
.dfwr-bound-marker.is-note-callout.is-highlighted .dfwr-bound-marker-icon,
|
|
1748
|
-
.dfwr-bound-marker.is-note-callout.is-highlighted .dfwr-bound-marker-number {
|
|
1749
|
-
animation: dfwr-selected-blink 1000ms ease-in-out infinite;
|
|
1750
|
-
}
|
|
1751
|
-
|
|
1752
1839
|
.dfwr-area-preview-layer .dfwr-bound-marker {
|
|
1753
1840
|
border-color: #63d7c7;
|
|
1754
1841
|
background: var(--df-review-color-panel);
|
|
@@ -1819,14 +1906,14 @@ function createStyleElement() {
|
|
|
1819
1906
|
line-height: 1;
|
|
1820
1907
|
}
|
|
1821
1908
|
|
|
1822
|
-
.dfwr-
|
|
1909
|
+
.dfwr-dom-draft {
|
|
1823
1910
|
position: fixed;
|
|
1824
1911
|
inset: 0;
|
|
1825
1912
|
z-index: 4;
|
|
1826
1913
|
pointer-events: none;
|
|
1827
1914
|
}
|
|
1828
1915
|
|
|
1829
|
-
.dfwr-
|
|
1916
|
+
.dfwr-dom-pin {
|
|
1830
1917
|
appearance: none;
|
|
1831
1918
|
position: fixed;
|
|
1832
1919
|
z-index: 5;
|
|
@@ -1844,11 +1931,11 @@ function createStyleElement() {
|
|
|
1844
1931
|
pointer-events: auto;
|
|
1845
1932
|
}
|
|
1846
1933
|
|
|
1847
|
-
.dfwr-
|
|
1934
|
+
.dfwr-dom-pin:active {
|
|
1848
1935
|
cursor: grabbing;
|
|
1849
1936
|
}
|
|
1850
1937
|
|
|
1851
|
-
.dfwr-
|
|
1938
|
+
.dfwr-dom-popover {
|
|
1852
1939
|
position: fixed;
|
|
1853
1940
|
z-index: 4;
|
|
1854
1941
|
width: min(320px, calc(100vw - 24px));
|
|
@@ -1861,14 +1948,14 @@ function createStyleElement() {
|
|
|
1861
1948
|
box-shadow: var(--df-review-shadow-popover);
|
|
1862
1949
|
}
|
|
1863
1950
|
|
|
1864
|
-
.dfwr-
|
|
1951
|
+
.dfwr-dom-popover.is-composer,
|
|
1865
1952
|
.dfwr-area-draft.is-composer {
|
|
1866
1953
|
max-height: min(360px, calc(100vh - 32px));
|
|
1867
1954
|
overflow: auto;
|
|
1868
1955
|
border-color: rgba(99, 215, 199, 0.56);
|
|
1869
1956
|
}
|
|
1870
1957
|
|
|
1871
|
-
.dfwr-shell.is-docked-composer .dfwr-
|
|
1958
|
+
.dfwr-shell.is-docked-composer .dfwr-dom-popover.is-docked-composer,
|
|
1872
1959
|
.dfwr-shell.is-docked-composer .dfwr-area-draft.is-docked-composer {
|
|
1873
1960
|
position: relative;
|
|
1874
1961
|
left: auto;
|
|
@@ -1882,7 +1969,7 @@ function createStyleElement() {
|
|
|
1882
1969
|
min-height: 184px;
|
|
1883
1970
|
}
|
|
1884
1971
|
|
|
1885
|
-
.dfwr-
|
|
1972
|
+
.dfwr-dom-popover.is-dragging,
|
|
1886
1973
|
.dfwr-area-draft.is-dragging {
|
|
1887
1974
|
user-select: none;
|
|
1888
1975
|
}
|
|
@@ -1926,7 +2013,7 @@ function createStyleElement() {
|
|
|
1926
2013
|
box-shadow: var(--df-review-shadow-popover);
|
|
1927
2014
|
}
|
|
1928
2015
|
|
|
1929
|
-
.dfwr-
|
|
2016
|
+
.dfwr-dom-popover .dfwr-actions {
|
|
1930
2017
|
padding: 0;
|
|
1931
2018
|
}
|
|
1932
2019
|
|
|
@@ -1974,14 +2061,6 @@ function createStyleElement() {
|
|
|
1974
2061
|
height: 18px;
|
|
1975
2062
|
}
|
|
1976
2063
|
|
|
1977
|
-
.dfwr-note-actions {
|
|
1978
|
-
justify-content: flex-end;
|
|
1979
|
-
}
|
|
1980
|
-
|
|
1981
|
-
.dfwr-note-actions .dfwr-button:first-child {
|
|
1982
|
-
margin-right: auto;
|
|
1983
|
-
}
|
|
1984
|
-
|
|
1985
2064
|
.dfwr-area-draft .dfwr-actions {
|
|
1986
2065
|
padding: 0;
|
|
1987
2066
|
}
|
|
@@ -1999,6 +2078,82 @@ function createStyleElement() {
|
|
|
1999
2078
|
overflow-wrap: anywhere;
|
|
2000
2079
|
}
|
|
2001
2080
|
|
|
2081
|
+
.dfwr-attachment-queue {
|
|
2082
|
+
display: grid;
|
|
2083
|
+
gap: 8px;
|
|
2084
|
+
min-width: 0;
|
|
2085
|
+
}
|
|
2086
|
+
|
|
2087
|
+
.dfwr-attachment-label {
|
|
2088
|
+
color: var(--df-review-color-text-muted);
|
|
2089
|
+
font-size: var(--df-review-font-size-xs);
|
|
2090
|
+
line-height: 1.35;
|
|
2091
|
+
}
|
|
2092
|
+
|
|
2093
|
+
.dfwr-attachment-list {
|
|
2094
|
+
display: grid;
|
|
2095
|
+
gap: 8px;
|
|
2096
|
+
}
|
|
2097
|
+
|
|
2098
|
+
.dfwr-attachment-item {
|
|
2099
|
+
display: grid;
|
|
2100
|
+
grid-template-columns: 42px minmax(0, 1fr) auto;
|
|
2101
|
+
align-items: center;
|
|
2102
|
+
gap: 8px;
|
|
2103
|
+
min-width: 0;
|
|
2104
|
+
padding: 6px;
|
|
2105
|
+
border: 1px solid rgba(255, 255, 255, 0.12);
|
|
2106
|
+
border-radius: var(--df-review-radius-sm);
|
|
2107
|
+
background: rgba(255, 255, 255, 0.04);
|
|
2108
|
+
}
|
|
2109
|
+
|
|
2110
|
+
.dfwr-attachment-thumb {
|
|
2111
|
+
display: block;
|
|
2112
|
+
width: 42px;
|
|
2113
|
+
height: 42px;
|
|
2114
|
+
object-fit: cover;
|
|
2115
|
+
border-radius: var(--df-review-radius-xs);
|
|
2116
|
+
background: var(--df-review-color-panel-strong);
|
|
2117
|
+
}
|
|
2118
|
+
|
|
2119
|
+
.dfwr-attachment-thumb.is-file {
|
|
2120
|
+
display: inline-flex;
|
|
2121
|
+
align-items: center;
|
|
2122
|
+
justify-content: center;
|
|
2123
|
+
color: var(--df-review-color-text-muted);
|
|
2124
|
+
font-size: var(--df-review-font-size-xs);
|
|
2125
|
+
font-weight: var(--df-review-font-weight-emphasis);
|
|
2126
|
+
}
|
|
2127
|
+
|
|
2128
|
+
.dfwr-attachment-name {
|
|
2129
|
+
min-width: 0;
|
|
2130
|
+
overflow: hidden;
|
|
2131
|
+
color: var(--df-review-color-text);
|
|
2132
|
+
font-size: var(--df-review-font-size-sm);
|
|
2133
|
+
line-height: 1.35;
|
|
2134
|
+
text-overflow: ellipsis;
|
|
2135
|
+
white-space: nowrap;
|
|
2136
|
+
}
|
|
2137
|
+
|
|
2138
|
+
.dfwr-attachment-remove {
|
|
2139
|
+
appearance: none;
|
|
2140
|
+
min-height: 28px;
|
|
2141
|
+
padding: 0 8px;
|
|
2142
|
+
border: 1px solid var(--df-review-color-border-strong);
|
|
2143
|
+
border-radius: var(--df-review-radius-sm);
|
|
2144
|
+
color: var(--df-review-color-text-muted);
|
|
2145
|
+
background: var(--df-review-color-control);
|
|
2146
|
+
cursor: pointer;
|
|
2147
|
+
font: inherit;
|
|
2148
|
+
font-size: var(--df-review-font-size-xs);
|
|
2149
|
+
line-height: 1;
|
|
2150
|
+
}
|
|
2151
|
+
|
|
2152
|
+
.dfwr-attachment-remove:hover {
|
|
2153
|
+
color: var(--df-review-color-text);
|
|
2154
|
+
background: var(--df-review-color-control-hover);
|
|
2155
|
+
}
|
|
2156
|
+
|
|
2002
2157
|
.dfwr-input,
|
|
2003
2158
|
.dfwr-select,
|
|
2004
2159
|
.dfwr-textarea {
|
|
@@ -2301,18 +2456,6 @@ function createStyleElement() {
|
|
|
2301
2456
|
}
|
|
2302
2457
|
}
|
|
2303
2458
|
|
|
2304
|
-
@keyframes dfwr-note-dot-pulse {
|
|
2305
|
-
0% {
|
|
2306
|
-
transform: translate(-50%, -50%) scale(0.88);
|
|
2307
|
-
}
|
|
2308
|
-
45% {
|
|
2309
|
-
transform: translate(-50%, -50%) scale(1.3);
|
|
2310
|
-
}
|
|
2311
|
-
100% {
|
|
2312
|
-
transform: translate(-50%, -50%) scale(1);
|
|
2313
|
-
}
|
|
2314
|
-
}
|
|
2315
|
-
|
|
2316
2459
|
@keyframes dfwr-selected-blink {
|
|
2317
2460
|
0%,
|
|
2318
2461
|
100% {
|
|
@@ -2361,1071 +2504,848 @@ function createStyleElement() {
|
|
|
2361
2504
|
return style;
|
|
2362
2505
|
}
|
|
2363
2506
|
|
|
2364
|
-
// src/core/
|
|
2365
|
-
function
|
|
2366
|
-
|
|
2367
|
-
|
|
2368
|
-
|
|
2369
|
-
|
|
2370
|
-
|
|
2371
|
-
|
|
2372
|
-
|
|
2373
|
-
|
|
2374
|
-
}
|
|
2375
|
-
|
|
2376
|
-
|
|
2377
|
-
|
|
2378
|
-
|
|
2379
|
-
|
|
2380
|
-
|
|
2381
|
-
|
|
2382
|
-
|
|
2383
|
-
|
|
2384
|
-
}
|
|
2385
|
-
if (selection) {
|
|
2386
|
-
parts.push(`rect ${formatSelection(selection.viewport)}`);
|
|
2387
|
-
}
|
|
2388
|
-
if (item.anchor) {
|
|
2389
|
-
parts.push(formatAnchorMeta(item.anchor));
|
|
2390
|
-
}
|
|
2391
|
-
return parts.join(" / ");
|
|
2507
|
+
// src/core/draft.attachments.ts
|
|
2508
|
+
function attachDraftImagePasteQueue(textarea, options) {
|
|
2509
|
+
textarea.addEventListener("paste", (event) => {
|
|
2510
|
+
const imageFiles = getClipboardImageFiles(event.clipboardData);
|
|
2511
|
+
if (imageFiles.length === 0) return;
|
|
2512
|
+
event.preventDefault();
|
|
2513
|
+
const text = event.clipboardData?.getData("text/plain");
|
|
2514
|
+
if (text) {
|
|
2515
|
+
insertTextAtTextareaSelection(textarea, text);
|
|
2516
|
+
options.onCommentChange(textarea.value);
|
|
2517
|
+
}
|
|
2518
|
+
const attachments = imageFiles.map(
|
|
2519
|
+
(file, index) => createDraftImageAttachment(file, index)
|
|
2520
|
+
);
|
|
2521
|
+
options.onAttachmentsChange([
|
|
2522
|
+
...options.getAttachments() ?? [],
|
|
2523
|
+
...attachments
|
|
2524
|
+
]);
|
|
2525
|
+
options.onPasteComplete();
|
|
2526
|
+
});
|
|
2392
2527
|
}
|
|
2393
|
-
function
|
|
2394
|
-
|
|
2395
|
-
|
|
2396
|
-
|
|
2397
|
-
|
|
2398
|
-
|
|
2399
|
-
|
|
2400
|
-
|
|
2528
|
+
function createDraftAttachmentQueue(ownerDocument, attachments, onRemove) {
|
|
2529
|
+
if (!attachments?.length) return void 0;
|
|
2530
|
+
const queue = ownerDocument.createElement("div");
|
|
2531
|
+
queue.className = "dfwr-attachment-queue";
|
|
2532
|
+
const label = ownerDocument.createElement("div");
|
|
2533
|
+
label.className = "dfwr-attachment-label";
|
|
2534
|
+
label.textContent = `Attachments (${attachments.length})`;
|
|
2535
|
+
const list = ownerDocument.createElement("div");
|
|
2536
|
+
list.className = "dfwr-attachment-list";
|
|
2537
|
+
attachments.forEach((attachment) => {
|
|
2538
|
+
const item = ownerDocument.createElement("div");
|
|
2539
|
+
item.className = "dfwr-attachment-item";
|
|
2540
|
+
const preview = createDraftAttachmentPreview(ownerDocument, attachment);
|
|
2541
|
+
const name = ownerDocument.createElement("div");
|
|
2542
|
+
name.className = "dfwr-attachment-name";
|
|
2543
|
+
name.textContent = attachment.name;
|
|
2544
|
+
name.title = attachment.name;
|
|
2545
|
+
const remove = ownerDocument.createElement("button");
|
|
2546
|
+
remove.className = "dfwr-attachment-remove";
|
|
2547
|
+
remove.type = "button";
|
|
2548
|
+
remove.textContent = "Remove";
|
|
2549
|
+
remove.setAttribute("aria-label", `Remove ${attachment.name}`);
|
|
2550
|
+
remove.addEventListener("click", (event) => {
|
|
2551
|
+
event.preventDefault();
|
|
2552
|
+
event.stopPropagation();
|
|
2553
|
+
onRemove(attachment.id);
|
|
2554
|
+
});
|
|
2555
|
+
item.append(preview, name, remove);
|
|
2556
|
+
list.append(item);
|
|
2401
2557
|
});
|
|
2558
|
+
queue.append(label, list);
|
|
2559
|
+
return queue;
|
|
2402
2560
|
}
|
|
2403
|
-
function
|
|
2404
|
-
|
|
2561
|
+
function removeDraftAttachment(attachments, attachmentId) {
|
|
2562
|
+
if (!attachments?.length) return [];
|
|
2563
|
+
const removed = attachments.find((attachment) => attachment.id === attachmentId);
|
|
2564
|
+
if (removed?.previewUrl) {
|
|
2565
|
+
URL.revokeObjectURL(removed.previewUrl);
|
|
2566
|
+
}
|
|
2567
|
+
return attachments.filter((attachment) => attachment.id !== attachmentId);
|
|
2405
2568
|
}
|
|
2406
|
-
function
|
|
2407
|
-
|
|
2569
|
+
function getClipboardImageFiles(data) {
|
|
2570
|
+
if (!data) return [];
|
|
2571
|
+
const itemFiles = Array.from(data.items).filter((item) => item.kind === "file" && item.type.startsWith("image/")).map((item) => item.getAsFile()).filter((file) => Boolean(file));
|
|
2572
|
+
if (itemFiles.length > 0) return itemFiles;
|
|
2573
|
+
return Array.from(data.files).filter((file) => file.type.startsWith("image/"));
|
|
2408
2574
|
}
|
|
2409
|
-
function
|
|
2410
|
-
|
|
2411
|
-
|
|
2412
|
-
|
|
2413
|
-
|
|
2414
|
-
|
|
2415
|
-
|
|
2575
|
+
function createDraftImageAttachment(file, index) {
|
|
2576
|
+
const mime = file.type || "image/png";
|
|
2577
|
+
const name = file.name || `pasted-image-${Date.now()}-${index + 1}${getImageExtension(mime)}`;
|
|
2578
|
+
return {
|
|
2579
|
+
id: createDraftAttachmentId(),
|
|
2580
|
+
file,
|
|
2581
|
+
name,
|
|
2582
|
+
mime,
|
|
2583
|
+
size: file.size,
|
|
2584
|
+
kind: "image",
|
|
2585
|
+
previewUrl: URL.createObjectURL(file),
|
|
2586
|
+
metadata: { source: "paste" }
|
|
2587
|
+
};
|
|
2416
2588
|
}
|
|
2417
|
-
function
|
|
2418
|
-
|
|
2419
|
-
|
|
2420
|
-
|
|
2421
|
-
|
|
2422
|
-
|
|
2423
|
-
if (
|
|
2424
|
-
|
|
2425
|
-
|
|
2426
|
-
|
|
2589
|
+
function createDraftAttachmentId() {
|
|
2590
|
+
return window.crypto?.randomUUID?.() ?? `draft-attachment-${Date.now()}-${Math.random().toString(36).slice(2)}`;
|
|
2591
|
+
}
|
|
2592
|
+
function getImageExtension(mime) {
|
|
2593
|
+
if (mime === "image/jpeg") return ".jpg";
|
|
2594
|
+
if (mime === "image/gif") return ".gif";
|
|
2595
|
+
if (mime === "image/webp") return ".webp";
|
|
2596
|
+
if (mime === "image/svg+xml") return ".svg";
|
|
2597
|
+
return ".png";
|
|
2598
|
+
}
|
|
2599
|
+
function insertTextAtTextareaSelection(textarea, text) {
|
|
2600
|
+
const start = textarea.selectionStart ?? textarea.value.length;
|
|
2601
|
+
const end = textarea.selectionEnd ?? start;
|
|
2602
|
+
textarea.value = [
|
|
2603
|
+
textarea.value.slice(0, start),
|
|
2604
|
+
text,
|
|
2605
|
+
textarea.value.slice(end)
|
|
2606
|
+
].join("");
|
|
2607
|
+
const nextSelection = start + text.length;
|
|
2608
|
+
textarea.setSelectionRange(nextSelection, nextSelection);
|
|
2609
|
+
}
|
|
2610
|
+
function createDraftAttachmentPreview(ownerDocument, attachment) {
|
|
2611
|
+
if (attachment.previewUrl && attachment.mime.startsWith("image/")) {
|
|
2612
|
+
const image = ownerDocument.createElement("img");
|
|
2613
|
+
image.className = "dfwr-attachment-thumb";
|
|
2614
|
+
image.src = attachment.previewUrl;
|
|
2615
|
+
image.alt = "";
|
|
2616
|
+
image.decoding = "async";
|
|
2617
|
+
return image;
|
|
2618
|
+
}
|
|
2619
|
+
const fallback = ownerDocument.createElement("div");
|
|
2620
|
+
fallback.className = "dfwr-attachment-thumb is-file";
|
|
2621
|
+
fallback.textContent = "IMG";
|
|
2622
|
+
return fallback;
|
|
2427
2623
|
}
|
|
2428
2624
|
|
|
2429
|
-
// src/core/
|
|
2430
|
-
var
|
|
2431
|
-
|
|
2432
|
-
|
|
2433
|
-
|
|
2434
|
-
|
|
2435
|
-
|
|
2436
|
-
|
|
2437
|
-
|
|
2438
|
-
|
|
2439
|
-
|
|
2440
|
-
|
|
2441
|
-
|
|
2442
|
-
|
|
2443
|
-
|
|
2444
|
-
|
|
2445
|
-
|
|
2446
|
-
|
|
2447
|
-
|
|
2448
|
-
|
|
2449
|
-
|
|
2450
|
-
|
|
2451
|
-
|
|
2452
|
-
|
|
2453
|
-
|
|
2454
|
-
|
|
2455
|
-
|
|
2456
|
-
|
|
2457
|
-
|
|
2458
|
-
|
|
2459
|
-
|
|
2460
|
-
|
|
2461
|
-
|
|
2462
|
-
|
|
2463
|
-
|
|
2464
|
-
|
|
2465
|
-
this.createBody(),
|
|
2466
|
-
this.createList()
|
|
2467
|
-
);
|
|
2468
|
-
shell.append(panel);
|
|
2469
|
-
}
|
|
2470
|
-
shell.append(this.createMarkerLayer());
|
|
2471
|
-
if (state.isOpen && hasDismissableDraft && !shouldDockComposer) {
|
|
2472
|
-
shell.append(this.createDraftCancelLayer());
|
|
2473
|
-
}
|
|
2474
|
-
if (state.isOpen && (state.mode === "note" || state.mode === "element")) {
|
|
2475
|
-
if (state.noteDraft) {
|
|
2476
|
-
const noteDraft = this.createNotePopover(state.noteDraft, {
|
|
2477
|
-
dockComposer: shouldDockComposer
|
|
2478
|
-
});
|
|
2479
|
-
shell.append(noteDraft.layer);
|
|
2480
|
-
dockedComposer = noteDraft.composer;
|
|
2481
|
-
} else {
|
|
2482
|
-
shell.append(
|
|
2483
|
-
state.mode === "element" ? this.createElementLayer() : this.createNoteLayer()
|
|
2484
|
-
);
|
|
2485
|
-
}
|
|
2486
|
-
}
|
|
2487
|
-
if (state.isOpen && state.mode === "area" && !state.areaDraft && !state.isSelectingArea) {
|
|
2488
|
-
shell.append(this.createAreaLayer());
|
|
2489
|
-
}
|
|
2490
|
-
if (state.isOpen && state.mode === "area" && state.areaDraft && this.config.options.ui?.panel === false) {
|
|
2491
|
-
if (state.areaDraft.selection) {
|
|
2492
|
-
shell.append(this.createAreaDraftOverlay(state.areaDraft));
|
|
2493
|
-
}
|
|
2494
|
-
const areaComposer = this.createAreaDraftPopover(state.areaDraft, {
|
|
2495
|
-
dockComposer: shouldDockComposer
|
|
2496
|
-
});
|
|
2497
|
-
if (shouldDockComposer) {
|
|
2498
|
-
dockedComposer = areaComposer;
|
|
2499
|
-
} else {
|
|
2500
|
-
shell.append(areaComposer);
|
|
2501
|
-
}
|
|
2502
|
-
}
|
|
2503
|
-
shadow.append(shell);
|
|
2504
|
-
this.renderShellComposer(dockedComposer);
|
|
2505
|
-
}
|
|
2506
|
-
get state() {
|
|
2507
|
-
return this.config.getState();
|
|
2508
|
-
}
|
|
2509
|
-
getShellComposerHost() {
|
|
2510
|
-
const environment = this.config.getEnvironment();
|
|
2511
|
-
if (this.config.options.ui?.panel !== false) return void 0;
|
|
2512
|
-
return environment?.composerHost ?? void 0;
|
|
2513
|
-
}
|
|
2514
|
-
renderShellComposer(composer) {
|
|
2515
|
-
const host = composer ? this.getShellComposerHost() : void 0;
|
|
2516
|
-
if (!host || !composer) {
|
|
2517
|
-
this.clearShellComposer();
|
|
2518
|
-
return;
|
|
2519
|
-
}
|
|
2520
|
-
if (this.shellComposerHost && this.shellComposerHost !== host) {
|
|
2521
|
-
this.clearShellComposer();
|
|
2522
|
-
}
|
|
2523
|
-
this.shellComposerHost = host;
|
|
2524
|
-
host.dataset.hasDraftComposer = "true";
|
|
2525
|
-
if (host.parentElement) {
|
|
2526
|
-
host.parentElement.dataset.hasDraftComposer = "true";
|
|
2527
|
-
}
|
|
2528
|
-
const shell = document.createElement("div");
|
|
2529
|
-
shell.className = "dfwr-shell is-open is-shell-draft is-docked-composer";
|
|
2530
|
-
shell.append(composer);
|
|
2531
|
-
host.replaceChildren(createStyleElement(), shell);
|
|
2532
|
-
}
|
|
2533
|
-
clearShellComposer() {
|
|
2534
|
-
const host = this.shellComposerHost;
|
|
2535
|
-
host?.replaceChildren();
|
|
2536
|
-
if (host) {
|
|
2537
|
-
delete host.dataset.hasDraftComposer;
|
|
2538
|
-
delete host.parentElement?.dataset.hasDraftComposer;
|
|
2539
|
-
}
|
|
2540
|
-
this.shellComposerHost = void 0;
|
|
2541
|
-
}
|
|
2542
|
-
createDraftCancelLayer() {
|
|
2543
|
-
const layer = document.createElement("div");
|
|
2544
|
-
layer.className = "dfwr-draft-cancel-layer";
|
|
2545
|
-
layer.setAttribute("aria-hidden", "true");
|
|
2546
|
-
layer.addEventListener("pointerdown", (event) => {
|
|
2547
|
-
if (event.button !== 0) return;
|
|
2548
|
-
this.cancelDraft(event);
|
|
2549
|
-
});
|
|
2550
|
-
return layer;
|
|
2551
|
-
}
|
|
2552
|
-
cancelDraft(event) {
|
|
2553
|
-
event?.preventDefault();
|
|
2554
|
-
event?.stopPropagation();
|
|
2555
|
-
event?.stopImmediatePropagation();
|
|
2556
|
-
this.config.actions.setModeState("idle");
|
|
2557
|
-
this.config.actions.clearDrafts();
|
|
2558
|
-
this.config.actions.setSelectingArea(false);
|
|
2559
|
-
this.config.actions.render();
|
|
2560
|
-
}
|
|
2561
|
-
// Draft adjustment geometry lives in draft.metrics.ts; these thin wrappers
|
|
2562
|
-
// supply the configured viewport presets so call sites stay unchanged.
|
|
2563
|
-
get viewportPresets() {
|
|
2564
|
-
return this.config.options.viewports?.presets;
|
|
2565
|
-
}
|
|
2566
|
-
getDraftAdjustmentMetrics(draft) {
|
|
2567
|
-
return getDraftAdjustmentMetrics(draft, this.viewportPresets);
|
|
2568
|
-
}
|
|
2569
|
-
hasDraftAdjustment(draft) {
|
|
2570
|
-
return hasDraftAdjustment(draft, this.viewportPresets);
|
|
2571
|
-
}
|
|
2572
|
-
getAdjustedDraftPoint(point, draft) {
|
|
2573
|
-
return getAdjustedDraftPoint(point, draft, this.viewportPresets);
|
|
2574
|
-
}
|
|
2575
|
-
getAdjustedDraftSelection(selection, draft) {
|
|
2576
|
-
return getAdjustedDraftSelection(
|
|
2577
|
-
selection,
|
|
2578
|
-
draft,
|
|
2579
|
-
this.viewportPresets
|
|
2580
|
-
);
|
|
2581
|
-
}
|
|
2582
|
-
getDraftViewportScale(viewport) {
|
|
2583
|
-
return getDraftViewportScale(viewport, this.viewportPresets);
|
|
2584
|
-
}
|
|
2585
|
-
getDraftComposerWidth(environment) {
|
|
2586
|
-
const bounds = environment.overlayRect;
|
|
2587
|
-
const margin = 12;
|
|
2588
|
-
return Math.min(360, Math.max(240, bounds.width - margin * 2));
|
|
2589
|
-
}
|
|
2590
|
-
getClampedComposerPosition(position, environment, size, bounds = environment.overlayRect) {
|
|
2591
|
-
const margin = 12;
|
|
2592
|
-
const width = size?.width ?? this.getDraftComposerWidth(environment);
|
|
2593
|
-
const height = size?.height ?? 236;
|
|
2594
|
-
return {
|
|
2595
|
-
x: clamp(
|
|
2596
|
-
position.x,
|
|
2597
|
-
bounds.left + margin,
|
|
2598
|
-
bounds.left + bounds.width - width - margin
|
|
2599
|
-
),
|
|
2600
|
-
y: clamp(
|
|
2601
|
-
position.y,
|
|
2602
|
-
bounds.top + margin,
|
|
2603
|
-
bounds.top + bounds.height - height - margin
|
|
2604
|
-
)
|
|
2605
|
-
};
|
|
2606
|
-
}
|
|
2607
|
-
getHostComposerBounds() {
|
|
2608
|
-
const root = document.documentElement;
|
|
2609
|
-
return {
|
|
2610
|
-
left: 0,
|
|
2611
|
-
top: 0,
|
|
2612
|
-
width: root.clientWidth || window.innerWidth,
|
|
2613
|
-
height: root.clientHeight || window.innerHeight
|
|
2614
|
-
};
|
|
2615
|
-
}
|
|
2616
|
-
getInitialDraftComposerPosition(selection, environment, size) {
|
|
2617
|
-
const bounds = this.getHostComposerBounds();
|
|
2618
|
-
const margin = 12;
|
|
2619
|
-
const gap = 20;
|
|
2620
|
-
if (!selection) {
|
|
2621
|
-
return this.getClampedComposerPosition(
|
|
2622
|
-
{
|
|
2623
|
-
x: environment.overlayRect.left + margin,
|
|
2624
|
-
y: environment.overlayRect.top + margin
|
|
2625
|
-
},
|
|
2626
|
-
environment,
|
|
2627
|
-
size,
|
|
2628
|
-
bounds
|
|
2629
|
-
);
|
|
2630
|
-
}
|
|
2631
|
-
const preferredX = selection.left + selection.width + gap;
|
|
2632
|
-
const maxX = bounds.left + bounds.width - size.width - margin;
|
|
2633
|
-
const x = preferredX <= maxX ? preferredX : selection.left - size.width - gap;
|
|
2634
|
-
return this.getClampedComposerPosition(
|
|
2625
|
+
// src/core/view/composer.position.ts
|
|
2626
|
+
var COMPOSER_MARGIN = 12;
|
|
2627
|
+
function getDraftComposerWidth(environment) {
|
|
2628
|
+
const bounds = environment.overlayRect;
|
|
2629
|
+
return Math.min(360, Math.max(240, bounds.width - COMPOSER_MARGIN * 2));
|
|
2630
|
+
}
|
|
2631
|
+
function getHostComposerBounds() {
|
|
2632
|
+
const root = document.documentElement;
|
|
2633
|
+
return {
|
|
2634
|
+
left: 0,
|
|
2635
|
+
top: 0,
|
|
2636
|
+
width: root.clientWidth || window.innerWidth,
|
|
2637
|
+
height: root.clientHeight || window.innerHeight
|
|
2638
|
+
};
|
|
2639
|
+
}
|
|
2640
|
+
function getClampedComposerPosition(position, environment, size, bounds = environment.overlayRect) {
|
|
2641
|
+
const width = size?.width ?? getDraftComposerWidth(environment);
|
|
2642
|
+
const height = size?.height ?? 236;
|
|
2643
|
+
return {
|
|
2644
|
+
x: clamp(
|
|
2645
|
+
position.x,
|
|
2646
|
+
bounds.left + COMPOSER_MARGIN,
|
|
2647
|
+
bounds.left + bounds.width - width - COMPOSER_MARGIN
|
|
2648
|
+
),
|
|
2649
|
+
y: clamp(
|
|
2650
|
+
position.y,
|
|
2651
|
+
bounds.top + COMPOSER_MARGIN,
|
|
2652
|
+
bounds.top + bounds.height - height - COMPOSER_MARGIN
|
|
2653
|
+
)
|
|
2654
|
+
};
|
|
2655
|
+
}
|
|
2656
|
+
function getInitialDraftComposerPosition(selection, environment, size) {
|
|
2657
|
+
const bounds = getHostComposerBounds();
|
|
2658
|
+
const gap = 20;
|
|
2659
|
+
if (!selection) {
|
|
2660
|
+
return getClampedComposerPosition(
|
|
2635
2661
|
{
|
|
2636
|
-
x,
|
|
2637
|
-
y:
|
|
2662
|
+
x: environment.overlayRect.left + COMPOSER_MARGIN,
|
|
2663
|
+
y: environment.overlayRect.top + COMPOSER_MARGIN
|
|
2638
2664
|
},
|
|
2639
2665
|
environment,
|
|
2640
2666
|
size,
|
|
2641
2667
|
bounds
|
|
2642
2668
|
);
|
|
2643
2669
|
}
|
|
2644
|
-
|
|
2645
|
-
|
|
2670
|
+
const preferredX = selection.left + selection.width + gap;
|
|
2671
|
+
const maxX = bounds.left + bounds.width - size.width - COMPOSER_MARGIN;
|
|
2672
|
+
const x = preferredX <= maxX ? preferredX : selection.left - size.width - gap;
|
|
2673
|
+
return getClampedComposerPosition(
|
|
2674
|
+
{
|
|
2675
|
+
x,
|
|
2676
|
+
y: selection.top
|
|
2677
|
+
},
|
|
2646
2678
|
environment,
|
|
2647
|
-
|
|
2648
|
-
|
|
2649
|
-
|
|
2650
|
-
|
|
2651
|
-
|
|
2652
|
-
|
|
2653
|
-
|
|
2654
|
-
|
|
2655
|
-
|
|
2656
|
-
|
|
2657
|
-
|
|
2658
|
-
|
|
2659
|
-
|
|
2660
|
-
|
|
2661
|
-
|
|
2662
|
-
height: estimatedHeight
|
|
2663
|
-
|
|
2664
|
-
return { width, left: position.x, top: position.y };
|
|
2665
|
-
}
|
|
2666
|
-
getSelectionMqMetrics(selection, viewport) {
|
|
2667
|
-
const { scale } = this.getDraftViewportScale(viewport);
|
|
2668
|
-
const ratio = scale > 0 ? 1 / scale : 1;
|
|
2669
|
-
return {
|
|
2670
|
-
x: selection.left * ratio,
|
|
2671
|
-
y: selection.top * ratio,
|
|
2672
|
-
width: selection.width * ratio,
|
|
2673
|
-
height: selection.height * ratio
|
|
2674
|
-
};
|
|
2675
|
-
}
|
|
2676
|
-
formatSignedPx(value) {
|
|
2677
|
-
if (value === 0) return "+0px";
|
|
2678
|
-
return `${value > 0 ? "+" : ""}${value}px`;
|
|
2679
|
-
}
|
|
2680
|
-
formatRoundedPx(value) {
|
|
2681
|
-
return `${Math.round(value)}px`;
|
|
2682
|
-
}
|
|
2683
|
-
getAdjustmentLabel() {
|
|
2684
|
-
return this.config.options.adjustmentLabel?.trim() || DEFAULT_ADJUSTMENT_LABEL;
|
|
2685
|
-
}
|
|
2686
|
-
getSelectionMetricLines(selection, viewport) {
|
|
2687
|
-
if (!selection) return ["area", "x none / y none", "w none / h none"];
|
|
2688
|
-
const metrics = this.getSelectionMqMetrics(selection, viewport);
|
|
2689
|
-
return [
|
|
2690
|
-
"area",
|
|
2691
|
-
`x ${this.formatRoundedPx(metrics.x)} / y ${this.formatRoundedPx(
|
|
2692
|
-
metrics.y
|
|
2693
|
-
)}`,
|
|
2694
|
-
`w ${this.formatRoundedPx(metrics.width)} / h ${this.formatRoundedPx(
|
|
2695
|
-
metrics.height
|
|
2696
|
-
)}`
|
|
2697
|
-
];
|
|
2698
|
-
}
|
|
2699
|
-
getAreaDraftMetricSelection(draft) {
|
|
2700
|
-
if (!draft.selection) return void 0;
|
|
2701
|
-
return toViewportSelection(draft.selection.viewport);
|
|
2702
|
-
}
|
|
2703
|
-
getDraftAdjustmentMetricLines(draft) {
|
|
2704
|
-
const metrics = this.getDraftAdjustmentMetrics(draft);
|
|
2705
|
-
return [
|
|
2706
|
-
`x ${this.formatSignedPx(metrics.x)} / y ${this.formatSignedPx(
|
|
2707
|
-
metrics.y
|
|
2708
|
-
)}`,
|
|
2709
|
-
`scale ${this.formatSignedPx(metrics.scale)}`
|
|
2710
|
-
];
|
|
2711
|
-
}
|
|
2712
|
-
withDraftAdjustmentComment(comment, draft) {
|
|
2713
|
-
if (!this.hasDraftAdjustment(draft)) return comment;
|
|
2714
|
-
const trimmedComment = comment.trim();
|
|
2715
|
-
const metrics = this.getDraftAdjustmentMetrics(draft);
|
|
2716
|
-
const adjustment = [
|
|
2717
|
-
`${this.getAdjustmentLabel()}: x ${this.formatSignedPx(
|
|
2718
|
-
metrics.x
|
|
2719
|
-
)}, y ${this.formatSignedPx(metrics.y)}, scale ${this.formatSignedPx(
|
|
2720
|
-
metrics.scale
|
|
2721
|
-
)}`,
|
|
2722
|
-
`(${metrics.presetLabel} viewport, ${Math.round(
|
|
2723
|
-
metrics.viewportWidth
|
|
2724
|
-
)}/design ${Math.round(metrics.designWidth)})`
|
|
2725
|
-
].join(" ");
|
|
2726
|
-
return trimmedComment ? `${trimmedComment}
|
|
2727
|
-
${adjustment}` : adjustment;
|
|
2728
|
-
}
|
|
2729
|
-
getAssigneeOption(assigneeId) {
|
|
2730
|
-
if (!assigneeId) return void 0;
|
|
2731
|
-
return this.config.options.assigneeOptions?.find(
|
|
2732
|
-
(option) => option.value === assigneeId
|
|
2679
|
+
size,
|
|
2680
|
+
bounds
|
|
2681
|
+
);
|
|
2682
|
+
}
|
|
2683
|
+
function getDraftComposerPosition({
|
|
2684
|
+
selection,
|
|
2685
|
+
environment,
|
|
2686
|
+
composerPosition,
|
|
2687
|
+
estimatedHeight
|
|
2688
|
+
}) {
|
|
2689
|
+
const width = getDraftComposerWidth(environment);
|
|
2690
|
+
if (composerPosition) {
|
|
2691
|
+
const clamped = getClampedComposerPosition(
|
|
2692
|
+
composerPosition,
|
|
2693
|
+
environment,
|
|
2694
|
+
{ width, height: estimatedHeight },
|
|
2695
|
+
getHostComposerBounds()
|
|
2733
2696
|
);
|
|
2697
|
+
return { width, left: clamped.x, top: clamped.y };
|
|
2734
2698
|
}
|
|
2735
|
-
|
|
2736
|
-
|
|
2737
|
-
|
|
2738
|
-
|
|
2739
|
-
|
|
2740
|
-
|
|
2741
|
-
|
|
2742
|
-
|
|
2743
|
-
|
|
2744
|
-
|
|
2745
|
-
|
|
2746
|
-
|
|
2747
|
-
|
|
2748
|
-
|
|
2749
|
-
|
|
2750
|
-
|
|
2751
|
-
const
|
|
2752
|
-
if (
|
|
2753
|
-
const
|
|
2754
|
-
|
|
2755
|
-
|
|
2756
|
-
|
|
2757
|
-
|
|
2758
|
-
|
|
2759
|
-
|
|
2760
|
-
|
|
2761
|
-
|
|
2762
|
-
|
|
2763
|
-
|
|
2764
|
-
|
|
2765
|
-
|
|
2766
|
-
}
|
|
2767
|
-
|
|
2768
|
-
|
|
2769
|
-
|
|
2770
|
-
|
|
2771
|
-
|
|
2772
|
-
|
|
2773
|
-
|
|
2774
|
-
|
|
2775
|
-
|
|
2776
|
-
|
|
2777
|
-
|
|
2778
|
-
|
|
2779
|
-
|
|
2780
|
-
|
|
2781
|
-
|
|
2782
|
-
|
|
2699
|
+
const position = getInitialDraftComposerPosition(selection, environment, {
|
|
2700
|
+
width,
|
|
2701
|
+
height: estimatedHeight
|
|
2702
|
+
});
|
|
2703
|
+
return { width, left: position.x, top: position.y };
|
|
2704
|
+
}
|
|
2705
|
+
function attachDraftComposerDrag({
|
|
2706
|
+
getEnvironment,
|
|
2707
|
+
popover,
|
|
2708
|
+
handle,
|
|
2709
|
+
onMove
|
|
2710
|
+
}) {
|
|
2711
|
+
let isDragging = false;
|
|
2712
|
+
let offsetX = 0;
|
|
2713
|
+
let offsetY = 0;
|
|
2714
|
+
const movePopover = (event) => {
|
|
2715
|
+
const environment = getEnvironment();
|
|
2716
|
+
if (!environment) return;
|
|
2717
|
+
const position = getClampedComposerPosition(
|
|
2718
|
+
{
|
|
2719
|
+
x: event.clientX - offsetX,
|
|
2720
|
+
y: event.clientY - offsetY
|
|
2721
|
+
},
|
|
2722
|
+
environment,
|
|
2723
|
+
{
|
|
2724
|
+
width: popover.offsetWidth,
|
|
2725
|
+
height: popover.offsetHeight
|
|
2726
|
+
},
|
|
2727
|
+
getHostComposerBounds()
|
|
2728
|
+
);
|
|
2729
|
+
popover.style.left = `${position.x}px`;
|
|
2730
|
+
popover.style.top = `${position.y}px`;
|
|
2731
|
+
onMove(position);
|
|
2732
|
+
};
|
|
2733
|
+
handle.addEventListener("pointerdown", (event) => {
|
|
2734
|
+
if (event.button !== 0) return;
|
|
2735
|
+
const rect = popover.getBoundingClientRect();
|
|
2736
|
+
offsetX = event.clientX - rect.left;
|
|
2737
|
+
offsetY = event.clientY - rect.top;
|
|
2738
|
+
isDragging = true;
|
|
2739
|
+
event.preventDefault();
|
|
2740
|
+
event.stopPropagation();
|
|
2741
|
+
handle.setPointerCapture(event.pointerId);
|
|
2742
|
+
popover.classList.add("is-dragging");
|
|
2743
|
+
});
|
|
2744
|
+
handle.addEventListener("pointermove", (event) => {
|
|
2745
|
+
if (!isDragging || !handle.hasPointerCapture(event.pointerId)) return;
|
|
2746
|
+
event.preventDefault();
|
|
2747
|
+
movePopover(event);
|
|
2748
|
+
});
|
|
2749
|
+
const stopDrag = (event) => {
|
|
2750
|
+
if (!isDragging || !handle.hasPointerCapture(event.pointerId)) return;
|
|
2751
|
+
event.preventDefault();
|
|
2752
|
+
event.stopPropagation();
|
|
2753
|
+
isDragging = false;
|
|
2754
|
+
handle.releasePointerCapture(event.pointerId);
|
|
2755
|
+
popover.classList.remove("is-dragging");
|
|
2756
|
+
movePopover(event);
|
|
2757
|
+
};
|
|
2758
|
+
handle.addEventListener("pointerup", stopDrag);
|
|
2759
|
+
handle.addEventListener("pointercancel", stopDrag);
|
|
2760
|
+
}
|
|
2761
|
+
|
|
2762
|
+
// src/core/view/icons.ts
|
|
2763
|
+
function createIcon(paths) {
|
|
2764
|
+
const svg = document.createElementNS("http://www.w3.org/2000/svg", "svg");
|
|
2765
|
+
svg.setAttribute("aria-hidden", "true");
|
|
2766
|
+
svg.setAttribute("viewBox", "0 0 24 24");
|
|
2767
|
+
svg.setAttribute("fill", "none");
|
|
2768
|
+
svg.setAttribute("stroke", "currentColor");
|
|
2769
|
+
svg.setAttribute("stroke-width", "2.4");
|
|
2770
|
+
svg.setAttribute("stroke-linecap", "round");
|
|
2771
|
+
svg.setAttribute("stroke-linejoin", "round");
|
|
2772
|
+
paths.forEach((d) => {
|
|
2773
|
+
const path = document.createElementNS("http://www.w3.org/2000/svg", "path");
|
|
2774
|
+
path.setAttribute("d", d);
|
|
2775
|
+
svg.append(path);
|
|
2776
|
+
});
|
|
2777
|
+
return svg;
|
|
2778
|
+
}
|
|
2779
|
+
function setAdjustmentToggleIcon(button, isActive) {
|
|
2780
|
+
const paths = isActive ? ["M20 6 9 17l-5-5"] : [
|
|
2781
|
+
"M12 2v20",
|
|
2782
|
+
"M2 12h20",
|
|
2783
|
+
"m9 5 3-3 3 3",
|
|
2784
|
+
"m9 19 3 3 3-3",
|
|
2785
|
+
"m5 9-3 3 3 3",
|
|
2786
|
+
"m19 9 3 3-3 3"
|
|
2787
|
+
];
|
|
2788
|
+
button.replaceChildren(createIcon(paths));
|
|
2789
|
+
}
|
|
2790
|
+
function createSpinner(className) {
|
|
2791
|
+
const spinner = document.createElement("span");
|
|
2792
|
+
spinner.className = className;
|
|
2793
|
+
spinner.setAttribute("aria-hidden", "true");
|
|
2794
|
+
return spinner;
|
|
2795
|
+
}
|
|
2796
|
+
|
|
2797
|
+
// src/core/view/draft.capture.ts
|
|
2798
|
+
function canCaptureViewport(config) {
|
|
2799
|
+
return Boolean(config.getEnvironment()?.captureViewport);
|
|
2800
|
+
}
|
|
2801
|
+
function getCaptureAreaDraft(draft) {
|
|
2802
|
+
return {
|
|
2803
|
+
viewport: draft.viewport,
|
|
2804
|
+
marker: draft.marker,
|
|
2805
|
+
selection: draft.selection
|
|
2806
|
+
};
|
|
2807
|
+
}
|
|
2808
|
+
function getCaptureDomDraft(config, draft, isElementDraft) {
|
|
2809
|
+
if (!isElementDraft) {
|
|
2783
2810
|
return {
|
|
2784
|
-
|
|
2785
|
-
|
|
2786
|
-
|
|
2787
|
-
assigneeName: this.getAssigneeName(assigneeId)
|
|
2811
|
+
viewport: draft.viewport,
|
|
2812
|
+
marker: draft.marker,
|
|
2813
|
+
selection: draft.selection
|
|
2788
2814
|
};
|
|
2789
2815
|
}
|
|
2790
|
-
|
|
2791
|
-
|
|
2792
|
-
|
|
2793
|
-
|
|
2794
|
-
|
|
2795
|
-
|
|
2796
|
-
|
|
2797
|
-
|
|
2798
|
-
|
|
2799
|
-
|
|
2800
|
-
|
|
2801
|
-
|
|
2802
|
-
|
|
2803
|
-
|
|
2804
|
-
|
|
2805
|
-
|
|
2806
|
-
|
|
2807
|
-
|
|
2808
|
-
|
|
2809
|
-
|
|
2810
|
-
|
|
2811
|
-
|
|
2812
|
-
|
|
2813
|
-
|
|
2816
|
+
const presets = config.options.viewports?.presets;
|
|
2817
|
+
const marker = {
|
|
2818
|
+
...draft.marker,
|
|
2819
|
+
viewport: roundPoint(
|
|
2820
|
+
getAdjustedDraftPoint(draft.marker.viewport, draft, presets)
|
|
2821
|
+
)
|
|
2822
|
+
};
|
|
2823
|
+
const selection = draft.selection ? {
|
|
2824
|
+
...draft.selection,
|
|
2825
|
+
viewport: toPublicSelection(
|
|
2826
|
+
getAdjustedDraftSelection(
|
|
2827
|
+
toViewportSelection(draft.selection.viewport),
|
|
2828
|
+
draft,
|
|
2829
|
+
presets
|
|
2830
|
+
)
|
|
2831
|
+
)
|
|
2832
|
+
} : void 0;
|
|
2833
|
+
return {
|
|
2834
|
+
viewport: draft.viewport,
|
|
2835
|
+
marker,
|
|
2836
|
+
selection
|
|
2837
|
+
};
|
|
2838
|
+
}
|
|
2839
|
+
function createDraftCaptureButton(config, draft, options) {
|
|
2840
|
+
const button = document.createElement("button");
|
|
2841
|
+
const state = config.getState();
|
|
2842
|
+
const isCapturing = state.isCapturingViewport;
|
|
2843
|
+
const canCapture = canCaptureViewport(config);
|
|
2844
|
+
button.className = "dfwr-button";
|
|
2845
|
+
button.type = "button";
|
|
2846
|
+
button.disabled = !canCapture || isCapturing || state.isCreatingItem;
|
|
2847
|
+
button.setAttribute("aria-busy", isCapturing ? "true" : "false");
|
|
2848
|
+
button.title = canCapture ? "Capture current viewport" : "Viewport capture helper is not available";
|
|
2849
|
+
if (isCapturing) {
|
|
2850
|
+
button.append(createSpinner("dfwr-spinner"), "Capturing...");
|
|
2851
|
+
} else {
|
|
2852
|
+
button.textContent = "Capture";
|
|
2853
|
+
}
|
|
2854
|
+
button.addEventListener("click", (event) => {
|
|
2855
|
+
event.preventDefault();
|
|
2856
|
+
event.stopPropagation();
|
|
2857
|
+
const currentState = config.getState();
|
|
2858
|
+
if (!canCaptureViewport(config) || currentState.isCapturingViewport) {
|
|
2814
2859
|
return;
|
|
2815
2860
|
}
|
|
2816
|
-
if (
|
|
2817
|
-
|
|
2818
|
-
|
|
2819
|
-
|
|
2820
|
-
|
|
2821
|
-
const clone = element.cloneNode(true);
|
|
2822
|
-
this.removeDuplicateIds(clone);
|
|
2823
|
-
this.copyComputedStyle(element, clone, environment);
|
|
2824
|
-
this.positionDraftPreviewClone(clone, element, computedStyle);
|
|
2825
|
-
environment.document.body?.appendChild(clone);
|
|
2826
|
-
this.draftPreview = {
|
|
2827
|
-
element,
|
|
2828
|
-
clone,
|
|
2829
|
-
visibility: element.style.visibility
|
|
2861
|
+
if (options.kind === "area") {
|
|
2862
|
+
const areaDraft = currentState.areaDraft ?? draft;
|
|
2863
|
+
const nextDraft2 = {
|
|
2864
|
+
...areaDraft,
|
|
2865
|
+
comment: options.textarea.value
|
|
2830
2866
|
};
|
|
2831
|
-
|
|
2867
|
+
config.actions.setAreaDraft(nextDraft2);
|
|
2868
|
+
void config.actions.captureAreaDraft(getCaptureAreaDraft(nextDraft2));
|
|
2869
|
+
return;
|
|
2832
2870
|
}
|
|
2833
|
-
const
|
|
2834
|
-
const
|
|
2835
|
-
|
|
2836
|
-
|
|
2837
|
-
|
|
2838
|
-
|
|
2839
|
-
|
|
2840
|
-
|
|
2841
|
-
|
|
2842
|
-
|
|
2843
|
-
|
|
2844
|
-
|
|
2845
|
-
|
|
2846
|
-
|
|
2847
|
-
|
|
2848
|
-
|
|
2849
|
-
|
|
2850
|
-
|
|
2851
|
-
|
|
2852
|
-
|
|
2853
|
-
|
|
2854
|
-
|
|
2855
|
-
|
|
2856
|
-
|
|
2857
|
-
|
|
2858
|
-
|
|
2859
|
-
|
|
2860
|
-
|
|
2861
|
-
|
|
2862
|
-
|
|
2863
|
-
|
|
2864
|
-
|
|
2865
|
-
|
|
2866
|
-
|
|
2867
|
-
|
|
2868
|
-
|
|
2869
|
-
|
|
2870
|
-
|
|
2871
|
-
|
|
2872
|
-
|
|
2871
|
+
const domDraft = currentState.domDraft ?? draft;
|
|
2872
|
+
const nextDraft = {
|
|
2873
|
+
...domDraft,
|
|
2874
|
+
comment: options.textarea.value
|
|
2875
|
+
};
|
|
2876
|
+
config.actions.setDomDraft(nextDraft);
|
|
2877
|
+
void config.actions.captureDomDraft(
|
|
2878
|
+
getCaptureDomDraft(config, nextDraft, options.isElementDraft)
|
|
2879
|
+
);
|
|
2880
|
+
});
|
|
2881
|
+
return button;
|
|
2882
|
+
}
|
|
2883
|
+
|
|
2884
|
+
// src/core/view/draft.text.ts
|
|
2885
|
+
var DEFAULT_ADJUSTMENT_LABEL = "Responsive CSS px adjustments";
|
|
2886
|
+
function getAdjustmentLabel(options) {
|
|
2887
|
+
return options.adjustmentLabel?.trim() || DEFAULT_ADJUSTMENT_LABEL;
|
|
2888
|
+
}
|
|
2889
|
+
function formatSignedPx(value) {
|
|
2890
|
+
if (value === 0) return "+0px";
|
|
2891
|
+
return `${value > 0 ? "+" : ""}${value}px`;
|
|
2892
|
+
}
|
|
2893
|
+
function formatRoundedPx(value) {
|
|
2894
|
+
return `${Math.round(value)}px`;
|
|
2895
|
+
}
|
|
2896
|
+
function getSelectionMqMetrics(selection, viewport, presets) {
|
|
2897
|
+
const { scale } = getDraftViewportScale(viewport, presets);
|
|
2898
|
+
const ratio = scale > 0 ? 1 / scale : 1;
|
|
2899
|
+
return {
|
|
2900
|
+
x: selection.left * ratio,
|
|
2901
|
+
y: selection.top * ratio,
|
|
2902
|
+
width: selection.width * ratio,
|
|
2903
|
+
height: selection.height * ratio
|
|
2904
|
+
};
|
|
2905
|
+
}
|
|
2906
|
+
function getSelectionMetricLines(selection, viewport, presets) {
|
|
2907
|
+
if (!selection) return ["area", "x none / y none", "w none / h none"];
|
|
2908
|
+
const metrics = getSelectionMqMetrics(selection, viewport, presets);
|
|
2909
|
+
return [
|
|
2910
|
+
"area",
|
|
2911
|
+
`x ${formatRoundedPx(metrics.x)} / y ${formatRoundedPx(metrics.y)}`,
|
|
2912
|
+
`w ${formatRoundedPx(metrics.width)} / h ${formatRoundedPx(
|
|
2913
|
+
metrics.height
|
|
2914
|
+
)}`
|
|
2915
|
+
];
|
|
2916
|
+
}
|
|
2917
|
+
function getAreaDraftMetricSelection(draft) {
|
|
2918
|
+
if (!draft.selection) return void 0;
|
|
2919
|
+
return toViewportSelection(draft.selection.viewport);
|
|
2920
|
+
}
|
|
2921
|
+
function getDraftAdjustmentMetricLines(draft, presets) {
|
|
2922
|
+
const metrics = getDraftAdjustmentMetrics(draft, presets);
|
|
2923
|
+
return [
|
|
2924
|
+
`x ${formatSignedPx(metrics.x)} / y ${formatSignedPx(metrics.y)}`,
|
|
2925
|
+
`scale ${formatSignedPx(metrics.scale)}`
|
|
2926
|
+
];
|
|
2927
|
+
}
|
|
2928
|
+
function withDraftAdjustmentComment(comment, draft, options) {
|
|
2929
|
+
const presets = options.viewports?.presets;
|
|
2930
|
+
if (!hasDraftAdjustment(draft, presets)) return comment;
|
|
2931
|
+
const trimmedComment = comment.trim();
|
|
2932
|
+
const metrics = getDraftAdjustmentMetrics(draft, presets);
|
|
2933
|
+
const adjustment = [
|
|
2934
|
+
`${getAdjustmentLabel(options)}: x ${formatSignedPx(
|
|
2935
|
+
metrics.x
|
|
2936
|
+
)}, y ${formatSignedPx(metrics.y)}, scale ${formatSignedPx(
|
|
2937
|
+
metrics.scale
|
|
2938
|
+
)}`,
|
|
2939
|
+
`(${metrics.presetLabel} viewport, ${Math.round(
|
|
2940
|
+
metrics.viewportWidth
|
|
2941
|
+
)}/design ${Math.round(metrics.designWidth)})`
|
|
2942
|
+
].join(" ");
|
|
2943
|
+
return trimmedComment ? `${trimmedComment}
|
|
2944
|
+
${adjustment}` : adjustment;
|
|
2945
|
+
}
|
|
2946
|
+
|
|
2947
|
+
// src/core/view/form.widgets.ts
|
|
2948
|
+
function getAssigneeOption(options, assigneeId) {
|
|
2949
|
+
if (!assigneeId) return void 0;
|
|
2950
|
+
return options.assigneeOptions?.find(
|
|
2951
|
+
(option) => option.value === assigneeId
|
|
2952
|
+
);
|
|
2953
|
+
}
|
|
2954
|
+
function getAssigneeName(options, assigneeId) {
|
|
2955
|
+
return getAssigneeOption(options, assigneeId)?.label;
|
|
2956
|
+
}
|
|
2957
|
+
function isTitleFieldEnabled(options) {
|
|
2958
|
+
return options.fields?.title === true;
|
|
2959
|
+
}
|
|
2960
|
+
function createDraftTitleInput(value, onInput) {
|
|
2961
|
+
const input = document.createElement("input");
|
|
2962
|
+
input.className = "dfwr-input";
|
|
2963
|
+
input.placeholder = "Title";
|
|
2964
|
+
input.type = "text";
|
|
2965
|
+
input.value = value ?? "";
|
|
2966
|
+
input.addEventListener("input", () => onInput(input.value));
|
|
2967
|
+
return input;
|
|
2968
|
+
}
|
|
2969
|
+
function createDraftAssigneeSelect(options, value, fallbackLabel, onChange) {
|
|
2970
|
+
const assigneeOptions = options.assigneeOptions ?? [];
|
|
2971
|
+
if (assigneeOptions.length === 0) return void 0;
|
|
2972
|
+
const assigneeTitle = options.assigneeTitle?.trim() || "Assignee";
|
|
2973
|
+
const select = document.createElement("select");
|
|
2974
|
+
select.className = "dfwr-select";
|
|
2975
|
+
const emptyOption = document.createElement("option");
|
|
2976
|
+
emptyOption.value = "";
|
|
2977
|
+
emptyOption.textContent = assigneeTitle;
|
|
2978
|
+
select.append(emptyOption);
|
|
2979
|
+
const hasUnknownAssignee = Boolean(value) && !assigneeOptions.some((option) => option.value === value);
|
|
2980
|
+
if (hasUnknownAssignee && value) {
|
|
2981
|
+
const option = document.createElement("option");
|
|
2982
|
+
option.value = value;
|
|
2983
|
+
option.textContent = fallbackLabel ?? value;
|
|
2984
|
+
select.append(option);
|
|
2985
|
+
}
|
|
2986
|
+
assigneeOptions.forEach((assigneeOption) => {
|
|
2987
|
+
const option = document.createElement("option");
|
|
2988
|
+
option.value = assigneeOption.value;
|
|
2989
|
+
option.textContent = assigneeOption.label;
|
|
2990
|
+
select.append(option);
|
|
2991
|
+
});
|
|
2992
|
+
select.value = value ?? "";
|
|
2993
|
+
select.addEventListener("change", () => {
|
|
2994
|
+
onChange(select.value || null, getAssigneeName(options, select.value));
|
|
2995
|
+
});
|
|
2996
|
+
return select;
|
|
2997
|
+
}
|
|
2998
|
+
function getDraftFields(options, titleInput, textarea, assigneeSelect) {
|
|
2999
|
+
const title = titleInput?.value.trim();
|
|
3000
|
+
const comment = textarea.value.trim();
|
|
3001
|
+
const assigneeId = assigneeSelect?.value.trim() || void 0;
|
|
3002
|
+
return {
|
|
3003
|
+
title: title || void 0,
|
|
3004
|
+
comment,
|
|
3005
|
+
assigneeId,
|
|
3006
|
+
assigneeName: getAssigneeName(options, assigneeId)
|
|
3007
|
+
};
|
|
3008
|
+
}
|
|
3009
|
+
function createFormActions({
|
|
3010
|
+
saveLabel,
|
|
3011
|
+
onSave,
|
|
3012
|
+
onCancel,
|
|
3013
|
+
isSaving,
|
|
3014
|
+
beforeSave,
|
|
3015
|
+
className,
|
|
3016
|
+
leading
|
|
3017
|
+
}) {
|
|
3018
|
+
const actions = document.createElement("div");
|
|
3019
|
+
actions.className = ["dfwr-actions", className].filter(Boolean).join(" ");
|
|
3020
|
+
const save = document.createElement("button");
|
|
3021
|
+
save.className = "dfwr-button is-primary";
|
|
3022
|
+
save.type = "button";
|
|
3023
|
+
save.disabled = isSaving;
|
|
3024
|
+
save.setAttribute("aria-busy", isSaving ? "true" : "false");
|
|
3025
|
+
if (isSaving) {
|
|
3026
|
+
save.append(createSpinner("dfwr-spinner"), "Saving...");
|
|
3027
|
+
} else {
|
|
3028
|
+
save.textContent = saveLabel;
|
|
3029
|
+
}
|
|
3030
|
+
save.addEventListener("click", (event) => {
|
|
3031
|
+
event.preventDefault();
|
|
3032
|
+
event.stopPropagation();
|
|
3033
|
+
if (save.disabled) return;
|
|
3034
|
+
onSave();
|
|
3035
|
+
});
|
|
3036
|
+
const cancel = document.createElement("button");
|
|
3037
|
+
cancel.className = "dfwr-button";
|
|
3038
|
+
cancel.type = "button";
|
|
3039
|
+
cancel.disabled = isSaving;
|
|
3040
|
+
cancel.textContent = "Cancel";
|
|
3041
|
+
cancel.addEventListener("click", (event) => {
|
|
3042
|
+
onCancel(event);
|
|
3043
|
+
});
|
|
3044
|
+
if (leading?.length) {
|
|
3045
|
+
actions.classList.add("has-leading");
|
|
3046
|
+
const leadingGroup = document.createElement("div");
|
|
3047
|
+
leadingGroup.className = "dfwr-actions-leading";
|
|
3048
|
+
leadingGroup.append(...leading);
|
|
3049
|
+
const primary = document.createElement("div");
|
|
3050
|
+
primary.className = "dfwr-actions-primary";
|
|
3051
|
+
primary.append(save, cancel);
|
|
3052
|
+
actions.append(leadingGroup, primary);
|
|
3053
|
+
return actions;
|
|
2873
3054
|
}
|
|
2874
|
-
|
|
2875
|
-
|
|
2876
|
-
|
|
2877
|
-
const property = computedStyle.item(index);
|
|
2878
|
-
clone.style.setProperty(
|
|
2879
|
-
property,
|
|
2880
|
-
computedStyle.getPropertyValue(property),
|
|
2881
|
-
computedStyle.getPropertyPriority(property)
|
|
2882
|
-
);
|
|
2883
|
-
}
|
|
3055
|
+
if (beforeSave?.length || className) {
|
|
3056
|
+
actions.append(cancel, ...beforeSave ?? [], save);
|
|
3057
|
+
return actions;
|
|
2884
3058
|
}
|
|
2885
|
-
|
|
2886
|
-
|
|
2887
|
-
|
|
2888
|
-
|
|
2889
|
-
|
|
3059
|
+
actions.append(save, cancel);
|
|
3060
|
+
return actions;
|
|
3061
|
+
}
|
|
3062
|
+
function createDraftError(message) {
|
|
3063
|
+
if (!message) return void 0;
|
|
3064
|
+
const error = document.createElement("p");
|
|
3065
|
+
error.className = "dfwr-form-error";
|
|
3066
|
+
error.setAttribute("role", "alert");
|
|
3067
|
+
error.textContent = message;
|
|
3068
|
+
return error;
|
|
3069
|
+
}
|
|
3070
|
+
function createDraftDragHandle(label) {
|
|
3071
|
+
const handle = document.createElement("button");
|
|
3072
|
+
handle.className = "dfwr-draft-drag-handle";
|
|
3073
|
+
handle.type = "button";
|
|
3074
|
+
handle.setAttribute("aria-label", label);
|
|
3075
|
+
return handle;
|
|
3076
|
+
}
|
|
3077
|
+
|
|
3078
|
+
// src/core/review/format.ts
|
|
3079
|
+
function formatDomDraftMeta(draft) {
|
|
3080
|
+
const parts = [
|
|
3081
|
+
`viewport ${formatSize(draft.viewport)}`,
|
|
3082
|
+
`point ${formatPoint(draft.marker.viewport)}`
|
|
3083
|
+
];
|
|
3084
|
+
if (draft.anchor) {
|
|
3085
|
+
parts.push(formatAnchorMeta(draft.anchor));
|
|
2890
3086
|
}
|
|
2891
|
-
|
|
2892
|
-
|
|
3087
|
+
return parts.join(" / ");
|
|
3088
|
+
}
|
|
3089
|
+
function formatItemMeta(item) {
|
|
3090
|
+
const parts = [formatDate(item.createdAt)];
|
|
3091
|
+
const marker = getItemMarker(item);
|
|
3092
|
+
const selection = getItemSelection(item);
|
|
3093
|
+
if (item.viewport) {
|
|
3094
|
+
parts.push(`viewport ${formatSize(item.viewport)}`);
|
|
2893
3095
|
}
|
|
2894
|
-
|
|
2895
|
-
|
|
2896
|
-
header.className = "dfwr-header";
|
|
2897
|
-
const title = document.createElement("div");
|
|
2898
|
-
title.className = "dfwr-title";
|
|
2899
|
-
title.textContent = "Review Kit";
|
|
2900
|
-
const meta = document.createElement("div");
|
|
2901
|
-
meta.className = "dfwr-meta";
|
|
2902
|
-
meta.textContent = getRouteKey(this.config.getEnvironment());
|
|
2903
|
-
const titleGroup = document.createElement("div");
|
|
2904
|
-
titleGroup.append(title, meta);
|
|
2905
|
-
const close = document.createElement("button");
|
|
2906
|
-
close.className = "dfwr-icon-button";
|
|
2907
|
-
close.type = "button";
|
|
2908
|
-
close.textContent = "x";
|
|
2909
|
-
close.setAttribute("aria-label", "Close");
|
|
2910
|
-
close.addEventListener("click", () => this.config.actions.close());
|
|
2911
|
-
header.append(titleGroup, close);
|
|
2912
|
-
return header;
|
|
3096
|
+
if (marker) {
|
|
3097
|
+
parts.push(`point ${formatPoint(marker.viewport)}`);
|
|
2913
3098
|
}
|
|
2914
|
-
|
|
2915
|
-
|
|
2916
|
-
toolbar.className = "dfwr-toolbar";
|
|
2917
|
-
toolbar.append(
|
|
2918
|
-
this.createToolbarButton("Note", this.state.mode === "note", () => {
|
|
2919
|
-
const mode = this.state.mode;
|
|
2920
|
-
this.config.actions.setModeState(mode === "note" ? "idle" : "note");
|
|
2921
|
-
this.config.actions.clearDrafts();
|
|
2922
|
-
this.config.actions.render();
|
|
2923
|
-
}),
|
|
2924
|
-
this.createToolbarButton("Element", this.state.mode === "element", () => {
|
|
2925
|
-
const mode = this.state.mode;
|
|
2926
|
-
this.config.actions.setModeState(
|
|
2927
|
-
mode === "element" ? "idle" : "element"
|
|
2928
|
-
);
|
|
2929
|
-
this.config.actions.clearDrafts();
|
|
2930
|
-
this.config.actions.render();
|
|
2931
|
-
}),
|
|
2932
|
-
this.createToolbarButton(
|
|
2933
|
-
this.state.isSelectingArea ? "Selecting" : "Area",
|
|
2934
|
-
this.state.mode === "area",
|
|
2935
|
-
() => {
|
|
2936
|
-
const mode = this.state.mode;
|
|
2937
|
-
this.config.actions.setModeState(mode === "area" ? "idle" : "area");
|
|
2938
|
-
this.config.actions.clearDrafts();
|
|
2939
|
-
this.config.actions.render();
|
|
2940
|
-
}
|
|
2941
|
-
),
|
|
2942
|
-
this.createToolbarButton("Refresh", false, () => {
|
|
2943
|
-
void this.config.actions.reload();
|
|
2944
|
-
})
|
|
2945
|
-
);
|
|
2946
|
-
return toolbar;
|
|
3099
|
+
if (selection) {
|
|
3100
|
+
parts.push(`rect ${formatSelection(selection.viewport)}`);
|
|
2947
3101
|
}
|
|
2948
|
-
|
|
2949
|
-
|
|
2950
|
-
button.className = `dfwr-button${active ? " is-active" : ""}`;
|
|
2951
|
-
button.type = "button";
|
|
2952
|
-
button.textContent = label;
|
|
2953
|
-
button.addEventListener("click", onClick);
|
|
2954
|
-
return button;
|
|
3102
|
+
if (item.anchor) {
|
|
3103
|
+
parts.push(formatAnchorMeta(item.anchor));
|
|
2955
3104
|
}
|
|
2956
|
-
|
|
2957
|
-
|
|
2958
|
-
|
|
2959
|
-
|
|
2960
|
-
|
|
2961
|
-
|
|
2962
|
-
|
|
2963
|
-
|
|
2964
|
-
|
|
2965
|
-
|
|
2966
|
-
|
|
2967
|
-
|
|
2968
|
-
|
|
2969
|
-
|
|
2970
|
-
|
|
2971
|
-
|
|
2972
|
-
|
|
3105
|
+
return parts.join(" / ");
|
|
3106
|
+
}
|
|
3107
|
+
function formatDate(value) {
|
|
3108
|
+
const date = new Date(value);
|
|
3109
|
+
if (Number.isNaN(date.getTime())) return value;
|
|
3110
|
+
return date.toLocaleString(void 0, {
|
|
3111
|
+
month: "2-digit",
|
|
3112
|
+
day: "2-digit",
|
|
3113
|
+
hour: "2-digit",
|
|
3114
|
+
minute: "2-digit"
|
|
3115
|
+
});
|
|
3116
|
+
}
|
|
3117
|
+
function formatSize(size) {
|
|
3118
|
+
return `${Math.round(size.width)}x${Math.round(size.height)}`;
|
|
3119
|
+
}
|
|
3120
|
+
function formatPoint(point) {
|
|
3121
|
+
return `${Math.round(point.x)},${Math.round(point.y)}`;
|
|
3122
|
+
}
|
|
3123
|
+
function formatSelection(selection) {
|
|
3124
|
+
return [
|
|
3125
|
+
Math.round(selection.x),
|
|
3126
|
+
Math.round(selection.y),
|
|
3127
|
+
Math.round(selection.width),
|
|
3128
|
+
Math.round(selection.height)
|
|
3129
|
+
].join(",");
|
|
3130
|
+
}
|
|
3131
|
+
function formatAnchorMeta(anchor) {
|
|
3132
|
+
const parts = [`dom ${anchor.strategy}`];
|
|
3133
|
+
if (typeof anchor.confidence === "number") {
|
|
3134
|
+
parts.push(`${Math.round(anchor.confidence * 100)}%`);
|
|
2973
3135
|
}
|
|
2974
|
-
|
|
2975
|
-
|
|
2976
|
-
|
|
2977
|
-
empty.textContent = this.state.noteDraft ? "Write the note in the page box." : this.state.mode === "element" ? "Click an element to add QA." : "Click on the page to place a note.";
|
|
2978
|
-
return empty;
|
|
3136
|
+
const candidates = getAnchorCandidates(anchor);
|
|
3137
|
+
if (candidates.length > 1) {
|
|
3138
|
+
parts.push(`${candidates.length} candidates`);
|
|
2979
3139
|
}
|
|
2980
|
-
|
|
2981
|
-
|
|
2982
|
-
|
|
2983
|
-
|
|
2984
|
-
|
|
2985
|
-
|
|
2986
|
-
|
|
2987
|
-
|
|
2988
|
-
|
|
2989
|
-
|
|
2990
|
-
|
|
2991
|
-
|
|
2992
|
-
|
|
2993
|
-
|
|
2994
|
-
|
|
2995
|
-
|
|
2996
|
-
|
|
2997
|
-
|
|
2998
|
-
|
|
2999
|
-
|
|
3000
|
-
|
|
3001
|
-
|
|
3002
|
-
|
|
3003
|
-
|
|
3004
|
-
|
|
3005
|
-
|
|
3006
|
-
|
|
3007
|
-
|
|
3008
|
-
|
|
3009
|
-
|
|
3010
|
-
|
|
3011
|
-
|
|
3012
|
-
|
|
3013
|
-
|
|
3014
|
-
|
|
3015
|
-
|
|
3016
|
-
|
|
3017
|
-
|
|
3018
|
-
|
|
3019
|
-
|
|
3020
|
-
|
|
3021
|
-
|
|
3022
|
-
|
|
3023
|
-
|
|
3024
|
-
|
|
3025
|
-
|
|
3026
|
-
|
|
3027
|
-
|
|
3028
|
-
|
|
3029
|
-
|
|
3030
|
-
|
|
3031
|
-
|
|
3032
|
-
|
|
3033
|
-
|
|
3034
|
-
|
|
3035
|
-
|
|
3036
|
-
|
|
3037
|
-
|
|
3140
|
+
return parts.join(" ");
|
|
3141
|
+
}
|
|
3142
|
+
|
|
3143
|
+
// src/core/view/markers.ts
|
|
3144
|
+
function createMarkerElement(itemId, hostPoint, label, scope, isBound, isHighlighted) {
|
|
3145
|
+
const marker = document.createElement("div");
|
|
3146
|
+
marker.className = [
|
|
3147
|
+
"dfwr-bound-marker",
|
|
3148
|
+
`is-scope-${scope}`,
|
|
3149
|
+
isBound ? "is-bound" : "is-fallback",
|
|
3150
|
+
isHighlighted ? "is-highlighted" : ""
|
|
3151
|
+
].filter(Boolean).join(" ");
|
|
3152
|
+
marker.style.left = `${hostPoint.x}px`;
|
|
3153
|
+
marker.style.top = `${hostPoint.y}px`;
|
|
3154
|
+
marker.dataset.scope = scope;
|
|
3155
|
+
if (itemId) {
|
|
3156
|
+
marker.dataset.reviewItemId = itemId;
|
|
3157
|
+
}
|
|
3158
|
+
const iconElement = document.createElement("span");
|
|
3159
|
+
iconElement.className = "dfwr-bound-marker-icon";
|
|
3160
|
+
iconElement.setAttribute("aria-hidden", "true");
|
|
3161
|
+
const labelElement = document.createElement("span");
|
|
3162
|
+
labelElement.className = "dfwr-bound-marker-number";
|
|
3163
|
+
labelElement.textContent = label;
|
|
3164
|
+
marker.append(iconElement, labelElement);
|
|
3165
|
+
return marker;
|
|
3166
|
+
}
|
|
3167
|
+
function createSelectionHighlight(selection, environment, isDraft) {
|
|
3168
|
+
const rect = toHostSelection(selection, environment);
|
|
3169
|
+
const highlight = document.createElement("div");
|
|
3170
|
+
highlight.className = `dfwr-selection-highlight${isDraft ? " is-draft" : ""}`;
|
|
3171
|
+
highlight.style.left = `${rect.left}px`;
|
|
3172
|
+
highlight.style.top = `${rect.top}px`;
|
|
3173
|
+
highlight.style.width = `${rect.width}px`;
|
|
3174
|
+
highlight.style.height = `${rect.height}px`;
|
|
3175
|
+
return highlight;
|
|
3176
|
+
}
|
|
3177
|
+
function createItemHighlightElements(selection, environment, item, label, scope, isBound, isHighlighted) {
|
|
3178
|
+
const rect = toHostSelection(selection, environment);
|
|
3179
|
+
const mode = getReviewItemHighlightMode(item);
|
|
3180
|
+
const highlight = document.createElement("div");
|
|
3181
|
+
highlight.className = [
|
|
3182
|
+
"dfwr-item-target-highlight",
|
|
3183
|
+
`is-mode-${mode}`,
|
|
3184
|
+
`is-scope-${scope}`,
|
|
3185
|
+
isBound ? "is-bound" : "is-fallback",
|
|
3186
|
+
isHighlighted ? "is-highlighted" : ""
|
|
3187
|
+
].filter(Boolean).join(" ");
|
|
3188
|
+
highlight.style.left = `${rect.left}px`;
|
|
3189
|
+
highlight.style.top = `${rect.top}px`;
|
|
3190
|
+
highlight.style.width = `${rect.width}px`;
|
|
3191
|
+
highlight.style.height = `${rect.height}px`;
|
|
3192
|
+
highlight.dataset.reviewItemId = item.id;
|
|
3193
|
+
const labelElement = document.createElement("div");
|
|
3194
|
+
labelElement.className = [
|
|
3195
|
+
"dfwr-item-target-label",
|
|
3196
|
+
`is-mode-${mode}`,
|
|
3197
|
+
`is-scope-${scope}`,
|
|
3198
|
+
isHighlighted ? "is-highlighted" : ""
|
|
3199
|
+
].filter(Boolean).join(" ");
|
|
3200
|
+
labelElement.textContent = label;
|
|
3201
|
+
labelElement.style.left = `${Math.max(4, rect.left)}px`;
|
|
3202
|
+
labelElement.style.top = `${Math.max(4, rect.top - 24)}px`;
|
|
3203
|
+
labelElement.dataset.reviewItemId = item.id;
|
|
3204
|
+
return [highlight, labelElement];
|
|
3205
|
+
}
|
|
3206
|
+
function createMarkerLayer({
|
|
3207
|
+
items,
|
|
3208
|
+
highlightedItemId,
|
|
3209
|
+
environment,
|
|
3210
|
+
presets,
|
|
3211
|
+
showCompactMarkers = true
|
|
3212
|
+
}) {
|
|
3213
|
+
const layer = document.createElement("div");
|
|
3214
|
+
layer.className = "dfwr-marker-layer";
|
|
3215
|
+
if (!environment) return layer;
|
|
3216
|
+
const currentScope = getReviewViewportScope(
|
|
3217
|
+
getViewportSize(environment),
|
|
3218
|
+
presets
|
|
3219
|
+
);
|
|
3220
|
+
getNumberedReviewItems(items, presets).forEach((numberedItem) => {
|
|
3221
|
+
const { item, scope, displayLabel } = numberedItem;
|
|
3222
|
+
if (!shouldShowMarkerForScope(scope, currentScope)) {
|
|
3223
|
+
return;
|
|
3038
3224
|
}
|
|
3039
|
-
const
|
|
3040
|
-
|
|
3041
|
-
|
|
3042
|
-
|
|
3043
|
-
|
|
3044
|
-
|
|
3045
|
-
|
|
3046
|
-
|
|
3047
|
-
|
|
3048
|
-
|
|
3049
|
-
|
|
3050
|
-
|
|
3051
|
-
|
|
3052
|
-
|
|
3053
|
-
|
|
3054
|
-
|
|
3055
|
-
textarea.className = "dfwr-textarea";
|
|
3056
|
-
textarea.placeholder = "Review comment";
|
|
3057
|
-
textarea.rows = 4;
|
|
3058
|
-
textarea.value = draft.comment ?? "";
|
|
3059
|
-
textarea.addEventListener("input", () => {
|
|
3060
|
-
const noteDraft = this.state.noteDraft;
|
|
3061
|
-
if (!noteDraft) return;
|
|
3062
|
-
this.config.actions.setNoteDraft({
|
|
3063
|
-
...noteDraft,
|
|
3064
|
-
comment: textarea.value
|
|
3065
|
-
});
|
|
3066
|
-
});
|
|
3067
|
-
const assigneeSelect = this.createDraftAssigneeSelect(
|
|
3068
|
-
draft.assigneeId,
|
|
3069
|
-
draft.assigneeName,
|
|
3070
|
-
(assigneeId, assigneeName) => {
|
|
3071
|
-
const noteDraft = this.state.noteDraft;
|
|
3072
|
-
if (!noteDraft) return;
|
|
3073
|
-
this.config.actions.setNoteDraft({
|
|
3074
|
-
...noteDraft,
|
|
3075
|
-
assigneeId,
|
|
3076
|
-
assigneeName
|
|
3077
|
-
});
|
|
3225
|
+
const isHighlighted = item.id === highlightedItemId;
|
|
3226
|
+
if (isHighlighted) {
|
|
3227
|
+
const selection = getItemHighlightSelection(item, environment);
|
|
3228
|
+
if (selection) {
|
|
3229
|
+
layer.append(
|
|
3230
|
+
...createItemHighlightElements(
|
|
3231
|
+
selection.viewport,
|
|
3232
|
+
environment,
|
|
3233
|
+
item,
|
|
3234
|
+
displayLabel,
|
|
3235
|
+
scope,
|
|
3236
|
+
selection.isBound,
|
|
3237
|
+
isHighlighted
|
|
3238
|
+
)
|
|
3239
|
+
);
|
|
3240
|
+
return;
|
|
3078
3241
|
}
|
|
3079
|
-
);
|
|
3080
|
-
const saveDraft = () => {
|
|
3081
|
-
const currentDraft = this.state.noteDraft ?? draft;
|
|
3082
|
-
const fields = this.getDraftFields(titleInput, textarea, assigneeSelect);
|
|
3083
|
-
const comment = fields.comment;
|
|
3084
|
-
if (!comment && !this.hasDraftAdjustment(currentDraft)) return;
|
|
3085
|
-
void this.config.actions.createItem({
|
|
3086
|
-
kind: "note",
|
|
3087
|
-
title: fields.title,
|
|
3088
|
-
comment: this.withDraftAdjustmentComment(comment, currentDraft),
|
|
3089
|
-
assigneeId: fields.assigneeId,
|
|
3090
|
-
assigneeName: fields.assigneeName,
|
|
3091
|
-
viewport: currentDraft.viewport,
|
|
3092
|
-
anchor: currentDraft.anchor,
|
|
3093
|
-
marker: currentDraft.marker,
|
|
3094
|
-
selection: currentDraft.selection
|
|
3095
|
-
});
|
|
3096
|
-
};
|
|
3097
|
-
const adjustmentControls = isElementDraft ? this.createAdjustmentControls({
|
|
3098
|
-
draft,
|
|
3099
|
-
pin,
|
|
3100
|
-
popover,
|
|
3101
|
-
selectionHighlight,
|
|
3102
|
-
textarea,
|
|
3103
|
-
dockToggle: options.dockComposer
|
|
3104
|
-
}) : void 0;
|
|
3105
|
-
const actions = this.createFormActions("Save note", saveDraft, {
|
|
3106
|
-
leading: adjustmentControls?.actionButton ? [adjustmentControls.actionButton] : void 0
|
|
3107
|
-
});
|
|
3108
|
-
const error = this.createDraftError();
|
|
3109
|
-
form.append(
|
|
3110
|
-
...meta ? [meta] : [],
|
|
3111
|
-
...adjustmentControls ? [adjustmentControls.panel] : [],
|
|
3112
|
-
...titleInput ? [titleInput] : [],
|
|
3113
|
-
textarea,
|
|
3114
|
-
...assigneeSelect ? [assigneeSelect] : [],
|
|
3115
|
-
...error ? [error] : [],
|
|
3116
|
-
actions
|
|
3117
|
-
);
|
|
3118
|
-
const dragHandle = isElementDraft && !options.dockComposer ? this.createDraftDragHandle("Move DOM composer") : void 0;
|
|
3119
|
-
popover.append(
|
|
3120
|
-
...dragHandle ? [dragHandle] : [],
|
|
3121
|
-
form
|
|
3122
|
-
);
|
|
3123
|
-
group.append(pin);
|
|
3124
|
-
if (!options.dockComposer) {
|
|
3125
|
-
group.append(popover);
|
|
3126
|
-
}
|
|
3127
|
-
if (dragHandle) {
|
|
3128
|
-
this.attachDraftComposerDrag(popover, dragHandle, (composerPosition) => {
|
|
3129
|
-
const noteDraft = this.state.noteDraft ?? draft;
|
|
3130
|
-
this.config.actions.setNoteDraft({
|
|
3131
|
-
...noteDraft,
|
|
3132
|
-
composerPosition,
|
|
3133
|
-
comment: textarea.value
|
|
3134
|
-
});
|
|
3135
|
-
});
|
|
3136
3242
|
}
|
|
3137
|
-
|
|
3138
|
-
|
|
3139
|
-
isElementDraft || options.dockComposer ? void 0 : popover,
|
|
3140
|
-
meta,
|
|
3141
|
-
textarea
|
|
3142
|
-
);
|
|
3143
|
-
if (!options.dockComposer) {
|
|
3144
|
-
window.setTimeout(() => {
|
|
3145
|
-
if (draft.adjustment?.isActive) {
|
|
3146
|
-
adjustmentControls?.focusTarget.focus();
|
|
3147
|
-
return;
|
|
3148
|
-
}
|
|
3149
|
-
textarea.focus();
|
|
3150
|
-
}, 0);
|
|
3151
|
-
}
|
|
3152
|
-
return {
|
|
3153
|
-
layer: group,
|
|
3154
|
-
composer: options.dockComposer ? popover : void 0
|
|
3155
|
-
};
|
|
3156
|
-
}
|
|
3157
|
-
createDraftDragHandle(label) {
|
|
3158
|
-
const handle = document.createElement("button");
|
|
3159
|
-
handle.className = "dfwr-draft-drag-handle";
|
|
3160
|
-
handle.type = "button";
|
|
3161
|
-
handle.setAttribute("aria-label", label);
|
|
3162
|
-
return handle;
|
|
3163
|
-
}
|
|
3164
|
-
createIcon(paths) {
|
|
3165
|
-
const svg = document.createElementNS("http://www.w3.org/2000/svg", "svg");
|
|
3166
|
-
svg.setAttribute("aria-hidden", "true");
|
|
3167
|
-
svg.setAttribute("viewBox", "0 0 24 24");
|
|
3168
|
-
svg.setAttribute("fill", "none");
|
|
3169
|
-
svg.setAttribute("stroke", "currentColor");
|
|
3170
|
-
svg.setAttribute("stroke-width", "2.4");
|
|
3171
|
-
svg.setAttribute("stroke-linecap", "round");
|
|
3172
|
-
svg.setAttribute("stroke-linejoin", "round");
|
|
3173
|
-
paths.forEach((d) => {
|
|
3174
|
-
const path = document.createElementNS("http://www.w3.org/2000/svg", "path");
|
|
3175
|
-
path.setAttribute("d", d);
|
|
3176
|
-
svg.append(path);
|
|
3177
|
-
});
|
|
3178
|
-
return svg;
|
|
3179
|
-
}
|
|
3180
|
-
setAdjustmentToggleIcon(button, isActive) {
|
|
3181
|
-
const paths = isActive ? ["M20 6 9 17l-5-5"] : [
|
|
3182
|
-
"M12 2v20",
|
|
3183
|
-
"M2 12h20",
|
|
3184
|
-
"m9 5 3-3 3 3",
|
|
3185
|
-
"m9 19 3 3 3-3",
|
|
3186
|
-
"m5 9-3 3 3 3",
|
|
3187
|
-
"m19 9 3 3-3 3"
|
|
3188
|
-
];
|
|
3189
|
-
button.replaceChildren(this.createIcon(paths));
|
|
3190
|
-
}
|
|
3191
|
-
attachDraftComposerDrag(popover, handle, onMove) {
|
|
3192
|
-
let isDragging = false;
|
|
3193
|
-
let offsetX = 0;
|
|
3194
|
-
let offsetY = 0;
|
|
3195
|
-
const movePopover = (event) => {
|
|
3196
|
-
const environment = this.config.getEnvironment();
|
|
3197
|
-
if (!environment) return;
|
|
3198
|
-
const position = this.getClampedComposerPosition(
|
|
3199
|
-
{
|
|
3200
|
-
x: event.clientX - offsetX,
|
|
3201
|
-
y: event.clientY - offsetY
|
|
3202
|
-
},
|
|
3203
|
-
environment,
|
|
3204
|
-
{
|
|
3205
|
-
width: popover.offsetWidth,
|
|
3206
|
-
height: popover.offsetHeight
|
|
3207
|
-
},
|
|
3208
|
-
this.getHostComposerBounds()
|
|
3209
|
-
);
|
|
3210
|
-
popover.style.left = `${position.x}px`;
|
|
3211
|
-
popover.style.top = `${position.y}px`;
|
|
3212
|
-
onMove(position);
|
|
3213
|
-
};
|
|
3214
|
-
handle.addEventListener("pointerdown", (event) => {
|
|
3215
|
-
if (event.button !== 0) return;
|
|
3216
|
-
const rect = popover.getBoundingClientRect();
|
|
3217
|
-
offsetX = event.clientX - rect.left;
|
|
3218
|
-
offsetY = event.clientY - rect.top;
|
|
3219
|
-
isDragging = true;
|
|
3220
|
-
event.preventDefault();
|
|
3221
|
-
event.stopPropagation();
|
|
3222
|
-
handle.setPointerCapture(event.pointerId);
|
|
3223
|
-
popover.classList.add("is-dragging");
|
|
3224
|
-
});
|
|
3225
|
-
handle.addEventListener("pointermove", (event) => {
|
|
3226
|
-
if (!isDragging || !handle.hasPointerCapture(event.pointerId)) return;
|
|
3227
|
-
event.preventDefault();
|
|
3228
|
-
movePopover(event);
|
|
3229
|
-
});
|
|
3230
|
-
const stopDrag = (event) => {
|
|
3231
|
-
if (!isDragging || !handle.hasPointerCapture(event.pointerId)) return;
|
|
3232
|
-
event.preventDefault();
|
|
3233
|
-
event.stopPropagation();
|
|
3234
|
-
isDragging = false;
|
|
3235
|
-
handle.releasePointerCapture(event.pointerId);
|
|
3236
|
-
popover.classList.remove("is-dragging");
|
|
3237
|
-
movePopover(event);
|
|
3238
|
-
};
|
|
3239
|
-
handle.addEventListener("pointerup", stopDrag);
|
|
3240
|
-
handle.addEventListener("pointercancel", stopDrag);
|
|
3241
|
-
}
|
|
3242
|
-
// Builds the element-adjustment controls (nudge the previewed element via
|
|
3243
|
-
// arrow keys / buttons). Wires keyboard deltas to the draft transform and
|
|
3244
|
-
// keeps the pin, popover, highlight and textarea in sync as the value changes.
|
|
3245
|
-
createAdjustmentControls({
|
|
3246
|
-
draft,
|
|
3247
|
-
pin,
|
|
3248
|
-
popover,
|
|
3249
|
-
selectionHighlight,
|
|
3250
|
-
textarea,
|
|
3251
|
-
dockToggle
|
|
3252
|
-
}) {
|
|
3253
|
-
const panel = document.createElement("div");
|
|
3254
|
-
panel.className = "dfwr-adjust-panel is-dom-adjust-panel";
|
|
3255
|
-
const header = document.createElement("div");
|
|
3256
|
-
header.className = "dfwr-adjust-panel-header";
|
|
3257
|
-
const help = document.createElement("div");
|
|
3258
|
-
help.className = "dfwr-adjust-help";
|
|
3259
|
-
help.textContent = this.getAdjustmentLabel();
|
|
3260
|
-
const adjust = document.createElement("button");
|
|
3261
|
-
adjust.className = "dfwr-adjust-toggle";
|
|
3262
|
-
adjust.type = "button";
|
|
3263
|
-
adjust.title = "Adjust DOM element with keyboard arrows";
|
|
3264
|
-
adjust.setAttribute("aria-label", "Adjust DOM element with keyboard arrows");
|
|
3265
|
-
const xyStatus = document.createElement("div");
|
|
3266
|
-
xyStatus.className = "dfwr-adjust-status";
|
|
3267
|
-
const scaleStatus = document.createElement("div");
|
|
3268
|
-
scaleStatus.className = "dfwr-adjust-status";
|
|
3269
|
-
const syncControls = (nextDraft) => {
|
|
3270
|
-
const isActive = nextDraft.adjustment?.isActive === true;
|
|
3271
|
-
panel.classList.toggle("is-active", isActive);
|
|
3272
|
-
adjust.classList.toggle("is-active", isActive);
|
|
3273
|
-
adjust.setAttribute("aria-pressed", isActive ? "true" : "false");
|
|
3274
|
-
this.setAdjustmentToggleIcon(adjust, isActive);
|
|
3275
|
-
adjust.title = isActive ? "Finish DOM adjustment" : "Adjust DOM element with keyboard arrows";
|
|
3276
|
-
adjust.setAttribute(
|
|
3277
|
-
"aria-label",
|
|
3278
|
-
isActive ? "Finish DOM adjustment" : "Adjust DOM element with keyboard arrows"
|
|
3279
|
-
);
|
|
3280
|
-
const [xyLine, scaleLine] = this.getDraftAdjustmentMetricLines(nextDraft);
|
|
3281
|
-
xyStatus.textContent = xyLine;
|
|
3282
|
-
scaleStatus.textContent = scaleLine;
|
|
3283
|
-
this.syncDraftAdjustmentUi({
|
|
3284
|
-
draft: nextDraft,
|
|
3285
|
-
pin,
|
|
3286
|
-
selectionHighlight
|
|
3287
|
-
});
|
|
3288
|
-
};
|
|
3289
|
-
const updateDraft = (updater) => {
|
|
3290
|
-
const currentDraft = this.state.noteDraft ?? draft;
|
|
3291
|
-
const nextDraft = updater(currentDraft);
|
|
3292
|
-
this.config.actions.setNoteDraft({
|
|
3293
|
-
...nextDraft,
|
|
3294
|
-
comment: textarea.value
|
|
3295
|
-
});
|
|
3296
|
-
syncControls(nextDraft);
|
|
3297
|
-
};
|
|
3298
|
-
adjust.addEventListener("click", () => {
|
|
3299
|
-
updateDraft((currentDraft) => ({
|
|
3300
|
-
...currentDraft,
|
|
3301
|
-
adjustment: {
|
|
3302
|
-
x: currentDraft.adjustment?.x ?? 0,
|
|
3303
|
-
y: currentDraft.adjustment?.y ?? 0,
|
|
3304
|
-
scale: currentDraft.adjustment?.scale ?? 0,
|
|
3305
|
-
isActive: currentDraft.adjustment?.isActive !== true
|
|
3306
|
-
}
|
|
3307
|
-
}));
|
|
3308
|
-
adjust.focus();
|
|
3309
|
-
});
|
|
3310
|
-
popover.addEventListener("keydown", (event) => {
|
|
3311
|
-
const currentDraft = this.state.noteDraft ?? draft;
|
|
3312
|
-
if (currentDraft.adjustment?.isActive !== true) return;
|
|
3313
|
-
const keyDelta = this.getAdjustmentKeyDelta(event);
|
|
3314
|
-
if (!keyDelta) return;
|
|
3315
|
-
event.preventDefault();
|
|
3316
|
-
event.stopPropagation();
|
|
3317
|
-
updateDraft((activeDraft) => ({
|
|
3318
|
-
...activeDraft,
|
|
3319
|
-
adjustment: {
|
|
3320
|
-
x: (activeDraft.adjustment?.x ?? 0) + keyDelta.x,
|
|
3321
|
-
y: (activeDraft.adjustment?.y ?? 0) + keyDelta.y,
|
|
3322
|
-
scale: (activeDraft.adjustment?.scale ?? 0) + keyDelta.scale,
|
|
3323
|
-
isActive: true
|
|
3324
|
-
}
|
|
3325
|
-
}));
|
|
3326
|
-
});
|
|
3327
|
-
header.append(help);
|
|
3328
|
-
if (!dockToggle) {
|
|
3329
|
-
header.append(adjust);
|
|
3243
|
+
if (!showCompactMarkers && !isHighlighted) {
|
|
3244
|
+
return;
|
|
3330
3245
|
}
|
|
3331
|
-
|
|
3332
|
-
|
|
3333
|
-
|
|
3334
|
-
panel,
|
|
3335
|
-
focusTarget: adjust,
|
|
3336
|
-
actionButton: dockToggle ? adjust : void 0
|
|
3337
|
-
};
|
|
3338
|
-
}
|
|
3339
|
-
getAdjustmentKeyDelta(event) {
|
|
3340
|
-
const step = event.shiftKey ? 10 : 1;
|
|
3341
|
-
if (event.key === "ArrowLeft") return { x: -step, y: 0, scale: 0 };
|
|
3342
|
-
if (event.key === "ArrowRight") return { x: step, y: 0, scale: 0 };
|
|
3343
|
-
if (event.key === "ArrowUp") return { x: 0, y: -step, scale: 0 };
|
|
3344
|
-
if (event.key === "ArrowDown") return { x: 0, y: step, scale: 0 };
|
|
3345
|
-
if (event.key.toLowerCase() === "w") return { x: 0, y: 0, scale: step };
|
|
3346
|
-
if (event.key.toLowerCase() === "s") return { x: 0, y: 0, scale: -step };
|
|
3347
|
-
return void 0;
|
|
3348
|
-
}
|
|
3349
|
-
syncDraftAdjustmentUi({
|
|
3350
|
-
draft,
|
|
3351
|
-
pin,
|
|
3352
|
-
selectionHighlight
|
|
3353
|
-
}) {
|
|
3354
|
-
const environment = this.config.getEnvironment();
|
|
3355
|
-
if (!environment) return;
|
|
3356
|
-
const hostPoint = toHostPoint(
|
|
3357
|
-
this.getAdjustedDraftPoint(draft.marker.viewport, draft),
|
|
3358
|
-
environment
|
|
3359
|
-
);
|
|
3360
|
-
pin.style.left = `${hostPoint.x}px`;
|
|
3361
|
-
pin.style.top = `${hostPoint.y}px`;
|
|
3362
|
-
if (draft.selection && selectionHighlight) {
|
|
3363
|
-
const rect = toHostSelection(
|
|
3364
|
-
this.getAdjustedDraftSelection(
|
|
3365
|
-
toViewportSelection(draft.selection.viewport),
|
|
3366
|
-
draft
|
|
3367
|
-
),
|
|
3368
|
-
environment
|
|
3369
|
-
);
|
|
3370
|
-
selectionHighlight.style.left = `${rect.left}px`;
|
|
3371
|
-
selectionHighlight.style.top = `${rect.top}px`;
|
|
3372
|
-
selectionHighlight.style.width = `${rect.width}px`;
|
|
3373
|
-
selectionHighlight.style.height = `${rect.height}px`;
|
|
3246
|
+
const point = getBoundMarkerPoint(item, environment);
|
|
3247
|
+
if (!point || !isPointInViewport(point.viewport, environment)) {
|
|
3248
|
+
return;
|
|
3374
3249
|
}
|
|
3375
|
-
|
|
3250
|
+
const hostPoint = toHostPoint(point.viewport, environment);
|
|
3251
|
+
const marker = createMarkerElement(
|
|
3252
|
+
item.id,
|
|
3253
|
+
hostPoint,
|
|
3254
|
+
displayLabel,
|
|
3255
|
+
scope,
|
|
3256
|
+
point.isBound,
|
|
3257
|
+
isHighlighted
|
|
3258
|
+
);
|
|
3259
|
+
marker.title = `${displayLabel} / ${item.comment}
|
|
3260
|
+
${formatItemMeta(item)}`;
|
|
3261
|
+
layer.append(marker);
|
|
3262
|
+
});
|
|
3263
|
+
return layer;
|
|
3264
|
+
}
|
|
3265
|
+
|
|
3266
|
+
// src/core/view/area.draft.ts
|
|
3267
|
+
function createAreaForm(context) {
|
|
3268
|
+
const { config } = context;
|
|
3269
|
+
const form = document.createElement("form");
|
|
3270
|
+
form.className = "dfwr-form";
|
|
3271
|
+
const areaDraft = config.getState().areaDraft;
|
|
3272
|
+
if (!areaDraft) {
|
|
3273
|
+
const empty = document.createElement("p");
|
|
3274
|
+
empty.className = "dfwr-empty";
|
|
3275
|
+
empty.textContent = "Drag on the page to select an area.";
|
|
3276
|
+
form.append(empty);
|
|
3277
|
+
return form;
|
|
3376
3278
|
}
|
|
3377
|
-
|
|
3378
|
-
|
|
3379
|
-
|
|
3380
|
-
|
|
3381
|
-
|
|
3382
|
-
|
|
3383
|
-
|
|
3384
|
-
|
|
3385
|
-
|
|
3386
|
-
|
|
3387
|
-
|
|
3388
|
-
|
|
3389
|
-
|
|
3390
|
-
|
|
3391
|
-
|
|
3392
|
-
|
|
3279
|
+
form.append(createAreaMetricsPanel(context, areaDraft));
|
|
3280
|
+
const titleInput = isTitleFieldEnabled(config.options) ? createDraftTitleInput(areaDraft.title, (title) => {
|
|
3281
|
+
const draft = config.getState().areaDraft;
|
|
3282
|
+
if (!draft) return;
|
|
3283
|
+
config.actions.setAreaDraft({
|
|
3284
|
+
...draft,
|
|
3285
|
+
title
|
|
3286
|
+
});
|
|
3287
|
+
}) : void 0;
|
|
3288
|
+
const textarea = document.createElement("textarea");
|
|
3289
|
+
textarea.className = "dfwr-textarea";
|
|
3290
|
+
textarea.placeholder = "Area comment";
|
|
3291
|
+
textarea.rows = 4;
|
|
3292
|
+
textarea.value = areaDraft.comment ?? "";
|
|
3293
|
+
textarea.addEventListener("input", () => {
|
|
3294
|
+
const draft = config.getState().areaDraft;
|
|
3295
|
+
if (!draft) return;
|
|
3296
|
+
config.actions.setAreaDraft({
|
|
3297
|
+
...draft,
|
|
3298
|
+
comment: textarea.value
|
|
3299
|
+
});
|
|
3300
|
+
});
|
|
3301
|
+
attachDraftImagePasteQueue(textarea, {
|
|
3302
|
+
getAttachments: () => config.getState().areaDraft?.attachments ?? areaDraft.attachments,
|
|
3303
|
+
onAttachmentsChange: (attachments) => {
|
|
3304
|
+
const draft = config.getState().areaDraft ?? areaDraft;
|
|
3305
|
+
config.actions.setAreaDraft({
|
|
3306
|
+
...draft,
|
|
3307
|
+
comment: textarea.value,
|
|
3308
|
+
attachments
|
|
3309
|
+
});
|
|
3310
|
+
},
|
|
3311
|
+
onCommentChange: (comment) => {
|
|
3312
|
+
const draft = config.getState().areaDraft ?? areaDraft;
|
|
3313
|
+
config.actions.setAreaDraft({
|
|
3393
3314
|
...draft,
|
|
3394
|
-
|
|
3315
|
+
comment
|
|
3395
3316
|
});
|
|
3396
|
-
}
|
|
3397
|
-
|
|
3398
|
-
|
|
3399
|
-
|
|
3400
|
-
|
|
3401
|
-
|
|
3402
|
-
|
|
3403
|
-
|
|
3317
|
+
},
|
|
3318
|
+
onPasteComplete: () => config.actions.render()
|
|
3319
|
+
});
|
|
3320
|
+
const assigneeSelect = createDraftAssigneeSelect(
|
|
3321
|
+
config.options,
|
|
3322
|
+
areaDraft.assigneeId,
|
|
3323
|
+
areaDraft.assigneeName,
|
|
3324
|
+
(assigneeId, assigneeName) => {
|
|
3325
|
+
const draft = config.getState().areaDraft;
|
|
3404
3326
|
if (!draft) return;
|
|
3405
|
-
|
|
3327
|
+
config.actions.setAreaDraft({
|
|
3406
3328
|
...draft,
|
|
3407
|
-
|
|
3329
|
+
assigneeId,
|
|
3330
|
+
assigneeName
|
|
3408
3331
|
});
|
|
3409
|
-
}
|
|
3410
|
-
|
|
3411
|
-
|
|
3412
|
-
|
|
3413
|
-
|
|
3414
|
-
|
|
3415
|
-
|
|
3416
|
-
|
|
3417
|
-
|
|
3418
|
-
|
|
3419
|
-
|
|
3420
|
-
|
|
3421
|
-
|
|
3422
|
-
|
|
3423
|
-
const actions = this.createFormActions("Save area", () => {
|
|
3424
|
-
const draft = this.state.areaDraft;
|
|
3425
|
-
const fields = this.getDraftFields(titleInput, textarea, assigneeSelect);
|
|
3332
|
+
}
|
|
3333
|
+
);
|
|
3334
|
+
const actions = createFormActions({
|
|
3335
|
+
saveLabel: "Save area",
|
|
3336
|
+
isSaving: config.getState().isCreatingItem,
|
|
3337
|
+
onCancel: context.cancelDraft,
|
|
3338
|
+
onSave: () => {
|
|
3339
|
+
const draft = config.getState().areaDraft;
|
|
3340
|
+
const fields = getDraftFields(
|
|
3341
|
+
config.options,
|
|
3342
|
+
titleInput,
|
|
3343
|
+
textarea,
|
|
3344
|
+
assigneeSelect
|
|
3345
|
+
);
|
|
3426
3346
|
const comment = fields.comment;
|
|
3427
|
-
if (!comment || !draft) return;
|
|
3428
|
-
void
|
|
3347
|
+
if (!comment && !draft?.attachments?.length || !draft) return;
|
|
3348
|
+
void config.actions.createItem({
|
|
3429
3349
|
kind: "area",
|
|
3430
3350
|
title: fields.title,
|
|
3431
3351
|
comment,
|
|
@@ -3434,603 +3354,1077 @@ ${adjustment}` : adjustment;
|
|
|
3434
3354
|
viewport: draft.viewport,
|
|
3435
3355
|
anchor: draft.anchor,
|
|
3436
3356
|
marker: draft.marker,
|
|
3437
|
-
selection: draft.selection
|
|
3357
|
+
selection: draft.selection,
|
|
3358
|
+
attachments: draft.attachments
|
|
3438
3359
|
});
|
|
3439
|
-
}
|
|
3440
|
-
|
|
3441
|
-
|
|
3442
|
-
|
|
3443
|
-
|
|
3444
|
-
|
|
3445
|
-
|
|
3446
|
-
|
|
3447
|
-
|
|
3448
|
-
|
|
3449
|
-
|
|
3450
|
-
|
|
3451
|
-
|
|
3452
|
-
|
|
3453
|
-
|
|
3454
|
-
|
|
3455
|
-
|
|
3456
|
-
this.getAreaDraftMetricSelection(draft),
|
|
3457
|
-
draft.viewport
|
|
3458
|
-
);
|
|
3459
|
-
help.textContent = labelLine;
|
|
3460
|
-
const xyStatus = document.createElement("div");
|
|
3461
|
-
xyStatus.className = "dfwr-adjust-status";
|
|
3462
|
-
xyStatus.textContent = xyLine;
|
|
3463
|
-
const sizeStatus = document.createElement("div");
|
|
3464
|
-
sizeStatus.className = "dfwr-adjust-status";
|
|
3465
|
-
sizeStatus.textContent = sizeLine;
|
|
3466
|
-
panel.append(help, xyStatus, sizeStatus);
|
|
3467
|
-
return panel;
|
|
3468
|
-
}
|
|
3469
|
-
createAreaDraftOverlay(draft) {
|
|
3470
|
-
const layer = document.createElement("div");
|
|
3471
|
-
layer.className = "dfwr-area-preview-layer";
|
|
3472
|
-
const environment = this.config.getEnvironment();
|
|
3473
|
-
if (!environment || !draft.selection) return layer;
|
|
3474
|
-
const selection = toViewportSelection(draft.selection.viewport);
|
|
3475
|
-
layer.append(this.createSelectionHighlight(selection, environment, true));
|
|
3476
|
-
if (draft.marker) {
|
|
3477
|
-
const hostPoint = toHostPoint(draft.marker.viewport, environment);
|
|
3478
|
-
layer.append(
|
|
3479
|
-
this.createMarkerElement(
|
|
3480
|
-
void 0,
|
|
3481
|
-
hostPoint,
|
|
3482
|
-
"\u2022",
|
|
3483
|
-
getReviewViewportScope(
|
|
3484
|
-
draft.viewport,
|
|
3485
|
-
this.config.options.viewports?.presets
|
|
3486
|
-
),
|
|
3487
|
-
true,
|
|
3488
|
-
true
|
|
3489
|
-
)
|
|
3360
|
+
},
|
|
3361
|
+
leading: [
|
|
3362
|
+
createDraftCaptureButton(config, areaDraft, {
|
|
3363
|
+
kind: "area",
|
|
3364
|
+
textarea
|
|
3365
|
+
})
|
|
3366
|
+
]
|
|
3367
|
+
});
|
|
3368
|
+
const error = createDraftError(config.getState().draftError);
|
|
3369
|
+
const attachmentQueue = createDraftAttachmentQueue(
|
|
3370
|
+
document,
|
|
3371
|
+
areaDraft.attachments,
|
|
3372
|
+
(attachmentId) => {
|
|
3373
|
+
const draft = config.getState().areaDraft ?? areaDraft;
|
|
3374
|
+
const attachments = removeDraftAttachment(
|
|
3375
|
+
draft.attachments,
|
|
3376
|
+
attachmentId
|
|
3490
3377
|
);
|
|
3378
|
+
config.actions.setAreaDraft({
|
|
3379
|
+
...draft,
|
|
3380
|
+
comment: textarea.value,
|
|
3381
|
+
attachments: attachments.length > 0 ? attachments : void 0
|
|
3382
|
+
});
|
|
3383
|
+
config.actions.render();
|
|
3491
3384
|
}
|
|
3492
|
-
|
|
3385
|
+
);
|
|
3386
|
+
form.append(
|
|
3387
|
+
...titleInput ? [titleInput] : [],
|
|
3388
|
+
textarea,
|
|
3389
|
+
...attachmentQueue ? [attachmentQueue] : [],
|
|
3390
|
+
...assigneeSelect ? [assigneeSelect] : [],
|
|
3391
|
+
...error ? [error] : [],
|
|
3392
|
+
actions
|
|
3393
|
+
);
|
|
3394
|
+
return form;
|
|
3395
|
+
}
|
|
3396
|
+
function createAreaMetricsPanel(context, draft) {
|
|
3397
|
+
const panel = document.createElement("div");
|
|
3398
|
+
panel.className = "dfwr-adjust-panel is-area-metrics-panel";
|
|
3399
|
+
const [labelLine, xyLine, sizeLine] = getSelectionMetricLines(
|
|
3400
|
+
getAreaDraftMetricSelection(draft),
|
|
3401
|
+
draft.viewport,
|
|
3402
|
+
context.config.options.viewports?.presets
|
|
3403
|
+
);
|
|
3404
|
+
const help = document.createElement("div");
|
|
3405
|
+
help.className = "dfwr-adjust-help";
|
|
3406
|
+
help.textContent = labelLine;
|
|
3407
|
+
const xyStatus = document.createElement("div");
|
|
3408
|
+
xyStatus.className = "dfwr-adjust-status";
|
|
3409
|
+
xyStatus.textContent = xyLine;
|
|
3410
|
+
const sizeStatus = document.createElement("div");
|
|
3411
|
+
sizeStatus.className = "dfwr-adjust-status";
|
|
3412
|
+
sizeStatus.textContent = sizeLine;
|
|
3413
|
+
panel.append(help, xyStatus, sizeStatus);
|
|
3414
|
+
return panel;
|
|
3415
|
+
}
|
|
3416
|
+
function createAreaDraftOverlay(context, draft) {
|
|
3417
|
+
const { config } = context;
|
|
3418
|
+
const layer = document.createElement("div");
|
|
3419
|
+
layer.className = "dfwr-area-preview-layer";
|
|
3420
|
+
const environment = config.getEnvironment();
|
|
3421
|
+
if (!environment || !draft.selection) return layer;
|
|
3422
|
+
const selection = toViewportSelection(draft.selection.viewport);
|
|
3423
|
+
layer.append(createSelectionHighlight(selection, environment, true));
|
|
3424
|
+
if (draft.marker) {
|
|
3425
|
+
const hostPoint = toHostPoint(draft.marker.viewport, environment);
|
|
3426
|
+
layer.append(
|
|
3427
|
+
createMarkerElement(
|
|
3428
|
+
void 0,
|
|
3429
|
+
hostPoint,
|
|
3430
|
+
"\u2022",
|
|
3431
|
+
getReviewViewportScope(
|
|
3432
|
+
draft.viewport,
|
|
3433
|
+
config.options.viewports?.presets
|
|
3434
|
+
),
|
|
3435
|
+
true,
|
|
3436
|
+
true
|
|
3437
|
+
)
|
|
3438
|
+
);
|
|
3493
3439
|
}
|
|
3494
|
-
|
|
3495
|
-
|
|
3496
|
-
|
|
3497
|
-
|
|
3498
|
-
|
|
3499
|
-
|
|
3500
|
-
|
|
3501
|
-
|
|
3502
|
-
|
|
3503
|
-
|
|
3504
|
-
|
|
3505
|
-
|
|
3506
|
-
|
|
3507
|
-
|
|
3508
|
-
|
|
3509
|
-
|
|
3510
|
-
|
|
3511
|
-
environment,
|
|
3512
|
-
composerPosition: draft.composerPosition,
|
|
3513
|
-
estimatedHeight: 220
|
|
3514
|
-
});
|
|
3515
|
-
popover.style.left = `${composer.left}px`;
|
|
3516
|
-
popover.style.top = `${composer.top}px`;
|
|
3517
|
-
popover.style.width = `${composer.width}px`;
|
|
3518
|
-
popover.style.right = "auto";
|
|
3519
|
-
}
|
|
3520
|
-
const dragHandle = options.dockComposer ? void 0 : this.createDraftDragHandle("Move area composer");
|
|
3521
|
-
popover.append(
|
|
3522
|
-
...dragHandle ? [dragHandle] : [],
|
|
3523
|
-
this.createAreaForm()
|
|
3440
|
+
return layer;
|
|
3441
|
+
}
|
|
3442
|
+
function createAreaDraftPopover(context, draft, options = {}) {
|
|
3443
|
+
const { config } = context;
|
|
3444
|
+
const environment = config.getEnvironment();
|
|
3445
|
+
const popover = document.createElement("div");
|
|
3446
|
+
popover.className = [
|
|
3447
|
+
"dfwr-area-draft",
|
|
3448
|
+
"is-composer",
|
|
3449
|
+
options.dockComposer ? "is-docked-composer" : ""
|
|
3450
|
+
].filter(Boolean).join(" ");
|
|
3451
|
+
if (options.dockComposer) {
|
|
3452
|
+
popover.style.width = "100%";
|
|
3453
|
+
} else if (environment && draft.selection) {
|
|
3454
|
+
const selection = toHostSelection(
|
|
3455
|
+
toViewportSelection(draft.selection.viewport),
|
|
3456
|
+
environment
|
|
3524
3457
|
);
|
|
3525
|
-
|
|
3526
|
-
|
|
3527
|
-
|
|
3528
|
-
|
|
3458
|
+
const composer = getDraftComposerPosition({
|
|
3459
|
+
selection,
|
|
3460
|
+
environment,
|
|
3461
|
+
composerPosition: draft.composerPosition,
|
|
3462
|
+
estimatedHeight: 220
|
|
3463
|
+
});
|
|
3464
|
+
popover.style.left = `${composer.left}px`;
|
|
3465
|
+
popover.style.top = `${composer.top}px`;
|
|
3466
|
+
popover.style.width = `${composer.width}px`;
|
|
3467
|
+
popover.style.right = "auto";
|
|
3468
|
+
}
|
|
3469
|
+
const dragHandle = options.dockComposer ? void 0 : createDraftDragHandle("Move area composer");
|
|
3470
|
+
popover.append(
|
|
3471
|
+
...dragHandle ? [dragHandle] : [],
|
|
3472
|
+
createAreaForm(context)
|
|
3473
|
+
);
|
|
3474
|
+
if (dragHandle) {
|
|
3475
|
+
attachDraftComposerDrag({
|
|
3476
|
+
getEnvironment: () => config.getEnvironment(),
|
|
3477
|
+
popover,
|
|
3478
|
+
handle: dragHandle,
|
|
3479
|
+
onMove: (composerPosition) => {
|
|
3480
|
+
const areaDraft = config.getState().areaDraft ?? draft;
|
|
3481
|
+
config.actions.setAreaDraft({
|
|
3529
3482
|
...areaDraft,
|
|
3530
3483
|
composerPosition
|
|
3531
3484
|
});
|
|
3485
|
+
}
|
|
3486
|
+
});
|
|
3487
|
+
}
|
|
3488
|
+
return popover;
|
|
3489
|
+
}
|
|
3490
|
+
|
|
3491
|
+
// src/core/view/dom.draft.ts
|
|
3492
|
+
function createDomDraftLayer(context, draft, options = {}) {
|
|
3493
|
+
const { config } = context;
|
|
3494
|
+
const presets = config.options.viewports?.presets;
|
|
3495
|
+
const environment = config.getEnvironment();
|
|
3496
|
+
const group = document.createElement("div");
|
|
3497
|
+
group.className = "dfwr-dom-draft";
|
|
3498
|
+
if (!environment) return { layer: group, composer: void 0 };
|
|
3499
|
+
const isElementDraft = config.getState().mode === "element" && Boolean(draft.selection);
|
|
3500
|
+
const hostPoint = toHostPoint(
|
|
3501
|
+
isElementDraft ? getAdjustedDraftPoint(draft.marker.viewport, draft, presets) : draft.marker.viewport,
|
|
3502
|
+
environment
|
|
3503
|
+
);
|
|
3504
|
+
let selectionHighlight;
|
|
3505
|
+
if (draft.selection) {
|
|
3506
|
+
const selection = toViewportSelection(draft.selection.viewport);
|
|
3507
|
+
selectionHighlight = createSelectionHighlight(
|
|
3508
|
+
isElementDraft ? getAdjustedDraftSelection(selection, draft, presets) : selection,
|
|
3509
|
+
environment,
|
|
3510
|
+
true
|
|
3511
|
+
);
|
|
3512
|
+
group.append(selectionHighlight);
|
|
3513
|
+
}
|
|
3514
|
+
const pin = document.createElement("button");
|
|
3515
|
+
pin.className = "dfwr-dom-pin";
|
|
3516
|
+
pin.type = "button";
|
|
3517
|
+
pin.setAttribute("aria-label", "Move DOM point");
|
|
3518
|
+
pin.style.left = `${hostPoint.x}px`;
|
|
3519
|
+
pin.style.top = `${hostPoint.y}px`;
|
|
3520
|
+
const popover = document.createElement("div");
|
|
3521
|
+
const position = getPopoverPosition(hostPoint, environment);
|
|
3522
|
+
popover.className = [
|
|
3523
|
+
"dfwr-dom-popover",
|
|
3524
|
+
isElementDraft ? "is-composer" : "",
|
|
3525
|
+
options.dockComposer ? "is-docked-composer" : ""
|
|
3526
|
+
].filter(Boolean).join(" ");
|
|
3527
|
+
if (options.dockComposer) {
|
|
3528
|
+
popover.style.width = "100%";
|
|
3529
|
+
} else if (isElementDraft) {
|
|
3530
|
+
const selection = draft.selection ? toHostSelection(
|
|
3531
|
+
getAdjustedDraftSelection(
|
|
3532
|
+
toViewportSelection(draft.selection.viewport),
|
|
3533
|
+
draft,
|
|
3534
|
+
presets
|
|
3535
|
+
),
|
|
3536
|
+
environment
|
|
3537
|
+
) : void 0;
|
|
3538
|
+
const composer = getDraftComposerPosition({
|
|
3539
|
+
selection,
|
|
3540
|
+
environment,
|
|
3541
|
+
composerPosition: draft.composerPosition,
|
|
3542
|
+
estimatedHeight: 252
|
|
3543
|
+
});
|
|
3544
|
+
popover.style.left = `${composer.left}px`;
|
|
3545
|
+
popover.style.top = `${composer.top}px`;
|
|
3546
|
+
popover.style.width = `${composer.width}px`;
|
|
3547
|
+
} else {
|
|
3548
|
+
popover.style.left = `${position.left}px`;
|
|
3549
|
+
popover.style.top = `${position.top}px`;
|
|
3550
|
+
}
|
|
3551
|
+
const form = document.createElement("form");
|
|
3552
|
+
form.className = "dfwr-form";
|
|
3553
|
+
const meta = isElementDraft ? void 0 : document.createElement("div");
|
|
3554
|
+
if (meta) {
|
|
3555
|
+
meta.className = "dfwr-item-date";
|
|
3556
|
+
meta.textContent = formatDomDraftMeta(draft);
|
|
3557
|
+
}
|
|
3558
|
+
const titleInput = isTitleFieldEnabled(config.options) ? createDraftTitleInput(draft.title, (title) => {
|
|
3559
|
+
const domDraft = config.getState().domDraft;
|
|
3560
|
+
if (!domDraft) return;
|
|
3561
|
+
config.actions.setDomDraft({
|
|
3562
|
+
...domDraft,
|
|
3563
|
+
title
|
|
3564
|
+
});
|
|
3565
|
+
}) : void 0;
|
|
3566
|
+
const textarea = document.createElement("textarea");
|
|
3567
|
+
textarea.className = "dfwr-textarea";
|
|
3568
|
+
textarea.placeholder = "Review comment";
|
|
3569
|
+
textarea.rows = 4;
|
|
3570
|
+
textarea.value = draft.comment ?? "";
|
|
3571
|
+
textarea.addEventListener("input", () => {
|
|
3572
|
+
const domDraft = config.getState().domDraft;
|
|
3573
|
+
if (!domDraft) return;
|
|
3574
|
+
config.actions.setDomDraft({
|
|
3575
|
+
...domDraft,
|
|
3576
|
+
comment: textarea.value
|
|
3577
|
+
});
|
|
3578
|
+
});
|
|
3579
|
+
attachDraftImagePasteQueue(textarea, {
|
|
3580
|
+
getAttachments: () => config.getState().domDraft?.attachments ?? draft.attachments,
|
|
3581
|
+
onAttachmentsChange: (attachments) => {
|
|
3582
|
+
const domDraft = config.getState().domDraft ?? draft;
|
|
3583
|
+
config.actions.setDomDraft({
|
|
3584
|
+
...domDraft,
|
|
3585
|
+
comment: textarea.value,
|
|
3586
|
+
attachments
|
|
3587
|
+
});
|
|
3588
|
+
},
|
|
3589
|
+
onCommentChange: (comment) => {
|
|
3590
|
+
const domDraft = config.getState().domDraft ?? draft;
|
|
3591
|
+
config.actions.setDomDraft({
|
|
3592
|
+
...domDraft,
|
|
3593
|
+
comment
|
|
3594
|
+
});
|
|
3595
|
+
},
|
|
3596
|
+
onPasteComplete: () => config.actions.render()
|
|
3597
|
+
});
|
|
3598
|
+
const assigneeSelect = createDraftAssigneeSelect(
|
|
3599
|
+
config.options,
|
|
3600
|
+
draft.assigneeId,
|
|
3601
|
+
draft.assigneeName,
|
|
3602
|
+
(assigneeId, assigneeName) => {
|
|
3603
|
+
const domDraft = config.getState().domDraft;
|
|
3604
|
+
if (!domDraft) return;
|
|
3605
|
+
config.actions.setDomDraft({
|
|
3606
|
+
...domDraft,
|
|
3607
|
+
assigneeId,
|
|
3608
|
+
assigneeName
|
|
3532
3609
|
});
|
|
3533
3610
|
}
|
|
3534
|
-
|
|
3535
|
-
|
|
3536
|
-
|
|
3537
|
-
const
|
|
3538
|
-
|
|
3539
|
-
|
|
3540
|
-
|
|
3541
|
-
|
|
3542
|
-
|
|
3543
|
-
|
|
3544
|
-
|
|
3545
|
-
if (
|
|
3546
|
-
|
|
3547
|
-
}
|
|
3548
|
-
|
|
3549
|
-
|
|
3550
|
-
|
|
3551
|
-
|
|
3552
|
-
|
|
3553
|
-
|
|
3554
|
-
|
|
3611
|
+
);
|
|
3612
|
+
const saveDraft = () => {
|
|
3613
|
+
const currentDraft = config.getState().domDraft ?? draft;
|
|
3614
|
+
const fields = getDraftFields(
|
|
3615
|
+
config.options,
|
|
3616
|
+
titleInput,
|
|
3617
|
+
textarea,
|
|
3618
|
+
assigneeSelect
|
|
3619
|
+
);
|
|
3620
|
+
const comment = fields.comment;
|
|
3621
|
+
const hasAttachments = Boolean(currentDraft.attachments?.length);
|
|
3622
|
+
if (!comment && !hasDraftAdjustment(currentDraft, presets) && !hasAttachments) {
|
|
3623
|
+
return;
|
|
3624
|
+
}
|
|
3625
|
+
void config.actions.createItem({
|
|
3626
|
+
kind: "dom",
|
|
3627
|
+
title: fields.title,
|
|
3628
|
+
comment: withDraftAdjustmentComment(
|
|
3629
|
+
comment,
|
|
3630
|
+
currentDraft,
|
|
3631
|
+
config.options
|
|
3632
|
+
),
|
|
3633
|
+
assigneeId: fields.assigneeId,
|
|
3634
|
+
assigneeName: fields.assigneeName,
|
|
3635
|
+
viewport: currentDraft.viewport,
|
|
3636
|
+
anchor: currentDraft.anchor,
|
|
3637
|
+
marker: currentDraft.marker,
|
|
3638
|
+
selection: currentDraft.selection,
|
|
3639
|
+
attachments: currentDraft.attachments
|
|
3555
3640
|
});
|
|
3556
|
-
|
|
3557
|
-
|
|
3558
|
-
|
|
3559
|
-
|
|
3560
|
-
|
|
3561
|
-
|
|
3562
|
-
|
|
3641
|
+
};
|
|
3642
|
+
const adjustmentControls = isElementDraft ? createAdjustmentControls(context, {
|
|
3643
|
+
draft,
|
|
3644
|
+
pin,
|
|
3645
|
+
popover,
|
|
3646
|
+
selectionHighlight,
|
|
3647
|
+
textarea,
|
|
3648
|
+
dockToggle: options.dockComposer
|
|
3649
|
+
}) : void 0;
|
|
3650
|
+
const leadingActions = [
|
|
3651
|
+
adjustmentControls?.actionButton,
|
|
3652
|
+
createDraftCaptureButton(config, draft, {
|
|
3653
|
+
kind: "dom",
|
|
3654
|
+
isElementDraft,
|
|
3655
|
+
textarea
|
|
3656
|
+
})
|
|
3657
|
+
].filter((element) => Boolean(element));
|
|
3658
|
+
const actions = createFormActions({
|
|
3659
|
+
saveLabel: "Save DOM QA",
|
|
3660
|
+
isSaving: config.getState().isCreatingItem,
|
|
3661
|
+
onSave: saveDraft,
|
|
3662
|
+
onCancel: context.cancelDraft,
|
|
3663
|
+
leading: leadingActions.length > 0 ? leadingActions : void 0
|
|
3664
|
+
});
|
|
3665
|
+
const error = createDraftError(config.getState().draftError);
|
|
3666
|
+
const attachmentQueue = createDraftAttachmentQueue(
|
|
3667
|
+
document,
|
|
3668
|
+
draft.attachments,
|
|
3669
|
+
(attachmentId) => {
|
|
3670
|
+
const domDraft = config.getState().domDraft ?? draft;
|
|
3671
|
+
const attachments = removeDraftAttachment(
|
|
3672
|
+
domDraft.attachments,
|
|
3673
|
+
attachmentId
|
|
3674
|
+
);
|
|
3675
|
+
config.actions.setDomDraft({
|
|
3676
|
+
...domDraft,
|
|
3677
|
+
comment: textarea.value,
|
|
3678
|
+
attachments: attachments.length > 0 ? attachments : void 0
|
|
3679
|
+
});
|
|
3680
|
+
config.actions.render();
|
|
3681
|
+
}
|
|
3682
|
+
);
|
|
3683
|
+
form.append(
|
|
3684
|
+
...meta ? [meta] : [],
|
|
3685
|
+
...adjustmentControls ? [adjustmentControls.panel] : [],
|
|
3686
|
+
...titleInput ? [titleInput] : [],
|
|
3687
|
+
textarea,
|
|
3688
|
+
...attachmentQueue ? [attachmentQueue] : [],
|
|
3689
|
+
...assigneeSelect ? [assigneeSelect] : [],
|
|
3690
|
+
...error ? [error] : [],
|
|
3691
|
+
actions
|
|
3692
|
+
);
|
|
3693
|
+
const dragHandle = isElementDraft && !options.dockComposer ? createDraftDragHandle("Move DOM composer") : void 0;
|
|
3694
|
+
popover.append(...dragHandle ? [dragHandle] : [], form);
|
|
3695
|
+
group.append(pin);
|
|
3696
|
+
if (!options.dockComposer) {
|
|
3697
|
+
group.append(popover);
|
|
3698
|
+
}
|
|
3699
|
+
if (dragHandle) {
|
|
3700
|
+
attachDraftComposerDrag({
|
|
3701
|
+
getEnvironment: () => config.getEnvironment(),
|
|
3702
|
+
popover,
|
|
3703
|
+
handle: dragHandle,
|
|
3704
|
+
onMove: (composerPosition) => {
|
|
3705
|
+
const domDraft = config.getState().domDraft ?? draft;
|
|
3706
|
+
config.actions.setDomDraft({
|
|
3707
|
+
...domDraft,
|
|
3708
|
+
composerPosition,
|
|
3709
|
+
comment: textarea.value
|
|
3710
|
+
});
|
|
3711
|
+
}
|
|
3563
3712
|
});
|
|
3564
|
-
if (options?.leading?.length) {
|
|
3565
|
-
actions.classList.add("has-leading");
|
|
3566
|
-
const leading = document.createElement("div");
|
|
3567
|
-
leading.className = "dfwr-actions-leading";
|
|
3568
|
-
leading.append(...options.leading);
|
|
3569
|
-
const primary = document.createElement("div");
|
|
3570
|
-
primary.className = "dfwr-actions-primary";
|
|
3571
|
-
primary.append(save, cancel);
|
|
3572
|
-
actions.append(leading, primary);
|
|
3573
|
-
return actions;
|
|
3574
|
-
}
|
|
3575
|
-
if (options?.beforeSave?.length || options?.className) {
|
|
3576
|
-
actions.append(cancel, ...options.beforeSave ?? [], save);
|
|
3577
|
-
return actions;
|
|
3578
|
-
}
|
|
3579
|
-
actions.append(save, cancel);
|
|
3580
|
-
return actions;
|
|
3581
3713
|
}
|
|
3582
|
-
|
|
3583
|
-
|
|
3584
|
-
|
|
3585
|
-
|
|
3586
|
-
|
|
3587
|
-
}
|
|
3588
|
-
|
|
3589
|
-
|
|
3590
|
-
|
|
3591
|
-
|
|
3592
|
-
|
|
3593
|
-
|
|
3594
|
-
|
|
3595
|
-
|
|
3596
|
-
createList() {
|
|
3597
|
-
const section = document.createElement("div");
|
|
3598
|
-
section.className = "dfwr-list";
|
|
3599
|
-
const state = this.state;
|
|
3600
|
-
const heading = document.createElement("div");
|
|
3601
|
-
heading.className = "dfwr-list-heading";
|
|
3602
|
-
heading.textContent = `Review items (${state.items.length})`;
|
|
3603
|
-
section.append(heading);
|
|
3604
|
-
if (state.items.length === 0) {
|
|
3605
|
-
const empty = document.createElement("p");
|
|
3606
|
-
empty.className = "dfwr-empty";
|
|
3607
|
-
empty.textContent = "No review items on this page.";
|
|
3608
|
-
section.append(empty);
|
|
3609
|
-
return section;
|
|
3610
|
-
}
|
|
3611
|
-
for (const numberedItem of getNumberedReviewItems(
|
|
3612
|
-
state.items,
|
|
3613
|
-
this.config.options.viewports?.presets
|
|
3614
|
-
)) {
|
|
3615
|
-
section.append(this.createListItem(numberedItem));
|
|
3616
|
-
}
|
|
3617
|
-
return section;
|
|
3714
|
+
attachDraftPinDrag(context, {
|
|
3715
|
+
pin,
|
|
3716
|
+
popover: isElementDraft || options.dockComposer ? void 0 : popover,
|
|
3717
|
+
meta,
|
|
3718
|
+
textarea
|
|
3719
|
+
});
|
|
3720
|
+
if (!options.dockComposer) {
|
|
3721
|
+
window.setTimeout(() => {
|
|
3722
|
+
if (draft.adjustment?.isActive) {
|
|
3723
|
+
adjustmentControls?.focusTarget.focus();
|
|
3724
|
+
return;
|
|
3725
|
+
}
|
|
3726
|
+
textarea.focus();
|
|
3727
|
+
}, 0);
|
|
3618
3728
|
}
|
|
3619
|
-
|
|
3620
|
-
|
|
3621
|
-
|
|
3622
|
-
|
|
3623
|
-
|
|
3624
|
-
|
|
3625
|
-
|
|
3729
|
+
return {
|
|
3730
|
+
layer: group,
|
|
3731
|
+
composer: options.dockComposer ? popover : void 0
|
|
3732
|
+
};
|
|
3733
|
+
}
|
|
3734
|
+
function createAdjustmentControls(context, {
|
|
3735
|
+
draft,
|
|
3736
|
+
pin,
|
|
3737
|
+
popover,
|
|
3738
|
+
selectionHighlight,
|
|
3739
|
+
textarea,
|
|
3740
|
+
dockToggle
|
|
3741
|
+
}) {
|
|
3742
|
+
const { config } = context;
|
|
3743
|
+
const presets = config.options.viewports?.presets;
|
|
3744
|
+
const panel = document.createElement("div");
|
|
3745
|
+
panel.className = "dfwr-adjust-panel is-dom-adjust-panel";
|
|
3746
|
+
const header = document.createElement("div");
|
|
3747
|
+
header.className = "dfwr-adjust-panel-header";
|
|
3748
|
+
const help = document.createElement("div");
|
|
3749
|
+
help.className = "dfwr-adjust-help";
|
|
3750
|
+
help.textContent = getAdjustmentLabel(config.options);
|
|
3751
|
+
const adjust = document.createElement("button");
|
|
3752
|
+
adjust.className = "dfwr-adjust-toggle";
|
|
3753
|
+
adjust.type = "button";
|
|
3754
|
+
adjust.title = "Adjust DOM element with keyboard arrows";
|
|
3755
|
+
adjust.setAttribute("aria-label", "Adjust DOM element with keyboard arrows");
|
|
3756
|
+
const xyStatus = document.createElement("div");
|
|
3757
|
+
xyStatus.className = "dfwr-adjust-status";
|
|
3758
|
+
const scaleStatus = document.createElement("div");
|
|
3759
|
+
scaleStatus.className = "dfwr-adjust-status";
|
|
3760
|
+
const syncControls = (nextDraft) => {
|
|
3761
|
+
const isActive = nextDraft.adjustment?.isActive === true;
|
|
3762
|
+
panel.classList.toggle("is-active", isActive);
|
|
3763
|
+
adjust.classList.toggle("is-active", isActive);
|
|
3764
|
+
adjust.setAttribute("aria-pressed", isActive ? "true" : "false");
|
|
3765
|
+
setAdjustmentToggleIcon(adjust, isActive);
|
|
3766
|
+
adjust.title = isActive ? "Finish DOM adjustment" : "Adjust DOM element with keyboard arrows";
|
|
3767
|
+
adjust.setAttribute(
|
|
3626
3768
|
"aria-label",
|
|
3627
|
-
|
|
3769
|
+
isActive ? "Finish DOM adjustment" : "Adjust DOM element with keyboard arrows"
|
|
3628
3770
|
);
|
|
3629
|
-
|
|
3630
|
-
|
|
3771
|
+
const [xyLine, scaleLine] = getDraftAdjustmentMetricLines(
|
|
3772
|
+
nextDraft,
|
|
3773
|
+
presets
|
|
3774
|
+
);
|
|
3775
|
+
xyStatus.textContent = xyLine;
|
|
3776
|
+
scaleStatus.textContent = scaleLine;
|
|
3777
|
+
syncDraftAdjustmentUi(context, {
|
|
3778
|
+
draft: nextDraft,
|
|
3779
|
+
pin,
|
|
3780
|
+
selectionHighlight
|
|
3631
3781
|
});
|
|
3632
|
-
|
|
3633
|
-
|
|
3634
|
-
|
|
3635
|
-
|
|
3782
|
+
};
|
|
3783
|
+
const updateDraft = (updater) => {
|
|
3784
|
+
const currentDraft = config.getState().domDraft ?? draft;
|
|
3785
|
+
const nextDraft = updater(currentDraft);
|
|
3786
|
+
config.actions.setDomDraft({
|
|
3787
|
+
...nextDraft,
|
|
3788
|
+
comment: textarea.value
|
|
3636
3789
|
});
|
|
3637
|
-
|
|
3638
|
-
|
|
3639
|
-
|
|
3640
|
-
|
|
3641
|
-
|
|
3642
|
-
|
|
3643
|
-
|
|
3644
|
-
|
|
3645
|
-
|
|
3646
|
-
|
|
3647
|
-
|
|
3648
|
-
|
|
3649
|
-
|
|
3650
|
-
|
|
3651
|
-
|
|
3652
|
-
|
|
3653
|
-
|
|
3654
|
-
const
|
|
3655
|
-
|
|
3656
|
-
|
|
3657
|
-
|
|
3658
|
-
|
|
3659
|
-
|
|
3660
|
-
|
|
3661
|
-
|
|
3662
|
-
|
|
3663
|
-
|
|
3664
|
-
|
|
3665
|
-
|
|
3666
|
-
|
|
3667
|
-
|
|
3668
|
-
|
|
3669
|
-
|
|
3670
|
-
|
|
3671
|
-
|
|
3672
|
-
|
|
3673
|
-
|
|
3790
|
+
syncControls(nextDraft);
|
|
3791
|
+
};
|
|
3792
|
+
adjust.addEventListener("click", () => {
|
|
3793
|
+
updateDraft((currentDraft) => ({
|
|
3794
|
+
...currentDraft,
|
|
3795
|
+
adjustment: {
|
|
3796
|
+
x: currentDraft.adjustment?.x ?? 0,
|
|
3797
|
+
y: currentDraft.adjustment?.y ?? 0,
|
|
3798
|
+
scale: currentDraft.adjustment?.scale ?? 0,
|
|
3799
|
+
isActive: currentDraft.adjustment?.isActive !== true
|
|
3800
|
+
}
|
|
3801
|
+
}));
|
|
3802
|
+
adjust.focus();
|
|
3803
|
+
});
|
|
3804
|
+
popover.addEventListener("keydown", (event) => {
|
|
3805
|
+
const currentDraft = config.getState().domDraft ?? draft;
|
|
3806
|
+
if (currentDraft.adjustment?.isActive !== true) return;
|
|
3807
|
+
const keyDelta = getAdjustmentKeyDelta(event);
|
|
3808
|
+
if (!keyDelta) return;
|
|
3809
|
+
event.preventDefault();
|
|
3810
|
+
event.stopPropagation();
|
|
3811
|
+
updateDraft((activeDraft) => ({
|
|
3812
|
+
...activeDraft,
|
|
3813
|
+
adjustment: {
|
|
3814
|
+
x: (activeDraft.adjustment?.x ?? 0) + keyDelta.x,
|
|
3815
|
+
y: (activeDraft.adjustment?.y ?? 0) + keyDelta.y,
|
|
3816
|
+
scale: (activeDraft.adjustment?.scale ?? 0) + keyDelta.scale,
|
|
3817
|
+
isActive: true
|
|
3818
|
+
}
|
|
3819
|
+
}));
|
|
3820
|
+
});
|
|
3821
|
+
header.append(help);
|
|
3822
|
+
if (!dockToggle) {
|
|
3823
|
+
header.append(adjust);
|
|
3824
|
+
}
|
|
3825
|
+
panel.append(header, xyStatus, scaleStatus);
|
|
3826
|
+
syncControls(draft);
|
|
3827
|
+
return {
|
|
3828
|
+
panel,
|
|
3829
|
+
focusTarget: adjust,
|
|
3830
|
+
// 도킹 모드에서는 토글 버튼을 폼 액션 줄(leading)로 옮긴다.
|
|
3831
|
+
actionButton: dockToggle ? adjust : void 0
|
|
3832
|
+
};
|
|
3833
|
+
}
|
|
3834
|
+
function getAdjustmentKeyDelta(event) {
|
|
3835
|
+
const step = event.shiftKey ? 10 : 1;
|
|
3836
|
+
if (event.key === "ArrowLeft") return { x: -step, y: 0, scale: 0 };
|
|
3837
|
+
if (event.key === "ArrowRight") return { x: step, y: 0, scale: 0 };
|
|
3838
|
+
if (event.key === "ArrowUp") return { x: 0, y: -step, scale: 0 };
|
|
3839
|
+
if (event.key === "ArrowDown") return { x: 0, y: step, scale: 0 };
|
|
3840
|
+
if (event.key.toLowerCase() === "w") return { x: 0, y: 0, scale: step };
|
|
3841
|
+
if (event.key.toLowerCase() === "s") return { x: 0, y: 0, scale: -step };
|
|
3842
|
+
return void 0;
|
|
3843
|
+
}
|
|
3844
|
+
function syncDraftAdjustmentUi(context, {
|
|
3845
|
+
draft,
|
|
3846
|
+
pin,
|
|
3847
|
+
selectionHighlight
|
|
3848
|
+
}) {
|
|
3849
|
+
const { config } = context;
|
|
3850
|
+
const presets = config.options.viewports?.presets;
|
|
3851
|
+
const environment = config.getEnvironment();
|
|
3852
|
+
if (!environment) return;
|
|
3853
|
+
const hostPoint = toHostPoint(
|
|
3854
|
+
getAdjustedDraftPoint(draft.marker.viewport, draft, presets),
|
|
3855
|
+
environment
|
|
3856
|
+
);
|
|
3857
|
+
pin.style.left = `${hostPoint.x}px`;
|
|
3858
|
+
pin.style.top = `${hostPoint.y}px`;
|
|
3859
|
+
if (draft.selection && selectionHighlight) {
|
|
3860
|
+
const rect = toHostSelection(
|
|
3861
|
+
getAdjustedDraftSelection(
|
|
3862
|
+
toViewportSelection(draft.selection.viewport),
|
|
3863
|
+
draft,
|
|
3864
|
+
presets
|
|
3865
|
+
),
|
|
3866
|
+
environment
|
|
3867
|
+
);
|
|
3868
|
+
selectionHighlight.style.left = `${rect.left}px`;
|
|
3869
|
+
selectionHighlight.style.top = `${rect.top}px`;
|
|
3870
|
+
selectionHighlight.style.width = `${rect.width}px`;
|
|
3871
|
+
selectionHighlight.style.height = `${rect.height}px`;
|
|
3872
|
+
}
|
|
3873
|
+
context.syncDraftPreview(draft);
|
|
3874
|
+
}
|
|
3875
|
+
function attachDraftPinDrag(context, {
|
|
3876
|
+
pin,
|
|
3877
|
+
popover,
|
|
3878
|
+
meta,
|
|
3879
|
+
textarea
|
|
3880
|
+
}) {
|
|
3881
|
+
const { config } = context;
|
|
3882
|
+
let isDragging = false;
|
|
3883
|
+
const moveDraftUi = (hostPoint) => {
|
|
3884
|
+
const environment = config.getEnvironment();
|
|
3885
|
+
if (!environment) return;
|
|
3886
|
+
const nextPoint = clampPoint(
|
|
3887
|
+
toTargetPoint(hostPoint, environment),
|
|
3888
|
+
environment
|
|
3889
|
+
);
|
|
3890
|
+
const nextHostPoint = toHostPoint(nextPoint, environment);
|
|
3891
|
+
pin.style.left = `${nextHostPoint.x}px`;
|
|
3892
|
+
pin.style.top = `${nextHostPoint.y}px`;
|
|
3893
|
+
if (popover) {
|
|
3894
|
+
const position = getPopoverPosition(nextHostPoint, environment);
|
|
3895
|
+
popover.style.left = `${position.left}px`;
|
|
3896
|
+
popover.style.top = `${position.top}px`;
|
|
3897
|
+
}
|
|
3898
|
+
const domDraft = config.getState().domDraft;
|
|
3899
|
+
if (!domDraft) return;
|
|
3900
|
+
const nextDraft = {
|
|
3901
|
+
...domDraft,
|
|
3902
|
+
marker: {
|
|
3903
|
+
...domDraft.marker,
|
|
3904
|
+
viewport: roundPoint(nextPoint)
|
|
3905
|
+
},
|
|
3906
|
+
comment: textarea.value
|
|
3907
|
+
};
|
|
3908
|
+
config.actions.setDomDraft(nextDraft);
|
|
3909
|
+
if (meta) {
|
|
3910
|
+
meta.textContent = formatDomDraftMeta(nextDraft);
|
|
3911
|
+
}
|
|
3912
|
+
};
|
|
3913
|
+
pin.addEventListener("pointerdown", (event) => {
|
|
3914
|
+
if (event.button !== 0) return;
|
|
3915
|
+
event.preventDefault();
|
|
3916
|
+
event.stopPropagation();
|
|
3917
|
+
isDragging = true;
|
|
3918
|
+
pin.setPointerCapture(event.pointerId);
|
|
3919
|
+
});
|
|
3920
|
+
pin.addEventListener("pointermove", (event) => {
|
|
3921
|
+
if (!isDragging || !pin.hasPointerCapture(event.pointerId)) return;
|
|
3922
|
+
event.preventDefault();
|
|
3923
|
+
moveDraftUi({
|
|
3924
|
+
x: event.clientX,
|
|
3925
|
+
y: event.clientY
|
|
3674
3926
|
});
|
|
3675
|
-
|
|
3676
|
-
|
|
3677
|
-
return
|
|
3927
|
+
});
|
|
3928
|
+
pin.addEventListener("pointerup", (event) => {
|
|
3929
|
+
if (!isDragging || !pin.hasPointerCapture(event.pointerId)) return;
|
|
3930
|
+
event.preventDefault();
|
|
3931
|
+
event.stopPropagation();
|
|
3932
|
+
isDragging = false;
|
|
3933
|
+
pin.releasePointerCapture(event.pointerId);
|
|
3934
|
+
const nextPoint = toTargetPointFromHostEvent(
|
|
3935
|
+
event,
|
|
3936
|
+
config.getEnvironment()
|
|
3937
|
+
);
|
|
3938
|
+
const currentDraft = config.getState().domDraft;
|
|
3939
|
+
const fields = {
|
|
3940
|
+
title: currentDraft?.title,
|
|
3941
|
+
comment: textarea.value,
|
|
3942
|
+
assigneeId: currentDraft?.assigneeId,
|
|
3943
|
+
assigneeName: currentDraft?.assigneeName
|
|
3944
|
+
};
|
|
3945
|
+
void config.actions.bindElementDraftToPoint(nextPoint, fields);
|
|
3946
|
+
});
|
|
3947
|
+
}
|
|
3948
|
+
|
|
3949
|
+
// src/core/view/panel.ts
|
|
3950
|
+
function createReviewPanel(config, renderAreaForm) {
|
|
3951
|
+
const panel = document.createElement("div");
|
|
3952
|
+
panel.className = "dfwr-panel";
|
|
3953
|
+
panel.setAttribute("role", "dialog");
|
|
3954
|
+
panel.setAttribute("aria-label", "Web review kit");
|
|
3955
|
+
panel.append(
|
|
3956
|
+
createHeader(config),
|
|
3957
|
+
createToolbar(config),
|
|
3958
|
+
createBody(config, renderAreaForm),
|
|
3959
|
+
createList(config)
|
|
3960
|
+
);
|
|
3961
|
+
return panel;
|
|
3962
|
+
}
|
|
3963
|
+
function createHeader(config) {
|
|
3964
|
+
const header = document.createElement("div");
|
|
3965
|
+
header.className = "dfwr-header";
|
|
3966
|
+
const title = document.createElement("div");
|
|
3967
|
+
title.className = "dfwr-title";
|
|
3968
|
+
title.textContent = "Review Kit";
|
|
3969
|
+
const meta = document.createElement("div");
|
|
3970
|
+
meta.className = "dfwr-meta";
|
|
3971
|
+
meta.textContent = getRouteKey(config.getEnvironment());
|
|
3972
|
+
const titleGroup = document.createElement("div");
|
|
3973
|
+
titleGroup.append(title, meta);
|
|
3974
|
+
const close = document.createElement("button");
|
|
3975
|
+
close.className = "dfwr-icon-button";
|
|
3976
|
+
close.type = "button";
|
|
3977
|
+
close.textContent = "x";
|
|
3978
|
+
close.setAttribute("aria-label", "Close");
|
|
3979
|
+
close.addEventListener("click", () => config.actions.close());
|
|
3980
|
+
header.append(titleGroup, close);
|
|
3981
|
+
return header;
|
|
3982
|
+
}
|
|
3983
|
+
function createToolbar(config) {
|
|
3984
|
+
const state = config.getState();
|
|
3985
|
+
const toolbar = document.createElement("div");
|
|
3986
|
+
toolbar.className = "dfwr-toolbar";
|
|
3987
|
+
const toggleMode = (target) => {
|
|
3988
|
+
const mode = config.getState().mode;
|
|
3989
|
+
config.actions.setModeState(mode === target ? "idle" : target);
|
|
3990
|
+
config.actions.clearDrafts();
|
|
3991
|
+
config.actions.render();
|
|
3992
|
+
};
|
|
3993
|
+
toolbar.append(
|
|
3994
|
+
createToolbarButton(
|
|
3995
|
+
"Element",
|
|
3996
|
+
state.mode === "element",
|
|
3997
|
+
() => toggleMode("element")
|
|
3998
|
+
),
|
|
3999
|
+
createToolbarButton(
|
|
4000
|
+
state.isSelectingArea ? "Selecting" : "Area",
|
|
4001
|
+
state.mode === "area",
|
|
4002
|
+
() => toggleMode("area")
|
|
4003
|
+
),
|
|
4004
|
+
createToolbarButton("Refresh", false, () => {
|
|
4005
|
+
void config.actions.reload();
|
|
4006
|
+
})
|
|
4007
|
+
);
|
|
4008
|
+
return toolbar;
|
|
4009
|
+
}
|
|
4010
|
+
function createToolbarButton(label, active, onClick) {
|
|
4011
|
+
const button = document.createElement("button");
|
|
4012
|
+
button.className = `dfwr-button${active ? " is-active" : ""}`;
|
|
4013
|
+
button.type = "button";
|
|
4014
|
+
button.textContent = label;
|
|
4015
|
+
button.addEventListener("click", onClick);
|
|
4016
|
+
return button;
|
|
4017
|
+
}
|
|
4018
|
+
function createBody(config, renderAreaForm) {
|
|
4019
|
+
const body = document.createElement("div");
|
|
4020
|
+
body.className = "dfwr-body";
|
|
4021
|
+
const state = config.getState();
|
|
4022
|
+
if (state.mode === "idle") {
|
|
4023
|
+
body.append(
|
|
4024
|
+
createEmptyText("Select an element or mark an area.")
|
|
4025
|
+
);
|
|
4026
|
+
return body;
|
|
3678
4027
|
}
|
|
3679
|
-
|
|
3680
|
-
|
|
3681
|
-
|
|
3682
|
-
|
|
3683
|
-
|
|
3684
|
-
const currentScope = getReviewViewportScope(
|
|
3685
|
-
getViewportSize(environment),
|
|
3686
|
-
this.config.options.viewports?.presets
|
|
4028
|
+
if (state.mode === "element") {
|
|
4029
|
+
body.append(
|
|
4030
|
+
createEmptyText(
|
|
4031
|
+
state.domDraft ? "Write the QA in the page box." : "Click an element to add QA."
|
|
4032
|
+
)
|
|
3687
4033
|
);
|
|
3688
|
-
|
|
3689
|
-
|
|
3690
|
-
|
|
3691
|
-
|
|
3692
|
-
|
|
3693
|
-
|
|
3694
|
-
|
|
3695
|
-
|
|
3696
|
-
|
|
3697
|
-
|
|
3698
|
-
|
|
3699
|
-
|
|
3700
|
-
|
|
3701
|
-
|
|
3702
|
-
|
|
3703
|
-
|
|
3704
|
-
|
|
3705
|
-
|
|
3706
|
-
|
|
3707
|
-
|
|
3708
|
-
|
|
3709
|
-
|
|
3710
|
-
|
|
3711
|
-
|
|
3712
|
-
|
|
3713
|
-
|
|
3714
|
-
|
|
3715
|
-
|
|
3716
|
-
|
|
4034
|
+
return body;
|
|
4035
|
+
}
|
|
4036
|
+
body.append(renderAreaForm());
|
|
4037
|
+
return body;
|
|
4038
|
+
}
|
|
4039
|
+
function createEmptyText(text) {
|
|
4040
|
+
const empty = document.createElement("p");
|
|
4041
|
+
empty.className = "dfwr-empty";
|
|
4042
|
+
empty.textContent = text;
|
|
4043
|
+
return empty;
|
|
4044
|
+
}
|
|
4045
|
+
function createList(config) {
|
|
4046
|
+
const section = document.createElement("div");
|
|
4047
|
+
section.className = "dfwr-list";
|
|
4048
|
+
const state = config.getState();
|
|
4049
|
+
const heading = document.createElement("div");
|
|
4050
|
+
heading.className = "dfwr-list-heading";
|
|
4051
|
+
heading.textContent = `Review items (${state.items.length})`;
|
|
4052
|
+
section.append(heading);
|
|
4053
|
+
if (state.items.length === 0) {
|
|
4054
|
+
section.append(createEmptyText("No review items on this page."));
|
|
4055
|
+
return section;
|
|
4056
|
+
}
|
|
4057
|
+
for (const numberedItem of getNumberedReviewItems(
|
|
4058
|
+
state.items,
|
|
4059
|
+
config.options.viewports?.presets
|
|
4060
|
+
)) {
|
|
4061
|
+
section.append(createListItem(config, numberedItem));
|
|
4062
|
+
}
|
|
4063
|
+
return section;
|
|
4064
|
+
}
|
|
4065
|
+
function createListItem(config, numberedItem) {
|
|
4066
|
+
const { item } = numberedItem;
|
|
4067
|
+
const row = document.createElement("article");
|
|
4068
|
+
row.className = "dfwr-item";
|
|
4069
|
+
row.tabIndex = 0;
|
|
4070
|
+
row.setAttribute("role", "button");
|
|
4071
|
+
row.setAttribute(
|
|
4072
|
+
"aria-label",
|
|
4073
|
+
`Restore review item: ${item.title ?? item.comment}`
|
|
4074
|
+
);
|
|
4075
|
+
row.addEventListener("click", () => {
|
|
4076
|
+
void config.actions.restoreItem(item);
|
|
4077
|
+
});
|
|
4078
|
+
row.addEventListener("keydown", (event) => {
|
|
4079
|
+
if (event.key !== "Enter" && event.key !== " ") return;
|
|
4080
|
+
event.preventDefault();
|
|
4081
|
+
void config.actions.restoreItem(item);
|
|
4082
|
+
});
|
|
4083
|
+
const body = document.createElement("div");
|
|
4084
|
+
body.className = "dfwr-item-body";
|
|
4085
|
+
const badges = document.createElement("div");
|
|
4086
|
+
badges.className = "dfwr-item-badges";
|
|
4087
|
+
const scope = document.createElement("div");
|
|
4088
|
+
scope.className = `dfwr-item-scope is-scope-${numberedItem.scope}`;
|
|
4089
|
+
scope.textContent = numberedItem.displayLabel;
|
|
4090
|
+
const kind = document.createElement("div");
|
|
4091
|
+
kind.className = "dfwr-item-kind";
|
|
4092
|
+
kind.textContent = item.kind;
|
|
4093
|
+
badges.append(scope, kind);
|
|
4094
|
+
const title = isTitleFieldEnabled(config.options) ? item.title?.trim() : "";
|
|
4095
|
+
const titleElement = title ? document.createElement("strong") : void 0;
|
|
4096
|
+
if (title && titleElement) {
|
|
4097
|
+
titleElement.className = "dfwr-item-title";
|
|
4098
|
+
titleElement.textContent = title;
|
|
4099
|
+
}
|
|
4100
|
+
const comment = document.createElement("p");
|
|
4101
|
+
comment.className = `dfwr-item-comment${title ? "" : " is-primary"}`;
|
|
4102
|
+
comment.textContent = item.comment;
|
|
4103
|
+
const date = document.createElement("time");
|
|
4104
|
+
date.className = "dfwr-item-date";
|
|
4105
|
+
date.dateTime = item.createdAt;
|
|
4106
|
+
date.textContent = formatItemMeta(item);
|
|
4107
|
+
body.append(badges, ...titleElement ? [titleElement] : [], comment, date);
|
|
4108
|
+
const actions = document.createElement("div");
|
|
4109
|
+
actions.className = "dfwr-item-actions";
|
|
4110
|
+
actions.addEventListener("click", (event) => event.stopPropagation());
|
|
4111
|
+
actions.addEventListener("keydown", (event) => event.stopPropagation());
|
|
4112
|
+
const remove = document.createElement("button");
|
|
4113
|
+
remove.className = "dfwr-icon-button";
|
|
4114
|
+
remove.type = "button";
|
|
4115
|
+
remove.textContent = "x";
|
|
4116
|
+
remove.setAttribute("aria-label", "Delete");
|
|
4117
|
+
remove.addEventListener("click", (event) => {
|
|
4118
|
+
event.stopPropagation();
|
|
4119
|
+
void config.actions.removeItem(item.id).then(() => config.actions.reload());
|
|
4120
|
+
});
|
|
4121
|
+
actions.append(remove);
|
|
4122
|
+
row.append(body, actions);
|
|
4123
|
+
return row;
|
|
4124
|
+
}
|
|
4125
|
+
|
|
4126
|
+
// src/core/view/selection.layers.ts
|
|
4127
|
+
function createElementLayer(config) {
|
|
4128
|
+
const layer = document.createElement("div");
|
|
4129
|
+
layer.className = "dfwr-element-layer";
|
|
4130
|
+
const environment = config.getEnvironment();
|
|
4131
|
+
const hover = document.createElement("div");
|
|
4132
|
+
hover.className = "dfwr-dom-hover";
|
|
4133
|
+
hover.hidden = true;
|
|
4134
|
+
layer.append(hover);
|
|
4135
|
+
if (environment) {
|
|
4136
|
+
placeLayerOverTarget(layer, environment);
|
|
4137
|
+
}
|
|
4138
|
+
const updateHover = (point) => {
|
|
4139
|
+
const nextEnvironment = config.getEnvironment();
|
|
4140
|
+
if (!nextEnvironment) return;
|
|
4141
|
+
const anchor = getDomAnchorFromPoint(
|
|
4142
|
+
clampPoint(point, nextEnvironment),
|
|
4143
|
+
config.options.anchors?.attribute,
|
|
4144
|
+
nextEnvironment
|
|
4145
|
+
);
|
|
4146
|
+
const selection = anchor ? getElementViewportSelection(anchor, nextEnvironment) : void 0;
|
|
4147
|
+
if (!selection) {
|
|
4148
|
+
hover.hidden = true;
|
|
4149
|
+
return;
|
|
4150
|
+
}
|
|
4151
|
+
const rect = toHostSelection(selection, nextEnvironment);
|
|
4152
|
+
hover.hidden = false;
|
|
4153
|
+
hover.style.left = `${rect.left}px`;
|
|
4154
|
+
hover.style.top = `${rect.top}px`;
|
|
4155
|
+
hover.style.width = `${rect.width}px`;
|
|
4156
|
+
hover.style.height = `${rect.height}px`;
|
|
4157
|
+
};
|
|
4158
|
+
layer.addEventListener("pointermove", (event) => {
|
|
4159
|
+
updateHover(toTargetPointFromHostEvent(event, config.getEnvironment()));
|
|
4160
|
+
});
|
|
4161
|
+
layer.addEventListener("pointerleave", () => {
|
|
4162
|
+
hover.hidden = true;
|
|
4163
|
+
});
|
|
4164
|
+
layer.addEventListener("pointerdown", (event) => {
|
|
4165
|
+
if (event.button !== 0) return;
|
|
4166
|
+
event.preventDefault();
|
|
4167
|
+
void config.actions.bindElementDraftToPoint(
|
|
4168
|
+
toTargetPointFromHostEvent(event, config.getEnvironment())
|
|
4169
|
+
);
|
|
4170
|
+
});
|
|
4171
|
+
return layer;
|
|
4172
|
+
}
|
|
4173
|
+
function createAreaLayer(config) {
|
|
4174
|
+
const layer = document.createElement("div");
|
|
4175
|
+
layer.className = "dfwr-area-layer";
|
|
4176
|
+
const environment = config.getEnvironment();
|
|
4177
|
+
if (environment) {
|
|
4178
|
+
placeLayerOverTarget(layer, environment);
|
|
4179
|
+
}
|
|
4180
|
+
const box = document.createElement("div");
|
|
4181
|
+
box.className = "dfwr-area-box";
|
|
4182
|
+
layer.append(box);
|
|
4183
|
+
let startX = 0;
|
|
4184
|
+
let startY = 0;
|
|
4185
|
+
let selection;
|
|
4186
|
+
let activePointerId;
|
|
4187
|
+
let isDragging = false;
|
|
4188
|
+
const ownerWindow = layer.ownerDocument.defaultView ?? window;
|
|
4189
|
+
const updateBox = (event) => {
|
|
4190
|
+
const nextEnvironment = config.getEnvironment();
|
|
4191
|
+
const nextPoint = toTargetPointFromHostEvent(event, nextEnvironment);
|
|
4192
|
+
const left = Math.min(startX, nextPoint.x);
|
|
4193
|
+
const top = Math.min(startY, nextPoint.y);
|
|
4194
|
+
const width = Math.abs(nextPoint.x - startX);
|
|
4195
|
+
const height = Math.abs(nextPoint.y - startY);
|
|
4196
|
+
selection = { left, top, width, height };
|
|
4197
|
+
const rect = nextEnvironment ? toHostSelection(selection, nextEnvironment) : selection;
|
|
4198
|
+
box.style.left = `${rect.left}px`;
|
|
4199
|
+
box.style.top = `${rect.top}px`;
|
|
4200
|
+
box.style.width = `${rect.width}px`;
|
|
4201
|
+
box.style.height = `${rect.height}px`;
|
|
4202
|
+
};
|
|
4203
|
+
const addDragListeners = () => {
|
|
4204
|
+
ownerWindow.addEventListener("pointermove", handlePointerMove, true);
|
|
4205
|
+
ownerWindow.addEventListener("pointerup", handlePointerUp, true);
|
|
4206
|
+
ownerWindow.addEventListener("pointercancel", handlePointerCancel, true);
|
|
4207
|
+
};
|
|
4208
|
+
const removeDragListeners = () => {
|
|
4209
|
+
ownerWindow.removeEventListener("pointermove", handlePointerMove, true);
|
|
4210
|
+
ownerWindow.removeEventListener("pointerup", handlePointerUp, true);
|
|
4211
|
+
ownerWindow.removeEventListener(
|
|
4212
|
+
"pointercancel",
|
|
4213
|
+
handlePointerCancel,
|
|
4214
|
+
true
|
|
4215
|
+
);
|
|
4216
|
+
};
|
|
4217
|
+
const releasePointerCapture = (event) => {
|
|
4218
|
+
try {
|
|
4219
|
+
if (layer.hasPointerCapture(event.pointerId)) {
|
|
4220
|
+
layer.releasePointerCapture(event.pointerId);
|
|
3717
4221
|
}
|
|
3718
|
-
|
|
3719
|
-
|
|
3720
|
-
|
|
3721
|
-
|
|
3722
|
-
|
|
3723
|
-
|
|
3724
|
-
|
|
3725
|
-
|
|
3726
|
-
|
|
3727
|
-
|
|
3728
|
-
|
|
3729
|
-
|
|
3730
|
-
|
|
4222
|
+
} catch {
|
|
4223
|
+
}
|
|
4224
|
+
};
|
|
4225
|
+
function isActivePointer(event) {
|
|
4226
|
+
return isDragging && event.pointerId === activePointerId;
|
|
4227
|
+
}
|
|
4228
|
+
const finishAreaSelection = (event) => {
|
|
4229
|
+
if (!isActivePointer(event)) return;
|
|
4230
|
+
event.preventDefault();
|
|
4231
|
+
updateBox(event);
|
|
4232
|
+
releasePointerCapture(event);
|
|
4233
|
+
removeDragListeners();
|
|
4234
|
+
isDragging = false;
|
|
4235
|
+
activePointerId = void 0;
|
|
4236
|
+
if (!selection || selection.width < 8 || selection.height < 8) return;
|
|
4237
|
+
config.actions.setSelectingArea(true);
|
|
4238
|
+
config.actions.render();
|
|
4239
|
+
void config.actions.createAreaDraft(selection);
|
|
4240
|
+
};
|
|
4241
|
+
function handlePointerMove(event) {
|
|
4242
|
+
if (!isActivePointer(event)) return;
|
|
4243
|
+
event.preventDefault();
|
|
4244
|
+
updateBox(event);
|
|
4245
|
+
}
|
|
4246
|
+
const handlePointerUp = (event) => {
|
|
4247
|
+
finishAreaSelection(event);
|
|
4248
|
+
};
|
|
4249
|
+
const handlePointerCancel = (event) => {
|
|
4250
|
+
if (!isActivePointer(event)) return;
|
|
4251
|
+
releasePointerCapture(event);
|
|
4252
|
+
removeDragListeners();
|
|
4253
|
+
isDragging = false;
|
|
4254
|
+
activePointerId = void 0;
|
|
4255
|
+
};
|
|
4256
|
+
layer.addEventListener("pointerdown", (event) => {
|
|
4257
|
+
if (event.button !== 0) return;
|
|
4258
|
+
event.preventDefault();
|
|
4259
|
+
activePointerId = event.pointerId;
|
|
4260
|
+
isDragging = true;
|
|
4261
|
+
try {
|
|
4262
|
+
layer.setPointerCapture(event.pointerId);
|
|
4263
|
+
} catch {
|
|
4264
|
+
}
|
|
4265
|
+
const startPoint = toTargetPointFromHostEvent(
|
|
4266
|
+
event,
|
|
4267
|
+
config.getEnvironment()
|
|
4268
|
+
);
|
|
4269
|
+
startX = startPoint.x;
|
|
4270
|
+
startY = startPoint.y;
|
|
4271
|
+
updateBox(event);
|
|
4272
|
+
addDragListeners();
|
|
4273
|
+
});
|
|
4274
|
+
layer.addEventListener("pointermove", handlePointerMove);
|
|
4275
|
+
layer.addEventListener("pointerup", handlePointerUp);
|
|
4276
|
+
layer.addEventListener("pointercancel", handlePointerCancel);
|
|
4277
|
+
return layer;
|
|
4278
|
+
}
|
|
4279
|
+
|
|
4280
|
+
// src/core/web.review.kit.view.ts
|
|
4281
|
+
var WebReviewKitView = class {
|
|
4282
|
+
constructor(config) {
|
|
4283
|
+
this.config = config;
|
|
4284
|
+
const presets = () => this.config.options.viewports?.presets;
|
|
4285
|
+
this.draftPreview = new DraftPreviewController({
|
|
4286
|
+
getEnvironment: () => this.config.getEnvironment(),
|
|
4287
|
+
getMetrics: (draft) => getDraftAdjustmentMetrics(draft, presets()),
|
|
4288
|
+
hasAdjustment: (draft) => hasDraftAdjustment(draft, presets())
|
|
3731
4289
|
});
|
|
3732
|
-
|
|
3733
|
-
|
|
3734
|
-
|
|
3735
|
-
|
|
3736
|
-
|
|
3737
|
-
const highlight = document.createElement("div");
|
|
3738
|
-
highlight.className = [
|
|
3739
|
-
"dfwr-item-target-highlight",
|
|
3740
|
-
`is-mode-${mode}`,
|
|
3741
|
-
isBound ? "is-bound" : "is-fallback",
|
|
3742
|
-
isHighlighted ? "is-highlighted" : ""
|
|
3743
|
-
].filter(Boolean).join(" ");
|
|
3744
|
-
highlight.style.left = `${rect.left}px`;
|
|
3745
|
-
highlight.style.top = `${rect.top}px`;
|
|
3746
|
-
highlight.style.width = `${rect.width}px`;
|
|
3747
|
-
highlight.style.height = `${rect.height}px`;
|
|
3748
|
-
highlight.dataset.reviewItemId = item.id;
|
|
3749
|
-
const labelElement = document.createElement("div");
|
|
3750
|
-
labelElement.className = [
|
|
3751
|
-
"dfwr-item-target-label",
|
|
3752
|
-
`is-mode-${mode}`,
|
|
3753
|
-
isHighlighted ? "is-highlighted" : ""
|
|
3754
|
-
].filter(Boolean).join(" ");
|
|
3755
|
-
labelElement.textContent = label;
|
|
3756
|
-
labelElement.style.left = `${Math.max(4, rect.left)}px`;
|
|
3757
|
-
labelElement.style.top = `${Math.max(4, rect.top - 24)}px`;
|
|
3758
|
-
labelElement.dataset.reviewItemId = item.id;
|
|
3759
|
-
return [highlight, labelElement];
|
|
4290
|
+
this.draftContext = {
|
|
4291
|
+
config: this.config,
|
|
4292
|
+
cancelDraft: (event) => this.cancelDraft(event),
|
|
4293
|
+
syncDraftPreview: (draft) => this.draftPreview.sync(draft)
|
|
4294
|
+
};
|
|
3760
4295
|
}
|
|
3761
|
-
|
|
3762
|
-
|
|
3763
|
-
|
|
3764
|
-
highlight.className = `dfwr-selection-highlight${isDraft ? " is-draft" : ""}`;
|
|
3765
|
-
highlight.style.left = `${rect.left}px`;
|
|
3766
|
-
highlight.style.top = `${rect.top}px`;
|
|
3767
|
-
highlight.style.width = `${rect.width}px`;
|
|
3768
|
-
highlight.style.height = `${rect.height}px`;
|
|
3769
|
-
return highlight;
|
|
4296
|
+
clearDraftPreview() {
|
|
4297
|
+
this.draftPreview.clear();
|
|
4298
|
+
this.clearShellComposer();
|
|
3770
4299
|
}
|
|
3771
|
-
|
|
3772
|
-
|
|
3773
|
-
const
|
|
3774
|
-
|
|
3775
|
-
"
|
|
3776
|
-
|
|
3777
|
-
|
|
3778
|
-
|
|
3779
|
-
|
|
4300
|
+
/** Rebuilds the entire shadow-root UI from the current state snapshot. */
|
|
4301
|
+
render(shadow, hiddenItemsStyle) {
|
|
4302
|
+
const state = this.state;
|
|
4303
|
+
this.draftPreview.sync(
|
|
4304
|
+
state.isOpen && state.mode === "element" ? state.domDraft : void 0
|
|
4305
|
+
);
|
|
4306
|
+
shadow.replaceChildren();
|
|
4307
|
+
shadow.append(createStyleElement());
|
|
4308
|
+
shadow.append(hiddenItemsStyle);
|
|
4309
|
+
const hasDismissableDraft = Boolean(state.domDraft || state.areaDraft);
|
|
4310
|
+
const shouldDockComposer = this.config.options.ui?.panel === false && hasDismissableDraft && Boolean(this.getShellComposerHost());
|
|
4311
|
+
let dockedComposer;
|
|
4312
|
+
const shell = document.createElement("div");
|
|
4313
|
+
shell.className = [
|
|
4314
|
+
"dfwr-shell",
|
|
4315
|
+
state.isOpen ? "is-open" : "",
|
|
4316
|
+
hasDismissableDraft && !shouldDockComposer ? "has-dismissible-draft" : ""
|
|
3780
4317
|
].filter(Boolean).join(" ");
|
|
3781
|
-
|
|
3782
|
-
|
|
3783
|
-
|
|
3784
|
-
|
|
3785
|
-
|
|
3786
|
-
|
|
3787
|
-
|
|
3788
|
-
|
|
3789
|
-
|
|
3790
|
-
|
|
3791
|
-
|
|
3792
|
-
|
|
3793
|
-
|
|
3794
|
-
|
|
3795
|
-
|
|
3796
|
-
|
|
3797
|
-
|
|
3798
|
-
|
|
3799
|
-
|
|
3800
|
-
|
|
3801
|
-
|
|
3802
|
-
|
|
3803
|
-
|
|
3804
|
-
|
|
3805
|
-
|
|
3806
|
-
|
|
3807
|
-
|
|
3808
|
-
|
|
4318
|
+
shell.setAttribute("aria-hidden", state.isOpen ? "false" : "true");
|
|
4319
|
+
if (this.config.options.ui?.panel !== false) {
|
|
4320
|
+
shell.append(
|
|
4321
|
+
createReviewPanel(
|
|
4322
|
+
this.config,
|
|
4323
|
+
() => createAreaForm(this.draftContext)
|
|
4324
|
+
)
|
|
4325
|
+
);
|
|
4326
|
+
}
|
|
4327
|
+
shell.append(
|
|
4328
|
+
createMarkerLayer({
|
|
4329
|
+
items: state.items,
|
|
4330
|
+
highlightedItemId: state.highlightedItemId,
|
|
4331
|
+
environment: this.config.getEnvironment(),
|
|
4332
|
+
presets: this.config.options.viewports?.presets,
|
|
4333
|
+
showCompactMarkers: this.config.options.ui?.markers !== "external"
|
|
4334
|
+
})
|
|
4335
|
+
);
|
|
4336
|
+
if (state.isOpen && hasDismissableDraft && !shouldDockComposer) {
|
|
4337
|
+
shell.append(this.createDraftCancelLayer());
|
|
4338
|
+
}
|
|
4339
|
+
if (state.isOpen && state.mode === "element") {
|
|
4340
|
+
if (state.domDraft) {
|
|
4341
|
+
const domDraft = createDomDraftLayer(this.draftContext, state.domDraft, {
|
|
4342
|
+
dockComposer: shouldDockComposer
|
|
4343
|
+
});
|
|
4344
|
+
shell.append(domDraft.layer);
|
|
4345
|
+
dockedComposer = domDraft.composer;
|
|
4346
|
+
} else {
|
|
4347
|
+
shell.append(createElementLayer(this.config));
|
|
3809
4348
|
}
|
|
3810
|
-
|
|
3811
|
-
|
|
3812
|
-
|
|
3813
|
-
|
|
3814
|
-
|
|
3815
|
-
|
|
3816
|
-
|
|
3817
|
-
},
|
|
3818
|
-
comment: textarea.value
|
|
3819
|
-
};
|
|
3820
|
-
this.config.actions.setNoteDraft(nextDraft);
|
|
3821
|
-
if (meta) {
|
|
3822
|
-
meta.textContent = formatNoteDraftMeta(nextDraft);
|
|
4349
|
+
}
|
|
4350
|
+
if (state.isOpen && state.mode === "area" && !state.areaDraft && !state.isSelectingArea) {
|
|
4351
|
+
shell.append(createAreaLayer(this.config));
|
|
4352
|
+
}
|
|
4353
|
+
if (state.isOpen && state.mode === "area" && state.areaDraft && this.config.options.ui?.panel === false) {
|
|
4354
|
+
if (state.areaDraft.selection) {
|
|
4355
|
+
shell.append(createAreaDraftOverlay(this.draftContext, state.areaDraft));
|
|
3823
4356
|
}
|
|
3824
|
-
|
|
3825
|
-
|
|
3826
|
-
|
|
3827
|
-
|
|
3828
|
-
event.stopPropagation();
|
|
3829
|
-
isDragging = true;
|
|
3830
|
-
pin.setPointerCapture(event.pointerId);
|
|
3831
|
-
});
|
|
3832
|
-
pin.addEventListener("pointermove", (event) => {
|
|
3833
|
-
if (!isDragging || !pin.hasPointerCapture(event.pointerId)) return;
|
|
3834
|
-
event.preventDefault();
|
|
3835
|
-
moveDraftUi({
|
|
3836
|
-
x: event.clientX,
|
|
3837
|
-
y: event.clientY
|
|
3838
|
-
});
|
|
3839
|
-
});
|
|
3840
|
-
pin.addEventListener("pointerup", (event) => {
|
|
3841
|
-
if (!isDragging || !pin.hasPointerCapture(event.pointerId)) return;
|
|
3842
|
-
event.preventDefault();
|
|
3843
|
-
event.stopPropagation();
|
|
3844
|
-
isDragging = false;
|
|
3845
|
-
pin.releasePointerCapture(event.pointerId);
|
|
3846
|
-
const nextPoint = toTargetPointFromHostEvent(
|
|
3847
|
-
event,
|
|
3848
|
-
this.config.getEnvironment()
|
|
4357
|
+
const areaComposer = createAreaDraftPopover(
|
|
4358
|
+
this.draftContext,
|
|
4359
|
+
state.areaDraft,
|
|
4360
|
+
{ dockComposer: shouldDockComposer }
|
|
3849
4361
|
);
|
|
3850
|
-
|
|
3851
|
-
|
|
3852
|
-
|
|
3853
|
-
|
|
3854
|
-
|
|
3855
|
-
|
|
3856
|
-
|
|
3857
|
-
|
|
3858
|
-
});
|
|
4362
|
+
if (shouldDockComposer) {
|
|
4363
|
+
dockedComposer = areaComposer;
|
|
4364
|
+
} else {
|
|
4365
|
+
shell.append(areaComposer);
|
|
4366
|
+
}
|
|
4367
|
+
}
|
|
4368
|
+
shadow.append(shell);
|
|
4369
|
+
this.renderShellComposer(dockedComposer);
|
|
3859
4370
|
}
|
|
3860
|
-
|
|
3861
|
-
|
|
3862
|
-
|
|
4371
|
+
get state() {
|
|
4372
|
+
return this.config.getState();
|
|
4373
|
+
}
|
|
4374
|
+
getShellComposerHost() {
|
|
3863
4375
|
const environment = this.config.getEnvironment();
|
|
3864
|
-
if (
|
|
3865
|
-
|
|
4376
|
+
if (this.config.options.ui?.panel !== false) return void 0;
|
|
4377
|
+
return environment?.composerHost ?? void 0;
|
|
4378
|
+
}
|
|
4379
|
+
/** Mounts the docked composer into the shell-provided host element. */
|
|
4380
|
+
renderShellComposer(composer) {
|
|
4381
|
+
const host = composer ? this.getShellComposerHost() : void 0;
|
|
4382
|
+
if (!host || !composer) {
|
|
4383
|
+
this.clearShellComposer();
|
|
4384
|
+
return;
|
|
3866
4385
|
}
|
|
3867
|
-
|
|
3868
|
-
|
|
3869
|
-
|
|
3870
|
-
|
|
3871
|
-
|
|
3872
|
-
|
|
3873
|
-
|
|
3874
|
-
|
|
4386
|
+
if (this.shellComposerHost && this.shellComposerHost !== host) {
|
|
4387
|
+
this.clearShellComposer();
|
|
4388
|
+
}
|
|
4389
|
+
this.shellComposerHost = host;
|
|
4390
|
+
host.dataset.hasDraftComposer = "true";
|
|
4391
|
+
if (host.parentElement) {
|
|
4392
|
+
host.parentElement.dataset.hasDraftComposer = "true";
|
|
4393
|
+
}
|
|
4394
|
+
const shell = document.createElement("div");
|
|
4395
|
+
shell.className = "dfwr-shell is-open is-shell-draft is-docked-composer";
|
|
4396
|
+
shell.append(composer);
|
|
4397
|
+
host.replaceChildren(createStyleElement(), shell);
|
|
3875
4398
|
}
|
|
3876
|
-
|
|
3877
|
-
const
|
|
3878
|
-
|
|
3879
|
-
|
|
3880
|
-
|
|
3881
|
-
|
|
3882
|
-
hover.hidden = true;
|
|
3883
|
-
layer.append(hover);
|
|
3884
|
-
if (environment) {
|
|
3885
|
-
placeLayerOverTarget(layer, environment);
|
|
4399
|
+
clearShellComposer() {
|
|
4400
|
+
const host = this.shellComposerHost;
|
|
4401
|
+
host?.replaceChildren();
|
|
4402
|
+
if (host) {
|
|
4403
|
+
delete host.dataset.hasDraftComposer;
|
|
4404
|
+
delete host.parentElement?.dataset.hasDraftComposer;
|
|
3886
4405
|
}
|
|
3887
|
-
|
|
3888
|
-
const nextEnvironment = this.config.getEnvironment();
|
|
3889
|
-
if (!nextEnvironment) return;
|
|
3890
|
-
const anchor = getDomAnchorFromPoint(
|
|
3891
|
-
clampPoint(point, nextEnvironment),
|
|
3892
|
-
this.config.options.anchors?.attribute,
|
|
3893
|
-
nextEnvironment
|
|
3894
|
-
);
|
|
3895
|
-
const selection = anchor ? getElementViewportSelection(anchor, nextEnvironment) : void 0;
|
|
3896
|
-
if (!selection) {
|
|
3897
|
-
hover.hidden = true;
|
|
3898
|
-
return;
|
|
3899
|
-
}
|
|
3900
|
-
const rect = toHostSelection(selection, nextEnvironment);
|
|
3901
|
-
hover.hidden = false;
|
|
3902
|
-
hover.style.left = `${rect.left}px`;
|
|
3903
|
-
hover.style.top = `${rect.top}px`;
|
|
3904
|
-
hover.style.width = `${rect.width}px`;
|
|
3905
|
-
hover.style.height = `${rect.height}px`;
|
|
3906
|
-
};
|
|
3907
|
-
layer.addEventListener("pointermove", (event) => {
|
|
3908
|
-
updateHover(toTargetPointFromHostEvent(event, this.config.getEnvironment()));
|
|
3909
|
-
});
|
|
3910
|
-
layer.addEventListener("pointerleave", () => {
|
|
3911
|
-
hover.hidden = true;
|
|
3912
|
-
});
|
|
3913
|
-
layer.addEventListener("pointerdown", (event) => {
|
|
3914
|
-
if (event.button !== 0) return;
|
|
3915
|
-
event.preventDefault();
|
|
3916
|
-
void this.config.actions.bindElementDraftToPoint(
|
|
3917
|
-
toTargetPointFromHostEvent(event, this.config.getEnvironment())
|
|
3918
|
-
);
|
|
3919
|
-
});
|
|
3920
|
-
return layer;
|
|
4406
|
+
this.shellComposerHost = void 0;
|
|
3921
4407
|
}
|
|
3922
|
-
|
|
4408
|
+
/** Full-screen click-away layer that cancels the active draft. */
|
|
4409
|
+
createDraftCancelLayer() {
|
|
3923
4410
|
const layer = document.createElement("div");
|
|
3924
|
-
layer.className = "dfwr-
|
|
3925
|
-
|
|
3926
|
-
if (environment) {
|
|
3927
|
-
placeLayerOverTarget(layer, environment);
|
|
3928
|
-
}
|
|
3929
|
-
const box = document.createElement("div");
|
|
3930
|
-
box.className = "dfwr-area-box";
|
|
3931
|
-
layer.append(box);
|
|
3932
|
-
let startX = 0;
|
|
3933
|
-
let startY = 0;
|
|
3934
|
-
let selection;
|
|
3935
|
-
let activePointerId;
|
|
3936
|
-
let isDragging = false;
|
|
3937
|
-
const ownerWindow = layer.ownerDocument.defaultView ?? window;
|
|
3938
|
-
const updateBox = (event) => {
|
|
3939
|
-
const nextEnvironment = this.config.getEnvironment();
|
|
3940
|
-
const nextPoint = toTargetPointFromHostEvent(
|
|
3941
|
-
event,
|
|
3942
|
-
nextEnvironment
|
|
3943
|
-
);
|
|
3944
|
-
const left = Math.min(startX, nextPoint.x);
|
|
3945
|
-
const top = Math.min(startY, nextPoint.y);
|
|
3946
|
-
const width = Math.abs(nextPoint.x - startX);
|
|
3947
|
-
const height = Math.abs(nextPoint.y - startY);
|
|
3948
|
-
const hostPoint = toHostPoint(
|
|
3949
|
-
{ x: left, y: top },
|
|
3950
|
-
nextEnvironment
|
|
3951
|
-
);
|
|
3952
|
-
selection = { left, top, width, height };
|
|
3953
|
-
box.style.left = `${hostPoint.x}px`;
|
|
3954
|
-
box.style.top = `${hostPoint.y}px`;
|
|
3955
|
-
box.style.width = `${width}px`;
|
|
3956
|
-
box.style.height = `${height}px`;
|
|
3957
|
-
};
|
|
3958
|
-
const addDragListeners = () => {
|
|
3959
|
-
ownerWindow.addEventListener("pointermove", handlePointerMove, true);
|
|
3960
|
-
ownerWindow.addEventListener("pointerup", handlePointerUp, true);
|
|
3961
|
-
ownerWindow.addEventListener("pointercancel", handlePointerCancel, true);
|
|
3962
|
-
};
|
|
3963
|
-
const removeDragListeners = () => {
|
|
3964
|
-
ownerWindow.removeEventListener("pointermove", handlePointerMove, true);
|
|
3965
|
-
ownerWindow.removeEventListener("pointerup", handlePointerUp, true);
|
|
3966
|
-
ownerWindow.removeEventListener(
|
|
3967
|
-
"pointercancel",
|
|
3968
|
-
handlePointerCancel,
|
|
3969
|
-
true
|
|
3970
|
-
);
|
|
3971
|
-
};
|
|
3972
|
-
const releasePointerCapture = (event) => {
|
|
3973
|
-
try {
|
|
3974
|
-
if (layer.hasPointerCapture(event.pointerId)) {
|
|
3975
|
-
layer.releasePointerCapture(event.pointerId);
|
|
3976
|
-
}
|
|
3977
|
-
} catch {
|
|
3978
|
-
}
|
|
3979
|
-
};
|
|
3980
|
-
function isActivePointer(event) {
|
|
3981
|
-
return isDragging && event.pointerId === activePointerId;
|
|
3982
|
-
}
|
|
3983
|
-
const finishAreaSelection = (event) => {
|
|
3984
|
-
if (!isActivePointer(event)) return;
|
|
3985
|
-
event.preventDefault();
|
|
3986
|
-
updateBox(event);
|
|
3987
|
-
releasePointerCapture(event);
|
|
3988
|
-
removeDragListeners();
|
|
3989
|
-
isDragging = false;
|
|
3990
|
-
activePointerId = void 0;
|
|
3991
|
-
if (!selection || selection.width < 8 || selection.height < 8) return;
|
|
3992
|
-
this.config.actions.setSelectingArea(true);
|
|
3993
|
-
this.config.actions.render();
|
|
3994
|
-
void this.config.actions.createAreaDraft(selection);
|
|
3995
|
-
};
|
|
3996
|
-
function handlePointerMove(event) {
|
|
3997
|
-
if (!isActivePointer(event)) return;
|
|
3998
|
-
event.preventDefault();
|
|
3999
|
-
updateBox(event);
|
|
4000
|
-
}
|
|
4001
|
-
const handlePointerUp = (event) => {
|
|
4002
|
-
finishAreaSelection(event);
|
|
4003
|
-
};
|
|
4004
|
-
const handlePointerCancel = (event) => {
|
|
4005
|
-
if (!isActivePointer(event)) return;
|
|
4006
|
-
releasePointerCapture(event);
|
|
4007
|
-
removeDragListeners();
|
|
4008
|
-
isDragging = false;
|
|
4009
|
-
activePointerId = void 0;
|
|
4010
|
-
};
|
|
4411
|
+
layer.className = "dfwr-draft-cancel-layer";
|
|
4412
|
+
layer.setAttribute("aria-hidden", "true");
|
|
4011
4413
|
layer.addEventListener("pointerdown", (event) => {
|
|
4012
4414
|
if (event.button !== 0) return;
|
|
4013
|
-
|
|
4014
|
-
activePointerId = event.pointerId;
|
|
4015
|
-
isDragging = true;
|
|
4016
|
-
try {
|
|
4017
|
-
layer.setPointerCapture(event.pointerId);
|
|
4018
|
-
} catch {
|
|
4019
|
-
}
|
|
4020
|
-
const startPoint = toTargetPointFromHostEvent(
|
|
4021
|
-
event,
|
|
4022
|
-
this.config.getEnvironment()
|
|
4023
|
-
);
|
|
4024
|
-
startX = startPoint.x;
|
|
4025
|
-
startY = startPoint.y;
|
|
4026
|
-
updateBox(event);
|
|
4027
|
-
addDragListeners();
|
|
4415
|
+
this.cancelDraft(event);
|
|
4028
4416
|
});
|
|
4029
|
-
layer.addEventListener("pointermove", handlePointerMove);
|
|
4030
|
-
layer.addEventListener("pointerup", handlePointerUp);
|
|
4031
|
-
layer.addEventListener("pointercancel", handlePointerCancel);
|
|
4032
4417
|
return layer;
|
|
4033
4418
|
}
|
|
4419
|
+
cancelDraft(event) {
|
|
4420
|
+
event?.preventDefault();
|
|
4421
|
+
event?.stopPropagation();
|
|
4422
|
+
event?.stopImmediatePropagation();
|
|
4423
|
+
this.config.actions.setModeState("idle");
|
|
4424
|
+
this.config.actions.clearDrafts();
|
|
4425
|
+
this.config.actions.setSelectingArea(false);
|
|
4426
|
+
this.config.actions.render();
|
|
4427
|
+
}
|
|
4034
4428
|
};
|
|
4035
4429
|
|
|
4036
4430
|
// src/core/web.review.kit.app.ts
|
|
@@ -4070,6 +4464,7 @@ var WebReviewKitApp = class {
|
|
|
4070
4464
|
this.items = [];
|
|
4071
4465
|
this.draftError = "";
|
|
4072
4466
|
this.isCreatingItem = false;
|
|
4467
|
+
this.isCapturingViewport = false;
|
|
4073
4468
|
this.isSelectingArea = false;
|
|
4074
4469
|
this.handleKeyDown = (event) => {
|
|
4075
4470
|
if (event.key === "Escape" && this.cancelMode()) {
|
|
@@ -4098,10 +4493,11 @@ var WebReviewKitApp = class {
|
|
|
4098
4493
|
isOpen: this.isOpen,
|
|
4099
4494
|
mode: this.mode,
|
|
4100
4495
|
items: this.items,
|
|
4101
|
-
|
|
4496
|
+
domDraft: this.domDraft,
|
|
4102
4497
|
areaDraft: this.areaDraft,
|
|
4103
4498
|
draftError: this.draftError,
|
|
4104
4499
|
isCreatingItem: this.isCreatingItem,
|
|
4500
|
+
isCapturingViewport: this.isCapturingViewport,
|
|
4105
4501
|
isSelectingArea: this.isSelectingArea,
|
|
4106
4502
|
highlightedItemId: this.highlightedItemId
|
|
4107
4503
|
}),
|
|
@@ -4113,13 +4509,9 @@ var WebReviewKitApp = class {
|
|
|
4113
4509
|
restoreItem: (item) => this.restoreItem(item),
|
|
4114
4510
|
removeItem: (itemId) => this.adapter.remove(itemId),
|
|
4115
4511
|
setModeState: (mode) => this.setModeState(mode),
|
|
4116
|
-
clearDrafts: () =>
|
|
4117
|
-
|
|
4118
|
-
this.
|
|
4119
|
-
this.draftError = "";
|
|
4120
|
-
},
|
|
4121
|
-
setNoteDraft: (draft) => {
|
|
4122
|
-
this.noteDraft = draft;
|
|
4512
|
+
clearDrafts: () => this.clearDrafts(),
|
|
4513
|
+
setDomDraft: (draft) => {
|
|
4514
|
+
this.domDraft = draft;
|
|
4123
4515
|
this.draftError = "";
|
|
4124
4516
|
},
|
|
4125
4517
|
setAreaDraft: (draft) => {
|
|
@@ -4130,7 +4522,8 @@ var WebReviewKitApp = class {
|
|
|
4130
4522
|
this.isSelectingArea = isSelectingArea;
|
|
4131
4523
|
},
|
|
4132
4524
|
createItem: (input) => this.createItem(input),
|
|
4133
|
-
|
|
4525
|
+
captureDomDraft: (input) => this.captureDomDraft(input),
|
|
4526
|
+
captureAreaDraft: (input) => this.captureAreaDraft(input),
|
|
4134
4527
|
bindElementDraftToPoint: (point, fields) => this.bindElementDraftToPoint(point, fields),
|
|
4135
4528
|
createAreaDraft: (selection) => this.createAreaDraft(selection)
|
|
4136
4529
|
}
|
|
@@ -4152,6 +4545,7 @@ var WebReviewKitApp = class {
|
|
|
4152
4545
|
}
|
|
4153
4546
|
destroy() {
|
|
4154
4547
|
this.view.clearDraftPreview();
|
|
4548
|
+
this.clearDrafts();
|
|
4155
4549
|
document.removeEventListener("keydown", this.handleKeyDown, true);
|
|
4156
4550
|
window.removeEventListener("scroll", this.handleViewportChange, true);
|
|
4157
4551
|
window.removeEventListener("resize", this.handleViewportChange);
|
|
@@ -4171,8 +4565,7 @@ var WebReviewKitApp = class {
|
|
|
4171
4565
|
close() {
|
|
4172
4566
|
this.isOpen = false;
|
|
4173
4567
|
this.setModeState("idle");
|
|
4174
|
-
this.
|
|
4175
|
-
this.areaDraft = void 0;
|
|
4568
|
+
this.clearDrafts();
|
|
4176
4569
|
this.isSelectingArea = false;
|
|
4177
4570
|
this.render();
|
|
4178
4571
|
}
|
|
@@ -4188,8 +4581,7 @@ var WebReviewKitApp = class {
|
|
|
4188
4581
|
this.isOpen = true;
|
|
4189
4582
|
}
|
|
4190
4583
|
this.setModeState(this.mode === mode ? "idle" : mode);
|
|
4191
|
-
this.
|
|
4192
|
-
this.areaDraft = void 0;
|
|
4584
|
+
this.clearDrafts();
|
|
4193
4585
|
this.render();
|
|
4194
4586
|
}
|
|
4195
4587
|
async startElementReview(element, comment) {
|
|
@@ -4197,8 +4589,7 @@ var WebReviewKitApp = class {
|
|
|
4197
4589
|
this.isOpen = true;
|
|
4198
4590
|
}
|
|
4199
4591
|
this.setModeState("element");
|
|
4200
|
-
this.
|
|
4201
|
-
this.areaDraft = void 0;
|
|
4592
|
+
this.clearDrafts();
|
|
4202
4593
|
this.isSelectingArea = false;
|
|
4203
4594
|
await this.bindElementDraftToElement(element, { comment });
|
|
4204
4595
|
}
|
|
@@ -4223,6 +4614,20 @@ var WebReviewKitApp = class {
|
|
|
4223
4614
|
this.hiddenItemIds = itemIds ? new Set(itemIds) : void 0;
|
|
4224
4615
|
this.updateHiddenItemsStyle();
|
|
4225
4616
|
}
|
|
4617
|
+
clearDrafts() {
|
|
4618
|
+
this.revokeDraftAttachmentPreviews(this.domDraft);
|
|
4619
|
+
this.revokeDraftAttachmentPreviews(this.areaDraft);
|
|
4620
|
+
this.domDraft = void 0;
|
|
4621
|
+
this.areaDraft = void 0;
|
|
4622
|
+
this.draftError = "";
|
|
4623
|
+
}
|
|
4624
|
+
revokeDraftAttachmentPreviews(draft) {
|
|
4625
|
+
draft?.attachments?.forEach((attachment) => {
|
|
4626
|
+
if (attachment.previewUrl) {
|
|
4627
|
+
URL.revokeObjectURL(attachment.previewUrl);
|
|
4628
|
+
}
|
|
4629
|
+
});
|
|
4630
|
+
}
|
|
4226
4631
|
clearHighlightedItem() {
|
|
4227
4632
|
if (!this.highlightedItemId) return;
|
|
4228
4633
|
this.highlightedItemId = void 0;
|
|
@@ -4258,18 +4663,17 @@ var WebReviewKitApp = class {
|
|
|
4258
4663
|
this.options.onModeChange?.(mode);
|
|
4259
4664
|
}
|
|
4260
4665
|
cancelMode() {
|
|
4261
|
-
if (this.mode === "idle" && !this.
|
|
4666
|
+
if (this.mode === "idle" && !this.domDraft && !this.areaDraft && !this.isSelectingArea) {
|
|
4262
4667
|
return false;
|
|
4263
4668
|
}
|
|
4264
4669
|
this.setModeState("idle");
|
|
4265
|
-
this.
|
|
4266
|
-
this.areaDraft = void 0;
|
|
4670
|
+
this.clearDrafts();
|
|
4267
4671
|
this.isSelectingArea = false;
|
|
4268
4672
|
this.render();
|
|
4269
4673
|
return true;
|
|
4270
4674
|
}
|
|
4271
4675
|
isDraftComposerFocused() {
|
|
4272
|
-
if (!this.
|
|
4676
|
+
if (!this.domDraft && !this.areaDraft) return false;
|
|
4273
4677
|
const composerHost = this.getEnvironment()?.composerHost;
|
|
4274
4678
|
const activeElement = composerHost?.ownerDocument.activeElement;
|
|
4275
4679
|
return Boolean(
|
|
@@ -4305,6 +4709,8 @@ var WebReviewKitApp = class {
|
|
|
4305
4709
|
};
|
|
4306
4710
|
const overlayRect = target.getOverlayRect?.() ?? rect;
|
|
4307
4711
|
const composerHost = target.getComposerHost?.();
|
|
4712
|
+
const scaleX = target.window.innerWidth > 0 ? rect.width / target.window.innerWidth : 1;
|
|
4713
|
+
const scaleY = target.window.innerHeight > 0 ? rect.height / target.window.innerHeight : 1;
|
|
4308
4714
|
return {
|
|
4309
4715
|
window: target.window,
|
|
4310
4716
|
document: target.document,
|
|
@@ -4314,13 +4720,16 @@ var WebReviewKitApp = class {
|
|
|
4314
4720
|
width: rect.width,
|
|
4315
4721
|
height: rect.height
|
|
4316
4722
|
},
|
|
4723
|
+
scaleX,
|
|
4724
|
+
scaleY,
|
|
4317
4725
|
overlayRect: {
|
|
4318
4726
|
left: overlayRect.left,
|
|
4319
4727
|
top: overlayRect.top,
|
|
4320
4728
|
width: overlayRect.width,
|
|
4321
4729
|
height: overlayRect.height
|
|
4322
4730
|
},
|
|
4323
|
-
composerHost
|
|
4731
|
+
composerHost,
|
|
4732
|
+
captureViewport: target.captureViewport
|
|
4324
4733
|
};
|
|
4325
4734
|
} catch {
|
|
4326
4735
|
return void 0;
|
|
@@ -4343,32 +4752,6 @@ var WebReviewKitApp = class {
|
|
|
4343
4752
|
if (!this.shadow) return;
|
|
4344
4753
|
this.view.render(this.shadow, this.createHiddenItemsStyleElement());
|
|
4345
4754
|
}
|
|
4346
|
-
async bindNoteDraftToPoint(point, fields = {}) {
|
|
4347
|
-
const environment = this.getEnvironment();
|
|
4348
|
-
if (!environment) return;
|
|
4349
|
-
const viewport = getViewportSize(environment);
|
|
4350
|
-
const nextPoint = clampPoint(point, environment);
|
|
4351
|
-
const draft = await this.withOverlayHidden(() => {
|
|
4352
|
-
const selection = getPointSelection(nextPoint);
|
|
4353
|
-
const anchor = getDomAnchor(
|
|
4354
|
-
selection,
|
|
4355
|
-
this.options.anchors?.attribute,
|
|
4356
|
-
environment
|
|
4357
|
-
);
|
|
4358
|
-
const marker = {
|
|
4359
|
-
viewport: roundPoint(nextPoint),
|
|
4360
|
-
relative: anchor ? getRelativePoint(nextPoint, anchor, environment) : void 0
|
|
4361
|
-
};
|
|
4362
|
-
return {
|
|
4363
|
-
viewport,
|
|
4364
|
-
anchor,
|
|
4365
|
-
marker,
|
|
4366
|
-
...fields
|
|
4367
|
-
};
|
|
4368
|
-
});
|
|
4369
|
-
this.noteDraft = draft;
|
|
4370
|
-
this.render();
|
|
4371
|
-
}
|
|
4372
4755
|
async bindElementDraftToPoint(point, fields = {}) {
|
|
4373
4756
|
const environment = this.getEnvironment();
|
|
4374
4757
|
if (!environment) return;
|
|
@@ -4417,7 +4800,7 @@ var WebReviewKitApp = class {
|
|
|
4417
4800
|
previewElement
|
|
4418
4801
|
};
|
|
4419
4802
|
});
|
|
4420
|
-
this.
|
|
4803
|
+
this.domDraft = draft;
|
|
4421
4804
|
this.render();
|
|
4422
4805
|
}
|
|
4423
4806
|
async bindElementDraftToElement(element, fields = {}) {
|
|
@@ -4458,7 +4841,7 @@ var WebReviewKitApp = class {
|
|
|
4458
4841
|
};
|
|
4459
4842
|
});
|
|
4460
4843
|
if (!draft) return;
|
|
4461
|
-
this.
|
|
4844
|
+
this.domDraft = draft;
|
|
4462
4845
|
this.render();
|
|
4463
4846
|
}
|
|
4464
4847
|
async createAreaDraft(selection) {
|
|
@@ -4471,16 +4854,27 @@ var WebReviewKitApp = class {
|
|
|
4471
4854
|
try {
|
|
4472
4855
|
const viewport = getViewportSize(environment);
|
|
4473
4856
|
this.areaDraft = await this.withOverlayHidden(() => {
|
|
4474
|
-
const
|
|
4857
|
+
const anchorPoint = clampPoint(
|
|
4858
|
+
getSelectionCenter(selection),
|
|
4859
|
+
environment
|
|
4860
|
+
);
|
|
4861
|
+
const anchor = getDomAnchorFromPoint(
|
|
4862
|
+
anchorPoint,
|
|
4863
|
+
this.options.anchors?.attribute,
|
|
4864
|
+
environment
|
|
4865
|
+
);
|
|
4866
|
+
const marker = createSelectionStartMarker(
|
|
4475
4867
|
selection,
|
|
4476
|
-
|
|
4868
|
+
anchor,
|
|
4477
4869
|
environment
|
|
4478
4870
|
);
|
|
4479
4871
|
const reviewSelection = {
|
|
4480
|
-
viewport: toPublicSelection(selection)
|
|
4872
|
+
viewport: toPublicSelection(selection),
|
|
4873
|
+
relative: anchor ? getRelativeSelection(selection, anchor, environment) : void 0
|
|
4481
4874
|
};
|
|
4482
4875
|
return {
|
|
4483
4876
|
viewport,
|
|
4877
|
+
anchor,
|
|
4484
4878
|
marker,
|
|
4485
4879
|
selection: reviewSelection
|
|
4486
4880
|
};
|
|
@@ -4501,6 +4895,127 @@ var WebReviewKitApp = class {
|
|
|
4501
4895
|
this.root.style.display = previousDisplay;
|
|
4502
4896
|
}
|
|
4503
4897
|
}
|
|
4898
|
+
async captureDomDraft(input) {
|
|
4899
|
+
if (this.isCapturingViewport) return;
|
|
4900
|
+
const environment = this.getEnvironment();
|
|
4901
|
+
const draft = this.domDraft;
|
|
4902
|
+
if (!draft) return;
|
|
4903
|
+
if (!environment?.captureViewport) {
|
|
4904
|
+
this.draftError = "Viewport capture helper is not available.";
|
|
4905
|
+
this.render();
|
|
4906
|
+
return;
|
|
4907
|
+
}
|
|
4908
|
+
const captureInput = this.createViewportCaptureInput(
|
|
4909
|
+
environment,
|
|
4910
|
+
input,
|
|
4911
|
+
input.selection?.viewport
|
|
4912
|
+
);
|
|
4913
|
+
this.draftError = "";
|
|
4914
|
+
this.isCapturingViewport = true;
|
|
4915
|
+
this.render();
|
|
4916
|
+
try {
|
|
4917
|
+
const result = await environment.captureViewport(captureInput);
|
|
4918
|
+
const attachment = this.createCaptureDraftAttachment(result, captureInput);
|
|
4919
|
+
const currentDraft = this.domDraft ?? draft;
|
|
4920
|
+
this.domDraft = {
|
|
4921
|
+
...currentDraft,
|
|
4922
|
+
attachments: [...currentDraft.attachments ?? [], attachment]
|
|
4923
|
+
};
|
|
4924
|
+
} catch (error) {
|
|
4925
|
+
this.draftError = this.getErrorMessage(
|
|
4926
|
+
error,
|
|
4927
|
+
"Failed to capture viewport."
|
|
4928
|
+
);
|
|
4929
|
+
} finally {
|
|
4930
|
+
this.isCapturingViewport = false;
|
|
4931
|
+
this.render();
|
|
4932
|
+
}
|
|
4933
|
+
}
|
|
4934
|
+
async captureAreaDraft(input) {
|
|
4935
|
+
if (this.isCapturingViewport) return;
|
|
4936
|
+
const environment = this.getEnvironment();
|
|
4937
|
+
const draft = this.areaDraft;
|
|
4938
|
+
if (!draft) return;
|
|
4939
|
+
if (!environment?.captureViewport) {
|
|
4940
|
+
this.draftError = "Viewport capture helper is not available.";
|
|
4941
|
+
this.render();
|
|
4942
|
+
return;
|
|
4943
|
+
}
|
|
4944
|
+
const captureInput = this.createViewportCaptureInput(
|
|
4945
|
+
environment,
|
|
4946
|
+
input,
|
|
4947
|
+
input.selection?.viewport
|
|
4948
|
+
);
|
|
4949
|
+
this.draftError = "";
|
|
4950
|
+
this.isCapturingViewport = true;
|
|
4951
|
+
this.render();
|
|
4952
|
+
try {
|
|
4953
|
+
const result = await environment.captureViewport(captureInput);
|
|
4954
|
+
const attachment = this.createCaptureDraftAttachment(result, captureInput);
|
|
4955
|
+
const currentDraft = this.areaDraft ?? draft;
|
|
4956
|
+
this.areaDraft = {
|
|
4957
|
+
...currentDraft,
|
|
4958
|
+
attachments: [...currentDraft.attachments ?? [], attachment]
|
|
4959
|
+
};
|
|
4960
|
+
} catch (error) {
|
|
4961
|
+
this.draftError = this.getErrorMessage(
|
|
4962
|
+
error,
|
|
4963
|
+
"Failed to capture viewport."
|
|
4964
|
+
);
|
|
4965
|
+
} finally {
|
|
4966
|
+
this.isCapturingViewport = false;
|
|
4967
|
+
this.render();
|
|
4968
|
+
}
|
|
4969
|
+
}
|
|
4970
|
+
createViewportCaptureInput(environment, draft, captureRegion) {
|
|
4971
|
+
const timestamp = (/* @__PURE__ */ new Date()).toISOString();
|
|
4972
|
+
const viewport = draft.viewport ?? getViewportSize(environment);
|
|
4973
|
+
const routeKey = getRouteKey(environment);
|
|
4974
|
+
return {
|
|
4975
|
+
routeKey,
|
|
4976
|
+
pageUrl: getPageUrl(environment),
|
|
4977
|
+
originalUrl: getOriginalUrl(environment),
|
|
4978
|
+
viewport,
|
|
4979
|
+
captureRegion,
|
|
4980
|
+
devicePixelRatio: environment.window.devicePixelRatio || 1,
|
|
4981
|
+
scroll: {
|
|
4982
|
+
x: environment.window.scrollX,
|
|
4983
|
+
y: environment.window.scrollY
|
|
4984
|
+
},
|
|
4985
|
+
marker: draft.marker,
|
|
4986
|
+
selection: draft.selection,
|
|
4987
|
+
timestamp
|
|
4988
|
+
};
|
|
4989
|
+
}
|
|
4990
|
+
createCaptureDraftAttachment(result, input) {
|
|
4991
|
+
const mime = result.mime || result.file.type || "image/png";
|
|
4992
|
+
const name = result.name || `review-capture-${Date.now()}.png`;
|
|
4993
|
+
return {
|
|
4994
|
+
id: createId(),
|
|
4995
|
+
file: result.file,
|
|
4996
|
+
name,
|
|
4997
|
+
mime,
|
|
4998
|
+
size: result.file.size,
|
|
4999
|
+
kind: "capture",
|
|
5000
|
+
previewUrl: mime.startsWith("image/") ? URL.createObjectURL(result.file) : void 0,
|
|
5001
|
+
metadata: {
|
|
5002
|
+
...result.metadata,
|
|
5003
|
+
source: "viewport-capture",
|
|
5004
|
+
target: "iframe",
|
|
5005
|
+
routeKey: input.routeKey,
|
|
5006
|
+
pageUrl: input.pageUrl,
|
|
5007
|
+
originalUrl: input.originalUrl,
|
|
5008
|
+
viewport: input.viewport,
|
|
5009
|
+
scroll: input.scroll,
|
|
5010
|
+
marker: input.marker,
|
|
5011
|
+
selection: input.selection,
|
|
5012
|
+
timestamp: input.timestamp,
|
|
5013
|
+
devicePixelRatio: input.devicePixelRatio,
|
|
5014
|
+
width: result.width,
|
|
5015
|
+
height: result.height
|
|
5016
|
+
}
|
|
5017
|
+
};
|
|
5018
|
+
}
|
|
4504
5019
|
async createItem(input) {
|
|
4505
5020
|
const environment = this.getEnvironment();
|
|
4506
5021
|
if (!environment || this.isCreatingItem) return;
|
|
@@ -4544,24 +5059,61 @@ var WebReviewKitApp = class {
|
|
|
4544
5059
|
this.isCreatingItem = true;
|
|
4545
5060
|
this.render();
|
|
4546
5061
|
try {
|
|
4547
|
-
const
|
|
5062
|
+
const attachments = await this.uploadDraftAttachments(
|
|
5063
|
+
input.attachments,
|
|
5064
|
+
item
|
|
5065
|
+
);
|
|
5066
|
+
const itemWithAttachments = attachments.length > 0 ? { ...item, attachments } : item;
|
|
5067
|
+
const createdItem = await this.adapter.create(itemWithAttachments);
|
|
4548
5068
|
this.setModeState("idle");
|
|
4549
|
-
this.
|
|
4550
|
-
this.areaDraft = void 0;
|
|
5069
|
+
this.clearDrafts();
|
|
4551
5070
|
this.highlightItem(createdItem.id);
|
|
4552
5071
|
await this.reload();
|
|
4553
5072
|
await this.options.onCreateItem?.(createdItem);
|
|
4554
5073
|
} catch (error) {
|
|
4555
|
-
this.draftError =
|
|
5074
|
+
this.draftError = this.getCreateItemErrorMessage(
|
|
5075
|
+
error,
|
|
5076
|
+
Boolean(input.attachments?.length)
|
|
5077
|
+
);
|
|
4556
5078
|
} finally {
|
|
4557
5079
|
this.isCreatingItem = false;
|
|
4558
5080
|
this.render();
|
|
4559
5081
|
}
|
|
4560
5082
|
}
|
|
5083
|
+
async uploadDraftAttachments(attachments, item) {
|
|
5084
|
+
if (!attachments?.length) return [];
|
|
5085
|
+
const uploadAttachment = this.adapter.uploadAttachment;
|
|
5086
|
+
if (!uploadAttachment) {
|
|
5087
|
+
throw new Error("Attachment upload adapter is not configured.");
|
|
5088
|
+
}
|
|
5089
|
+
return Promise.all(
|
|
5090
|
+
attachments.map(
|
|
5091
|
+
(attachment) => uploadAttachment({
|
|
5092
|
+
file: attachment.file,
|
|
5093
|
+
name: attachment.name,
|
|
5094
|
+
mime: attachment.mime,
|
|
5095
|
+
kind: attachment.kind,
|
|
5096
|
+
item,
|
|
5097
|
+
metadata: attachment.metadata
|
|
5098
|
+
})
|
|
5099
|
+
)
|
|
5100
|
+
);
|
|
5101
|
+
}
|
|
5102
|
+
getCreateItemErrorMessage(error, wasUploadingAttachments) {
|
|
5103
|
+
const message = this.getErrorMessage(error, "Failed to save QA.");
|
|
5104
|
+
const reason = error && typeof error === "object" && "reason" in error && typeof error.reason === "string" ? ` (${error.reason})` : "";
|
|
5105
|
+
return wasUploadingAttachments && reason ? `Attachment upload failed${reason}: ${message}` : message;
|
|
5106
|
+
}
|
|
5107
|
+
getErrorMessage(error, fallback) {
|
|
5108
|
+
if (error instanceof Error) return error.message;
|
|
5109
|
+
if (error && typeof error === "object" && "message" in error && typeof error.message === "string") {
|
|
5110
|
+
return error.message;
|
|
5111
|
+
}
|
|
5112
|
+
return fallback;
|
|
5113
|
+
}
|
|
4561
5114
|
async restoreItem(item) {
|
|
4562
5115
|
this.setModeState("idle");
|
|
4563
|
-
this.
|
|
4564
|
-
this.areaDraft = void 0;
|
|
5116
|
+
this.clearDrafts();
|
|
4565
5117
|
if (this.options.onRestoreItem) {
|
|
4566
5118
|
await this.options.onRestoreItem(item);
|
|
4567
5119
|
return;
|
|
@@ -4614,15 +5166,22 @@ export {
|
|
|
4614
5166
|
normalizeReviewItemStatus,
|
|
4615
5167
|
localAdapter,
|
|
4616
5168
|
DEFAULT_REVIEW_FIGMA_IMAGE_FORMAT,
|
|
5169
|
+
isPointInViewport,
|
|
5170
|
+
toHostPoint,
|
|
4617
5171
|
clamp,
|
|
5172
|
+
isHotkey,
|
|
5173
|
+
getHotkeyActionKey,
|
|
4618
5174
|
DEFAULT_REVIEW_VIEWPORTS,
|
|
4619
5175
|
findReviewViewportPreset,
|
|
4620
5176
|
getReviewViewportScope,
|
|
4621
5177
|
getReviewItemScope,
|
|
4622
5178
|
getReviewItemScopeLabel,
|
|
4623
5179
|
getNumberedReviewItems,
|
|
5180
|
+
getBoundMarkerPoint,
|
|
5181
|
+
getItemHighlightSelection,
|
|
5182
|
+
shouldShowMarkerForScope,
|
|
4624
5183
|
runWithAutoScrollBehavior,
|
|
4625
5184
|
reviewTypographyTokens,
|
|
4626
5185
|
createWebReviewKit
|
|
4627
5186
|
};
|
|
4628
|
-
//# sourceMappingURL=chunk-
|
|
5187
|
+
//# sourceMappingURL=chunk-4ZP7B7R6.js.map
|