@designfever/web-review-kit 0.7.3 → 0.8.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +8 -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 +14936 -11239
- package/dist/react-shell.cjs.map +1 -1
- package/dist/react-shell.d.cts +6 -4
- package/dist/react-shell.d.ts +6 -4
- package/dist/react-shell.js +11615 -8399
- 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 +20 -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 +46 -8
- package/docs/release-notes-0.8.0.md +175 -0
- package/docs/release-notes-0.8.1.md +23 -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
package/dist/index.cjs
CHANGED
|
@@ -157,8 +157,8 @@ function localAdapter(options = {}) {
|
|
|
157
157
|
function normalizeStoredReviewItem(value) {
|
|
158
158
|
if (!value || typeof value !== "object") return void 0;
|
|
159
159
|
const raw = value;
|
|
160
|
-
const kind = raw.kind === "
|
|
161
|
-
if (kind !== "
|
|
160
|
+
const kind = raw.kind === "capture" ? "area" : raw.kind;
|
|
161
|
+
if (kind !== "dom" && kind !== "area") return void 0;
|
|
162
162
|
const { screenshot: _screenshot, reviewNumber: _reviewNumber, ...item } = raw;
|
|
163
163
|
if (kind === raw.kind && _screenshot === void 0 && _reviewNumber === void 0) {
|
|
164
164
|
return raw;
|
|
@@ -333,6 +333,8 @@ function rowToReviewItem(row, options) {
|
|
|
333
333
|
const routeKey = row.route_key || item.routeKey || item.normalizedPath || "/";
|
|
334
334
|
const viewport = item.viewport ?? { width: 390, height: 720 };
|
|
335
335
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
336
|
+
const kind = normalizeReviewItemKind(item);
|
|
337
|
+
if (!kind) return null;
|
|
336
338
|
return {
|
|
337
339
|
...item,
|
|
338
340
|
id: row.id,
|
|
@@ -341,7 +343,7 @@ function rowToReviewItem(row, options) {
|
|
|
341
343
|
routeKey,
|
|
342
344
|
pageUrl: item.pageUrl || toAbsoluteReviewUrl(routeKey),
|
|
343
345
|
normalizedPath: item.normalizedPath || routeKey,
|
|
344
|
-
kind
|
|
346
|
+
kind,
|
|
345
347
|
comment: item.comment || "",
|
|
346
348
|
status,
|
|
347
349
|
viewport,
|
|
@@ -358,6 +360,10 @@ function rowToReviewItem(row, options) {
|
|
|
358
360
|
updatedAt: row.updated_at ?? item.updatedAt ?? now
|
|
359
361
|
};
|
|
360
362
|
}
|
|
363
|
+
function normalizeReviewItemKind(item) {
|
|
364
|
+
if (item.kind === "area" || item.kind === "dom") return item.kind;
|
|
365
|
+
return null;
|
|
366
|
+
}
|
|
361
367
|
async function unwrapResponse(request, label) {
|
|
362
368
|
const { data, error } = await request;
|
|
363
369
|
if (error) {
|
|
@@ -1321,26 +1327,34 @@ function getPopoverBounds(environment) {
|
|
|
1321
1327
|
}
|
|
1322
1328
|
return environment.overlayRect;
|
|
1323
1329
|
}
|
|
1330
|
+
function getEnvironmentScale(environment) {
|
|
1331
|
+
const scaleX = typeof environment.scaleX === "number" && Number.isFinite(environment.scaleX) && environment.scaleX > 0 ? environment.scaleX : 1;
|
|
1332
|
+
const scaleY = typeof environment.scaleY === "number" && Number.isFinite(environment.scaleY) && environment.scaleY > 0 ? environment.scaleY : 1;
|
|
1333
|
+
return { scaleX, scaleY };
|
|
1334
|
+
}
|
|
1324
1335
|
function toHostPoint(point, environment) {
|
|
1325
1336
|
if (!environment) return point;
|
|
1337
|
+
const { scaleX, scaleY } = getEnvironmentScale(environment);
|
|
1326
1338
|
return {
|
|
1327
|
-
x: point.x + environment.viewportRect.left,
|
|
1328
|
-
y: point.y + environment.viewportRect.top
|
|
1339
|
+
x: point.x * scaleX + environment.viewportRect.left,
|
|
1340
|
+
y: point.y * scaleY + environment.viewportRect.top
|
|
1329
1341
|
};
|
|
1330
1342
|
}
|
|
1331
1343
|
function toHostSelection(selection, environment) {
|
|
1344
|
+
const { scaleX, scaleY } = getEnvironmentScale(environment);
|
|
1332
1345
|
return {
|
|
1333
|
-
left: selection.left + environment.viewportRect.left,
|
|
1334
|
-
top: selection.top + environment.viewportRect.top,
|
|
1335
|
-
width: selection.width,
|
|
1336
|
-
height: selection.height
|
|
1346
|
+
left: selection.left * scaleX + environment.viewportRect.left,
|
|
1347
|
+
top: selection.top * scaleY + environment.viewportRect.top,
|
|
1348
|
+
width: selection.width * scaleX,
|
|
1349
|
+
height: selection.height * scaleY
|
|
1337
1350
|
};
|
|
1338
1351
|
}
|
|
1339
1352
|
function toTargetPoint(point, environment) {
|
|
1340
1353
|
if (!environment) return point;
|
|
1354
|
+
const { scaleX, scaleY } = getEnvironmentScale(environment);
|
|
1341
1355
|
return {
|
|
1342
|
-
x: point.x - environment.viewportRect.left,
|
|
1343
|
-
y: point.y - environment.viewportRect.top
|
|
1356
|
+
x: (point.x - environment.viewportRect.left) / scaleX,
|
|
1357
|
+
y: (point.y - environment.viewportRect.top) / scaleY
|
|
1344
1358
|
};
|
|
1345
1359
|
}
|
|
1346
1360
|
function toTargetPointFromHostEvent(event, environment) {
|
|
@@ -1389,11 +1403,6 @@ var SEMANTIC_ANCHOR_ATTRIBUTES = [
|
|
|
1389
1403
|
"name",
|
|
1390
1404
|
"href"
|
|
1391
1405
|
];
|
|
1392
|
-
function getDomAnchor(selection, configuredAttribute = "data-qa-id", environment) {
|
|
1393
|
-
const x = selection.left + selection.width / 2;
|
|
1394
|
-
const y = selection.top + selection.height / 2;
|
|
1395
|
-
return getDomAnchorFromPoint({ x, y }, configuredAttribute, environment);
|
|
1396
|
-
}
|
|
1397
1406
|
function getDomAnchorFromPoint(point, configuredAttribute = "data-qa-id", environment) {
|
|
1398
1407
|
const target = environment.document.elementFromPoint(point.x, point.y);
|
|
1399
1408
|
if (!target) return void 0;
|
|
@@ -1943,6 +1952,34 @@ function createId() {
|
|
|
1943
1952
|
}
|
|
1944
1953
|
|
|
1945
1954
|
// src/core/hotkey.ts
|
|
1955
|
+
var HOTKEY_KEY_ALIASES = {
|
|
1956
|
+
q: ["\u3142", "\u3143"],
|
|
1957
|
+
w: ["\u3148", "\u3149"],
|
|
1958
|
+
e: ["\u3137", "\u3138"],
|
|
1959
|
+
r: ["\u3131", "\u3132"],
|
|
1960
|
+
t: ["\u3145", "\u3146"],
|
|
1961
|
+
y: ["\u315B"],
|
|
1962
|
+
u: ["\u3155"],
|
|
1963
|
+
i: ["\u3151"],
|
|
1964
|
+
o: ["\u3150", "\u3152"],
|
|
1965
|
+
p: ["\u3154", "\u3156"],
|
|
1966
|
+
a: ["\u3141"],
|
|
1967
|
+
s: ["\u3134"],
|
|
1968
|
+
d: ["\u3147"],
|
|
1969
|
+
f: ["\u3139"],
|
|
1970
|
+
g: ["\u314E"],
|
|
1971
|
+
h: ["\u3157"],
|
|
1972
|
+
j: ["\u3153"],
|
|
1973
|
+
k: ["\u314F"],
|
|
1974
|
+
l: ["\u3163"],
|
|
1975
|
+
z: ["\u314B"],
|
|
1976
|
+
x: ["\u314C"],
|
|
1977
|
+
c: ["\u314A"],
|
|
1978
|
+
v: ["\u314D"],
|
|
1979
|
+
b: ["\u3160"],
|
|
1980
|
+
n: ["\u315C"],
|
|
1981
|
+
m: ["\u3161"]
|
|
1982
|
+
};
|
|
1946
1983
|
function isHotkey(event, hotkey) {
|
|
1947
1984
|
const parts = hotkey.split("+").map((part) => part.trim().toLowerCase()).filter(Boolean);
|
|
1948
1985
|
const key = parts.find(
|
|
@@ -1963,10 +2000,7 @@ function isHotkeyKey(event, key) {
|
|
|
1963
2000
|
const normalizedKey = key.toLowerCase();
|
|
1964
2001
|
if (event.key.toLowerCase() === normalizedKey) return true;
|
|
1965
2002
|
if (getHotkeyCode(normalizedKey) === event.code) return true;
|
|
1966
|
-
|
|
1967
|
-
q: ["\u3142", "\u3143"]
|
|
1968
|
-
};
|
|
1969
|
-
return aliases[normalizedKey]?.includes(event.key) ?? false;
|
|
2003
|
+
return HOTKEY_KEY_ALIASES[normalizedKey]?.includes(event.key) ?? false;
|
|
1970
2004
|
}
|
|
1971
2005
|
function getHotkeyCode(key) {
|
|
1972
2006
|
if (/^[a-z]$/.test(key)) return `Key${key.toUpperCase()}`;
|
|
@@ -2008,8 +2042,7 @@ var REVIEW_SCOPE_LABELS = {
|
|
|
2008
2042
|
mobile: "Mobile",
|
|
2009
2043
|
tablet: "Tablet",
|
|
2010
2044
|
desktop: "Desktop",
|
|
2011
|
-
wide: "Wide"
|
|
2012
|
-
dom: "Element"
|
|
2045
|
+
wide: "Wide"
|
|
2013
2046
|
};
|
|
2014
2047
|
var normalizeReviewItemScope = (value) => {
|
|
2015
2048
|
if (value === "element") return "dom";
|
|
@@ -2055,7 +2088,6 @@ function getReviewItemScope(item, presets = DEFAULT_REVIEW_VIEWPORTS) {
|
|
|
2055
2088
|
}
|
|
2056
2089
|
function getReviewItemScopeLabel(item, presets = DEFAULT_REVIEW_VIEWPORTS) {
|
|
2057
2090
|
const scope = getReviewItemScope(item, presets);
|
|
2058
|
-
if (scope === "dom") return REVIEW_SCOPE_LABELS.dom;
|
|
2059
2091
|
const preset = findReviewViewportPreset(item.viewport, presets);
|
|
2060
2092
|
return preset.label || REVIEW_SCOPE_LABELS[scope];
|
|
2061
2093
|
}
|
|
@@ -2097,7 +2129,7 @@ function normalizeReviewNumber(value) {
|
|
|
2097
2129
|
function getBoundMarkerPoint(item, environment) {
|
|
2098
2130
|
const marker = getItemMarker(item);
|
|
2099
2131
|
if (!marker) return void 0;
|
|
2100
|
-
if (item.
|
|
2132
|
+
if (item.anchor && marker.relative) {
|
|
2101
2133
|
const resolved = resolveAnchorElement(item.anchor, environment);
|
|
2102
2134
|
const element = resolved?.element;
|
|
2103
2135
|
if (element) {
|
|
@@ -2145,27 +2177,25 @@ function getItemHighlightSelection(item, environment) {
|
|
|
2145
2177
|
environment
|
|
2146
2178
|
);
|
|
2147
2179
|
}
|
|
2148
|
-
return
|
|
2149
|
-
[
|
|
2150
|
-
getAnchorHighlightSelection(item, environment),
|
|
2151
|
-
getBoundSelection(item, environment),
|
|
2152
|
-
getPointHighlightSelection(item, environment)
|
|
2153
|
-
],
|
|
2154
|
-
environment
|
|
2155
|
-
);
|
|
2180
|
+
return void 0;
|
|
2156
2181
|
}
|
|
2157
2182
|
function getReviewItemHighlightMode(item) {
|
|
2158
2183
|
if (isDomReviewItem(item)) return "dom";
|
|
2159
|
-
|
|
2160
|
-
return "note";
|
|
2184
|
+
return "area";
|
|
2161
2185
|
}
|
|
2162
2186
|
function getItemMarker(item) {
|
|
2163
2187
|
if (item.marker) return item.marker;
|
|
2164
2188
|
const selection = getItemSelection(item);
|
|
2165
2189
|
if (!selection?.viewport) return void 0;
|
|
2166
2190
|
return {
|
|
2167
|
-
viewport: roundPoint(
|
|
2168
|
-
|
|
2191
|
+
viewport: roundPoint({
|
|
2192
|
+
x: selection.viewport.x,
|
|
2193
|
+
y: selection.viewport.y
|
|
2194
|
+
}),
|
|
2195
|
+
relative: selection.relative ? roundPoint({
|
|
2196
|
+
x: selection.relative.x,
|
|
2197
|
+
y: selection.relative.y
|
|
2198
|
+
}) : void 0
|
|
2169
2199
|
};
|
|
2170
2200
|
}
|
|
2171
2201
|
function getItemSelection(item) {
|
|
@@ -2184,17 +2214,20 @@ function getItemSelection(item) {
|
|
|
2184
2214
|
function shouldShowMarkerForScope(scope, currentScope) {
|
|
2185
2215
|
return scope === currentScope;
|
|
2186
2216
|
}
|
|
2187
|
-
function
|
|
2188
|
-
const
|
|
2217
|
+
function createSelectionStartMarker(selection, anchor, environment) {
|
|
2218
|
+
const startPoint = {
|
|
2219
|
+
x: selection.left,
|
|
2220
|
+
y: selection.top
|
|
2221
|
+
};
|
|
2189
2222
|
return {
|
|
2190
|
-
viewport: roundPoint(
|
|
2191
|
-
relative: anchor ? getRelativePoint(
|
|
2223
|
+
viewport: roundPoint(startPoint),
|
|
2224
|
+
relative: anchor ? getRelativePoint(startPoint, anchor, environment) : void 0
|
|
2192
2225
|
};
|
|
2193
2226
|
}
|
|
2194
2227
|
function getBoundSelection(item, environment) {
|
|
2195
2228
|
const selection = getItemSelection(item);
|
|
2196
2229
|
if (!selection?.viewport) return void 0;
|
|
2197
|
-
if (item.
|
|
2230
|
+
if (item.anchor && selection.relative) {
|
|
2198
2231
|
const resolved = resolveAnchorElement(item.anchor, environment);
|
|
2199
2232
|
const element = resolved?.element;
|
|
2200
2233
|
if (element) {
|
|
@@ -2256,7 +2289,7 @@ function getVisibleHighlightSelection(candidates, environment) {
|
|
|
2256
2289
|
);
|
|
2257
2290
|
}
|
|
2258
2291
|
function isDomReviewItem(item) {
|
|
2259
|
-
return item.
|
|
2292
|
+
return item.kind === "dom" || item.scope === "dom";
|
|
2260
2293
|
}
|
|
2261
2294
|
|
|
2262
2295
|
// src/core/scroll.ts
|
|
@@ -2353,6 +2386,117 @@ function getAdjustedDraftSelection(selection, draft, presets) {
|
|
|
2353
2386
|
};
|
|
2354
2387
|
}
|
|
2355
2388
|
|
|
2389
|
+
// src/core/draft.preview.ts
|
|
2390
|
+
var DraftPreviewController = class {
|
|
2391
|
+
constructor(config) {
|
|
2392
|
+
this.config = config;
|
|
2393
|
+
}
|
|
2394
|
+
clear() {
|
|
2395
|
+
if (!this.snapshot) return;
|
|
2396
|
+
const { element, clone, visibility } = this.snapshot;
|
|
2397
|
+
clone.remove();
|
|
2398
|
+
element.style.visibility = visibility;
|
|
2399
|
+
this.snapshot = void 0;
|
|
2400
|
+
}
|
|
2401
|
+
sync(draft) {
|
|
2402
|
+
const environment = this.config.getEnvironment();
|
|
2403
|
+
if (!draft || !environment || !this.config.hasAdjustment(draft)) {
|
|
2404
|
+
this.clear();
|
|
2405
|
+
return;
|
|
2406
|
+
}
|
|
2407
|
+
const element = this.getStyleableDraftElement(draft, environment);
|
|
2408
|
+
if (!element) {
|
|
2409
|
+
this.clear();
|
|
2410
|
+
return;
|
|
2411
|
+
}
|
|
2412
|
+
if (this.snapshot?.element !== element) {
|
|
2413
|
+
this.clear();
|
|
2414
|
+
}
|
|
2415
|
+
if (!this.snapshot) {
|
|
2416
|
+
const computedStyle = environment.window.getComputedStyle(element);
|
|
2417
|
+
const clone = element.cloneNode(true);
|
|
2418
|
+
removeDuplicateIds(clone);
|
|
2419
|
+
copyComputedStyle(element, clone, environment);
|
|
2420
|
+
positionDraftPreviewClone(clone, element, computedStyle);
|
|
2421
|
+
environment.document.body?.appendChild(clone);
|
|
2422
|
+
this.snapshot = {
|
|
2423
|
+
element,
|
|
2424
|
+
clone,
|
|
2425
|
+
visibility: element.style.visibility
|
|
2426
|
+
};
|
|
2427
|
+
element.style.visibility = "hidden";
|
|
2428
|
+
}
|
|
2429
|
+
const metrics = this.config.getMetrics(draft);
|
|
2430
|
+
const translate = `translate(${toCssNumber(metrics.cssX)}px, ${toCssNumber(
|
|
2431
|
+
metrics.cssY
|
|
2432
|
+
)}px)`;
|
|
2433
|
+
const scale = metrics.scaleFactor === 1 ? "" : `scale(${toCssNumber(metrics.scaleFactor)})`;
|
|
2434
|
+
this.snapshot.clone.style.transform = [translate, scale].filter(Boolean).join(" ");
|
|
2435
|
+
}
|
|
2436
|
+
getStyleableDraftElement(draft, environment) {
|
|
2437
|
+
if (draft.previewElement && draft.previewElement.ownerDocument === environment.document && "style" in draft.previewElement) {
|
|
2438
|
+
return draft.previewElement;
|
|
2439
|
+
}
|
|
2440
|
+
if (!draft.anchor) return void 0;
|
|
2441
|
+
const preferredSelection = draft.selection ? toViewportSelection(draft.selection.viewport) : void 0;
|
|
2442
|
+
const element = resolveAnchorElement(
|
|
2443
|
+
draft.anchor,
|
|
2444
|
+
environment,
|
|
2445
|
+
preferredSelection
|
|
2446
|
+
)?.element;
|
|
2447
|
+
if (!element) return void 0;
|
|
2448
|
+
if ("style" in element) return element;
|
|
2449
|
+
return void 0;
|
|
2450
|
+
}
|
|
2451
|
+
};
|
|
2452
|
+
function positionDraftPreviewClone(clone, element, computedStyle) {
|
|
2453
|
+
const rect = element.getBoundingClientRect();
|
|
2454
|
+
clone.setAttribute("data-dfwr-adjust-preview", "true");
|
|
2455
|
+
clone.setAttribute("aria-hidden", "true");
|
|
2456
|
+
clone.style.position = "fixed";
|
|
2457
|
+
clone.style.left = `${toCssNumber(rect.left)}px`;
|
|
2458
|
+
clone.style.top = `${toCssNumber(rect.top)}px`;
|
|
2459
|
+
clone.style.right = "auto";
|
|
2460
|
+
clone.style.bottom = "auto";
|
|
2461
|
+
clone.style.width = `${toCssNumber(rect.width)}px`;
|
|
2462
|
+
clone.style.height = `${toCssNumber(rect.height)}px`;
|
|
2463
|
+
clone.style.maxWidth = "none";
|
|
2464
|
+
clone.style.maxHeight = "none";
|
|
2465
|
+
clone.style.margin = "0";
|
|
2466
|
+
clone.style.boxSizing = "border-box";
|
|
2467
|
+
clone.style.display = getDraftPreviewDisplay(computedStyle.display);
|
|
2468
|
+
clone.style.zIndex = "2147483646";
|
|
2469
|
+
clone.style.pointerEvents = "none";
|
|
2470
|
+
clone.style.transition = "none";
|
|
2471
|
+
clone.style.willChange = "transform";
|
|
2472
|
+
clone.style.transformOrigin = "top left";
|
|
2473
|
+
clone.style.transform = "none";
|
|
2474
|
+
}
|
|
2475
|
+
function getDraftPreviewDisplay(display) {
|
|
2476
|
+
if (display === "inline" || display === "contents") return "inline-block";
|
|
2477
|
+
return display || "block";
|
|
2478
|
+
}
|
|
2479
|
+
function copyComputedStyle(element, clone, environment) {
|
|
2480
|
+
const computedStyle = environment.window.getComputedStyle(element);
|
|
2481
|
+
for (let index = 0; index < computedStyle.length; index += 1) {
|
|
2482
|
+
const property = computedStyle.item(index);
|
|
2483
|
+
clone.style.setProperty(
|
|
2484
|
+
property,
|
|
2485
|
+
computedStyle.getPropertyValue(property),
|
|
2486
|
+
computedStyle.getPropertyPriority(property)
|
|
2487
|
+
);
|
|
2488
|
+
}
|
|
2489
|
+
}
|
|
2490
|
+
function removeDuplicateIds(element) {
|
|
2491
|
+
element.removeAttribute("id");
|
|
2492
|
+
element.querySelectorAll("[id]").forEach((child) => {
|
|
2493
|
+
child.removeAttribute("id");
|
|
2494
|
+
});
|
|
2495
|
+
}
|
|
2496
|
+
function toCssNumber(value) {
|
|
2497
|
+
return Math.round(value * 1e3) / 1e3;
|
|
2498
|
+
}
|
|
2499
|
+
|
|
2356
2500
|
// src/core/typography.tokens.ts
|
|
2357
2501
|
var reviewTypographyTokens = `
|
|
2358
2502
|
--df-review-font-sans: Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
|
|
@@ -2676,6 +2820,36 @@ function createStyleElement() {
|
|
|
2676
2820
|
--dfwr-item-color-rgb: 255, 143, 97;
|
|
2677
2821
|
}
|
|
2678
2822
|
|
|
2823
|
+
.dfwr-item-target-highlight.is-scope-mobile,
|
|
2824
|
+
.dfwr-item-target-label.is-scope-mobile {
|
|
2825
|
+
--dfwr-item-color: #7cc7ff;
|
|
2826
|
+
--dfwr-item-color-rgb: 124, 199, 255;
|
|
2827
|
+
}
|
|
2828
|
+
|
|
2829
|
+
.dfwr-item-target-highlight.is-scope-tablet,
|
|
2830
|
+
.dfwr-item-target-label.is-scope-tablet {
|
|
2831
|
+
--dfwr-item-color: #63d7c7;
|
|
2832
|
+
--dfwr-item-color-rgb: 99, 215, 199;
|
|
2833
|
+
}
|
|
2834
|
+
|
|
2835
|
+
.dfwr-item-target-highlight.is-scope-desktop,
|
|
2836
|
+
.dfwr-item-target-label.is-scope-desktop {
|
|
2837
|
+
--dfwr-item-color: #f3b75f;
|
|
2838
|
+
--dfwr-item-color-rgb: 243, 183, 95;
|
|
2839
|
+
}
|
|
2840
|
+
|
|
2841
|
+
.dfwr-item-target-highlight.is-scope-wide,
|
|
2842
|
+
.dfwr-item-target-label.is-scope-wide {
|
|
2843
|
+
--dfwr-item-color: #c99cff;
|
|
2844
|
+
--dfwr-item-color-rgb: 201, 156, 255;
|
|
2845
|
+
}
|
|
2846
|
+
|
|
2847
|
+
.dfwr-item-target-highlight.is-scope-dom,
|
|
2848
|
+
.dfwr-item-target-label.is-scope-dom {
|
|
2849
|
+
--dfwr-item-color: #ff8f61;
|
|
2850
|
+
--dfwr-item-color-rgb: 255, 143, 97;
|
|
2851
|
+
}
|
|
2852
|
+
|
|
2679
2853
|
.dfwr-item-target-highlight {
|
|
2680
2854
|
position: fixed;
|
|
2681
2855
|
z-index: 2;
|
|
@@ -2684,7 +2858,6 @@ function createStyleElement() {
|
|
|
2684
2858
|
background: rgba(var(--dfwr-item-color-rgb), 0.08);
|
|
2685
2859
|
box-shadow:
|
|
2686
2860
|
0 0 0 1px rgba(31, 36, 40, 0.78),
|
|
2687
|
-
0 0 0 9999px rgba(0, 0, 0, 0.08),
|
|
2688
2861
|
0 12px 30px rgba(0, 0, 0, 0.24);
|
|
2689
2862
|
pointer-events: none;
|
|
2690
2863
|
}
|
|
@@ -2768,89 +2941,6 @@ function createStyleElement() {
|
|
|
2768
2941
|
border-style: dashed;
|
|
2769
2942
|
}
|
|
2770
2943
|
|
|
2771
|
-
.dfwr-bound-marker.is-note-callout,
|
|
2772
|
-
.dfwr-bound-marker.is-note-callout.is-highlighted {
|
|
2773
|
-
--dfwr-scope: #7cc7ff;
|
|
2774
|
-
--dfwr-scope-rgb: 124, 199, 255;
|
|
2775
|
-
min-width: 0;
|
|
2776
|
-
width: 0;
|
|
2777
|
-
height: 0;
|
|
2778
|
-
padding: 0;
|
|
2779
|
-
transform: none;
|
|
2780
|
-
border: 0;
|
|
2781
|
-
border-radius: 0;
|
|
2782
|
-
background: transparent;
|
|
2783
|
-
box-shadow: none;
|
|
2784
|
-
color: var(--dfwr-scope);
|
|
2785
|
-
animation: none;
|
|
2786
|
-
overflow: visible;
|
|
2787
|
-
}
|
|
2788
|
-
|
|
2789
|
-
.dfwr-bound-marker.is-note-callout::before {
|
|
2790
|
-
content: "";
|
|
2791
|
-
position: absolute;
|
|
2792
|
-
left: 0;
|
|
2793
|
-
top: 0;
|
|
2794
|
-
width: 8px;
|
|
2795
|
-
height: 8px;
|
|
2796
|
-
transform: translate(-50%, -50%);
|
|
2797
|
-
border: 2px solid #111820;
|
|
2798
|
-
border-radius: var(--df-review-radius-pill);
|
|
2799
|
-
background: var(--dfwr-scope);
|
|
2800
|
-
box-shadow:
|
|
2801
|
-
0 0 0 3px rgba(var(--dfwr-scope-rgb), 0.22),
|
|
2802
|
-
0 6px 16px rgba(0, 0, 0, 0.28);
|
|
2803
|
-
}
|
|
2804
|
-
|
|
2805
|
-
.dfwr-bound-marker.is-note-callout.is-highlighted::before {
|
|
2806
|
-
animation: dfwr-note-dot-pulse 1000ms ease-in-out infinite;
|
|
2807
|
-
}
|
|
2808
|
-
|
|
2809
|
-
.dfwr-bound-marker.is-note-callout .dfwr-bound-marker-icon {
|
|
2810
|
-
position: absolute;
|
|
2811
|
-
left: 0;
|
|
2812
|
-
top: 0;
|
|
2813
|
-
width: 31px;
|
|
2814
|
-
height: 2px;
|
|
2815
|
-
transform: rotate(-42deg);
|
|
2816
|
-
transform-origin: left center;
|
|
2817
|
-
border-radius: var(--df-review-radius-pill);
|
|
2818
|
-
background: currentColor;
|
|
2819
|
-
opacity: 1;
|
|
2820
|
-
}
|
|
2821
|
-
|
|
2822
|
-
.dfwr-bound-marker.is-note-callout .dfwr-bound-marker-icon::before,
|
|
2823
|
-
.dfwr-bound-marker.is-note-callout .dfwr-bound-marker-icon::after {
|
|
2824
|
-
display: none;
|
|
2825
|
-
}
|
|
2826
|
-
|
|
2827
|
-
.dfwr-bound-marker.is-note-callout .dfwr-bound-marker-number {
|
|
2828
|
-
position: absolute;
|
|
2829
|
-
left: 24px;
|
|
2830
|
-
top: -41px;
|
|
2831
|
-
display: inline-flex;
|
|
2832
|
-
align-items: center;
|
|
2833
|
-
justify-content: center;
|
|
2834
|
-
min-width: 28px;
|
|
2835
|
-
height: 20px;
|
|
2836
|
-
padding: 0 7px;
|
|
2837
|
-
border: 1px solid var(--dfwr-scope);
|
|
2838
|
-
border-radius: 4px;
|
|
2839
|
-
background: var(--dfwr-scope);
|
|
2840
|
-
box-shadow:
|
|
2841
|
-
0 0 0 3px rgba(var(--dfwr-scope-rgb), 0.18),
|
|
2842
|
-
0 8px 18px rgba(0, 0, 0, 0.28);
|
|
2843
|
-
color: #111820;
|
|
2844
|
-
text-align: center;
|
|
2845
|
-
line-height: 1;
|
|
2846
|
-
white-space: nowrap;
|
|
2847
|
-
}
|
|
2848
|
-
|
|
2849
|
-
.dfwr-bound-marker.is-note-callout.is-highlighted .dfwr-bound-marker-icon,
|
|
2850
|
-
.dfwr-bound-marker.is-note-callout.is-highlighted .dfwr-bound-marker-number {
|
|
2851
|
-
animation: dfwr-selected-blink 1000ms ease-in-out infinite;
|
|
2852
|
-
}
|
|
2853
|
-
|
|
2854
2944
|
.dfwr-area-preview-layer .dfwr-bound-marker {
|
|
2855
2945
|
border-color: #63d7c7;
|
|
2856
2946
|
background: var(--df-review-color-panel);
|
|
@@ -2921,14 +3011,14 @@ function createStyleElement() {
|
|
|
2921
3011
|
line-height: 1;
|
|
2922
3012
|
}
|
|
2923
3013
|
|
|
2924
|
-
.dfwr-
|
|
3014
|
+
.dfwr-dom-draft {
|
|
2925
3015
|
position: fixed;
|
|
2926
3016
|
inset: 0;
|
|
2927
3017
|
z-index: 4;
|
|
2928
3018
|
pointer-events: none;
|
|
2929
3019
|
}
|
|
2930
3020
|
|
|
2931
|
-
.dfwr-
|
|
3021
|
+
.dfwr-dom-pin {
|
|
2932
3022
|
appearance: none;
|
|
2933
3023
|
position: fixed;
|
|
2934
3024
|
z-index: 5;
|
|
@@ -2946,11 +3036,11 @@ function createStyleElement() {
|
|
|
2946
3036
|
pointer-events: auto;
|
|
2947
3037
|
}
|
|
2948
3038
|
|
|
2949
|
-
.dfwr-
|
|
3039
|
+
.dfwr-dom-pin:active {
|
|
2950
3040
|
cursor: grabbing;
|
|
2951
3041
|
}
|
|
2952
3042
|
|
|
2953
|
-
.dfwr-
|
|
3043
|
+
.dfwr-dom-popover {
|
|
2954
3044
|
position: fixed;
|
|
2955
3045
|
z-index: 4;
|
|
2956
3046
|
width: min(320px, calc(100vw - 24px));
|
|
@@ -2963,14 +3053,14 @@ function createStyleElement() {
|
|
|
2963
3053
|
box-shadow: var(--df-review-shadow-popover);
|
|
2964
3054
|
}
|
|
2965
3055
|
|
|
2966
|
-
.dfwr-
|
|
3056
|
+
.dfwr-dom-popover.is-composer,
|
|
2967
3057
|
.dfwr-area-draft.is-composer {
|
|
2968
3058
|
max-height: min(360px, calc(100vh - 32px));
|
|
2969
3059
|
overflow: auto;
|
|
2970
3060
|
border-color: rgba(99, 215, 199, 0.56);
|
|
2971
3061
|
}
|
|
2972
3062
|
|
|
2973
|
-
.dfwr-shell.is-docked-composer .dfwr-
|
|
3063
|
+
.dfwr-shell.is-docked-composer .dfwr-dom-popover.is-docked-composer,
|
|
2974
3064
|
.dfwr-shell.is-docked-composer .dfwr-area-draft.is-docked-composer {
|
|
2975
3065
|
position: relative;
|
|
2976
3066
|
left: auto;
|
|
@@ -2984,7 +3074,7 @@ function createStyleElement() {
|
|
|
2984
3074
|
min-height: 184px;
|
|
2985
3075
|
}
|
|
2986
3076
|
|
|
2987
|
-
.dfwr-
|
|
3077
|
+
.dfwr-dom-popover.is-dragging,
|
|
2988
3078
|
.dfwr-area-draft.is-dragging {
|
|
2989
3079
|
user-select: none;
|
|
2990
3080
|
}
|
|
@@ -3028,7 +3118,7 @@ function createStyleElement() {
|
|
|
3028
3118
|
box-shadow: var(--df-review-shadow-popover);
|
|
3029
3119
|
}
|
|
3030
3120
|
|
|
3031
|
-
.dfwr-
|
|
3121
|
+
.dfwr-dom-popover .dfwr-actions {
|
|
3032
3122
|
padding: 0;
|
|
3033
3123
|
}
|
|
3034
3124
|
|
|
@@ -3076,14 +3166,6 @@ function createStyleElement() {
|
|
|
3076
3166
|
height: 18px;
|
|
3077
3167
|
}
|
|
3078
3168
|
|
|
3079
|
-
.dfwr-note-actions {
|
|
3080
|
-
justify-content: flex-end;
|
|
3081
|
-
}
|
|
3082
|
-
|
|
3083
|
-
.dfwr-note-actions .dfwr-button:first-child {
|
|
3084
|
-
margin-right: auto;
|
|
3085
|
-
}
|
|
3086
|
-
|
|
3087
3169
|
.dfwr-area-draft .dfwr-actions {
|
|
3088
3170
|
padding: 0;
|
|
3089
3171
|
}
|
|
@@ -3101,6 +3183,82 @@ function createStyleElement() {
|
|
|
3101
3183
|
overflow-wrap: anywhere;
|
|
3102
3184
|
}
|
|
3103
3185
|
|
|
3186
|
+
.dfwr-attachment-queue {
|
|
3187
|
+
display: grid;
|
|
3188
|
+
gap: 8px;
|
|
3189
|
+
min-width: 0;
|
|
3190
|
+
}
|
|
3191
|
+
|
|
3192
|
+
.dfwr-attachment-label {
|
|
3193
|
+
color: var(--df-review-color-text-muted);
|
|
3194
|
+
font-size: var(--df-review-font-size-xs);
|
|
3195
|
+
line-height: 1.35;
|
|
3196
|
+
}
|
|
3197
|
+
|
|
3198
|
+
.dfwr-attachment-list {
|
|
3199
|
+
display: grid;
|
|
3200
|
+
gap: 8px;
|
|
3201
|
+
}
|
|
3202
|
+
|
|
3203
|
+
.dfwr-attachment-item {
|
|
3204
|
+
display: grid;
|
|
3205
|
+
grid-template-columns: 42px minmax(0, 1fr) auto;
|
|
3206
|
+
align-items: center;
|
|
3207
|
+
gap: 8px;
|
|
3208
|
+
min-width: 0;
|
|
3209
|
+
padding: 6px;
|
|
3210
|
+
border: 1px solid rgba(255, 255, 255, 0.12);
|
|
3211
|
+
border-radius: var(--df-review-radius-sm);
|
|
3212
|
+
background: rgba(255, 255, 255, 0.04);
|
|
3213
|
+
}
|
|
3214
|
+
|
|
3215
|
+
.dfwr-attachment-thumb {
|
|
3216
|
+
display: block;
|
|
3217
|
+
width: 42px;
|
|
3218
|
+
height: 42px;
|
|
3219
|
+
object-fit: cover;
|
|
3220
|
+
border-radius: var(--df-review-radius-xs);
|
|
3221
|
+
background: var(--df-review-color-panel-strong);
|
|
3222
|
+
}
|
|
3223
|
+
|
|
3224
|
+
.dfwr-attachment-thumb.is-file {
|
|
3225
|
+
display: inline-flex;
|
|
3226
|
+
align-items: center;
|
|
3227
|
+
justify-content: center;
|
|
3228
|
+
color: var(--df-review-color-text-muted);
|
|
3229
|
+
font-size: var(--df-review-font-size-xs);
|
|
3230
|
+
font-weight: var(--df-review-font-weight-emphasis);
|
|
3231
|
+
}
|
|
3232
|
+
|
|
3233
|
+
.dfwr-attachment-name {
|
|
3234
|
+
min-width: 0;
|
|
3235
|
+
overflow: hidden;
|
|
3236
|
+
color: var(--df-review-color-text);
|
|
3237
|
+
font-size: var(--df-review-font-size-sm);
|
|
3238
|
+
line-height: 1.35;
|
|
3239
|
+
text-overflow: ellipsis;
|
|
3240
|
+
white-space: nowrap;
|
|
3241
|
+
}
|
|
3242
|
+
|
|
3243
|
+
.dfwr-attachment-remove {
|
|
3244
|
+
appearance: none;
|
|
3245
|
+
min-height: 28px;
|
|
3246
|
+
padding: 0 8px;
|
|
3247
|
+
border: 1px solid var(--df-review-color-border-strong);
|
|
3248
|
+
border-radius: var(--df-review-radius-sm);
|
|
3249
|
+
color: var(--df-review-color-text-muted);
|
|
3250
|
+
background: var(--df-review-color-control);
|
|
3251
|
+
cursor: pointer;
|
|
3252
|
+
font: inherit;
|
|
3253
|
+
font-size: var(--df-review-font-size-xs);
|
|
3254
|
+
line-height: 1;
|
|
3255
|
+
}
|
|
3256
|
+
|
|
3257
|
+
.dfwr-attachment-remove:hover {
|
|
3258
|
+
color: var(--df-review-color-text);
|
|
3259
|
+
background: var(--df-review-color-control-hover);
|
|
3260
|
+
}
|
|
3261
|
+
|
|
3104
3262
|
.dfwr-input,
|
|
3105
3263
|
.dfwr-select,
|
|
3106
3264
|
.dfwr-textarea {
|
|
@@ -3403,18 +3561,6 @@ function createStyleElement() {
|
|
|
3403
3561
|
}
|
|
3404
3562
|
}
|
|
3405
3563
|
|
|
3406
|
-
@keyframes dfwr-note-dot-pulse {
|
|
3407
|
-
0% {
|
|
3408
|
-
transform: translate(-50%, -50%) scale(0.88);
|
|
3409
|
-
}
|
|
3410
|
-
45% {
|
|
3411
|
-
transform: translate(-50%, -50%) scale(1.3);
|
|
3412
|
-
}
|
|
3413
|
-
100% {
|
|
3414
|
-
transform: translate(-50%, -50%) scale(1);
|
|
3415
|
-
}
|
|
3416
|
-
}
|
|
3417
|
-
|
|
3418
3564
|
@keyframes dfwr-selected-blink {
|
|
3419
3565
|
0%,
|
|
3420
3566
|
100% {
|
|
@@ -3463,1071 +3609,848 @@ function createStyleElement() {
|
|
|
3463
3609
|
return style;
|
|
3464
3610
|
}
|
|
3465
3611
|
|
|
3466
|
-
// src/core/
|
|
3467
|
-
function
|
|
3468
|
-
|
|
3469
|
-
|
|
3470
|
-
|
|
3471
|
-
|
|
3472
|
-
|
|
3473
|
-
|
|
3474
|
-
|
|
3475
|
-
|
|
3476
|
-
}
|
|
3477
|
-
|
|
3478
|
-
|
|
3479
|
-
|
|
3480
|
-
|
|
3481
|
-
|
|
3482
|
-
|
|
3483
|
-
|
|
3484
|
-
|
|
3485
|
-
parts.push(`point ${formatPoint(marker.viewport)}`);
|
|
3486
|
-
}
|
|
3487
|
-
if (selection) {
|
|
3488
|
-
parts.push(`rect ${formatSelection(selection.viewport)}`);
|
|
3489
|
-
}
|
|
3490
|
-
if (item.anchor) {
|
|
3491
|
-
parts.push(formatAnchorMeta(item.anchor));
|
|
3492
|
-
}
|
|
3493
|
-
return parts.join(" / ");
|
|
3494
|
-
}
|
|
3495
|
-
function formatDate(value) {
|
|
3496
|
-
const date = new Date(value);
|
|
3497
|
-
if (Number.isNaN(date.getTime())) return value;
|
|
3498
|
-
return date.toLocaleString(void 0, {
|
|
3499
|
-
month: "2-digit",
|
|
3500
|
-
day: "2-digit",
|
|
3501
|
-
hour: "2-digit",
|
|
3502
|
-
minute: "2-digit"
|
|
3612
|
+
// src/core/draft.attachments.ts
|
|
3613
|
+
function attachDraftImagePasteQueue(textarea, options) {
|
|
3614
|
+
textarea.addEventListener("paste", (event) => {
|
|
3615
|
+
const imageFiles = getClipboardImageFiles(event.clipboardData);
|
|
3616
|
+
if (imageFiles.length === 0) return;
|
|
3617
|
+
event.preventDefault();
|
|
3618
|
+
const text = event.clipboardData?.getData("text/plain");
|
|
3619
|
+
if (text) {
|
|
3620
|
+
insertTextAtTextareaSelection(textarea, text);
|
|
3621
|
+
options.onCommentChange(textarea.value);
|
|
3622
|
+
}
|
|
3623
|
+
const attachments = imageFiles.map(
|
|
3624
|
+
(file, index) => createDraftImageAttachment(file, index)
|
|
3625
|
+
);
|
|
3626
|
+
options.onAttachmentsChange([
|
|
3627
|
+
...options.getAttachments() ?? [],
|
|
3628
|
+
...attachments
|
|
3629
|
+
]);
|
|
3630
|
+
options.onPasteComplete();
|
|
3503
3631
|
});
|
|
3504
3632
|
}
|
|
3505
|
-
function
|
|
3506
|
-
|
|
3633
|
+
function createDraftAttachmentQueue(ownerDocument, attachments, onRemove) {
|
|
3634
|
+
if (!attachments?.length) return void 0;
|
|
3635
|
+
const queue = ownerDocument.createElement("div");
|
|
3636
|
+
queue.className = "dfwr-attachment-queue";
|
|
3637
|
+
const label = ownerDocument.createElement("div");
|
|
3638
|
+
label.className = "dfwr-attachment-label";
|
|
3639
|
+
label.textContent = `Attachments (${attachments.length})`;
|
|
3640
|
+
const list = ownerDocument.createElement("div");
|
|
3641
|
+
list.className = "dfwr-attachment-list";
|
|
3642
|
+
attachments.forEach((attachment) => {
|
|
3643
|
+
const item = ownerDocument.createElement("div");
|
|
3644
|
+
item.className = "dfwr-attachment-item";
|
|
3645
|
+
const preview = createDraftAttachmentPreview(ownerDocument, attachment);
|
|
3646
|
+
const name = ownerDocument.createElement("div");
|
|
3647
|
+
name.className = "dfwr-attachment-name";
|
|
3648
|
+
name.textContent = attachment.name;
|
|
3649
|
+
name.title = attachment.name;
|
|
3650
|
+
const remove = ownerDocument.createElement("button");
|
|
3651
|
+
remove.className = "dfwr-attachment-remove";
|
|
3652
|
+
remove.type = "button";
|
|
3653
|
+
remove.textContent = "Remove";
|
|
3654
|
+
remove.setAttribute("aria-label", `Remove ${attachment.name}`);
|
|
3655
|
+
remove.addEventListener("click", (event) => {
|
|
3656
|
+
event.preventDefault();
|
|
3657
|
+
event.stopPropagation();
|
|
3658
|
+
onRemove(attachment.id);
|
|
3659
|
+
});
|
|
3660
|
+
item.append(preview, name, remove);
|
|
3661
|
+
list.append(item);
|
|
3662
|
+
});
|
|
3663
|
+
queue.append(label, list);
|
|
3664
|
+
return queue;
|
|
3665
|
+
}
|
|
3666
|
+
function removeDraftAttachment(attachments, attachmentId) {
|
|
3667
|
+
if (!attachments?.length) return [];
|
|
3668
|
+
const removed = attachments.find((attachment) => attachment.id === attachmentId);
|
|
3669
|
+
if (removed?.previewUrl) {
|
|
3670
|
+
URL.revokeObjectURL(removed.previewUrl);
|
|
3671
|
+
}
|
|
3672
|
+
return attachments.filter((attachment) => attachment.id !== attachmentId);
|
|
3673
|
+
}
|
|
3674
|
+
function getClipboardImageFiles(data) {
|
|
3675
|
+
if (!data) return [];
|
|
3676
|
+
const itemFiles = Array.from(data.items).filter((item) => item.kind === "file" && item.type.startsWith("image/")).map((item) => item.getAsFile()).filter((file) => Boolean(file));
|
|
3677
|
+
if (itemFiles.length > 0) return itemFiles;
|
|
3678
|
+
return Array.from(data.files).filter((file) => file.type.startsWith("image/"));
|
|
3679
|
+
}
|
|
3680
|
+
function createDraftImageAttachment(file, index) {
|
|
3681
|
+
const mime = file.type || "image/png";
|
|
3682
|
+
const name = file.name || `pasted-image-${Date.now()}-${index + 1}${getImageExtension(mime)}`;
|
|
3683
|
+
return {
|
|
3684
|
+
id: createDraftAttachmentId(),
|
|
3685
|
+
file,
|
|
3686
|
+
name,
|
|
3687
|
+
mime,
|
|
3688
|
+
size: file.size,
|
|
3689
|
+
kind: "image",
|
|
3690
|
+
previewUrl: URL.createObjectURL(file),
|
|
3691
|
+
metadata: { source: "paste" }
|
|
3692
|
+
};
|
|
3507
3693
|
}
|
|
3508
|
-
function
|
|
3509
|
-
return
|
|
3510
|
-
}
|
|
3511
|
-
function
|
|
3512
|
-
return
|
|
3513
|
-
|
|
3514
|
-
|
|
3515
|
-
|
|
3516
|
-
|
|
3517
|
-
|
|
3694
|
+
function createDraftAttachmentId() {
|
|
3695
|
+
return window.crypto?.randomUUID?.() ?? `draft-attachment-${Date.now()}-${Math.random().toString(36).slice(2)}`;
|
|
3696
|
+
}
|
|
3697
|
+
function getImageExtension(mime) {
|
|
3698
|
+
if (mime === "image/jpeg") return ".jpg";
|
|
3699
|
+
if (mime === "image/gif") return ".gif";
|
|
3700
|
+
if (mime === "image/webp") return ".webp";
|
|
3701
|
+
if (mime === "image/svg+xml") return ".svg";
|
|
3702
|
+
return ".png";
|
|
3703
|
+
}
|
|
3704
|
+
function insertTextAtTextareaSelection(textarea, text) {
|
|
3705
|
+
const start = textarea.selectionStart ?? textarea.value.length;
|
|
3706
|
+
const end = textarea.selectionEnd ?? start;
|
|
3707
|
+
textarea.value = [
|
|
3708
|
+
textarea.value.slice(0, start),
|
|
3709
|
+
text,
|
|
3710
|
+
textarea.value.slice(end)
|
|
3711
|
+
].join("");
|
|
3712
|
+
const nextSelection = start + text.length;
|
|
3713
|
+
textarea.setSelectionRange(nextSelection, nextSelection);
|
|
3714
|
+
}
|
|
3715
|
+
function createDraftAttachmentPreview(ownerDocument, attachment) {
|
|
3716
|
+
if (attachment.previewUrl && attachment.mime.startsWith("image/")) {
|
|
3717
|
+
const image = ownerDocument.createElement("img");
|
|
3718
|
+
image.className = "dfwr-attachment-thumb";
|
|
3719
|
+
image.src = attachment.previewUrl;
|
|
3720
|
+
image.alt = "";
|
|
3721
|
+
image.decoding = "async";
|
|
3722
|
+
return image;
|
|
3723
|
+
}
|
|
3724
|
+
const fallback = ownerDocument.createElement("div");
|
|
3725
|
+
fallback.className = "dfwr-attachment-thumb is-file";
|
|
3726
|
+
fallback.textContent = "IMG";
|
|
3727
|
+
return fallback;
|
|
3728
|
+
}
|
|
3729
|
+
|
|
3730
|
+
// src/core/view/composer.position.ts
|
|
3731
|
+
var COMPOSER_MARGIN = 12;
|
|
3732
|
+
function getDraftComposerWidth(environment) {
|
|
3733
|
+
const bounds = environment.overlayRect;
|
|
3734
|
+
return Math.min(360, Math.max(240, bounds.width - COMPOSER_MARGIN * 2));
|
|
3735
|
+
}
|
|
3736
|
+
function getHostComposerBounds() {
|
|
3737
|
+
const root = document.documentElement;
|
|
3738
|
+
return {
|
|
3739
|
+
left: 0,
|
|
3740
|
+
top: 0,
|
|
3741
|
+
width: root.clientWidth || window.innerWidth,
|
|
3742
|
+
height: root.clientHeight || window.innerHeight
|
|
3743
|
+
};
|
|
3518
3744
|
}
|
|
3519
|
-
function
|
|
3520
|
-
const
|
|
3521
|
-
|
|
3522
|
-
|
|
3523
|
-
|
|
3524
|
-
|
|
3525
|
-
|
|
3526
|
-
|
|
3527
|
-
|
|
3528
|
-
|
|
3745
|
+
function getClampedComposerPosition(position, environment, size, bounds = environment.overlayRect) {
|
|
3746
|
+
const width = size?.width ?? getDraftComposerWidth(environment);
|
|
3747
|
+
const height = size?.height ?? 236;
|
|
3748
|
+
return {
|
|
3749
|
+
x: clamp(
|
|
3750
|
+
position.x,
|
|
3751
|
+
bounds.left + COMPOSER_MARGIN,
|
|
3752
|
+
bounds.left + bounds.width - width - COMPOSER_MARGIN
|
|
3753
|
+
),
|
|
3754
|
+
y: clamp(
|
|
3755
|
+
position.y,
|
|
3756
|
+
bounds.top + COMPOSER_MARGIN,
|
|
3757
|
+
bounds.top + bounds.height - height - COMPOSER_MARGIN
|
|
3758
|
+
)
|
|
3759
|
+
};
|
|
3529
3760
|
}
|
|
3530
|
-
|
|
3531
|
-
|
|
3532
|
-
|
|
3533
|
-
|
|
3534
|
-
|
|
3535
|
-
this.config = config;
|
|
3536
|
-
}
|
|
3537
|
-
clearDraftPreview() {
|
|
3538
|
-
this.restoreDraftPreview();
|
|
3539
|
-
this.clearShellComposer();
|
|
3540
|
-
}
|
|
3541
|
-
render(shadow, hiddenItemsStyle) {
|
|
3542
|
-
const state = this.state;
|
|
3543
|
-
this.syncDraftPreview(
|
|
3544
|
-
state.isOpen && state.mode === "element" ? state.noteDraft : void 0
|
|
3545
|
-
);
|
|
3546
|
-
shadow.replaceChildren();
|
|
3547
|
-
shadow.append(createStyleElement());
|
|
3548
|
-
shadow.append(hiddenItemsStyle);
|
|
3549
|
-
const hasDismissableDraft = Boolean(state.noteDraft || state.areaDraft);
|
|
3550
|
-
const shouldDockComposer = this.config.options.ui?.panel === false && hasDismissableDraft && Boolean(this.getShellComposerHost());
|
|
3551
|
-
let dockedComposer;
|
|
3552
|
-
const shell = document.createElement("div");
|
|
3553
|
-
shell.className = [
|
|
3554
|
-
"dfwr-shell",
|
|
3555
|
-
state.isOpen ? "is-open" : "",
|
|
3556
|
-
hasDismissableDraft && !shouldDockComposer ? "has-dismissible-draft" : ""
|
|
3557
|
-
].filter(Boolean).join(" ");
|
|
3558
|
-
shell.setAttribute("aria-hidden", state.isOpen ? "false" : "true");
|
|
3559
|
-
if (this.config.options.ui?.panel !== false) {
|
|
3560
|
-
const panel = document.createElement("div");
|
|
3561
|
-
panel.className = "dfwr-panel";
|
|
3562
|
-
panel.setAttribute("role", "dialog");
|
|
3563
|
-
panel.setAttribute("aria-label", "Web review kit");
|
|
3564
|
-
panel.append(
|
|
3565
|
-
this.createHeader(),
|
|
3566
|
-
this.createToolbar(),
|
|
3567
|
-
this.createBody(),
|
|
3568
|
-
this.createList()
|
|
3569
|
-
);
|
|
3570
|
-
shell.append(panel);
|
|
3571
|
-
}
|
|
3572
|
-
shell.append(this.createMarkerLayer());
|
|
3573
|
-
if (state.isOpen && hasDismissableDraft && !shouldDockComposer) {
|
|
3574
|
-
shell.append(this.createDraftCancelLayer());
|
|
3575
|
-
}
|
|
3576
|
-
if (state.isOpen && (state.mode === "note" || state.mode === "element")) {
|
|
3577
|
-
if (state.noteDraft) {
|
|
3578
|
-
const noteDraft = this.createNotePopover(state.noteDraft, {
|
|
3579
|
-
dockComposer: shouldDockComposer
|
|
3580
|
-
});
|
|
3581
|
-
shell.append(noteDraft.layer);
|
|
3582
|
-
dockedComposer = noteDraft.composer;
|
|
3583
|
-
} else {
|
|
3584
|
-
shell.append(
|
|
3585
|
-
state.mode === "element" ? this.createElementLayer() : this.createNoteLayer()
|
|
3586
|
-
);
|
|
3587
|
-
}
|
|
3588
|
-
}
|
|
3589
|
-
if (state.isOpen && state.mode === "area" && !state.areaDraft && !state.isSelectingArea) {
|
|
3590
|
-
shell.append(this.createAreaLayer());
|
|
3591
|
-
}
|
|
3592
|
-
if (state.isOpen && state.mode === "area" && state.areaDraft && this.config.options.ui?.panel === false) {
|
|
3593
|
-
if (state.areaDraft.selection) {
|
|
3594
|
-
shell.append(this.createAreaDraftOverlay(state.areaDraft));
|
|
3595
|
-
}
|
|
3596
|
-
const areaComposer = this.createAreaDraftPopover(state.areaDraft, {
|
|
3597
|
-
dockComposer: shouldDockComposer
|
|
3598
|
-
});
|
|
3599
|
-
if (shouldDockComposer) {
|
|
3600
|
-
dockedComposer = areaComposer;
|
|
3601
|
-
} else {
|
|
3602
|
-
shell.append(areaComposer);
|
|
3603
|
-
}
|
|
3604
|
-
}
|
|
3605
|
-
shadow.append(shell);
|
|
3606
|
-
this.renderShellComposer(dockedComposer);
|
|
3607
|
-
}
|
|
3608
|
-
get state() {
|
|
3609
|
-
return this.config.getState();
|
|
3610
|
-
}
|
|
3611
|
-
getShellComposerHost() {
|
|
3612
|
-
const environment = this.config.getEnvironment();
|
|
3613
|
-
if (this.config.options.ui?.panel !== false) return void 0;
|
|
3614
|
-
return environment?.composerHost ?? void 0;
|
|
3615
|
-
}
|
|
3616
|
-
renderShellComposer(composer) {
|
|
3617
|
-
const host = composer ? this.getShellComposerHost() : void 0;
|
|
3618
|
-
if (!host || !composer) {
|
|
3619
|
-
this.clearShellComposer();
|
|
3620
|
-
return;
|
|
3621
|
-
}
|
|
3622
|
-
if (this.shellComposerHost && this.shellComposerHost !== host) {
|
|
3623
|
-
this.clearShellComposer();
|
|
3624
|
-
}
|
|
3625
|
-
this.shellComposerHost = host;
|
|
3626
|
-
host.dataset.hasDraftComposer = "true";
|
|
3627
|
-
if (host.parentElement) {
|
|
3628
|
-
host.parentElement.dataset.hasDraftComposer = "true";
|
|
3629
|
-
}
|
|
3630
|
-
const shell = document.createElement("div");
|
|
3631
|
-
shell.className = "dfwr-shell is-open is-shell-draft is-docked-composer";
|
|
3632
|
-
shell.append(composer);
|
|
3633
|
-
host.replaceChildren(createStyleElement(), shell);
|
|
3634
|
-
}
|
|
3635
|
-
clearShellComposer() {
|
|
3636
|
-
const host = this.shellComposerHost;
|
|
3637
|
-
host?.replaceChildren();
|
|
3638
|
-
if (host) {
|
|
3639
|
-
delete host.dataset.hasDraftComposer;
|
|
3640
|
-
delete host.parentElement?.dataset.hasDraftComposer;
|
|
3641
|
-
}
|
|
3642
|
-
this.shellComposerHost = void 0;
|
|
3643
|
-
}
|
|
3644
|
-
createDraftCancelLayer() {
|
|
3645
|
-
const layer = document.createElement("div");
|
|
3646
|
-
layer.className = "dfwr-draft-cancel-layer";
|
|
3647
|
-
layer.setAttribute("aria-hidden", "true");
|
|
3648
|
-
layer.addEventListener("pointerdown", (event) => {
|
|
3649
|
-
if (event.button !== 0) return;
|
|
3650
|
-
this.cancelDraft(event);
|
|
3651
|
-
});
|
|
3652
|
-
return layer;
|
|
3653
|
-
}
|
|
3654
|
-
cancelDraft(event) {
|
|
3655
|
-
event?.preventDefault();
|
|
3656
|
-
event?.stopPropagation();
|
|
3657
|
-
event?.stopImmediatePropagation();
|
|
3658
|
-
this.config.actions.setModeState("idle");
|
|
3659
|
-
this.config.actions.clearDrafts();
|
|
3660
|
-
this.config.actions.setSelectingArea(false);
|
|
3661
|
-
this.config.actions.render();
|
|
3662
|
-
}
|
|
3663
|
-
// Draft adjustment geometry lives in draft.metrics.ts; these thin wrappers
|
|
3664
|
-
// supply the configured viewport presets so call sites stay unchanged.
|
|
3665
|
-
get viewportPresets() {
|
|
3666
|
-
return this.config.options.viewports?.presets;
|
|
3667
|
-
}
|
|
3668
|
-
getDraftAdjustmentMetrics(draft) {
|
|
3669
|
-
return getDraftAdjustmentMetrics(draft, this.viewportPresets);
|
|
3670
|
-
}
|
|
3671
|
-
hasDraftAdjustment(draft) {
|
|
3672
|
-
return hasDraftAdjustment(draft, this.viewportPresets);
|
|
3673
|
-
}
|
|
3674
|
-
getAdjustedDraftPoint(point, draft) {
|
|
3675
|
-
return getAdjustedDraftPoint(point, draft, this.viewportPresets);
|
|
3676
|
-
}
|
|
3677
|
-
getAdjustedDraftSelection(selection, draft) {
|
|
3678
|
-
return getAdjustedDraftSelection(
|
|
3679
|
-
selection,
|
|
3680
|
-
draft,
|
|
3681
|
-
this.viewportPresets
|
|
3682
|
-
);
|
|
3683
|
-
}
|
|
3684
|
-
getDraftViewportScale(viewport) {
|
|
3685
|
-
return getDraftViewportScale(viewport, this.viewportPresets);
|
|
3686
|
-
}
|
|
3687
|
-
getDraftComposerWidth(environment) {
|
|
3688
|
-
const bounds = environment.overlayRect;
|
|
3689
|
-
const margin = 12;
|
|
3690
|
-
return Math.min(360, Math.max(240, bounds.width - margin * 2));
|
|
3691
|
-
}
|
|
3692
|
-
getClampedComposerPosition(position, environment, size, bounds = environment.overlayRect) {
|
|
3693
|
-
const margin = 12;
|
|
3694
|
-
const width = size?.width ?? this.getDraftComposerWidth(environment);
|
|
3695
|
-
const height = size?.height ?? 236;
|
|
3696
|
-
return {
|
|
3697
|
-
x: clamp(
|
|
3698
|
-
position.x,
|
|
3699
|
-
bounds.left + margin,
|
|
3700
|
-
bounds.left + bounds.width - width - margin
|
|
3701
|
-
),
|
|
3702
|
-
y: clamp(
|
|
3703
|
-
position.y,
|
|
3704
|
-
bounds.top + margin,
|
|
3705
|
-
bounds.top + bounds.height - height - margin
|
|
3706
|
-
)
|
|
3707
|
-
};
|
|
3708
|
-
}
|
|
3709
|
-
getHostComposerBounds() {
|
|
3710
|
-
const root = document.documentElement;
|
|
3711
|
-
return {
|
|
3712
|
-
left: 0,
|
|
3713
|
-
top: 0,
|
|
3714
|
-
width: root.clientWidth || window.innerWidth,
|
|
3715
|
-
height: root.clientHeight || window.innerHeight
|
|
3716
|
-
};
|
|
3717
|
-
}
|
|
3718
|
-
getInitialDraftComposerPosition(selection, environment, size) {
|
|
3719
|
-
const bounds = this.getHostComposerBounds();
|
|
3720
|
-
const margin = 12;
|
|
3721
|
-
const gap = 20;
|
|
3722
|
-
if (!selection) {
|
|
3723
|
-
return this.getClampedComposerPosition(
|
|
3724
|
-
{
|
|
3725
|
-
x: environment.overlayRect.left + margin,
|
|
3726
|
-
y: environment.overlayRect.top + margin
|
|
3727
|
-
},
|
|
3728
|
-
environment,
|
|
3729
|
-
size,
|
|
3730
|
-
bounds
|
|
3731
|
-
);
|
|
3732
|
-
}
|
|
3733
|
-
const preferredX = selection.left + selection.width + gap;
|
|
3734
|
-
const maxX = bounds.left + bounds.width - size.width - margin;
|
|
3735
|
-
const x = preferredX <= maxX ? preferredX : selection.left - size.width - gap;
|
|
3736
|
-
return this.getClampedComposerPosition(
|
|
3761
|
+
function getInitialDraftComposerPosition(selection, environment, size) {
|
|
3762
|
+
const bounds = getHostComposerBounds();
|
|
3763
|
+
const gap = 20;
|
|
3764
|
+
if (!selection) {
|
|
3765
|
+
return getClampedComposerPosition(
|
|
3737
3766
|
{
|
|
3738
|
-
x,
|
|
3739
|
-
y:
|
|
3767
|
+
x: environment.overlayRect.left + COMPOSER_MARGIN,
|
|
3768
|
+
y: environment.overlayRect.top + COMPOSER_MARGIN
|
|
3740
3769
|
},
|
|
3741
3770
|
environment,
|
|
3742
3771
|
size,
|
|
3743
3772
|
bounds
|
|
3744
3773
|
);
|
|
3745
3774
|
}
|
|
3746
|
-
|
|
3747
|
-
|
|
3775
|
+
const preferredX = selection.left + selection.width + gap;
|
|
3776
|
+
const maxX = bounds.left + bounds.width - size.width - COMPOSER_MARGIN;
|
|
3777
|
+
const x = preferredX <= maxX ? preferredX : selection.left - size.width - gap;
|
|
3778
|
+
return getClampedComposerPosition(
|
|
3779
|
+
{
|
|
3780
|
+
x,
|
|
3781
|
+
y: selection.top
|
|
3782
|
+
},
|
|
3748
3783
|
environment,
|
|
3749
|
-
|
|
3750
|
-
|
|
3751
|
-
|
|
3752
|
-
|
|
3753
|
-
|
|
3754
|
-
|
|
3755
|
-
|
|
3756
|
-
|
|
3757
|
-
|
|
3758
|
-
|
|
3759
|
-
|
|
3760
|
-
|
|
3761
|
-
|
|
3762
|
-
|
|
3763
|
-
|
|
3764
|
-
height: estimatedHeight
|
|
3765
|
-
|
|
3766
|
-
|
|
3784
|
+
size,
|
|
3785
|
+
bounds
|
|
3786
|
+
);
|
|
3787
|
+
}
|
|
3788
|
+
function getDraftComposerPosition({
|
|
3789
|
+
selection,
|
|
3790
|
+
environment,
|
|
3791
|
+
composerPosition,
|
|
3792
|
+
estimatedHeight
|
|
3793
|
+
}) {
|
|
3794
|
+
const width = getDraftComposerWidth(environment);
|
|
3795
|
+
if (composerPosition) {
|
|
3796
|
+
const clamped = getClampedComposerPosition(
|
|
3797
|
+
composerPosition,
|
|
3798
|
+
environment,
|
|
3799
|
+
{ width, height: estimatedHeight },
|
|
3800
|
+
getHostComposerBounds()
|
|
3801
|
+
);
|
|
3802
|
+
return { width, left: clamped.x, top: clamped.y };
|
|
3767
3803
|
}
|
|
3768
|
-
|
|
3769
|
-
|
|
3770
|
-
|
|
3804
|
+
const position = getInitialDraftComposerPosition(selection, environment, {
|
|
3805
|
+
width,
|
|
3806
|
+
height: estimatedHeight
|
|
3807
|
+
});
|
|
3808
|
+
return { width, left: position.x, top: position.y };
|
|
3809
|
+
}
|
|
3810
|
+
function attachDraftComposerDrag({
|
|
3811
|
+
getEnvironment,
|
|
3812
|
+
popover,
|
|
3813
|
+
handle,
|
|
3814
|
+
onMove
|
|
3815
|
+
}) {
|
|
3816
|
+
let isDragging = false;
|
|
3817
|
+
let offsetX = 0;
|
|
3818
|
+
let offsetY = 0;
|
|
3819
|
+
const movePopover = (event) => {
|
|
3820
|
+
const environment = getEnvironment();
|
|
3821
|
+
if (!environment) return;
|
|
3822
|
+
const position = getClampedComposerPosition(
|
|
3823
|
+
{
|
|
3824
|
+
x: event.clientX - offsetX,
|
|
3825
|
+
y: event.clientY - offsetY
|
|
3826
|
+
},
|
|
3827
|
+
environment,
|
|
3828
|
+
{
|
|
3829
|
+
width: popover.offsetWidth,
|
|
3830
|
+
height: popover.offsetHeight
|
|
3831
|
+
},
|
|
3832
|
+
getHostComposerBounds()
|
|
3833
|
+
);
|
|
3834
|
+
popover.style.left = `${position.x}px`;
|
|
3835
|
+
popover.style.top = `${position.y}px`;
|
|
3836
|
+
onMove(position);
|
|
3837
|
+
};
|
|
3838
|
+
handle.addEventListener("pointerdown", (event) => {
|
|
3839
|
+
if (event.button !== 0) return;
|
|
3840
|
+
const rect = popover.getBoundingClientRect();
|
|
3841
|
+
offsetX = event.clientX - rect.left;
|
|
3842
|
+
offsetY = event.clientY - rect.top;
|
|
3843
|
+
isDragging = true;
|
|
3844
|
+
event.preventDefault();
|
|
3845
|
+
event.stopPropagation();
|
|
3846
|
+
handle.setPointerCapture(event.pointerId);
|
|
3847
|
+
popover.classList.add("is-dragging");
|
|
3848
|
+
});
|
|
3849
|
+
handle.addEventListener("pointermove", (event) => {
|
|
3850
|
+
if (!isDragging || !handle.hasPointerCapture(event.pointerId)) return;
|
|
3851
|
+
event.preventDefault();
|
|
3852
|
+
movePopover(event);
|
|
3853
|
+
});
|
|
3854
|
+
const stopDrag = (event) => {
|
|
3855
|
+
if (!isDragging || !handle.hasPointerCapture(event.pointerId)) return;
|
|
3856
|
+
event.preventDefault();
|
|
3857
|
+
event.stopPropagation();
|
|
3858
|
+
isDragging = false;
|
|
3859
|
+
handle.releasePointerCapture(event.pointerId);
|
|
3860
|
+
popover.classList.remove("is-dragging");
|
|
3861
|
+
movePopover(event);
|
|
3862
|
+
};
|
|
3863
|
+
handle.addEventListener("pointerup", stopDrag);
|
|
3864
|
+
handle.addEventListener("pointercancel", stopDrag);
|
|
3865
|
+
}
|
|
3866
|
+
|
|
3867
|
+
// src/core/view/icons.ts
|
|
3868
|
+
function createIcon(paths) {
|
|
3869
|
+
const svg = document.createElementNS("http://www.w3.org/2000/svg", "svg");
|
|
3870
|
+
svg.setAttribute("aria-hidden", "true");
|
|
3871
|
+
svg.setAttribute("viewBox", "0 0 24 24");
|
|
3872
|
+
svg.setAttribute("fill", "none");
|
|
3873
|
+
svg.setAttribute("stroke", "currentColor");
|
|
3874
|
+
svg.setAttribute("stroke-width", "2.4");
|
|
3875
|
+
svg.setAttribute("stroke-linecap", "round");
|
|
3876
|
+
svg.setAttribute("stroke-linejoin", "round");
|
|
3877
|
+
paths.forEach((d) => {
|
|
3878
|
+
const path = document.createElementNS("http://www.w3.org/2000/svg", "path");
|
|
3879
|
+
path.setAttribute("d", d);
|
|
3880
|
+
svg.append(path);
|
|
3881
|
+
});
|
|
3882
|
+
return svg;
|
|
3883
|
+
}
|
|
3884
|
+
function setAdjustmentToggleIcon(button, isActive) {
|
|
3885
|
+
const paths = isActive ? ["M20 6 9 17l-5-5"] : [
|
|
3886
|
+
"M12 2v20",
|
|
3887
|
+
"M2 12h20",
|
|
3888
|
+
"m9 5 3-3 3 3",
|
|
3889
|
+
"m9 19 3 3 3-3",
|
|
3890
|
+
"m5 9-3 3 3 3",
|
|
3891
|
+
"m19 9 3 3-3 3"
|
|
3892
|
+
];
|
|
3893
|
+
button.replaceChildren(createIcon(paths));
|
|
3894
|
+
}
|
|
3895
|
+
function createSpinner(className) {
|
|
3896
|
+
const spinner = document.createElement("span");
|
|
3897
|
+
spinner.className = className;
|
|
3898
|
+
spinner.setAttribute("aria-hidden", "true");
|
|
3899
|
+
return spinner;
|
|
3900
|
+
}
|
|
3901
|
+
|
|
3902
|
+
// src/core/view/draft.capture.ts
|
|
3903
|
+
function canCaptureViewport(config) {
|
|
3904
|
+
return Boolean(config.getEnvironment()?.captureViewport);
|
|
3905
|
+
}
|
|
3906
|
+
function getCaptureAreaDraft(draft) {
|
|
3907
|
+
return {
|
|
3908
|
+
viewport: draft.viewport,
|
|
3909
|
+
marker: draft.marker,
|
|
3910
|
+
selection: draft.selection
|
|
3911
|
+
};
|
|
3912
|
+
}
|
|
3913
|
+
function getCaptureDomDraft(config, draft, isElementDraft) {
|
|
3914
|
+
if (!isElementDraft) {
|
|
3771
3915
|
return {
|
|
3772
|
-
|
|
3773
|
-
|
|
3774
|
-
|
|
3775
|
-
height: selection.height * ratio
|
|
3916
|
+
viewport: draft.viewport,
|
|
3917
|
+
marker: draft.marker,
|
|
3918
|
+
selection: draft.selection
|
|
3776
3919
|
};
|
|
3777
3920
|
}
|
|
3778
|
-
|
|
3779
|
-
|
|
3780
|
-
|
|
3781
|
-
|
|
3782
|
-
|
|
3783
|
-
|
|
3921
|
+
const presets = config.options.viewports?.presets;
|
|
3922
|
+
const marker = {
|
|
3923
|
+
...draft.marker,
|
|
3924
|
+
viewport: roundPoint(
|
|
3925
|
+
getAdjustedDraftPoint(draft.marker.viewport, draft, presets)
|
|
3926
|
+
)
|
|
3927
|
+
};
|
|
3928
|
+
const selection = draft.selection ? {
|
|
3929
|
+
...draft.selection,
|
|
3930
|
+
viewport: toPublicSelection(
|
|
3931
|
+
getAdjustedDraftSelection(
|
|
3932
|
+
toViewportSelection(draft.selection.viewport),
|
|
3933
|
+
draft,
|
|
3934
|
+
presets
|
|
3935
|
+
)
|
|
3936
|
+
)
|
|
3937
|
+
} : void 0;
|
|
3938
|
+
return {
|
|
3939
|
+
viewport: draft.viewport,
|
|
3940
|
+
marker,
|
|
3941
|
+
selection
|
|
3942
|
+
};
|
|
3943
|
+
}
|
|
3944
|
+
function createDraftCaptureButton(config, draft, options) {
|
|
3945
|
+
const button = document.createElement("button");
|
|
3946
|
+
const state = config.getState();
|
|
3947
|
+
const isCapturing = state.isCapturingViewport;
|
|
3948
|
+
const canCapture = canCaptureViewport(config);
|
|
3949
|
+
button.className = "dfwr-button";
|
|
3950
|
+
button.type = "button";
|
|
3951
|
+
button.disabled = !canCapture || isCapturing || state.isCreatingItem;
|
|
3952
|
+
button.setAttribute("aria-busy", isCapturing ? "true" : "false");
|
|
3953
|
+
button.title = canCapture ? "Capture current viewport" : "Viewport capture helper is not available";
|
|
3954
|
+
if (isCapturing) {
|
|
3955
|
+
button.append(createSpinner("dfwr-spinner"), "Capturing...");
|
|
3956
|
+
} else {
|
|
3957
|
+
button.textContent = "Capture";
|
|
3958
|
+
}
|
|
3959
|
+
button.addEventListener("click", (event) => {
|
|
3960
|
+
event.preventDefault();
|
|
3961
|
+
event.stopPropagation();
|
|
3962
|
+
const currentState = config.getState();
|
|
3963
|
+
if (!canCaptureViewport(config) || currentState.isCapturingViewport) {
|
|
3964
|
+
return;
|
|
3965
|
+
}
|
|
3966
|
+
if (options.kind === "area") {
|
|
3967
|
+
const areaDraft = currentState.areaDraft ?? draft;
|
|
3968
|
+
const nextDraft2 = {
|
|
3969
|
+
...areaDraft,
|
|
3970
|
+
comment: options.textarea.value
|
|
3971
|
+
};
|
|
3972
|
+
config.actions.setAreaDraft(nextDraft2);
|
|
3973
|
+
void config.actions.captureAreaDraft(getCaptureAreaDraft(nextDraft2));
|
|
3974
|
+
return;
|
|
3975
|
+
}
|
|
3976
|
+
const domDraft = currentState.domDraft ?? draft;
|
|
3977
|
+
const nextDraft = {
|
|
3978
|
+
...domDraft,
|
|
3979
|
+
comment: options.textarea.value
|
|
3980
|
+
};
|
|
3981
|
+
config.actions.setDomDraft(nextDraft);
|
|
3982
|
+
void config.actions.captureDomDraft(
|
|
3983
|
+
getCaptureDomDraft(config, nextDraft, options.isElementDraft)
|
|
3984
|
+
);
|
|
3985
|
+
});
|
|
3986
|
+
return button;
|
|
3987
|
+
}
|
|
3988
|
+
|
|
3989
|
+
// src/core/view/draft.text.ts
|
|
3990
|
+
var DEFAULT_ADJUSTMENT_LABEL = "Responsive CSS px adjustments";
|
|
3991
|
+
function getAdjustmentLabel(options) {
|
|
3992
|
+
return options.adjustmentLabel?.trim() || DEFAULT_ADJUSTMENT_LABEL;
|
|
3993
|
+
}
|
|
3994
|
+
function formatSignedPx(value) {
|
|
3995
|
+
if (value === 0) return "+0px";
|
|
3996
|
+
return `${value > 0 ? "+" : ""}${value}px`;
|
|
3997
|
+
}
|
|
3998
|
+
function formatRoundedPx(value) {
|
|
3999
|
+
return `${Math.round(value)}px`;
|
|
4000
|
+
}
|
|
4001
|
+
function getSelectionMqMetrics(selection, viewport, presets) {
|
|
4002
|
+
const { scale } = getDraftViewportScale(viewport, presets);
|
|
4003
|
+
const ratio = scale > 0 ? 1 / scale : 1;
|
|
4004
|
+
return {
|
|
4005
|
+
x: selection.left * ratio,
|
|
4006
|
+
y: selection.top * ratio,
|
|
4007
|
+
width: selection.width * ratio,
|
|
4008
|
+
height: selection.height * ratio
|
|
4009
|
+
};
|
|
4010
|
+
}
|
|
4011
|
+
function getSelectionMetricLines(selection, viewport, presets) {
|
|
4012
|
+
if (!selection) return ["area", "x none / y none", "w none / h none"];
|
|
4013
|
+
const metrics = getSelectionMqMetrics(selection, viewport, presets);
|
|
4014
|
+
return [
|
|
4015
|
+
"area",
|
|
4016
|
+
`x ${formatRoundedPx(metrics.x)} / y ${formatRoundedPx(metrics.y)}`,
|
|
4017
|
+
`w ${formatRoundedPx(metrics.width)} / h ${formatRoundedPx(
|
|
4018
|
+
metrics.height
|
|
4019
|
+
)}`
|
|
4020
|
+
];
|
|
4021
|
+
}
|
|
4022
|
+
function getAreaDraftMetricSelection(draft) {
|
|
4023
|
+
if (!draft.selection) return void 0;
|
|
4024
|
+
return toViewportSelection(draft.selection.viewport);
|
|
4025
|
+
}
|
|
4026
|
+
function getDraftAdjustmentMetricLines(draft, presets) {
|
|
4027
|
+
const metrics = getDraftAdjustmentMetrics(draft, presets);
|
|
4028
|
+
return [
|
|
4029
|
+
`x ${formatSignedPx(metrics.x)} / y ${formatSignedPx(metrics.y)}`,
|
|
4030
|
+
`scale ${formatSignedPx(metrics.scale)}`
|
|
4031
|
+
];
|
|
4032
|
+
}
|
|
4033
|
+
function withDraftAdjustmentComment(comment, draft, options) {
|
|
4034
|
+
const presets = options.viewports?.presets;
|
|
4035
|
+
if (!hasDraftAdjustment(draft, presets)) return comment;
|
|
4036
|
+
const trimmedComment = comment.trim();
|
|
4037
|
+
const metrics = getDraftAdjustmentMetrics(draft, presets);
|
|
4038
|
+
const adjustment = [
|
|
4039
|
+
`${getAdjustmentLabel(options)}: x ${formatSignedPx(
|
|
4040
|
+
metrics.x
|
|
4041
|
+
)}, y ${formatSignedPx(metrics.y)}, scale ${formatSignedPx(
|
|
4042
|
+
metrics.scale
|
|
4043
|
+
)}`,
|
|
4044
|
+
`(${metrics.presetLabel} viewport, ${Math.round(
|
|
4045
|
+
metrics.viewportWidth
|
|
4046
|
+
)}/design ${Math.round(metrics.designWidth)})`
|
|
4047
|
+
].join(" ");
|
|
4048
|
+
return trimmedComment ? `${trimmedComment}
|
|
4049
|
+
${adjustment}` : adjustment;
|
|
4050
|
+
}
|
|
4051
|
+
|
|
4052
|
+
// src/core/view/form.widgets.ts
|
|
4053
|
+
function getAssigneeOption(options, assigneeId) {
|
|
4054
|
+
if (!assigneeId) return void 0;
|
|
4055
|
+
return options.assigneeOptions?.find(
|
|
4056
|
+
(option) => option.value === assigneeId
|
|
4057
|
+
);
|
|
4058
|
+
}
|
|
4059
|
+
function getAssigneeName(options, assigneeId) {
|
|
4060
|
+
return getAssigneeOption(options, assigneeId)?.label;
|
|
4061
|
+
}
|
|
4062
|
+
function isTitleFieldEnabled(options) {
|
|
4063
|
+
return options.fields?.title === true;
|
|
4064
|
+
}
|
|
4065
|
+
function createDraftTitleInput(value, onInput) {
|
|
4066
|
+
const input = document.createElement("input");
|
|
4067
|
+
input.className = "dfwr-input";
|
|
4068
|
+
input.placeholder = "Title";
|
|
4069
|
+
input.type = "text";
|
|
4070
|
+
input.value = value ?? "";
|
|
4071
|
+
input.addEventListener("input", () => onInput(input.value));
|
|
4072
|
+
return input;
|
|
4073
|
+
}
|
|
4074
|
+
function createDraftAssigneeSelect(options, value, fallbackLabel, onChange) {
|
|
4075
|
+
const assigneeOptions = options.assigneeOptions ?? [];
|
|
4076
|
+
if (assigneeOptions.length === 0) return void 0;
|
|
4077
|
+
const assigneeTitle = options.assigneeTitle?.trim() || "Assignee";
|
|
4078
|
+
const select = document.createElement("select");
|
|
4079
|
+
select.className = "dfwr-select";
|
|
4080
|
+
const emptyOption = document.createElement("option");
|
|
4081
|
+
emptyOption.value = "";
|
|
4082
|
+
emptyOption.textContent = assigneeTitle;
|
|
4083
|
+
select.append(emptyOption);
|
|
4084
|
+
const hasUnknownAssignee = Boolean(value) && !assigneeOptions.some((option) => option.value === value);
|
|
4085
|
+
if (hasUnknownAssignee && value) {
|
|
4086
|
+
const option = document.createElement("option");
|
|
4087
|
+
option.value = value;
|
|
4088
|
+
option.textContent = fallbackLabel ?? value;
|
|
4089
|
+
select.append(option);
|
|
4090
|
+
}
|
|
4091
|
+
assigneeOptions.forEach((assigneeOption) => {
|
|
4092
|
+
const option = document.createElement("option");
|
|
4093
|
+
option.value = assigneeOption.value;
|
|
4094
|
+
option.textContent = assigneeOption.label;
|
|
4095
|
+
select.append(option);
|
|
4096
|
+
});
|
|
4097
|
+
select.value = value ?? "";
|
|
4098
|
+
select.addEventListener("change", () => {
|
|
4099
|
+
onChange(select.value || null, getAssigneeName(options, select.value));
|
|
4100
|
+
});
|
|
4101
|
+
return select;
|
|
4102
|
+
}
|
|
4103
|
+
function getDraftFields(options, titleInput, textarea, assigneeSelect) {
|
|
4104
|
+
const title = titleInput?.value.trim();
|
|
4105
|
+
const comment = textarea.value.trim();
|
|
4106
|
+
const assigneeId = assigneeSelect?.value.trim() || void 0;
|
|
4107
|
+
return {
|
|
4108
|
+
title: title || void 0,
|
|
4109
|
+
comment,
|
|
4110
|
+
assigneeId,
|
|
4111
|
+
assigneeName: getAssigneeName(options, assigneeId)
|
|
4112
|
+
};
|
|
4113
|
+
}
|
|
4114
|
+
function createFormActions({
|
|
4115
|
+
saveLabel,
|
|
4116
|
+
onSave,
|
|
4117
|
+
onCancel,
|
|
4118
|
+
isSaving,
|
|
4119
|
+
beforeSave,
|
|
4120
|
+
className,
|
|
4121
|
+
leading
|
|
4122
|
+
}) {
|
|
4123
|
+
const actions = document.createElement("div");
|
|
4124
|
+
actions.className = ["dfwr-actions", className].filter(Boolean).join(" ");
|
|
4125
|
+
const save = document.createElement("button");
|
|
4126
|
+
save.className = "dfwr-button is-primary";
|
|
4127
|
+
save.type = "button";
|
|
4128
|
+
save.disabled = isSaving;
|
|
4129
|
+
save.setAttribute("aria-busy", isSaving ? "true" : "false");
|
|
4130
|
+
if (isSaving) {
|
|
4131
|
+
save.append(createSpinner("dfwr-spinner"), "Saving...");
|
|
4132
|
+
} else {
|
|
4133
|
+
save.textContent = saveLabel;
|
|
4134
|
+
}
|
|
4135
|
+
save.addEventListener("click", (event) => {
|
|
4136
|
+
event.preventDefault();
|
|
4137
|
+
event.stopPropagation();
|
|
4138
|
+
if (save.disabled) return;
|
|
4139
|
+
onSave();
|
|
4140
|
+
});
|
|
4141
|
+
const cancel = document.createElement("button");
|
|
4142
|
+
cancel.className = "dfwr-button";
|
|
4143
|
+
cancel.type = "button";
|
|
4144
|
+
cancel.disabled = isSaving;
|
|
4145
|
+
cancel.textContent = "Cancel";
|
|
4146
|
+
cancel.addEventListener("click", (event) => {
|
|
4147
|
+
onCancel(event);
|
|
4148
|
+
});
|
|
4149
|
+
if (leading?.length) {
|
|
4150
|
+
actions.classList.add("has-leading");
|
|
4151
|
+
const leadingGroup = document.createElement("div");
|
|
4152
|
+
leadingGroup.className = "dfwr-actions-leading";
|
|
4153
|
+
leadingGroup.append(...leading);
|
|
4154
|
+
const primary = document.createElement("div");
|
|
4155
|
+
primary.className = "dfwr-actions-primary";
|
|
4156
|
+
primary.append(save, cancel);
|
|
4157
|
+
actions.append(leadingGroup, primary);
|
|
4158
|
+
return actions;
|
|
3784
4159
|
}
|
|
3785
|
-
|
|
3786
|
-
|
|
4160
|
+
if (beforeSave?.length || className) {
|
|
4161
|
+
actions.append(cancel, ...beforeSave ?? [], save);
|
|
4162
|
+
return actions;
|
|
3787
4163
|
}
|
|
3788
|
-
|
|
3789
|
-
|
|
3790
|
-
|
|
3791
|
-
|
|
3792
|
-
|
|
3793
|
-
|
|
3794
|
-
|
|
3795
|
-
|
|
3796
|
-
|
|
3797
|
-
|
|
3798
|
-
|
|
3799
|
-
|
|
3800
|
-
|
|
3801
|
-
|
|
3802
|
-
|
|
3803
|
-
|
|
3804
|
-
|
|
3805
|
-
|
|
3806
|
-
|
|
3807
|
-
|
|
3808
|
-
|
|
3809
|
-
|
|
3810
|
-
|
|
3811
|
-
|
|
3812
|
-
|
|
3813
|
-
|
|
3814
|
-
|
|
3815
|
-
if (!this.hasDraftAdjustment(draft)) return comment;
|
|
3816
|
-
const trimmedComment = comment.trim();
|
|
3817
|
-
const metrics = this.getDraftAdjustmentMetrics(draft);
|
|
3818
|
-
const adjustment = [
|
|
3819
|
-
`${this.getAdjustmentLabel()}: x ${this.formatSignedPx(
|
|
3820
|
-
metrics.x
|
|
3821
|
-
)}, y ${this.formatSignedPx(metrics.y)}, scale ${this.formatSignedPx(
|
|
3822
|
-
metrics.scale
|
|
3823
|
-
)}`,
|
|
3824
|
-
`(${metrics.presetLabel} viewport, ${Math.round(
|
|
3825
|
-
metrics.viewportWidth
|
|
3826
|
-
)}/design ${Math.round(metrics.designWidth)})`
|
|
3827
|
-
].join(" ");
|
|
3828
|
-
return trimmedComment ? `${trimmedComment}
|
|
3829
|
-
${adjustment}` : adjustment;
|
|
4164
|
+
actions.append(save, cancel);
|
|
4165
|
+
return actions;
|
|
4166
|
+
}
|
|
4167
|
+
function createDraftError(message) {
|
|
4168
|
+
if (!message) return void 0;
|
|
4169
|
+
const error = document.createElement("p");
|
|
4170
|
+
error.className = "dfwr-form-error";
|
|
4171
|
+
error.setAttribute("role", "alert");
|
|
4172
|
+
error.textContent = message;
|
|
4173
|
+
return error;
|
|
4174
|
+
}
|
|
4175
|
+
function createDraftDragHandle(label) {
|
|
4176
|
+
const handle = document.createElement("button");
|
|
4177
|
+
handle.className = "dfwr-draft-drag-handle";
|
|
4178
|
+
handle.type = "button";
|
|
4179
|
+
handle.setAttribute("aria-label", label);
|
|
4180
|
+
return handle;
|
|
4181
|
+
}
|
|
4182
|
+
|
|
4183
|
+
// src/core/review/format.ts
|
|
4184
|
+
function formatDomDraftMeta(draft) {
|
|
4185
|
+
const parts = [
|
|
4186
|
+
`viewport ${formatSize(draft.viewport)}`,
|
|
4187
|
+
`point ${formatPoint(draft.marker.viewport)}`
|
|
4188
|
+
];
|
|
4189
|
+
if (draft.anchor) {
|
|
4190
|
+
parts.push(formatAnchorMeta(draft.anchor));
|
|
3830
4191
|
}
|
|
3831
|
-
|
|
3832
|
-
|
|
3833
|
-
|
|
3834
|
-
|
|
3835
|
-
|
|
4192
|
+
return parts.join(" / ");
|
|
4193
|
+
}
|
|
4194
|
+
function formatItemMeta(item) {
|
|
4195
|
+
const parts = [formatDate(item.createdAt)];
|
|
4196
|
+
const marker = getItemMarker(item);
|
|
4197
|
+
const selection = getItemSelection(item);
|
|
4198
|
+
if (item.viewport) {
|
|
4199
|
+
parts.push(`viewport ${formatSize(item.viewport)}`);
|
|
3836
4200
|
}
|
|
3837
|
-
|
|
3838
|
-
|
|
4201
|
+
if (marker) {
|
|
4202
|
+
parts.push(`point ${formatPoint(marker.viewport)}`);
|
|
3839
4203
|
}
|
|
3840
|
-
|
|
3841
|
-
|
|
3842
|
-
input.className = "dfwr-input";
|
|
3843
|
-
input.placeholder = "Title";
|
|
3844
|
-
input.type = "text";
|
|
3845
|
-
input.value = value ?? "";
|
|
3846
|
-
input.addEventListener("input", () => onInput(input.value));
|
|
3847
|
-
return input;
|
|
4204
|
+
if (selection) {
|
|
4205
|
+
parts.push(`rect ${formatSelection(selection.viewport)}`);
|
|
3848
4206
|
}
|
|
3849
|
-
|
|
3850
|
-
|
|
3851
|
-
}
|
|
3852
|
-
createDraftAssigneeSelect(value, fallbackLabel, onChange) {
|
|
3853
|
-
const assigneeOptions = this.config.options.assigneeOptions ?? [];
|
|
3854
|
-
if (assigneeOptions.length === 0) return void 0;
|
|
3855
|
-
const assigneeTitle = this.config.options.assigneeTitle?.trim() || "Assignee";
|
|
3856
|
-
const select = document.createElement("select");
|
|
3857
|
-
select.className = "dfwr-select";
|
|
3858
|
-
const emptyOption = document.createElement("option");
|
|
3859
|
-
emptyOption.value = "";
|
|
3860
|
-
emptyOption.textContent = assigneeTitle;
|
|
3861
|
-
select.append(emptyOption);
|
|
3862
|
-
const hasUnknownAssignee = Boolean(value) && !assigneeOptions.some((option) => option.value === value);
|
|
3863
|
-
if (hasUnknownAssignee && value) {
|
|
3864
|
-
const option = document.createElement("option");
|
|
3865
|
-
option.value = value;
|
|
3866
|
-
option.textContent = fallbackLabel ?? value;
|
|
3867
|
-
select.append(option);
|
|
3868
|
-
}
|
|
3869
|
-
assigneeOptions.forEach((assigneeOption) => {
|
|
3870
|
-
const option = document.createElement("option");
|
|
3871
|
-
option.value = assigneeOption.value;
|
|
3872
|
-
option.textContent = assigneeOption.label;
|
|
3873
|
-
select.append(option);
|
|
3874
|
-
});
|
|
3875
|
-
select.value = value ?? "";
|
|
3876
|
-
select.addEventListener("change", () => {
|
|
3877
|
-
onChange(select.value || null, this.getAssigneeName(select.value));
|
|
3878
|
-
});
|
|
3879
|
-
return select;
|
|
4207
|
+
if (item.anchor) {
|
|
4208
|
+
parts.push(formatAnchorMeta(item.anchor));
|
|
3880
4209
|
}
|
|
3881
|
-
|
|
3882
|
-
|
|
3883
|
-
|
|
3884
|
-
|
|
3885
|
-
|
|
3886
|
-
|
|
3887
|
-
|
|
3888
|
-
|
|
3889
|
-
|
|
3890
|
-
|
|
4210
|
+
return parts.join(" / ");
|
|
4211
|
+
}
|
|
4212
|
+
function formatDate(value) {
|
|
4213
|
+
const date = new Date(value);
|
|
4214
|
+
if (Number.isNaN(date.getTime())) return value;
|
|
4215
|
+
return date.toLocaleString(void 0, {
|
|
4216
|
+
month: "2-digit",
|
|
4217
|
+
day: "2-digit",
|
|
4218
|
+
hour: "2-digit",
|
|
4219
|
+
minute: "2-digit"
|
|
4220
|
+
});
|
|
4221
|
+
}
|
|
4222
|
+
function formatSize(size) {
|
|
4223
|
+
return `${Math.round(size.width)}x${Math.round(size.height)}`;
|
|
4224
|
+
}
|
|
4225
|
+
function formatPoint(point) {
|
|
4226
|
+
return `${Math.round(point.x)},${Math.round(point.y)}`;
|
|
4227
|
+
}
|
|
4228
|
+
function formatSelection(selection) {
|
|
4229
|
+
return [
|
|
4230
|
+
Math.round(selection.x),
|
|
4231
|
+
Math.round(selection.y),
|
|
4232
|
+
Math.round(selection.width),
|
|
4233
|
+
Math.round(selection.height)
|
|
4234
|
+
].join(",");
|
|
4235
|
+
}
|
|
4236
|
+
function formatAnchorMeta(anchor) {
|
|
4237
|
+
const parts = [`dom ${anchor.strategy}`];
|
|
4238
|
+
if (typeof anchor.confidence === "number") {
|
|
4239
|
+
parts.push(`${Math.round(anchor.confidence * 100)}%`);
|
|
3891
4240
|
}
|
|
3892
|
-
|
|
3893
|
-
|
|
3894
|
-
|
|
3895
|
-
}
|
|
3896
|
-
if (!draft.anchor) return void 0;
|
|
3897
|
-
const preferredSelection = draft.selection ? toViewportSelection(draft.selection.viewport) : void 0;
|
|
3898
|
-
const element = resolveAnchorElement(
|
|
3899
|
-
draft.anchor,
|
|
3900
|
-
environment,
|
|
3901
|
-
preferredSelection
|
|
3902
|
-
)?.element;
|
|
3903
|
-
if (!element) return void 0;
|
|
3904
|
-
if ("style" in element) return element;
|
|
3905
|
-
return void 0;
|
|
4241
|
+
const candidates = getAnchorCandidates(anchor);
|
|
4242
|
+
if (candidates.length > 1) {
|
|
4243
|
+
parts.push(`${candidates.length} candidates`);
|
|
3906
4244
|
}
|
|
3907
|
-
|
|
3908
|
-
|
|
3909
|
-
|
|
3910
|
-
|
|
3911
|
-
|
|
3912
|
-
|
|
3913
|
-
|
|
3914
|
-
|
|
3915
|
-
|
|
4245
|
+
return parts.join(" ");
|
|
4246
|
+
}
|
|
4247
|
+
|
|
4248
|
+
// src/core/view/markers.ts
|
|
4249
|
+
function createMarkerElement(itemId, hostPoint, label, scope, isBound, isHighlighted) {
|
|
4250
|
+
const marker = document.createElement("div");
|
|
4251
|
+
marker.className = [
|
|
4252
|
+
"dfwr-bound-marker",
|
|
4253
|
+
`is-scope-${scope}`,
|
|
4254
|
+
isBound ? "is-bound" : "is-fallback",
|
|
4255
|
+
isHighlighted ? "is-highlighted" : ""
|
|
4256
|
+
].filter(Boolean).join(" ");
|
|
4257
|
+
marker.style.left = `${hostPoint.x}px`;
|
|
4258
|
+
marker.style.top = `${hostPoint.y}px`;
|
|
4259
|
+
marker.dataset.scope = scope;
|
|
4260
|
+
if (itemId) {
|
|
4261
|
+
marker.dataset.reviewItemId = itemId;
|
|
4262
|
+
}
|
|
4263
|
+
const iconElement = document.createElement("span");
|
|
4264
|
+
iconElement.className = "dfwr-bound-marker-icon";
|
|
4265
|
+
iconElement.setAttribute("aria-hidden", "true");
|
|
4266
|
+
const labelElement = document.createElement("span");
|
|
4267
|
+
labelElement.className = "dfwr-bound-marker-number";
|
|
4268
|
+
labelElement.textContent = label;
|
|
4269
|
+
marker.append(iconElement, labelElement);
|
|
4270
|
+
return marker;
|
|
4271
|
+
}
|
|
4272
|
+
function createSelectionHighlight(selection, environment, isDraft) {
|
|
4273
|
+
const rect = toHostSelection(selection, environment);
|
|
4274
|
+
const highlight = document.createElement("div");
|
|
4275
|
+
highlight.className = `dfwr-selection-highlight${isDraft ? " is-draft" : ""}`;
|
|
4276
|
+
highlight.style.left = `${rect.left}px`;
|
|
4277
|
+
highlight.style.top = `${rect.top}px`;
|
|
4278
|
+
highlight.style.width = `${rect.width}px`;
|
|
4279
|
+
highlight.style.height = `${rect.height}px`;
|
|
4280
|
+
return highlight;
|
|
4281
|
+
}
|
|
4282
|
+
function createItemHighlightElements(selection, environment, item, label, scope, isBound, isHighlighted) {
|
|
4283
|
+
const rect = toHostSelection(selection, environment);
|
|
4284
|
+
const mode = getReviewItemHighlightMode(item);
|
|
4285
|
+
const highlight = document.createElement("div");
|
|
4286
|
+
highlight.className = [
|
|
4287
|
+
"dfwr-item-target-highlight",
|
|
4288
|
+
`is-mode-${mode}`,
|
|
4289
|
+
`is-scope-${scope}`,
|
|
4290
|
+
isBound ? "is-bound" : "is-fallback",
|
|
4291
|
+
isHighlighted ? "is-highlighted" : ""
|
|
4292
|
+
].filter(Boolean).join(" ");
|
|
4293
|
+
highlight.style.left = `${rect.left}px`;
|
|
4294
|
+
highlight.style.top = `${rect.top}px`;
|
|
4295
|
+
highlight.style.width = `${rect.width}px`;
|
|
4296
|
+
highlight.style.height = `${rect.height}px`;
|
|
4297
|
+
highlight.dataset.reviewItemId = item.id;
|
|
4298
|
+
const labelElement = document.createElement("div");
|
|
4299
|
+
labelElement.className = [
|
|
4300
|
+
"dfwr-item-target-label",
|
|
4301
|
+
`is-mode-${mode}`,
|
|
4302
|
+
`is-scope-${scope}`,
|
|
4303
|
+
isHighlighted ? "is-highlighted" : ""
|
|
4304
|
+
].filter(Boolean).join(" ");
|
|
4305
|
+
labelElement.textContent = label;
|
|
4306
|
+
labelElement.style.left = `${Math.max(4, rect.left)}px`;
|
|
4307
|
+
labelElement.style.top = `${Math.max(4, rect.top - 24)}px`;
|
|
4308
|
+
labelElement.dataset.reviewItemId = item.id;
|
|
4309
|
+
return [highlight, labelElement];
|
|
4310
|
+
}
|
|
4311
|
+
function createMarkerLayer({
|
|
4312
|
+
items,
|
|
4313
|
+
highlightedItemId,
|
|
4314
|
+
environment,
|
|
4315
|
+
presets,
|
|
4316
|
+
showCompactMarkers = true
|
|
4317
|
+
}) {
|
|
4318
|
+
const layer = document.createElement("div");
|
|
4319
|
+
layer.className = "dfwr-marker-layer";
|
|
4320
|
+
if (!environment) return layer;
|
|
4321
|
+
const currentScope = getReviewViewportScope(
|
|
4322
|
+
getViewportSize(environment),
|
|
4323
|
+
presets
|
|
4324
|
+
);
|
|
4325
|
+
getNumberedReviewItems(items, presets).forEach((numberedItem) => {
|
|
4326
|
+
const { item, scope, displayLabel } = numberedItem;
|
|
4327
|
+
if (!shouldShowMarkerForScope(scope, currentScope)) {
|
|
3916
4328
|
return;
|
|
3917
4329
|
}
|
|
3918
|
-
|
|
3919
|
-
|
|
3920
|
-
|
|
3921
|
-
|
|
3922
|
-
|
|
3923
|
-
|
|
3924
|
-
|
|
3925
|
-
|
|
3926
|
-
|
|
3927
|
-
|
|
3928
|
-
|
|
3929
|
-
|
|
3930
|
-
|
|
3931
|
-
|
|
3932
|
-
};
|
|
3933
|
-
element.style.visibility = "hidden";
|
|
3934
|
-
}
|
|
3935
|
-
const metrics = this.getDraftAdjustmentMetrics(draft);
|
|
3936
|
-
const translate = `translate(${this.toCssNumber(metrics.cssX)}px, ${this.toCssNumber(
|
|
3937
|
-
metrics.cssY
|
|
3938
|
-
)}px)`;
|
|
3939
|
-
const scale = metrics.scaleFactor === 1 ? "" : `scale(${this.toCssNumber(metrics.scaleFactor)})`;
|
|
3940
|
-
this.draftPreview.clone.style.transform = [translate, scale].filter(Boolean).join(" ");
|
|
3941
|
-
}
|
|
3942
|
-
restoreDraftPreview() {
|
|
3943
|
-
if (!this.draftPreview) return;
|
|
3944
|
-
const { element, clone, visibility } = this.draftPreview;
|
|
3945
|
-
clone.remove();
|
|
3946
|
-
element.style.visibility = visibility;
|
|
3947
|
-
this.draftPreview = void 0;
|
|
3948
|
-
}
|
|
3949
|
-
positionDraftPreviewClone(clone, element, computedStyle) {
|
|
3950
|
-
const rect = element.getBoundingClientRect();
|
|
3951
|
-
clone.setAttribute("data-dfwr-adjust-preview", "true");
|
|
3952
|
-
clone.setAttribute("aria-hidden", "true");
|
|
3953
|
-
clone.style.position = "fixed";
|
|
3954
|
-
clone.style.left = `${this.toCssNumber(rect.left)}px`;
|
|
3955
|
-
clone.style.top = `${this.toCssNumber(rect.top)}px`;
|
|
3956
|
-
clone.style.right = "auto";
|
|
3957
|
-
clone.style.bottom = "auto";
|
|
3958
|
-
clone.style.width = `${this.toCssNumber(rect.width)}px`;
|
|
3959
|
-
clone.style.height = `${this.toCssNumber(rect.height)}px`;
|
|
3960
|
-
clone.style.maxWidth = "none";
|
|
3961
|
-
clone.style.maxHeight = "none";
|
|
3962
|
-
clone.style.margin = "0";
|
|
3963
|
-
clone.style.boxSizing = "border-box";
|
|
3964
|
-
clone.style.display = this.getDraftPreviewDisplay(computedStyle.display);
|
|
3965
|
-
clone.style.zIndex = "2147483646";
|
|
3966
|
-
clone.style.pointerEvents = "none";
|
|
3967
|
-
clone.style.transition = "none";
|
|
3968
|
-
clone.style.willChange = "transform";
|
|
3969
|
-
clone.style.transformOrigin = "top left";
|
|
3970
|
-
clone.style.transform = "none";
|
|
3971
|
-
}
|
|
3972
|
-
getDraftPreviewDisplay(display) {
|
|
3973
|
-
if (display === "inline" || display === "contents") return "inline-block";
|
|
3974
|
-
return display || "block";
|
|
3975
|
-
}
|
|
3976
|
-
copyComputedStyle(element, clone, environment) {
|
|
3977
|
-
const computedStyle = environment.window.getComputedStyle(element);
|
|
3978
|
-
for (let index = 0; index < computedStyle.length; index += 1) {
|
|
3979
|
-
const property = computedStyle.item(index);
|
|
3980
|
-
clone.style.setProperty(
|
|
3981
|
-
property,
|
|
3982
|
-
computedStyle.getPropertyValue(property),
|
|
3983
|
-
computedStyle.getPropertyPriority(property)
|
|
3984
|
-
);
|
|
3985
|
-
}
|
|
3986
|
-
}
|
|
3987
|
-
removeDuplicateIds(element) {
|
|
3988
|
-
element.removeAttribute("id");
|
|
3989
|
-
element.querySelectorAll("[id]").forEach((child) => {
|
|
3990
|
-
child.removeAttribute("id");
|
|
3991
|
-
});
|
|
3992
|
-
}
|
|
3993
|
-
toCssNumber(value) {
|
|
3994
|
-
return Math.round(value * 1e3) / 1e3;
|
|
3995
|
-
}
|
|
3996
|
-
createHeader() {
|
|
3997
|
-
const header = document.createElement("div");
|
|
3998
|
-
header.className = "dfwr-header";
|
|
3999
|
-
const title = document.createElement("div");
|
|
4000
|
-
title.className = "dfwr-title";
|
|
4001
|
-
title.textContent = "Review Kit";
|
|
4002
|
-
const meta = document.createElement("div");
|
|
4003
|
-
meta.className = "dfwr-meta";
|
|
4004
|
-
meta.textContent = getRouteKey(this.config.getEnvironment());
|
|
4005
|
-
const titleGroup = document.createElement("div");
|
|
4006
|
-
titleGroup.append(title, meta);
|
|
4007
|
-
const close = document.createElement("button");
|
|
4008
|
-
close.className = "dfwr-icon-button";
|
|
4009
|
-
close.type = "button";
|
|
4010
|
-
close.textContent = "x";
|
|
4011
|
-
close.setAttribute("aria-label", "Close");
|
|
4012
|
-
close.addEventListener("click", () => this.config.actions.close());
|
|
4013
|
-
header.append(titleGroup, close);
|
|
4014
|
-
return header;
|
|
4015
|
-
}
|
|
4016
|
-
createToolbar() {
|
|
4017
|
-
const toolbar = document.createElement("div");
|
|
4018
|
-
toolbar.className = "dfwr-toolbar";
|
|
4019
|
-
toolbar.append(
|
|
4020
|
-
this.createToolbarButton("Note", this.state.mode === "note", () => {
|
|
4021
|
-
const mode = this.state.mode;
|
|
4022
|
-
this.config.actions.setModeState(mode === "note" ? "idle" : "note");
|
|
4023
|
-
this.config.actions.clearDrafts();
|
|
4024
|
-
this.config.actions.render();
|
|
4025
|
-
}),
|
|
4026
|
-
this.createToolbarButton("Element", this.state.mode === "element", () => {
|
|
4027
|
-
const mode = this.state.mode;
|
|
4028
|
-
this.config.actions.setModeState(
|
|
4029
|
-
mode === "element" ? "idle" : "element"
|
|
4330
|
+
const isHighlighted = item.id === highlightedItemId;
|
|
4331
|
+
if (isHighlighted) {
|
|
4332
|
+
const selection = getItemHighlightSelection(item, environment);
|
|
4333
|
+
if (selection) {
|
|
4334
|
+
layer.append(
|
|
4335
|
+
...createItemHighlightElements(
|
|
4336
|
+
selection.viewport,
|
|
4337
|
+
environment,
|
|
4338
|
+
item,
|
|
4339
|
+
displayLabel,
|
|
4340
|
+
scope,
|
|
4341
|
+
selection.isBound,
|
|
4342
|
+
isHighlighted
|
|
4343
|
+
)
|
|
4030
4344
|
);
|
|
4031
|
-
|
|
4032
|
-
this.config.actions.render();
|
|
4033
|
-
}),
|
|
4034
|
-
this.createToolbarButton(
|
|
4035
|
-
this.state.isSelectingArea ? "Selecting" : "Area",
|
|
4036
|
-
this.state.mode === "area",
|
|
4037
|
-
() => {
|
|
4038
|
-
const mode = this.state.mode;
|
|
4039
|
-
this.config.actions.setModeState(mode === "area" ? "idle" : "area");
|
|
4040
|
-
this.config.actions.clearDrafts();
|
|
4041
|
-
this.config.actions.render();
|
|
4042
|
-
}
|
|
4043
|
-
),
|
|
4044
|
-
this.createToolbarButton("Refresh", false, () => {
|
|
4045
|
-
void this.config.actions.reload();
|
|
4046
|
-
})
|
|
4047
|
-
);
|
|
4048
|
-
return toolbar;
|
|
4049
|
-
}
|
|
4050
|
-
createToolbarButton(label, active, onClick) {
|
|
4051
|
-
const button = document.createElement("button");
|
|
4052
|
-
button.className = `dfwr-button${active ? " is-active" : ""}`;
|
|
4053
|
-
button.type = "button";
|
|
4054
|
-
button.textContent = label;
|
|
4055
|
-
button.addEventListener("click", onClick);
|
|
4056
|
-
return button;
|
|
4057
|
-
}
|
|
4058
|
-
createBody() {
|
|
4059
|
-
const body = document.createElement("div");
|
|
4060
|
-
body.className = "dfwr-body";
|
|
4061
|
-
const state = this.state;
|
|
4062
|
-
if (state.mode === "idle") {
|
|
4063
|
-
const empty = document.createElement("p");
|
|
4064
|
-
empty.className = "dfwr-empty";
|
|
4065
|
-
empty.textContent = "Add a note or mark an area.";
|
|
4066
|
-
body.append(empty);
|
|
4067
|
-
return body;
|
|
4068
|
-
}
|
|
4069
|
-
if (state.mode === "note" || state.mode === "element") {
|
|
4070
|
-
body.append(this.createNoteBody());
|
|
4071
|
-
return body;
|
|
4072
|
-
}
|
|
4073
|
-
body.append(this.createAreaForm());
|
|
4074
|
-
return body;
|
|
4075
|
-
}
|
|
4076
|
-
createNoteBody() {
|
|
4077
|
-
const empty = document.createElement("p");
|
|
4078
|
-
empty.className = "dfwr-empty";
|
|
4079
|
-
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.";
|
|
4080
|
-
return empty;
|
|
4081
|
-
}
|
|
4082
|
-
// Builds the note draft layer: the on-page marker/highlight plus its composer
|
|
4083
|
-
// popover. When dockComposer is set the composer renders into the side panel
|
|
4084
|
-
// instead of floating next to the marker (used for the docked review mode).
|
|
4085
|
-
createNotePopover(draft, options = {}) {
|
|
4086
|
-
const environment = this.config.getEnvironment();
|
|
4087
|
-
const group = document.createElement("div");
|
|
4088
|
-
group.className = "dfwr-note-draft";
|
|
4089
|
-
if (!environment) return { layer: group, composer: void 0 };
|
|
4090
|
-
const isElementDraft = this.state.mode === "element" && Boolean(draft.selection);
|
|
4091
|
-
const hostPoint = toHostPoint(
|
|
4092
|
-
isElementDraft ? this.getAdjustedDraftPoint(draft.marker.viewport, draft) : draft.marker.viewport,
|
|
4093
|
-
environment
|
|
4094
|
-
);
|
|
4095
|
-
let selectionHighlight;
|
|
4096
|
-
if (draft.selection) {
|
|
4097
|
-
const selection = toViewportSelection(draft.selection.viewport);
|
|
4098
|
-
selectionHighlight = this.createSelectionHighlight(
|
|
4099
|
-
isElementDraft ? this.getAdjustedDraftSelection(selection, draft) : selection,
|
|
4100
|
-
environment,
|
|
4101
|
-
true
|
|
4102
|
-
);
|
|
4103
|
-
group.append(selectionHighlight);
|
|
4104
|
-
}
|
|
4105
|
-
const pin = document.createElement("button");
|
|
4106
|
-
pin.className = "dfwr-note-pin";
|
|
4107
|
-
pin.type = "button";
|
|
4108
|
-
pin.setAttribute("aria-label", "Move note point");
|
|
4109
|
-
pin.style.left = `${hostPoint.x}px`;
|
|
4110
|
-
pin.style.top = `${hostPoint.y}px`;
|
|
4111
|
-
const popover = document.createElement("div");
|
|
4112
|
-
const position = getPopoverPosition(hostPoint, environment);
|
|
4113
|
-
popover.className = [
|
|
4114
|
-
"dfwr-note-popover",
|
|
4115
|
-
isElementDraft ? "is-composer" : "",
|
|
4116
|
-
options.dockComposer ? "is-docked-composer" : ""
|
|
4117
|
-
].filter(Boolean).join(" ");
|
|
4118
|
-
if (options.dockComposer) {
|
|
4119
|
-
popover.style.width = "100%";
|
|
4120
|
-
} else if (isElementDraft) {
|
|
4121
|
-
const selection = draft.selection ? toHostSelection(
|
|
4122
|
-
this.getAdjustedDraftSelection(
|
|
4123
|
-
toViewportSelection(draft.selection.viewport),
|
|
4124
|
-
draft
|
|
4125
|
-
),
|
|
4126
|
-
environment
|
|
4127
|
-
) : void 0;
|
|
4128
|
-
const composer = this.getDraftComposerPosition({
|
|
4129
|
-
selection,
|
|
4130
|
-
environment,
|
|
4131
|
-
composerPosition: draft.composerPosition,
|
|
4132
|
-
estimatedHeight: 252
|
|
4133
|
-
});
|
|
4134
|
-
popover.style.left = `${composer.left}px`;
|
|
4135
|
-
popover.style.top = `${composer.top}px`;
|
|
4136
|
-
popover.style.width = `${composer.width}px`;
|
|
4137
|
-
} else {
|
|
4138
|
-
popover.style.left = `${position.left}px`;
|
|
4139
|
-
popover.style.top = `${position.top}px`;
|
|
4140
|
-
}
|
|
4141
|
-
const form = document.createElement("form");
|
|
4142
|
-
form.className = "dfwr-form";
|
|
4143
|
-
const meta = isElementDraft ? void 0 : document.createElement("div");
|
|
4144
|
-
if (meta) {
|
|
4145
|
-
meta.className = "dfwr-item-date";
|
|
4146
|
-
meta.textContent = formatNoteDraftMeta(draft);
|
|
4147
|
-
}
|
|
4148
|
-
const titleInput = this.isTitleFieldEnabled() ? this.createDraftTitleInput(draft.title, (title) => {
|
|
4149
|
-
const noteDraft = this.state.noteDraft;
|
|
4150
|
-
if (!noteDraft) return;
|
|
4151
|
-
this.config.actions.setNoteDraft({
|
|
4152
|
-
...noteDraft,
|
|
4153
|
-
title
|
|
4154
|
-
});
|
|
4155
|
-
}) : void 0;
|
|
4156
|
-
const textarea = document.createElement("textarea");
|
|
4157
|
-
textarea.className = "dfwr-textarea";
|
|
4158
|
-
textarea.placeholder = "Review comment";
|
|
4159
|
-
textarea.rows = 4;
|
|
4160
|
-
textarea.value = draft.comment ?? "";
|
|
4161
|
-
textarea.addEventListener("input", () => {
|
|
4162
|
-
const noteDraft = this.state.noteDraft;
|
|
4163
|
-
if (!noteDraft) return;
|
|
4164
|
-
this.config.actions.setNoteDraft({
|
|
4165
|
-
...noteDraft,
|
|
4166
|
-
comment: textarea.value
|
|
4167
|
-
});
|
|
4168
|
-
});
|
|
4169
|
-
const assigneeSelect = this.createDraftAssigneeSelect(
|
|
4170
|
-
draft.assigneeId,
|
|
4171
|
-
draft.assigneeName,
|
|
4172
|
-
(assigneeId, assigneeName) => {
|
|
4173
|
-
const noteDraft = this.state.noteDraft;
|
|
4174
|
-
if (!noteDraft) return;
|
|
4175
|
-
this.config.actions.setNoteDraft({
|
|
4176
|
-
...noteDraft,
|
|
4177
|
-
assigneeId,
|
|
4178
|
-
assigneeName
|
|
4179
|
-
});
|
|
4345
|
+
return;
|
|
4180
4346
|
}
|
|
4181
|
-
);
|
|
4182
|
-
const saveDraft = () => {
|
|
4183
|
-
const currentDraft = this.state.noteDraft ?? draft;
|
|
4184
|
-
const fields = this.getDraftFields(titleInput, textarea, assigneeSelect);
|
|
4185
|
-
const comment = fields.comment;
|
|
4186
|
-
if (!comment && !this.hasDraftAdjustment(currentDraft)) return;
|
|
4187
|
-
void this.config.actions.createItem({
|
|
4188
|
-
kind: "note",
|
|
4189
|
-
title: fields.title,
|
|
4190
|
-
comment: this.withDraftAdjustmentComment(comment, currentDraft),
|
|
4191
|
-
assigneeId: fields.assigneeId,
|
|
4192
|
-
assigneeName: fields.assigneeName,
|
|
4193
|
-
viewport: currentDraft.viewport,
|
|
4194
|
-
anchor: currentDraft.anchor,
|
|
4195
|
-
marker: currentDraft.marker,
|
|
4196
|
-
selection: currentDraft.selection
|
|
4197
|
-
});
|
|
4198
|
-
};
|
|
4199
|
-
const adjustmentControls = isElementDraft ? this.createAdjustmentControls({
|
|
4200
|
-
draft,
|
|
4201
|
-
pin,
|
|
4202
|
-
popover,
|
|
4203
|
-
selectionHighlight,
|
|
4204
|
-
textarea,
|
|
4205
|
-
dockToggle: options.dockComposer
|
|
4206
|
-
}) : void 0;
|
|
4207
|
-
const actions = this.createFormActions("Save note", saveDraft, {
|
|
4208
|
-
leading: adjustmentControls?.actionButton ? [adjustmentControls.actionButton] : void 0
|
|
4209
|
-
});
|
|
4210
|
-
const error = this.createDraftError();
|
|
4211
|
-
form.append(
|
|
4212
|
-
...meta ? [meta] : [],
|
|
4213
|
-
...adjustmentControls ? [adjustmentControls.panel] : [],
|
|
4214
|
-
...titleInput ? [titleInput] : [],
|
|
4215
|
-
textarea,
|
|
4216
|
-
...assigneeSelect ? [assigneeSelect] : [],
|
|
4217
|
-
...error ? [error] : [],
|
|
4218
|
-
actions
|
|
4219
|
-
);
|
|
4220
|
-
const dragHandle = isElementDraft && !options.dockComposer ? this.createDraftDragHandle("Move DOM composer") : void 0;
|
|
4221
|
-
popover.append(
|
|
4222
|
-
...dragHandle ? [dragHandle] : [],
|
|
4223
|
-
form
|
|
4224
|
-
);
|
|
4225
|
-
group.append(pin);
|
|
4226
|
-
if (!options.dockComposer) {
|
|
4227
|
-
group.append(popover);
|
|
4228
|
-
}
|
|
4229
|
-
if (dragHandle) {
|
|
4230
|
-
this.attachDraftComposerDrag(popover, dragHandle, (composerPosition) => {
|
|
4231
|
-
const noteDraft = this.state.noteDraft ?? draft;
|
|
4232
|
-
this.config.actions.setNoteDraft({
|
|
4233
|
-
...noteDraft,
|
|
4234
|
-
composerPosition,
|
|
4235
|
-
comment: textarea.value
|
|
4236
|
-
});
|
|
4237
|
-
});
|
|
4238
4347
|
}
|
|
4239
|
-
|
|
4240
|
-
|
|
4241
|
-
isElementDraft || options.dockComposer ? void 0 : popover,
|
|
4242
|
-
meta,
|
|
4243
|
-
textarea
|
|
4244
|
-
);
|
|
4245
|
-
if (!options.dockComposer) {
|
|
4246
|
-
window.setTimeout(() => {
|
|
4247
|
-
if (draft.adjustment?.isActive) {
|
|
4248
|
-
adjustmentControls?.focusTarget.focus();
|
|
4249
|
-
return;
|
|
4250
|
-
}
|
|
4251
|
-
textarea.focus();
|
|
4252
|
-
}, 0);
|
|
4348
|
+
if (!showCompactMarkers && !isHighlighted) {
|
|
4349
|
+
return;
|
|
4253
4350
|
}
|
|
4254
|
-
|
|
4255
|
-
|
|
4256
|
-
|
|
4257
|
-
};
|
|
4258
|
-
}
|
|
4259
|
-
createDraftDragHandle(label) {
|
|
4260
|
-
const handle = document.createElement("button");
|
|
4261
|
-
handle.className = "dfwr-draft-drag-handle";
|
|
4262
|
-
handle.type = "button";
|
|
4263
|
-
handle.setAttribute("aria-label", label);
|
|
4264
|
-
return handle;
|
|
4265
|
-
}
|
|
4266
|
-
createIcon(paths) {
|
|
4267
|
-
const svg = document.createElementNS("http://www.w3.org/2000/svg", "svg");
|
|
4268
|
-
svg.setAttribute("aria-hidden", "true");
|
|
4269
|
-
svg.setAttribute("viewBox", "0 0 24 24");
|
|
4270
|
-
svg.setAttribute("fill", "none");
|
|
4271
|
-
svg.setAttribute("stroke", "currentColor");
|
|
4272
|
-
svg.setAttribute("stroke-width", "2.4");
|
|
4273
|
-
svg.setAttribute("stroke-linecap", "round");
|
|
4274
|
-
svg.setAttribute("stroke-linejoin", "round");
|
|
4275
|
-
paths.forEach((d) => {
|
|
4276
|
-
const path = document.createElementNS("http://www.w3.org/2000/svg", "path");
|
|
4277
|
-
path.setAttribute("d", d);
|
|
4278
|
-
svg.append(path);
|
|
4279
|
-
});
|
|
4280
|
-
return svg;
|
|
4281
|
-
}
|
|
4282
|
-
setAdjustmentToggleIcon(button, isActive) {
|
|
4283
|
-
const paths = isActive ? ["M20 6 9 17l-5-5"] : [
|
|
4284
|
-
"M12 2v20",
|
|
4285
|
-
"M2 12h20",
|
|
4286
|
-
"m9 5 3-3 3 3",
|
|
4287
|
-
"m9 19 3 3 3-3",
|
|
4288
|
-
"m5 9-3 3 3 3",
|
|
4289
|
-
"m19 9 3 3-3 3"
|
|
4290
|
-
];
|
|
4291
|
-
button.replaceChildren(this.createIcon(paths));
|
|
4292
|
-
}
|
|
4293
|
-
attachDraftComposerDrag(popover, handle, onMove) {
|
|
4294
|
-
let isDragging = false;
|
|
4295
|
-
let offsetX = 0;
|
|
4296
|
-
let offsetY = 0;
|
|
4297
|
-
const movePopover = (event) => {
|
|
4298
|
-
const environment = this.config.getEnvironment();
|
|
4299
|
-
if (!environment) return;
|
|
4300
|
-
const position = this.getClampedComposerPosition(
|
|
4301
|
-
{
|
|
4302
|
-
x: event.clientX - offsetX,
|
|
4303
|
-
y: event.clientY - offsetY
|
|
4304
|
-
},
|
|
4305
|
-
environment,
|
|
4306
|
-
{
|
|
4307
|
-
width: popover.offsetWidth,
|
|
4308
|
-
height: popover.offsetHeight
|
|
4309
|
-
},
|
|
4310
|
-
this.getHostComposerBounds()
|
|
4311
|
-
);
|
|
4312
|
-
popover.style.left = `${position.x}px`;
|
|
4313
|
-
popover.style.top = `${position.y}px`;
|
|
4314
|
-
onMove(position);
|
|
4315
|
-
};
|
|
4316
|
-
handle.addEventListener("pointerdown", (event) => {
|
|
4317
|
-
if (event.button !== 0) return;
|
|
4318
|
-
const rect = popover.getBoundingClientRect();
|
|
4319
|
-
offsetX = event.clientX - rect.left;
|
|
4320
|
-
offsetY = event.clientY - rect.top;
|
|
4321
|
-
isDragging = true;
|
|
4322
|
-
event.preventDefault();
|
|
4323
|
-
event.stopPropagation();
|
|
4324
|
-
handle.setPointerCapture(event.pointerId);
|
|
4325
|
-
popover.classList.add("is-dragging");
|
|
4326
|
-
});
|
|
4327
|
-
handle.addEventListener("pointermove", (event) => {
|
|
4328
|
-
if (!isDragging || !handle.hasPointerCapture(event.pointerId)) return;
|
|
4329
|
-
event.preventDefault();
|
|
4330
|
-
movePopover(event);
|
|
4331
|
-
});
|
|
4332
|
-
const stopDrag = (event) => {
|
|
4333
|
-
if (!isDragging || !handle.hasPointerCapture(event.pointerId)) return;
|
|
4334
|
-
event.preventDefault();
|
|
4335
|
-
event.stopPropagation();
|
|
4336
|
-
isDragging = false;
|
|
4337
|
-
handle.releasePointerCapture(event.pointerId);
|
|
4338
|
-
popover.classList.remove("is-dragging");
|
|
4339
|
-
movePopover(event);
|
|
4340
|
-
};
|
|
4341
|
-
handle.addEventListener("pointerup", stopDrag);
|
|
4342
|
-
handle.addEventListener("pointercancel", stopDrag);
|
|
4343
|
-
}
|
|
4344
|
-
// Builds the element-adjustment controls (nudge the previewed element via
|
|
4345
|
-
// arrow keys / buttons). Wires keyboard deltas to the draft transform and
|
|
4346
|
-
// keeps the pin, popover, highlight and textarea in sync as the value changes.
|
|
4347
|
-
createAdjustmentControls({
|
|
4348
|
-
draft,
|
|
4349
|
-
pin,
|
|
4350
|
-
popover,
|
|
4351
|
-
selectionHighlight,
|
|
4352
|
-
textarea,
|
|
4353
|
-
dockToggle
|
|
4354
|
-
}) {
|
|
4355
|
-
const panel = document.createElement("div");
|
|
4356
|
-
panel.className = "dfwr-adjust-panel is-dom-adjust-panel";
|
|
4357
|
-
const header = document.createElement("div");
|
|
4358
|
-
header.className = "dfwr-adjust-panel-header";
|
|
4359
|
-
const help = document.createElement("div");
|
|
4360
|
-
help.className = "dfwr-adjust-help";
|
|
4361
|
-
help.textContent = this.getAdjustmentLabel();
|
|
4362
|
-
const adjust = document.createElement("button");
|
|
4363
|
-
adjust.className = "dfwr-adjust-toggle";
|
|
4364
|
-
adjust.type = "button";
|
|
4365
|
-
adjust.title = "Adjust DOM element with keyboard arrows";
|
|
4366
|
-
adjust.setAttribute("aria-label", "Adjust DOM element with keyboard arrows");
|
|
4367
|
-
const xyStatus = document.createElement("div");
|
|
4368
|
-
xyStatus.className = "dfwr-adjust-status";
|
|
4369
|
-
const scaleStatus = document.createElement("div");
|
|
4370
|
-
scaleStatus.className = "dfwr-adjust-status";
|
|
4371
|
-
const syncControls = (nextDraft) => {
|
|
4372
|
-
const isActive = nextDraft.adjustment?.isActive === true;
|
|
4373
|
-
panel.classList.toggle("is-active", isActive);
|
|
4374
|
-
adjust.classList.toggle("is-active", isActive);
|
|
4375
|
-
adjust.setAttribute("aria-pressed", isActive ? "true" : "false");
|
|
4376
|
-
this.setAdjustmentToggleIcon(adjust, isActive);
|
|
4377
|
-
adjust.title = isActive ? "Finish DOM adjustment" : "Adjust DOM element with keyboard arrows";
|
|
4378
|
-
adjust.setAttribute(
|
|
4379
|
-
"aria-label",
|
|
4380
|
-
isActive ? "Finish DOM adjustment" : "Adjust DOM element with keyboard arrows"
|
|
4381
|
-
);
|
|
4382
|
-
const [xyLine, scaleLine] = this.getDraftAdjustmentMetricLines(nextDraft);
|
|
4383
|
-
xyStatus.textContent = xyLine;
|
|
4384
|
-
scaleStatus.textContent = scaleLine;
|
|
4385
|
-
this.syncDraftAdjustmentUi({
|
|
4386
|
-
draft: nextDraft,
|
|
4387
|
-
pin,
|
|
4388
|
-
selectionHighlight
|
|
4389
|
-
});
|
|
4390
|
-
};
|
|
4391
|
-
const updateDraft = (updater) => {
|
|
4392
|
-
const currentDraft = this.state.noteDraft ?? draft;
|
|
4393
|
-
const nextDraft = updater(currentDraft);
|
|
4394
|
-
this.config.actions.setNoteDraft({
|
|
4395
|
-
...nextDraft,
|
|
4396
|
-
comment: textarea.value
|
|
4397
|
-
});
|
|
4398
|
-
syncControls(nextDraft);
|
|
4399
|
-
};
|
|
4400
|
-
adjust.addEventListener("click", () => {
|
|
4401
|
-
updateDraft((currentDraft) => ({
|
|
4402
|
-
...currentDraft,
|
|
4403
|
-
adjustment: {
|
|
4404
|
-
x: currentDraft.adjustment?.x ?? 0,
|
|
4405
|
-
y: currentDraft.adjustment?.y ?? 0,
|
|
4406
|
-
scale: currentDraft.adjustment?.scale ?? 0,
|
|
4407
|
-
isActive: currentDraft.adjustment?.isActive !== true
|
|
4408
|
-
}
|
|
4409
|
-
}));
|
|
4410
|
-
adjust.focus();
|
|
4411
|
-
});
|
|
4412
|
-
popover.addEventListener("keydown", (event) => {
|
|
4413
|
-
const currentDraft = this.state.noteDraft ?? draft;
|
|
4414
|
-
if (currentDraft.adjustment?.isActive !== true) return;
|
|
4415
|
-
const keyDelta = this.getAdjustmentKeyDelta(event);
|
|
4416
|
-
if (!keyDelta) return;
|
|
4417
|
-
event.preventDefault();
|
|
4418
|
-
event.stopPropagation();
|
|
4419
|
-
updateDraft((activeDraft) => ({
|
|
4420
|
-
...activeDraft,
|
|
4421
|
-
adjustment: {
|
|
4422
|
-
x: (activeDraft.adjustment?.x ?? 0) + keyDelta.x,
|
|
4423
|
-
y: (activeDraft.adjustment?.y ?? 0) + keyDelta.y,
|
|
4424
|
-
scale: (activeDraft.adjustment?.scale ?? 0) + keyDelta.scale,
|
|
4425
|
-
isActive: true
|
|
4426
|
-
}
|
|
4427
|
-
}));
|
|
4428
|
-
});
|
|
4429
|
-
header.append(help);
|
|
4430
|
-
if (!dockToggle) {
|
|
4431
|
-
header.append(adjust);
|
|
4351
|
+
const point = getBoundMarkerPoint(item, environment);
|
|
4352
|
+
if (!point || !isPointInViewport(point.viewport, environment)) {
|
|
4353
|
+
return;
|
|
4432
4354
|
}
|
|
4433
|
-
|
|
4434
|
-
|
|
4435
|
-
|
|
4436
|
-
|
|
4437
|
-
|
|
4438
|
-
|
|
4439
|
-
|
|
4440
|
-
|
|
4441
|
-
getAdjustmentKeyDelta(event) {
|
|
4442
|
-
const step = event.shiftKey ? 10 : 1;
|
|
4443
|
-
if (event.key === "ArrowLeft") return { x: -step, y: 0, scale: 0 };
|
|
4444
|
-
if (event.key === "ArrowRight") return { x: step, y: 0, scale: 0 };
|
|
4445
|
-
if (event.key === "ArrowUp") return { x: 0, y: -step, scale: 0 };
|
|
4446
|
-
if (event.key === "ArrowDown") return { x: 0, y: step, scale: 0 };
|
|
4447
|
-
if (event.key.toLowerCase() === "w") return { x: 0, y: 0, scale: step };
|
|
4448
|
-
if (event.key.toLowerCase() === "s") return { x: 0, y: 0, scale: -step };
|
|
4449
|
-
return void 0;
|
|
4450
|
-
}
|
|
4451
|
-
syncDraftAdjustmentUi({
|
|
4452
|
-
draft,
|
|
4453
|
-
pin,
|
|
4454
|
-
selectionHighlight
|
|
4455
|
-
}) {
|
|
4456
|
-
const environment = this.config.getEnvironment();
|
|
4457
|
-
if (!environment) return;
|
|
4458
|
-
const hostPoint = toHostPoint(
|
|
4459
|
-
this.getAdjustedDraftPoint(draft.marker.viewport, draft),
|
|
4460
|
-
environment
|
|
4355
|
+
const hostPoint = toHostPoint(point.viewport, environment);
|
|
4356
|
+
const marker = createMarkerElement(
|
|
4357
|
+
item.id,
|
|
4358
|
+
hostPoint,
|
|
4359
|
+
displayLabel,
|
|
4360
|
+
scope,
|
|
4361
|
+
point.isBound,
|
|
4362
|
+
isHighlighted
|
|
4461
4363
|
);
|
|
4462
|
-
|
|
4463
|
-
|
|
4464
|
-
|
|
4465
|
-
|
|
4466
|
-
|
|
4467
|
-
|
|
4468
|
-
|
|
4469
|
-
|
|
4470
|
-
|
|
4471
|
-
|
|
4472
|
-
|
|
4473
|
-
|
|
4474
|
-
|
|
4475
|
-
|
|
4476
|
-
|
|
4477
|
-
|
|
4478
|
-
|
|
4479
|
-
|
|
4480
|
-
|
|
4481
|
-
|
|
4482
|
-
|
|
4483
|
-
|
|
4484
|
-
|
|
4485
|
-
|
|
4486
|
-
|
|
4487
|
-
|
|
4488
|
-
|
|
4489
|
-
}
|
|
4490
|
-
|
|
4491
|
-
|
|
4492
|
-
|
|
4493
|
-
|
|
4494
|
-
|
|
4364
|
+
marker.title = `${displayLabel} / ${item.comment}
|
|
4365
|
+
${formatItemMeta(item)}`;
|
|
4366
|
+
layer.append(marker);
|
|
4367
|
+
});
|
|
4368
|
+
return layer;
|
|
4369
|
+
}
|
|
4370
|
+
|
|
4371
|
+
// src/core/view/area.draft.ts
|
|
4372
|
+
function createAreaForm(context) {
|
|
4373
|
+
const { config } = context;
|
|
4374
|
+
const form = document.createElement("form");
|
|
4375
|
+
form.className = "dfwr-form";
|
|
4376
|
+
const areaDraft = config.getState().areaDraft;
|
|
4377
|
+
if (!areaDraft) {
|
|
4378
|
+
const empty = document.createElement("p");
|
|
4379
|
+
empty.className = "dfwr-empty";
|
|
4380
|
+
empty.textContent = "Drag on the page to select an area.";
|
|
4381
|
+
form.append(empty);
|
|
4382
|
+
return form;
|
|
4383
|
+
}
|
|
4384
|
+
form.append(createAreaMetricsPanel(context, areaDraft));
|
|
4385
|
+
const titleInput = isTitleFieldEnabled(config.options) ? createDraftTitleInput(areaDraft.title, (title) => {
|
|
4386
|
+
const draft = config.getState().areaDraft;
|
|
4387
|
+
if (!draft) return;
|
|
4388
|
+
config.actions.setAreaDraft({
|
|
4389
|
+
...draft,
|
|
4390
|
+
title
|
|
4391
|
+
});
|
|
4392
|
+
}) : void 0;
|
|
4393
|
+
const textarea = document.createElement("textarea");
|
|
4394
|
+
textarea.className = "dfwr-textarea";
|
|
4395
|
+
textarea.placeholder = "Area comment";
|
|
4396
|
+
textarea.rows = 4;
|
|
4397
|
+
textarea.value = areaDraft.comment ?? "";
|
|
4398
|
+
textarea.addEventListener("input", () => {
|
|
4399
|
+
const draft = config.getState().areaDraft;
|
|
4400
|
+
if (!draft) return;
|
|
4401
|
+
config.actions.setAreaDraft({
|
|
4402
|
+
...draft,
|
|
4403
|
+
comment: textarea.value
|
|
4404
|
+
});
|
|
4405
|
+
});
|
|
4406
|
+
attachDraftImagePasteQueue(textarea, {
|
|
4407
|
+
getAttachments: () => config.getState().areaDraft?.attachments ?? areaDraft.attachments,
|
|
4408
|
+
onAttachmentsChange: (attachments) => {
|
|
4409
|
+
const draft = config.getState().areaDraft ?? areaDraft;
|
|
4410
|
+
config.actions.setAreaDraft({
|
|
4411
|
+
...draft,
|
|
4412
|
+
comment: textarea.value,
|
|
4413
|
+
attachments
|
|
4414
|
+
});
|
|
4415
|
+
},
|
|
4416
|
+
onCommentChange: (comment) => {
|
|
4417
|
+
const draft = config.getState().areaDraft ?? areaDraft;
|
|
4418
|
+
config.actions.setAreaDraft({
|
|
4495
4419
|
...draft,
|
|
4496
|
-
|
|
4420
|
+
comment
|
|
4497
4421
|
});
|
|
4498
|
-
}
|
|
4499
|
-
|
|
4500
|
-
|
|
4501
|
-
|
|
4502
|
-
|
|
4503
|
-
|
|
4504
|
-
|
|
4505
|
-
|
|
4422
|
+
},
|
|
4423
|
+
onPasteComplete: () => config.actions.render()
|
|
4424
|
+
});
|
|
4425
|
+
const assigneeSelect = createDraftAssigneeSelect(
|
|
4426
|
+
config.options,
|
|
4427
|
+
areaDraft.assigneeId,
|
|
4428
|
+
areaDraft.assigneeName,
|
|
4429
|
+
(assigneeId, assigneeName) => {
|
|
4430
|
+
const draft = config.getState().areaDraft;
|
|
4506
4431
|
if (!draft) return;
|
|
4507
|
-
|
|
4432
|
+
config.actions.setAreaDraft({
|
|
4508
4433
|
...draft,
|
|
4509
|
-
|
|
4434
|
+
assigneeId,
|
|
4435
|
+
assigneeName
|
|
4510
4436
|
});
|
|
4511
|
-
}
|
|
4512
|
-
|
|
4513
|
-
|
|
4514
|
-
|
|
4515
|
-
|
|
4516
|
-
|
|
4517
|
-
|
|
4518
|
-
|
|
4519
|
-
|
|
4520
|
-
|
|
4521
|
-
|
|
4522
|
-
|
|
4523
|
-
|
|
4524
|
-
|
|
4525
|
-
const actions = this.createFormActions("Save area", () => {
|
|
4526
|
-
const draft = this.state.areaDraft;
|
|
4527
|
-
const fields = this.getDraftFields(titleInput, textarea, assigneeSelect);
|
|
4437
|
+
}
|
|
4438
|
+
);
|
|
4439
|
+
const actions = createFormActions({
|
|
4440
|
+
saveLabel: "Save area",
|
|
4441
|
+
isSaving: config.getState().isCreatingItem,
|
|
4442
|
+
onCancel: context.cancelDraft,
|
|
4443
|
+
onSave: () => {
|
|
4444
|
+
const draft = config.getState().areaDraft;
|
|
4445
|
+
const fields = getDraftFields(
|
|
4446
|
+
config.options,
|
|
4447
|
+
titleInput,
|
|
4448
|
+
textarea,
|
|
4449
|
+
assigneeSelect
|
|
4450
|
+
);
|
|
4528
4451
|
const comment = fields.comment;
|
|
4529
|
-
if (!comment || !draft) return;
|
|
4530
|
-
void
|
|
4452
|
+
if (!comment && !draft?.attachments?.length || !draft) return;
|
|
4453
|
+
void config.actions.createItem({
|
|
4531
4454
|
kind: "area",
|
|
4532
4455
|
title: fields.title,
|
|
4533
4456
|
comment,
|
|
@@ -4536,603 +4459,1077 @@ ${adjustment}` : adjustment;
|
|
|
4536
4459
|
viewport: draft.viewport,
|
|
4537
4460
|
anchor: draft.anchor,
|
|
4538
4461
|
marker: draft.marker,
|
|
4539
|
-
selection: draft.selection
|
|
4462
|
+
selection: draft.selection,
|
|
4463
|
+
attachments: draft.attachments
|
|
4540
4464
|
});
|
|
4541
|
-
}
|
|
4542
|
-
|
|
4543
|
-
|
|
4544
|
-
|
|
4545
|
-
|
|
4546
|
-
|
|
4547
|
-
|
|
4548
|
-
|
|
4549
|
-
|
|
4550
|
-
|
|
4551
|
-
|
|
4552
|
-
|
|
4553
|
-
|
|
4554
|
-
|
|
4555
|
-
|
|
4556
|
-
|
|
4557
|
-
|
|
4558
|
-
this.getAreaDraftMetricSelection(draft),
|
|
4559
|
-
draft.viewport
|
|
4560
|
-
);
|
|
4561
|
-
help.textContent = labelLine;
|
|
4562
|
-
const xyStatus = document.createElement("div");
|
|
4563
|
-
xyStatus.className = "dfwr-adjust-status";
|
|
4564
|
-
xyStatus.textContent = xyLine;
|
|
4565
|
-
const sizeStatus = document.createElement("div");
|
|
4566
|
-
sizeStatus.className = "dfwr-adjust-status";
|
|
4567
|
-
sizeStatus.textContent = sizeLine;
|
|
4568
|
-
panel.append(help, xyStatus, sizeStatus);
|
|
4569
|
-
return panel;
|
|
4570
|
-
}
|
|
4571
|
-
createAreaDraftOverlay(draft) {
|
|
4572
|
-
const layer = document.createElement("div");
|
|
4573
|
-
layer.className = "dfwr-area-preview-layer";
|
|
4574
|
-
const environment = this.config.getEnvironment();
|
|
4575
|
-
if (!environment || !draft.selection) return layer;
|
|
4576
|
-
const selection = toViewportSelection(draft.selection.viewport);
|
|
4577
|
-
layer.append(this.createSelectionHighlight(selection, environment, true));
|
|
4578
|
-
if (draft.marker) {
|
|
4579
|
-
const hostPoint = toHostPoint(draft.marker.viewport, environment);
|
|
4580
|
-
layer.append(
|
|
4581
|
-
this.createMarkerElement(
|
|
4582
|
-
void 0,
|
|
4583
|
-
hostPoint,
|
|
4584
|
-
"\u2022",
|
|
4585
|
-
getReviewViewportScope(
|
|
4586
|
-
draft.viewport,
|
|
4587
|
-
this.config.options.viewports?.presets
|
|
4588
|
-
),
|
|
4589
|
-
true,
|
|
4590
|
-
true
|
|
4591
|
-
)
|
|
4465
|
+
},
|
|
4466
|
+
leading: [
|
|
4467
|
+
createDraftCaptureButton(config, areaDraft, {
|
|
4468
|
+
kind: "area",
|
|
4469
|
+
textarea
|
|
4470
|
+
})
|
|
4471
|
+
]
|
|
4472
|
+
});
|
|
4473
|
+
const error = createDraftError(config.getState().draftError);
|
|
4474
|
+
const attachmentQueue = createDraftAttachmentQueue(
|
|
4475
|
+
document,
|
|
4476
|
+
areaDraft.attachments,
|
|
4477
|
+
(attachmentId) => {
|
|
4478
|
+
const draft = config.getState().areaDraft ?? areaDraft;
|
|
4479
|
+
const attachments = removeDraftAttachment(
|
|
4480
|
+
draft.attachments,
|
|
4481
|
+
attachmentId
|
|
4592
4482
|
);
|
|
4483
|
+
config.actions.setAreaDraft({
|
|
4484
|
+
...draft,
|
|
4485
|
+
comment: textarea.value,
|
|
4486
|
+
attachments: attachments.length > 0 ? attachments : void 0
|
|
4487
|
+
});
|
|
4488
|
+
config.actions.render();
|
|
4593
4489
|
}
|
|
4594
|
-
|
|
4490
|
+
);
|
|
4491
|
+
form.append(
|
|
4492
|
+
...titleInput ? [titleInput] : [],
|
|
4493
|
+
textarea,
|
|
4494
|
+
...attachmentQueue ? [attachmentQueue] : [],
|
|
4495
|
+
...assigneeSelect ? [assigneeSelect] : [],
|
|
4496
|
+
...error ? [error] : [],
|
|
4497
|
+
actions
|
|
4498
|
+
);
|
|
4499
|
+
return form;
|
|
4500
|
+
}
|
|
4501
|
+
function createAreaMetricsPanel(context, draft) {
|
|
4502
|
+
const panel = document.createElement("div");
|
|
4503
|
+
panel.className = "dfwr-adjust-panel is-area-metrics-panel";
|
|
4504
|
+
const [labelLine, xyLine, sizeLine] = getSelectionMetricLines(
|
|
4505
|
+
getAreaDraftMetricSelection(draft),
|
|
4506
|
+
draft.viewport,
|
|
4507
|
+
context.config.options.viewports?.presets
|
|
4508
|
+
);
|
|
4509
|
+
const help = document.createElement("div");
|
|
4510
|
+
help.className = "dfwr-adjust-help";
|
|
4511
|
+
help.textContent = labelLine;
|
|
4512
|
+
const xyStatus = document.createElement("div");
|
|
4513
|
+
xyStatus.className = "dfwr-adjust-status";
|
|
4514
|
+
xyStatus.textContent = xyLine;
|
|
4515
|
+
const sizeStatus = document.createElement("div");
|
|
4516
|
+
sizeStatus.className = "dfwr-adjust-status";
|
|
4517
|
+
sizeStatus.textContent = sizeLine;
|
|
4518
|
+
panel.append(help, xyStatus, sizeStatus);
|
|
4519
|
+
return panel;
|
|
4520
|
+
}
|
|
4521
|
+
function createAreaDraftOverlay(context, draft) {
|
|
4522
|
+
const { config } = context;
|
|
4523
|
+
const layer = document.createElement("div");
|
|
4524
|
+
layer.className = "dfwr-area-preview-layer";
|
|
4525
|
+
const environment = config.getEnvironment();
|
|
4526
|
+
if (!environment || !draft.selection) return layer;
|
|
4527
|
+
const selection = toViewportSelection(draft.selection.viewport);
|
|
4528
|
+
layer.append(createSelectionHighlight(selection, environment, true));
|
|
4529
|
+
if (draft.marker) {
|
|
4530
|
+
const hostPoint = toHostPoint(draft.marker.viewport, environment);
|
|
4531
|
+
layer.append(
|
|
4532
|
+
createMarkerElement(
|
|
4533
|
+
void 0,
|
|
4534
|
+
hostPoint,
|
|
4535
|
+
"\u2022",
|
|
4536
|
+
getReviewViewportScope(
|
|
4537
|
+
draft.viewport,
|
|
4538
|
+
config.options.viewports?.presets
|
|
4539
|
+
),
|
|
4540
|
+
true,
|
|
4541
|
+
true
|
|
4542
|
+
)
|
|
4543
|
+
);
|
|
4595
4544
|
}
|
|
4596
|
-
|
|
4597
|
-
|
|
4598
|
-
|
|
4599
|
-
|
|
4600
|
-
|
|
4601
|
-
|
|
4602
|
-
|
|
4603
|
-
|
|
4604
|
-
|
|
4605
|
-
|
|
4606
|
-
|
|
4607
|
-
|
|
4608
|
-
|
|
4609
|
-
|
|
4610
|
-
|
|
4611
|
-
|
|
4612
|
-
|
|
4613
|
-
environment,
|
|
4614
|
-
composerPosition: draft.composerPosition,
|
|
4615
|
-
estimatedHeight: 220
|
|
4616
|
-
});
|
|
4617
|
-
popover.style.left = `${composer.left}px`;
|
|
4618
|
-
popover.style.top = `${composer.top}px`;
|
|
4619
|
-
popover.style.width = `${composer.width}px`;
|
|
4620
|
-
popover.style.right = "auto";
|
|
4621
|
-
}
|
|
4622
|
-
const dragHandle = options.dockComposer ? void 0 : this.createDraftDragHandle("Move area composer");
|
|
4623
|
-
popover.append(
|
|
4624
|
-
...dragHandle ? [dragHandle] : [],
|
|
4625
|
-
this.createAreaForm()
|
|
4545
|
+
return layer;
|
|
4546
|
+
}
|
|
4547
|
+
function createAreaDraftPopover(context, draft, options = {}) {
|
|
4548
|
+
const { config } = context;
|
|
4549
|
+
const environment = config.getEnvironment();
|
|
4550
|
+
const popover = document.createElement("div");
|
|
4551
|
+
popover.className = [
|
|
4552
|
+
"dfwr-area-draft",
|
|
4553
|
+
"is-composer",
|
|
4554
|
+
options.dockComposer ? "is-docked-composer" : ""
|
|
4555
|
+
].filter(Boolean).join(" ");
|
|
4556
|
+
if (options.dockComposer) {
|
|
4557
|
+
popover.style.width = "100%";
|
|
4558
|
+
} else if (environment && draft.selection) {
|
|
4559
|
+
const selection = toHostSelection(
|
|
4560
|
+
toViewportSelection(draft.selection.viewport),
|
|
4561
|
+
environment
|
|
4626
4562
|
);
|
|
4627
|
-
|
|
4628
|
-
|
|
4629
|
-
|
|
4630
|
-
|
|
4563
|
+
const composer = getDraftComposerPosition({
|
|
4564
|
+
selection,
|
|
4565
|
+
environment,
|
|
4566
|
+
composerPosition: draft.composerPosition,
|
|
4567
|
+
estimatedHeight: 220
|
|
4568
|
+
});
|
|
4569
|
+
popover.style.left = `${composer.left}px`;
|
|
4570
|
+
popover.style.top = `${composer.top}px`;
|
|
4571
|
+
popover.style.width = `${composer.width}px`;
|
|
4572
|
+
popover.style.right = "auto";
|
|
4573
|
+
}
|
|
4574
|
+
const dragHandle = options.dockComposer ? void 0 : createDraftDragHandle("Move area composer");
|
|
4575
|
+
popover.append(
|
|
4576
|
+
...dragHandle ? [dragHandle] : [],
|
|
4577
|
+
createAreaForm(context)
|
|
4578
|
+
);
|
|
4579
|
+
if (dragHandle) {
|
|
4580
|
+
attachDraftComposerDrag({
|
|
4581
|
+
getEnvironment: () => config.getEnvironment(),
|
|
4582
|
+
popover,
|
|
4583
|
+
handle: dragHandle,
|
|
4584
|
+
onMove: (composerPosition) => {
|
|
4585
|
+
const areaDraft = config.getState().areaDraft ?? draft;
|
|
4586
|
+
config.actions.setAreaDraft({
|
|
4631
4587
|
...areaDraft,
|
|
4632
4588
|
composerPosition
|
|
4633
4589
|
});
|
|
4590
|
+
}
|
|
4591
|
+
});
|
|
4592
|
+
}
|
|
4593
|
+
return popover;
|
|
4594
|
+
}
|
|
4595
|
+
|
|
4596
|
+
// src/core/view/dom.draft.ts
|
|
4597
|
+
function createDomDraftLayer(context, draft, options = {}) {
|
|
4598
|
+
const { config } = context;
|
|
4599
|
+
const presets = config.options.viewports?.presets;
|
|
4600
|
+
const environment = config.getEnvironment();
|
|
4601
|
+
const group = document.createElement("div");
|
|
4602
|
+
group.className = "dfwr-dom-draft";
|
|
4603
|
+
if (!environment) return { layer: group, composer: void 0 };
|
|
4604
|
+
const isElementDraft = config.getState().mode === "element" && Boolean(draft.selection);
|
|
4605
|
+
const hostPoint = toHostPoint(
|
|
4606
|
+
isElementDraft ? getAdjustedDraftPoint(draft.marker.viewport, draft, presets) : draft.marker.viewport,
|
|
4607
|
+
environment
|
|
4608
|
+
);
|
|
4609
|
+
let selectionHighlight;
|
|
4610
|
+
if (draft.selection) {
|
|
4611
|
+
const selection = toViewportSelection(draft.selection.viewport);
|
|
4612
|
+
selectionHighlight = createSelectionHighlight(
|
|
4613
|
+
isElementDraft ? getAdjustedDraftSelection(selection, draft, presets) : selection,
|
|
4614
|
+
environment,
|
|
4615
|
+
true
|
|
4616
|
+
);
|
|
4617
|
+
group.append(selectionHighlight);
|
|
4618
|
+
}
|
|
4619
|
+
const pin = document.createElement("button");
|
|
4620
|
+
pin.className = "dfwr-dom-pin";
|
|
4621
|
+
pin.type = "button";
|
|
4622
|
+
pin.setAttribute("aria-label", "Move DOM point");
|
|
4623
|
+
pin.style.left = `${hostPoint.x}px`;
|
|
4624
|
+
pin.style.top = `${hostPoint.y}px`;
|
|
4625
|
+
const popover = document.createElement("div");
|
|
4626
|
+
const position = getPopoverPosition(hostPoint, environment);
|
|
4627
|
+
popover.className = [
|
|
4628
|
+
"dfwr-dom-popover",
|
|
4629
|
+
isElementDraft ? "is-composer" : "",
|
|
4630
|
+
options.dockComposer ? "is-docked-composer" : ""
|
|
4631
|
+
].filter(Boolean).join(" ");
|
|
4632
|
+
if (options.dockComposer) {
|
|
4633
|
+
popover.style.width = "100%";
|
|
4634
|
+
} else if (isElementDraft) {
|
|
4635
|
+
const selection = draft.selection ? toHostSelection(
|
|
4636
|
+
getAdjustedDraftSelection(
|
|
4637
|
+
toViewportSelection(draft.selection.viewport),
|
|
4638
|
+
draft,
|
|
4639
|
+
presets
|
|
4640
|
+
),
|
|
4641
|
+
environment
|
|
4642
|
+
) : void 0;
|
|
4643
|
+
const composer = getDraftComposerPosition({
|
|
4644
|
+
selection,
|
|
4645
|
+
environment,
|
|
4646
|
+
composerPosition: draft.composerPosition,
|
|
4647
|
+
estimatedHeight: 252
|
|
4648
|
+
});
|
|
4649
|
+
popover.style.left = `${composer.left}px`;
|
|
4650
|
+
popover.style.top = `${composer.top}px`;
|
|
4651
|
+
popover.style.width = `${composer.width}px`;
|
|
4652
|
+
} else {
|
|
4653
|
+
popover.style.left = `${position.left}px`;
|
|
4654
|
+
popover.style.top = `${position.top}px`;
|
|
4655
|
+
}
|
|
4656
|
+
const form = document.createElement("form");
|
|
4657
|
+
form.className = "dfwr-form";
|
|
4658
|
+
const meta = isElementDraft ? void 0 : document.createElement("div");
|
|
4659
|
+
if (meta) {
|
|
4660
|
+
meta.className = "dfwr-item-date";
|
|
4661
|
+
meta.textContent = formatDomDraftMeta(draft);
|
|
4662
|
+
}
|
|
4663
|
+
const titleInput = isTitleFieldEnabled(config.options) ? createDraftTitleInput(draft.title, (title) => {
|
|
4664
|
+
const domDraft = config.getState().domDraft;
|
|
4665
|
+
if (!domDraft) return;
|
|
4666
|
+
config.actions.setDomDraft({
|
|
4667
|
+
...domDraft,
|
|
4668
|
+
title
|
|
4669
|
+
});
|
|
4670
|
+
}) : void 0;
|
|
4671
|
+
const textarea = document.createElement("textarea");
|
|
4672
|
+
textarea.className = "dfwr-textarea";
|
|
4673
|
+
textarea.placeholder = "Review comment";
|
|
4674
|
+
textarea.rows = 4;
|
|
4675
|
+
textarea.value = draft.comment ?? "";
|
|
4676
|
+
textarea.addEventListener("input", () => {
|
|
4677
|
+
const domDraft = config.getState().domDraft;
|
|
4678
|
+
if (!domDraft) return;
|
|
4679
|
+
config.actions.setDomDraft({
|
|
4680
|
+
...domDraft,
|
|
4681
|
+
comment: textarea.value
|
|
4682
|
+
});
|
|
4683
|
+
});
|
|
4684
|
+
attachDraftImagePasteQueue(textarea, {
|
|
4685
|
+
getAttachments: () => config.getState().domDraft?.attachments ?? draft.attachments,
|
|
4686
|
+
onAttachmentsChange: (attachments) => {
|
|
4687
|
+
const domDraft = config.getState().domDraft ?? draft;
|
|
4688
|
+
config.actions.setDomDraft({
|
|
4689
|
+
...domDraft,
|
|
4690
|
+
comment: textarea.value,
|
|
4691
|
+
attachments
|
|
4692
|
+
});
|
|
4693
|
+
},
|
|
4694
|
+
onCommentChange: (comment) => {
|
|
4695
|
+
const domDraft = config.getState().domDraft ?? draft;
|
|
4696
|
+
config.actions.setDomDraft({
|
|
4697
|
+
...domDraft,
|
|
4698
|
+
comment
|
|
4699
|
+
});
|
|
4700
|
+
},
|
|
4701
|
+
onPasteComplete: () => config.actions.render()
|
|
4702
|
+
});
|
|
4703
|
+
const assigneeSelect = createDraftAssigneeSelect(
|
|
4704
|
+
config.options,
|
|
4705
|
+
draft.assigneeId,
|
|
4706
|
+
draft.assigneeName,
|
|
4707
|
+
(assigneeId, assigneeName) => {
|
|
4708
|
+
const domDraft = config.getState().domDraft;
|
|
4709
|
+
if (!domDraft) return;
|
|
4710
|
+
config.actions.setDomDraft({
|
|
4711
|
+
...domDraft,
|
|
4712
|
+
assigneeId,
|
|
4713
|
+
assigneeName
|
|
4634
4714
|
});
|
|
4635
4715
|
}
|
|
4636
|
-
|
|
4637
|
-
|
|
4638
|
-
|
|
4639
|
-
const
|
|
4640
|
-
|
|
4641
|
-
|
|
4642
|
-
|
|
4643
|
-
|
|
4644
|
-
|
|
4645
|
-
|
|
4646
|
-
|
|
4647
|
-
if (
|
|
4648
|
-
|
|
4649
|
-
}
|
|
4650
|
-
|
|
4651
|
-
|
|
4652
|
-
|
|
4653
|
-
|
|
4654
|
-
|
|
4655
|
-
|
|
4656
|
-
|
|
4716
|
+
);
|
|
4717
|
+
const saveDraft = () => {
|
|
4718
|
+
const currentDraft = config.getState().domDraft ?? draft;
|
|
4719
|
+
const fields = getDraftFields(
|
|
4720
|
+
config.options,
|
|
4721
|
+
titleInput,
|
|
4722
|
+
textarea,
|
|
4723
|
+
assigneeSelect
|
|
4724
|
+
);
|
|
4725
|
+
const comment = fields.comment;
|
|
4726
|
+
const hasAttachments = Boolean(currentDraft.attachments?.length);
|
|
4727
|
+
if (!comment && !hasDraftAdjustment(currentDraft, presets) && !hasAttachments) {
|
|
4728
|
+
return;
|
|
4729
|
+
}
|
|
4730
|
+
void config.actions.createItem({
|
|
4731
|
+
kind: "dom",
|
|
4732
|
+
title: fields.title,
|
|
4733
|
+
comment: withDraftAdjustmentComment(
|
|
4734
|
+
comment,
|
|
4735
|
+
currentDraft,
|
|
4736
|
+
config.options
|
|
4737
|
+
),
|
|
4738
|
+
assigneeId: fields.assigneeId,
|
|
4739
|
+
assigneeName: fields.assigneeName,
|
|
4740
|
+
viewport: currentDraft.viewport,
|
|
4741
|
+
anchor: currentDraft.anchor,
|
|
4742
|
+
marker: currentDraft.marker,
|
|
4743
|
+
selection: currentDraft.selection,
|
|
4744
|
+
attachments: currentDraft.attachments
|
|
4657
4745
|
});
|
|
4658
|
-
|
|
4659
|
-
|
|
4660
|
-
|
|
4661
|
-
|
|
4662
|
-
|
|
4663
|
-
|
|
4664
|
-
|
|
4746
|
+
};
|
|
4747
|
+
const adjustmentControls = isElementDraft ? createAdjustmentControls(context, {
|
|
4748
|
+
draft,
|
|
4749
|
+
pin,
|
|
4750
|
+
popover,
|
|
4751
|
+
selectionHighlight,
|
|
4752
|
+
textarea,
|
|
4753
|
+
dockToggle: options.dockComposer
|
|
4754
|
+
}) : void 0;
|
|
4755
|
+
const leadingActions = [
|
|
4756
|
+
adjustmentControls?.actionButton,
|
|
4757
|
+
createDraftCaptureButton(config, draft, {
|
|
4758
|
+
kind: "dom",
|
|
4759
|
+
isElementDraft,
|
|
4760
|
+
textarea
|
|
4761
|
+
})
|
|
4762
|
+
].filter((element) => Boolean(element));
|
|
4763
|
+
const actions = createFormActions({
|
|
4764
|
+
saveLabel: "Save DOM QA",
|
|
4765
|
+
isSaving: config.getState().isCreatingItem,
|
|
4766
|
+
onSave: saveDraft,
|
|
4767
|
+
onCancel: context.cancelDraft,
|
|
4768
|
+
leading: leadingActions.length > 0 ? leadingActions : void 0
|
|
4769
|
+
});
|
|
4770
|
+
const error = createDraftError(config.getState().draftError);
|
|
4771
|
+
const attachmentQueue = createDraftAttachmentQueue(
|
|
4772
|
+
document,
|
|
4773
|
+
draft.attachments,
|
|
4774
|
+
(attachmentId) => {
|
|
4775
|
+
const domDraft = config.getState().domDraft ?? draft;
|
|
4776
|
+
const attachments = removeDraftAttachment(
|
|
4777
|
+
domDraft.attachments,
|
|
4778
|
+
attachmentId
|
|
4779
|
+
);
|
|
4780
|
+
config.actions.setDomDraft({
|
|
4781
|
+
...domDraft,
|
|
4782
|
+
comment: textarea.value,
|
|
4783
|
+
attachments: attachments.length > 0 ? attachments : void 0
|
|
4784
|
+
});
|
|
4785
|
+
config.actions.render();
|
|
4786
|
+
}
|
|
4787
|
+
);
|
|
4788
|
+
form.append(
|
|
4789
|
+
...meta ? [meta] : [],
|
|
4790
|
+
...adjustmentControls ? [adjustmentControls.panel] : [],
|
|
4791
|
+
...titleInput ? [titleInput] : [],
|
|
4792
|
+
textarea,
|
|
4793
|
+
...attachmentQueue ? [attachmentQueue] : [],
|
|
4794
|
+
...assigneeSelect ? [assigneeSelect] : [],
|
|
4795
|
+
...error ? [error] : [],
|
|
4796
|
+
actions
|
|
4797
|
+
);
|
|
4798
|
+
const dragHandle = isElementDraft && !options.dockComposer ? createDraftDragHandle("Move DOM composer") : void 0;
|
|
4799
|
+
popover.append(...dragHandle ? [dragHandle] : [], form);
|
|
4800
|
+
group.append(pin);
|
|
4801
|
+
if (!options.dockComposer) {
|
|
4802
|
+
group.append(popover);
|
|
4803
|
+
}
|
|
4804
|
+
if (dragHandle) {
|
|
4805
|
+
attachDraftComposerDrag({
|
|
4806
|
+
getEnvironment: () => config.getEnvironment(),
|
|
4807
|
+
popover,
|
|
4808
|
+
handle: dragHandle,
|
|
4809
|
+
onMove: (composerPosition) => {
|
|
4810
|
+
const domDraft = config.getState().domDraft ?? draft;
|
|
4811
|
+
config.actions.setDomDraft({
|
|
4812
|
+
...domDraft,
|
|
4813
|
+
composerPosition,
|
|
4814
|
+
comment: textarea.value
|
|
4815
|
+
});
|
|
4816
|
+
}
|
|
4665
4817
|
});
|
|
4666
|
-
if (options?.leading?.length) {
|
|
4667
|
-
actions.classList.add("has-leading");
|
|
4668
|
-
const leading = document.createElement("div");
|
|
4669
|
-
leading.className = "dfwr-actions-leading";
|
|
4670
|
-
leading.append(...options.leading);
|
|
4671
|
-
const primary = document.createElement("div");
|
|
4672
|
-
primary.className = "dfwr-actions-primary";
|
|
4673
|
-
primary.append(save, cancel);
|
|
4674
|
-
actions.append(leading, primary);
|
|
4675
|
-
return actions;
|
|
4676
|
-
}
|
|
4677
|
-
if (options?.beforeSave?.length || options?.className) {
|
|
4678
|
-
actions.append(cancel, ...options.beforeSave ?? [], save);
|
|
4679
|
-
return actions;
|
|
4680
|
-
}
|
|
4681
|
-
actions.append(save, cancel);
|
|
4682
|
-
return actions;
|
|
4683
4818
|
}
|
|
4684
|
-
|
|
4685
|
-
|
|
4686
|
-
|
|
4687
|
-
|
|
4688
|
-
|
|
4689
|
-
}
|
|
4690
|
-
|
|
4691
|
-
|
|
4692
|
-
|
|
4693
|
-
|
|
4694
|
-
|
|
4695
|
-
|
|
4696
|
-
|
|
4697
|
-
|
|
4698
|
-
createList() {
|
|
4699
|
-
const section = document.createElement("div");
|
|
4700
|
-
section.className = "dfwr-list";
|
|
4701
|
-
const state = this.state;
|
|
4702
|
-
const heading = document.createElement("div");
|
|
4703
|
-
heading.className = "dfwr-list-heading";
|
|
4704
|
-
heading.textContent = `Review items (${state.items.length})`;
|
|
4705
|
-
section.append(heading);
|
|
4706
|
-
if (state.items.length === 0) {
|
|
4707
|
-
const empty = document.createElement("p");
|
|
4708
|
-
empty.className = "dfwr-empty";
|
|
4709
|
-
empty.textContent = "No review items on this page.";
|
|
4710
|
-
section.append(empty);
|
|
4711
|
-
return section;
|
|
4712
|
-
}
|
|
4713
|
-
for (const numberedItem of getNumberedReviewItems(
|
|
4714
|
-
state.items,
|
|
4715
|
-
this.config.options.viewports?.presets
|
|
4716
|
-
)) {
|
|
4717
|
-
section.append(this.createListItem(numberedItem));
|
|
4718
|
-
}
|
|
4719
|
-
return section;
|
|
4819
|
+
attachDraftPinDrag(context, {
|
|
4820
|
+
pin,
|
|
4821
|
+
popover: isElementDraft || options.dockComposer ? void 0 : popover,
|
|
4822
|
+
meta,
|
|
4823
|
+
textarea
|
|
4824
|
+
});
|
|
4825
|
+
if (!options.dockComposer) {
|
|
4826
|
+
window.setTimeout(() => {
|
|
4827
|
+
if (draft.adjustment?.isActive) {
|
|
4828
|
+
adjustmentControls?.focusTarget.focus();
|
|
4829
|
+
return;
|
|
4830
|
+
}
|
|
4831
|
+
textarea.focus();
|
|
4832
|
+
}, 0);
|
|
4720
4833
|
}
|
|
4721
|
-
|
|
4722
|
-
|
|
4723
|
-
|
|
4724
|
-
|
|
4725
|
-
|
|
4726
|
-
|
|
4727
|
-
|
|
4834
|
+
return {
|
|
4835
|
+
layer: group,
|
|
4836
|
+
composer: options.dockComposer ? popover : void 0
|
|
4837
|
+
};
|
|
4838
|
+
}
|
|
4839
|
+
function createAdjustmentControls(context, {
|
|
4840
|
+
draft,
|
|
4841
|
+
pin,
|
|
4842
|
+
popover,
|
|
4843
|
+
selectionHighlight,
|
|
4844
|
+
textarea,
|
|
4845
|
+
dockToggle
|
|
4846
|
+
}) {
|
|
4847
|
+
const { config } = context;
|
|
4848
|
+
const presets = config.options.viewports?.presets;
|
|
4849
|
+
const panel = document.createElement("div");
|
|
4850
|
+
panel.className = "dfwr-adjust-panel is-dom-adjust-panel";
|
|
4851
|
+
const header = document.createElement("div");
|
|
4852
|
+
header.className = "dfwr-adjust-panel-header";
|
|
4853
|
+
const help = document.createElement("div");
|
|
4854
|
+
help.className = "dfwr-adjust-help";
|
|
4855
|
+
help.textContent = getAdjustmentLabel(config.options);
|
|
4856
|
+
const adjust = document.createElement("button");
|
|
4857
|
+
adjust.className = "dfwr-adjust-toggle";
|
|
4858
|
+
adjust.type = "button";
|
|
4859
|
+
adjust.title = "Adjust DOM element with keyboard arrows";
|
|
4860
|
+
adjust.setAttribute("aria-label", "Adjust DOM element with keyboard arrows");
|
|
4861
|
+
const xyStatus = document.createElement("div");
|
|
4862
|
+
xyStatus.className = "dfwr-adjust-status";
|
|
4863
|
+
const scaleStatus = document.createElement("div");
|
|
4864
|
+
scaleStatus.className = "dfwr-adjust-status";
|
|
4865
|
+
const syncControls = (nextDraft) => {
|
|
4866
|
+
const isActive = nextDraft.adjustment?.isActive === true;
|
|
4867
|
+
panel.classList.toggle("is-active", isActive);
|
|
4868
|
+
adjust.classList.toggle("is-active", isActive);
|
|
4869
|
+
adjust.setAttribute("aria-pressed", isActive ? "true" : "false");
|
|
4870
|
+
setAdjustmentToggleIcon(adjust, isActive);
|
|
4871
|
+
adjust.title = isActive ? "Finish DOM adjustment" : "Adjust DOM element with keyboard arrows";
|
|
4872
|
+
adjust.setAttribute(
|
|
4728
4873
|
"aria-label",
|
|
4729
|
-
|
|
4874
|
+
isActive ? "Finish DOM adjustment" : "Adjust DOM element with keyboard arrows"
|
|
4875
|
+
);
|
|
4876
|
+
const [xyLine, scaleLine] = getDraftAdjustmentMetricLines(
|
|
4877
|
+
nextDraft,
|
|
4878
|
+
presets
|
|
4730
4879
|
);
|
|
4731
|
-
|
|
4732
|
-
|
|
4880
|
+
xyStatus.textContent = xyLine;
|
|
4881
|
+
scaleStatus.textContent = scaleLine;
|
|
4882
|
+
syncDraftAdjustmentUi(context, {
|
|
4883
|
+
draft: nextDraft,
|
|
4884
|
+
pin,
|
|
4885
|
+
selectionHighlight
|
|
4733
4886
|
});
|
|
4734
|
-
|
|
4735
|
-
|
|
4736
|
-
|
|
4737
|
-
|
|
4887
|
+
};
|
|
4888
|
+
const updateDraft = (updater) => {
|
|
4889
|
+
const currentDraft = config.getState().domDraft ?? draft;
|
|
4890
|
+
const nextDraft = updater(currentDraft);
|
|
4891
|
+
config.actions.setDomDraft({
|
|
4892
|
+
...nextDraft,
|
|
4893
|
+
comment: textarea.value
|
|
4738
4894
|
});
|
|
4739
|
-
|
|
4740
|
-
|
|
4741
|
-
|
|
4742
|
-
|
|
4743
|
-
|
|
4744
|
-
|
|
4745
|
-
|
|
4746
|
-
|
|
4747
|
-
|
|
4748
|
-
|
|
4749
|
-
|
|
4750
|
-
|
|
4751
|
-
|
|
4752
|
-
|
|
4753
|
-
|
|
4754
|
-
|
|
4755
|
-
|
|
4756
|
-
const
|
|
4757
|
-
|
|
4758
|
-
|
|
4759
|
-
|
|
4760
|
-
|
|
4761
|
-
|
|
4762
|
-
|
|
4763
|
-
|
|
4764
|
-
|
|
4765
|
-
|
|
4766
|
-
|
|
4767
|
-
|
|
4768
|
-
|
|
4769
|
-
|
|
4770
|
-
|
|
4771
|
-
|
|
4772
|
-
|
|
4773
|
-
|
|
4774
|
-
|
|
4775
|
-
|
|
4895
|
+
syncControls(nextDraft);
|
|
4896
|
+
};
|
|
4897
|
+
adjust.addEventListener("click", () => {
|
|
4898
|
+
updateDraft((currentDraft) => ({
|
|
4899
|
+
...currentDraft,
|
|
4900
|
+
adjustment: {
|
|
4901
|
+
x: currentDraft.adjustment?.x ?? 0,
|
|
4902
|
+
y: currentDraft.adjustment?.y ?? 0,
|
|
4903
|
+
scale: currentDraft.adjustment?.scale ?? 0,
|
|
4904
|
+
isActive: currentDraft.adjustment?.isActive !== true
|
|
4905
|
+
}
|
|
4906
|
+
}));
|
|
4907
|
+
adjust.focus();
|
|
4908
|
+
});
|
|
4909
|
+
popover.addEventListener("keydown", (event) => {
|
|
4910
|
+
const currentDraft = config.getState().domDraft ?? draft;
|
|
4911
|
+
if (currentDraft.adjustment?.isActive !== true) return;
|
|
4912
|
+
const keyDelta = getAdjustmentKeyDelta(event);
|
|
4913
|
+
if (!keyDelta) return;
|
|
4914
|
+
event.preventDefault();
|
|
4915
|
+
event.stopPropagation();
|
|
4916
|
+
updateDraft((activeDraft) => ({
|
|
4917
|
+
...activeDraft,
|
|
4918
|
+
adjustment: {
|
|
4919
|
+
x: (activeDraft.adjustment?.x ?? 0) + keyDelta.x,
|
|
4920
|
+
y: (activeDraft.adjustment?.y ?? 0) + keyDelta.y,
|
|
4921
|
+
scale: (activeDraft.adjustment?.scale ?? 0) + keyDelta.scale,
|
|
4922
|
+
isActive: true
|
|
4923
|
+
}
|
|
4924
|
+
}));
|
|
4925
|
+
});
|
|
4926
|
+
header.append(help);
|
|
4927
|
+
if (!dockToggle) {
|
|
4928
|
+
header.append(adjust);
|
|
4929
|
+
}
|
|
4930
|
+
panel.append(header, xyStatus, scaleStatus);
|
|
4931
|
+
syncControls(draft);
|
|
4932
|
+
return {
|
|
4933
|
+
panel,
|
|
4934
|
+
focusTarget: adjust,
|
|
4935
|
+
// 도킹 모드에서는 토글 버튼을 폼 액션 줄(leading)로 옮긴다.
|
|
4936
|
+
actionButton: dockToggle ? adjust : void 0
|
|
4937
|
+
};
|
|
4938
|
+
}
|
|
4939
|
+
function getAdjustmentKeyDelta(event) {
|
|
4940
|
+
const step = event.shiftKey ? 10 : 1;
|
|
4941
|
+
if (event.key === "ArrowLeft") return { x: -step, y: 0, scale: 0 };
|
|
4942
|
+
if (event.key === "ArrowRight") return { x: step, y: 0, scale: 0 };
|
|
4943
|
+
if (event.key === "ArrowUp") return { x: 0, y: -step, scale: 0 };
|
|
4944
|
+
if (event.key === "ArrowDown") return { x: 0, y: step, scale: 0 };
|
|
4945
|
+
if (event.key.toLowerCase() === "w") return { x: 0, y: 0, scale: step };
|
|
4946
|
+
if (event.key.toLowerCase() === "s") return { x: 0, y: 0, scale: -step };
|
|
4947
|
+
return void 0;
|
|
4948
|
+
}
|
|
4949
|
+
function syncDraftAdjustmentUi(context, {
|
|
4950
|
+
draft,
|
|
4951
|
+
pin,
|
|
4952
|
+
selectionHighlight
|
|
4953
|
+
}) {
|
|
4954
|
+
const { config } = context;
|
|
4955
|
+
const presets = config.options.viewports?.presets;
|
|
4956
|
+
const environment = config.getEnvironment();
|
|
4957
|
+
if (!environment) return;
|
|
4958
|
+
const hostPoint = toHostPoint(
|
|
4959
|
+
getAdjustedDraftPoint(draft.marker.viewport, draft, presets),
|
|
4960
|
+
environment
|
|
4961
|
+
);
|
|
4962
|
+
pin.style.left = `${hostPoint.x}px`;
|
|
4963
|
+
pin.style.top = `${hostPoint.y}px`;
|
|
4964
|
+
if (draft.selection && selectionHighlight) {
|
|
4965
|
+
const rect = toHostSelection(
|
|
4966
|
+
getAdjustedDraftSelection(
|
|
4967
|
+
toViewportSelection(draft.selection.viewport),
|
|
4968
|
+
draft,
|
|
4969
|
+
presets
|
|
4970
|
+
),
|
|
4971
|
+
environment
|
|
4972
|
+
);
|
|
4973
|
+
selectionHighlight.style.left = `${rect.left}px`;
|
|
4974
|
+
selectionHighlight.style.top = `${rect.top}px`;
|
|
4975
|
+
selectionHighlight.style.width = `${rect.width}px`;
|
|
4976
|
+
selectionHighlight.style.height = `${rect.height}px`;
|
|
4977
|
+
}
|
|
4978
|
+
context.syncDraftPreview(draft);
|
|
4979
|
+
}
|
|
4980
|
+
function attachDraftPinDrag(context, {
|
|
4981
|
+
pin,
|
|
4982
|
+
popover,
|
|
4983
|
+
meta,
|
|
4984
|
+
textarea
|
|
4985
|
+
}) {
|
|
4986
|
+
const { config } = context;
|
|
4987
|
+
let isDragging = false;
|
|
4988
|
+
const moveDraftUi = (hostPoint) => {
|
|
4989
|
+
const environment = config.getEnvironment();
|
|
4990
|
+
if (!environment) return;
|
|
4991
|
+
const nextPoint = clampPoint(
|
|
4992
|
+
toTargetPoint(hostPoint, environment),
|
|
4993
|
+
environment
|
|
4994
|
+
);
|
|
4995
|
+
const nextHostPoint = toHostPoint(nextPoint, environment);
|
|
4996
|
+
pin.style.left = `${nextHostPoint.x}px`;
|
|
4997
|
+
pin.style.top = `${nextHostPoint.y}px`;
|
|
4998
|
+
if (popover) {
|
|
4999
|
+
const position = getPopoverPosition(nextHostPoint, environment);
|
|
5000
|
+
popover.style.left = `${position.left}px`;
|
|
5001
|
+
popover.style.top = `${position.top}px`;
|
|
5002
|
+
}
|
|
5003
|
+
const domDraft = config.getState().domDraft;
|
|
5004
|
+
if (!domDraft) return;
|
|
5005
|
+
const nextDraft = {
|
|
5006
|
+
...domDraft,
|
|
5007
|
+
marker: {
|
|
5008
|
+
...domDraft.marker,
|
|
5009
|
+
viewport: roundPoint(nextPoint)
|
|
5010
|
+
},
|
|
5011
|
+
comment: textarea.value
|
|
5012
|
+
};
|
|
5013
|
+
config.actions.setDomDraft(nextDraft);
|
|
5014
|
+
if (meta) {
|
|
5015
|
+
meta.textContent = formatDomDraftMeta(nextDraft);
|
|
5016
|
+
}
|
|
5017
|
+
};
|
|
5018
|
+
pin.addEventListener("pointerdown", (event) => {
|
|
5019
|
+
if (event.button !== 0) return;
|
|
5020
|
+
event.preventDefault();
|
|
5021
|
+
event.stopPropagation();
|
|
5022
|
+
isDragging = true;
|
|
5023
|
+
pin.setPointerCapture(event.pointerId);
|
|
5024
|
+
});
|
|
5025
|
+
pin.addEventListener("pointermove", (event) => {
|
|
5026
|
+
if (!isDragging || !pin.hasPointerCapture(event.pointerId)) return;
|
|
5027
|
+
event.preventDefault();
|
|
5028
|
+
moveDraftUi({
|
|
5029
|
+
x: event.clientX,
|
|
5030
|
+
y: event.clientY
|
|
4776
5031
|
});
|
|
4777
|
-
|
|
4778
|
-
|
|
4779
|
-
return
|
|
5032
|
+
});
|
|
5033
|
+
pin.addEventListener("pointerup", (event) => {
|
|
5034
|
+
if (!isDragging || !pin.hasPointerCapture(event.pointerId)) return;
|
|
5035
|
+
event.preventDefault();
|
|
5036
|
+
event.stopPropagation();
|
|
5037
|
+
isDragging = false;
|
|
5038
|
+
pin.releasePointerCapture(event.pointerId);
|
|
5039
|
+
const nextPoint = toTargetPointFromHostEvent(
|
|
5040
|
+
event,
|
|
5041
|
+
config.getEnvironment()
|
|
5042
|
+
);
|
|
5043
|
+
const currentDraft = config.getState().domDraft;
|
|
5044
|
+
const fields = {
|
|
5045
|
+
title: currentDraft?.title,
|
|
5046
|
+
comment: textarea.value,
|
|
5047
|
+
assigneeId: currentDraft?.assigneeId,
|
|
5048
|
+
assigneeName: currentDraft?.assigneeName
|
|
5049
|
+
};
|
|
5050
|
+
void config.actions.bindElementDraftToPoint(nextPoint, fields);
|
|
5051
|
+
});
|
|
5052
|
+
}
|
|
5053
|
+
|
|
5054
|
+
// src/core/view/panel.ts
|
|
5055
|
+
function createReviewPanel(config, renderAreaForm) {
|
|
5056
|
+
const panel = document.createElement("div");
|
|
5057
|
+
panel.className = "dfwr-panel";
|
|
5058
|
+
panel.setAttribute("role", "dialog");
|
|
5059
|
+
panel.setAttribute("aria-label", "Web review kit");
|
|
5060
|
+
panel.append(
|
|
5061
|
+
createHeader(config),
|
|
5062
|
+
createToolbar(config),
|
|
5063
|
+
createBody(config, renderAreaForm),
|
|
5064
|
+
createList(config)
|
|
5065
|
+
);
|
|
5066
|
+
return panel;
|
|
5067
|
+
}
|
|
5068
|
+
function createHeader(config) {
|
|
5069
|
+
const header = document.createElement("div");
|
|
5070
|
+
header.className = "dfwr-header";
|
|
5071
|
+
const title = document.createElement("div");
|
|
5072
|
+
title.className = "dfwr-title";
|
|
5073
|
+
title.textContent = "Review Kit";
|
|
5074
|
+
const meta = document.createElement("div");
|
|
5075
|
+
meta.className = "dfwr-meta";
|
|
5076
|
+
meta.textContent = getRouteKey(config.getEnvironment());
|
|
5077
|
+
const titleGroup = document.createElement("div");
|
|
5078
|
+
titleGroup.append(title, meta);
|
|
5079
|
+
const close = document.createElement("button");
|
|
5080
|
+
close.className = "dfwr-icon-button";
|
|
5081
|
+
close.type = "button";
|
|
5082
|
+
close.textContent = "x";
|
|
5083
|
+
close.setAttribute("aria-label", "Close");
|
|
5084
|
+
close.addEventListener("click", () => config.actions.close());
|
|
5085
|
+
header.append(titleGroup, close);
|
|
5086
|
+
return header;
|
|
5087
|
+
}
|
|
5088
|
+
function createToolbar(config) {
|
|
5089
|
+
const state = config.getState();
|
|
5090
|
+
const toolbar = document.createElement("div");
|
|
5091
|
+
toolbar.className = "dfwr-toolbar";
|
|
5092
|
+
const toggleMode = (target) => {
|
|
5093
|
+
const mode = config.getState().mode;
|
|
5094
|
+
config.actions.setModeState(mode === target ? "idle" : target);
|
|
5095
|
+
config.actions.clearDrafts();
|
|
5096
|
+
config.actions.render();
|
|
5097
|
+
};
|
|
5098
|
+
toolbar.append(
|
|
5099
|
+
createToolbarButton(
|
|
5100
|
+
"Element",
|
|
5101
|
+
state.mode === "element",
|
|
5102
|
+
() => toggleMode("element")
|
|
5103
|
+
),
|
|
5104
|
+
createToolbarButton(
|
|
5105
|
+
state.isSelectingArea ? "Selecting" : "Area",
|
|
5106
|
+
state.mode === "area",
|
|
5107
|
+
() => toggleMode("area")
|
|
5108
|
+
),
|
|
5109
|
+
createToolbarButton("Refresh", false, () => {
|
|
5110
|
+
void config.actions.reload();
|
|
5111
|
+
})
|
|
5112
|
+
);
|
|
5113
|
+
return toolbar;
|
|
5114
|
+
}
|
|
5115
|
+
function createToolbarButton(label, active, onClick) {
|
|
5116
|
+
const button = document.createElement("button");
|
|
5117
|
+
button.className = `dfwr-button${active ? " is-active" : ""}`;
|
|
5118
|
+
button.type = "button";
|
|
5119
|
+
button.textContent = label;
|
|
5120
|
+
button.addEventListener("click", onClick);
|
|
5121
|
+
return button;
|
|
5122
|
+
}
|
|
5123
|
+
function createBody(config, renderAreaForm) {
|
|
5124
|
+
const body = document.createElement("div");
|
|
5125
|
+
body.className = "dfwr-body";
|
|
5126
|
+
const state = config.getState();
|
|
5127
|
+
if (state.mode === "idle") {
|
|
5128
|
+
body.append(
|
|
5129
|
+
createEmptyText("Select an element or mark an area.")
|
|
5130
|
+
);
|
|
5131
|
+
return body;
|
|
4780
5132
|
}
|
|
4781
|
-
|
|
4782
|
-
|
|
4783
|
-
|
|
4784
|
-
|
|
4785
|
-
|
|
4786
|
-
const currentScope = getReviewViewportScope(
|
|
4787
|
-
getViewportSize(environment),
|
|
4788
|
-
this.config.options.viewports?.presets
|
|
5133
|
+
if (state.mode === "element") {
|
|
5134
|
+
body.append(
|
|
5135
|
+
createEmptyText(
|
|
5136
|
+
state.domDraft ? "Write the QA in the page box." : "Click an element to add QA."
|
|
5137
|
+
)
|
|
4789
5138
|
);
|
|
4790
|
-
|
|
4791
|
-
|
|
4792
|
-
|
|
4793
|
-
|
|
4794
|
-
|
|
4795
|
-
|
|
4796
|
-
|
|
4797
|
-
|
|
4798
|
-
|
|
4799
|
-
|
|
4800
|
-
|
|
4801
|
-
|
|
4802
|
-
|
|
4803
|
-
|
|
4804
|
-
|
|
4805
|
-
|
|
4806
|
-
|
|
4807
|
-
|
|
4808
|
-
|
|
4809
|
-
|
|
4810
|
-
|
|
4811
|
-
|
|
4812
|
-
|
|
4813
|
-
|
|
4814
|
-
|
|
4815
|
-
|
|
4816
|
-
|
|
4817
|
-
|
|
4818
|
-
|
|
5139
|
+
return body;
|
|
5140
|
+
}
|
|
5141
|
+
body.append(renderAreaForm());
|
|
5142
|
+
return body;
|
|
5143
|
+
}
|
|
5144
|
+
function createEmptyText(text) {
|
|
5145
|
+
const empty = document.createElement("p");
|
|
5146
|
+
empty.className = "dfwr-empty";
|
|
5147
|
+
empty.textContent = text;
|
|
5148
|
+
return empty;
|
|
5149
|
+
}
|
|
5150
|
+
function createList(config) {
|
|
5151
|
+
const section = document.createElement("div");
|
|
5152
|
+
section.className = "dfwr-list";
|
|
5153
|
+
const state = config.getState();
|
|
5154
|
+
const heading = document.createElement("div");
|
|
5155
|
+
heading.className = "dfwr-list-heading";
|
|
5156
|
+
heading.textContent = `Review items (${state.items.length})`;
|
|
5157
|
+
section.append(heading);
|
|
5158
|
+
if (state.items.length === 0) {
|
|
5159
|
+
section.append(createEmptyText("No review items on this page."));
|
|
5160
|
+
return section;
|
|
5161
|
+
}
|
|
5162
|
+
for (const numberedItem of getNumberedReviewItems(
|
|
5163
|
+
state.items,
|
|
5164
|
+
config.options.viewports?.presets
|
|
5165
|
+
)) {
|
|
5166
|
+
section.append(createListItem(config, numberedItem));
|
|
5167
|
+
}
|
|
5168
|
+
return section;
|
|
5169
|
+
}
|
|
5170
|
+
function createListItem(config, numberedItem) {
|
|
5171
|
+
const { item } = numberedItem;
|
|
5172
|
+
const row = document.createElement("article");
|
|
5173
|
+
row.className = "dfwr-item";
|
|
5174
|
+
row.tabIndex = 0;
|
|
5175
|
+
row.setAttribute("role", "button");
|
|
5176
|
+
row.setAttribute(
|
|
5177
|
+
"aria-label",
|
|
5178
|
+
`Restore review item: ${item.title ?? item.comment}`
|
|
5179
|
+
);
|
|
5180
|
+
row.addEventListener("click", () => {
|
|
5181
|
+
void config.actions.restoreItem(item);
|
|
5182
|
+
});
|
|
5183
|
+
row.addEventListener("keydown", (event) => {
|
|
5184
|
+
if (event.key !== "Enter" && event.key !== " ") return;
|
|
5185
|
+
event.preventDefault();
|
|
5186
|
+
void config.actions.restoreItem(item);
|
|
5187
|
+
});
|
|
5188
|
+
const body = document.createElement("div");
|
|
5189
|
+
body.className = "dfwr-item-body";
|
|
5190
|
+
const badges = document.createElement("div");
|
|
5191
|
+
badges.className = "dfwr-item-badges";
|
|
5192
|
+
const scope = document.createElement("div");
|
|
5193
|
+
scope.className = `dfwr-item-scope is-scope-${numberedItem.scope}`;
|
|
5194
|
+
scope.textContent = numberedItem.displayLabel;
|
|
5195
|
+
const kind = document.createElement("div");
|
|
5196
|
+
kind.className = "dfwr-item-kind";
|
|
5197
|
+
kind.textContent = item.kind;
|
|
5198
|
+
badges.append(scope, kind);
|
|
5199
|
+
const title = isTitleFieldEnabled(config.options) ? item.title?.trim() : "";
|
|
5200
|
+
const titleElement = title ? document.createElement("strong") : void 0;
|
|
5201
|
+
if (title && titleElement) {
|
|
5202
|
+
titleElement.className = "dfwr-item-title";
|
|
5203
|
+
titleElement.textContent = title;
|
|
5204
|
+
}
|
|
5205
|
+
const comment = document.createElement("p");
|
|
5206
|
+
comment.className = `dfwr-item-comment${title ? "" : " is-primary"}`;
|
|
5207
|
+
comment.textContent = item.comment;
|
|
5208
|
+
const date = document.createElement("time");
|
|
5209
|
+
date.className = "dfwr-item-date";
|
|
5210
|
+
date.dateTime = item.createdAt;
|
|
5211
|
+
date.textContent = formatItemMeta(item);
|
|
5212
|
+
body.append(badges, ...titleElement ? [titleElement] : [], comment, date);
|
|
5213
|
+
const actions = document.createElement("div");
|
|
5214
|
+
actions.className = "dfwr-item-actions";
|
|
5215
|
+
actions.addEventListener("click", (event) => event.stopPropagation());
|
|
5216
|
+
actions.addEventListener("keydown", (event) => event.stopPropagation());
|
|
5217
|
+
const remove = document.createElement("button");
|
|
5218
|
+
remove.className = "dfwr-icon-button";
|
|
5219
|
+
remove.type = "button";
|
|
5220
|
+
remove.textContent = "x";
|
|
5221
|
+
remove.setAttribute("aria-label", "Delete");
|
|
5222
|
+
remove.addEventListener("click", (event) => {
|
|
5223
|
+
event.stopPropagation();
|
|
5224
|
+
void config.actions.removeItem(item.id).then(() => config.actions.reload());
|
|
5225
|
+
});
|
|
5226
|
+
actions.append(remove);
|
|
5227
|
+
row.append(body, actions);
|
|
5228
|
+
return row;
|
|
5229
|
+
}
|
|
5230
|
+
|
|
5231
|
+
// src/core/view/selection.layers.ts
|
|
5232
|
+
function createElementLayer(config) {
|
|
5233
|
+
const layer = document.createElement("div");
|
|
5234
|
+
layer.className = "dfwr-element-layer";
|
|
5235
|
+
const environment = config.getEnvironment();
|
|
5236
|
+
const hover = document.createElement("div");
|
|
5237
|
+
hover.className = "dfwr-dom-hover";
|
|
5238
|
+
hover.hidden = true;
|
|
5239
|
+
layer.append(hover);
|
|
5240
|
+
if (environment) {
|
|
5241
|
+
placeLayerOverTarget(layer, environment);
|
|
5242
|
+
}
|
|
5243
|
+
const updateHover = (point) => {
|
|
5244
|
+
const nextEnvironment = config.getEnvironment();
|
|
5245
|
+
if (!nextEnvironment) return;
|
|
5246
|
+
const anchor = getDomAnchorFromPoint(
|
|
5247
|
+
clampPoint(point, nextEnvironment),
|
|
5248
|
+
config.options.anchors?.attribute,
|
|
5249
|
+
nextEnvironment
|
|
5250
|
+
);
|
|
5251
|
+
const selection = anchor ? getElementViewportSelection(anchor, nextEnvironment) : void 0;
|
|
5252
|
+
if (!selection) {
|
|
5253
|
+
hover.hidden = true;
|
|
5254
|
+
return;
|
|
5255
|
+
}
|
|
5256
|
+
const rect = toHostSelection(selection, nextEnvironment);
|
|
5257
|
+
hover.hidden = false;
|
|
5258
|
+
hover.style.left = `${rect.left}px`;
|
|
5259
|
+
hover.style.top = `${rect.top}px`;
|
|
5260
|
+
hover.style.width = `${rect.width}px`;
|
|
5261
|
+
hover.style.height = `${rect.height}px`;
|
|
5262
|
+
};
|
|
5263
|
+
layer.addEventListener("pointermove", (event) => {
|
|
5264
|
+
updateHover(toTargetPointFromHostEvent(event, config.getEnvironment()));
|
|
5265
|
+
});
|
|
5266
|
+
layer.addEventListener("pointerleave", () => {
|
|
5267
|
+
hover.hidden = true;
|
|
5268
|
+
});
|
|
5269
|
+
layer.addEventListener("pointerdown", (event) => {
|
|
5270
|
+
if (event.button !== 0) return;
|
|
5271
|
+
event.preventDefault();
|
|
5272
|
+
void config.actions.bindElementDraftToPoint(
|
|
5273
|
+
toTargetPointFromHostEvent(event, config.getEnvironment())
|
|
5274
|
+
);
|
|
5275
|
+
});
|
|
5276
|
+
return layer;
|
|
5277
|
+
}
|
|
5278
|
+
function createAreaLayer(config) {
|
|
5279
|
+
const layer = document.createElement("div");
|
|
5280
|
+
layer.className = "dfwr-area-layer";
|
|
5281
|
+
const environment = config.getEnvironment();
|
|
5282
|
+
if (environment) {
|
|
5283
|
+
placeLayerOverTarget(layer, environment);
|
|
5284
|
+
}
|
|
5285
|
+
const box = document.createElement("div");
|
|
5286
|
+
box.className = "dfwr-area-box";
|
|
5287
|
+
layer.append(box);
|
|
5288
|
+
let startX = 0;
|
|
5289
|
+
let startY = 0;
|
|
5290
|
+
let selection;
|
|
5291
|
+
let activePointerId;
|
|
5292
|
+
let isDragging = false;
|
|
5293
|
+
const ownerWindow = layer.ownerDocument.defaultView ?? window;
|
|
5294
|
+
const updateBox = (event) => {
|
|
5295
|
+
const nextEnvironment = config.getEnvironment();
|
|
5296
|
+
const nextPoint = toTargetPointFromHostEvent(event, nextEnvironment);
|
|
5297
|
+
const left = Math.min(startX, nextPoint.x);
|
|
5298
|
+
const top = Math.min(startY, nextPoint.y);
|
|
5299
|
+
const width = Math.abs(nextPoint.x - startX);
|
|
5300
|
+
const height = Math.abs(nextPoint.y - startY);
|
|
5301
|
+
selection = { left, top, width, height };
|
|
5302
|
+
const rect = nextEnvironment ? toHostSelection(selection, nextEnvironment) : selection;
|
|
5303
|
+
box.style.left = `${rect.left}px`;
|
|
5304
|
+
box.style.top = `${rect.top}px`;
|
|
5305
|
+
box.style.width = `${rect.width}px`;
|
|
5306
|
+
box.style.height = `${rect.height}px`;
|
|
5307
|
+
};
|
|
5308
|
+
const addDragListeners = () => {
|
|
5309
|
+
ownerWindow.addEventListener("pointermove", handlePointerMove, true);
|
|
5310
|
+
ownerWindow.addEventListener("pointerup", handlePointerUp, true);
|
|
5311
|
+
ownerWindow.addEventListener("pointercancel", handlePointerCancel, true);
|
|
5312
|
+
};
|
|
5313
|
+
const removeDragListeners = () => {
|
|
5314
|
+
ownerWindow.removeEventListener("pointermove", handlePointerMove, true);
|
|
5315
|
+
ownerWindow.removeEventListener("pointerup", handlePointerUp, true);
|
|
5316
|
+
ownerWindow.removeEventListener(
|
|
5317
|
+
"pointercancel",
|
|
5318
|
+
handlePointerCancel,
|
|
5319
|
+
true
|
|
5320
|
+
);
|
|
5321
|
+
};
|
|
5322
|
+
const releasePointerCapture = (event) => {
|
|
5323
|
+
try {
|
|
5324
|
+
if (layer.hasPointerCapture(event.pointerId)) {
|
|
5325
|
+
layer.releasePointerCapture(event.pointerId);
|
|
4819
5326
|
}
|
|
4820
|
-
|
|
4821
|
-
|
|
4822
|
-
|
|
4823
|
-
|
|
4824
|
-
|
|
4825
|
-
|
|
4826
|
-
|
|
4827
|
-
|
|
4828
|
-
|
|
4829
|
-
|
|
4830
|
-
|
|
4831
|
-
|
|
4832
|
-
|
|
5327
|
+
} catch {
|
|
5328
|
+
}
|
|
5329
|
+
};
|
|
5330
|
+
function isActivePointer(event) {
|
|
5331
|
+
return isDragging && event.pointerId === activePointerId;
|
|
5332
|
+
}
|
|
5333
|
+
const finishAreaSelection = (event) => {
|
|
5334
|
+
if (!isActivePointer(event)) return;
|
|
5335
|
+
event.preventDefault();
|
|
5336
|
+
updateBox(event);
|
|
5337
|
+
releasePointerCapture(event);
|
|
5338
|
+
removeDragListeners();
|
|
5339
|
+
isDragging = false;
|
|
5340
|
+
activePointerId = void 0;
|
|
5341
|
+
if (!selection || selection.width < 8 || selection.height < 8) return;
|
|
5342
|
+
config.actions.setSelectingArea(true);
|
|
5343
|
+
config.actions.render();
|
|
5344
|
+
void config.actions.createAreaDraft(selection);
|
|
5345
|
+
};
|
|
5346
|
+
function handlePointerMove(event) {
|
|
5347
|
+
if (!isActivePointer(event)) return;
|
|
5348
|
+
event.preventDefault();
|
|
5349
|
+
updateBox(event);
|
|
5350
|
+
}
|
|
5351
|
+
const handlePointerUp = (event) => {
|
|
5352
|
+
finishAreaSelection(event);
|
|
5353
|
+
};
|
|
5354
|
+
const handlePointerCancel = (event) => {
|
|
5355
|
+
if (!isActivePointer(event)) return;
|
|
5356
|
+
releasePointerCapture(event);
|
|
5357
|
+
removeDragListeners();
|
|
5358
|
+
isDragging = false;
|
|
5359
|
+
activePointerId = void 0;
|
|
5360
|
+
};
|
|
5361
|
+
layer.addEventListener("pointerdown", (event) => {
|
|
5362
|
+
if (event.button !== 0) return;
|
|
5363
|
+
event.preventDefault();
|
|
5364
|
+
activePointerId = event.pointerId;
|
|
5365
|
+
isDragging = true;
|
|
5366
|
+
try {
|
|
5367
|
+
layer.setPointerCapture(event.pointerId);
|
|
5368
|
+
} catch {
|
|
5369
|
+
}
|
|
5370
|
+
const startPoint = toTargetPointFromHostEvent(
|
|
5371
|
+
event,
|
|
5372
|
+
config.getEnvironment()
|
|
5373
|
+
);
|
|
5374
|
+
startX = startPoint.x;
|
|
5375
|
+
startY = startPoint.y;
|
|
5376
|
+
updateBox(event);
|
|
5377
|
+
addDragListeners();
|
|
5378
|
+
});
|
|
5379
|
+
layer.addEventListener("pointermove", handlePointerMove);
|
|
5380
|
+
layer.addEventListener("pointerup", handlePointerUp);
|
|
5381
|
+
layer.addEventListener("pointercancel", handlePointerCancel);
|
|
5382
|
+
return layer;
|
|
5383
|
+
}
|
|
5384
|
+
|
|
5385
|
+
// src/core/web.review.kit.view.ts
|
|
5386
|
+
var WebReviewKitView = class {
|
|
5387
|
+
constructor(config) {
|
|
5388
|
+
this.config = config;
|
|
5389
|
+
const presets = () => this.config.options.viewports?.presets;
|
|
5390
|
+
this.draftPreview = new DraftPreviewController({
|
|
5391
|
+
getEnvironment: () => this.config.getEnvironment(),
|
|
5392
|
+
getMetrics: (draft) => getDraftAdjustmentMetrics(draft, presets()),
|
|
5393
|
+
hasAdjustment: (draft) => hasDraftAdjustment(draft, presets())
|
|
4833
5394
|
});
|
|
4834
|
-
|
|
5395
|
+
this.draftContext = {
|
|
5396
|
+
config: this.config,
|
|
5397
|
+
cancelDraft: (event) => this.cancelDraft(event),
|
|
5398
|
+
syncDraftPreview: (draft) => this.draftPreview.sync(draft)
|
|
5399
|
+
};
|
|
4835
5400
|
}
|
|
4836
|
-
|
|
4837
|
-
|
|
4838
|
-
|
|
4839
|
-
|
|
4840
|
-
|
|
4841
|
-
|
|
4842
|
-
|
|
4843
|
-
|
|
4844
|
-
|
|
4845
|
-
|
|
4846
|
-
|
|
4847
|
-
|
|
4848
|
-
|
|
4849
|
-
|
|
4850
|
-
|
|
4851
|
-
|
|
4852
|
-
|
|
4853
|
-
|
|
4854
|
-
|
|
4855
|
-
|
|
4856
|
-
|
|
4857
|
-
labelElement.textContent = label;
|
|
4858
|
-
labelElement.style.left = `${Math.max(4, rect.left)}px`;
|
|
4859
|
-
labelElement.style.top = `${Math.max(4, rect.top - 24)}px`;
|
|
4860
|
-
labelElement.dataset.reviewItemId = item.id;
|
|
4861
|
-
return [highlight, labelElement];
|
|
4862
|
-
}
|
|
4863
|
-
createSelectionHighlight(selection, environment, isDraft) {
|
|
4864
|
-
const rect = toHostSelection(selection, environment);
|
|
4865
|
-
const highlight = document.createElement("div");
|
|
4866
|
-
highlight.className = `dfwr-selection-highlight${isDraft ? " is-draft" : ""}`;
|
|
4867
|
-
highlight.style.left = `${rect.left}px`;
|
|
4868
|
-
highlight.style.top = `${rect.top}px`;
|
|
4869
|
-
highlight.style.width = `${rect.width}px`;
|
|
4870
|
-
highlight.style.height = `${rect.height}px`;
|
|
4871
|
-
return highlight;
|
|
4872
|
-
}
|
|
4873
|
-
createMarkerElement(itemId, hostPoint, label, scope, isBound, isHighlighted, variant = "default") {
|
|
4874
|
-
const isNoteCallout = variant === "note";
|
|
4875
|
-
const marker = document.createElement("div");
|
|
4876
|
-
marker.className = [
|
|
4877
|
-
"dfwr-bound-marker",
|
|
4878
|
-
isNoteCallout ? "is-note-callout" : "",
|
|
4879
|
-
`is-scope-${scope}`,
|
|
4880
|
-
isBound ? "is-bound" : "is-fallback",
|
|
4881
|
-
isHighlighted ? "is-highlighted" : ""
|
|
5401
|
+
clearDraftPreview() {
|
|
5402
|
+
this.draftPreview.clear();
|
|
5403
|
+
this.clearShellComposer();
|
|
5404
|
+
}
|
|
5405
|
+
/** Rebuilds the entire shadow-root UI from the current state snapshot. */
|
|
5406
|
+
render(shadow, hiddenItemsStyle) {
|
|
5407
|
+
const state = this.state;
|
|
5408
|
+
this.draftPreview.sync(
|
|
5409
|
+
state.isOpen && state.mode === "element" ? state.domDraft : void 0
|
|
5410
|
+
);
|
|
5411
|
+
shadow.replaceChildren();
|
|
5412
|
+
shadow.append(createStyleElement());
|
|
5413
|
+
shadow.append(hiddenItemsStyle);
|
|
5414
|
+
const hasDismissableDraft = Boolean(state.domDraft || state.areaDraft);
|
|
5415
|
+
const shouldDockComposer = this.config.options.ui?.panel === false && hasDismissableDraft && Boolean(this.getShellComposerHost());
|
|
5416
|
+
let dockedComposer;
|
|
5417
|
+
const shell = document.createElement("div");
|
|
5418
|
+
shell.className = [
|
|
5419
|
+
"dfwr-shell",
|
|
5420
|
+
state.isOpen ? "is-open" : "",
|
|
5421
|
+
hasDismissableDraft && !shouldDockComposer ? "has-dismissible-draft" : ""
|
|
4882
5422
|
].filter(Boolean).join(" ");
|
|
4883
|
-
|
|
4884
|
-
|
|
4885
|
-
|
|
4886
|
-
|
|
4887
|
-
|
|
4888
|
-
|
|
4889
|
-
|
|
4890
|
-
|
|
4891
|
-
|
|
4892
|
-
|
|
4893
|
-
|
|
4894
|
-
|
|
4895
|
-
|
|
4896
|
-
|
|
4897
|
-
|
|
4898
|
-
|
|
4899
|
-
|
|
4900
|
-
|
|
4901
|
-
|
|
4902
|
-
|
|
4903
|
-
|
|
4904
|
-
|
|
4905
|
-
|
|
4906
|
-
|
|
4907
|
-
|
|
4908
|
-
|
|
4909
|
-
|
|
4910
|
-
|
|
5423
|
+
shell.setAttribute("aria-hidden", state.isOpen ? "false" : "true");
|
|
5424
|
+
if (this.config.options.ui?.panel !== false) {
|
|
5425
|
+
shell.append(
|
|
5426
|
+
createReviewPanel(
|
|
5427
|
+
this.config,
|
|
5428
|
+
() => createAreaForm(this.draftContext)
|
|
5429
|
+
)
|
|
5430
|
+
);
|
|
5431
|
+
}
|
|
5432
|
+
shell.append(
|
|
5433
|
+
createMarkerLayer({
|
|
5434
|
+
items: state.items,
|
|
5435
|
+
highlightedItemId: state.highlightedItemId,
|
|
5436
|
+
environment: this.config.getEnvironment(),
|
|
5437
|
+
presets: this.config.options.viewports?.presets,
|
|
5438
|
+
showCompactMarkers: this.config.options.ui?.markers !== "external"
|
|
5439
|
+
})
|
|
5440
|
+
);
|
|
5441
|
+
if (state.isOpen && hasDismissableDraft && !shouldDockComposer) {
|
|
5442
|
+
shell.append(this.createDraftCancelLayer());
|
|
5443
|
+
}
|
|
5444
|
+
if (state.isOpen && state.mode === "element") {
|
|
5445
|
+
if (state.domDraft) {
|
|
5446
|
+
const domDraft = createDomDraftLayer(this.draftContext, state.domDraft, {
|
|
5447
|
+
dockComposer: shouldDockComposer
|
|
5448
|
+
});
|
|
5449
|
+
shell.append(domDraft.layer);
|
|
5450
|
+
dockedComposer = domDraft.composer;
|
|
5451
|
+
} else {
|
|
5452
|
+
shell.append(createElementLayer(this.config));
|
|
4911
5453
|
}
|
|
4912
|
-
|
|
4913
|
-
|
|
4914
|
-
|
|
4915
|
-
|
|
4916
|
-
|
|
4917
|
-
|
|
4918
|
-
|
|
4919
|
-
},
|
|
4920
|
-
comment: textarea.value
|
|
4921
|
-
};
|
|
4922
|
-
this.config.actions.setNoteDraft(nextDraft);
|
|
4923
|
-
if (meta) {
|
|
4924
|
-
meta.textContent = formatNoteDraftMeta(nextDraft);
|
|
5454
|
+
}
|
|
5455
|
+
if (state.isOpen && state.mode === "area" && !state.areaDraft && !state.isSelectingArea) {
|
|
5456
|
+
shell.append(createAreaLayer(this.config));
|
|
5457
|
+
}
|
|
5458
|
+
if (state.isOpen && state.mode === "area" && state.areaDraft && this.config.options.ui?.panel === false) {
|
|
5459
|
+
if (state.areaDraft.selection) {
|
|
5460
|
+
shell.append(createAreaDraftOverlay(this.draftContext, state.areaDraft));
|
|
4925
5461
|
}
|
|
4926
|
-
|
|
4927
|
-
|
|
4928
|
-
|
|
4929
|
-
|
|
4930
|
-
event.stopPropagation();
|
|
4931
|
-
isDragging = true;
|
|
4932
|
-
pin.setPointerCapture(event.pointerId);
|
|
4933
|
-
});
|
|
4934
|
-
pin.addEventListener("pointermove", (event) => {
|
|
4935
|
-
if (!isDragging || !pin.hasPointerCapture(event.pointerId)) return;
|
|
4936
|
-
event.preventDefault();
|
|
4937
|
-
moveDraftUi({
|
|
4938
|
-
x: event.clientX,
|
|
4939
|
-
y: event.clientY
|
|
4940
|
-
});
|
|
4941
|
-
});
|
|
4942
|
-
pin.addEventListener("pointerup", (event) => {
|
|
4943
|
-
if (!isDragging || !pin.hasPointerCapture(event.pointerId)) return;
|
|
4944
|
-
event.preventDefault();
|
|
4945
|
-
event.stopPropagation();
|
|
4946
|
-
isDragging = false;
|
|
4947
|
-
pin.releasePointerCapture(event.pointerId);
|
|
4948
|
-
const nextPoint = toTargetPointFromHostEvent(
|
|
4949
|
-
event,
|
|
4950
|
-
this.config.getEnvironment()
|
|
5462
|
+
const areaComposer = createAreaDraftPopover(
|
|
5463
|
+
this.draftContext,
|
|
5464
|
+
state.areaDraft,
|
|
5465
|
+
{ dockComposer: shouldDockComposer }
|
|
4951
5466
|
);
|
|
4952
|
-
|
|
4953
|
-
|
|
4954
|
-
|
|
4955
|
-
|
|
4956
|
-
|
|
4957
|
-
|
|
4958
|
-
|
|
4959
|
-
|
|
4960
|
-
});
|
|
5467
|
+
if (shouldDockComposer) {
|
|
5468
|
+
dockedComposer = areaComposer;
|
|
5469
|
+
} else {
|
|
5470
|
+
shell.append(areaComposer);
|
|
5471
|
+
}
|
|
5472
|
+
}
|
|
5473
|
+
shadow.append(shell);
|
|
5474
|
+
this.renderShellComposer(dockedComposer);
|
|
4961
5475
|
}
|
|
4962
|
-
|
|
4963
|
-
|
|
4964
|
-
|
|
5476
|
+
get state() {
|
|
5477
|
+
return this.config.getState();
|
|
5478
|
+
}
|
|
5479
|
+
getShellComposerHost() {
|
|
4965
5480
|
const environment = this.config.getEnvironment();
|
|
4966
|
-
if (
|
|
4967
|
-
|
|
5481
|
+
if (this.config.options.ui?.panel !== false) return void 0;
|
|
5482
|
+
return environment?.composerHost ?? void 0;
|
|
5483
|
+
}
|
|
5484
|
+
/** Mounts the docked composer into the shell-provided host element. */
|
|
5485
|
+
renderShellComposer(composer) {
|
|
5486
|
+
const host = composer ? this.getShellComposerHost() : void 0;
|
|
5487
|
+
if (!host || !composer) {
|
|
5488
|
+
this.clearShellComposer();
|
|
5489
|
+
return;
|
|
4968
5490
|
}
|
|
4969
|
-
|
|
4970
|
-
|
|
4971
|
-
|
|
4972
|
-
|
|
4973
|
-
|
|
4974
|
-
|
|
4975
|
-
|
|
4976
|
-
|
|
5491
|
+
if (this.shellComposerHost && this.shellComposerHost !== host) {
|
|
5492
|
+
this.clearShellComposer();
|
|
5493
|
+
}
|
|
5494
|
+
this.shellComposerHost = host;
|
|
5495
|
+
host.dataset.hasDraftComposer = "true";
|
|
5496
|
+
if (host.parentElement) {
|
|
5497
|
+
host.parentElement.dataset.hasDraftComposer = "true";
|
|
5498
|
+
}
|
|
5499
|
+
const shell = document.createElement("div");
|
|
5500
|
+
shell.className = "dfwr-shell is-open is-shell-draft is-docked-composer";
|
|
5501
|
+
shell.append(composer);
|
|
5502
|
+
host.replaceChildren(createStyleElement(), shell);
|
|
4977
5503
|
}
|
|
4978
|
-
|
|
4979
|
-
const
|
|
4980
|
-
|
|
4981
|
-
|
|
4982
|
-
|
|
4983
|
-
|
|
4984
|
-
hover.hidden = true;
|
|
4985
|
-
layer.append(hover);
|
|
4986
|
-
if (environment) {
|
|
4987
|
-
placeLayerOverTarget(layer, environment);
|
|
5504
|
+
clearShellComposer() {
|
|
5505
|
+
const host = this.shellComposerHost;
|
|
5506
|
+
host?.replaceChildren();
|
|
5507
|
+
if (host) {
|
|
5508
|
+
delete host.dataset.hasDraftComposer;
|
|
5509
|
+
delete host.parentElement?.dataset.hasDraftComposer;
|
|
4988
5510
|
}
|
|
4989
|
-
|
|
4990
|
-
const nextEnvironment = this.config.getEnvironment();
|
|
4991
|
-
if (!nextEnvironment) return;
|
|
4992
|
-
const anchor = getDomAnchorFromPoint(
|
|
4993
|
-
clampPoint(point, nextEnvironment),
|
|
4994
|
-
this.config.options.anchors?.attribute,
|
|
4995
|
-
nextEnvironment
|
|
4996
|
-
);
|
|
4997
|
-
const selection = anchor ? getElementViewportSelection(anchor, nextEnvironment) : void 0;
|
|
4998
|
-
if (!selection) {
|
|
4999
|
-
hover.hidden = true;
|
|
5000
|
-
return;
|
|
5001
|
-
}
|
|
5002
|
-
const rect = toHostSelection(selection, nextEnvironment);
|
|
5003
|
-
hover.hidden = false;
|
|
5004
|
-
hover.style.left = `${rect.left}px`;
|
|
5005
|
-
hover.style.top = `${rect.top}px`;
|
|
5006
|
-
hover.style.width = `${rect.width}px`;
|
|
5007
|
-
hover.style.height = `${rect.height}px`;
|
|
5008
|
-
};
|
|
5009
|
-
layer.addEventListener("pointermove", (event) => {
|
|
5010
|
-
updateHover(toTargetPointFromHostEvent(event, this.config.getEnvironment()));
|
|
5011
|
-
});
|
|
5012
|
-
layer.addEventListener("pointerleave", () => {
|
|
5013
|
-
hover.hidden = true;
|
|
5014
|
-
});
|
|
5015
|
-
layer.addEventListener("pointerdown", (event) => {
|
|
5016
|
-
if (event.button !== 0) return;
|
|
5017
|
-
event.preventDefault();
|
|
5018
|
-
void this.config.actions.bindElementDraftToPoint(
|
|
5019
|
-
toTargetPointFromHostEvent(event, this.config.getEnvironment())
|
|
5020
|
-
);
|
|
5021
|
-
});
|
|
5022
|
-
return layer;
|
|
5511
|
+
this.shellComposerHost = void 0;
|
|
5023
5512
|
}
|
|
5024
|
-
|
|
5513
|
+
/** Full-screen click-away layer that cancels the active draft. */
|
|
5514
|
+
createDraftCancelLayer() {
|
|
5025
5515
|
const layer = document.createElement("div");
|
|
5026
|
-
layer.className = "dfwr-
|
|
5027
|
-
|
|
5028
|
-
if (environment) {
|
|
5029
|
-
placeLayerOverTarget(layer, environment);
|
|
5030
|
-
}
|
|
5031
|
-
const box = document.createElement("div");
|
|
5032
|
-
box.className = "dfwr-area-box";
|
|
5033
|
-
layer.append(box);
|
|
5034
|
-
let startX = 0;
|
|
5035
|
-
let startY = 0;
|
|
5036
|
-
let selection;
|
|
5037
|
-
let activePointerId;
|
|
5038
|
-
let isDragging = false;
|
|
5039
|
-
const ownerWindow = layer.ownerDocument.defaultView ?? window;
|
|
5040
|
-
const updateBox = (event) => {
|
|
5041
|
-
const nextEnvironment = this.config.getEnvironment();
|
|
5042
|
-
const nextPoint = toTargetPointFromHostEvent(
|
|
5043
|
-
event,
|
|
5044
|
-
nextEnvironment
|
|
5045
|
-
);
|
|
5046
|
-
const left = Math.min(startX, nextPoint.x);
|
|
5047
|
-
const top = Math.min(startY, nextPoint.y);
|
|
5048
|
-
const width = Math.abs(nextPoint.x - startX);
|
|
5049
|
-
const height = Math.abs(nextPoint.y - startY);
|
|
5050
|
-
const hostPoint = toHostPoint(
|
|
5051
|
-
{ x: left, y: top },
|
|
5052
|
-
nextEnvironment
|
|
5053
|
-
);
|
|
5054
|
-
selection = { left, top, width, height };
|
|
5055
|
-
box.style.left = `${hostPoint.x}px`;
|
|
5056
|
-
box.style.top = `${hostPoint.y}px`;
|
|
5057
|
-
box.style.width = `${width}px`;
|
|
5058
|
-
box.style.height = `${height}px`;
|
|
5059
|
-
};
|
|
5060
|
-
const addDragListeners = () => {
|
|
5061
|
-
ownerWindow.addEventListener("pointermove", handlePointerMove, true);
|
|
5062
|
-
ownerWindow.addEventListener("pointerup", handlePointerUp, true);
|
|
5063
|
-
ownerWindow.addEventListener("pointercancel", handlePointerCancel, true);
|
|
5064
|
-
};
|
|
5065
|
-
const removeDragListeners = () => {
|
|
5066
|
-
ownerWindow.removeEventListener("pointermove", handlePointerMove, true);
|
|
5067
|
-
ownerWindow.removeEventListener("pointerup", handlePointerUp, true);
|
|
5068
|
-
ownerWindow.removeEventListener(
|
|
5069
|
-
"pointercancel",
|
|
5070
|
-
handlePointerCancel,
|
|
5071
|
-
true
|
|
5072
|
-
);
|
|
5073
|
-
};
|
|
5074
|
-
const releasePointerCapture = (event) => {
|
|
5075
|
-
try {
|
|
5076
|
-
if (layer.hasPointerCapture(event.pointerId)) {
|
|
5077
|
-
layer.releasePointerCapture(event.pointerId);
|
|
5078
|
-
}
|
|
5079
|
-
} catch {
|
|
5080
|
-
}
|
|
5081
|
-
};
|
|
5082
|
-
function isActivePointer(event) {
|
|
5083
|
-
return isDragging && event.pointerId === activePointerId;
|
|
5084
|
-
}
|
|
5085
|
-
const finishAreaSelection = (event) => {
|
|
5086
|
-
if (!isActivePointer(event)) return;
|
|
5087
|
-
event.preventDefault();
|
|
5088
|
-
updateBox(event);
|
|
5089
|
-
releasePointerCapture(event);
|
|
5090
|
-
removeDragListeners();
|
|
5091
|
-
isDragging = false;
|
|
5092
|
-
activePointerId = void 0;
|
|
5093
|
-
if (!selection || selection.width < 8 || selection.height < 8) return;
|
|
5094
|
-
this.config.actions.setSelectingArea(true);
|
|
5095
|
-
this.config.actions.render();
|
|
5096
|
-
void this.config.actions.createAreaDraft(selection);
|
|
5097
|
-
};
|
|
5098
|
-
function handlePointerMove(event) {
|
|
5099
|
-
if (!isActivePointer(event)) return;
|
|
5100
|
-
event.preventDefault();
|
|
5101
|
-
updateBox(event);
|
|
5102
|
-
}
|
|
5103
|
-
const handlePointerUp = (event) => {
|
|
5104
|
-
finishAreaSelection(event);
|
|
5105
|
-
};
|
|
5106
|
-
const handlePointerCancel = (event) => {
|
|
5107
|
-
if (!isActivePointer(event)) return;
|
|
5108
|
-
releasePointerCapture(event);
|
|
5109
|
-
removeDragListeners();
|
|
5110
|
-
isDragging = false;
|
|
5111
|
-
activePointerId = void 0;
|
|
5112
|
-
};
|
|
5516
|
+
layer.className = "dfwr-draft-cancel-layer";
|
|
5517
|
+
layer.setAttribute("aria-hidden", "true");
|
|
5113
5518
|
layer.addEventListener("pointerdown", (event) => {
|
|
5114
5519
|
if (event.button !== 0) return;
|
|
5115
|
-
|
|
5116
|
-
activePointerId = event.pointerId;
|
|
5117
|
-
isDragging = true;
|
|
5118
|
-
try {
|
|
5119
|
-
layer.setPointerCapture(event.pointerId);
|
|
5120
|
-
} catch {
|
|
5121
|
-
}
|
|
5122
|
-
const startPoint = toTargetPointFromHostEvent(
|
|
5123
|
-
event,
|
|
5124
|
-
this.config.getEnvironment()
|
|
5125
|
-
);
|
|
5126
|
-
startX = startPoint.x;
|
|
5127
|
-
startY = startPoint.y;
|
|
5128
|
-
updateBox(event);
|
|
5129
|
-
addDragListeners();
|
|
5520
|
+
this.cancelDraft(event);
|
|
5130
5521
|
});
|
|
5131
|
-
layer.addEventListener("pointermove", handlePointerMove);
|
|
5132
|
-
layer.addEventListener("pointerup", handlePointerUp);
|
|
5133
|
-
layer.addEventListener("pointercancel", handlePointerCancel);
|
|
5134
5522
|
return layer;
|
|
5135
5523
|
}
|
|
5524
|
+
cancelDraft(event) {
|
|
5525
|
+
event?.preventDefault();
|
|
5526
|
+
event?.stopPropagation();
|
|
5527
|
+
event?.stopImmediatePropagation();
|
|
5528
|
+
this.config.actions.setModeState("idle");
|
|
5529
|
+
this.config.actions.clearDrafts();
|
|
5530
|
+
this.config.actions.setSelectingArea(false);
|
|
5531
|
+
this.config.actions.render();
|
|
5532
|
+
}
|
|
5136
5533
|
};
|
|
5137
5534
|
|
|
5138
5535
|
// src/core/web.review.kit.app.ts
|
|
@@ -5172,6 +5569,7 @@ var WebReviewKitApp = class {
|
|
|
5172
5569
|
this.items = [];
|
|
5173
5570
|
this.draftError = "";
|
|
5174
5571
|
this.isCreatingItem = false;
|
|
5572
|
+
this.isCapturingViewport = false;
|
|
5175
5573
|
this.isSelectingArea = false;
|
|
5176
5574
|
this.handleKeyDown = (event) => {
|
|
5177
5575
|
if (event.key === "Escape" && this.cancelMode()) {
|
|
@@ -5200,10 +5598,11 @@ var WebReviewKitApp = class {
|
|
|
5200
5598
|
isOpen: this.isOpen,
|
|
5201
5599
|
mode: this.mode,
|
|
5202
5600
|
items: this.items,
|
|
5203
|
-
|
|
5601
|
+
domDraft: this.domDraft,
|
|
5204
5602
|
areaDraft: this.areaDraft,
|
|
5205
5603
|
draftError: this.draftError,
|
|
5206
5604
|
isCreatingItem: this.isCreatingItem,
|
|
5605
|
+
isCapturingViewport: this.isCapturingViewport,
|
|
5207
5606
|
isSelectingArea: this.isSelectingArea,
|
|
5208
5607
|
highlightedItemId: this.highlightedItemId
|
|
5209
5608
|
}),
|
|
@@ -5215,13 +5614,9 @@ var WebReviewKitApp = class {
|
|
|
5215
5614
|
restoreItem: (item) => this.restoreItem(item),
|
|
5216
5615
|
removeItem: (itemId) => this.adapter.remove(itemId),
|
|
5217
5616
|
setModeState: (mode) => this.setModeState(mode),
|
|
5218
|
-
clearDrafts: () =>
|
|
5219
|
-
|
|
5220
|
-
this.
|
|
5221
|
-
this.draftError = "";
|
|
5222
|
-
},
|
|
5223
|
-
setNoteDraft: (draft) => {
|
|
5224
|
-
this.noteDraft = draft;
|
|
5617
|
+
clearDrafts: () => this.clearDrafts(),
|
|
5618
|
+
setDomDraft: (draft) => {
|
|
5619
|
+
this.domDraft = draft;
|
|
5225
5620
|
this.draftError = "";
|
|
5226
5621
|
},
|
|
5227
5622
|
setAreaDraft: (draft) => {
|
|
@@ -5232,7 +5627,8 @@ var WebReviewKitApp = class {
|
|
|
5232
5627
|
this.isSelectingArea = isSelectingArea;
|
|
5233
5628
|
},
|
|
5234
5629
|
createItem: (input) => this.createItem(input),
|
|
5235
|
-
|
|
5630
|
+
captureDomDraft: (input) => this.captureDomDraft(input),
|
|
5631
|
+
captureAreaDraft: (input) => this.captureAreaDraft(input),
|
|
5236
5632
|
bindElementDraftToPoint: (point, fields) => this.bindElementDraftToPoint(point, fields),
|
|
5237
5633
|
createAreaDraft: (selection) => this.createAreaDraft(selection)
|
|
5238
5634
|
}
|
|
@@ -5254,6 +5650,7 @@ var WebReviewKitApp = class {
|
|
|
5254
5650
|
}
|
|
5255
5651
|
destroy() {
|
|
5256
5652
|
this.view.clearDraftPreview();
|
|
5653
|
+
this.clearDrafts();
|
|
5257
5654
|
document.removeEventListener("keydown", this.handleKeyDown, true);
|
|
5258
5655
|
window.removeEventListener("scroll", this.handleViewportChange, true);
|
|
5259
5656
|
window.removeEventListener("resize", this.handleViewportChange);
|
|
@@ -5273,8 +5670,7 @@ var WebReviewKitApp = class {
|
|
|
5273
5670
|
close() {
|
|
5274
5671
|
this.isOpen = false;
|
|
5275
5672
|
this.setModeState("idle");
|
|
5276
|
-
this.
|
|
5277
|
-
this.areaDraft = void 0;
|
|
5673
|
+
this.clearDrafts();
|
|
5278
5674
|
this.isSelectingArea = false;
|
|
5279
5675
|
this.render();
|
|
5280
5676
|
}
|
|
@@ -5290,8 +5686,7 @@ var WebReviewKitApp = class {
|
|
|
5290
5686
|
this.isOpen = true;
|
|
5291
5687
|
}
|
|
5292
5688
|
this.setModeState(this.mode === mode ? "idle" : mode);
|
|
5293
|
-
this.
|
|
5294
|
-
this.areaDraft = void 0;
|
|
5689
|
+
this.clearDrafts();
|
|
5295
5690
|
this.render();
|
|
5296
5691
|
}
|
|
5297
5692
|
async startElementReview(element, comment) {
|
|
@@ -5299,8 +5694,7 @@ var WebReviewKitApp = class {
|
|
|
5299
5694
|
this.isOpen = true;
|
|
5300
5695
|
}
|
|
5301
5696
|
this.setModeState("element");
|
|
5302
|
-
this.
|
|
5303
|
-
this.areaDraft = void 0;
|
|
5697
|
+
this.clearDrafts();
|
|
5304
5698
|
this.isSelectingArea = false;
|
|
5305
5699
|
await this.bindElementDraftToElement(element, { comment });
|
|
5306
5700
|
}
|
|
@@ -5325,6 +5719,20 @@ var WebReviewKitApp = class {
|
|
|
5325
5719
|
this.hiddenItemIds = itemIds ? new Set(itemIds) : void 0;
|
|
5326
5720
|
this.updateHiddenItemsStyle();
|
|
5327
5721
|
}
|
|
5722
|
+
clearDrafts() {
|
|
5723
|
+
this.revokeDraftAttachmentPreviews(this.domDraft);
|
|
5724
|
+
this.revokeDraftAttachmentPreviews(this.areaDraft);
|
|
5725
|
+
this.domDraft = void 0;
|
|
5726
|
+
this.areaDraft = void 0;
|
|
5727
|
+
this.draftError = "";
|
|
5728
|
+
}
|
|
5729
|
+
revokeDraftAttachmentPreviews(draft) {
|
|
5730
|
+
draft?.attachments?.forEach((attachment) => {
|
|
5731
|
+
if (attachment.previewUrl) {
|
|
5732
|
+
URL.revokeObjectURL(attachment.previewUrl);
|
|
5733
|
+
}
|
|
5734
|
+
});
|
|
5735
|
+
}
|
|
5328
5736
|
clearHighlightedItem() {
|
|
5329
5737
|
if (!this.highlightedItemId) return;
|
|
5330
5738
|
this.highlightedItemId = void 0;
|
|
@@ -5360,18 +5768,17 @@ var WebReviewKitApp = class {
|
|
|
5360
5768
|
this.options.onModeChange?.(mode);
|
|
5361
5769
|
}
|
|
5362
5770
|
cancelMode() {
|
|
5363
|
-
if (this.mode === "idle" && !this.
|
|
5771
|
+
if (this.mode === "idle" && !this.domDraft && !this.areaDraft && !this.isSelectingArea) {
|
|
5364
5772
|
return false;
|
|
5365
5773
|
}
|
|
5366
5774
|
this.setModeState("idle");
|
|
5367
|
-
this.
|
|
5368
|
-
this.areaDraft = void 0;
|
|
5775
|
+
this.clearDrafts();
|
|
5369
5776
|
this.isSelectingArea = false;
|
|
5370
5777
|
this.render();
|
|
5371
5778
|
return true;
|
|
5372
5779
|
}
|
|
5373
5780
|
isDraftComposerFocused() {
|
|
5374
|
-
if (!this.
|
|
5781
|
+
if (!this.domDraft && !this.areaDraft) return false;
|
|
5375
5782
|
const composerHost = this.getEnvironment()?.composerHost;
|
|
5376
5783
|
const activeElement = composerHost?.ownerDocument.activeElement;
|
|
5377
5784
|
return Boolean(
|
|
@@ -5407,6 +5814,8 @@ var WebReviewKitApp = class {
|
|
|
5407
5814
|
};
|
|
5408
5815
|
const overlayRect = target.getOverlayRect?.() ?? rect;
|
|
5409
5816
|
const composerHost = target.getComposerHost?.();
|
|
5817
|
+
const scaleX = target.window.innerWidth > 0 ? rect.width / target.window.innerWidth : 1;
|
|
5818
|
+
const scaleY = target.window.innerHeight > 0 ? rect.height / target.window.innerHeight : 1;
|
|
5410
5819
|
return {
|
|
5411
5820
|
window: target.window,
|
|
5412
5821
|
document: target.document,
|
|
@@ -5416,13 +5825,16 @@ var WebReviewKitApp = class {
|
|
|
5416
5825
|
width: rect.width,
|
|
5417
5826
|
height: rect.height
|
|
5418
5827
|
},
|
|
5828
|
+
scaleX,
|
|
5829
|
+
scaleY,
|
|
5419
5830
|
overlayRect: {
|
|
5420
5831
|
left: overlayRect.left,
|
|
5421
5832
|
top: overlayRect.top,
|
|
5422
5833
|
width: overlayRect.width,
|
|
5423
5834
|
height: overlayRect.height
|
|
5424
5835
|
},
|
|
5425
|
-
composerHost
|
|
5836
|
+
composerHost,
|
|
5837
|
+
captureViewport: target.captureViewport
|
|
5426
5838
|
};
|
|
5427
5839
|
} catch {
|
|
5428
5840
|
return void 0;
|
|
@@ -5445,32 +5857,6 @@ var WebReviewKitApp = class {
|
|
|
5445
5857
|
if (!this.shadow) return;
|
|
5446
5858
|
this.view.render(this.shadow, this.createHiddenItemsStyleElement());
|
|
5447
5859
|
}
|
|
5448
|
-
async bindNoteDraftToPoint(point, fields = {}) {
|
|
5449
|
-
const environment = this.getEnvironment();
|
|
5450
|
-
if (!environment) return;
|
|
5451
|
-
const viewport = getViewportSize(environment);
|
|
5452
|
-
const nextPoint = clampPoint(point, environment);
|
|
5453
|
-
const draft = await this.withOverlayHidden(() => {
|
|
5454
|
-
const selection = getPointSelection(nextPoint);
|
|
5455
|
-
const anchor = getDomAnchor(
|
|
5456
|
-
selection,
|
|
5457
|
-
this.options.anchors?.attribute,
|
|
5458
|
-
environment
|
|
5459
|
-
);
|
|
5460
|
-
const marker = {
|
|
5461
|
-
viewport: roundPoint(nextPoint),
|
|
5462
|
-
relative: anchor ? getRelativePoint(nextPoint, anchor, environment) : void 0
|
|
5463
|
-
};
|
|
5464
|
-
return {
|
|
5465
|
-
viewport,
|
|
5466
|
-
anchor,
|
|
5467
|
-
marker,
|
|
5468
|
-
...fields
|
|
5469
|
-
};
|
|
5470
|
-
});
|
|
5471
|
-
this.noteDraft = draft;
|
|
5472
|
-
this.render();
|
|
5473
|
-
}
|
|
5474
5860
|
async bindElementDraftToPoint(point, fields = {}) {
|
|
5475
5861
|
const environment = this.getEnvironment();
|
|
5476
5862
|
if (!environment) return;
|
|
@@ -5519,7 +5905,7 @@ var WebReviewKitApp = class {
|
|
|
5519
5905
|
previewElement
|
|
5520
5906
|
};
|
|
5521
5907
|
});
|
|
5522
|
-
this.
|
|
5908
|
+
this.domDraft = draft;
|
|
5523
5909
|
this.render();
|
|
5524
5910
|
}
|
|
5525
5911
|
async bindElementDraftToElement(element, fields = {}) {
|
|
@@ -5560,7 +5946,7 @@ var WebReviewKitApp = class {
|
|
|
5560
5946
|
};
|
|
5561
5947
|
});
|
|
5562
5948
|
if (!draft) return;
|
|
5563
|
-
this.
|
|
5949
|
+
this.domDraft = draft;
|
|
5564
5950
|
this.render();
|
|
5565
5951
|
}
|
|
5566
5952
|
async createAreaDraft(selection) {
|
|
@@ -5573,16 +5959,27 @@ var WebReviewKitApp = class {
|
|
|
5573
5959
|
try {
|
|
5574
5960
|
const viewport = getViewportSize(environment);
|
|
5575
5961
|
this.areaDraft = await this.withOverlayHidden(() => {
|
|
5576
|
-
const
|
|
5962
|
+
const anchorPoint = clampPoint(
|
|
5963
|
+
getSelectionCenter(selection),
|
|
5964
|
+
environment
|
|
5965
|
+
);
|
|
5966
|
+
const anchor = getDomAnchorFromPoint(
|
|
5967
|
+
anchorPoint,
|
|
5968
|
+
this.options.anchors?.attribute,
|
|
5969
|
+
environment
|
|
5970
|
+
);
|
|
5971
|
+
const marker = createSelectionStartMarker(
|
|
5577
5972
|
selection,
|
|
5578
|
-
|
|
5973
|
+
anchor,
|
|
5579
5974
|
environment
|
|
5580
5975
|
);
|
|
5581
5976
|
const reviewSelection = {
|
|
5582
|
-
viewport: toPublicSelection(selection)
|
|
5977
|
+
viewport: toPublicSelection(selection),
|
|
5978
|
+
relative: anchor ? getRelativeSelection(selection, anchor, environment) : void 0
|
|
5583
5979
|
};
|
|
5584
5980
|
return {
|
|
5585
5981
|
viewport,
|
|
5982
|
+
anchor,
|
|
5586
5983
|
marker,
|
|
5587
5984
|
selection: reviewSelection
|
|
5588
5985
|
};
|
|
@@ -5603,6 +6000,127 @@ var WebReviewKitApp = class {
|
|
|
5603
6000
|
this.root.style.display = previousDisplay;
|
|
5604
6001
|
}
|
|
5605
6002
|
}
|
|
6003
|
+
async captureDomDraft(input) {
|
|
6004
|
+
if (this.isCapturingViewport) return;
|
|
6005
|
+
const environment = this.getEnvironment();
|
|
6006
|
+
const draft = this.domDraft;
|
|
6007
|
+
if (!draft) return;
|
|
6008
|
+
if (!environment?.captureViewport) {
|
|
6009
|
+
this.draftError = "Viewport capture helper is not available.";
|
|
6010
|
+
this.render();
|
|
6011
|
+
return;
|
|
6012
|
+
}
|
|
6013
|
+
const captureInput = this.createViewportCaptureInput(
|
|
6014
|
+
environment,
|
|
6015
|
+
input,
|
|
6016
|
+
input.selection?.viewport
|
|
6017
|
+
);
|
|
6018
|
+
this.draftError = "";
|
|
6019
|
+
this.isCapturingViewport = true;
|
|
6020
|
+
this.render();
|
|
6021
|
+
try {
|
|
6022
|
+
const result = await environment.captureViewport(captureInput);
|
|
6023
|
+
const attachment = this.createCaptureDraftAttachment(result, captureInput);
|
|
6024
|
+
const currentDraft = this.domDraft ?? draft;
|
|
6025
|
+
this.domDraft = {
|
|
6026
|
+
...currentDraft,
|
|
6027
|
+
attachments: [...currentDraft.attachments ?? [], attachment]
|
|
6028
|
+
};
|
|
6029
|
+
} catch (error) {
|
|
6030
|
+
this.draftError = this.getErrorMessage(
|
|
6031
|
+
error,
|
|
6032
|
+
"Failed to capture viewport."
|
|
6033
|
+
);
|
|
6034
|
+
} finally {
|
|
6035
|
+
this.isCapturingViewport = false;
|
|
6036
|
+
this.render();
|
|
6037
|
+
}
|
|
6038
|
+
}
|
|
6039
|
+
async captureAreaDraft(input) {
|
|
6040
|
+
if (this.isCapturingViewport) return;
|
|
6041
|
+
const environment = this.getEnvironment();
|
|
6042
|
+
const draft = this.areaDraft;
|
|
6043
|
+
if (!draft) return;
|
|
6044
|
+
if (!environment?.captureViewport) {
|
|
6045
|
+
this.draftError = "Viewport capture helper is not available.";
|
|
6046
|
+
this.render();
|
|
6047
|
+
return;
|
|
6048
|
+
}
|
|
6049
|
+
const captureInput = this.createViewportCaptureInput(
|
|
6050
|
+
environment,
|
|
6051
|
+
input,
|
|
6052
|
+
input.selection?.viewport
|
|
6053
|
+
);
|
|
6054
|
+
this.draftError = "";
|
|
6055
|
+
this.isCapturingViewport = true;
|
|
6056
|
+
this.render();
|
|
6057
|
+
try {
|
|
6058
|
+
const result = await environment.captureViewport(captureInput);
|
|
6059
|
+
const attachment = this.createCaptureDraftAttachment(result, captureInput);
|
|
6060
|
+
const currentDraft = this.areaDraft ?? draft;
|
|
6061
|
+
this.areaDraft = {
|
|
6062
|
+
...currentDraft,
|
|
6063
|
+
attachments: [...currentDraft.attachments ?? [], attachment]
|
|
6064
|
+
};
|
|
6065
|
+
} catch (error) {
|
|
6066
|
+
this.draftError = this.getErrorMessage(
|
|
6067
|
+
error,
|
|
6068
|
+
"Failed to capture viewport."
|
|
6069
|
+
);
|
|
6070
|
+
} finally {
|
|
6071
|
+
this.isCapturingViewport = false;
|
|
6072
|
+
this.render();
|
|
6073
|
+
}
|
|
6074
|
+
}
|
|
6075
|
+
createViewportCaptureInput(environment, draft, captureRegion) {
|
|
6076
|
+
const timestamp = (/* @__PURE__ */ new Date()).toISOString();
|
|
6077
|
+
const viewport = draft.viewport ?? getViewportSize(environment);
|
|
6078
|
+
const routeKey = getRouteKey(environment);
|
|
6079
|
+
return {
|
|
6080
|
+
routeKey,
|
|
6081
|
+
pageUrl: getPageUrl(environment),
|
|
6082
|
+
originalUrl: getOriginalUrl(environment),
|
|
6083
|
+
viewport,
|
|
6084
|
+
captureRegion,
|
|
6085
|
+
devicePixelRatio: environment.window.devicePixelRatio || 1,
|
|
6086
|
+
scroll: {
|
|
6087
|
+
x: environment.window.scrollX,
|
|
6088
|
+
y: environment.window.scrollY
|
|
6089
|
+
},
|
|
6090
|
+
marker: draft.marker,
|
|
6091
|
+
selection: draft.selection,
|
|
6092
|
+
timestamp
|
|
6093
|
+
};
|
|
6094
|
+
}
|
|
6095
|
+
createCaptureDraftAttachment(result, input) {
|
|
6096
|
+
const mime = result.mime || result.file.type || "image/png";
|
|
6097
|
+
const name = result.name || `review-capture-${Date.now()}.png`;
|
|
6098
|
+
return {
|
|
6099
|
+
id: createId(),
|
|
6100
|
+
file: result.file,
|
|
6101
|
+
name,
|
|
6102
|
+
mime,
|
|
6103
|
+
size: result.file.size,
|
|
6104
|
+
kind: "capture",
|
|
6105
|
+
previewUrl: mime.startsWith("image/") ? URL.createObjectURL(result.file) : void 0,
|
|
6106
|
+
metadata: {
|
|
6107
|
+
...result.metadata,
|
|
6108
|
+
source: "viewport-capture",
|
|
6109
|
+
target: "iframe",
|
|
6110
|
+
routeKey: input.routeKey,
|
|
6111
|
+
pageUrl: input.pageUrl,
|
|
6112
|
+
originalUrl: input.originalUrl,
|
|
6113
|
+
viewport: input.viewport,
|
|
6114
|
+
scroll: input.scroll,
|
|
6115
|
+
marker: input.marker,
|
|
6116
|
+
selection: input.selection,
|
|
6117
|
+
timestamp: input.timestamp,
|
|
6118
|
+
devicePixelRatio: input.devicePixelRatio,
|
|
6119
|
+
width: result.width,
|
|
6120
|
+
height: result.height
|
|
6121
|
+
}
|
|
6122
|
+
};
|
|
6123
|
+
}
|
|
5606
6124
|
async createItem(input) {
|
|
5607
6125
|
const environment = this.getEnvironment();
|
|
5608
6126
|
if (!environment || this.isCreatingItem) return;
|
|
@@ -5646,24 +6164,61 @@ var WebReviewKitApp = class {
|
|
|
5646
6164
|
this.isCreatingItem = true;
|
|
5647
6165
|
this.render();
|
|
5648
6166
|
try {
|
|
5649
|
-
const
|
|
6167
|
+
const attachments = await this.uploadDraftAttachments(
|
|
6168
|
+
input.attachments,
|
|
6169
|
+
item
|
|
6170
|
+
);
|
|
6171
|
+
const itemWithAttachments = attachments.length > 0 ? { ...item, attachments } : item;
|
|
6172
|
+
const createdItem = await this.adapter.create(itemWithAttachments);
|
|
5650
6173
|
this.setModeState("idle");
|
|
5651
|
-
this.
|
|
5652
|
-
this.areaDraft = void 0;
|
|
6174
|
+
this.clearDrafts();
|
|
5653
6175
|
this.highlightItem(createdItem.id);
|
|
5654
6176
|
await this.reload();
|
|
5655
6177
|
await this.options.onCreateItem?.(createdItem);
|
|
5656
6178
|
} catch (error) {
|
|
5657
|
-
this.draftError =
|
|
6179
|
+
this.draftError = this.getCreateItemErrorMessage(
|
|
6180
|
+
error,
|
|
6181
|
+
Boolean(input.attachments?.length)
|
|
6182
|
+
);
|
|
5658
6183
|
} finally {
|
|
5659
6184
|
this.isCreatingItem = false;
|
|
5660
6185
|
this.render();
|
|
5661
6186
|
}
|
|
5662
6187
|
}
|
|
6188
|
+
async uploadDraftAttachments(attachments, item) {
|
|
6189
|
+
if (!attachments?.length) return [];
|
|
6190
|
+
const uploadAttachment = this.adapter.uploadAttachment;
|
|
6191
|
+
if (!uploadAttachment) {
|
|
6192
|
+
throw new Error("Attachment upload adapter is not configured.");
|
|
6193
|
+
}
|
|
6194
|
+
return Promise.all(
|
|
6195
|
+
attachments.map(
|
|
6196
|
+
(attachment) => uploadAttachment({
|
|
6197
|
+
file: attachment.file,
|
|
6198
|
+
name: attachment.name,
|
|
6199
|
+
mime: attachment.mime,
|
|
6200
|
+
kind: attachment.kind,
|
|
6201
|
+
item,
|
|
6202
|
+
metadata: attachment.metadata
|
|
6203
|
+
})
|
|
6204
|
+
)
|
|
6205
|
+
);
|
|
6206
|
+
}
|
|
6207
|
+
getCreateItemErrorMessage(error, wasUploadingAttachments) {
|
|
6208
|
+
const message = this.getErrorMessage(error, "Failed to save QA.");
|
|
6209
|
+
const reason = error && typeof error === "object" && "reason" in error && typeof error.reason === "string" ? ` (${error.reason})` : "";
|
|
6210
|
+
return wasUploadingAttachments && reason ? `Attachment upload failed${reason}: ${message}` : message;
|
|
6211
|
+
}
|
|
6212
|
+
getErrorMessage(error, fallback) {
|
|
6213
|
+
if (error instanceof Error) return error.message;
|
|
6214
|
+
if (error && typeof error === "object" && "message" in error && typeof error.message === "string") {
|
|
6215
|
+
return error.message;
|
|
6216
|
+
}
|
|
6217
|
+
return fallback;
|
|
6218
|
+
}
|
|
5663
6219
|
async restoreItem(item) {
|
|
5664
6220
|
this.setModeState("idle");
|
|
5665
|
-
this.
|
|
5666
|
-
this.areaDraft = void 0;
|
|
6221
|
+
this.clearDrafts();
|
|
5667
6222
|
if (this.options.onRestoreItem) {
|
|
5668
6223
|
await this.options.onRestoreItem(item);
|
|
5669
6224
|
return;
|