@designfever/web-review-kit 0.7.2 → 0.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +7 -4
- package/dist/{chunk-RPVLRULC.js → chunk-4ZP7B7R6.js} +2345 -1786
- package/dist/chunk-4ZP7B7R6.js.map +1 -0
- package/dist/{chunk-K26WLRRP.js → chunk-UNDQZ4Y2.js} +33 -9
- package/dist/chunk-UNDQZ4Y2.js.map +1 -0
- package/dist/index.cjs +2381 -1801
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +17 -5
- package/dist/index.d.ts +17 -5
- package/dist/index.js +11 -3
- package/dist/index.js.map +1 -1
- package/dist/react-shell.cjs +14508 -11099
- package/dist/react-shell.cjs.map +1 -1
- package/dist/react-shell.d.cts +5 -4
- package/dist/react-shell.d.ts +5 -4
- package/dist/react-shell.js +10634 -7710
- package/dist/react-shell.js.map +1 -1
- package/dist/{types-DT9Z66mV.d.cts → types-BM8E7BFV.d.cts} +63 -3
- package/dist/{types-DT9Z66mV.d.ts → types-BM8E7BFV.d.ts} +63 -3
- package/dist/vite.cjs +344 -48
- package/dist/vite.cjs.map +1 -1
- package/dist/vite.d.cts +2 -0
- package/dist/vite.d.ts +2 -0
- package/dist/vite.js +346 -50
- package/dist/vite.js.map +1 -1
- package/docs/README.md +19 -11
- package/docs/adapters.md +56 -4
- package/docs/adaptor.sample.ts +53 -3
- package/docs/architecture.md +117 -3
- package/docs/figma-overlay.md +51 -0
- package/docs/grid-overlay.md +5 -0
- package/docs/installation.md +23 -8
- package/docs/release-notes-0.7.3.md +30 -0
- package/docs/release-notes-0.8.0.md +175 -0
- package/docs/testing.md +68 -0
- package/package.json +14 -5
- package/dist/chunk-K26WLRRP.js.map +0 -1
- 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
|
@@ -30,6 +30,7 @@ __export(index_exports, {
|
|
|
30
30
|
REVIEW_WORKFLOW_STATUS_OPTIONS: () => REVIEW_WORKFLOW_STATUS_OPTIONS,
|
|
31
31
|
ReviewFigmaTokenError: () => ReviewFigmaTokenError,
|
|
32
32
|
collectReviewFigmaReleaseSnapshot: () => collectReviewFigmaReleaseSnapshot,
|
|
33
|
+
createEndpointReviewFigmaImageStore: () => createEndpointReviewFigmaImageStore,
|
|
33
34
|
createRemoteReviewFigmaImageStore: () => createRemoteReviewFigmaImageStore,
|
|
34
35
|
createReviewFigmaFrameUrl: () => createReviewFigmaFrameUrl,
|
|
35
36
|
createReviewFigmaImageStoreClient: () => createReviewFigmaImageStoreClient,
|
|
@@ -156,8 +157,8 @@ function localAdapter(options = {}) {
|
|
|
156
157
|
function normalizeStoredReviewItem(value) {
|
|
157
158
|
if (!value || typeof value !== "object") return void 0;
|
|
158
159
|
const raw = value;
|
|
159
|
-
const kind = raw.kind === "
|
|
160
|
-
if (kind !== "
|
|
160
|
+
const kind = raw.kind === "capture" ? "area" : raw.kind;
|
|
161
|
+
if (kind !== "dom" && kind !== "area") return void 0;
|
|
161
162
|
const { screenshot: _screenshot, reviewNumber: _reviewNumber, ...item } = raw;
|
|
162
163
|
if (kind === raw.kind && _screenshot === void 0 && _reviewNumber === void 0) {
|
|
163
164
|
return raw;
|
|
@@ -332,6 +333,8 @@ function rowToReviewItem(row, options) {
|
|
|
332
333
|
const routeKey = row.route_key || item.routeKey || item.normalizedPath || "/";
|
|
333
334
|
const viewport = item.viewport ?? { width: 390, height: 720 };
|
|
334
335
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
336
|
+
const kind = normalizeReviewItemKind(item);
|
|
337
|
+
if (!kind) return null;
|
|
335
338
|
return {
|
|
336
339
|
...item,
|
|
337
340
|
id: row.id,
|
|
@@ -340,7 +343,7 @@ function rowToReviewItem(row, options) {
|
|
|
340
343
|
routeKey,
|
|
341
344
|
pageUrl: item.pageUrl || toAbsoluteReviewUrl(routeKey),
|
|
342
345
|
normalizedPath: item.normalizedPath || routeKey,
|
|
343
|
-
kind
|
|
346
|
+
kind,
|
|
344
347
|
comment: item.comment || "",
|
|
345
348
|
status,
|
|
346
349
|
viewport,
|
|
@@ -357,6 +360,10 @@ function rowToReviewItem(row, options) {
|
|
|
357
360
|
updatedAt: row.updated_at ?? item.updatedAt ?? now
|
|
358
361
|
};
|
|
359
362
|
}
|
|
363
|
+
function normalizeReviewItemKind(item) {
|
|
364
|
+
if (item.kind === "area" || item.kind === "dom") return item.kind;
|
|
365
|
+
return null;
|
|
366
|
+
}
|
|
360
367
|
async function unwrapResponse(request, label) {
|
|
361
368
|
const { data, error } = await request;
|
|
362
369
|
if (error) {
|
|
@@ -534,8 +541,14 @@ function createReviewFigmaImageApiUrl({
|
|
|
534
541
|
// src/figma/image.store.ts
|
|
535
542
|
var DEFAULT_REVIEW_FIGMA_IMAGE_STORE_ENDPOINT = "/__dfwr/figma-images";
|
|
536
543
|
function createReviewFigmaImageStoreClient(options = {}) {
|
|
544
|
+
return createEndpointReviewFigmaImageStore(options);
|
|
545
|
+
}
|
|
546
|
+
function createEndpointReviewFigmaImageStore(options = {}) {
|
|
537
547
|
const endpoint = options.endpoint ?? DEFAULT_REVIEW_FIGMA_IMAGE_STORE_ENDPOINT;
|
|
538
|
-
const request = createReviewFigmaImageStoreRequest(
|
|
548
|
+
const request = createReviewFigmaImageStoreRequest(
|
|
549
|
+
options.fetch,
|
|
550
|
+
options.headers
|
|
551
|
+
);
|
|
539
552
|
return {
|
|
540
553
|
listImages(target) {
|
|
541
554
|
const url = `${endpoint}?target=${encodeURIComponent(
|
|
@@ -756,19 +769,27 @@ function getReviewFigmaImageMimeType(format) {
|
|
|
756
769
|
if (format === "png") return "image/png";
|
|
757
770
|
return "image/webp";
|
|
758
771
|
}
|
|
759
|
-
function createReviewFigmaImageStoreRequest(
|
|
772
|
+
function createReviewFigmaImageStoreRequest(fetchOption, headersProvider) {
|
|
760
773
|
return async (input, init = {}) => {
|
|
761
774
|
const requestFetch = fetchOption ?? globalThis.fetch;
|
|
762
775
|
if (!requestFetch) throw new Error("Figma image store requires fetch.");
|
|
763
776
|
const figmaToken = init.figmaToken ?? "";
|
|
764
|
-
const {
|
|
777
|
+
const {
|
|
778
|
+
figmaToken: _figmaToken,
|
|
779
|
+
headers: requestHeaders,
|
|
780
|
+
...requestInit
|
|
781
|
+
} = init;
|
|
782
|
+
const headers = new Headers();
|
|
783
|
+
headers.set("Content-Type", "application/json");
|
|
784
|
+
if (figmaToken) headers.set("X-Figma-Token", figmaToken);
|
|
785
|
+
appendReviewFigmaImageStoreHeaders(
|
|
786
|
+
headers,
|
|
787
|
+
await readReviewFigmaImageStoreHeaders(headersProvider)
|
|
788
|
+
);
|
|
789
|
+
appendReviewFigmaImageStoreHeaders(headers, requestHeaders);
|
|
765
790
|
const response = await requestFetch(input, {
|
|
766
791
|
...requestInit,
|
|
767
|
-
headers
|
|
768
|
-
"Content-Type": "application/json",
|
|
769
|
-
...figmaToken ? { "X-Figma-Token": figmaToken } : {},
|
|
770
|
-
...requestInit.headers ?? {}
|
|
771
|
-
}
|
|
792
|
+
headers
|
|
772
793
|
});
|
|
773
794
|
const text = await response.text();
|
|
774
795
|
const body = text ? JSON.parse(text) : null;
|
|
@@ -779,6 +800,15 @@ function createReviewFigmaImageStoreRequest(endpoint, fetchOption) {
|
|
|
779
800
|
return body;
|
|
780
801
|
};
|
|
781
802
|
}
|
|
803
|
+
async function readReviewFigmaImageStoreHeaders(provider) {
|
|
804
|
+
return typeof provider === "function" ? provider() : provider;
|
|
805
|
+
}
|
|
806
|
+
function appendReviewFigmaImageStoreHeaders(target, source) {
|
|
807
|
+
if (!source) return;
|
|
808
|
+
new Headers(source).forEach((value, key) => {
|
|
809
|
+
target.set(key, value);
|
|
810
|
+
});
|
|
811
|
+
}
|
|
782
812
|
function readReviewFigmaImageToken(provider) {
|
|
783
813
|
const token = typeof provider === "function" ? provider() : provider;
|
|
784
814
|
return typeof token === "string" ? token.trim() : "";
|
|
@@ -1297,26 +1327,34 @@ function getPopoverBounds(environment) {
|
|
|
1297
1327
|
}
|
|
1298
1328
|
return environment.overlayRect;
|
|
1299
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
|
+
}
|
|
1300
1335
|
function toHostPoint(point, environment) {
|
|
1301
1336
|
if (!environment) return point;
|
|
1337
|
+
const { scaleX, scaleY } = getEnvironmentScale(environment);
|
|
1302
1338
|
return {
|
|
1303
|
-
x: point.x + environment.viewportRect.left,
|
|
1304
|
-
y: point.y + environment.viewportRect.top
|
|
1339
|
+
x: point.x * scaleX + environment.viewportRect.left,
|
|
1340
|
+
y: point.y * scaleY + environment.viewportRect.top
|
|
1305
1341
|
};
|
|
1306
1342
|
}
|
|
1307
1343
|
function toHostSelection(selection, environment) {
|
|
1344
|
+
const { scaleX, scaleY } = getEnvironmentScale(environment);
|
|
1308
1345
|
return {
|
|
1309
|
-
left: selection.left + environment.viewportRect.left,
|
|
1310
|
-
top: selection.top + environment.viewportRect.top,
|
|
1311
|
-
width: selection.width,
|
|
1312
|
-
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
|
|
1313
1350
|
};
|
|
1314
1351
|
}
|
|
1315
1352
|
function toTargetPoint(point, environment) {
|
|
1316
1353
|
if (!environment) return point;
|
|
1354
|
+
const { scaleX, scaleY } = getEnvironmentScale(environment);
|
|
1317
1355
|
return {
|
|
1318
|
-
x: point.x - environment.viewportRect.left,
|
|
1319
|
-
y: point.y - environment.viewportRect.top
|
|
1356
|
+
x: (point.x - environment.viewportRect.left) / scaleX,
|
|
1357
|
+
y: (point.y - environment.viewportRect.top) / scaleY
|
|
1320
1358
|
};
|
|
1321
1359
|
}
|
|
1322
1360
|
function toTargetPointFromHostEvent(event, environment) {
|
|
@@ -1365,11 +1403,6 @@ var SEMANTIC_ANCHOR_ATTRIBUTES = [
|
|
|
1365
1403
|
"name",
|
|
1366
1404
|
"href"
|
|
1367
1405
|
];
|
|
1368
|
-
function getDomAnchor(selection, configuredAttribute = "data-qa-id", environment) {
|
|
1369
|
-
const x = selection.left + selection.width / 2;
|
|
1370
|
-
const y = selection.top + selection.height / 2;
|
|
1371
|
-
return getDomAnchorFromPoint({ x, y }, configuredAttribute, environment);
|
|
1372
|
-
}
|
|
1373
1406
|
function getDomAnchorFromPoint(point, configuredAttribute = "data-qa-id", environment) {
|
|
1374
1407
|
const target = environment.document.elementFromPoint(point.x, point.y);
|
|
1375
1408
|
if (!target) return void 0;
|
|
@@ -1919,6 +1952,34 @@ function createId() {
|
|
|
1919
1952
|
}
|
|
1920
1953
|
|
|
1921
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
|
+
};
|
|
1922
1983
|
function isHotkey(event, hotkey) {
|
|
1923
1984
|
const parts = hotkey.split("+").map((part) => part.trim().toLowerCase()).filter(Boolean);
|
|
1924
1985
|
const key = parts.find(
|
|
@@ -1939,10 +2000,7 @@ function isHotkeyKey(event, key) {
|
|
|
1939
2000
|
const normalizedKey = key.toLowerCase();
|
|
1940
2001
|
if (event.key.toLowerCase() === normalizedKey) return true;
|
|
1941
2002
|
if (getHotkeyCode(normalizedKey) === event.code) return true;
|
|
1942
|
-
|
|
1943
|
-
q: ["\u3142", "\u3143"]
|
|
1944
|
-
};
|
|
1945
|
-
return aliases[normalizedKey]?.includes(event.key) ?? false;
|
|
2003
|
+
return HOTKEY_KEY_ALIASES[normalizedKey]?.includes(event.key) ?? false;
|
|
1946
2004
|
}
|
|
1947
2005
|
function getHotkeyCode(key) {
|
|
1948
2006
|
if (/^[a-z]$/.test(key)) return `Key${key.toUpperCase()}`;
|
|
@@ -1984,8 +2042,7 @@ var REVIEW_SCOPE_LABELS = {
|
|
|
1984
2042
|
mobile: "Mobile",
|
|
1985
2043
|
tablet: "Tablet",
|
|
1986
2044
|
desktop: "Desktop",
|
|
1987
|
-
wide: "Wide"
|
|
1988
|
-
dom: "Element"
|
|
2045
|
+
wide: "Wide"
|
|
1989
2046
|
};
|
|
1990
2047
|
var normalizeReviewItemScope = (value) => {
|
|
1991
2048
|
if (value === "element") return "dom";
|
|
@@ -2031,7 +2088,6 @@ function getReviewItemScope(item, presets = DEFAULT_REVIEW_VIEWPORTS) {
|
|
|
2031
2088
|
}
|
|
2032
2089
|
function getReviewItemScopeLabel(item, presets = DEFAULT_REVIEW_VIEWPORTS) {
|
|
2033
2090
|
const scope = getReviewItemScope(item, presets);
|
|
2034
|
-
if (scope === "dom") return REVIEW_SCOPE_LABELS.dom;
|
|
2035
2091
|
const preset = findReviewViewportPreset(item.viewport, presets);
|
|
2036
2092
|
return preset.label || REVIEW_SCOPE_LABELS[scope];
|
|
2037
2093
|
}
|
|
@@ -2073,7 +2129,7 @@ function normalizeReviewNumber(value) {
|
|
|
2073
2129
|
function getBoundMarkerPoint(item, environment) {
|
|
2074
2130
|
const marker = getItemMarker(item);
|
|
2075
2131
|
if (!marker) return void 0;
|
|
2076
|
-
if (item.
|
|
2132
|
+
if (item.anchor && marker.relative) {
|
|
2077
2133
|
const resolved = resolveAnchorElement(item.anchor, environment);
|
|
2078
2134
|
const element = resolved?.element;
|
|
2079
2135
|
if (element) {
|
|
@@ -2121,27 +2177,25 @@ function getItemHighlightSelection(item, environment) {
|
|
|
2121
2177
|
environment
|
|
2122
2178
|
);
|
|
2123
2179
|
}
|
|
2124
|
-
return
|
|
2125
|
-
[
|
|
2126
|
-
getAnchorHighlightSelection(item, environment),
|
|
2127
|
-
getBoundSelection(item, environment),
|
|
2128
|
-
getPointHighlightSelection(item, environment)
|
|
2129
|
-
],
|
|
2130
|
-
environment
|
|
2131
|
-
);
|
|
2180
|
+
return void 0;
|
|
2132
2181
|
}
|
|
2133
2182
|
function getReviewItemHighlightMode(item) {
|
|
2134
2183
|
if (isDomReviewItem(item)) return "dom";
|
|
2135
|
-
|
|
2136
|
-
return "note";
|
|
2184
|
+
return "area";
|
|
2137
2185
|
}
|
|
2138
2186
|
function getItemMarker(item) {
|
|
2139
2187
|
if (item.marker) return item.marker;
|
|
2140
2188
|
const selection = getItemSelection(item);
|
|
2141
2189
|
if (!selection?.viewport) return void 0;
|
|
2142
2190
|
return {
|
|
2143
|
-
viewport: roundPoint(
|
|
2144
|
-
|
|
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
|
|
2145
2199
|
};
|
|
2146
2200
|
}
|
|
2147
2201
|
function getItemSelection(item) {
|
|
@@ -2160,17 +2214,20 @@ function getItemSelection(item) {
|
|
|
2160
2214
|
function shouldShowMarkerForScope(scope, currentScope) {
|
|
2161
2215
|
return scope === currentScope;
|
|
2162
2216
|
}
|
|
2163
|
-
function
|
|
2164
|
-
const
|
|
2217
|
+
function createSelectionStartMarker(selection, anchor, environment) {
|
|
2218
|
+
const startPoint = {
|
|
2219
|
+
x: selection.left,
|
|
2220
|
+
y: selection.top
|
|
2221
|
+
};
|
|
2165
2222
|
return {
|
|
2166
|
-
viewport: roundPoint(
|
|
2167
|
-
relative: anchor ? getRelativePoint(
|
|
2223
|
+
viewport: roundPoint(startPoint),
|
|
2224
|
+
relative: anchor ? getRelativePoint(startPoint, anchor, environment) : void 0
|
|
2168
2225
|
};
|
|
2169
2226
|
}
|
|
2170
2227
|
function getBoundSelection(item, environment) {
|
|
2171
2228
|
const selection = getItemSelection(item);
|
|
2172
2229
|
if (!selection?.viewport) return void 0;
|
|
2173
|
-
if (item.
|
|
2230
|
+
if (item.anchor && selection.relative) {
|
|
2174
2231
|
const resolved = resolveAnchorElement(item.anchor, environment);
|
|
2175
2232
|
const element = resolved?.element;
|
|
2176
2233
|
if (element) {
|
|
@@ -2232,7 +2289,7 @@ function getVisibleHighlightSelection(candidates, environment) {
|
|
|
2232
2289
|
);
|
|
2233
2290
|
}
|
|
2234
2291
|
function isDomReviewItem(item) {
|
|
2235
|
-
return item.
|
|
2292
|
+
return item.kind === "dom" || item.scope === "dom";
|
|
2236
2293
|
}
|
|
2237
2294
|
|
|
2238
2295
|
// src/core/scroll.ts
|
|
@@ -2329,6 +2386,117 @@ function getAdjustedDraftSelection(selection, draft, presets) {
|
|
|
2329
2386
|
};
|
|
2330
2387
|
}
|
|
2331
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
|
+
|
|
2332
2500
|
// src/core/typography.tokens.ts
|
|
2333
2501
|
var reviewTypographyTokens = `
|
|
2334
2502
|
--df-review-font-sans: Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
|
|
@@ -2652,6 +2820,36 @@ function createStyleElement() {
|
|
|
2652
2820
|
--dfwr-item-color-rgb: 255, 143, 97;
|
|
2653
2821
|
}
|
|
2654
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
|
+
|
|
2655
2853
|
.dfwr-item-target-highlight {
|
|
2656
2854
|
position: fixed;
|
|
2657
2855
|
z-index: 2;
|
|
@@ -2660,7 +2858,6 @@ function createStyleElement() {
|
|
|
2660
2858
|
background: rgba(var(--dfwr-item-color-rgb), 0.08);
|
|
2661
2859
|
box-shadow:
|
|
2662
2860
|
0 0 0 1px rgba(31, 36, 40, 0.78),
|
|
2663
|
-
0 0 0 9999px rgba(0, 0, 0, 0.08),
|
|
2664
2861
|
0 12px 30px rgba(0, 0, 0, 0.24);
|
|
2665
2862
|
pointer-events: none;
|
|
2666
2863
|
}
|
|
@@ -2744,89 +2941,6 @@ function createStyleElement() {
|
|
|
2744
2941
|
border-style: dashed;
|
|
2745
2942
|
}
|
|
2746
2943
|
|
|
2747
|
-
.dfwr-bound-marker.is-note-callout,
|
|
2748
|
-
.dfwr-bound-marker.is-note-callout.is-highlighted {
|
|
2749
|
-
--dfwr-scope: #7cc7ff;
|
|
2750
|
-
--dfwr-scope-rgb: 124, 199, 255;
|
|
2751
|
-
min-width: 0;
|
|
2752
|
-
width: 0;
|
|
2753
|
-
height: 0;
|
|
2754
|
-
padding: 0;
|
|
2755
|
-
transform: none;
|
|
2756
|
-
border: 0;
|
|
2757
|
-
border-radius: 0;
|
|
2758
|
-
background: transparent;
|
|
2759
|
-
box-shadow: none;
|
|
2760
|
-
color: var(--dfwr-scope);
|
|
2761
|
-
animation: none;
|
|
2762
|
-
overflow: visible;
|
|
2763
|
-
}
|
|
2764
|
-
|
|
2765
|
-
.dfwr-bound-marker.is-note-callout::before {
|
|
2766
|
-
content: "";
|
|
2767
|
-
position: absolute;
|
|
2768
|
-
left: 0;
|
|
2769
|
-
top: 0;
|
|
2770
|
-
width: 8px;
|
|
2771
|
-
height: 8px;
|
|
2772
|
-
transform: translate(-50%, -50%);
|
|
2773
|
-
border: 2px solid #111820;
|
|
2774
|
-
border-radius: var(--df-review-radius-pill);
|
|
2775
|
-
background: var(--dfwr-scope);
|
|
2776
|
-
box-shadow:
|
|
2777
|
-
0 0 0 3px rgba(var(--dfwr-scope-rgb), 0.22),
|
|
2778
|
-
0 6px 16px rgba(0, 0, 0, 0.28);
|
|
2779
|
-
}
|
|
2780
|
-
|
|
2781
|
-
.dfwr-bound-marker.is-note-callout.is-highlighted::before {
|
|
2782
|
-
animation: dfwr-note-dot-pulse 1000ms ease-in-out infinite;
|
|
2783
|
-
}
|
|
2784
|
-
|
|
2785
|
-
.dfwr-bound-marker.is-note-callout .dfwr-bound-marker-icon {
|
|
2786
|
-
position: absolute;
|
|
2787
|
-
left: 0;
|
|
2788
|
-
top: 0;
|
|
2789
|
-
width: 31px;
|
|
2790
|
-
height: 2px;
|
|
2791
|
-
transform: rotate(-42deg);
|
|
2792
|
-
transform-origin: left center;
|
|
2793
|
-
border-radius: var(--df-review-radius-pill);
|
|
2794
|
-
background: currentColor;
|
|
2795
|
-
opacity: 1;
|
|
2796
|
-
}
|
|
2797
|
-
|
|
2798
|
-
.dfwr-bound-marker.is-note-callout .dfwr-bound-marker-icon::before,
|
|
2799
|
-
.dfwr-bound-marker.is-note-callout .dfwr-bound-marker-icon::after {
|
|
2800
|
-
display: none;
|
|
2801
|
-
}
|
|
2802
|
-
|
|
2803
|
-
.dfwr-bound-marker.is-note-callout .dfwr-bound-marker-number {
|
|
2804
|
-
position: absolute;
|
|
2805
|
-
left: 24px;
|
|
2806
|
-
top: -41px;
|
|
2807
|
-
display: inline-flex;
|
|
2808
|
-
align-items: center;
|
|
2809
|
-
justify-content: center;
|
|
2810
|
-
min-width: 28px;
|
|
2811
|
-
height: 20px;
|
|
2812
|
-
padding: 0 7px;
|
|
2813
|
-
border: 1px solid var(--dfwr-scope);
|
|
2814
|
-
border-radius: 4px;
|
|
2815
|
-
background: var(--dfwr-scope);
|
|
2816
|
-
box-shadow:
|
|
2817
|
-
0 0 0 3px rgba(var(--dfwr-scope-rgb), 0.18),
|
|
2818
|
-
0 8px 18px rgba(0, 0, 0, 0.28);
|
|
2819
|
-
color: #111820;
|
|
2820
|
-
text-align: center;
|
|
2821
|
-
line-height: 1;
|
|
2822
|
-
white-space: nowrap;
|
|
2823
|
-
}
|
|
2824
|
-
|
|
2825
|
-
.dfwr-bound-marker.is-note-callout.is-highlighted .dfwr-bound-marker-icon,
|
|
2826
|
-
.dfwr-bound-marker.is-note-callout.is-highlighted .dfwr-bound-marker-number {
|
|
2827
|
-
animation: dfwr-selected-blink 1000ms ease-in-out infinite;
|
|
2828
|
-
}
|
|
2829
|
-
|
|
2830
2944
|
.dfwr-area-preview-layer .dfwr-bound-marker {
|
|
2831
2945
|
border-color: #63d7c7;
|
|
2832
2946
|
background: var(--df-review-color-panel);
|
|
@@ -2897,14 +3011,14 @@ function createStyleElement() {
|
|
|
2897
3011
|
line-height: 1;
|
|
2898
3012
|
}
|
|
2899
3013
|
|
|
2900
|
-
.dfwr-
|
|
3014
|
+
.dfwr-dom-draft {
|
|
2901
3015
|
position: fixed;
|
|
2902
3016
|
inset: 0;
|
|
2903
3017
|
z-index: 4;
|
|
2904
3018
|
pointer-events: none;
|
|
2905
3019
|
}
|
|
2906
3020
|
|
|
2907
|
-
.dfwr-
|
|
3021
|
+
.dfwr-dom-pin {
|
|
2908
3022
|
appearance: none;
|
|
2909
3023
|
position: fixed;
|
|
2910
3024
|
z-index: 5;
|
|
@@ -2922,11 +3036,11 @@ function createStyleElement() {
|
|
|
2922
3036
|
pointer-events: auto;
|
|
2923
3037
|
}
|
|
2924
3038
|
|
|
2925
|
-
.dfwr-
|
|
3039
|
+
.dfwr-dom-pin:active {
|
|
2926
3040
|
cursor: grabbing;
|
|
2927
3041
|
}
|
|
2928
3042
|
|
|
2929
|
-
.dfwr-
|
|
3043
|
+
.dfwr-dom-popover {
|
|
2930
3044
|
position: fixed;
|
|
2931
3045
|
z-index: 4;
|
|
2932
3046
|
width: min(320px, calc(100vw - 24px));
|
|
@@ -2939,14 +3053,14 @@ function createStyleElement() {
|
|
|
2939
3053
|
box-shadow: var(--df-review-shadow-popover);
|
|
2940
3054
|
}
|
|
2941
3055
|
|
|
2942
|
-
.dfwr-
|
|
3056
|
+
.dfwr-dom-popover.is-composer,
|
|
2943
3057
|
.dfwr-area-draft.is-composer {
|
|
2944
3058
|
max-height: min(360px, calc(100vh - 32px));
|
|
2945
3059
|
overflow: auto;
|
|
2946
3060
|
border-color: rgba(99, 215, 199, 0.56);
|
|
2947
3061
|
}
|
|
2948
3062
|
|
|
2949
|
-
.dfwr-shell.is-docked-composer .dfwr-
|
|
3063
|
+
.dfwr-shell.is-docked-composer .dfwr-dom-popover.is-docked-composer,
|
|
2950
3064
|
.dfwr-shell.is-docked-composer .dfwr-area-draft.is-docked-composer {
|
|
2951
3065
|
position: relative;
|
|
2952
3066
|
left: auto;
|
|
@@ -2960,7 +3074,7 @@ function createStyleElement() {
|
|
|
2960
3074
|
min-height: 184px;
|
|
2961
3075
|
}
|
|
2962
3076
|
|
|
2963
|
-
.dfwr-
|
|
3077
|
+
.dfwr-dom-popover.is-dragging,
|
|
2964
3078
|
.dfwr-area-draft.is-dragging {
|
|
2965
3079
|
user-select: none;
|
|
2966
3080
|
}
|
|
@@ -3004,7 +3118,7 @@ function createStyleElement() {
|
|
|
3004
3118
|
box-shadow: var(--df-review-shadow-popover);
|
|
3005
3119
|
}
|
|
3006
3120
|
|
|
3007
|
-
.dfwr-
|
|
3121
|
+
.dfwr-dom-popover .dfwr-actions {
|
|
3008
3122
|
padding: 0;
|
|
3009
3123
|
}
|
|
3010
3124
|
|
|
@@ -3052,14 +3166,6 @@ function createStyleElement() {
|
|
|
3052
3166
|
height: 18px;
|
|
3053
3167
|
}
|
|
3054
3168
|
|
|
3055
|
-
.dfwr-note-actions {
|
|
3056
|
-
justify-content: flex-end;
|
|
3057
|
-
}
|
|
3058
|
-
|
|
3059
|
-
.dfwr-note-actions .dfwr-button:first-child {
|
|
3060
|
-
margin-right: auto;
|
|
3061
|
-
}
|
|
3062
|
-
|
|
3063
3169
|
.dfwr-area-draft .dfwr-actions {
|
|
3064
3170
|
padding: 0;
|
|
3065
3171
|
}
|
|
@@ -3077,6 +3183,82 @@ function createStyleElement() {
|
|
|
3077
3183
|
overflow-wrap: anywhere;
|
|
3078
3184
|
}
|
|
3079
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
|
+
|
|
3080
3262
|
.dfwr-input,
|
|
3081
3263
|
.dfwr-select,
|
|
3082
3264
|
.dfwr-textarea {
|
|
@@ -3379,18 +3561,6 @@ function createStyleElement() {
|
|
|
3379
3561
|
}
|
|
3380
3562
|
}
|
|
3381
3563
|
|
|
3382
|
-
@keyframes dfwr-note-dot-pulse {
|
|
3383
|
-
0% {
|
|
3384
|
-
transform: translate(-50%, -50%) scale(0.88);
|
|
3385
|
-
}
|
|
3386
|
-
45% {
|
|
3387
|
-
transform: translate(-50%, -50%) scale(1.3);
|
|
3388
|
-
}
|
|
3389
|
-
100% {
|
|
3390
|
-
transform: translate(-50%, -50%) scale(1);
|
|
3391
|
-
}
|
|
3392
|
-
}
|
|
3393
|
-
|
|
3394
3564
|
@keyframes dfwr-selected-blink {
|
|
3395
3565
|
0%,
|
|
3396
3566
|
100% {
|
|
@@ -3439,1071 +3609,848 @@ function createStyleElement() {
|
|
|
3439
3609
|
return style;
|
|
3440
3610
|
}
|
|
3441
3611
|
|
|
3442
|
-
// src/core/
|
|
3443
|
-
function
|
|
3444
|
-
|
|
3445
|
-
|
|
3446
|
-
|
|
3447
|
-
|
|
3448
|
-
|
|
3449
|
-
|
|
3450
|
-
|
|
3451
|
-
|
|
3452
|
-
}
|
|
3453
|
-
|
|
3454
|
-
|
|
3455
|
-
|
|
3456
|
-
|
|
3457
|
-
|
|
3458
|
-
|
|
3459
|
-
|
|
3460
|
-
|
|
3461
|
-
parts.push(`point ${formatPoint(marker.viewport)}`);
|
|
3462
|
-
}
|
|
3463
|
-
if (selection) {
|
|
3464
|
-
parts.push(`rect ${formatSelection(selection.viewport)}`);
|
|
3465
|
-
}
|
|
3466
|
-
if (item.anchor) {
|
|
3467
|
-
parts.push(formatAnchorMeta(item.anchor));
|
|
3468
|
-
}
|
|
3469
|
-
return parts.join(" / ");
|
|
3470
|
-
}
|
|
3471
|
-
function formatDate(value) {
|
|
3472
|
-
const date = new Date(value);
|
|
3473
|
-
if (Number.isNaN(date.getTime())) return value;
|
|
3474
|
-
return date.toLocaleString(void 0, {
|
|
3475
|
-
month: "2-digit",
|
|
3476
|
-
day: "2-digit",
|
|
3477
|
-
hour: "2-digit",
|
|
3478
|
-
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();
|
|
3479
3631
|
});
|
|
3480
3632
|
}
|
|
3481
|
-
function
|
|
3482
|
-
|
|
3483
|
-
|
|
3484
|
-
|
|
3485
|
-
|
|
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
|
+
};
|
|
3486
3693
|
}
|
|
3487
|
-
function
|
|
3488
|
-
return
|
|
3489
|
-
|
|
3490
|
-
|
|
3491
|
-
|
|
3492
|
-
|
|
3493
|
-
|
|
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
|
+
};
|
|
3494
3744
|
}
|
|
3495
|
-
function
|
|
3496
|
-
const
|
|
3497
|
-
|
|
3498
|
-
|
|
3499
|
-
|
|
3500
|
-
|
|
3501
|
-
|
|
3502
|
-
|
|
3503
|
-
|
|
3504
|
-
|
|
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
|
+
};
|
|
3505
3760
|
}
|
|
3506
|
-
|
|
3507
|
-
|
|
3508
|
-
|
|
3509
|
-
|
|
3510
|
-
|
|
3511
|
-
this.config = config;
|
|
3512
|
-
}
|
|
3513
|
-
clearDraftPreview() {
|
|
3514
|
-
this.restoreDraftPreview();
|
|
3515
|
-
this.clearShellComposer();
|
|
3516
|
-
}
|
|
3517
|
-
render(shadow, hiddenItemsStyle) {
|
|
3518
|
-
const state = this.state;
|
|
3519
|
-
this.syncDraftPreview(
|
|
3520
|
-
state.isOpen && state.mode === "element" ? state.noteDraft : void 0
|
|
3521
|
-
);
|
|
3522
|
-
shadow.replaceChildren();
|
|
3523
|
-
shadow.append(createStyleElement());
|
|
3524
|
-
shadow.append(hiddenItemsStyle);
|
|
3525
|
-
const hasDismissableDraft = Boolean(state.noteDraft || state.areaDraft);
|
|
3526
|
-
const shouldDockComposer = this.config.options.ui?.panel === false && hasDismissableDraft && Boolean(this.getShellComposerHost());
|
|
3527
|
-
let dockedComposer;
|
|
3528
|
-
const shell = document.createElement("div");
|
|
3529
|
-
shell.className = [
|
|
3530
|
-
"dfwr-shell",
|
|
3531
|
-
state.isOpen ? "is-open" : "",
|
|
3532
|
-
hasDismissableDraft && !shouldDockComposer ? "has-dismissible-draft" : ""
|
|
3533
|
-
].filter(Boolean).join(" ");
|
|
3534
|
-
shell.setAttribute("aria-hidden", state.isOpen ? "false" : "true");
|
|
3535
|
-
if (this.config.options.ui?.panel !== false) {
|
|
3536
|
-
const panel = document.createElement("div");
|
|
3537
|
-
panel.className = "dfwr-panel";
|
|
3538
|
-
panel.setAttribute("role", "dialog");
|
|
3539
|
-
panel.setAttribute("aria-label", "Web review kit");
|
|
3540
|
-
panel.append(
|
|
3541
|
-
this.createHeader(),
|
|
3542
|
-
this.createToolbar(),
|
|
3543
|
-
this.createBody(),
|
|
3544
|
-
this.createList()
|
|
3545
|
-
);
|
|
3546
|
-
shell.append(panel);
|
|
3547
|
-
}
|
|
3548
|
-
shell.append(this.createMarkerLayer());
|
|
3549
|
-
if (state.isOpen && hasDismissableDraft && !shouldDockComposer) {
|
|
3550
|
-
shell.append(this.createDraftCancelLayer());
|
|
3551
|
-
}
|
|
3552
|
-
if (state.isOpen && (state.mode === "note" || state.mode === "element")) {
|
|
3553
|
-
if (state.noteDraft) {
|
|
3554
|
-
const noteDraft = this.createNotePopover(state.noteDraft, {
|
|
3555
|
-
dockComposer: shouldDockComposer
|
|
3556
|
-
});
|
|
3557
|
-
shell.append(noteDraft.layer);
|
|
3558
|
-
dockedComposer = noteDraft.composer;
|
|
3559
|
-
} else {
|
|
3560
|
-
shell.append(
|
|
3561
|
-
state.mode === "element" ? this.createElementLayer() : this.createNoteLayer()
|
|
3562
|
-
);
|
|
3563
|
-
}
|
|
3564
|
-
}
|
|
3565
|
-
if (state.isOpen && state.mode === "area" && !state.areaDraft && !state.isSelectingArea) {
|
|
3566
|
-
shell.append(this.createAreaLayer());
|
|
3567
|
-
}
|
|
3568
|
-
if (state.isOpen && state.mode === "area" && state.areaDraft && this.config.options.ui?.panel === false) {
|
|
3569
|
-
if (state.areaDraft.selection) {
|
|
3570
|
-
shell.append(this.createAreaDraftOverlay(state.areaDraft));
|
|
3571
|
-
}
|
|
3572
|
-
const areaComposer = this.createAreaDraftPopover(state.areaDraft, {
|
|
3573
|
-
dockComposer: shouldDockComposer
|
|
3574
|
-
});
|
|
3575
|
-
if (shouldDockComposer) {
|
|
3576
|
-
dockedComposer = areaComposer;
|
|
3577
|
-
} else {
|
|
3578
|
-
shell.append(areaComposer);
|
|
3579
|
-
}
|
|
3580
|
-
}
|
|
3581
|
-
shadow.append(shell);
|
|
3582
|
-
this.renderShellComposer(dockedComposer);
|
|
3583
|
-
}
|
|
3584
|
-
get state() {
|
|
3585
|
-
return this.config.getState();
|
|
3586
|
-
}
|
|
3587
|
-
getShellComposerHost() {
|
|
3588
|
-
const environment = this.config.getEnvironment();
|
|
3589
|
-
if (this.config.options.ui?.panel !== false) return void 0;
|
|
3590
|
-
return environment?.composerHost ?? void 0;
|
|
3591
|
-
}
|
|
3592
|
-
renderShellComposer(composer) {
|
|
3593
|
-
const host = composer ? this.getShellComposerHost() : void 0;
|
|
3594
|
-
if (!host || !composer) {
|
|
3595
|
-
this.clearShellComposer();
|
|
3596
|
-
return;
|
|
3597
|
-
}
|
|
3598
|
-
if (this.shellComposerHost && this.shellComposerHost !== host) {
|
|
3599
|
-
this.clearShellComposer();
|
|
3600
|
-
}
|
|
3601
|
-
this.shellComposerHost = host;
|
|
3602
|
-
host.dataset.hasDraftComposer = "true";
|
|
3603
|
-
if (host.parentElement) {
|
|
3604
|
-
host.parentElement.dataset.hasDraftComposer = "true";
|
|
3605
|
-
}
|
|
3606
|
-
const shell = document.createElement("div");
|
|
3607
|
-
shell.className = "dfwr-shell is-open is-shell-draft is-docked-composer";
|
|
3608
|
-
shell.append(composer);
|
|
3609
|
-
host.replaceChildren(createStyleElement(), shell);
|
|
3610
|
-
}
|
|
3611
|
-
clearShellComposer() {
|
|
3612
|
-
const host = this.shellComposerHost;
|
|
3613
|
-
host?.replaceChildren();
|
|
3614
|
-
if (host) {
|
|
3615
|
-
delete host.dataset.hasDraftComposer;
|
|
3616
|
-
delete host.parentElement?.dataset.hasDraftComposer;
|
|
3617
|
-
}
|
|
3618
|
-
this.shellComposerHost = void 0;
|
|
3619
|
-
}
|
|
3620
|
-
createDraftCancelLayer() {
|
|
3621
|
-
const layer = document.createElement("div");
|
|
3622
|
-
layer.className = "dfwr-draft-cancel-layer";
|
|
3623
|
-
layer.setAttribute("aria-hidden", "true");
|
|
3624
|
-
layer.addEventListener("pointerdown", (event) => {
|
|
3625
|
-
if (event.button !== 0) return;
|
|
3626
|
-
this.cancelDraft(event);
|
|
3627
|
-
});
|
|
3628
|
-
return layer;
|
|
3629
|
-
}
|
|
3630
|
-
cancelDraft(event) {
|
|
3631
|
-
event?.preventDefault();
|
|
3632
|
-
event?.stopPropagation();
|
|
3633
|
-
event?.stopImmediatePropagation();
|
|
3634
|
-
this.config.actions.setModeState("idle");
|
|
3635
|
-
this.config.actions.clearDrafts();
|
|
3636
|
-
this.config.actions.setSelectingArea(false);
|
|
3637
|
-
this.config.actions.render();
|
|
3638
|
-
}
|
|
3639
|
-
// Draft adjustment geometry lives in draft.metrics.ts; these thin wrappers
|
|
3640
|
-
// supply the configured viewport presets so call sites stay unchanged.
|
|
3641
|
-
get viewportPresets() {
|
|
3642
|
-
return this.config.options.viewports?.presets;
|
|
3643
|
-
}
|
|
3644
|
-
getDraftAdjustmentMetrics(draft) {
|
|
3645
|
-
return getDraftAdjustmentMetrics(draft, this.viewportPresets);
|
|
3646
|
-
}
|
|
3647
|
-
hasDraftAdjustment(draft) {
|
|
3648
|
-
return hasDraftAdjustment(draft, this.viewportPresets);
|
|
3649
|
-
}
|
|
3650
|
-
getAdjustedDraftPoint(point, draft) {
|
|
3651
|
-
return getAdjustedDraftPoint(point, draft, this.viewportPresets);
|
|
3652
|
-
}
|
|
3653
|
-
getAdjustedDraftSelection(selection, draft) {
|
|
3654
|
-
return getAdjustedDraftSelection(
|
|
3655
|
-
selection,
|
|
3656
|
-
draft,
|
|
3657
|
-
this.viewportPresets
|
|
3658
|
-
);
|
|
3659
|
-
}
|
|
3660
|
-
getDraftViewportScale(viewport) {
|
|
3661
|
-
return getDraftViewportScale(viewport, this.viewportPresets);
|
|
3662
|
-
}
|
|
3663
|
-
getDraftComposerWidth(environment) {
|
|
3664
|
-
const bounds = environment.overlayRect;
|
|
3665
|
-
const margin = 12;
|
|
3666
|
-
return Math.min(360, Math.max(240, bounds.width - margin * 2));
|
|
3667
|
-
}
|
|
3668
|
-
getClampedComposerPosition(position, environment, size, bounds = environment.overlayRect) {
|
|
3669
|
-
const margin = 12;
|
|
3670
|
-
const width = size?.width ?? this.getDraftComposerWidth(environment);
|
|
3671
|
-
const height = size?.height ?? 236;
|
|
3672
|
-
return {
|
|
3673
|
-
x: clamp(
|
|
3674
|
-
position.x,
|
|
3675
|
-
bounds.left + margin,
|
|
3676
|
-
bounds.left + bounds.width - width - margin
|
|
3677
|
-
),
|
|
3678
|
-
y: clamp(
|
|
3679
|
-
position.y,
|
|
3680
|
-
bounds.top + margin,
|
|
3681
|
-
bounds.top + bounds.height - height - margin
|
|
3682
|
-
)
|
|
3683
|
-
};
|
|
3684
|
-
}
|
|
3685
|
-
getHostComposerBounds() {
|
|
3686
|
-
const root = document.documentElement;
|
|
3687
|
-
return {
|
|
3688
|
-
left: 0,
|
|
3689
|
-
top: 0,
|
|
3690
|
-
width: root.clientWidth || window.innerWidth,
|
|
3691
|
-
height: root.clientHeight || window.innerHeight
|
|
3692
|
-
};
|
|
3693
|
-
}
|
|
3694
|
-
getInitialDraftComposerPosition(selection, environment, size) {
|
|
3695
|
-
const bounds = this.getHostComposerBounds();
|
|
3696
|
-
const margin = 12;
|
|
3697
|
-
const gap = 20;
|
|
3698
|
-
if (!selection) {
|
|
3699
|
-
return this.getClampedComposerPosition(
|
|
3700
|
-
{
|
|
3701
|
-
x: environment.overlayRect.left + margin,
|
|
3702
|
-
y: environment.overlayRect.top + margin
|
|
3703
|
-
},
|
|
3704
|
-
environment,
|
|
3705
|
-
size,
|
|
3706
|
-
bounds
|
|
3707
|
-
);
|
|
3708
|
-
}
|
|
3709
|
-
const preferredX = selection.left + selection.width + gap;
|
|
3710
|
-
const maxX = bounds.left + bounds.width - size.width - margin;
|
|
3711
|
-
const x = preferredX <= maxX ? preferredX : selection.left - size.width - gap;
|
|
3712
|
-
return this.getClampedComposerPosition(
|
|
3761
|
+
function getInitialDraftComposerPosition(selection, environment, size) {
|
|
3762
|
+
const bounds = getHostComposerBounds();
|
|
3763
|
+
const gap = 20;
|
|
3764
|
+
if (!selection) {
|
|
3765
|
+
return getClampedComposerPosition(
|
|
3713
3766
|
{
|
|
3714
|
-
x,
|
|
3715
|
-
y:
|
|
3767
|
+
x: environment.overlayRect.left + COMPOSER_MARGIN,
|
|
3768
|
+
y: environment.overlayRect.top + COMPOSER_MARGIN
|
|
3716
3769
|
},
|
|
3717
3770
|
environment,
|
|
3718
3771
|
size,
|
|
3719
3772
|
bounds
|
|
3720
3773
|
);
|
|
3721
3774
|
}
|
|
3722
|
-
|
|
3723
|
-
|
|
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
|
+
},
|
|
3724
3783
|
environment,
|
|
3725
|
-
|
|
3726
|
-
|
|
3727
|
-
|
|
3728
|
-
|
|
3729
|
-
|
|
3730
|
-
|
|
3731
|
-
|
|
3732
|
-
|
|
3733
|
-
|
|
3734
|
-
|
|
3735
|
-
|
|
3736
|
-
|
|
3737
|
-
|
|
3738
|
-
|
|
3739
|
-
|
|
3740
|
-
height: estimatedHeight
|
|
3741
|
-
|
|
3742
|
-
|
|
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 };
|
|
3743
3803
|
}
|
|
3744
|
-
|
|
3745
|
-
|
|
3746
|
-
|
|
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) {
|
|
3747
3915
|
return {
|
|
3748
|
-
|
|
3749
|
-
|
|
3750
|
-
|
|
3751
|
-
height: selection.height * ratio
|
|
3916
|
+
viewport: draft.viewport,
|
|
3917
|
+
marker: draft.marker,
|
|
3918
|
+
selection: draft.selection
|
|
3752
3919
|
};
|
|
3753
3920
|
}
|
|
3754
|
-
|
|
3755
|
-
|
|
3756
|
-
|
|
3757
|
-
|
|
3758
|
-
|
|
3759
|
-
|
|
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;
|
|
3760
4159
|
}
|
|
3761
|
-
|
|
3762
|
-
|
|
4160
|
+
if (beforeSave?.length || className) {
|
|
4161
|
+
actions.append(cancel, ...beforeSave ?? [], save);
|
|
4162
|
+
return actions;
|
|
3763
4163
|
}
|
|
3764
|
-
|
|
3765
|
-
|
|
3766
|
-
|
|
3767
|
-
|
|
3768
|
-
|
|
3769
|
-
|
|
3770
|
-
|
|
3771
|
-
|
|
3772
|
-
|
|
3773
|
-
|
|
3774
|
-
|
|
3775
|
-
|
|
3776
|
-
|
|
3777
|
-
|
|
3778
|
-
|
|
3779
|
-
|
|
3780
|
-
|
|
3781
|
-
|
|
3782
|
-
|
|
3783
|
-
|
|
3784
|
-
|
|
3785
|
-
|
|
3786
|
-
|
|
3787
|
-
|
|
3788
|
-
|
|
3789
|
-
|
|
3790
|
-
|
|
3791
|
-
if (!this.hasDraftAdjustment(draft)) return comment;
|
|
3792
|
-
const trimmedComment = comment.trim();
|
|
3793
|
-
const metrics = this.getDraftAdjustmentMetrics(draft);
|
|
3794
|
-
const adjustment = [
|
|
3795
|
-
`${this.getAdjustmentLabel()}: x ${this.formatSignedPx(
|
|
3796
|
-
metrics.x
|
|
3797
|
-
)}, y ${this.formatSignedPx(metrics.y)}, scale ${this.formatSignedPx(
|
|
3798
|
-
metrics.scale
|
|
3799
|
-
)}`,
|
|
3800
|
-
`(${metrics.presetLabel} viewport, ${Math.round(
|
|
3801
|
-
metrics.viewportWidth
|
|
3802
|
-
)}/design ${Math.round(metrics.designWidth)})`
|
|
3803
|
-
].join(" ");
|
|
3804
|
-
return trimmedComment ? `${trimmedComment}
|
|
3805
|
-
${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));
|
|
3806
4191
|
}
|
|
3807
|
-
|
|
3808
|
-
|
|
3809
|
-
|
|
3810
|
-
|
|
3811
|
-
|
|
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)}`);
|
|
3812
4200
|
}
|
|
3813
|
-
|
|
3814
|
-
|
|
4201
|
+
if (marker) {
|
|
4202
|
+
parts.push(`point ${formatPoint(marker.viewport)}`);
|
|
3815
4203
|
}
|
|
3816
|
-
|
|
3817
|
-
|
|
3818
|
-
input.className = "dfwr-input";
|
|
3819
|
-
input.placeholder = "Title";
|
|
3820
|
-
input.type = "text";
|
|
3821
|
-
input.value = value ?? "";
|
|
3822
|
-
input.addEventListener("input", () => onInput(input.value));
|
|
3823
|
-
return input;
|
|
4204
|
+
if (selection) {
|
|
4205
|
+
parts.push(`rect ${formatSelection(selection.viewport)}`);
|
|
3824
4206
|
}
|
|
3825
|
-
|
|
3826
|
-
|
|
3827
|
-
}
|
|
3828
|
-
createDraftAssigneeSelect(value, fallbackLabel, onChange) {
|
|
3829
|
-
const assigneeOptions = this.config.options.assigneeOptions ?? [];
|
|
3830
|
-
if (assigneeOptions.length === 0) return void 0;
|
|
3831
|
-
const assigneeTitle = this.config.options.assigneeTitle?.trim() || "Assignee";
|
|
3832
|
-
const select = document.createElement("select");
|
|
3833
|
-
select.className = "dfwr-select";
|
|
3834
|
-
const emptyOption = document.createElement("option");
|
|
3835
|
-
emptyOption.value = "";
|
|
3836
|
-
emptyOption.textContent = assigneeTitle;
|
|
3837
|
-
select.append(emptyOption);
|
|
3838
|
-
const hasUnknownAssignee = Boolean(value) && !assigneeOptions.some((option) => option.value === value);
|
|
3839
|
-
if (hasUnknownAssignee && value) {
|
|
3840
|
-
const option = document.createElement("option");
|
|
3841
|
-
option.value = value;
|
|
3842
|
-
option.textContent = fallbackLabel ?? value;
|
|
3843
|
-
select.append(option);
|
|
3844
|
-
}
|
|
3845
|
-
assigneeOptions.forEach((assigneeOption) => {
|
|
3846
|
-
const option = document.createElement("option");
|
|
3847
|
-
option.value = assigneeOption.value;
|
|
3848
|
-
option.textContent = assigneeOption.label;
|
|
3849
|
-
select.append(option);
|
|
3850
|
-
});
|
|
3851
|
-
select.value = value ?? "";
|
|
3852
|
-
select.addEventListener("change", () => {
|
|
3853
|
-
onChange(select.value || null, this.getAssigneeName(select.value));
|
|
3854
|
-
});
|
|
3855
|
-
return select;
|
|
4207
|
+
if (item.anchor) {
|
|
4208
|
+
parts.push(formatAnchorMeta(item.anchor));
|
|
3856
4209
|
}
|
|
3857
|
-
|
|
3858
|
-
|
|
3859
|
-
|
|
3860
|
-
|
|
3861
|
-
|
|
3862
|
-
|
|
3863
|
-
|
|
3864
|
-
|
|
3865
|
-
|
|
3866
|
-
|
|
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)}%`);
|
|
3867
4240
|
}
|
|
3868
|
-
|
|
3869
|
-
|
|
3870
|
-
|
|
3871
|
-
}
|
|
3872
|
-
if (!draft.anchor) return void 0;
|
|
3873
|
-
const preferredSelection = draft.selection ? toViewportSelection(draft.selection.viewport) : void 0;
|
|
3874
|
-
const element = resolveAnchorElement(
|
|
3875
|
-
draft.anchor,
|
|
3876
|
-
environment,
|
|
3877
|
-
preferredSelection
|
|
3878
|
-
)?.element;
|
|
3879
|
-
if (!element) return void 0;
|
|
3880
|
-
if ("style" in element) return element;
|
|
3881
|
-
return void 0;
|
|
4241
|
+
const candidates = getAnchorCandidates(anchor);
|
|
4242
|
+
if (candidates.length > 1) {
|
|
4243
|
+
parts.push(`${candidates.length} candidates`);
|
|
3882
4244
|
}
|
|
3883
|
-
|
|
3884
|
-
|
|
3885
|
-
|
|
3886
|
-
|
|
3887
|
-
|
|
3888
|
-
|
|
3889
|
-
|
|
3890
|
-
|
|
3891
|
-
|
|
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)) {
|
|
3892
4328
|
return;
|
|
3893
4329
|
}
|
|
3894
|
-
|
|
3895
|
-
|
|
3896
|
-
|
|
3897
|
-
|
|
3898
|
-
|
|
3899
|
-
|
|
3900
|
-
|
|
3901
|
-
|
|
3902
|
-
|
|
3903
|
-
|
|
3904
|
-
|
|
3905
|
-
|
|
3906
|
-
|
|
3907
|
-
|
|
3908
|
-
};
|
|
3909
|
-
element.style.visibility = "hidden";
|
|
3910
|
-
}
|
|
3911
|
-
const metrics = this.getDraftAdjustmentMetrics(draft);
|
|
3912
|
-
const translate = `translate(${this.toCssNumber(metrics.cssX)}px, ${this.toCssNumber(
|
|
3913
|
-
metrics.cssY
|
|
3914
|
-
)}px)`;
|
|
3915
|
-
const scale = metrics.scaleFactor === 1 ? "" : `scale(${this.toCssNumber(metrics.scaleFactor)})`;
|
|
3916
|
-
this.draftPreview.clone.style.transform = [translate, scale].filter(Boolean).join(" ");
|
|
3917
|
-
}
|
|
3918
|
-
restoreDraftPreview() {
|
|
3919
|
-
if (!this.draftPreview) return;
|
|
3920
|
-
const { element, clone, visibility } = this.draftPreview;
|
|
3921
|
-
clone.remove();
|
|
3922
|
-
element.style.visibility = visibility;
|
|
3923
|
-
this.draftPreview = void 0;
|
|
3924
|
-
}
|
|
3925
|
-
positionDraftPreviewClone(clone, element, computedStyle) {
|
|
3926
|
-
const rect = element.getBoundingClientRect();
|
|
3927
|
-
clone.setAttribute("data-dfwr-adjust-preview", "true");
|
|
3928
|
-
clone.setAttribute("aria-hidden", "true");
|
|
3929
|
-
clone.style.position = "fixed";
|
|
3930
|
-
clone.style.left = `${this.toCssNumber(rect.left)}px`;
|
|
3931
|
-
clone.style.top = `${this.toCssNumber(rect.top)}px`;
|
|
3932
|
-
clone.style.right = "auto";
|
|
3933
|
-
clone.style.bottom = "auto";
|
|
3934
|
-
clone.style.width = `${this.toCssNumber(rect.width)}px`;
|
|
3935
|
-
clone.style.height = `${this.toCssNumber(rect.height)}px`;
|
|
3936
|
-
clone.style.maxWidth = "none";
|
|
3937
|
-
clone.style.maxHeight = "none";
|
|
3938
|
-
clone.style.margin = "0";
|
|
3939
|
-
clone.style.boxSizing = "border-box";
|
|
3940
|
-
clone.style.display = this.getDraftPreviewDisplay(computedStyle.display);
|
|
3941
|
-
clone.style.zIndex = "2147483646";
|
|
3942
|
-
clone.style.pointerEvents = "none";
|
|
3943
|
-
clone.style.transition = "none";
|
|
3944
|
-
clone.style.willChange = "transform";
|
|
3945
|
-
clone.style.transformOrigin = "top left";
|
|
3946
|
-
clone.style.transform = "none";
|
|
3947
|
-
}
|
|
3948
|
-
getDraftPreviewDisplay(display) {
|
|
3949
|
-
if (display === "inline" || display === "contents") return "inline-block";
|
|
3950
|
-
return display || "block";
|
|
3951
|
-
}
|
|
3952
|
-
copyComputedStyle(element, clone, environment) {
|
|
3953
|
-
const computedStyle = environment.window.getComputedStyle(element);
|
|
3954
|
-
for (let index = 0; index < computedStyle.length; index += 1) {
|
|
3955
|
-
const property = computedStyle.item(index);
|
|
3956
|
-
clone.style.setProperty(
|
|
3957
|
-
property,
|
|
3958
|
-
computedStyle.getPropertyValue(property),
|
|
3959
|
-
computedStyle.getPropertyPriority(property)
|
|
3960
|
-
);
|
|
3961
|
-
}
|
|
3962
|
-
}
|
|
3963
|
-
removeDuplicateIds(element) {
|
|
3964
|
-
element.removeAttribute("id");
|
|
3965
|
-
element.querySelectorAll("[id]").forEach((child) => {
|
|
3966
|
-
child.removeAttribute("id");
|
|
3967
|
-
});
|
|
3968
|
-
}
|
|
3969
|
-
toCssNumber(value) {
|
|
3970
|
-
return Math.round(value * 1e3) / 1e3;
|
|
3971
|
-
}
|
|
3972
|
-
createHeader() {
|
|
3973
|
-
const header = document.createElement("div");
|
|
3974
|
-
header.className = "dfwr-header";
|
|
3975
|
-
const title = document.createElement("div");
|
|
3976
|
-
title.className = "dfwr-title";
|
|
3977
|
-
title.textContent = "Review Kit";
|
|
3978
|
-
const meta = document.createElement("div");
|
|
3979
|
-
meta.className = "dfwr-meta";
|
|
3980
|
-
meta.textContent = getRouteKey(this.config.getEnvironment());
|
|
3981
|
-
const titleGroup = document.createElement("div");
|
|
3982
|
-
titleGroup.append(title, meta);
|
|
3983
|
-
const close = document.createElement("button");
|
|
3984
|
-
close.className = "dfwr-icon-button";
|
|
3985
|
-
close.type = "button";
|
|
3986
|
-
close.textContent = "x";
|
|
3987
|
-
close.setAttribute("aria-label", "Close");
|
|
3988
|
-
close.addEventListener("click", () => this.config.actions.close());
|
|
3989
|
-
header.append(titleGroup, close);
|
|
3990
|
-
return header;
|
|
3991
|
-
}
|
|
3992
|
-
createToolbar() {
|
|
3993
|
-
const toolbar = document.createElement("div");
|
|
3994
|
-
toolbar.className = "dfwr-toolbar";
|
|
3995
|
-
toolbar.append(
|
|
3996
|
-
this.createToolbarButton("Note", this.state.mode === "note", () => {
|
|
3997
|
-
const mode = this.state.mode;
|
|
3998
|
-
this.config.actions.setModeState(mode === "note" ? "idle" : "note");
|
|
3999
|
-
this.config.actions.clearDrafts();
|
|
4000
|
-
this.config.actions.render();
|
|
4001
|
-
}),
|
|
4002
|
-
this.createToolbarButton("Element", this.state.mode === "element", () => {
|
|
4003
|
-
const mode = this.state.mode;
|
|
4004
|
-
this.config.actions.setModeState(
|
|
4005
|
-
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
|
+
)
|
|
4006
4344
|
);
|
|
4007
|
-
|
|
4008
|
-
this.config.actions.render();
|
|
4009
|
-
}),
|
|
4010
|
-
this.createToolbarButton(
|
|
4011
|
-
this.state.isSelectingArea ? "Selecting" : "Area",
|
|
4012
|
-
this.state.mode === "area",
|
|
4013
|
-
() => {
|
|
4014
|
-
const mode = this.state.mode;
|
|
4015
|
-
this.config.actions.setModeState(mode === "area" ? "idle" : "area");
|
|
4016
|
-
this.config.actions.clearDrafts();
|
|
4017
|
-
this.config.actions.render();
|
|
4018
|
-
}
|
|
4019
|
-
),
|
|
4020
|
-
this.createToolbarButton("Refresh", false, () => {
|
|
4021
|
-
void this.config.actions.reload();
|
|
4022
|
-
})
|
|
4023
|
-
);
|
|
4024
|
-
return toolbar;
|
|
4025
|
-
}
|
|
4026
|
-
createToolbarButton(label, active, onClick) {
|
|
4027
|
-
const button = document.createElement("button");
|
|
4028
|
-
button.className = `dfwr-button${active ? " is-active" : ""}`;
|
|
4029
|
-
button.type = "button";
|
|
4030
|
-
button.textContent = label;
|
|
4031
|
-
button.addEventListener("click", onClick);
|
|
4032
|
-
return button;
|
|
4033
|
-
}
|
|
4034
|
-
createBody() {
|
|
4035
|
-
const body = document.createElement("div");
|
|
4036
|
-
body.className = "dfwr-body";
|
|
4037
|
-
const state = this.state;
|
|
4038
|
-
if (state.mode === "idle") {
|
|
4039
|
-
const empty = document.createElement("p");
|
|
4040
|
-
empty.className = "dfwr-empty";
|
|
4041
|
-
empty.textContent = "Add a note or mark an area.";
|
|
4042
|
-
body.append(empty);
|
|
4043
|
-
return body;
|
|
4044
|
-
}
|
|
4045
|
-
if (state.mode === "note" || state.mode === "element") {
|
|
4046
|
-
body.append(this.createNoteBody());
|
|
4047
|
-
return body;
|
|
4048
|
-
}
|
|
4049
|
-
body.append(this.createAreaForm());
|
|
4050
|
-
return body;
|
|
4051
|
-
}
|
|
4052
|
-
createNoteBody() {
|
|
4053
|
-
const empty = document.createElement("p");
|
|
4054
|
-
empty.className = "dfwr-empty";
|
|
4055
|
-
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.";
|
|
4056
|
-
return empty;
|
|
4057
|
-
}
|
|
4058
|
-
// Builds the note draft layer: the on-page marker/highlight plus its composer
|
|
4059
|
-
// popover. When dockComposer is set the composer renders into the side panel
|
|
4060
|
-
// instead of floating next to the marker (used for the docked review mode).
|
|
4061
|
-
createNotePopover(draft, options = {}) {
|
|
4062
|
-
const environment = this.config.getEnvironment();
|
|
4063
|
-
const group = document.createElement("div");
|
|
4064
|
-
group.className = "dfwr-note-draft";
|
|
4065
|
-
if (!environment) return { layer: group, composer: void 0 };
|
|
4066
|
-
const isElementDraft = this.state.mode === "element" && Boolean(draft.selection);
|
|
4067
|
-
const hostPoint = toHostPoint(
|
|
4068
|
-
isElementDraft ? this.getAdjustedDraftPoint(draft.marker.viewport, draft) : draft.marker.viewport,
|
|
4069
|
-
environment
|
|
4070
|
-
);
|
|
4071
|
-
let selectionHighlight;
|
|
4072
|
-
if (draft.selection) {
|
|
4073
|
-
const selection = toViewportSelection(draft.selection.viewport);
|
|
4074
|
-
selectionHighlight = this.createSelectionHighlight(
|
|
4075
|
-
isElementDraft ? this.getAdjustedDraftSelection(selection, draft) : selection,
|
|
4076
|
-
environment,
|
|
4077
|
-
true
|
|
4078
|
-
);
|
|
4079
|
-
group.append(selectionHighlight);
|
|
4080
|
-
}
|
|
4081
|
-
const pin = document.createElement("button");
|
|
4082
|
-
pin.className = "dfwr-note-pin";
|
|
4083
|
-
pin.type = "button";
|
|
4084
|
-
pin.setAttribute("aria-label", "Move note point");
|
|
4085
|
-
pin.style.left = `${hostPoint.x}px`;
|
|
4086
|
-
pin.style.top = `${hostPoint.y}px`;
|
|
4087
|
-
const popover = document.createElement("div");
|
|
4088
|
-
const position = getPopoverPosition(hostPoint, environment);
|
|
4089
|
-
popover.className = [
|
|
4090
|
-
"dfwr-note-popover",
|
|
4091
|
-
isElementDraft ? "is-composer" : "",
|
|
4092
|
-
options.dockComposer ? "is-docked-composer" : ""
|
|
4093
|
-
].filter(Boolean).join(" ");
|
|
4094
|
-
if (options.dockComposer) {
|
|
4095
|
-
popover.style.width = "100%";
|
|
4096
|
-
} else if (isElementDraft) {
|
|
4097
|
-
const selection = draft.selection ? toHostSelection(
|
|
4098
|
-
this.getAdjustedDraftSelection(
|
|
4099
|
-
toViewportSelection(draft.selection.viewport),
|
|
4100
|
-
draft
|
|
4101
|
-
),
|
|
4102
|
-
environment
|
|
4103
|
-
) : void 0;
|
|
4104
|
-
const composer = this.getDraftComposerPosition({
|
|
4105
|
-
selection,
|
|
4106
|
-
environment,
|
|
4107
|
-
composerPosition: draft.composerPosition,
|
|
4108
|
-
estimatedHeight: 252
|
|
4109
|
-
});
|
|
4110
|
-
popover.style.left = `${composer.left}px`;
|
|
4111
|
-
popover.style.top = `${composer.top}px`;
|
|
4112
|
-
popover.style.width = `${composer.width}px`;
|
|
4113
|
-
} else {
|
|
4114
|
-
popover.style.left = `${position.left}px`;
|
|
4115
|
-
popover.style.top = `${position.top}px`;
|
|
4116
|
-
}
|
|
4117
|
-
const form = document.createElement("form");
|
|
4118
|
-
form.className = "dfwr-form";
|
|
4119
|
-
const meta = isElementDraft ? void 0 : document.createElement("div");
|
|
4120
|
-
if (meta) {
|
|
4121
|
-
meta.className = "dfwr-item-date";
|
|
4122
|
-
meta.textContent = formatNoteDraftMeta(draft);
|
|
4123
|
-
}
|
|
4124
|
-
const titleInput = this.isTitleFieldEnabled() ? this.createDraftTitleInput(draft.title, (title) => {
|
|
4125
|
-
const noteDraft = this.state.noteDraft;
|
|
4126
|
-
if (!noteDraft) return;
|
|
4127
|
-
this.config.actions.setNoteDraft({
|
|
4128
|
-
...noteDraft,
|
|
4129
|
-
title
|
|
4130
|
-
});
|
|
4131
|
-
}) : void 0;
|
|
4132
|
-
const textarea = document.createElement("textarea");
|
|
4133
|
-
textarea.className = "dfwr-textarea";
|
|
4134
|
-
textarea.placeholder = "Review comment";
|
|
4135
|
-
textarea.rows = 4;
|
|
4136
|
-
textarea.value = draft.comment ?? "";
|
|
4137
|
-
textarea.addEventListener("input", () => {
|
|
4138
|
-
const noteDraft = this.state.noteDraft;
|
|
4139
|
-
if (!noteDraft) return;
|
|
4140
|
-
this.config.actions.setNoteDraft({
|
|
4141
|
-
...noteDraft,
|
|
4142
|
-
comment: textarea.value
|
|
4143
|
-
});
|
|
4144
|
-
});
|
|
4145
|
-
const assigneeSelect = this.createDraftAssigneeSelect(
|
|
4146
|
-
draft.assigneeId,
|
|
4147
|
-
draft.assigneeName,
|
|
4148
|
-
(assigneeId, assigneeName) => {
|
|
4149
|
-
const noteDraft = this.state.noteDraft;
|
|
4150
|
-
if (!noteDraft) return;
|
|
4151
|
-
this.config.actions.setNoteDraft({
|
|
4152
|
-
...noteDraft,
|
|
4153
|
-
assigneeId,
|
|
4154
|
-
assigneeName
|
|
4155
|
-
});
|
|
4345
|
+
return;
|
|
4156
4346
|
}
|
|
4157
|
-
);
|
|
4158
|
-
const saveDraft = () => {
|
|
4159
|
-
const currentDraft = this.state.noteDraft ?? draft;
|
|
4160
|
-
const fields = this.getDraftFields(titleInput, textarea, assigneeSelect);
|
|
4161
|
-
const comment = fields.comment;
|
|
4162
|
-
if (!comment && !this.hasDraftAdjustment(currentDraft)) return;
|
|
4163
|
-
void this.config.actions.createItem({
|
|
4164
|
-
kind: "note",
|
|
4165
|
-
title: fields.title,
|
|
4166
|
-
comment: this.withDraftAdjustmentComment(comment, currentDraft),
|
|
4167
|
-
assigneeId: fields.assigneeId,
|
|
4168
|
-
assigneeName: fields.assigneeName,
|
|
4169
|
-
viewport: currentDraft.viewport,
|
|
4170
|
-
anchor: currentDraft.anchor,
|
|
4171
|
-
marker: currentDraft.marker,
|
|
4172
|
-
selection: currentDraft.selection
|
|
4173
|
-
});
|
|
4174
|
-
};
|
|
4175
|
-
const adjustmentControls = isElementDraft ? this.createAdjustmentControls({
|
|
4176
|
-
draft,
|
|
4177
|
-
pin,
|
|
4178
|
-
popover,
|
|
4179
|
-
selectionHighlight,
|
|
4180
|
-
textarea,
|
|
4181
|
-
dockToggle: options.dockComposer
|
|
4182
|
-
}) : void 0;
|
|
4183
|
-
const actions = this.createFormActions("Save note", saveDraft, {
|
|
4184
|
-
leading: adjustmentControls?.actionButton ? [adjustmentControls.actionButton] : void 0
|
|
4185
|
-
});
|
|
4186
|
-
const error = this.createDraftError();
|
|
4187
|
-
form.append(
|
|
4188
|
-
...meta ? [meta] : [],
|
|
4189
|
-
...adjustmentControls ? [adjustmentControls.panel] : [],
|
|
4190
|
-
...titleInput ? [titleInput] : [],
|
|
4191
|
-
textarea,
|
|
4192
|
-
...assigneeSelect ? [assigneeSelect] : [],
|
|
4193
|
-
...error ? [error] : [],
|
|
4194
|
-
actions
|
|
4195
|
-
);
|
|
4196
|
-
const dragHandle = isElementDraft && !options.dockComposer ? this.createDraftDragHandle("Move DOM composer") : void 0;
|
|
4197
|
-
popover.append(
|
|
4198
|
-
...dragHandle ? [dragHandle] : [],
|
|
4199
|
-
form
|
|
4200
|
-
);
|
|
4201
|
-
group.append(pin);
|
|
4202
|
-
if (!options.dockComposer) {
|
|
4203
|
-
group.append(popover);
|
|
4204
|
-
}
|
|
4205
|
-
if (dragHandle) {
|
|
4206
|
-
this.attachDraftComposerDrag(popover, dragHandle, (composerPosition) => {
|
|
4207
|
-
const noteDraft = this.state.noteDraft ?? draft;
|
|
4208
|
-
this.config.actions.setNoteDraft({
|
|
4209
|
-
...noteDraft,
|
|
4210
|
-
composerPosition,
|
|
4211
|
-
comment: textarea.value
|
|
4212
|
-
});
|
|
4213
|
-
});
|
|
4214
4347
|
}
|
|
4215
|
-
|
|
4216
|
-
|
|
4217
|
-
isElementDraft || options.dockComposer ? void 0 : popover,
|
|
4218
|
-
meta,
|
|
4219
|
-
textarea
|
|
4220
|
-
);
|
|
4221
|
-
if (!options.dockComposer) {
|
|
4222
|
-
window.setTimeout(() => {
|
|
4223
|
-
if (draft.adjustment?.isActive) {
|
|
4224
|
-
adjustmentControls?.focusTarget.focus();
|
|
4225
|
-
return;
|
|
4226
|
-
}
|
|
4227
|
-
textarea.focus();
|
|
4228
|
-
}, 0);
|
|
4348
|
+
if (!showCompactMarkers && !isHighlighted) {
|
|
4349
|
+
return;
|
|
4229
4350
|
}
|
|
4230
|
-
|
|
4231
|
-
|
|
4232
|
-
|
|
4233
|
-
};
|
|
4234
|
-
}
|
|
4235
|
-
createDraftDragHandle(label) {
|
|
4236
|
-
const handle = document.createElement("button");
|
|
4237
|
-
handle.className = "dfwr-draft-drag-handle";
|
|
4238
|
-
handle.type = "button";
|
|
4239
|
-
handle.setAttribute("aria-label", label);
|
|
4240
|
-
return handle;
|
|
4241
|
-
}
|
|
4242
|
-
createIcon(paths) {
|
|
4243
|
-
const svg = document.createElementNS("http://www.w3.org/2000/svg", "svg");
|
|
4244
|
-
svg.setAttribute("aria-hidden", "true");
|
|
4245
|
-
svg.setAttribute("viewBox", "0 0 24 24");
|
|
4246
|
-
svg.setAttribute("fill", "none");
|
|
4247
|
-
svg.setAttribute("stroke", "currentColor");
|
|
4248
|
-
svg.setAttribute("stroke-width", "2.4");
|
|
4249
|
-
svg.setAttribute("stroke-linecap", "round");
|
|
4250
|
-
svg.setAttribute("stroke-linejoin", "round");
|
|
4251
|
-
paths.forEach((d) => {
|
|
4252
|
-
const path = document.createElementNS("http://www.w3.org/2000/svg", "path");
|
|
4253
|
-
path.setAttribute("d", d);
|
|
4254
|
-
svg.append(path);
|
|
4255
|
-
});
|
|
4256
|
-
return svg;
|
|
4257
|
-
}
|
|
4258
|
-
setAdjustmentToggleIcon(button, isActive) {
|
|
4259
|
-
const paths = isActive ? ["M20 6 9 17l-5-5"] : [
|
|
4260
|
-
"M12 2v20",
|
|
4261
|
-
"M2 12h20",
|
|
4262
|
-
"m9 5 3-3 3 3",
|
|
4263
|
-
"m9 19 3 3 3-3",
|
|
4264
|
-
"m5 9-3 3 3 3",
|
|
4265
|
-
"m19 9 3 3-3 3"
|
|
4266
|
-
];
|
|
4267
|
-
button.replaceChildren(this.createIcon(paths));
|
|
4268
|
-
}
|
|
4269
|
-
attachDraftComposerDrag(popover, handle, onMove) {
|
|
4270
|
-
let isDragging = false;
|
|
4271
|
-
let offsetX = 0;
|
|
4272
|
-
let offsetY = 0;
|
|
4273
|
-
const movePopover = (event) => {
|
|
4274
|
-
const environment = this.config.getEnvironment();
|
|
4275
|
-
if (!environment) return;
|
|
4276
|
-
const position = this.getClampedComposerPosition(
|
|
4277
|
-
{
|
|
4278
|
-
x: event.clientX - offsetX,
|
|
4279
|
-
y: event.clientY - offsetY
|
|
4280
|
-
},
|
|
4281
|
-
environment,
|
|
4282
|
-
{
|
|
4283
|
-
width: popover.offsetWidth,
|
|
4284
|
-
height: popover.offsetHeight
|
|
4285
|
-
},
|
|
4286
|
-
this.getHostComposerBounds()
|
|
4287
|
-
);
|
|
4288
|
-
popover.style.left = `${position.x}px`;
|
|
4289
|
-
popover.style.top = `${position.y}px`;
|
|
4290
|
-
onMove(position);
|
|
4291
|
-
};
|
|
4292
|
-
handle.addEventListener("pointerdown", (event) => {
|
|
4293
|
-
if (event.button !== 0) return;
|
|
4294
|
-
const rect = popover.getBoundingClientRect();
|
|
4295
|
-
offsetX = event.clientX - rect.left;
|
|
4296
|
-
offsetY = event.clientY - rect.top;
|
|
4297
|
-
isDragging = true;
|
|
4298
|
-
event.preventDefault();
|
|
4299
|
-
event.stopPropagation();
|
|
4300
|
-
handle.setPointerCapture(event.pointerId);
|
|
4301
|
-
popover.classList.add("is-dragging");
|
|
4302
|
-
});
|
|
4303
|
-
handle.addEventListener("pointermove", (event) => {
|
|
4304
|
-
if (!isDragging || !handle.hasPointerCapture(event.pointerId)) return;
|
|
4305
|
-
event.preventDefault();
|
|
4306
|
-
movePopover(event);
|
|
4307
|
-
});
|
|
4308
|
-
const stopDrag = (event) => {
|
|
4309
|
-
if (!isDragging || !handle.hasPointerCapture(event.pointerId)) return;
|
|
4310
|
-
event.preventDefault();
|
|
4311
|
-
event.stopPropagation();
|
|
4312
|
-
isDragging = false;
|
|
4313
|
-
handle.releasePointerCapture(event.pointerId);
|
|
4314
|
-
popover.classList.remove("is-dragging");
|
|
4315
|
-
movePopover(event);
|
|
4316
|
-
};
|
|
4317
|
-
handle.addEventListener("pointerup", stopDrag);
|
|
4318
|
-
handle.addEventListener("pointercancel", stopDrag);
|
|
4319
|
-
}
|
|
4320
|
-
// Builds the element-adjustment controls (nudge the previewed element via
|
|
4321
|
-
// arrow keys / buttons). Wires keyboard deltas to the draft transform and
|
|
4322
|
-
// keeps the pin, popover, highlight and textarea in sync as the value changes.
|
|
4323
|
-
createAdjustmentControls({
|
|
4324
|
-
draft,
|
|
4325
|
-
pin,
|
|
4326
|
-
popover,
|
|
4327
|
-
selectionHighlight,
|
|
4328
|
-
textarea,
|
|
4329
|
-
dockToggle
|
|
4330
|
-
}) {
|
|
4331
|
-
const panel = document.createElement("div");
|
|
4332
|
-
panel.className = "dfwr-adjust-panel is-dom-adjust-panel";
|
|
4333
|
-
const header = document.createElement("div");
|
|
4334
|
-
header.className = "dfwr-adjust-panel-header";
|
|
4335
|
-
const help = document.createElement("div");
|
|
4336
|
-
help.className = "dfwr-adjust-help";
|
|
4337
|
-
help.textContent = this.getAdjustmentLabel();
|
|
4338
|
-
const adjust = document.createElement("button");
|
|
4339
|
-
adjust.className = "dfwr-adjust-toggle";
|
|
4340
|
-
adjust.type = "button";
|
|
4341
|
-
adjust.title = "Adjust DOM element with keyboard arrows";
|
|
4342
|
-
adjust.setAttribute("aria-label", "Adjust DOM element with keyboard arrows");
|
|
4343
|
-
const xyStatus = document.createElement("div");
|
|
4344
|
-
xyStatus.className = "dfwr-adjust-status";
|
|
4345
|
-
const scaleStatus = document.createElement("div");
|
|
4346
|
-
scaleStatus.className = "dfwr-adjust-status";
|
|
4347
|
-
const syncControls = (nextDraft) => {
|
|
4348
|
-
const isActive = nextDraft.adjustment?.isActive === true;
|
|
4349
|
-
panel.classList.toggle("is-active", isActive);
|
|
4350
|
-
adjust.classList.toggle("is-active", isActive);
|
|
4351
|
-
adjust.setAttribute("aria-pressed", isActive ? "true" : "false");
|
|
4352
|
-
this.setAdjustmentToggleIcon(adjust, isActive);
|
|
4353
|
-
adjust.title = isActive ? "Finish DOM adjustment" : "Adjust DOM element with keyboard arrows";
|
|
4354
|
-
adjust.setAttribute(
|
|
4355
|
-
"aria-label",
|
|
4356
|
-
isActive ? "Finish DOM adjustment" : "Adjust DOM element with keyboard arrows"
|
|
4357
|
-
);
|
|
4358
|
-
const [xyLine, scaleLine] = this.getDraftAdjustmentMetricLines(nextDraft);
|
|
4359
|
-
xyStatus.textContent = xyLine;
|
|
4360
|
-
scaleStatus.textContent = scaleLine;
|
|
4361
|
-
this.syncDraftAdjustmentUi({
|
|
4362
|
-
draft: nextDraft,
|
|
4363
|
-
pin,
|
|
4364
|
-
selectionHighlight
|
|
4365
|
-
});
|
|
4366
|
-
};
|
|
4367
|
-
const updateDraft = (updater) => {
|
|
4368
|
-
const currentDraft = this.state.noteDraft ?? draft;
|
|
4369
|
-
const nextDraft = updater(currentDraft);
|
|
4370
|
-
this.config.actions.setNoteDraft({
|
|
4371
|
-
...nextDraft,
|
|
4372
|
-
comment: textarea.value
|
|
4373
|
-
});
|
|
4374
|
-
syncControls(nextDraft);
|
|
4375
|
-
};
|
|
4376
|
-
adjust.addEventListener("click", () => {
|
|
4377
|
-
updateDraft((currentDraft) => ({
|
|
4378
|
-
...currentDraft,
|
|
4379
|
-
adjustment: {
|
|
4380
|
-
x: currentDraft.adjustment?.x ?? 0,
|
|
4381
|
-
y: currentDraft.adjustment?.y ?? 0,
|
|
4382
|
-
scale: currentDraft.adjustment?.scale ?? 0,
|
|
4383
|
-
isActive: currentDraft.adjustment?.isActive !== true
|
|
4384
|
-
}
|
|
4385
|
-
}));
|
|
4386
|
-
adjust.focus();
|
|
4387
|
-
});
|
|
4388
|
-
popover.addEventListener("keydown", (event) => {
|
|
4389
|
-
const currentDraft = this.state.noteDraft ?? draft;
|
|
4390
|
-
if (currentDraft.adjustment?.isActive !== true) return;
|
|
4391
|
-
const keyDelta = this.getAdjustmentKeyDelta(event);
|
|
4392
|
-
if (!keyDelta) return;
|
|
4393
|
-
event.preventDefault();
|
|
4394
|
-
event.stopPropagation();
|
|
4395
|
-
updateDraft((activeDraft) => ({
|
|
4396
|
-
...activeDraft,
|
|
4397
|
-
adjustment: {
|
|
4398
|
-
x: (activeDraft.adjustment?.x ?? 0) + keyDelta.x,
|
|
4399
|
-
y: (activeDraft.adjustment?.y ?? 0) + keyDelta.y,
|
|
4400
|
-
scale: (activeDraft.adjustment?.scale ?? 0) + keyDelta.scale,
|
|
4401
|
-
isActive: true
|
|
4402
|
-
}
|
|
4403
|
-
}));
|
|
4404
|
-
});
|
|
4405
|
-
header.append(help);
|
|
4406
|
-
if (!dockToggle) {
|
|
4407
|
-
header.append(adjust);
|
|
4351
|
+
const point = getBoundMarkerPoint(item, environment);
|
|
4352
|
+
if (!point || !isPointInViewport(point.viewport, environment)) {
|
|
4353
|
+
return;
|
|
4408
4354
|
}
|
|
4409
|
-
|
|
4410
|
-
|
|
4411
|
-
|
|
4412
|
-
|
|
4413
|
-
|
|
4414
|
-
|
|
4415
|
-
|
|
4416
|
-
|
|
4417
|
-
getAdjustmentKeyDelta(event) {
|
|
4418
|
-
const step = event.shiftKey ? 10 : 1;
|
|
4419
|
-
if (event.key === "ArrowLeft") return { x: -step, y: 0, scale: 0 };
|
|
4420
|
-
if (event.key === "ArrowRight") return { x: step, y: 0, scale: 0 };
|
|
4421
|
-
if (event.key === "ArrowUp") return { x: 0, y: -step, scale: 0 };
|
|
4422
|
-
if (event.key === "ArrowDown") return { x: 0, y: step, scale: 0 };
|
|
4423
|
-
if (event.key.toLowerCase() === "w") return { x: 0, y: 0, scale: step };
|
|
4424
|
-
if (event.key.toLowerCase() === "s") return { x: 0, y: 0, scale: -step };
|
|
4425
|
-
return void 0;
|
|
4426
|
-
}
|
|
4427
|
-
syncDraftAdjustmentUi({
|
|
4428
|
-
draft,
|
|
4429
|
-
pin,
|
|
4430
|
-
selectionHighlight
|
|
4431
|
-
}) {
|
|
4432
|
-
const environment = this.config.getEnvironment();
|
|
4433
|
-
if (!environment) return;
|
|
4434
|
-
const hostPoint = toHostPoint(
|
|
4435
|
-
this.getAdjustedDraftPoint(draft.marker.viewport, draft),
|
|
4436
|
-
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
|
|
4437
4363
|
);
|
|
4438
|
-
|
|
4439
|
-
|
|
4440
|
-
|
|
4441
|
-
|
|
4442
|
-
|
|
4443
|
-
|
|
4444
|
-
|
|
4445
|
-
|
|
4446
|
-
|
|
4447
|
-
|
|
4448
|
-
|
|
4449
|
-
|
|
4450
|
-
|
|
4451
|
-
|
|
4452
|
-
|
|
4453
|
-
|
|
4454
|
-
|
|
4455
|
-
|
|
4456
|
-
|
|
4457
|
-
|
|
4458
|
-
|
|
4459
|
-
|
|
4460
|
-
|
|
4461
|
-
|
|
4462
|
-
|
|
4463
|
-
|
|
4464
|
-
|
|
4465
|
-
}
|
|
4466
|
-
|
|
4467
|
-
|
|
4468
|
-
|
|
4469
|
-
|
|
4470
|
-
|
|
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({
|
|
4471
4419
|
...draft,
|
|
4472
|
-
|
|
4420
|
+
comment
|
|
4473
4421
|
});
|
|
4474
|
-
}
|
|
4475
|
-
|
|
4476
|
-
|
|
4477
|
-
|
|
4478
|
-
|
|
4479
|
-
|
|
4480
|
-
|
|
4481
|
-
|
|
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;
|
|
4482
4431
|
if (!draft) return;
|
|
4483
|
-
|
|
4432
|
+
config.actions.setAreaDraft({
|
|
4484
4433
|
...draft,
|
|
4485
|
-
|
|
4434
|
+
assigneeId,
|
|
4435
|
+
assigneeName
|
|
4486
4436
|
});
|
|
4487
|
-
}
|
|
4488
|
-
|
|
4489
|
-
|
|
4490
|
-
|
|
4491
|
-
|
|
4492
|
-
|
|
4493
|
-
|
|
4494
|
-
|
|
4495
|
-
|
|
4496
|
-
|
|
4497
|
-
|
|
4498
|
-
|
|
4499
|
-
|
|
4500
|
-
|
|
4501
|
-
const actions = this.createFormActions("Save area", () => {
|
|
4502
|
-
const draft = this.state.areaDraft;
|
|
4503
|
-
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
|
+
);
|
|
4504
4451
|
const comment = fields.comment;
|
|
4505
|
-
if (!comment || !draft) return;
|
|
4506
|
-
void
|
|
4452
|
+
if (!comment && !draft?.attachments?.length || !draft) return;
|
|
4453
|
+
void config.actions.createItem({
|
|
4507
4454
|
kind: "area",
|
|
4508
4455
|
title: fields.title,
|
|
4509
4456
|
comment,
|
|
@@ -4512,603 +4459,1077 @@ ${adjustment}` : adjustment;
|
|
|
4512
4459
|
viewport: draft.viewport,
|
|
4513
4460
|
anchor: draft.anchor,
|
|
4514
4461
|
marker: draft.marker,
|
|
4515
|
-
selection: draft.selection
|
|
4462
|
+
selection: draft.selection,
|
|
4463
|
+
attachments: draft.attachments
|
|
4516
4464
|
});
|
|
4517
|
-
}
|
|
4518
|
-
|
|
4519
|
-
|
|
4520
|
-
|
|
4521
|
-
|
|
4522
|
-
|
|
4523
|
-
|
|
4524
|
-
|
|
4525
|
-
|
|
4526
|
-
|
|
4527
|
-
|
|
4528
|
-
|
|
4529
|
-
|
|
4530
|
-
|
|
4531
|
-
|
|
4532
|
-
|
|
4533
|
-
|
|
4534
|
-
this.getAreaDraftMetricSelection(draft),
|
|
4535
|
-
draft.viewport
|
|
4536
|
-
);
|
|
4537
|
-
help.textContent = labelLine;
|
|
4538
|
-
const xyStatus = document.createElement("div");
|
|
4539
|
-
xyStatus.className = "dfwr-adjust-status";
|
|
4540
|
-
xyStatus.textContent = xyLine;
|
|
4541
|
-
const sizeStatus = document.createElement("div");
|
|
4542
|
-
sizeStatus.className = "dfwr-adjust-status";
|
|
4543
|
-
sizeStatus.textContent = sizeLine;
|
|
4544
|
-
panel.append(help, xyStatus, sizeStatus);
|
|
4545
|
-
return panel;
|
|
4546
|
-
}
|
|
4547
|
-
createAreaDraftOverlay(draft) {
|
|
4548
|
-
const layer = document.createElement("div");
|
|
4549
|
-
layer.className = "dfwr-area-preview-layer";
|
|
4550
|
-
const environment = this.config.getEnvironment();
|
|
4551
|
-
if (!environment || !draft.selection) return layer;
|
|
4552
|
-
const selection = toViewportSelection(draft.selection.viewport);
|
|
4553
|
-
layer.append(this.createSelectionHighlight(selection, environment, true));
|
|
4554
|
-
if (draft.marker) {
|
|
4555
|
-
const hostPoint = toHostPoint(draft.marker.viewport, environment);
|
|
4556
|
-
layer.append(
|
|
4557
|
-
this.createMarkerElement(
|
|
4558
|
-
void 0,
|
|
4559
|
-
hostPoint,
|
|
4560
|
-
"\u2022",
|
|
4561
|
-
getReviewViewportScope(
|
|
4562
|
-
draft.viewport,
|
|
4563
|
-
this.config.options.viewports?.presets
|
|
4564
|
-
),
|
|
4565
|
-
true,
|
|
4566
|
-
true
|
|
4567
|
-
)
|
|
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
|
|
4568
4482
|
);
|
|
4483
|
+
config.actions.setAreaDraft({
|
|
4484
|
+
...draft,
|
|
4485
|
+
comment: textarea.value,
|
|
4486
|
+
attachments: attachments.length > 0 ? attachments : void 0
|
|
4487
|
+
});
|
|
4488
|
+
config.actions.render();
|
|
4569
4489
|
}
|
|
4570
|
-
|
|
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
|
+
);
|
|
4571
4544
|
}
|
|
4572
|
-
|
|
4573
|
-
|
|
4574
|
-
|
|
4575
|
-
|
|
4576
|
-
|
|
4577
|
-
|
|
4578
|
-
|
|
4579
|
-
|
|
4580
|
-
|
|
4581
|
-
|
|
4582
|
-
|
|
4583
|
-
|
|
4584
|
-
|
|
4585
|
-
|
|
4586
|
-
|
|
4587
|
-
|
|
4588
|
-
|
|
4589
|
-
environment,
|
|
4590
|
-
composerPosition: draft.composerPosition,
|
|
4591
|
-
estimatedHeight: 220
|
|
4592
|
-
});
|
|
4593
|
-
popover.style.left = `${composer.left}px`;
|
|
4594
|
-
popover.style.top = `${composer.top}px`;
|
|
4595
|
-
popover.style.width = `${composer.width}px`;
|
|
4596
|
-
popover.style.right = "auto";
|
|
4597
|
-
}
|
|
4598
|
-
const dragHandle = options.dockComposer ? void 0 : this.createDraftDragHandle("Move area composer");
|
|
4599
|
-
popover.append(
|
|
4600
|
-
...dragHandle ? [dragHandle] : [],
|
|
4601
|
-
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
|
|
4602
4562
|
);
|
|
4603
|
-
|
|
4604
|
-
|
|
4605
|
-
|
|
4606
|
-
|
|
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({
|
|
4607
4587
|
...areaDraft,
|
|
4608
4588
|
composerPosition
|
|
4609
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
|
|
4610
4714
|
});
|
|
4611
4715
|
}
|
|
4612
|
-
|
|
4613
|
-
|
|
4614
|
-
|
|
4615
|
-
const
|
|
4616
|
-
|
|
4617
|
-
|
|
4618
|
-
|
|
4619
|
-
|
|
4620
|
-
|
|
4621
|
-
|
|
4622
|
-
|
|
4623
|
-
if (
|
|
4624
|
-
|
|
4625
|
-
}
|
|
4626
|
-
|
|
4627
|
-
|
|
4628
|
-
|
|
4629
|
-
|
|
4630
|
-
|
|
4631
|
-
|
|
4632
|
-
|
|
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
|
|
4633
4745
|
});
|
|
4634
|
-
|
|
4635
|
-
|
|
4636
|
-
|
|
4637
|
-
|
|
4638
|
-
|
|
4639
|
-
|
|
4640
|
-
|
|
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
|
+
}
|
|
4641
4817
|
});
|
|
4642
|
-
if (options?.leading?.length) {
|
|
4643
|
-
actions.classList.add("has-leading");
|
|
4644
|
-
const leading = document.createElement("div");
|
|
4645
|
-
leading.className = "dfwr-actions-leading";
|
|
4646
|
-
leading.append(...options.leading);
|
|
4647
|
-
const primary = document.createElement("div");
|
|
4648
|
-
primary.className = "dfwr-actions-primary";
|
|
4649
|
-
primary.append(save, cancel);
|
|
4650
|
-
actions.append(leading, primary);
|
|
4651
|
-
return actions;
|
|
4652
|
-
}
|
|
4653
|
-
if (options?.beforeSave?.length || options?.className) {
|
|
4654
|
-
actions.append(cancel, ...options.beforeSave ?? [], save);
|
|
4655
|
-
return actions;
|
|
4656
|
-
}
|
|
4657
|
-
actions.append(save, cancel);
|
|
4658
|
-
return actions;
|
|
4659
4818
|
}
|
|
4660
|
-
|
|
4661
|
-
|
|
4662
|
-
|
|
4663
|
-
|
|
4664
|
-
|
|
4665
|
-
}
|
|
4666
|
-
|
|
4667
|
-
|
|
4668
|
-
|
|
4669
|
-
|
|
4670
|
-
|
|
4671
|
-
|
|
4672
|
-
|
|
4673
|
-
|
|
4674
|
-
createList() {
|
|
4675
|
-
const section = document.createElement("div");
|
|
4676
|
-
section.className = "dfwr-list";
|
|
4677
|
-
const state = this.state;
|
|
4678
|
-
const heading = document.createElement("div");
|
|
4679
|
-
heading.className = "dfwr-list-heading";
|
|
4680
|
-
heading.textContent = `Review items (${state.items.length})`;
|
|
4681
|
-
section.append(heading);
|
|
4682
|
-
if (state.items.length === 0) {
|
|
4683
|
-
const empty = document.createElement("p");
|
|
4684
|
-
empty.className = "dfwr-empty";
|
|
4685
|
-
empty.textContent = "No review items on this page.";
|
|
4686
|
-
section.append(empty);
|
|
4687
|
-
return section;
|
|
4688
|
-
}
|
|
4689
|
-
for (const numberedItem of getNumberedReviewItems(
|
|
4690
|
-
state.items,
|
|
4691
|
-
this.config.options.viewports?.presets
|
|
4692
|
-
)) {
|
|
4693
|
-
section.append(this.createListItem(numberedItem));
|
|
4694
|
-
}
|
|
4695
|
-
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);
|
|
4696
4833
|
}
|
|
4697
|
-
|
|
4698
|
-
|
|
4699
|
-
|
|
4700
|
-
|
|
4701
|
-
|
|
4702
|
-
|
|
4703
|
-
|
|
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(
|
|
4704
4873
|
"aria-label",
|
|
4705
|
-
|
|
4874
|
+
isActive ? "Finish DOM adjustment" : "Adjust DOM element with keyboard arrows"
|
|
4875
|
+
);
|
|
4876
|
+
const [xyLine, scaleLine] = getDraftAdjustmentMetricLines(
|
|
4877
|
+
nextDraft,
|
|
4878
|
+
presets
|
|
4706
4879
|
);
|
|
4707
|
-
|
|
4708
|
-
|
|
4880
|
+
xyStatus.textContent = xyLine;
|
|
4881
|
+
scaleStatus.textContent = scaleLine;
|
|
4882
|
+
syncDraftAdjustmentUi(context, {
|
|
4883
|
+
draft: nextDraft,
|
|
4884
|
+
pin,
|
|
4885
|
+
selectionHighlight
|
|
4709
4886
|
});
|
|
4710
|
-
|
|
4711
|
-
|
|
4712
|
-
|
|
4713
|
-
|
|
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
|
|
4714
4894
|
});
|
|
4715
|
-
|
|
4716
|
-
|
|
4717
|
-
|
|
4718
|
-
|
|
4719
|
-
|
|
4720
|
-
|
|
4721
|
-
|
|
4722
|
-
|
|
4723
|
-
|
|
4724
|
-
|
|
4725
|
-
|
|
4726
|
-
|
|
4727
|
-
|
|
4728
|
-
|
|
4729
|
-
|
|
4730
|
-
|
|
4731
|
-
|
|
4732
|
-
const
|
|
4733
|
-
|
|
4734
|
-
|
|
4735
|
-
|
|
4736
|
-
|
|
4737
|
-
|
|
4738
|
-
|
|
4739
|
-
|
|
4740
|
-
|
|
4741
|
-
|
|
4742
|
-
|
|
4743
|
-
|
|
4744
|
-
|
|
4745
|
-
|
|
4746
|
-
|
|
4747
|
-
|
|
4748
|
-
|
|
4749
|
-
|
|
4750
|
-
|
|
4751
|
-
|
|
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
|
|
4752
5031
|
});
|
|
4753
|
-
|
|
4754
|
-
|
|
4755
|
-
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;
|
|
4756
5132
|
}
|
|
4757
|
-
|
|
4758
|
-
|
|
4759
|
-
|
|
4760
|
-
|
|
4761
|
-
|
|
4762
|
-
const currentScope = getReviewViewportScope(
|
|
4763
|
-
getViewportSize(environment),
|
|
4764
|
-
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
|
+
)
|
|
4765
5138
|
);
|
|
4766
|
-
|
|
4767
|
-
|
|
4768
|
-
|
|
4769
|
-
|
|
4770
|
-
|
|
4771
|
-
|
|
4772
|
-
|
|
4773
|
-
|
|
4774
|
-
|
|
4775
|
-
|
|
4776
|
-
|
|
4777
|
-
|
|
4778
|
-
|
|
4779
|
-
|
|
4780
|
-
|
|
4781
|
-
|
|
4782
|
-
|
|
4783
|
-
|
|
4784
|
-
|
|
4785
|
-
|
|
4786
|
-
|
|
4787
|
-
|
|
4788
|
-
|
|
4789
|
-
|
|
4790
|
-
|
|
4791
|
-
|
|
4792
|
-
|
|
4793
|
-
|
|
4794
|
-
|
|
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);
|
|
4795
5326
|
}
|
|
4796
|
-
|
|
4797
|
-
|
|
4798
|
-
|
|
4799
|
-
|
|
4800
|
-
|
|
4801
|
-
|
|
4802
|
-
|
|
4803
|
-
|
|
4804
|
-
|
|
4805
|
-
|
|
4806
|
-
|
|
4807
|
-
|
|
4808
|
-
|
|
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())
|
|
4809
5394
|
});
|
|
4810
|
-
|
|
5395
|
+
this.draftContext = {
|
|
5396
|
+
config: this.config,
|
|
5397
|
+
cancelDraft: (event) => this.cancelDraft(event),
|
|
5398
|
+
syncDraftPreview: (draft) => this.draftPreview.sync(draft)
|
|
5399
|
+
};
|
|
4811
5400
|
}
|
|
4812
|
-
|
|
4813
|
-
|
|
4814
|
-
|
|
4815
|
-
|
|
4816
|
-
|
|
4817
|
-
|
|
4818
|
-
|
|
4819
|
-
|
|
4820
|
-
|
|
4821
|
-
|
|
4822
|
-
|
|
4823
|
-
|
|
4824
|
-
|
|
4825
|
-
|
|
4826
|
-
|
|
4827
|
-
|
|
4828
|
-
|
|
4829
|
-
|
|
4830
|
-
|
|
4831
|
-
|
|
4832
|
-
|
|
4833
|
-
labelElement.textContent = label;
|
|
4834
|
-
labelElement.style.left = `${Math.max(4, rect.left)}px`;
|
|
4835
|
-
labelElement.style.top = `${Math.max(4, rect.top - 24)}px`;
|
|
4836
|
-
labelElement.dataset.reviewItemId = item.id;
|
|
4837
|
-
return [highlight, labelElement];
|
|
4838
|
-
}
|
|
4839
|
-
createSelectionHighlight(selection, environment, isDraft) {
|
|
4840
|
-
const rect = toHostSelection(selection, environment);
|
|
4841
|
-
const highlight = document.createElement("div");
|
|
4842
|
-
highlight.className = `dfwr-selection-highlight${isDraft ? " is-draft" : ""}`;
|
|
4843
|
-
highlight.style.left = `${rect.left}px`;
|
|
4844
|
-
highlight.style.top = `${rect.top}px`;
|
|
4845
|
-
highlight.style.width = `${rect.width}px`;
|
|
4846
|
-
highlight.style.height = `${rect.height}px`;
|
|
4847
|
-
return highlight;
|
|
4848
|
-
}
|
|
4849
|
-
createMarkerElement(itemId, hostPoint, label, scope, isBound, isHighlighted, variant = "default") {
|
|
4850
|
-
const isNoteCallout = variant === "note";
|
|
4851
|
-
const marker = document.createElement("div");
|
|
4852
|
-
marker.className = [
|
|
4853
|
-
"dfwr-bound-marker",
|
|
4854
|
-
isNoteCallout ? "is-note-callout" : "",
|
|
4855
|
-
`is-scope-${scope}`,
|
|
4856
|
-
isBound ? "is-bound" : "is-fallback",
|
|
4857
|
-
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" : ""
|
|
4858
5422
|
].filter(Boolean).join(" ");
|
|
4859
|
-
|
|
4860
|
-
|
|
4861
|
-
|
|
4862
|
-
|
|
4863
|
-
|
|
4864
|
-
|
|
4865
|
-
|
|
4866
|
-
|
|
4867
|
-
|
|
4868
|
-
|
|
4869
|
-
|
|
4870
|
-
|
|
4871
|
-
|
|
4872
|
-
|
|
4873
|
-
|
|
4874
|
-
|
|
4875
|
-
|
|
4876
|
-
|
|
4877
|
-
|
|
4878
|
-
|
|
4879
|
-
|
|
4880
|
-
|
|
4881
|
-
|
|
4882
|
-
|
|
4883
|
-
|
|
4884
|
-
|
|
4885
|
-
|
|
4886
|
-
|
|
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));
|
|
4887
5453
|
}
|
|
4888
|
-
|
|
4889
|
-
|
|
4890
|
-
|
|
4891
|
-
|
|
4892
|
-
|
|
4893
|
-
|
|
4894
|
-
|
|
4895
|
-
},
|
|
4896
|
-
comment: textarea.value
|
|
4897
|
-
};
|
|
4898
|
-
this.config.actions.setNoteDraft(nextDraft);
|
|
4899
|
-
if (meta) {
|
|
4900
|
-
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));
|
|
4901
5461
|
}
|
|
4902
|
-
|
|
4903
|
-
|
|
4904
|
-
|
|
4905
|
-
|
|
4906
|
-
event.stopPropagation();
|
|
4907
|
-
isDragging = true;
|
|
4908
|
-
pin.setPointerCapture(event.pointerId);
|
|
4909
|
-
});
|
|
4910
|
-
pin.addEventListener("pointermove", (event) => {
|
|
4911
|
-
if (!isDragging || !pin.hasPointerCapture(event.pointerId)) return;
|
|
4912
|
-
event.preventDefault();
|
|
4913
|
-
moveDraftUi({
|
|
4914
|
-
x: event.clientX,
|
|
4915
|
-
y: event.clientY
|
|
4916
|
-
});
|
|
4917
|
-
});
|
|
4918
|
-
pin.addEventListener("pointerup", (event) => {
|
|
4919
|
-
if (!isDragging || !pin.hasPointerCapture(event.pointerId)) return;
|
|
4920
|
-
event.preventDefault();
|
|
4921
|
-
event.stopPropagation();
|
|
4922
|
-
isDragging = false;
|
|
4923
|
-
pin.releasePointerCapture(event.pointerId);
|
|
4924
|
-
const nextPoint = toTargetPointFromHostEvent(
|
|
4925
|
-
event,
|
|
4926
|
-
this.config.getEnvironment()
|
|
5462
|
+
const areaComposer = createAreaDraftPopover(
|
|
5463
|
+
this.draftContext,
|
|
5464
|
+
state.areaDraft,
|
|
5465
|
+
{ dockComposer: shouldDockComposer }
|
|
4927
5466
|
);
|
|
4928
|
-
|
|
4929
|
-
|
|
4930
|
-
|
|
4931
|
-
|
|
4932
|
-
|
|
4933
|
-
|
|
4934
|
-
|
|
4935
|
-
|
|
4936
|
-
});
|
|
5467
|
+
if (shouldDockComposer) {
|
|
5468
|
+
dockedComposer = areaComposer;
|
|
5469
|
+
} else {
|
|
5470
|
+
shell.append(areaComposer);
|
|
5471
|
+
}
|
|
5472
|
+
}
|
|
5473
|
+
shadow.append(shell);
|
|
5474
|
+
this.renderShellComposer(dockedComposer);
|
|
4937
5475
|
}
|
|
4938
|
-
|
|
4939
|
-
|
|
4940
|
-
|
|
5476
|
+
get state() {
|
|
5477
|
+
return this.config.getState();
|
|
5478
|
+
}
|
|
5479
|
+
getShellComposerHost() {
|
|
4941
5480
|
const environment = this.config.getEnvironment();
|
|
4942
|
-
if (
|
|
4943
|
-
|
|
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;
|
|
4944
5490
|
}
|
|
4945
|
-
|
|
4946
|
-
|
|
4947
|
-
|
|
4948
|
-
|
|
4949
|
-
|
|
4950
|
-
|
|
4951
|
-
|
|
4952
|
-
|
|
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);
|
|
4953
5503
|
}
|
|
4954
|
-
|
|
4955
|
-
const
|
|
4956
|
-
|
|
4957
|
-
|
|
4958
|
-
|
|
4959
|
-
|
|
4960
|
-
hover.hidden = true;
|
|
4961
|
-
layer.append(hover);
|
|
4962
|
-
if (environment) {
|
|
4963
|
-
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;
|
|
4964
5510
|
}
|
|
4965
|
-
|
|
4966
|
-
const nextEnvironment = this.config.getEnvironment();
|
|
4967
|
-
if (!nextEnvironment) return;
|
|
4968
|
-
const anchor = getDomAnchorFromPoint(
|
|
4969
|
-
clampPoint(point, nextEnvironment),
|
|
4970
|
-
this.config.options.anchors?.attribute,
|
|
4971
|
-
nextEnvironment
|
|
4972
|
-
);
|
|
4973
|
-
const selection = anchor ? getElementViewportSelection(anchor, nextEnvironment) : void 0;
|
|
4974
|
-
if (!selection) {
|
|
4975
|
-
hover.hidden = true;
|
|
4976
|
-
return;
|
|
4977
|
-
}
|
|
4978
|
-
const rect = toHostSelection(selection, nextEnvironment);
|
|
4979
|
-
hover.hidden = false;
|
|
4980
|
-
hover.style.left = `${rect.left}px`;
|
|
4981
|
-
hover.style.top = `${rect.top}px`;
|
|
4982
|
-
hover.style.width = `${rect.width}px`;
|
|
4983
|
-
hover.style.height = `${rect.height}px`;
|
|
4984
|
-
};
|
|
4985
|
-
layer.addEventListener("pointermove", (event) => {
|
|
4986
|
-
updateHover(toTargetPointFromHostEvent(event, this.config.getEnvironment()));
|
|
4987
|
-
});
|
|
4988
|
-
layer.addEventListener("pointerleave", () => {
|
|
4989
|
-
hover.hidden = true;
|
|
4990
|
-
});
|
|
4991
|
-
layer.addEventListener("pointerdown", (event) => {
|
|
4992
|
-
if (event.button !== 0) return;
|
|
4993
|
-
event.preventDefault();
|
|
4994
|
-
void this.config.actions.bindElementDraftToPoint(
|
|
4995
|
-
toTargetPointFromHostEvent(event, this.config.getEnvironment())
|
|
4996
|
-
);
|
|
4997
|
-
});
|
|
4998
|
-
return layer;
|
|
5511
|
+
this.shellComposerHost = void 0;
|
|
4999
5512
|
}
|
|
5000
|
-
|
|
5513
|
+
/** Full-screen click-away layer that cancels the active draft. */
|
|
5514
|
+
createDraftCancelLayer() {
|
|
5001
5515
|
const layer = document.createElement("div");
|
|
5002
|
-
layer.className = "dfwr-
|
|
5003
|
-
|
|
5004
|
-
if (environment) {
|
|
5005
|
-
placeLayerOverTarget(layer, environment);
|
|
5006
|
-
}
|
|
5007
|
-
const box = document.createElement("div");
|
|
5008
|
-
box.className = "dfwr-area-box";
|
|
5009
|
-
layer.append(box);
|
|
5010
|
-
let startX = 0;
|
|
5011
|
-
let startY = 0;
|
|
5012
|
-
let selection;
|
|
5013
|
-
let activePointerId;
|
|
5014
|
-
let isDragging = false;
|
|
5015
|
-
const ownerWindow = layer.ownerDocument.defaultView ?? window;
|
|
5016
|
-
const updateBox = (event) => {
|
|
5017
|
-
const nextEnvironment = this.config.getEnvironment();
|
|
5018
|
-
const nextPoint = toTargetPointFromHostEvent(
|
|
5019
|
-
event,
|
|
5020
|
-
nextEnvironment
|
|
5021
|
-
);
|
|
5022
|
-
const left = Math.min(startX, nextPoint.x);
|
|
5023
|
-
const top = Math.min(startY, nextPoint.y);
|
|
5024
|
-
const width = Math.abs(nextPoint.x - startX);
|
|
5025
|
-
const height = Math.abs(nextPoint.y - startY);
|
|
5026
|
-
const hostPoint = toHostPoint(
|
|
5027
|
-
{ x: left, y: top },
|
|
5028
|
-
nextEnvironment
|
|
5029
|
-
);
|
|
5030
|
-
selection = { left, top, width, height };
|
|
5031
|
-
box.style.left = `${hostPoint.x}px`;
|
|
5032
|
-
box.style.top = `${hostPoint.y}px`;
|
|
5033
|
-
box.style.width = `${width}px`;
|
|
5034
|
-
box.style.height = `${height}px`;
|
|
5035
|
-
};
|
|
5036
|
-
const addDragListeners = () => {
|
|
5037
|
-
ownerWindow.addEventListener("pointermove", handlePointerMove, true);
|
|
5038
|
-
ownerWindow.addEventListener("pointerup", handlePointerUp, true);
|
|
5039
|
-
ownerWindow.addEventListener("pointercancel", handlePointerCancel, true);
|
|
5040
|
-
};
|
|
5041
|
-
const removeDragListeners = () => {
|
|
5042
|
-
ownerWindow.removeEventListener("pointermove", handlePointerMove, true);
|
|
5043
|
-
ownerWindow.removeEventListener("pointerup", handlePointerUp, true);
|
|
5044
|
-
ownerWindow.removeEventListener(
|
|
5045
|
-
"pointercancel",
|
|
5046
|
-
handlePointerCancel,
|
|
5047
|
-
true
|
|
5048
|
-
);
|
|
5049
|
-
};
|
|
5050
|
-
const releasePointerCapture = (event) => {
|
|
5051
|
-
try {
|
|
5052
|
-
if (layer.hasPointerCapture(event.pointerId)) {
|
|
5053
|
-
layer.releasePointerCapture(event.pointerId);
|
|
5054
|
-
}
|
|
5055
|
-
} catch {
|
|
5056
|
-
}
|
|
5057
|
-
};
|
|
5058
|
-
function isActivePointer(event) {
|
|
5059
|
-
return isDragging && event.pointerId === activePointerId;
|
|
5060
|
-
}
|
|
5061
|
-
const finishAreaSelection = (event) => {
|
|
5062
|
-
if (!isActivePointer(event)) return;
|
|
5063
|
-
event.preventDefault();
|
|
5064
|
-
updateBox(event);
|
|
5065
|
-
releasePointerCapture(event);
|
|
5066
|
-
removeDragListeners();
|
|
5067
|
-
isDragging = false;
|
|
5068
|
-
activePointerId = void 0;
|
|
5069
|
-
if (!selection || selection.width < 8 || selection.height < 8) return;
|
|
5070
|
-
this.config.actions.setSelectingArea(true);
|
|
5071
|
-
this.config.actions.render();
|
|
5072
|
-
void this.config.actions.createAreaDraft(selection);
|
|
5073
|
-
};
|
|
5074
|
-
function handlePointerMove(event) {
|
|
5075
|
-
if (!isActivePointer(event)) return;
|
|
5076
|
-
event.preventDefault();
|
|
5077
|
-
updateBox(event);
|
|
5078
|
-
}
|
|
5079
|
-
const handlePointerUp = (event) => {
|
|
5080
|
-
finishAreaSelection(event);
|
|
5081
|
-
};
|
|
5082
|
-
const handlePointerCancel = (event) => {
|
|
5083
|
-
if (!isActivePointer(event)) return;
|
|
5084
|
-
releasePointerCapture(event);
|
|
5085
|
-
removeDragListeners();
|
|
5086
|
-
isDragging = false;
|
|
5087
|
-
activePointerId = void 0;
|
|
5088
|
-
};
|
|
5516
|
+
layer.className = "dfwr-draft-cancel-layer";
|
|
5517
|
+
layer.setAttribute("aria-hidden", "true");
|
|
5089
5518
|
layer.addEventListener("pointerdown", (event) => {
|
|
5090
5519
|
if (event.button !== 0) return;
|
|
5091
|
-
|
|
5092
|
-
activePointerId = event.pointerId;
|
|
5093
|
-
isDragging = true;
|
|
5094
|
-
try {
|
|
5095
|
-
layer.setPointerCapture(event.pointerId);
|
|
5096
|
-
} catch {
|
|
5097
|
-
}
|
|
5098
|
-
const startPoint = toTargetPointFromHostEvent(
|
|
5099
|
-
event,
|
|
5100
|
-
this.config.getEnvironment()
|
|
5101
|
-
);
|
|
5102
|
-
startX = startPoint.x;
|
|
5103
|
-
startY = startPoint.y;
|
|
5104
|
-
updateBox(event);
|
|
5105
|
-
addDragListeners();
|
|
5520
|
+
this.cancelDraft(event);
|
|
5106
5521
|
});
|
|
5107
|
-
layer.addEventListener("pointermove", handlePointerMove);
|
|
5108
|
-
layer.addEventListener("pointerup", handlePointerUp);
|
|
5109
|
-
layer.addEventListener("pointercancel", handlePointerCancel);
|
|
5110
5522
|
return layer;
|
|
5111
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
|
+
}
|
|
5112
5533
|
};
|
|
5113
5534
|
|
|
5114
5535
|
// src/core/web.review.kit.app.ts
|
|
@@ -5148,6 +5569,7 @@ var WebReviewKitApp = class {
|
|
|
5148
5569
|
this.items = [];
|
|
5149
5570
|
this.draftError = "";
|
|
5150
5571
|
this.isCreatingItem = false;
|
|
5572
|
+
this.isCapturingViewport = false;
|
|
5151
5573
|
this.isSelectingArea = false;
|
|
5152
5574
|
this.handleKeyDown = (event) => {
|
|
5153
5575
|
if (event.key === "Escape" && this.cancelMode()) {
|
|
@@ -5176,10 +5598,11 @@ var WebReviewKitApp = class {
|
|
|
5176
5598
|
isOpen: this.isOpen,
|
|
5177
5599
|
mode: this.mode,
|
|
5178
5600
|
items: this.items,
|
|
5179
|
-
|
|
5601
|
+
domDraft: this.domDraft,
|
|
5180
5602
|
areaDraft: this.areaDraft,
|
|
5181
5603
|
draftError: this.draftError,
|
|
5182
5604
|
isCreatingItem: this.isCreatingItem,
|
|
5605
|
+
isCapturingViewport: this.isCapturingViewport,
|
|
5183
5606
|
isSelectingArea: this.isSelectingArea,
|
|
5184
5607
|
highlightedItemId: this.highlightedItemId
|
|
5185
5608
|
}),
|
|
@@ -5191,13 +5614,9 @@ var WebReviewKitApp = class {
|
|
|
5191
5614
|
restoreItem: (item) => this.restoreItem(item),
|
|
5192
5615
|
removeItem: (itemId) => this.adapter.remove(itemId),
|
|
5193
5616
|
setModeState: (mode) => this.setModeState(mode),
|
|
5194
|
-
clearDrafts: () =>
|
|
5195
|
-
|
|
5196
|
-
this.
|
|
5197
|
-
this.draftError = "";
|
|
5198
|
-
},
|
|
5199
|
-
setNoteDraft: (draft) => {
|
|
5200
|
-
this.noteDraft = draft;
|
|
5617
|
+
clearDrafts: () => this.clearDrafts(),
|
|
5618
|
+
setDomDraft: (draft) => {
|
|
5619
|
+
this.domDraft = draft;
|
|
5201
5620
|
this.draftError = "";
|
|
5202
5621
|
},
|
|
5203
5622
|
setAreaDraft: (draft) => {
|
|
@@ -5208,7 +5627,8 @@ var WebReviewKitApp = class {
|
|
|
5208
5627
|
this.isSelectingArea = isSelectingArea;
|
|
5209
5628
|
},
|
|
5210
5629
|
createItem: (input) => this.createItem(input),
|
|
5211
|
-
|
|
5630
|
+
captureDomDraft: (input) => this.captureDomDraft(input),
|
|
5631
|
+
captureAreaDraft: (input) => this.captureAreaDraft(input),
|
|
5212
5632
|
bindElementDraftToPoint: (point, fields) => this.bindElementDraftToPoint(point, fields),
|
|
5213
5633
|
createAreaDraft: (selection) => this.createAreaDraft(selection)
|
|
5214
5634
|
}
|
|
@@ -5230,6 +5650,7 @@ var WebReviewKitApp = class {
|
|
|
5230
5650
|
}
|
|
5231
5651
|
destroy() {
|
|
5232
5652
|
this.view.clearDraftPreview();
|
|
5653
|
+
this.clearDrafts();
|
|
5233
5654
|
document.removeEventListener("keydown", this.handleKeyDown, true);
|
|
5234
5655
|
window.removeEventListener("scroll", this.handleViewportChange, true);
|
|
5235
5656
|
window.removeEventListener("resize", this.handleViewportChange);
|
|
@@ -5249,8 +5670,7 @@ var WebReviewKitApp = class {
|
|
|
5249
5670
|
close() {
|
|
5250
5671
|
this.isOpen = false;
|
|
5251
5672
|
this.setModeState("idle");
|
|
5252
|
-
this.
|
|
5253
|
-
this.areaDraft = void 0;
|
|
5673
|
+
this.clearDrafts();
|
|
5254
5674
|
this.isSelectingArea = false;
|
|
5255
5675
|
this.render();
|
|
5256
5676
|
}
|
|
@@ -5266,8 +5686,7 @@ var WebReviewKitApp = class {
|
|
|
5266
5686
|
this.isOpen = true;
|
|
5267
5687
|
}
|
|
5268
5688
|
this.setModeState(this.mode === mode ? "idle" : mode);
|
|
5269
|
-
this.
|
|
5270
|
-
this.areaDraft = void 0;
|
|
5689
|
+
this.clearDrafts();
|
|
5271
5690
|
this.render();
|
|
5272
5691
|
}
|
|
5273
5692
|
async startElementReview(element, comment) {
|
|
@@ -5275,8 +5694,7 @@ var WebReviewKitApp = class {
|
|
|
5275
5694
|
this.isOpen = true;
|
|
5276
5695
|
}
|
|
5277
5696
|
this.setModeState("element");
|
|
5278
|
-
this.
|
|
5279
|
-
this.areaDraft = void 0;
|
|
5697
|
+
this.clearDrafts();
|
|
5280
5698
|
this.isSelectingArea = false;
|
|
5281
5699
|
await this.bindElementDraftToElement(element, { comment });
|
|
5282
5700
|
}
|
|
@@ -5301,6 +5719,20 @@ var WebReviewKitApp = class {
|
|
|
5301
5719
|
this.hiddenItemIds = itemIds ? new Set(itemIds) : void 0;
|
|
5302
5720
|
this.updateHiddenItemsStyle();
|
|
5303
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
|
+
}
|
|
5304
5736
|
clearHighlightedItem() {
|
|
5305
5737
|
if (!this.highlightedItemId) return;
|
|
5306
5738
|
this.highlightedItemId = void 0;
|
|
@@ -5336,18 +5768,17 @@ var WebReviewKitApp = class {
|
|
|
5336
5768
|
this.options.onModeChange?.(mode);
|
|
5337
5769
|
}
|
|
5338
5770
|
cancelMode() {
|
|
5339
|
-
if (this.mode === "idle" && !this.
|
|
5771
|
+
if (this.mode === "idle" && !this.domDraft && !this.areaDraft && !this.isSelectingArea) {
|
|
5340
5772
|
return false;
|
|
5341
5773
|
}
|
|
5342
5774
|
this.setModeState("idle");
|
|
5343
|
-
this.
|
|
5344
|
-
this.areaDraft = void 0;
|
|
5775
|
+
this.clearDrafts();
|
|
5345
5776
|
this.isSelectingArea = false;
|
|
5346
5777
|
this.render();
|
|
5347
5778
|
return true;
|
|
5348
5779
|
}
|
|
5349
5780
|
isDraftComposerFocused() {
|
|
5350
|
-
if (!this.
|
|
5781
|
+
if (!this.domDraft && !this.areaDraft) return false;
|
|
5351
5782
|
const composerHost = this.getEnvironment()?.composerHost;
|
|
5352
5783
|
const activeElement = composerHost?.ownerDocument.activeElement;
|
|
5353
5784
|
return Boolean(
|
|
@@ -5383,6 +5814,8 @@ var WebReviewKitApp = class {
|
|
|
5383
5814
|
};
|
|
5384
5815
|
const overlayRect = target.getOverlayRect?.() ?? rect;
|
|
5385
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;
|
|
5386
5819
|
return {
|
|
5387
5820
|
window: target.window,
|
|
5388
5821
|
document: target.document,
|
|
@@ -5392,13 +5825,16 @@ var WebReviewKitApp = class {
|
|
|
5392
5825
|
width: rect.width,
|
|
5393
5826
|
height: rect.height
|
|
5394
5827
|
},
|
|
5828
|
+
scaleX,
|
|
5829
|
+
scaleY,
|
|
5395
5830
|
overlayRect: {
|
|
5396
5831
|
left: overlayRect.left,
|
|
5397
5832
|
top: overlayRect.top,
|
|
5398
5833
|
width: overlayRect.width,
|
|
5399
5834
|
height: overlayRect.height
|
|
5400
5835
|
},
|
|
5401
|
-
composerHost
|
|
5836
|
+
composerHost,
|
|
5837
|
+
captureViewport: target.captureViewport
|
|
5402
5838
|
};
|
|
5403
5839
|
} catch {
|
|
5404
5840
|
return void 0;
|
|
@@ -5421,32 +5857,6 @@ var WebReviewKitApp = class {
|
|
|
5421
5857
|
if (!this.shadow) return;
|
|
5422
5858
|
this.view.render(this.shadow, this.createHiddenItemsStyleElement());
|
|
5423
5859
|
}
|
|
5424
|
-
async bindNoteDraftToPoint(point, fields = {}) {
|
|
5425
|
-
const environment = this.getEnvironment();
|
|
5426
|
-
if (!environment) return;
|
|
5427
|
-
const viewport = getViewportSize(environment);
|
|
5428
|
-
const nextPoint = clampPoint(point, environment);
|
|
5429
|
-
const draft = await this.withOverlayHidden(() => {
|
|
5430
|
-
const selection = getPointSelection(nextPoint);
|
|
5431
|
-
const anchor = getDomAnchor(
|
|
5432
|
-
selection,
|
|
5433
|
-
this.options.anchors?.attribute,
|
|
5434
|
-
environment
|
|
5435
|
-
);
|
|
5436
|
-
const marker = {
|
|
5437
|
-
viewport: roundPoint(nextPoint),
|
|
5438
|
-
relative: anchor ? getRelativePoint(nextPoint, anchor, environment) : void 0
|
|
5439
|
-
};
|
|
5440
|
-
return {
|
|
5441
|
-
viewport,
|
|
5442
|
-
anchor,
|
|
5443
|
-
marker,
|
|
5444
|
-
...fields
|
|
5445
|
-
};
|
|
5446
|
-
});
|
|
5447
|
-
this.noteDraft = draft;
|
|
5448
|
-
this.render();
|
|
5449
|
-
}
|
|
5450
5860
|
async bindElementDraftToPoint(point, fields = {}) {
|
|
5451
5861
|
const environment = this.getEnvironment();
|
|
5452
5862
|
if (!environment) return;
|
|
@@ -5495,7 +5905,7 @@ var WebReviewKitApp = class {
|
|
|
5495
5905
|
previewElement
|
|
5496
5906
|
};
|
|
5497
5907
|
});
|
|
5498
|
-
this.
|
|
5908
|
+
this.domDraft = draft;
|
|
5499
5909
|
this.render();
|
|
5500
5910
|
}
|
|
5501
5911
|
async bindElementDraftToElement(element, fields = {}) {
|
|
@@ -5536,7 +5946,7 @@ var WebReviewKitApp = class {
|
|
|
5536
5946
|
};
|
|
5537
5947
|
});
|
|
5538
5948
|
if (!draft) return;
|
|
5539
|
-
this.
|
|
5949
|
+
this.domDraft = draft;
|
|
5540
5950
|
this.render();
|
|
5541
5951
|
}
|
|
5542
5952
|
async createAreaDraft(selection) {
|
|
@@ -5549,16 +5959,27 @@ var WebReviewKitApp = class {
|
|
|
5549
5959
|
try {
|
|
5550
5960
|
const viewport = getViewportSize(environment);
|
|
5551
5961
|
this.areaDraft = await this.withOverlayHidden(() => {
|
|
5552
|
-
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(
|
|
5553
5972
|
selection,
|
|
5554
|
-
|
|
5973
|
+
anchor,
|
|
5555
5974
|
environment
|
|
5556
5975
|
);
|
|
5557
5976
|
const reviewSelection = {
|
|
5558
|
-
viewport: toPublicSelection(selection)
|
|
5977
|
+
viewport: toPublicSelection(selection),
|
|
5978
|
+
relative: anchor ? getRelativeSelection(selection, anchor, environment) : void 0
|
|
5559
5979
|
};
|
|
5560
5980
|
return {
|
|
5561
5981
|
viewport,
|
|
5982
|
+
anchor,
|
|
5562
5983
|
marker,
|
|
5563
5984
|
selection: reviewSelection
|
|
5564
5985
|
};
|
|
@@ -5579,6 +6000,127 @@ var WebReviewKitApp = class {
|
|
|
5579
6000
|
this.root.style.display = previousDisplay;
|
|
5580
6001
|
}
|
|
5581
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
|
+
}
|
|
5582
6124
|
async createItem(input) {
|
|
5583
6125
|
const environment = this.getEnvironment();
|
|
5584
6126
|
if (!environment || this.isCreatingItem) return;
|
|
@@ -5622,24 +6164,61 @@ var WebReviewKitApp = class {
|
|
|
5622
6164
|
this.isCreatingItem = true;
|
|
5623
6165
|
this.render();
|
|
5624
6166
|
try {
|
|
5625
|
-
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);
|
|
5626
6173
|
this.setModeState("idle");
|
|
5627
|
-
this.
|
|
5628
|
-
this.areaDraft = void 0;
|
|
6174
|
+
this.clearDrafts();
|
|
5629
6175
|
this.highlightItem(createdItem.id);
|
|
5630
6176
|
await this.reload();
|
|
5631
6177
|
await this.options.onCreateItem?.(createdItem);
|
|
5632
6178
|
} catch (error) {
|
|
5633
|
-
this.draftError =
|
|
6179
|
+
this.draftError = this.getCreateItemErrorMessage(
|
|
6180
|
+
error,
|
|
6181
|
+
Boolean(input.attachments?.length)
|
|
6182
|
+
);
|
|
5634
6183
|
} finally {
|
|
5635
6184
|
this.isCreatingItem = false;
|
|
5636
6185
|
this.render();
|
|
5637
6186
|
}
|
|
5638
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
|
+
}
|
|
5639
6219
|
async restoreItem(item) {
|
|
5640
6220
|
this.setModeState("idle");
|
|
5641
|
-
this.
|
|
5642
|
-
this.areaDraft = void 0;
|
|
6221
|
+
this.clearDrafts();
|
|
5643
6222
|
if (this.options.onRestoreItem) {
|
|
5644
6223
|
await this.options.onRestoreItem(item);
|
|
5645
6224
|
return;
|
|
@@ -5698,6 +6277,7 @@ function createNoopController() {
|
|
|
5698
6277
|
REVIEW_WORKFLOW_STATUS_OPTIONS,
|
|
5699
6278
|
ReviewFigmaTokenError,
|
|
5700
6279
|
collectReviewFigmaReleaseSnapshot,
|
|
6280
|
+
createEndpointReviewFigmaImageStore,
|
|
5701
6281
|
createRemoteReviewFigmaImageStore,
|
|
5702
6282
|
createReviewFigmaFrameUrl,
|
|
5703
6283
|
createReviewFigmaImageStoreClient,
|