issue-sticker 0.10.0 → 0.12.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/CHANGELOG.md +60 -0
- package/dist/_commonjsHelpers-DWwsNxpa.js +8 -0
- package/dist/{externalUserPayload-DNGAp9bP.js → externalUserPayload-DWcXyz2U.js} +2538 -473
- package/dist/index-DA6SoSp7.js +3009 -0
- package/dist/issue-sticker-headless-react.js +3 -3
- package/dist/issue-sticker-headless.js +136 -74
- package/dist/issue-sticker-react.js +1489 -642
- package/dist/{three-_bdgDAmI.js → three-B228j4M6.js} +11 -11
- package/dist/{tooltip-UijEbdDX.js → tooltip-CT7e9xbU.js} +1 -1
- package/headless.d.ts +67 -0
- package/package.json +2 -1
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { t as getCanvasSource, v as onCanvasSourceRegistered, C as CANVAS_INTERACTION_TAG, w as gzip_1, R as REPLAY_EVENT_TYPE, x as record, a as cn, p as useCallbackRef$1,
|
|
1
|
+
import { t as getCanvasSource, v as onCanvasSourceRegistered, C as CANVAS_INTERACTION_TAG, w as gzip_1, R as REPLAY_EVENT_TYPE, x as record, a as cn, p as useCallbackRef$1, k as useComposedRefs, h as Primitive, P as Presence, l as Portal$1, i as composeEventHandlers, m as createContextScope, n as createSlot, D as DismissableLayer, y as Content, z as createPopperScope, A as Anchor, f as useControllableState, B as Root2$1, g as useId, E as Arrow, d as usePortalContainer, e as OVERLAY_LAYER, s as stopEscapePropagation, o as useLayoutEffect2, V as VISUALLY_HIDDEN_STYLES, c as createLucideIcon, F as clsx, S as Slot$2, O as OVERLAY_LAYER_INDEX, M as MousePointer2 } from "./three-B228j4M6.js";
|
|
2
2
|
import { jsx, jsxs, Fragment } from "react/jsx-runtime";
|
|
3
3
|
import * as React from "react";
|
|
4
|
-
import React__default, { useState,
|
|
4
|
+
import React__default, { useState, useRef, useEffect, useCallback, createContext, useContext, useLayoutEffect, useMemo } from "react";
|
|
5
5
|
import * as ReactDOM from "react-dom";
|
|
6
6
|
const DEFAULT_MAX_DURATION_MS = 12e4;
|
|
7
7
|
const MIME_TYPE_CANDIDATES = ["video/webm;codecs=vp9", "video/webm"];
|
|
@@ -83,7 +83,8 @@ class ScreenRecordingController {
|
|
|
83
83
|
const mimeType = chooseMimeType(this.isTypeSupported);
|
|
84
84
|
const mediaRecorder = this.createMediaRecorder(stream, {
|
|
85
85
|
mimeType,
|
|
86
|
-
|
|
86
|
+
// 실측(VP9, 2배율, 계속 스크롤): 1.5Mbps 에서 12px 글자 손상 없음, 1.0Mbps 는 잔상.
|
|
87
|
+
videoBitsPerSecond: 15e5
|
|
87
88
|
});
|
|
88
89
|
this.mediaRecorder = mediaRecorder;
|
|
89
90
|
mediaRecorder.ondataavailable = (event) => {
|
|
@@ -894,7 +895,7 @@ function settleFrame() {
|
|
|
894
895
|
async function resolveNodeId(canvas) {
|
|
895
896
|
var _a;
|
|
896
897
|
try {
|
|
897
|
-
const { record: record2 } = await import("./three-
|
|
898
|
+
const { record: record2 } = await import("./three-B228j4M6.js").then((n) => n.I);
|
|
898
899
|
const id = (_a = record2.mirror) == null ? void 0 : _a.getId(canvas);
|
|
899
900
|
return typeof id === "number" && id > 0 ? id : null;
|
|
900
901
|
} catch {
|
|
@@ -913,7 +914,7 @@ function startCanvasInteractionTracking(options = {}) {
|
|
|
913
914
|
const lastSnapshot = /* @__PURE__ */ new WeakMap();
|
|
914
915
|
let pending = null;
|
|
915
916
|
const emit2 = options.emit ?? ((tag, payload) => {
|
|
916
|
-
void import("./three-
|
|
917
|
+
void import("./three-B228j4M6.js").then((n) => n.I).then(({ addCustomEvent }) => addCustomEvent(tag, payload));
|
|
917
918
|
});
|
|
918
919
|
const pointerOf = (started, rect) => [
|
|
919
920
|
(started.x - rect.left) / rect.width * 2 - 1,
|
|
@@ -1184,10 +1185,6 @@ function dropEventsAfter(segments, cutoff) {
|
|
|
1184
1185
|
return segments.map((s2) => ({ events: s2.events.filter((e2) => e2.timestamp <= cutoff) })).filter((s2) => s2.events.length > 0);
|
|
1185
1186
|
}
|
|
1186
1187
|
const REPLAY_SNAPSHOT_MAX_BYTES = 16 * 1024 * 1024;
|
|
1187
|
-
async function sha256Hex$1(bytes) {
|
|
1188
|
-
const digest = await crypto.subtle.digest("SHA-256", bytes.buffer);
|
|
1189
|
-
return Array.from(new Uint8Array(digest)).map((b) => b.toString(16).padStart(2, "0")).join("");
|
|
1190
|
-
}
|
|
1191
1188
|
async function serializeSnapshot(input) {
|
|
1192
1189
|
const redact = input.redactEvents ?? ((e2) => e2);
|
|
1193
1190
|
const maxBytes = input.maxBytes ?? REPLAY_SNAPSHOT_MAX_BYTES;
|
|
@@ -1201,10 +1198,8 @@ async function serializeSnapshot(input) {
|
|
|
1201
1198
|
segments = segments.slice(1);
|
|
1202
1199
|
({ events, payload } = compress2(segments));
|
|
1203
1200
|
}
|
|
1204
|
-
const checksumSha256 = await sha256Hex$1(payload);
|
|
1205
1201
|
return {
|
|
1206
1202
|
payload,
|
|
1207
|
-
checksumSha256,
|
|
1208
1203
|
windowSeconds: spanSeconds(segments),
|
|
1209
1204
|
eventCount: events.length,
|
|
1210
1205
|
sizeBytes: payload.byteLength,
|
|
@@ -2065,8 +2060,8 @@ class WebSocketCollector {
|
|
|
2065
2060
|
}
|
|
2066
2061
|
const LOG_BACKUP_SESSION_KEY = "__issueStickerLogSession";
|
|
2067
2062
|
const DB_NAME$1 = "issue-sticker-logs";
|
|
2068
|
-
const DB_VERSION$
|
|
2069
|
-
const STORE_NAME$
|
|
2063
|
+
const DB_VERSION$2 = 1;
|
|
2064
|
+
const STORE_NAME$2 = "sessions";
|
|
2070
2065
|
const LOG_BACKUP_TTL_MS = 24 * 60 * 60 * 1e3;
|
|
2071
2066
|
const LOG_BACKUP_BUDGET_BYTES = 8 * 1024 * 1024;
|
|
2072
2067
|
function jsonLength(value) {
|
|
@@ -2147,12 +2142,12 @@ function requestToPromise$1(request) {
|
|
|
2147
2142
|
request.onerror = () => reject(request.error);
|
|
2148
2143
|
});
|
|
2149
2144
|
}
|
|
2150
|
-
function openDatabase$
|
|
2145
|
+
function openDatabase$2() {
|
|
2151
2146
|
return new Promise((resolve, reject) => {
|
|
2152
|
-
const request = indexedDB.open(DB_NAME$1, DB_VERSION$
|
|
2147
|
+
const request = indexedDB.open(DB_NAME$1, DB_VERSION$2);
|
|
2153
2148
|
request.onupgradeneeded = () => {
|
|
2154
|
-
if (!request.result.objectStoreNames.contains(STORE_NAME$
|
|
2155
|
-
request.result.createObjectStore(STORE_NAME$
|
|
2149
|
+
if (!request.result.objectStoreNames.contains(STORE_NAME$2)) {
|
|
2150
|
+
request.result.createObjectStore(STORE_NAME$2, { keyPath: "sessionId" });
|
|
2156
2151
|
}
|
|
2157
2152
|
};
|
|
2158
2153
|
request.onsuccess = () => {
|
|
@@ -2167,10 +2162,10 @@ function openDatabase$1() {
|
|
|
2167
2162
|
function createIndexedDbLogBackup() {
|
|
2168
2163
|
return {
|
|
2169
2164
|
async load(sessionId) {
|
|
2170
|
-
const db = await openDatabase$
|
|
2165
|
+
const db = await openDatabase$2();
|
|
2171
2166
|
try {
|
|
2172
2167
|
const record2 = await requestToPromise$1(
|
|
2173
|
-
db.transaction(STORE_NAME$
|
|
2168
|
+
db.transaction(STORE_NAME$2, "readonly").objectStore(STORE_NAME$2).get(sessionId)
|
|
2174
2169
|
);
|
|
2175
2170
|
if (!record2) return null;
|
|
2176
2171
|
if (record2.expiresAt <= Date.now()) return null;
|
|
@@ -2180,7 +2175,7 @@ function createIndexedDbLogBackup() {
|
|
|
2180
2175
|
}
|
|
2181
2176
|
},
|
|
2182
2177
|
async save(sessionId, payload) {
|
|
2183
|
-
const db = await openDatabase$
|
|
2178
|
+
const db = await openDatabase$2();
|
|
2184
2179
|
try {
|
|
2185
2180
|
const record2 = {
|
|
2186
2181
|
sessionId,
|
|
@@ -2188,16 +2183,16 @@ function createIndexedDbLogBackup() {
|
|
|
2188
2183
|
expiresAt: Date.now() + LOG_BACKUP_TTL_MS
|
|
2189
2184
|
};
|
|
2190
2185
|
await requestToPromise$1(
|
|
2191
|
-
db.transaction(STORE_NAME$
|
|
2186
|
+
db.transaction(STORE_NAME$2, "readwrite").objectStore(STORE_NAME$2).put(record2)
|
|
2192
2187
|
);
|
|
2193
2188
|
} finally {
|
|
2194
2189
|
db.close();
|
|
2195
2190
|
}
|
|
2196
2191
|
},
|
|
2197
2192
|
async prune() {
|
|
2198
|
-
const db = await openDatabase$
|
|
2193
|
+
const db = await openDatabase$2();
|
|
2199
2194
|
try {
|
|
2200
|
-
const store = db.transaction(STORE_NAME$
|
|
2195
|
+
const store = db.transaction(STORE_NAME$2, "readwrite").objectStore(STORE_NAME$2);
|
|
2201
2196
|
const records = await requestToPromise$1(store.getAll());
|
|
2202
2197
|
const now = Date.now();
|
|
2203
2198
|
const expired = records.filter((record2) => record2.expiresAt <= now);
|
|
@@ -2210,10 +2205,10 @@ function createIndexedDbLogBackup() {
|
|
|
2210
2205
|
}
|
|
2211
2206
|
},
|
|
2212
2207
|
async clear(sessionId) {
|
|
2213
|
-
const db = await openDatabase$
|
|
2208
|
+
const db = await openDatabase$2();
|
|
2214
2209
|
try {
|
|
2215
2210
|
await requestToPromise$1(
|
|
2216
|
-
db.transaction(STORE_NAME$
|
|
2211
|
+
db.transaction(STORE_NAME$2, "readwrite").objectStore(STORE_NAME$2).delete(sessionId)
|
|
2217
2212
|
);
|
|
2218
2213
|
} finally {
|
|
2219
2214
|
db.close();
|
|
@@ -2283,8 +2278,8 @@ function createSafeLogBackup(store) {
|
|
|
2283
2278
|
};
|
|
2284
2279
|
}
|
|
2285
2280
|
const DB_NAME = "issue-sticker-replay";
|
|
2286
|
-
const DB_VERSION = 1;
|
|
2287
|
-
const STORE_NAME = "segments";
|
|
2281
|
+
const DB_VERSION$1 = 1;
|
|
2282
|
+
const STORE_NAME$1 = "segments";
|
|
2288
2283
|
const REPLAY_BACKUP_TTL_MS = 24 * 60 * 60 * 1e3;
|
|
2289
2284
|
const REPLAY_BACKUP_SIZE_BUDGET = 32 * 1024 * 1024;
|
|
2290
2285
|
const eventSizes = /* @__PURE__ */ new WeakMap();
|
|
@@ -2336,12 +2331,12 @@ function requestToPromise(request) {
|
|
|
2336
2331
|
request.onerror = () => reject(request.error);
|
|
2337
2332
|
});
|
|
2338
2333
|
}
|
|
2339
|
-
function openDatabase() {
|
|
2334
|
+
function openDatabase$1() {
|
|
2340
2335
|
return new Promise((resolve, reject) => {
|
|
2341
|
-
const request = indexedDB.open(DB_NAME, DB_VERSION);
|
|
2336
|
+
const request = indexedDB.open(DB_NAME, DB_VERSION$1);
|
|
2342
2337
|
request.onupgradeneeded = () => {
|
|
2343
|
-
if (!request.result.objectStoreNames.contains(STORE_NAME)) {
|
|
2344
|
-
request.result.createObjectStore(STORE_NAME, { keyPath: "sessionId" });
|
|
2338
|
+
if (!request.result.objectStoreNames.contains(STORE_NAME$1)) {
|
|
2339
|
+
request.result.createObjectStore(STORE_NAME$1, { keyPath: "sessionId" });
|
|
2345
2340
|
}
|
|
2346
2341
|
};
|
|
2347
2342
|
request.onsuccess = () => {
|
|
@@ -2356,10 +2351,10 @@ function openDatabase() {
|
|
|
2356
2351
|
function createIndexedDbReplayBackup() {
|
|
2357
2352
|
return {
|
|
2358
2353
|
async load(sessionId) {
|
|
2359
|
-
const db = await openDatabase();
|
|
2354
|
+
const db = await openDatabase$1();
|
|
2360
2355
|
try {
|
|
2361
2356
|
const record2 = await requestToPromise(
|
|
2362
|
-
db.transaction(STORE_NAME, "readonly").objectStore(STORE_NAME).get(sessionId)
|
|
2357
|
+
db.transaction(STORE_NAME$1, "readonly").objectStore(STORE_NAME$1).get(sessionId)
|
|
2363
2358
|
);
|
|
2364
2359
|
if (!record2) return null;
|
|
2365
2360
|
if (record2.expiresAt <= Date.now()) return null;
|
|
@@ -2369,7 +2364,7 @@ function createIndexedDbReplayBackup() {
|
|
|
2369
2364
|
}
|
|
2370
2365
|
},
|
|
2371
2366
|
async save(sessionId, segments) {
|
|
2372
|
-
const db = await openDatabase();
|
|
2367
|
+
const db = await openDatabase$1();
|
|
2373
2368
|
try {
|
|
2374
2369
|
const record2 = {
|
|
2375
2370
|
sessionId,
|
|
@@ -2377,16 +2372,16 @@ function createIndexedDbReplayBackup() {
|
|
|
2377
2372
|
expiresAt: Date.now() + REPLAY_BACKUP_TTL_MS
|
|
2378
2373
|
};
|
|
2379
2374
|
await requestToPromise(
|
|
2380
|
-
db.transaction(STORE_NAME, "readwrite").objectStore(STORE_NAME).put(record2)
|
|
2375
|
+
db.transaction(STORE_NAME$1, "readwrite").objectStore(STORE_NAME$1).put(record2)
|
|
2381
2376
|
);
|
|
2382
2377
|
} finally {
|
|
2383
2378
|
db.close();
|
|
2384
2379
|
}
|
|
2385
2380
|
},
|
|
2386
2381
|
async prune() {
|
|
2387
|
-
const db = await openDatabase();
|
|
2382
|
+
const db = await openDatabase$1();
|
|
2388
2383
|
try {
|
|
2389
|
-
const store = db.transaction(STORE_NAME, "readwrite").objectStore(STORE_NAME);
|
|
2384
|
+
const store = db.transaction(STORE_NAME$1, "readwrite").objectStore(STORE_NAME$1);
|
|
2390
2385
|
const records = await requestToPromise(store.getAll());
|
|
2391
2386
|
const now = Date.now();
|
|
2392
2387
|
const expired = records.filter((record2) => record2.expiresAt <= now);
|
|
@@ -2399,10 +2394,10 @@ function createIndexedDbReplayBackup() {
|
|
|
2399
2394
|
}
|
|
2400
2395
|
},
|
|
2401
2396
|
async clear(sessionId) {
|
|
2402
|
-
const db = await openDatabase();
|
|
2397
|
+
const db = await openDatabase$1();
|
|
2403
2398
|
try {
|
|
2404
2399
|
await requestToPromise(
|
|
2405
|
-
db.transaction(STORE_NAME, "readwrite").objectStore(STORE_NAME).delete(sessionId)
|
|
2400
|
+
db.transaction(STORE_NAME$1, "readwrite").objectStore(STORE_NAME$1).delete(sessionId)
|
|
2406
2401
|
);
|
|
2407
2402
|
} finally {
|
|
2408
2403
|
db.close();
|
|
@@ -2546,6 +2541,26 @@ function createReplaySession(options = {}) {
|
|
|
2546
2541
|
}
|
|
2547
2542
|
};
|
|
2548
2543
|
}
|
|
2544
|
+
function readReleaseMarker() {
|
|
2545
|
+
try {
|
|
2546
|
+
const g = globalThis.__ISSUE_STICKER_RELEASE__;
|
|
2547
|
+
if (g && typeof g.id === "string" && g.id.length > 0) {
|
|
2548
|
+
return {
|
|
2549
|
+
release: g.id,
|
|
2550
|
+
release_source: typeof g.source === "string" && g.source.length > 0 ? g.source : "global"
|
|
2551
|
+
};
|
|
2552
|
+
}
|
|
2553
|
+
if (typeof document !== "undefined") {
|
|
2554
|
+
const meta = document.querySelector('meta[name="issue-sticker:release"]');
|
|
2555
|
+
const content = meta == null ? void 0 : meta.getAttribute("content");
|
|
2556
|
+
if (content && content.length > 0) {
|
|
2557
|
+
return { release: content, release_source: "meta" };
|
|
2558
|
+
}
|
|
2559
|
+
}
|
|
2560
|
+
} catch {
|
|
2561
|
+
}
|
|
2562
|
+
return null;
|
|
2563
|
+
}
|
|
2549
2564
|
function createInitialSnapshot() {
|
|
2550
2565
|
return {
|
|
2551
2566
|
available: false,
|
|
@@ -2651,6 +2666,24 @@ class SessionReplayCaptureController {
|
|
|
2651
2666
|
this.listeners.forEach((listener) => listener(this.snapshot));
|
|
2652
2667
|
}
|
|
2653
2668
|
}
|
|
2669
|
+
function dataUrlToBlob(dataUrl) {
|
|
2670
|
+
var _a;
|
|
2671
|
+
const comma = dataUrl.indexOf(",");
|
|
2672
|
+
const header = dataUrl.slice(0, comma);
|
|
2673
|
+
const mime = ((_a = /^data:([^;,]+)/.exec(header)) == null ? void 0 : _a[1]) ?? "application/octet-stream";
|
|
2674
|
+
const binary = atob(dataUrl.slice(comma + 1));
|
|
2675
|
+
const bytes = new Uint8Array(binary.length);
|
|
2676
|
+
for (let i2 = 0; i2 < binary.length; i2 += 1) bytes[i2] = binary.charCodeAt(i2);
|
|
2677
|
+
return new Blob([bytes], { type: mime });
|
|
2678
|
+
}
|
|
2679
|
+
function blobToDataUrl(blob) {
|
|
2680
|
+
return new Promise((resolve, reject) => {
|
|
2681
|
+
const reader = new FileReader();
|
|
2682
|
+
reader.onload = () => resolve(String(reader.result));
|
|
2683
|
+
reader.onerror = () => reject(reader.error ?? new Error("스크린샷을 dataUrl 로 변환하지 못했습니다."));
|
|
2684
|
+
reader.readAsDataURL(blob);
|
|
2685
|
+
});
|
|
2686
|
+
}
|
|
2654
2687
|
const SCREENSHOT_DATA_URL_QUALITY = 0.95;
|
|
2655
2688
|
let nextCaptureRunId = 0;
|
|
2656
2689
|
function createCaptureAttributeNames() {
|
|
@@ -2861,14 +2894,6 @@ function createModernStickySnapshot(root, attributeNames) {
|
|
|
2861
2894
|
}
|
|
2862
2895
|
};
|
|
2863
2896
|
}
|
|
2864
|
-
function blobToDataUrl(blob) {
|
|
2865
|
-
return new Promise((resolve, reject) => {
|
|
2866
|
-
const reader = new FileReader();
|
|
2867
|
-
reader.onload = () => resolve(String(reader.result));
|
|
2868
|
-
reader.onerror = () => reject(reader.error ?? new Error("스크린샷을 dataUrl 로 변환하지 못했습니다."));
|
|
2869
|
-
reader.readAsDataURL(blob);
|
|
2870
|
-
});
|
|
2871
|
-
}
|
|
2872
2897
|
function captureOrigin(element) {
|
|
2873
2898
|
if (element === document.documentElement || element === document.body) {
|
|
2874
2899
|
return { x: 0, y: 0 };
|
|
@@ -2926,162 +2951,1023 @@ async function captureWithModernScreenshot(options) {
|
|
|
2926
2951
|
stickySnapshot.cleanup();
|
|
2927
2952
|
}
|
|
2928
2953
|
}
|
|
2929
|
-
const
|
|
2930
|
-
|
|
2931
|
-
|
|
2932
|
-
|
|
2933
|
-
|
|
2934
|
-
|
|
2954
|
+
const ELEMENT_CROP_PADDING_PX = 8;
|
|
2955
|
+
const ELEMENT_CROP_QUALITY = 0.95;
|
|
2956
|
+
function measureElementCropTarget(element) {
|
|
2957
|
+
var _a;
|
|
2958
|
+
if (element.ownerDocument !== document) return null;
|
|
2959
|
+
if (element === document.documentElement || element === document.body) return null;
|
|
2960
|
+
if ((((_a = window.visualViewport) == null ? void 0 : _a.scale) ?? 1) !== 1) return null;
|
|
2961
|
+
const rect = element.getBoundingClientRect();
|
|
2962
|
+
return {
|
|
2963
|
+
rect: { left: rect.left, top: rect.top, right: rect.right, bottom: rect.bottom },
|
|
2964
|
+
viewport: { width: window.innerWidth, height: window.innerHeight }
|
|
2965
|
+
};
|
|
2935
2966
|
}
|
|
2936
|
-
|
|
2937
|
-
|
|
2938
|
-
|
|
2939
|
-
|
|
2940
|
-
|
|
2967
|
+
function computeElementCropRect(rect, viewport, image, padding = ELEMENT_CROP_PADDING_PX) {
|
|
2968
|
+
if (viewport.width <= 0 || viewport.height <= 0 || image.width <= 0 || image.height <= 0) {
|
|
2969
|
+
return null;
|
|
2970
|
+
}
|
|
2971
|
+
const visibleWidth = Math.min(rect.right, viewport.width) - Math.max(rect.left, 0);
|
|
2972
|
+
const visibleHeight = Math.min(rect.bottom, viewport.height) - Math.max(rect.top, 0);
|
|
2973
|
+
if (visibleWidth < 1 || visibleHeight < 1) return null;
|
|
2974
|
+
const left = Math.max(rect.left - padding, 0);
|
|
2975
|
+
const top = Math.max(rect.top - padding, 0);
|
|
2976
|
+
const right = Math.min(rect.right + padding, viewport.width);
|
|
2977
|
+
const bottom = Math.min(rect.bottom + padding, viewport.height);
|
|
2978
|
+
const kx = image.width / viewport.width;
|
|
2979
|
+
const ky = image.height / viewport.height;
|
|
2980
|
+
const sx = Math.round(left * kx);
|
|
2981
|
+
const sy = Math.round(top * ky);
|
|
2982
|
+
const sw = Math.min(Math.round(right * kx), image.width) - sx;
|
|
2983
|
+
const sh = Math.min(Math.round(bottom * ky), image.height) - sy;
|
|
2984
|
+
return sw >= 1 && sh >= 1 ? { sx, sy, sw, sh } : null;
|
|
2985
|
+
}
|
|
2986
|
+
async function cropElementFromViewportCapture(viewportDataUrl, target) {
|
|
2987
|
+
const bitmap = await createImageBitmap(dataUrlToBlob(viewportDataUrl));
|
|
2988
|
+
try {
|
|
2989
|
+
const crop = computeElementCropRect(target.rect, target.viewport, {
|
|
2990
|
+
width: bitmap.width,
|
|
2991
|
+
height: bitmap.height
|
|
2992
|
+
});
|
|
2993
|
+
if (!crop) return void 0;
|
|
2994
|
+
const canvas = new OffscreenCanvas(crop.sw, crop.sh);
|
|
2995
|
+
const context = canvas.getContext("2d");
|
|
2996
|
+
if (!context) return void 0;
|
|
2997
|
+
context.drawImage(bitmap, crop.sx, crop.sy, crop.sw, crop.sh, 0, 0, crop.sw, crop.sh);
|
|
2998
|
+
return await blobToDataUrl(
|
|
2999
|
+
await canvas.convertToBlob({ type: "image/webp", quality: ELEMENT_CROP_QUALITY })
|
|
3000
|
+
);
|
|
3001
|
+
} finally {
|
|
3002
|
+
bitmap.close();
|
|
2941
3003
|
}
|
|
2942
3004
|
}
|
|
2943
|
-
function
|
|
2944
|
-
|
|
3005
|
+
async function captureViewportWithElementCrop({
|
|
3006
|
+
element,
|
|
3007
|
+
excludeSelectors = []
|
|
3008
|
+
}) {
|
|
3009
|
+
const target = measureElementCropTarget(element);
|
|
3010
|
+
const fullDataUrl = await captureWithModernScreenshot({
|
|
3011
|
+
element: document.documentElement,
|
|
3012
|
+
excludeSelectors,
|
|
3013
|
+
width: window.innerWidth,
|
|
3014
|
+
height: window.innerHeight
|
|
3015
|
+
}).catch(() => void 0);
|
|
3016
|
+
if (!fullDataUrl || !target) return { fullDataUrl };
|
|
3017
|
+
const elementDataUrl = await cropElementFromViewportCapture(fullDataUrl, target).catch(
|
|
3018
|
+
() => void 0
|
|
3019
|
+
);
|
|
3020
|
+
return { fullDataUrl, elementDataUrl };
|
|
2945
3021
|
}
|
|
2946
|
-
|
|
2947
|
-
|
|
2948
|
-
|
|
2949
|
-
|
|
2950
|
-
const log = console[method];
|
|
2951
|
-
log(ISSUE_STICKER_INTERNAL_LOG_PREFIX, `[${scope}]`, ...args);
|
|
3022
|
+
const UPLOAD_MAX_AGE_MS = 24 * 60 * 60 * 1e3;
|
|
3023
|
+
const TUS_CHUNK_SIZE = 6 * 1024 * 1024;
|
|
3024
|
+
const TUS_RETRY_DELAYS = [0, 3e3, 5e3, 1e4, 2e4];
|
|
3025
|
+
class PermanentUploadError extends Error {
|
|
2952
3026
|
}
|
|
2953
|
-
|
|
2954
|
-
|
|
2955
|
-
|
|
2956
|
-
|
|
2957
|
-
|
|
2958
|
-
|
|
2959
|
-
|
|
2960
|
-
|
|
2961
|
-
|
|
2962
|
-
|
|
2963
|
-
|
|
2964
|
-
|
|
2965
|
-
|
|
2966
|
-
|
|
2967
|
-
|
|
2968
|
-
|
|
2969
|
-
|
|
2970
|
-
|
|
2971
|
-
|
|
2972
|
-
|
|
2973
|
-
|
|
2974
|
-
|
|
2975
|
-
this.name = "DirectUploadError";
|
|
3027
|
+
class UploadStoppedError extends Error {
|
|
3028
|
+
}
|
|
3029
|
+
const RETRYABLE_CLIENT_STATUSES = /* @__PURE__ */ new Set([401, 408, 423, 429]);
|
|
3030
|
+
function readStatus(error) {
|
|
3031
|
+
if (!error || typeof error !== "object") return void 0;
|
|
3032
|
+
const status = error.status;
|
|
3033
|
+
if (typeof status === "number") return status;
|
|
3034
|
+
const response = error.originalResponse;
|
|
3035
|
+
return typeof (response == null ? void 0 : response.getStatus) === "function" ? response.getStatus() : void 0;
|
|
3036
|
+
}
|
|
3037
|
+
function isPermanentStatus(status) {
|
|
3038
|
+
return status !== void 0 && status >= 400 && status < 500 && !RETRYABLE_CLIENT_STATUSES.has(status);
|
|
3039
|
+
}
|
|
3040
|
+
function toApiFailure(error) {
|
|
3041
|
+
return isPermanentStatus(readStatus(error)) ? new PermanentUploadError(error instanceof Error ? error.message : String(error)) : error;
|
|
3042
|
+
}
|
|
3043
|
+
const SIGNATURE_STATUSES = /* @__PURE__ */ new Set([400, 401, 403]);
|
|
3044
|
+
function toTusFailure(error) {
|
|
3045
|
+
if (error instanceof UploadStoppedError) return error;
|
|
3046
|
+
const status = readStatus(error);
|
|
3047
|
+
if (status !== void 0 && SIGNATURE_STATUSES.has(status)) {
|
|
3048
|
+
return new PermanentUploadError(`tus ${status}`);
|
|
2976
3049
|
}
|
|
3050
|
+
if (status === 409) return error;
|
|
3051
|
+
return isPermanentStatus(status) ? new PermanentUploadError(`tus ${status}`) : error;
|
|
2977
3052
|
}
|
|
2978
|
-
|
|
2979
|
-
|
|
2980
|
-
return Array.from(bytes).map((byte) => byte.toString(16).padStart(2, "0")).join("");
|
|
3053
|
+
function stripMimeParameters(mimeType) {
|
|
3054
|
+
return mimeType.split(";", 1)[0].trim().toLowerCase();
|
|
2981
3055
|
}
|
|
2982
|
-
|
|
2983
|
-
|
|
2984
|
-
|
|
3056
|
+
function callSafely(call) {
|
|
3057
|
+
try {
|
|
3058
|
+
call();
|
|
3059
|
+
} catch {
|
|
2985
3060
|
}
|
|
3061
|
+
}
|
|
3062
|
+
function readBlob(blob) {
|
|
3063
|
+
if (typeof blob.arrayBuffer === "function") return blob.arrayBuffer();
|
|
2986
3064
|
return new Promise((resolve, reject) => {
|
|
2987
3065
|
const reader = new FileReader();
|
|
2988
|
-
reader.onload = () =>
|
|
2989
|
-
|
|
2990
|
-
|
|
2991
|
-
} else {
|
|
2992
|
-
reject(new Error("파일 내용을 ArrayBuffer로 읽지 못했습니다."));
|
|
2993
|
-
}
|
|
2994
|
-
};
|
|
2995
|
-
reader.onerror = () => reject(reader.error ?? new Error("파일 읽기에 실패했습니다."));
|
|
2996
|
-
reader.readAsArrayBuffer(file);
|
|
3066
|
+
reader.onload = () => resolve(reader.result);
|
|
3067
|
+
reader.onerror = () => reject(reader.error ?? new Error("업로드 파일을 읽지 못했습니다."));
|
|
3068
|
+
reader.readAsArrayBuffer(blob);
|
|
2997
3069
|
});
|
|
2998
3070
|
}
|
|
2999
|
-
|
|
3071
|
+
function lockName(recordId) {
|
|
3072
|
+
return `issue-sticker-upload:${recordId}`;
|
|
3073
|
+
}
|
|
3074
|
+
function lockManager() {
|
|
3075
|
+
return typeof navigator !== "undefined" ? navigator.locks : void 0;
|
|
3076
|
+
}
|
|
3077
|
+
async function heldLockNames() {
|
|
3000
3078
|
var _a;
|
|
3001
|
-
|
|
3002
|
-
|
|
3079
|
+
try {
|
|
3080
|
+
const snapshot = await ((_a = lockManager()) == null ? void 0 : _a.query());
|
|
3081
|
+
return new Set(((snapshot == null ? void 0 : snapshot.held) ?? []).flatMap((lock) => lock.name ? [lock.name] : []));
|
|
3082
|
+
} catch {
|
|
3083
|
+
return /* @__PURE__ */ new Set();
|
|
3003
3084
|
}
|
|
3004
|
-
const digest = await globalThis.crypto.subtle.digest("SHA-256", await readFileArrayBuffer(file));
|
|
3005
|
-
return bytesToHex(new Uint8Array(digest));
|
|
3006
3085
|
}
|
|
3007
|
-
function
|
|
3008
|
-
|
|
3009
|
-
|
|
3010
|
-
const normalized = (_a = (mimeType || fallback).split(";", 1)[0]) == null ? void 0 : _a.trim().toLowerCase();
|
|
3011
|
-
return normalized && normalized.length > 0 ? normalized : fallback;
|
|
3086
|
+
async function sha256Hex(blob) {
|
|
3087
|
+
const digest = await crypto.subtle.digest("SHA-256", await readBlob(blob));
|
|
3088
|
+
return Array.from(new Uint8Array(digest), (byte) => byte.toString(16).padStart(2, "0")).join("");
|
|
3012
3089
|
}
|
|
3013
|
-
|
|
3014
|
-
|
|
3015
|
-
|
|
3016
|
-
|
|
3017
|
-
|
|
3018
|
-
|
|
3019
|
-
|
|
3020
|
-
|
|
3021
|
-
|
|
3022
|
-
|
|
3023
|
-
|
|
3024
|
-
|
|
3025
|
-
|
|
3026
|
-
|
|
3090
|
+
class MediaUploadQueue {
|
|
3091
|
+
constructor(deps) {
|
|
3092
|
+
this.deps = deps;
|
|
3093
|
+
this.records = /* @__PURE__ */ new Map();
|
|
3094
|
+
this.states = /* @__PURE__ */ new Map();
|
|
3095
|
+
this.listeners = /* @__PURE__ */ new Set();
|
|
3096
|
+
this.running = false;
|
|
3097
|
+
this.stopped = false;
|
|
3098
|
+
this.cancelCurrent = null;
|
|
3099
|
+
this.finished = /* @__PURE__ */ new Set();
|
|
3100
|
+
this.failureNotified = /* @__PURE__ */ new Set();
|
|
3101
|
+
this.persisted = /* @__PURE__ */ new Set();
|
|
3102
|
+
this.held = /* @__PURE__ */ new Set();
|
|
3103
|
+
this.resolveStopped = () => void 0;
|
|
3104
|
+
this.handleOnline = () => this.requeueFailed();
|
|
3105
|
+
this.now = deps.now ?? Date.now;
|
|
3106
|
+
this.whenStopped = new Promise((resolve) => {
|
|
3107
|
+
this.resolveStopped = resolve;
|
|
3108
|
+
});
|
|
3109
|
+
if (typeof window !== "undefined") window.addEventListener("online", this.handleOnline);
|
|
3110
|
+
}
|
|
3111
|
+
/**
|
|
3112
|
+
* 저장소에 넣은 뒤 resolve 한다. 저장이 실패하면 메모리로만 진행한다.
|
|
3113
|
+
* reject 하지 않는다 — 이슈는 이미 생성됐다. 기록조차 만들지 못하면 이슈 id 로 abort 해
|
|
3114
|
+
* 서버가 이슈를 바로 failed 로 내리게 한다.
|
|
3115
|
+
* 기록은 호출한 틱에 생긴다(첫 await 전). 같은 이슈의 다른 업로드와 함께 넣을 때 이 순서에 기댄다.
|
|
3116
|
+
*/
|
|
3117
|
+
async enqueue(input) {
|
|
3118
|
+
let record2;
|
|
3119
|
+
try {
|
|
3120
|
+
record2 = {
|
|
3121
|
+
id: crypto.randomUUID(),
|
|
3122
|
+
...input,
|
|
3123
|
+
mimeType: stripMimeParameters(input.mimeType),
|
|
3124
|
+
createdAt: this.now()
|
|
3125
|
+
};
|
|
3126
|
+
} catch {
|
|
3127
|
+
await this.deps.api.abortUpload({ uploadId: null, issueId: input.issueId, mediaType: input.mediaType }).catch(() => void 0);
|
|
3128
|
+
this.emit({ type: "aborted", issueId: input.issueId, mediaType: input.mediaType });
|
|
3129
|
+
return;
|
|
3130
|
+
}
|
|
3131
|
+
this.records.set(record2.id, record2);
|
|
3132
|
+
this.setState(record2, "queued", 0);
|
|
3133
|
+
await this.persist(record2);
|
|
3134
|
+
this.kick();
|
|
3135
|
+
}
|
|
3136
|
+
/**
|
|
3137
|
+
* 저장소의 이 프로젝트 기록을 메모리로 가져와 붙잡아 둔다. 이미 돌고 있는 처리가 있어도
|
|
3138
|
+
* requeueFailed 가 이 프로젝트를 풀 때까지 가져온 기록은 시작하지 않는다. 새로 enqueue 한
|
|
3139
|
+
* 기록은 붙잡지 않는다.
|
|
3140
|
+
* 다른 탭이 잠금을 쥐고 올리는 기록은 가져오지도 세지도 않는다.
|
|
3141
|
+
* 새로 읽어 온 기록 중 만료되지 않은 수를 돌려준다.
|
|
3142
|
+
* 이어 올리기는 두 저장소를 모두 읽은 뒤 시작하는 `IssueUploadQueues.resume` 으로 한다.
|
|
3143
|
+
*/
|
|
3144
|
+
async load(projectKey) {
|
|
3145
|
+
const held = await heldLockNames();
|
|
3146
|
+
const stored = await this.deps.store.list().catch(() => []);
|
|
3147
|
+
let resumable = 0;
|
|
3148
|
+
for (const record2 of stored) {
|
|
3149
|
+
if (record2.projectKey !== projectKey || this.records.has(record2.id) || this.finished.has(record2.id) || held.has(lockName(record2.id))) {
|
|
3150
|
+
continue;
|
|
3151
|
+
}
|
|
3152
|
+
this.persisted.add(record2.id);
|
|
3153
|
+
const normalized = { ...record2, mediaType: record2.mediaType ?? "video" };
|
|
3154
|
+
this.records.set(normalized.id, normalized);
|
|
3155
|
+
this.held.add(normalized.id);
|
|
3156
|
+
this.setState(normalized, "queued", 0);
|
|
3157
|
+
if (!this.isExpired(normalized)) resumable += 1;
|
|
3158
|
+
}
|
|
3159
|
+
return resumable;
|
|
3160
|
+
}
|
|
3161
|
+
/**
|
|
3162
|
+
* 모든 프로젝트·워크스페이스의 만료 기록을 저장소에서 지운다. 서버에는 알리지 않는다 —
|
|
3163
|
+
* 생성 24시간이 지난 업로드 중 이슈는 서버 cron 이 failed 로 내린다. 이 대기열이 들고 있는
|
|
3164
|
+
* 기록은 처리 순서에서 abort 하므로 건드리지 않는다. 지운 수를 돌려준다.
|
|
3165
|
+
*/
|
|
3166
|
+
async pruneExpired() {
|
|
3167
|
+
const stored = await this.deps.store.list().catch(() => []);
|
|
3168
|
+
const expired = stored.filter(
|
|
3169
|
+
(record2) => this.isExpired(record2) && !this.records.has(record2.id) && !this.finished.has(record2.id)
|
|
3027
3170
|
);
|
|
3171
|
+
await Promise.all(
|
|
3172
|
+
expired.map((record2) => this.deps.store.delete(record2.id).catch(() => void 0))
|
|
3173
|
+
);
|
|
3174
|
+
return expired.length;
|
|
3028
3175
|
}
|
|
3029
|
-
|
|
3030
|
-
|
|
3031
|
-
|
|
3032
|
-
|
|
3033
|
-
|
|
3034
|
-
|
|
3035
|
-
|
|
3036
|
-
|
|
3037
|
-
|
|
3038
|
-
|
|
3039
|
-
|
|
3040
|
-
|
|
3041
|
-
|
|
3042
|
-
|
|
3043
|
-
|
|
3044
|
-
|
|
3176
|
+
/** 그 이슈의 실패 기록을 전부 다시 올린다. 첨부 대기열은 한 이슈에 리플레이·소켓 두 기록을 둔다. */
|
|
3177
|
+
retry(issueId) {
|
|
3178
|
+
var _a;
|
|
3179
|
+
let requeued = false;
|
|
3180
|
+
for (const record2 of this.records.values()) {
|
|
3181
|
+
if (record2.issueId !== issueId || ((_a = this.states.get(record2.id)) == null ? void 0 : _a.status) !== "failed") continue;
|
|
3182
|
+
this.failureNotified.delete(record2.id);
|
|
3183
|
+
this.setState(record2, "queued", 0);
|
|
3184
|
+
requeued = true;
|
|
3185
|
+
}
|
|
3186
|
+
if (requeued) this.kick();
|
|
3187
|
+
}
|
|
3188
|
+
/** 그 이슈에 아직 처리할(queued·uploading) 기록이 있는가. 실패로 멈춘 기록은 세지 않는다. */
|
|
3189
|
+
isPending(issueId) {
|
|
3190
|
+
return [...this.records.values()].some(
|
|
3191
|
+
(record2) => {
|
|
3192
|
+
var _a;
|
|
3193
|
+
return record2.issueId === issueId && ((_a = this.states.get(record2.id)) == null ? void 0 : _a.status) !== "failed";
|
|
3045
3194
|
}
|
|
3046
|
-
|
|
3047
|
-
});
|
|
3048
|
-
const upload = init.uploads[0];
|
|
3049
|
-
if (!(upload == null ? void 0 : upload.id) || !upload.signedUrl) {
|
|
3050
|
-
throw new Error("signed upload URL 발급 응답이 올바르지 않습니다.");
|
|
3195
|
+
);
|
|
3051
3196
|
}
|
|
3052
|
-
|
|
3053
|
-
|
|
3197
|
+
subscribe(listener) {
|
|
3198
|
+
this.listeners.add(listener);
|
|
3199
|
+
callSafely(() => listener(this.snapshot()));
|
|
3200
|
+
return () => {
|
|
3201
|
+
this.listeners.delete(listener);
|
|
3202
|
+
};
|
|
3054
3203
|
}
|
|
3055
|
-
|
|
3056
|
-
|
|
3057
|
-
|
|
3058
|
-
|
|
3059
|
-
|
|
3060
|
-
|
|
3061
|
-
|
|
3062
|
-
|
|
3063
|
-
|
|
3064
|
-
|
|
3065
|
-
|
|
3066
|
-
|
|
3067
|
-
|
|
3068
|
-
|
|
3069
|
-
|
|
3070
|
-
|
|
3071
|
-
|
|
3072
|
-
|
|
3073
|
-
|
|
3074
|
-
|
|
3075
|
-
|
|
3076
|
-
|
|
3204
|
+
hasActiveUploads() {
|
|
3205
|
+
return [...this.states.values()].some((state) => state.status !== "failed");
|
|
3206
|
+
}
|
|
3207
|
+
stop() {
|
|
3208
|
+
var _a;
|
|
3209
|
+
this.stopped = true;
|
|
3210
|
+
this.resolveStopped();
|
|
3211
|
+
if (typeof window !== "undefined") window.removeEventListener("online", this.handleOnline);
|
|
3212
|
+
(_a = this.cancelCurrent) == null ? void 0 : _a.call(this);
|
|
3213
|
+
this.listeners.clear();
|
|
3214
|
+
}
|
|
3215
|
+
/**
|
|
3216
|
+
* 기록을(projectKey 를 주면 그 프로젝트만) 처리 대상으로 돌린다. load 가 붙잡아 둔 기록을 풀고,
|
|
3217
|
+
* failed 로 멈춘 기록은 다시 대기시킨 뒤 처리를 시작한다.
|
|
3218
|
+
*/
|
|
3219
|
+
requeueFailed(projectKey) {
|
|
3220
|
+
var _a;
|
|
3221
|
+
for (const record2 of this.records.values()) {
|
|
3222
|
+
if (projectKey !== void 0 && record2.projectKey !== projectKey) continue;
|
|
3223
|
+
this.held.delete(record2.id);
|
|
3224
|
+
if (((_a = this.states.get(record2.id)) == null ? void 0 : _a.status) === "failed") this.setState(record2, "queued", 0);
|
|
3225
|
+
}
|
|
3226
|
+
this.kick();
|
|
3227
|
+
}
|
|
3228
|
+
kick() {
|
|
3229
|
+
if (this.running || this.stopped) return;
|
|
3230
|
+
this.running = true;
|
|
3231
|
+
void this.drain().finally(() => {
|
|
3232
|
+
this.running = false;
|
|
3233
|
+
if (this.nextQueued()) this.kick();
|
|
3234
|
+
});
|
|
3235
|
+
}
|
|
3236
|
+
nextQueued() {
|
|
3237
|
+
return [...this.records.values()].find(
|
|
3238
|
+
(record2) => {
|
|
3239
|
+
var _a;
|
|
3240
|
+
return !this.held.has(record2.id) && ((_a = this.states.get(record2.id)) == null ? void 0 : _a.status) === "queued";
|
|
3077
3241
|
}
|
|
3078
|
-
|
|
3079
|
-
}
|
|
3080
|
-
|
|
3081
|
-
|
|
3082
|
-
|
|
3083
|
-
|
|
3084
|
-
|
|
3242
|
+
);
|
|
3243
|
+
}
|
|
3244
|
+
async drain() {
|
|
3245
|
+
for (let record2 = this.nextQueued(); record2 && !this.stopped; record2 = this.nextQueued()) {
|
|
3246
|
+
await this.process(record2);
|
|
3247
|
+
}
|
|
3248
|
+
}
|
|
3249
|
+
/**
|
|
3250
|
+
* 저장소는 같은 사이트의 모든 탭이 함께 쓴다. 탭마다 resume 하면 같은 기록을 두 탭이 동시에
|
|
3251
|
+
* 올리게 되므로 기록 id 잠금을 쥔 탭만 처리한다. 잠금을 못 얻으면 이 탭 메모리에서만 내려놓고
|
|
3252
|
+
* 저장소 기록은 그대로 둔다. 탭이 닫히면 잠금이 풀리므로 다음 resume 에서 다시 가져간다.
|
|
3253
|
+
* navigator.locks 가 없거나 요청이 거절되는 환경(비보안 컨텍스트 등)은 잠금 없이 처리한다.
|
|
3254
|
+
*/
|
|
3255
|
+
async process(record2) {
|
|
3256
|
+
const locks = lockManager();
|
|
3257
|
+
if (!locks) return this.processRecord(record2);
|
|
3258
|
+
let entered = false;
|
|
3259
|
+
try {
|
|
3260
|
+
await locks.request(lockName(record2.id), { ifAvailable: true }, async (lock) => {
|
|
3261
|
+
entered = true;
|
|
3262
|
+
if (!lock || !await this.refresh(record2)) {
|
|
3263
|
+
this.release(record2);
|
|
3264
|
+
return;
|
|
3265
|
+
}
|
|
3266
|
+
await Promise.race([this.processRecord(record2), this.whenStopped]);
|
|
3267
|
+
});
|
|
3268
|
+
} catch {
|
|
3269
|
+
if (!entered) await this.processRecord(record2);
|
|
3270
|
+
}
|
|
3271
|
+
}
|
|
3272
|
+
/**
|
|
3273
|
+
* 이 탭이 기록을 읽은 뒤 잠금을 얻기까지 다른 탭이 그 기록을 처리했을 수 있다. 저장소에서
|
|
3274
|
+
* 사라졌으면 다른 탭이 끝낸 것이라 false, 남아 있으면 그 탭이 받은 uploadId·이어 올릴 URL 을
|
|
3275
|
+
* 가져온다. 저장하지 못해 메모리에만 있는 기록과 저장소를 읽지 못한 경우는 그대로 진행한다.
|
|
3276
|
+
*/
|
|
3277
|
+
async refresh(record2) {
|
|
3278
|
+
if (!this.persisted.has(record2.id)) return true;
|
|
3279
|
+
const fresh = await this.deps.store.load(record2.id).catch(() => void 0);
|
|
3280
|
+
if (fresh === null) return false;
|
|
3281
|
+
if (fresh) Object.assign(record2, fresh, { mediaType: fresh.mediaType ?? record2.mediaType });
|
|
3282
|
+
return true;
|
|
3283
|
+
}
|
|
3284
|
+
/** 다른 탭이 맡은 기록을 이 대기열에서만 뺀다. 알림·서버 호출·저장소 삭제는 하지 않는다. */
|
|
3285
|
+
release(record2) {
|
|
3286
|
+
this.failureNotified.delete(record2.id);
|
|
3287
|
+
this.persisted.delete(record2.id);
|
|
3288
|
+
this.records.delete(record2.id);
|
|
3289
|
+
this.states.delete(record2.id);
|
|
3290
|
+
this.notify();
|
|
3291
|
+
}
|
|
3292
|
+
async processRecord(record2) {
|
|
3293
|
+
var _a;
|
|
3294
|
+
if (this.isExpired(record2)) {
|
|
3295
|
+
await this.discard(record2);
|
|
3296
|
+
return;
|
|
3297
|
+
}
|
|
3298
|
+
this.setState(record2, "uploading", 0);
|
|
3299
|
+
try {
|
|
3300
|
+
await this.ensureInitialized(record2);
|
|
3301
|
+
await this.uploadWithResign(record2);
|
|
3302
|
+
this.throwIfStopped();
|
|
3303
|
+
await this.complete(record2);
|
|
3304
|
+
} catch (error) {
|
|
3305
|
+
if (this.stopped) return;
|
|
3306
|
+
if (error instanceof PermanentUploadError) {
|
|
3307
|
+
await this.discard(record2);
|
|
3308
|
+
return;
|
|
3309
|
+
}
|
|
3310
|
+
this.setState(record2, "failed", ((_a = this.states.get(record2.id)) == null ? void 0 : _a.bytesSent) ?? 0);
|
|
3311
|
+
if (typeof navigator !== "undefined" && navigator.onLine === false) return;
|
|
3312
|
+
if (!this.failureNotified.has(record2.id)) {
|
|
3313
|
+
this.failureNotified.add(record2.id);
|
|
3314
|
+
this.emit({ type: "failed", issueId: record2.issueId, mediaType: record2.mediaType });
|
|
3315
|
+
}
|
|
3316
|
+
return;
|
|
3317
|
+
}
|
|
3318
|
+
if (this.stopped) return;
|
|
3319
|
+
await this.finish(record2, "completed");
|
|
3320
|
+
}
|
|
3321
|
+
async ensureInitialized(record2) {
|
|
3322
|
+
if (record2.uploadId) return;
|
|
3323
|
+
const checksumSha256 = await sha256Hex(record2.blob).catch((error) => {
|
|
3324
|
+
throw new PermanentUploadError(error instanceof Error ? error.message : String(error));
|
|
3325
|
+
});
|
|
3326
|
+
this.throwIfStopped();
|
|
3327
|
+
const init = await this.callApi(
|
|
3328
|
+
() => this.deps.api.initUpload({
|
|
3329
|
+
mediaType: record2.mediaType,
|
|
3330
|
+
issueId: record2.issueId,
|
|
3331
|
+
mimeType: record2.mimeType,
|
|
3332
|
+
sizeBytes: record2.blob.size,
|
|
3333
|
+
checksumSha256
|
|
3334
|
+
})
|
|
3335
|
+
);
|
|
3336
|
+
Object.assign(record2, init);
|
|
3337
|
+
await this.persistProgress(record2);
|
|
3338
|
+
}
|
|
3339
|
+
/**
|
|
3340
|
+
* complete 의 409 는 다른 요청(같은 기록을 이어 올린 다른 탭 등)이 먼저 연결해 상태가 바뀐 경우다.
|
|
3341
|
+
* 이미 이슈에 붙었으므로 완료로 본다.
|
|
3342
|
+
*/
|
|
3343
|
+
async complete(record2) {
|
|
3344
|
+
try {
|
|
3345
|
+
await this.deps.api.completeUpload({
|
|
3346
|
+
uploadId: record2.uploadId,
|
|
3347
|
+
issueId: record2.issueId,
|
|
3348
|
+
mediaType: record2.mediaType,
|
|
3349
|
+
replayMeta: record2.replayMeta
|
|
3350
|
+
});
|
|
3351
|
+
} catch (error) {
|
|
3352
|
+
if (readStatus(error) === 409) return;
|
|
3353
|
+
throw toApiFailure(error);
|
|
3354
|
+
}
|
|
3355
|
+
}
|
|
3356
|
+
/** stop() 뒤에는 진행 중이던 처리도 서버를 더 부르지 않는다. 기록은 저장소에 남겨 다음 resume 에 맡긴다. */
|
|
3357
|
+
throwIfStopped() {
|
|
3358
|
+
if (this.stopped) throw new UploadStoppedError();
|
|
3359
|
+
}
|
|
3360
|
+
async callApi(call) {
|
|
3361
|
+
try {
|
|
3362
|
+
return await call();
|
|
3363
|
+
} catch (error) {
|
|
3364
|
+
throw toApiFailure(error);
|
|
3365
|
+
}
|
|
3366
|
+
}
|
|
3367
|
+
/** 업로드 토큰은 2시간이면 만료된다. 400·401·403 이면 같은 path 로 다시 서명받아 한 번만 이어 간다. */
|
|
3368
|
+
async uploadWithResign(record2) {
|
|
3369
|
+
try {
|
|
3370
|
+
await this.runTus(record2);
|
|
3371
|
+
return;
|
|
3372
|
+
} catch (error) {
|
|
3373
|
+
const status = readStatus(error);
|
|
3374
|
+
if (status === void 0 || !SIGNATURE_STATUSES.has(status)) throw toTusFailure(error);
|
|
3375
|
+
}
|
|
3376
|
+
let token;
|
|
3377
|
+
try {
|
|
3378
|
+
({ token } = await this.deps.api.resignUpload(record2.uploadId));
|
|
3379
|
+
} catch (error) {
|
|
3380
|
+
if (readStatus(error) === 409) return;
|
|
3381
|
+
throw toApiFailure(error);
|
|
3382
|
+
}
|
|
3383
|
+
record2.token = token;
|
|
3384
|
+
await this.persistProgress(record2);
|
|
3385
|
+
try {
|
|
3386
|
+
await this.runTus(record2);
|
|
3387
|
+
} catch (error) {
|
|
3388
|
+
throw toTusFailure(error);
|
|
3389
|
+
}
|
|
3390
|
+
}
|
|
3391
|
+
async runTus(record2) {
|
|
3392
|
+
const { Upload } = await this.deps.loadTus();
|
|
3393
|
+
this.throwIfStopped();
|
|
3394
|
+
await new Promise((resolve, reject) => {
|
|
3395
|
+
const upload = new Upload(record2.blob, {
|
|
3396
|
+
endpoint: record2.resumableUrl,
|
|
3397
|
+
uploadUrl: record2.tusUploadUrl ?? null,
|
|
3398
|
+
chunkSize: TUS_CHUNK_SIZE,
|
|
3399
|
+
retryDelays: TUS_RETRY_DELAYS,
|
|
3400
|
+
// 이어 올릴 URL 은 우리 기록에 둔다. tus 의 localStorage 저장은 쓰지 않는다.
|
|
3401
|
+
storeFingerprintForResuming: false,
|
|
3402
|
+
headers: { "x-signature": record2.token ?? "", "x-client-info": this.deps.clientInfo },
|
|
3403
|
+
metadata: {
|
|
3404
|
+
bucketName: record2.bucket ?? "",
|
|
3405
|
+
objectName: record2.path ?? "",
|
|
3406
|
+
contentType: record2.mimeType,
|
|
3407
|
+
cacheControl: "3600"
|
|
3408
|
+
},
|
|
3409
|
+
onUploadUrlAvailable: () => {
|
|
3410
|
+
record2.tusUploadUrl = upload.url ?? void 0;
|
|
3411
|
+
void this.persistProgress(record2);
|
|
3412
|
+
},
|
|
3413
|
+
onProgress: (bytesSent, bytesTotal) => this.setState(record2, "uploading", bytesSent, bytesTotal),
|
|
3414
|
+
onSuccess: () => resolve(),
|
|
3415
|
+
onError: (error) => reject(error)
|
|
3416
|
+
});
|
|
3417
|
+
this.cancelCurrent = () => {
|
|
3418
|
+
void upload.abort(false);
|
|
3419
|
+
reject(new UploadStoppedError());
|
|
3420
|
+
};
|
|
3421
|
+
upload.start();
|
|
3422
|
+
}).finally(() => {
|
|
3423
|
+
this.cancelCurrent = null;
|
|
3424
|
+
});
|
|
3425
|
+
}
|
|
3426
|
+
async discard(record2) {
|
|
3427
|
+
await this.deps.api.abortUpload({
|
|
3428
|
+
uploadId: record2.uploadId ?? null,
|
|
3429
|
+
issueId: record2.issueId,
|
|
3430
|
+
mediaType: record2.mediaType
|
|
3431
|
+
}).catch(() => void 0);
|
|
3432
|
+
await this.finish(record2, "aborted");
|
|
3433
|
+
}
|
|
3434
|
+
/**
|
|
3435
|
+
* 끝낸 기록을 저장소에서 지운 뒤 메모리에서 빼고 알린다. 메모리에서 빼는 것과 알림 사이에
|
|
3436
|
+
* await 를 두지 않는다 — 알리미는 두 대기열의 메모리 기록으로 그 이슈가 끝났는지 보므로, 그
|
|
3437
|
+
* 사이 다른 대기열이 끝나면 양쪽이 서로 끝난 것으로 보고 따로 알린다.
|
|
3438
|
+
*/
|
|
3439
|
+
async finish(record2, type) {
|
|
3440
|
+
this.finished.add(record2.id);
|
|
3441
|
+
await this.deps.store.delete(record2.id).catch(() => void 0);
|
|
3442
|
+
this.persisted.delete(record2.id);
|
|
3443
|
+
this.failureNotified.delete(record2.id);
|
|
3444
|
+
this.records.delete(record2.id);
|
|
3445
|
+
this.states.delete(record2.id);
|
|
3446
|
+
this.notify();
|
|
3447
|
+
this.emit({ type, issueId: record2.issueId, mediaType: record2.mediaType });
|
|
3448
|
+
}
|
|
3449
|
+
async persist(record2) {
|
|
3450
|
+
await this.deps.store.save(record2).then(
|
|
3451
|
+
() => this.persisted.add(record2.id),
|
|
3452
|
+
() => void 0
|
|
3453
|
+
);
|
|
3454
|
+
}
|
|
3455
|
+
/**
|
|
3456
|
+
* 처리 중에 받은 결과(init·재서명·이어 올릴 URL)를 저장한다. stop() 뒤에는 쓰지 않는다 —
|
|
3457
|
+
* 잠금을 넘겨받은 새 대기열이 저장한 내용을 덮어쓰지 않게 한다.
|
|
3458
|
+
*/
|
|
3459
|
+
async persistProgress(record2) {
|
|
3460
|
+
if (this.stopped) return;
|
|
3461
|
+
await this.persist(record2);
|
|
3462
|
+
}
|
|
3463
|
+
isExpired(record2) {
|
|
3464
|
+
return this.now() - record2.createdAt >= UPLOAD_MAX_AGE_MS;
|
|
3465
|
+
}
|
|
3466
|
+
setState(record2, status, bytesSent, bytesTotal = record2.blob.size) {
|
|
3467
|
+
this.states.set(record2.id, {
|
|
3468
|
+
issueId: record2.issueId,
|
|
3469
|
+
mediaType: record2.mediaType,
|
|
3470
|
+
status,
|
|
3471
|
+
bytesSent,
|
|
3472
|
+
bytesTotal
|
|
3473
|
+
});
|
|
3474
|
+
this.notify();
|
|
3475
|
+
}
|
|
3476
|
+
snapshot() {
|
|
3477
|
+
return [...this.records.keys()].flatMap((id) => {
|
|
3478
|
+
const state = this.states.get(id);
|
|
3479
|
+
return state ? [state] : [];
|
|
3480
|
+
});
|
|
3481
|
+
}
|
|
3482
|
+
notify() {
|
|
3483
|
+
const states = this.snapshot();
|
|
3484
|
+
this.listeners.forEach((listener) => callSafely(() => listener(states)));
|
|
3485
|
+
}
|
|
3486
|
+
emit(event) {
|
|
3487
|
+
callSafely(() => {
|
|
3488
|
+
var _a, _b;
|
|
3489
|
+
return (_b = (_a = this.deps).onEvent) == null ? void 0 : _b.call(_a, event);
|
|
3490
|
+
});
|
|
3491
|
+
}
|
|
3492
|
+
}
|
|
3493
|
+
const VIDEO_UPLOAD_DB_NAME = "issue-sticker-video-uploads";
|
|
3494
|
+
const ATTACHMENT_UPLOAD_DB_NAME = "issue-sticker-attachment-uploads";
|
|
3495
|
+
const DB_VERSION = 1;
|
|
3496
|
+
const STORE_NAME = "uploads";
|
|
3497
|
+
function openDatabase(dbName) {
|
|
3498
|
+
return new Promise((resolve, reject) => {
|
|
3499
|
+
const request = indexedDB.open(dbName, DB_VERSION);
|
|
3500
|
+
request.onupgradeneeded = () => {
|
|
3501
|
+
if (!request.result.objectStoreNames.contains(STORE_NAME)) {
|
|
3502
|
+
request.result.createObjectStore(STORE_NAME, { keyPath: "id" });
|
|
3503
|
+
}
|
|
3504
|
+
};
|
|
3505
|
+
request.onsuccess = () => {
|
|
3506
|
+
const db = request.result;
|
|
3507
|
+
db.onversionchange = () => db.close();
|
|
3508
|
+
resolve(db);
|
|
3509
|
+
};
|
|
3510
|
+
request.onerror = () => reject(request.error);
|
|
3511
|
+
request.onblocked = () => reject(new Error("indexedDB open blocked"));
|
|
3512
|
+
});
|
|
3513
|
+
}
|
|
3514
|
+
async function withStore(dbName, mode, run) {
|
|
3515
|
+
const db = await openDatabase(dbName);
|
|
3516
|
+
try {
|
|
3517
|
+
return await new Promise((resolve, reject) => {
|
|
3518
|
+
const tx = db.transaction(STORE_NAME, mode);
|
|
3519
|
+
const request = run(tx.objectStore(STORE_NAME));
|
|
3520
|
+
tx.oncomplete = () => resolve(request.result);
|
|
3521
|
+
tx.onabort = () => reject(tx.error ?? request.error ?? new Error("indexedDB aborted"));
|
|
3522
|
+
tx.onerror = () => reject(tx.error ?? request.error);
|
|
3523
|
+
});
|
|
3524
|
+
} finally {
|
|
3525
|
+
db.close();
|
|
3526
|
+
}
|
|
3527
|
+
}
|
|
3528
|
+
function createIndexedDbPendingUploadStore(dbName) {
|
|
3529
|
+
return {
|
|
3530
|
+
async load(id) {
|
|
3531
|
+
const found = await withStore(
|
|
3532
|
+
dbName,
|
|
3533
|
+
"readonly",
|
|
3534
|
+
(store) => store.get(id)
|
|
3535
|
+
);
|
|
3536
|
+
return found ?? null;
|
|
3537
|
+
},
|
|
3538
|
+
async save(record2) {
|
|
3539
|
+
await withStore(dbName, "readwrite", (store) => store.put(record2));
|
|
3540
|
+
},
|
|
3541
|
+
async delete(id) {
|
|
3542
|
+
await withStore(dbName, "readwrite", (store) => store.delete(id));
|
|
3543
|
+
},
|
|
3544
|
+
async list() {
|
|
3545
|
+
const all = await withStore(dbName, "readonly", (store) => store.getAll());
|
|
3546
|
+
return all.sort((a2, b) => a2.createdAt - b.createdAt);
|
|
3547
|
+
}
|
|
3548
|
+
};
|
|
3549
|
+
}
|
|
3550
|
+
const loadTusClient = () => import("./index-DA6SoSp7.js");
|
|
3551
|
+
class IssueUploadNotifier {
|
|
3552
|
+
constructor(deps) {
|
|
3553
|
+
this.deps = deps;
|
|
3554
|
+
this.outcomes = /* @__PURE__ */ new Map();
|
|
3555
|
+
}
|
|
3556
|
+
handle(event) {
|
|
3557
|
+
const notice = this.outcomes.get(event.issueId) ?? {
|
|
3558
|
+
issueId: event.issueId,
|
|
3559
|
+
completed: [],
|
|
3560
|
+
failed: [],
|
|
3561
|
+
aborted: []
|
|
3562
|
+
};
|
|
3563
|
+
notice[event.type].push(event.mediaType);
|
|
3564
|
+
this.outcomes.set(event.issueId, notice);
|
|
3565
|
+
if (this.deps.isPending(event.issueId)) return;
|
|
3566
|
+
this.outcomes.delete(event.issueId);
|
|
3567
|
+
try {
|
|
3568
|
+
this.deps.onNotice(notice);
|
|
3569
|
+
} catch {
|
|
3570
|
+
}
|
|
3571
|
+
}
|
|
3572
|
+
}
|
|
3573
|
+
function createIssueUploadQueues(deps) {
|
|
3574
|
+
const { onEvent, onNotice } = deps;
|
|
3575
|
+
const notifier = onNotice ? new IssueUploadNotifier({ isPending: (issueId) => isPending(issueId), onNotice }) : null;
|
|
3576
|
+
const create = (store) => new MediaUploadQueue({
|
|
3577
|
+
api: deps.api,
|
|
3578
|
+
store,
|
|
3579
|
+
loadTus: deps.loadTus,
|
|
3580
|
+
clientInfo: deps.clientInfo,
|
|
3581
|
+
onEvent: (event) => {
|
|
3582
|
+
onEvent == null ? void 0 : onEvent(event);
|
|
3583
|
+
notifier == null ? void 0 : notifier.handle(event);
|
|
3584
|
+
}
|
|
3585
|
+
});
|
|
3586
|
+
const video = create(deps.videoStore);
|
|
3587
|
+
const attachments = create(deps.attachmentStore);
|
|
3588
|
+
const isPending = (issueId) => video.isPending(issueId) || attachments.isPending(issueId);
|
|
3589
|
+
return {
|
|
3590
|
+
video,
|
|
3591
|
+
attachments,
|
|
3592
|
+
async resume(projectKeys) {
|
|
3593
|
+
const [videoCounts] = await Promise.all([
|
|
3594
|
+
Promise.all(projectKeys.map((key) => video.load(key))),
|
|
3595
|
+
Promise.all(projectKeys.map((key) => attachments.load(key)))
|
|
3596
|
+
]);
|
|
3597
|
+
for (const key of projectKeys) {
|
|
3598
|
+
video.requeueFailed(key);
|
|
3599
|
+
attachments.requeueFailed(key);
|
|
3600
|
+
}
|
|
3601
|
+
return videoCounts.reduce((sum, count2) => sum + count2, 0);
|
|
3602
|
+
},
|
|
3603
|
+
retry(issueId) {
|
|
3604
|
+
video.retry(issueId);
|
|
3605
|
+
attachments.retry(issueId);
|
|
3606
|
+
},
|
|
3607
|
+
stop() {
|
|
3608
|
+
video.stop();
|
|
3609
|
+
attachments.stop();
|
|
3610
|
+
},
|
|
3611
|
+
isPending
|
|
3612
|
+
};
|
|
3613
|
+
}
|
|
3614
|
+
let nextId = 0;
|
|
3615
|
+
let toasts = [];
|
|
3616
|
+
const listeners = /* @__PURE__ */ new Set();
|
|
3617
|
+
function emit() {
|
|
3618
|
+
listeners.forEach((l2) => l2());
|
|
3619
|
+
}
|
|
3620
|
+
function addToast(message2, type, options) {
|
|
3621
|
+
const id = `toast-${++nextId}`;
|
|
3622
|
+
const t2 = {
|
|
3623
|
+
id,
|
|
3624
|
+
message: message2,
|
|
3625
|
+
type,
|
|
3626
|
+
title: options == null ? void 0 : options.title,
|
|
3627
|
+
action: options == null ? void 0 : options.action,
|
|
3628
|
+
duration: (options == null ? void 0 : options.duration) ?? 2e3,
|
|
3629
|
+
createdAt: Date.now()
|
|
3630
|
+
};
|
|
3631
|
+
toasts = [...toasts, t2];
|
|
3632
|
+
emit();
|
|
3633
|
+
return id;
|
|
3634
|
+
}
|
|
3635
|
+
function dismiss(id) {
|
|
3636
|
+
toasts = toasts.filter((t2) => t2.id !== id);
|
|
3637
|
+
emit();
|
|
3638
|
+
}
|
|
3639
|
+
function dismissAll() {
|
|
3640
|
+
toasts = [];
|
|
3641
|
+
emit();
|
|
3642
|
+
}
|
|
3643
|
+
function toast(message2, options) {
|
|
3644
|
+
return addToast(message2, "default", options);
|
|
3645
|
+
}
|
|
3646
|
+
toast.success = (message2, options) => addToast(message2, "success", options);
|
|
3647
|
+
toast.warning = (message2, options) => addToast(message2, "warning", options);
|
|
3648
|
+
toast.error = (message2, options) => addToast(message2, "error", options);
|
|
3649
|
+
toast.info = (message2, options) => addToast(message2, "info", options);
|
|
3650
|
+
toast.dismiss = dismiss;
|
|
3651
|
+
toast.dismissAll = dismissAll;
|
|
3652
|
+
function subscribe(listener) {
|
|
3653
|
+
listeners.add(listener);
|
|
3654
|
+
return () => listeners.delete(listener);
|
|
3655
|
+
}
|
|
3656
|
+
function getSnapshot() {
|
|
3657
|
+
return toasts;
|
|
3658
|
+
}
|
|
3659
|
+
function dropOldestFrames(connections, bytesToDrop) {
|
|
3660
|
+
const order = connections.flatMap(
|
|
3661
|
+
(connection, index) => connection.frames.map((frame) => {
|
|
3662
|
+
var _a;
|
|
3663
|
+
return { index, t: frame.t, bytes: ((_a = frame.data) == null ? void 0 : _a.length) ?? 0 };
|
|
3664
|
+
})
|
|
3665
|
+
).sort((a2, b) => a2.t - b.t);
|
|
3666
|
+
const dropCount = connections.map(() => 0);
|
|
3667
|
+
const dropBytes = connections.map(() => 0);
|
|
3668
|
+
let removed = 0;
|
|
3669
|
+
for (const entry of order) {
|
|
3670
|
+
if (removed >= bytesToDrop) break;
|
|
3671
|
+
dropCount[entry.index] += 1;
|
|
3672
|
+
dropBytes[entry.index] += entry.bytes;
|
|
3673
|
+
removed += entry.bytes;
|
|
3674
|
+
}
|
|
3675
|
+
return connections.map((connection, index) => {
|
|
3676
|
+
const count2 = dropCount[index];
|
|
3677
|
+
if (count2 === 0) return connection;
|
|
3678
|
+
return {
|
|
3679
|
+
...connection,
|
|
3680
|
+
frames: connection.frames.slice(count2),
|
|
3681
|
+
droppedFrames: connection.droppedFrames + count2,
|
|
3682
|
+
droppedBytes: connection.droppedBytes + dropBytes[index]
|
|
3683
|
+
};
|
|
3684
|
+
});
|
|
3685
|
+
}
|
|
3686
|
+
const FIT_ATTEMPTS = 6;
|
|
3687
|
+
async function serializeSocketTranscript(connections, maxBytes = SOCKET_TRANSCRIPT_MAX_BYTES) {
|
|
3688
|
+
const frameCount = connections.reduce(
|
|
3689
|
+
(sum, connection) => sum + connection.frames.length + connection.droppedFrames,
|
|
3690
|
+
0
|
|
3691
|
+
);
|
|
3692
|
+
if (connections.length === 0 || frameCount === 0) return null;
|
|
3693
|
+
let current = connections;
|
|
3694
|
+
let payload = gzip_1(JSON.stringify({ connections: current }));
|
|
3695
|
+
for (let attempt = 0; attempt < FIT_ATTEMPTS && payload.byteLength > maxBytes; attempt += 1) {
|
|
3696
|
+
const storedBytes = current.reduce(
|
|
3697
|
+
(sum, connection) => sum + storedFrameBytes(connection.frames),
|
|
3698
|
+
0
|
|
3699
|
+
);
|
|
3700
|
+
if (storedBytes === 0) break;
|
|
3701
|
+
const overRatio = 1 - maxBytes / payload.byteLength;
|
|
3702
|
+
current = dropOldestFrames(current, storedBytes * Math.min(1, overRatio + 0.1));
|
|
3703
|
+
payload = gzip_1(JSON.stringify({ connections: current }));
|
|
3704
|
+
}
|
|
3705
|
+
return {
|
|
3706
|
+
payload,
|
|
3707
|
+
sizeBytes: payload.byteLength,
|
|
3708
|
+
connectionCount: connections.length,
|
|
3709
|
+
frameCount
|
|
3710
|
+
};
|
|
3711
|
+
}
|
|
3712
|
+
const ATTACHMENT_MIME_TYPE = "application/gzip";
|
|
3713
|
+
function toSessionReplayMeta(replay) {
|
|
3714
|
+
const marker = readReleaseMarker();
|
|
3715
|
+
return {
|
|
3716
|
+
window_seconds: replay.windowSeconds,
|
|
3717
|
+
event_count: replay.eventCount,
|
|
3718
|
+
size_bytes: replay.sizeBytes,
|
|
3719
|
+
rrweb_version: replay.rrwebVersion,
|
|
3720
|
+
privacy_config_version: replay.privacyConfigVersion,
|
|
3721
|
+
...marker ? { release: marker.release, release_source: marker.release_source } : {}
|
|
3722
|
+
};
|
|
3723
|
+
}
|
|
3724
|
+
function toAttachmentUploads(input) {
|
|
3725
|
+
const uploads = [];
|
|
3726
|
+
const { replay, socket } = input;
|
|
3727
|
+
if (replay && replay.eventCount > 0) {
|
|
3728
|
+
uploads.push({
|
|
3729
|
+
mediaType: "replay",
|
|
3730
|
+
bytes: replay.payload,
|
|
3731
|
+
mimeType: ATTACHMENT_MIME_TYPE,
|
|
3732
|
+
replayMeta: toSessionReplayMeta(replay)
|
|
3733
|
+
});
|
|
3734
|
+
}
|
|
3735
|
+
if (socket && socket.sizeBytes <= SOCKET_TRANSCRIPT_MAX_BYTES) {
|
|
3736
|
+
uploads.push({ mediaType: "socket", bytes: socket.payload, mimeType: ATTACHMENT_MIME_TYPE });
|
|
3737
|
+
}
|
|
3738
|
+
return uploads;
|
|
3739
|
+
}
|
|
3740
|
+
async function collectAttachmentUploads(input) {
|
|
3741
|
+
const { takeReplay, getConnections, onError } = input;
|
|
3742
|
+
const attempt = (kind, run) => Promise.resolve().then(run).catch((error) => {
|
|
3743
|
+
onError == null ? void 0 : onError(kind, error);
|
|
3744
|
+
return null;
|
|
3745
|
+
});
|
|
3746
|
+
const replay = takeReplay ? await attempt("replay", takeReplay) : null;
|
|
3747
|
+
const socket = await attempt(
|
|
3748
|
+
"socket",
|
|
3749
|
+
async () => serializeSocketTranscript(await getConnections())
|
|
3750
|
+
);
|
|
3751
|
+
return toAttachmentUploads({ replay, socket });
|
|
3752
|
+
}
|
|
3753
|
+
function hasReplayUpload(uploads) {
|
|
3754
|
+
return uploads.some((upload) => upload.mediaType === "replay");
|
|
3755
|
+
}
|
|
3756
|
+
async function enqueueIssueUploads(input) {
|
|
3757
|
+
const { issueId, projectKey } = input;
|
|
3758
|
+
const pending = [];
|
|
3759
|
+
if (input.video) {
|
|
3760
|
+
pending.push(
|
|
3761
|
+
input.videoQueue.enqueue({ issueId, projectKey, mediaType: "video", ...input.video })
|
|
3762
|
+
);
|
|
3763
|
+
}
|
|
3764
|
+
for (const attachment of input.attachments) {
|
|
3765
|
+
pending.push(
|
|
3766
|
+
input.attachmentQueue.enqueue({
|
|
3767
|
+
issueId,
|
|
3768
|
+
projectKey,
|
|
3769
|
+
mediaType: attachment.mediaType,
|
|
3770
|
+
blob: new Blob([attachment.bytes], { type: attachment.mimeType }),
|
|
3771
|
+
mimeType: attachment.mimeType,
|
|
3772
|
+
replayMeta: attachment.replayMeta
|
|
3773
|
+
})
|
|
3774
|
+
);
|
|
3775
|
+
}
|
|
3776
|
+
await Promise.all(pending);
|
|
3777
|
+
}
|
|
3778
|
+
class DirectUploadError extends Error {
|
|
3779
|
+
constructor(message2, status, data) {
|
|
3780
|
+
super(message2);
|
|
3781
|
+
this.status = status;
|
|
3782
|
+
this.data = data;
|
|
3783
|
+
this.name = "DirectUploadError";
|
|
3784
|
+
}
|
|
3785
|
+
}
|
|
3786
|
+
const uploadIdByUrl = /* @__PURE__ */ new Map();
|
|
3787
|
+
function bytesToHex(bytes) {
|
|
3788
|
+
return Array.from(bytes).map((byte) => byte.toString(16).padStart(2, "0")).join("");
|
|
3789
|
+
}
|
|
3790
|
+
async function readFileArrayBuffer(file) {
|
|
3791
|
+
if (typeof file.arrayBuffer === "function") {
|
|
3792
|
+
return file.arrayBuffer();
|
|
3793
|
+
}
|
|
3794
|
+
return new Promise((resolve, reject) => {
|
|
3795
|
+
const reader = new FileReader();
|
|
3796
|
+
reader.onload = () => {
|
|
3797
|
+
if (reader.result instanceof ArrayBuffer) {
|
|
3798
|
+
resolve(reader.result);
|
|
3799
|
+
} else {
|
|
3800
|
+
reject(new Error("파일 내용을 ArrayBuffer로 읽지 못했습니다."));
|
|
3801
|
+
}
|
|
3802
|
+
};
|
|
3803
|
+
reader.onerror = () => reject(reader.error ?? new Error("파일 읽기에 실패했습니다."));
|
|
3804
|
+
reader.readAsArrayBuffer(file);
|
|
3805
|
+
});
|
|
3806
|
+
}
|
|
3807
|
+
async function calculateSha256Hex(file) {
|
|
3808
|
+
var _a;
|
|
3809
|
+
if (!((_a = globalThis.crypto) == null ? void 0 : _a.subtle)) {
|
|
3810
|
+
throw new Error("Web Crypto SHA-256 API를 사용할 수 없어 signed upload를 시작할 수 없습니다.");
|
|
3811
|
+
}
|
|
3812
|
+
const digest = await globalThis.crypto.subtle.digest("SHA-256", await readFileArrayBuffer(file));
|
|
3813
|
+
return bytesToHex(new Uint8Array(digest));
|
|
3814
|
+
}
|
|
3815
|
+
function normalizeUploadMimeType(mimeType, mediaType) {
|
|
3816
|
+
var _a;
|
|
3817
|
+
const fallback = mediaType === "screenshot" ? "image/webp" : mediaType === "replay" || mediaType === "socket" ? "application/gzip" : "video/webm";
|
|
3818
|
+
const normalized = (_a = (mimeType || fallback).split(";", 1)[0]) == null ? void 0 : _a.trim().toLowerCase();
|
|
3819
|
+
return normalized && normalized.length > 0 ? normalized : fallback;
|
|
3820
|
+
}
|
|
3821
|
+
async function directUploadToSignedUrl(signedUrl, file, contentType) {
|
|
3822
|
+
const response = await fetch(signedUrl, {
|
|
3823
|
+
method: "PUT",
|
|
3824
|
+
headers: {
|
|
3825
|
+
"Content-Type": contentType,
|
|
3826
|
+
"x-client-info": "issue-sticker-sdk"
|
|
3827
|
+
},
|
|
3828
|
+
body: file
|
|
3829
|
+
});
|
|
3830
|
+
if (!response.ok) {
|
|
3831
|
+
const body = await response.text().catch(() => "");
|
|
3832
|
+
throw new DirectUploadError(
|
|
3833
|
+
`스토리지 업로드 실패: ${response.status}${body ? ` ${body}` : ""}`,
|
|
3834
|
+
response.status,
|
|
3835
|
+
body || void 0
|
|
3836
|
+
);
|
|
3837
|
+
}
|
|
3838
|
+
}
|
|
3839
|
+
function getRegisteredUploadId(publicUrl, mediaType) {
|
|
3840
|
+
const record2 = uploadIdByUrl.get(publicUrl);
|
|
3841
|
+
return (record2 == null ? void 0 : record2.mediaType) === mediaType ? record2.id : void 0;
|
|
3842
|
+
}
|
|
3843
|
+
async function uploadSignedMediaWithApi(api, file, mediaType, options = {}) {
|
|
3844
|
+
const checksumSha256 = await calculateSha256Hex(file);
|
|
3845
|
+
const contentType = normalizeUploadMimeType(file.type, mediaType);
|
|
3846
|
+
const init = await api.post("/api/media/uploads/init", {
|
|
3847
|
+
files: [
|
|
3848
|
+
{
|
|
3849
|
+
mediaType,
|
|
3850
|
+
mimeType: contentType,
|
|
3851
|
+
sizeBytes: file.size,
|
|
3852
|
+
checksumSha256,
|
|
3853
|
+
issueId: options.issueId
|
|
3854
|
+
}
|
|
3855
|
+
]
|
|
3856
|
+
});
|
|
3857
|
+
const upload = init.uploads[0];
|
|
3858
|
+
if (!(upload == null ? void 0 : upload.id) || !upload.signedUrl) {
|
|
3859
|
+
throw new Error("signed upload URL 발급 응답이 올바르지 않습니다.");
|
|
3860
|
+
}
|
|
3861
|
+
if (file.size > upload.maxSizeBytes) {
|
|
3862
|
+
throw new Error(`${mediaType} 파일 크기가 허용 한도를 초과했습니다.`);
|
|
3863
|
+
}
|
|
3864
|
+
await api.directUpload(upload.signedUrl, file, contentType);
|
|
3865
|
+
const complete = await api.post("/api/media/uploads/complete", {
|
|
3866
|
+
uploadIds: [upload.id],
|
|
3867
|
+
issueId: options.issueId
|
|
3868
|
+
});
|
|
3869
|
+
const completed = complete.uploads[0];
|
|
3870
|
+
const publicUrl = (completed == null ? void 0 : completed.publicUrl) || upload.publicUrl;
|
|
3871
|
+
const uploadId = (completed == null ? void 0 : completed.id) || upload.id;
|
|
3872
|
+
uploadIdByUrl.set(publicUrl, { id: uploadId, mediaType });
|
|
3873
|
+
return {
|
|
3874
|
+
id: uploadId,
|
|
3875
|
+
url: publicUrl
|
|
3876
|
+
};
|
|
3877
|
+
}
|
|
3878
|
+
function createMediaUploadApi(api) {
|
|
3879
|
+
return {
|
|
3880
|
+
async initUpload({ mediaType, issueId, mimeType, sizeBytes, checksumSha256 }) {
|
|
3881
|
+
const { uploads } = await api.post(
|
|
3882
|
+
"/api/media/uploads/init",
|
|
3883
|
+
{ files: [{ mediaType, mimeType, sizeBytes, checksumSha256, issueId }] }
|
|
3884
|
+
);
|
|
3885
|
+
const upload = uploads[0];
|
|
3886
|
+
if (!(upload == null ? void 0 : upload.id) || !upload.resumableUrl) {
|
|
3887
|
+
throw new Error("업로드 URL 발급 응답이 올바르지 않습니다.");
|
|
3888
|
+
}
|
|
3889
|
+
return {
|
|
3890
|
+
uploadId: upload.id,
|
|
3891
|
+
bucket: upload.bucket,
|
|
3892
|
+
path: upload.path,
|
|
3893
|
+
token: upload.token,
|
|
3894
|
+
resumableUrl: upload.resumableUrl
|
|
3895
|
+
};
|
|
3896
|
+
},
|
|
3897
|
+
resignUpload: (uploadId) => api.post(`/api/media/uploads/${encodeURIComponent(uploadId)}/resign`),
|
|
3898
|
+
async completeUpload({ uploadId, issueId, replayMeta }) {
|
|
3899
|
+
await api.post("/api/media/uploads/complete", {
|
|
3900
|
+
uploadIds: [uploadId],
|
|
3901
|
+
issueId,
|
|
3902
|
+
...replayMeta ? { replayMeta } : {}
|
|
3903
|
+
});
|
|
3904
|
+
},
|
|
3905
|
+
async abortUpload({ uploadId, issueId, mediaType }) {
|
|
3906
|
+
await api.post("/api/media/uploads/abort", {
|
|
3907
|
+
issueId,
|
|
3908
|
+
mediaType,
|
|
3909
|
+
...uploadId ? { uploadId } : {}
|
|
3910
|
+
});
|
|
3911
|
+
}
|
|
3912
|
+
};
|
|
3913
|
+
}
|
|
3914
|
+
const ISSUE_STICKER_INTERNAL_LOG_PREFIX = "__ISSUE_STICKER_INTERNAL_DEBUG__";
|
|
3915
|
+
function isInternalDebugLog$1(message2) {
|
|
3916
|
+
return Boolean(message2 == null ? void 0 : message2.includes(ISSUE_STICKER_INTERNAL_LOG_PREFIX));
|
|
3917
|
+
}
|
|
3918
|
+
function isIssueStickerInternalConsoleLog$1(log) {
|
|
3919
|
+
return isInternalDebugLog$1(log.message);
|
|
3920
|
+
}
|
|
3921
|
+
const __vite_import_meta_env__ = { "DEV": false };
|
|
3922
|
+
function isDevLoggingEnabled() {
|
|
3923
|
+
const env = __vite_import_meta_env__;
|
|
3924
|
+
{
|
|
3925
|
+
return env.DEV;
|
|
3926
|
+
}
|
|
3927
|
+
}
|
|
3928
|
+
function isInternalDebugLog(message2) {
|
|
3929
|
+
return Boolean(message2 == null ? void 0 : message2.includes(ISSUE_STICKER_INTERNAL_LOG_PREFIX));
|
|
3930
|
+
}
|
|
3931
|
+
function write(method, scope, args) {
|
|
3932
|
+
if (!isDevLoggingEnabled()) {
|
|
3933
|
+
return;
|
|
3934
|
+
}
|
|
3935
|
+
const log = console[method];
|
|
3936
|
+
log(ISSUE_STICKER_INTERNAL_LOG_PREFIX, `[${scope}]`, ...args);
|
|
3937
|
+
}
|
|
3938
|
+
const devLogger = {
|
|
3939
|
+
log(scope, ...args) {
|
|
3940
|
+
write("log", scope, args);
|
|
3941
|
+
},
|
|
3942
|
+
info(scope, ...args) {
|
|
3943
|
+
write("info", scope, args);
|
|
3944
|
+
},
|
|
3945
|
+
warn(scope, ...args) {
|
|
3946
|
+
write("warn", scope, args);
|
|
3947
|
+
},
|
|
3948
|
+
error(scope, ...args) {
|
|
3949
|
+
write("error", scope, args);
|
|
3950
|
+
},
|
|
3951
|
+
debug(scope, ...args) {
|
|
3952
|
+
write("debug", scope, args);
|
|
3953
|
+
}
|
|
3954
|
+
};
|
|
3955
|
+
function _mergeNamespaces$1(e2, t2) {
|
|
3956
|
+
return t2.forEach((function(t3) {
|
|
3957
|
+
t3 && "string" != typeof t3 && !Array.isArray(t3) && Object.keys(t3).forEach((function(r2) {
|
|
3958
|
+
if ("default" !== r2 && !(r2 in e2)) {
|
|
3959
|
+
var i2 = Object.getOwnPropertyDescriptor(t3, r2);
|
|
3960
|
+
Object.defineProperty(e2, r2, i2.get ? i2 : { enumerable: true, get: function() {
|
|
3961
|
+
return t3[r2];
|
|
3962
|
+
} });
|
|
3963
|
+
}
|
|
3964
|
+
}));
|
|
3965
|
+
})), Object.freeze(e2);
|
|
3966
|
+
}
|
|
3967
|
+
function copyExifWithoutOrientation(e2, t2) {
|
|
3968
|
+
return new Promise((function(r2, i2) {
|
|
3969
|
+
let o2;
|
|
3970
|
+
return getApp1Segment(e2).then((function(e3) {
|
|
3085
3971
|
try {
|
|
3086
3972
|
return o2 = e3, r2(new Blob([t2.slice(0, 2), o2, t2.slice(2)], { type: "image/jpeg" }));
|
|
3087
3973
|
} catch (e4) {
|
|
@@ -5435,6 +6321,7 @@ class NetworkCollector {
|
|
|
5435
6321
|
var _a;
|
|
5436
6322
|
if (!this.running) return;
|
|
5437
6323
|
if (this.isServiceRequest(log.url)) return;
|
|
6324
|
+
if (hasSelfTrafficHeader(log.requestHeaders)) return;
|
|
5438
6325
|
this.logs.push(log);
|
|
5439
6326
|
if (this.logs.length > NETWORK_LOG_CLIENT_BUFFER_SIZE) {
|
|
5440
6327
|
this.logs = this.logs.slice(-NETWORK_LOG_CLIENT_BUFFER_SIZE);
|
|
@@ -5622,90 +6509,32 @@ class NetworkCollector {
|
|
|
5622
6509
|
* 무조건 되돌리면 우리 뒤에 붙은 계측(Sentry 등)의 래퍼가 통째로 사라진다 — 고객의 에러
|
|
5623
6510
|
* 추적이 조용히 죽는다. 남의 것이 위에 있으면 우리 래퍼는 체인에 남겨 두고 수집만 멈춘다.
|
|
5624
6511
|
*/
|
|
5625
|
-
restoreFetch() {
|
|
5626
|
-
if (!this.originalFetch) return;
|
|
5627
|
-
if (window.fetch === this.patchedFetch) {
|
|
5628
|
-
window.fetch = this.originalFetch;
|
|
5629
|
-
}
|
|
5630
|
-
this.originalFetch = null;
|
|
5631
|
-
this.patchedFetch = null;
|
|
5632
|
-
clearPristineFetch();
|
|
5633
|
-
}
|
|
5634
|
-
/** fetch 와 같은 규칙 — 우리 래퍼가 아직 걸려 있을 때만 되돌린다. */
|
|
5635
|
-
restoreXHR() {
|
|
5636
|
-
const patched = this.patchedXHR;
|
|
5637
|
-
if (this.originalXHROpen && (!patched || XMLHttpRequest.prototype.open === patched.open)) {
|
|
5638
|
-
XMLHttpRequest.prototype.open = this.originalXHROpen;
|
|
5639
|
-
}
|
|
5640
|
-
if (this.originalXHRSend && (!patched || XMLHttpRequest.prototype.send === patched.send)) {
|
|
5641
|
-
XMLHttpRequest.prototype.send = this.originalXHRSend;
|
|
5642
|
-
}
|
|
5643
|
-
if (this.originalXHRSetRequestHeader && (!patched || XMLHttpRequest.prototype.setRequestHeader === patched.setRequestHeader)) {
|
|
5644
|
-
XMLHttpRequest.prototype.setRequestHeader = this.originalXHRSetRequestHeader;
|
|
5645
|
-
}
|
|
5646
|
-
this.originalXHROpen = null;
|
|
5647
|
-
this.originalXHRSend = null;
|
|
5648
|
-
this.originalXHRSetRequestHeader = null;
|
|
5649
|
-
this.patchedXHR = null;
|
|
5650
|
-
}
|
|
5651
|
-
}
|
|
5652
|
-
async function sha256Hex(bytes) {
|
|
5653
|
-
const digest = await crypto.subtle.digest("SHA-256", bytes.buffer);
|
|
5654
|
-
return Array.from(new Uint8Array(digest)).map((byte) => byte.toString(16).padStart(2, "0")).join("");
|
|
5655
|
-
}
|
|
5656
|
-
function dropOldestFrames(connections, bytesToDrop) {
|
|
5657
|
-
const order = connections.flatMap(
|
|
5658
|
-
(connection, index) => connection.frames.map((frame) => {
|
|
5659
|
-
var _a;
|
|
5660
|
-
return { index, t: frame.t, bytes: ((_a = frame.data) == null ? void 0 : _a.length) ?? 0 };
|
|
5661
|
-
})
|
|
5662
|
-
).sort((a2, b) => a2.t - b.t);
|
|
5663
|
-
const dropCount = connections.map(() => 0);
|
|
5664
|
-
const dropBytes = connections.map(() => 0);
|
|
5665
|
-
let removed = 0;
|
|
5666
|
-
for (const entry of order) {
|
|
5667
|
-
if (removed >= bytesToDrop) break;
|
|
5668
|
-
dropCount[entry.index] += 1;
|
|
5669
|
-
dropBytes[entry.index] += entry.bytes;
|
|
5670
|
-
removed += entry.bytes;
|
|
5671
|
-
}
|
|
5672
|
-
return connections.map((connection, index) => {
|
|
5673
|
-
const count2 = dropCount[index];
|
|
5674
|
-
if (count2 === 0) return connection;
|
|
5675
|
-
return {
|
|
5676
|
-
...connection,
|
|
5677
|
-
frames: connection.frames.slice(count2),
|
|
5678
|
-
droppedFrames: connection.droppedFrames + count2,
|
|
5679
|
-
droppedBytes: connection.droppedBytes + dropBytes[index]
|
|
5680
|
-
};
|
|
5681
|
-
});
|
|
5682
|
-
}
|
|
5683
|
-
const FIT_ATTEMPTS = 6;
|
|
5684
|
-
async function serializeSocketTranscript(connections, maxBytes = SOCKET_TRANSCRIPT_MAX_BYTES) {
|
|
5685
|
-
const frameCount = connections.reduce(
|
|
5686
|
-
(sum, connection) => sum + connection.frames.length + connection.droppedFrames,
|
|
5687
|
-
0
|
|
5688
|
-
);
|
|
5689
|
-
if (connections.length === 0 || frameCount === 0) return null;
|
|
5690
|
-
let current = connections;
|
|
5691
|
-
let payload = gzip_1(JSON.stringify({ connections: current }));
|
|
5692
|
-
for (let attempt = 0; attempt < FIT_ATTEMPTS && payload.byteLength > maxBytes; attempt += 1) {
|
|
5693
|
-
const storedBytes = current.reduce(
|
|
5694
|
-
(sum, connection) => sum + storedFrameBytes(connection.frames),
|
|
5695
|
-
0
|
|
5696
|
-
);
|
|
5697
|
-
if (storedBytes === 0) break;
|
|
5698
|
-
const overRatio = 1 - maxBytes / payload.byteLength;
|
|
5699
|
-
current = dropOldestFrames(current, storedBytes * Math.min(1, overRatio + 0.1));
|
|
5700
|
-
payload = gzip_1(JSON.stringify({ connections: current }));
|
|
6512
|
+
restoreFetch() {
|
|
6513
|
+
if (!this.originalFetch) return;
|
|
6514
|
+
if (window.fetch === this.patchedFetch) {
|
|
6515
|
+
window.fetch = this.originalFetch;
|
|
6516
|
+
}
|
|
6517
|
+
this.originalFetch = null;
|
|
6518
|
+
this.patchedFetch = null;
|
|
6519
|
+
clearPristineFetch();
|
|
6520
|
+
}
|
|
6521
|
+
/** fetch 와 같은 규칙 — 우리 래퍼가 아직 걸려 있을 때만 되돌린다. */
|
|
6522
|
+
restoreXHR() {
|
|
6523
|
+
const patched = this.patchedXHR;
|
|
6524
|
+
if (this.originalXHROpen && (!patched || XMLHttpRequest.prototype.open === patched.open)) {
|
|
6525
|
+
XMLHttpRequest.prototype.open = this.originalXHROpen;
|
|
6526
|
+
}
|
|
6527
|
+
if (this.originalXHRSend && (!patched || XMLHttpRequest.prototype.send === patched.send)) {
|
|
6528
|
+
XMLHttpRequest.prototype.send = this.originalXHRSend;
|
|
6529
|
+
}
|
|
6530
|
+
if (this.originalXHRSetRequestHeader && (!patched || XMLHttpRequest.prototype.setRequestHeader === patched.setRequestHeader)) {
|
|
6531
|
+
XMLHttpRequest.prototype.setRequestHeader = this.originalXHRSetRequestHeader;
|
|
6532
|
+
}
|
|
6533
|
+
this.originalXHROpen = null;
|
|
6534
|
+
this.originalXHRSend = null;
|
|
6535
|
+
this.originalXHRSetRequestHeader = null;
|
|
6536
|
+
this.patchedXHR = null;
|
|
5701
6537
|
}
|
|
5702
|
-
return {
|
|
5703
|
-
payload,
|
|
5704
|
-
checksumSha256: await sha256Hex(payload),
|
|
5705
|
-
sizeBytes: payload.byteLength,
|
|
5706
|
-
connectionCount: connections.length,
|
|
5707
|
-
frameCount
|
|
5708
|
-
};
|
|
5709
6538
|
}
|
|
5710
6539
|
var NetworkLogType = /* @__PURE__ */ ((NetworkLogType2) => {
|
|
5711
6540
|
NetworkLogType2["MAIN_FRAME"] = "main_frame";
|
|
@@ -6108,75 +6937,905 @@ function appendCustomFieldFilterGroups(groups, customFields) {
|
|
|
6108
6937
|
options
|
|
6109
6938
|
});
|
|
6110
6939
|
}
|
|
6111
|
-
}
|
|
6112
|
-
function appendJiraFieldFilterGroups(groups, exposedFieldIds, fields) {
|
|
6113
|
-
for (const field of fields) {
|
|
6114
|
-
const normalizedFieldId = normalizeJiraFieldId(field.id);
|
|
6115
|
-
if (!exposedFieldIds.has(normalizedFieldId) || !JIRA_FILTERABLE_FIELD_IDS.has(normalizedFieldId)) {
|
|
6116
|
-
continue;
|
|
6940
|
+
}
|
|
6941
|
+
function appendJiraFieldFilterGroups(groups, exposedFieldIds, fields) {
|
|
6942
|
+
for (const field of fields) {
|
|
6943
|
+
const normalizedFieldId = normalizeJiraFieldId(field.id);
|
|
6944
|
+
if (!exposedFieldIds.has(normalizedFieldId) || !JIRA_FILTERABLE_FIELD_IDS.has(normalizedFieldId)) {
|
|
6945
|
+
continue;
|
|
6946
|
+
}
|
|
6947
|
+
const options = getJiraFieldFilterOptions(field);
|
|
6948
|
+
if (!options.length) continue;
|
|
6949
|
+
upsertFilterGroup(groups, {
|
|
6950
|
+
kind: "jira",
|
|
6951
|
+
id: `jira:${normalizedFieldId}`,
|
|
6952
|
+
fieldName: normalizedFieldId,
|
|
6953
|
+
label: field.name,
|
|
6954
|
+
options
|
|
6955
|
+
});
|
|
6956
|
+
}
|
|
6957
|
+
}
|
|
6958
|
+
function appendNotionFieldFilterGroups(groups, selectedFields, fields) {
|
|
6959
|
+
for (const field of fields) {
|
|
6960
|
+
if (!selectedFields.has(field.id) && !selectedFields.has(field.name)) continue;
|
|
6961
|
+
if (!NOTION_FILTERABLE_FIELD_TYPES.has(field.type)) continue;
|
|
6962
|
+
const options = getNotionFieldFilterOptions(field);
|
|
6963
|
+
if (!options.length) continue;
|
|
6964
|
+
upsertFilterGroup(groups, {
|
|
6965
|
+
kind: "notion",
|
|
6966
|
+
id: `notion:${field.id}`,
|
|
6967
|
+
fieldName: field.name,
|
|
6968
|
+
label: field.name,
|
|
6969
|
+
options
|
|
6970
|
+
});
|
|
6971
|
+
}
|
|
6972
|
+
}
|
|
6973
|
+
function composeIssueFilterGroups(integrations, customFields) {
|
|
6974
|
+
const groups = /* @__PURE__ */ new Map();
|
|
6975
|
+
const jiraIntegration = integrations.find((integration) => {
|
|
6976
|
+
const config = getJiraConfig(integration);
|
|
6977
|
+
return integration.is_active !== false && config;
|
|
6978
|
+
});
|
|
6979
|
+
appendCustomFieldFilterGroups(groups, customFields);
|
|
6980
|
+
const jiraConfig = jiraIntegration ? getJiraConfig(jiraIntegration) : null;
|
|
6981
|
+
const configExposedFields = getExposedFields(jiraConfig);
|
|
6982
|
+
if (configExposedFields.length) {
|
|
6983
|
+
appendJiraFieldFilterGroups(
|
|
6984
|
+
groups,
|
|
6985
|
+
new Set(configExposedFields),
|
|
6986
|
+
getLastKnownJiraFields(jiraConfig)
|
|
6987
|
+
);
|
|
6988
|
+
}
|
|
6989
|
+
for (const integration of integrations) {
|
|
6990
|
+
if (integration.is_active === false) continue;
|
|
6991
|
+
const notionConfig = getNotionConfig(integration);
|
|
6992
|
+
if (!notionConfig) continue;
|
|
6993
|
+
appendNotionFieldFilterGroups(
|
|
6994
|
+
groups,
|
|
6995
|
+
new Set(getSelectedNotionFields(notionConfig)),
|
|
6996
|
+
getLastKnownNotionFields(notionConfig)
|
|
6997
|
+
);
|
|
6998
|
+
}
|
|
6999
|
+
return Array.from(groups.values()).sort((a2, b) => a2.label.localeCompare(b.label));
|
|
7000
|
+
}
|
|
7001
|
+
const IGNORED_NAME = /^(Primitive\.|Slot|Presence|Portal|Fragment|Suspense|StrictMode)|(Provider|Context|Consumer)$/;
|
|
7002
|
+
const MAX_DEPTH = 300;
|
|
7003
|
+
function fiberOf(element) {
|
|
7004
|
+
for (const key of Object.keys(element)) {
|
|
7005
|
+
if (key.startsWith("__reactFiber$") || key.startsWith("__reactInternalInstance$")) {
|
|
7006
|
+
return element[key] ?? null;
|
|
7007
|
+
}
|
|
7008
|
+
}
|
|
7009
|
+
return null;
|
|
7010
|
+
}
|
|
7011
|
+
function nameOf(type) {
|
|
7012
|
+
if (typeof type === "function") {
|
|
7013
|
+
const fn = type;
|
|
7014
|
+
return fn.displayName ?? fn.name ?? null;
|
|
7015
|
+
}
|
|
7016
|
+
if (type && typeof type === "object") {
|
|
7017
|
+
const wrapper = type;
|
|
7018
|
+
return wrapper.displayName ?? nameOf(wrapper.render) ?? nameOf(wrapper.type);
|
|
7019
|
+
}
|
|
7020
|
+
return null;
|
|
7021
|
+
}
|
|
7022
|
+
const reactAdapter = {
|
|
7023
|
+
framework: "react",
|
|
7024
|
+
collect(element) {
|
|
7025
|
+
let fiber = fiberOf(element);
|
|
7026
|
+
if (!fiber) return null;
|
|
7027
|
+
const names = [];
|
|
7028
|
+
for (let depth = 0; fiber && depth < MAX_DEPTH; depth += 1) {
|
|
7029
|
+
const name = nameOf(fiber.type);
|
|
7030
|
+
if (typeof name === "string" && name && !IGNORED_NAME.test(name)) names.unshift(name);
|
|
7031
|
+
fiber = fiber.return;
|
|
7032
|
+
}
|
|
7033
|
+
return names;
|
|
7034
|
+
}
|
|
7035
|
+
};
|
|
7036
|
+
const ADAPTERS = [reactAdapter];
|
|
7037
|
+
const MIN_NAME_LENGTH = 3;
|
|
7038
|
+
const MAX_NAME_LENGTH = 60;
|
|
7039
|
+
function cleanPath(names) {
|
|
7040
|
+
if (!names) return [];
|
|
7041
|
+
const kept = names.filter((name) => typeof name === "string").map(
|
|
7042
|
+
(name) => name.replace(/\s+/g, " ").trim().slice(0, MAX_NAME_LENGTH).replace(/[\uD800-\uDBFF]$/, "")
|
|
7043
|
+
).filter((name) => name.length >= MIN_NAME_LENGTH);
|
|
7044
|
+
const deduped = kept.filter((name, index) => name !== kept[index - 1]);
|
|
7045
|
+
return deduped.slice(-8);
|
|
7046
|
+
}
|
|
7047
|
+
function collectComponentPath(element, adapters = ADAPTERS) {
|
|
7048
|
+
for (const adapter of adapters) {
|
|
7049
|
+
let path = [];
|
|
7050
|
+
try {
|
|
7051
|
+
path = cleanPath(adapter.collect(element));
|
|
7052
|
+
} catch {
|
|
7053
|
+
path = [];
|
|
7054
|
+
}
|
|
7055
|
+
if (path.length) return { framework: adapter.framework, path };
|
|
7056
|
+
}
|
|
7057
|
+
return null;
|
|
7058
|
+
}
|
|
7059
|
+
var toStr = Object.prototype.toString;
|
|
7060
|
+
function isCallable(fn) {
|
|
7061
|
+
return typeof fn === "function" || toStr.call(fn) === "[object Function]";
|
|
7062
|
+
}
|
|
7063
|
+
function toInteger(value) {
|
|
7064
|
+
var number = Number(value);
|
|
7065
|
+
if (isNaN(number)) {
|
|
7066
|
+
return 0;
|
|
7067
|
+
}
|
|
7068
|
+
if (number === 0 || !isFinite(number)) {
|
|
7069
|
+
return number;
|
|
7070
|
+
}
|
|
7071
|
+
return (number > 0 ? 1 : -1) * Math.floor(Math.abs(number));
|
|
7072
|
+
}
|
|
7073
|
+
var maxSafeInteger = Math.pow(2, 53) - 1;
|
|
7074
|
+
function toLength(value) {
|
|
7075
|
+
var len = toInteger(value);
|
|
7076
|
+
return Math.min(Math.max(len, 0), maxSafeInteger);
|
|
7077
|
+
}
|
|
7078
|
+
function arrayFrom(arrayLike, mapFn) {
|
|
7079
|
+
var C = Array;
|
|
7080
|
+
var items = Object(arrayLike);
|
|
7081
|
+
if (arrayLike == null) {
|
|
7082
|
+
throw new TypeError("Array.from requires an array-like object - not null or undefined");
|
|
7083
|
+
}
|
|
7084
|
+
var len = toLength(items.length);
|
|
7085
|
+
var A = isCallable(C) ? Object(new C(len)) : new Array(len);
|
|
7086
|
+
var k = 0;
|
|
7087
|
+
var kValue;
|
|
7088
|
+
while (k < len) {
|
|
7089
|
+
kValue = items[k];
|
|
7090
|
+
{
|
|
7091
|
+
A[k] = kValue;
|
|
7092
|
+
}
|
|
7093
|
+
k += 1;
|
|
7094
|
+
}
|
|
7095
|
+
A.length = len;
|
|
7096
|
+
return A;
|
|
7097
|
+
}
|
|
7098
|
+
function _typeof(o2) {
|
|
7099
|
+
"@babel/helpers - typeof";
|
|
7100
|
+
return _typeof = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function(o3) {
|
|
7101
|
+
return typeof o3;
|
|
7102
|
+
} : function(o3) {
|
|
7103
|
+
return o3 && "function" == typeof Symbol && o3.constructor === Symbol && o3 !== Symbol.prototype ? "symbol" : typeof o3;
|
|
7104
|
+
}, _typeof(o2);
|
|
7105
|
+
}
|
|
7106
|
+
function _classCallCheck(instance, Constructor) {
|
|
7107
|
+
if (!(instance instanceof Constructor)) {
|
|
7108
|
+
throw new TypeError("Cannot call a class as a function");
|
|
7109
|
+
}
|
|
7110
|
+
}
|
|
7111
|
+
function _defineProperties(target, props) {
|
|
7112
|
+
for (var i2 = 0; i2 < props.length; i2++) {
|
|
7113
|
+
var descriptor = props[i2];
|
|
7114
|
+
descriptor.enumerable = descriptor.enumerable || false;
|
|
7115
|
+
descriptor.configurable = true;
|
|
7116
|
+
if ("value" in descriptor) descriptor.writable = true;
|
|
7117
|
+
Object.defineProperty(target, _toPropertyKey(descriptor.key), descriptor);
|
|
7118
|
+
}
|
|
7119
|
+
}
|
|
7120
|
+
function _createClass(Constructor, protoProps, staticProps) {
|
|
7121
|
+
if (protoProps) _defineProperties(Constructor.prototype, protoProps);
|
|
7122
|
+
Object.defineProperty(Constructor, "prototype", { writable: false });
|
|
7123
|
+
return Constructor;
|
|
7124
|
+
}
|
|
7125
|
+
function _defineProperty(obj, key, value) {
|
|
7126
|
+
key = _toPropertyKey(key);
|
|
7127
|
+
if (key in obj) {
|
|
7128
|
+
Object.defineProperty(obj, key, { value, enumerable: true, configurable: true, writable: true });
|
|
7129
|
+
} else {
|
|
7130
|
+
obj[key] = value;
|
|
7131
|
+
}
|
|
7132
|
+
return obj;
|
|
7133
|
+
}
|
|
7134
|
+
function _toPropertyKey(t2) {
|
|
7135
|
+
var i2 = _toPrimitive(t2, "string");
|
|
7136
|
+
return "symbol" == _typeof(i2) ? i2 : i2 + "";
|
|
7137
|
+
}
|
|
7138
|
+
function _toPrimitive(t2, r2) {
|
|
7139
|
+
if ("object" != _typeof(t2) || !t2) return t2;
|
|
7140
|
+
var e2 = t2[Symbol.toPrimitive];
|
|
7141
|
+
if (void 0 !== e2) {
|
|
7142
|
+
var i2 = e2.call(t2, r2);
|
|
7143
|
+
if ("object" != _typeof(i2)) return i2;
|
|
7144
|
+
throw new TypeError("@@toPrimitive must return a primitive value.");
|
|
7145
|
+
}
|
|
7146
|
+
return String(t2);
|
|
7147
|
+
}
|
|
7148
|
+
var SetLike = /* @__PURE__ */ (function() {
|
|
7149
|
+
function SetLike2() {
|
|
7150
|
+
var items = arguments.length > 0 && arguments[0] !== void 0 ? arguments[0] : [];
|
|
7151
|
+
_classCallCheck(this, SetLike2);
|
|
7152
|
+
_defineProperty(this, "items", void 0);
|
|
7153
|
+
this.items = items;
|
|
7154
|
+
}
|
|
7155
|
+
return _createClass(SetLike2, [{
|
|
7156
|
+
key: "add",
|
|
7157
|
+
value: function add(value) {
|
|
7158
|
+
if (this.has(value) === false) {
|
|
7159
|
+
this.items.push(value);
|
|
7160
|
+
}
|
|
7161
|
+
return this;
|
|
7162
|
+
}
|
|
7163
|
+
}, {
|
|
7164
|
+
key: "clear",
|
|
7165
|
+
value: function clear() {
|
|
7166
|
+
this.items = [];
|
|
7167
|
+
}
|
|
7168
|
+
}, {
|
|
7169
|
+
key: "delete",
|
|
7170
|
+
value: function _delete(value) {
|
|
7171
|
+
var previousLength = this.items.length;
|
|
7172
|
+
this.items = this.items.filter(function(item) {
|
|
7173
|
+
return item !== value;
|
|
7174
|
+
});
|
|
7175
|
+
return previousLength !== this.items.length;
|
|
7176
|
+
}
|
|
7177
|
+
}, {
|
|
7178
|
+
key: "forEach",
|
|
7179
|
+
value: function forEach(callbackfn) {
|
|
7180
|
+
var _this = this;
|
|
7181
|
+
this.items.forEach(function(item) {
|
|
7182
|
+
callbackfn(item, item, _this);
|
|
7183
|
+
});
|
|
7184
|
+
}
|
|
7185
|
+
}, {
|
|
7186
|
+
key: "has",
|
|
7187
|
+
value: function has(value) {
|
|
7188
|
+
return this.items.indexOf(value) !== -1;
|
|
7189
|
+
}
|
|
7190
|
+
}, {
|
|
7191
|
+
key: "size",
|
|
7192
|
+
get: function get() {
|
|
7193
|
+
return this.items.length;
|
|
7194
|
+
}
|
|
7195
|
+
}]);
|
|
7196
|
+
})();
|
|
7197
|
+
const SetLike$1 = typeof Set === "undefined" ? Set : SetLike;
|
|
7198
|
+
function getLocalName(element) {
|
|
7199
|
+
var _element$localName;
|
|
7200
|
+
return (
|
|
7201
|
+
// eslint-disable-next-line no-restricted-properties -- actual guard for environments without localName
|
|
7202
|
+
(_element$localName = element.localName) !== null && _element$localName !== void 0 ? _element$localName : (
|
|
7203
|
+
// eslint-disable-next-line no-restricted-properties -- required for the fallback
|
|
7204
|
+
element.tagName.toLowerCase()
|
|
7205
|
+
)
|
|
7206
|
+
);
|
|
7207
|
+
}
|
|
7208
|
+
var localNameToRoleMappings = {
|
|
7209
|
+
article: "article",
|
|
7210
|
+
aside: "complementary",
|
|
7211
|
+
button: "button",
|
|
7212
|
+
datalist: "listbox",
|
|
7213
|
+
dd: "definition",
|
|
7214
|
+
details: "group",
|
|
7215
|
+
dialog: "dialog",
|
|
7216
|
+
dt: "term",
|
|
7217
|
+
fieldset: "group",
|
|
7218
|
+
figure: "figure",
|
|
7219
|
+
// WARNING: Only with an accessible name
|
|
7220
|
+
form: "form",
|
|
7221
|
+
footer: "contentinfo",
|
|
7222
|
+
h1: "heading",
|
|
7223
|
+
h2: "heading",
|
|
7224
|
+
h3: "heading",
|
|
7225
|
+
h4: "heading",
|
|
7226
|
+
h5: "heading",
|
|
7227
|
+
h6: "heading",
|
|
7228
|
+
header: "banner",
|
|
7229
|
+
hr: "separator",
|
|
7230
|
+
html: "document",
|
|
7231
|
+
legend: "legend",
|
|
7232
|
+
li: "listitem",
|
|
7233
|
+
math: "math",
|
|
7234
|
+
main: "main",
|
|
7235
|
+
menu: "list",
|
|
7236
|
+
nav: "navigation",
|
|
7237
|
+
ol: "list",
|
|
7238
|
+
optgroup: "group",
|
|
7239
|
+
// WARNING: Only in certain context
|
|
7240
|
+
option: "option",
|
|
7241
|
+
output: "status",
|
|
7242
|
+
progress: "progressbar",
|
|
7243
|
+
// WARNING: Only with an accessible name
|
|
7244
|
+
section: "region",
|
|
7245
|
+
summary: "button",
|
|
7246
|
+
table: "table",
|
|
7247
|
+
tbody: "rowgroup",
|
|
7248
|
+
textarea: "textbox",
|
|
7249
|
+
tfoot: "rowgroup",
|
|
7250
|
+
// WARNING: Only in certain context
|
|
7251
|
+
td: "cell",
|
|
7252
|
+
th: "columnheader",
|
|
7253
|
+
thead: "rowgroup",
|
|
7254
|
+
tr: "row",
|
|
7255
|
+
ul: "list"
|
|
7256
|
+
};
|
|
7257
|
+
var prohibitedAttributes = {
|
|
7258
|
+
caption: /* @__PURE__ */ new Set(["aria-label", "aria-labelledby"]),
|
|
7259
|
+
code: /* @__PURE__ */ new Set(["aria-label", "aria-labelledby"]),
|
|
7260
|
+
deletion: /* @__PURE__ */ new Set(["aria-label", "aria-labelledby"]),
|
|
7261
|
+
emphasis: /* @__PURE__ */ new Set(["aria-label", "aria-labelledby"]),
|
|
7262
|
+
generic: /* @__PURE__ */ new Set(["aria-label", "aria-labelledby", "aria-roledescription"]),
|
|
7263
|
+
insertion: /* @__PURE__ */ new Set(["aria-label", "aria-labelledby"]),
|
|
7264
|
+
none: /* @__PURE__ */ new Set(["aria-label", "aria-labelledby"]),
|
|
7265
|
+
paragraph: /* @__PURE__ */ new Set(["aria-label", "aria-labelledby"]),
|
|
7266
|
+
presentation: /* @__PURE__ */ new Set(["aria-label", "aria-labelledby"]),
|
|
7267
|
+
strong: /* @__PURE__ */ new Set(["aria-label", "aria-labelledby"]),
|
|
7268
|
+
subscript: /* @__PURE__ */ new Set(["aria-label", "aria-labelledby"]),
|
|
7269
|
+
superscript: /* @__PURE__ */ new Set(["aria-label", "aria-labelledby"])
|
|
7270
|
+
};
|
|
7271
|
+
function hasGlobalAriaAttributes(element, role) {
|
|
7272
|
+
return [
|
|
7273
|
+
"aria-atomic",
|
|
7274
|
+
"aria-busy",
|
|
7275
|
+
"aria-controls",
|
|
7276
|
+
"aria-current",
|
|
7277
|
+
"aria-description",
|
|
7278
|
+
"aria-describedby",
|
|
7279
|
+
"aria-details",
|
|
7280
|
+
// "disabled",
|
|
7281
|
+
"aria-dropeffect",
|
|
7282
|
+
// "errormessage",
|
|
7283
|
+
"aria-flowto",
|
|
7284
|
+
"aria-grabbed",
|
|
7285
|
+
// "haspopup",
|
|
7286
|
+
"aria-hidden",
|
|
7287
|
+
// "invalid",
|
|
7288
|
+
"aria-keyshortcuts",
|
|
7289
|
+
"aria-label",
|
|
7290
|
+
"aria-labelledby",
|
|
7291
|
+
"aria-live",
|
|
7292
|
+
"aria-owns",
|
|
7293
|
+
"aria-relevant",
|
|
7294
|
+
"aria-roledescription"
|
|
7295
|
+
].some(function(attributeName) {
|
|
7296
|
+
var _prohibitedAttributes;
|
|
7297
|
+
return element.hasAttribute(attributeName) && !((_prohibitedAttributes = prohibitedAttributes[role]) !== null && _prohibitedAttributes !== void 0 && _prohibitedAttributes.has(attributeName));
|
|
7298
|
+
});
|
|
7299
|
+
}
|
|
7300
|
+
function ignorePresentationalRole(element, implicitRole) {
|
|
7301
|
+
return hasGlobalAriaAttributes(element, implicitRole);
|
|
7302
|
+
}
|
|
7303
|
+
function getRole(element) {
|
|
7304
|
+
var explicitRole = getExplicitRole(element);
|
|
7305
|
+
if (explicitRole === null || presentationRoles.indexOf(explicitRole) !== -1) {
|
|
7306
|
+
var implicitRole = getImplicitRole(element);
|
|
7307
|
+
if (presentationRoles.indexOf(explicitRole || "") === -1 || ignorePresentationalRole(element, implicitRole || "")) {
|
|
7308
|
+
return implicitRole;
|
|
7309
|
+
}
|
|
7310
|
+
}
|
|
7311
|
+
return explicitRole;
|
|
7312
|
+
}
|
|
7313
|
+
function getImplicitRole(element) {
|
|
7314
|
+
var mappedByTag = localNameToRoleMappings[getLocalName(element)];
|
|
7315
|
+
if (mappedByTag !== void 0) {
|
|
7316
|
+
return mappedByTag;
|
|
7317
|
+
}
|
|
7318
|
+
switch (getLocalName(element)) {
|
|
7319
|
+
case "a":
|
|
7320
|
+
case "area":
|
|
7321
|
+
case "link":
|
|
7322
|
+
if (element.hasAttribute("href")) {
|
|
7323
|
+
return "link";
|
|
7324
|
+
}
|
|
7325
|
+
break;
|
|
7326
|
+
case "img":
|
|
7327
|
+
if (element.getAttribute("alt") === "" && !ignorePresentationalRole(element, "img")) {
|
|
7328
|
+
return "presentation";
|
|
7329
|
+
}
|
|
7330
|
+
return "img";
|
|
7331
|
+
case "input": {
|
|
7332
|
+
var _ref = element, type = _ref.type;
|
|
7333
|
+
switch (type) {
|
|
7334
|
+
case "button":
|
|
7335
|
+
case "image":
|
|
7336
|
+
case "reset":
|
|
7337
|
+
case "submit":
|
|
7338
|
+
return "button";
|
|
7339
|
+
case "checkbox":
|
|
7340
|
+
case "radio":
|
|
7341
|
+
return type;
|
|
7342
|
+
case "range":
|
|
7343
|
+
return "slider";
|
|
7344
|
+
case "email":
|
|
7345
|
+
case "tel":
|
|
7346
|
+
case "text":
|
|
7347
|
+
case "url":
|
|
7348
|
+
if (element.hasAttribute("list")) {
|
|
7349
|
+
return "combobox";
|
|
7350
|
+
}
|
|
7351
|
+
return "textbox";
|
|
7352
|
+
case "search":
|
|
7353
|
+
if (element.hasAttribute("list")) {
|
|
7354
|
+
return "combobox";
|
|
7355
|
+
}
|
|
7356
|
+
return "searchbox";
|
|
7357
|
+
case "number":
|
|
7358
|
+
return "spinbutton";
|
|
7359
|
+
default:
|
|
7360
|
+
return null;
|
|
7361
|
+
}
|
|
7362
|
+
}
|
|
7363
|
+
case "select":
|
|
7364
|
+
if (element.hasAttribute("multiple") || element.size > 1) {
|
|
7365
|
+
return "listbox";
|
|
7366
|
+
}
|
|
7367
|
+
return "combobox";
|
|
7368
|
+
}
|
|
7369
|
+
return null;
|
|
7370
|
+
}
|
|
7371
|
+
function getExplicitRole(element) {
|
|
7372
|
+
var role = element.getAttribute("role");
|
|
7373
|
+
if (role !== null) {
|
|
7374
|
+
var explicitRole = role.trim().split(" ")[0];
|
|
7375
|
+
if (explicitRole.length > 0) {
|
|
7376
|
+
return explicitRole;
|
|
7377
|
+
}
|
|
7378
|
+
}
|
|
7379
|
+
return null;
|
|
7380
|
+
}
|
|
7381
|
+
var presentationRoles = ["presentation", "none"];
|
|
7382
|
+
function isElement(node) {
|
|
7383
|
+
return node !== null && node.nodeType === node.ELEMENT_NODE;
|
|
7384
|
+
}
|
|
7385
|
+
function isHTMLTableCaptionElement(node) {
|
|
7386
|
+
return isElement(node) && getLocalName(node) === "caption";
|
|
7387
|
+
}
|
|
7388
|
+
function isHTMLInputElement(node) {
|
|
7389
|
+
return isElement(node) && getLocalName(node) === "input";
|
|
7390
|
+
}
|
|
7391
|
+
function isHTMLOptGroupElement(node) {
|
|
7392
|
+
return isElement(node) && getLocalName(node) === "optgroup";
|
|
7393
|
+
}
|
|
7394
|
+
function isHTMLSelectElement(node) {
|
|
7395
|
+
return isElement(node) && getLocalName(node) === "select";
|
|
7396
|
+
}
|
|
7397
|
+
function isHTMLTableElement(node) {
|
|
7398
|
+
return isElement(node) && getLocalName(node) === "table";
|
|
7399
|
+
}
|
|
7400
|
+
function isHTMLTextAreaElement(node) {
|
|
7401
|
+
return isElement(node) && getLocalName(node) === "textarea";
|
|
7402
|
+
}
|
|
7403
|
+
function safeWindow(node) {
|
|
7404
|
+
var _ref = node.ownerDocument === null ? node : node.ownerDocument, defaultView = _ref.defaultView;
|
|
7405
|
+
if (defaultView === null) {
|
|
7406
|
+
throw new TypeError("no window available");
|
|
7407
|
+
}
|
|
7408
|
+
return defaultView;
|
|
7409
|
+
}
|
|
7410
|
+
function isHTMLFieldSetElement(node) {
|
|
7411
|
+
return isElement(node) && getLocalName(node) === "fieldset";
|
|
7412
|
+
}
|
|
7413
|
+
function isHTMLLegendElement(node) {
|
|
7414
|
+
return isElement(node) && getLocalName(node) === "legend";
|
|
7415
|
+
}
|
|
7416
|
+
function isHTMLSlotElement(node) {
|
|
7417
|
+
return isElement(node) && getLocalName(node) === "slot";
|
|
7418
|
+
}
|
|
7419
|
+
function isSVGElement(node) {
|
|
7420
|
+
return isElement(node) && node.ownerSVGElement !== void 0;
|
|
7421
|
+
}
|
|
7422
|
+
function isSVGSVGElement(node) {
|
|
7423
|
+
return isElement(node) && getLocalName(node) === "svg";
|
|
7424
|
+
}
|
|
7425
|
+
function isSVGTitleElement(node) {
|
|
7426
|
+
return isSVGElement(node) && getLocalName(node) === "title";
|
|
7427
|
+
}
|
|
7428
|
+
function queryIdRefs(node, attributeName) {
|
|
7429
|
+
if (isElement(node) && node.hasAttribute(attributeName)) {
|
|
7430
|
+
var ids = node.getAttribute(attributeName).split(" ");
|
|
7431
|
+
var root = node.getRootNode ? node.getRootNode() : node.ownerDocument;
|
|
7432
|
+
return ids.map(function(id) {
|
|
7433
|
+
return root.getElementById(id);
|
|
7434
|
+
}).filter(
|
|
7435
|
+
function(element) {
|
|
7436
|
+
return element !== null;
|
|
7437
|
+
}
|
|
7438
|
+
// TODO: why does this not narrow?
|
|
7439
|
+
);
|
|
7440
|
+
}
|
|
7441
|
+
return [];
|
|
7442
|
+
}
|
|
7443
|
+
function hasAnyConcreteRoles(node, roles) {
|
|
7444
|
+
if (isElement(node)) {
|
|
7445
|
+
return roles.indexOf(getRole(node)) !== -1;
|
|
7446
|
+
}
|
|
7447
|
+
return false;
|
|
7448
|
+
}
|
|
7449
|
+
function asFlatString(s2) {
|
|
7450
|
+
return s2.trim().replace(/\s\s+/g, " ");
|
|
7451
|
+
}
|
|
7452
|
+
function isHidden$1(node, getComputedStyleImplementation) {
|
|
7453
|
+
if (!isElement(node)) {
|
|
7454
|
+
return false;
|
|
7455
|
+
}
|
|
7456
|
+
if (node.hasAttribute("hidden") || node.getAttribute("aria-hidden") === "true") {
|
|
7457
|
+
return true;
|
|
7458
|
+
}
|
|
7459
|
+
var style = getComputedStyleImplementation(node);
|
|
7460
|
+
return style.getPropertyValue("display") === "none" || style.getPropertyValue("visibility") === "hidden";
|
|
7461
|
+
}
|
|
7462
|
+
function isControl(node) {
|
|
7463
|
+
return hasAnyConcreteRoles(node, ["button", "combobox", "listbox", "textbox"]) || hasAbstractRole(node, "range");
|
|
7464
|
+
}
|
|
7465
|
+
function hasAbstractRole(node, role) {
|
|
7466
|
+
if (!isElement(node)) {
|
|
7467
|
+
return false;
|
|
7468
|
+
}
|
|
7469
|
+
switch (role) {
|
|
7470
|
+
case "range":
|
|
7471
|
+
return hasAnyConcreteRoles(node, ["meter", "progressbar", "scrollbar", "slider", "spinbutton"]);
|
|
7472
|
+
default:
|
|
7473
|
+
throw new TypeError("No knowledge about abstract role '".concat(role, "'. This is likely a bug :("));
|
|
7474
|
+
}
|
|
7475
|
+
}
|
|
7476
|
+
function querySelectorAllSubtree(element, selectors) {
|
|
7477
|
+
var elements = arrayFrom(element.querySelectorAll(selectors));
|
|
7478
|
+
queryIdRefs(element, "aria-owns").forEach(function(root) {
|
|
7479
|
+
elements.push.apply(elements, arrayFrom(root.querySelectorAll(selectors)));
|
|
7480
|
+
});
|
|
7481
|
+
return elements;
|
|
7482
|
+
}
|
|
7483
|
+
function querySelectedOptions(listbox) {
|
|
7484
|
+
if (isHTMLSelectElement(listbox)) {
|
|
7485
|
+
return listbox.selectedOptions || querySelectorAllSubtree(listbox, "[selected]");
|
|
7486
|
+
}
|
|
7487
|
+
return querySelectorAllSubtree(listbox, '[aria-selected="true"]');
|
|
7488
|
+
}
|
|
7489
|
+
function isMarkedPresentational(node) {
|
|
7490
|
+
return hasAnyConcreteRoles(node, presentationRoles);
|
|
7491
|
+
}
|
|
7492
|
+
function isNativeHostLanguageTextAlternativeElement(node) {
|
|
7493
|
+
return isHTMLTableCaptionElement(node);
|
|
7494
|
+
}
|
|
7495
|
+
function allowsNameFromContent(node) {
|
|
7496
|
+
return hasAnyConcreteRoles(node, ["button", "cell", "checkbox", "columnheader", "gridcell", "heading", "label", "legend", "link", "menuitem", "menuitemcheckbox", "menuitemradio", "option", "radio", "row", "rowheader", "switch", "tab", "tooltip", "treeitem"]);
|
|
7497
|
+
}
|
|
7498
|
+
function isDescendantOfNativeHostLanguageTextAlternativeElement(node) {
|
|
7499
|
+
return false;
|
|
7500
|
+
}
|
|
7501
|
+
function getValueOfTextbox(element) {
|
|
7502
|
+
if (isHTMLInputElement(element) || isHTMLTextAreaElement(element)) {
|
|
7503
|
+
return element.value;
|
|
7504
|
+
}
|
|
7505
|
+
return element.textContent || "";
|
|
7506
|
+
}
|
|
7507
|
+
function getTextualContent(declaration) {
|
|
7508
|
+
var content = declaration.getPropertyValue("content");
|
|
7509
|
+
if (/^["'].*["']$/.test(content)) {
|
|
7510
|
+
return content.slice(1, -1);
|
|
7511
|
+
}
|
|
7512
|
+
return "";
|
|
7513
|
+
}
|
|
7514
|
+
function isLabelableElement(element) {
|
|
7515
|
+
var localName = getLocalName(element);
|
|
7516
|
+
return localName === "button" || localName === "input" && element.getAttribute("type") !== "hidden" || localName === "meter" || localName === "output" || localName === "progress" || localName === "select" || localName === "textarea";
|
|
7517
|
+
}
|
|
7518
|
+
function findLabelableElement(element) {
|
|
7519
|
+
if (isLabelableElement(element)) {
|
|
7520
|
+
return element;
|
|
7521
|
+
}
|
|
7522
|
+
var labelableElement = null;
|
|
7523
|
+
element.childNodes.forEach(function(childNode) {
|
|
7524
|
+
if (labelableElement === null && isElement(childNode)) {
|
|
7525
|
+
var descendantLabelableElement = findLabelableElement(childNode);
|
|
7526
|
+
if (descendantLabelableElement !== null) {
|
|
7527
|
+
labelableElement = descendantLabelableElement;
|
|
7528
|
+
}
|
|
7529
|
+
}
|
|
7530
|
+
});
|
|
7531
|
+
return labelableElement;
|
|
7532
|
+
}
|
|
7533
|
+
function getControlOfLabel(label) {
|
|
7534
|
+
if (label.control !== void 0) {
|
|
7535
|
+
return label.control;
|
|
7536
|
+
}
|
|
7537
|
+
var htmlFor = label.getAttribute("for");
|
|
7538
|
+
if (htmlFor !== null) {
|
|
7539
|
+
return label.ownerDocument.getElementById(htmlFor);
|
|
7540
|
+
}
|
|
7541
|
+
return findLabelableElement(label);
|
|
7542
|
+
}
|
|
7543
|
+
function getLabels$1(element) {
|
|
7544
|
+
var labelsProperty = element.labels;
|
|
7545
|
+
if (labelsProperty === null) {
|
|
7546
|
+
return labelsProperty;
|
|
7547
|
+
}
|
|
7548
|
+
if (labelsProperty !== void 0) {
|
|
7549
|
+
return arrayFrom(labelsProperty);
|
|
7550
|
+
}
|
|
7551
|
+
if (!isLabelableElement(element)) {
|
|
7552
|
+
return null;
|
|
7553
|
+
}
|
|
7554
|
+
var document2 = element.ownerDocument;
|
|
7555
|
+
return arrayFrom(document2.querySelectorAll("label")).filter(function(label) {
|
|
7556
|
+
return getControlOfLabel(label) === element;
|
|
7557
|
+
});
|
|
7558
|
+
}
|
|
7559
|
+
function getSlotContents(slot) {
|
|
7560
|
+
var assignedNodes = slot.assignedNodes();
|
|
7561
|
+
if (assignedNodes.length === 0) {
|
|
7562
|
+
return arrayFrom(slot.childNodes);
|
|
7563
|
+
}
|
|
7564
|
+
return assignedNodes;
|
|
7565
|
+
}
|
|
7566
|
+
function computeTextAlternative(root) {
|
|
7567
|
+
var options = arguments.length > 1 && arguments[1] !== void 0 ? arguments[1] : {};
|
|
7568
|
+
var consultedNodes = new SetLike$1();
|
|
7569
|
+
var computedStyles = typeof Map === "undefined" ? void 0 : /* @__PURE__ */ new Map();
|
|
7570
|
+
var window2 = safeWindow(root);
|
|
7571
|
+
var _options$compute = options.compute, compute = _options$compute === void 0 ? "name" : _options$compute, _options$computedStyl = options.computedStyleSupportsPseudoElements, computedStyleSupportsPseudoElements = _options$computedStyl === void 0 ? options.getComputedStyle !== void 0 : _options$computedStyl, _options$getComputedS = options.getComputedStyle, uncachedGetComputedStyle = _options$getComputedS === void 0 ? window2.getComputedStyle.bind(window2) : _options$getComputedS, _options$hidden = options.hidden, hidden = _options$hidden === void 0 ? false : _options$hidden;
|
|
7572
|
+
var getComputedStyle2 = function getComputedStyle3(el, pseudoElement) {
|
|
7573
|
+
if (pseudoElement !== void 0) {
|
|
7574
|
+
throw new Error("use uncachedGetComputedStyle directly for pseudo elements");
|
|
7575
|
+
}
|
|
7576
|
+
if (computedStyles === void 0) {
|
|
7577
|
+
return uncachedGetComputedStyle(el);
|
|
7578
|
+
}
|
|
7579
|
+
var cachedStyles = computedStyles.get(el);
|
|
7580
|
+
if (cachedStyles) {
|
|
7581
|
+
return cachedStyles;
|
|
7582
|
+
}
|
|
7583
|
+
var style = uncachedGetComputedStyle(el, pseudoElement);
|
|
7584
|
+
computedStyles.set(el, style);
|
|
7585
|
+
return style;
|
|
7586
|
+
};
|
|
7587
|
+
function computeMiscTextAlternative(node, context) {
|
|
7588
|
+
var accumulatedText = "";
|
|
7589
|
+
if (isElement(node) && computedStyleSupportsPseudoElements) {
|
|
7590
|
+
var pseudoBefore = uncachedGetComputedStyle(node, "::before");
|
|
7591
|
+
var beforeContent = getTextualContent(pseudoBefore);
|
|
7592
|
+
accumulatedText = "".concat(beforeContent, " ").concat(accumulatedText);
|
|
7593
|
+
}
|
|
7594
|
+
var childNodes = isHTMLSlotElement(node) ? getSlotContents(node) : arrayFrom(node.childNodes).concat(queryIdRefs(node, "aria-owns"));
|
|
7595
|
+
childNodes.forEach(function(child) {
|
|
7596
|
+
var result = computeTextAlternative2(child, {
|
|
7597
|
+
isEmbeddedInLabel: context.isEmbeddedInLabel,
|
|
7598
|
+
isReferenced: false,
|
|
7599
|
+
recursion: true
|
|
7600
|
+
});
|
|
7601
|
+
var display = isElement(child) ? getComputedStyle2(child).getPropertyValue("display") : "inline";
|
|
7602
|
+
var separator = display !== "inline" ? " " : "";
|
|
7603
|
+
accumulatedText += "".concat(separator).concat(result).concat(separator);
|
|
7604
|
+
});
|
|
7605
|
+
if (isElement(node) && computedStyleSupportsPseudoElements) {
|
|
7606
|
+
var pseudoAfter = uncachedGetComputedStyle(node, "::after");
|
|
7607
|
+
var afterContent = getTextualContent(pseudoAfter);
|
|
7608
|
+
accumulatedText = "".concat(accumulatedText, " ").concat(afterContent);
|
|
7609
|
+
}
|
|
7610
|
+
return accumulatedText.trim();
|
|
7611
|
+
}
|
|
7612
|
+
function useAttribute(element, attributeName) {
|
|
7613
|
+
var attribute = element.getAttributeNode(attributeName);
|
|
7614
|
+
if (attribute !== null && !consultedNodes.has(attribute) && attribute.value.trim() !== "") {
|
|
7615
|
+
consultedNodes.add(attribute);
|
|
7616
|
+
return attribute.value;
|
|
7617
|
+
}
|
|
7618
|
+
return null;
|
|
7619
|
+
}
|
|
7620
|
+
function computeTooltipAttributeValue(node) {
|
|
7621
|
+
if (!isElement(node)) {
|
|
7622
|
+
return null;
|
|
7623
|
+
}
|
|
7624
|
+
return useAttribute(node, "title");
|
|
7625
|
+
}
|
|
7626
|
+
function computeElementTextAlternative(node) {
|
|
7627
|
+
if (!isElement(node)) {
|
|
7628
|
+
return null;
|
|
7629
|
+
}
|
|
7630
|
+
if (isHTMLFieldSetElement(node)) {
|
|
7631
|
+
consultedNodes.add(node);
|
|
7632
|
+
var children = arrayFrom(node.childNodes);
|
|
7633
|
+
for (var i2 = 0; i2 < children.length; i2 += 1) {
|
|
7634
|
+
var child = children[i2];
|
|
7635
|
+
if (isHTMLLegendElement(child)) {
|
|
7636
|
+
return computeTextAlternative2(child, {
|
|
7637
|
+
isEmbeddedInLabel: false,
|
|
7638
|
+
isReferenced: false,
|
|
7639
|
+
recursion: false
|
|
7640
|
+
});
|
|
7641
|
+
}
|
|
7642
|
+
}
|
|
7643
|
+
} else if (isHTMLTableElement(node)) {
|
|
7644
|
+
consultedNodes.add(node);
|
|
7645
|
+
var _children = arrayFrom(node.childNodes);
|
|
7646
|
+
for (var _i = 0; _i < _children.length; _i += 1) {
|
|
7647
|
+
var _child = _children[_i];
|
|
7648
|
+
if (isHTMLTableCaptionElement(_child)) {
|
|
7649
|
+
return computeTextAlternative2(_child, {
|
|
7650
|
+
isEmbeddedInLabel: false,
|
|
7651
|
+
isReferenced: false,
|
|
7652
|
+
recursion: false
|
|
7653
|
+
});
|
|
7654
|
+
}
|
|
7655
|
+
}
|
|
7656
|
+
} else if (isSVGSVGElement(node)) {
|
|
7657
|
+
consultedNodes.add(node);
|
|
7658
|
+
var _children2 = arrayFrom(node.childNodes);
|
|
7659
|
+
for (var _i2 = 0; _i2 < _children2.length; _i2 += 1) {
|
|
7660
|
+
var _child2 = _children2[_i2];
|
|
7661
|
+
if (isSVGTitleElement(_child2)) {
|
|
7662
|
+
return _child2.textContent;
|
|
7663
|
+
}
|
|
7664
|
+
}
|
|
7665
|
+
return null;
|
|
7666
|
+
} else if (getLocalName(node) === "img" || getLocalName(node) === "area") {
|
|
7667
|
+
var nameFromAlt = useAttribute(node, "alt");
|
|
7668
|
+
if (nameFromAlt !== null) {
|
|
7669
|
+
return nameFromAlt;
|
|
7670
|
+
}
|
|
7671
|
+
} else if (isHTMLOptGroupElement(node)) {
|
|
7672
|
+
var nameFromLabel = useAttribute(node, "label");
|
|
7673
|
+
if (nameFromLabel !== null) {
|
|
7674
|
+
return nameFromLabel;
|
|
7675
|
+
}
|
|
7676
|
+
}
|
|
7677
|
+
if (isHTMLInputElement(node) && (node.type === "button" || node.type === "submit" || node.type === "reset")) {
|
|
7678
|
+
var nameFromValue = useAttribute(node, "value");
|
|
7679
|
+
if (nameFromValue !== null) {
|
|
7680
|
+
return nameFromValue;
|
|
7681
|
+
}
|
|
7682
|
+
if (node.type === "submit") {
|
|
7683
|
+
return "Submit";
|
|
7684
|
+
}
|
|
7685
|
+
if (node.type === "reset") {
|
|
7686
|
+
return "Reset";
|
|
7687
|
+
}
|
|
7688
|
+
}
|
|
7689
|
+
var labels = getLabels$1(node);
|
|
7690
|
+
if (labels !== null && labels.length !== 0) {
|
|
7691
|
+
consultedNodes.add(node);
|
|
7692
|
+
return arrayFrom(labels).map(function(element) {
|
|
7693
|
+
return computeTextAlternative2(element, {
|
|
7694
|
+
isEmbeddedInLabel: true,
|
|
7695
|
+
isReferenced: false,
|
|
7696
|
+
recursion: true
|
|
7697
|
+
});
|
|
7698
|
+
}).filter(function(label) {
|
|
7699
|
+
return label.length > 0;
|
|
7700
|
+
}).join(" ");
|
|
7701
|
+
}
|
|
7702
|
+
if (isHTMLInputElement(node) && node.type === "image") {
|
|
7703
|
+
var _nameFromAlt = useAttribute(node, "alt");
|
|
7704
|
+
if (_nameFromAlt !== null) {
|
|
7705
|
+
return _nameFromAlt;
|
|
7706
|
+
}
|
|
7707
|
+
var nameFromTitle = useAttribute(node, "title");
|
|
7708
|
+
if (nameFromTitle !== null) {
|
|
7709
|
+
return nameFromTitle;
|
|
7710
|
+
}
|
|
7711
|
+
return "Submit Query";
|
|
7712
|
+
}
|
|
7713
|
+
if (hasAnyConcreteRoles(node, ["button"])) {
|
|
7714
|
+
var nameFromSubTree = computeMiscTextAlternative(node, {
|
|
7715
|
+
isEmbeddedInLabel: false
|
|
7716
|
+
});
|
|
7717
|
+
if (nameFromSubTree !== "") {
|
|
7718
|
+
return nameFromSubTree;
|
|
7719
|
+
}
|
|
7720
|
+
}
|
|
7721
|
+
return null;
|
|
7722
|
+
}
|
|
7723
|
+
function computeTextAlternative2(current, context) {
|
|
7724
|
+
if (consultedNodes.has(current)) {
|
|
7725
|
+
return "";
|
|
7726
|
+
}
|
|
7727
|
+
if (!hidden && isHidden$1(current, getComputedStyle2) && !context.isReferenced) {
|
|
7728
|
+
consultedNodes.add(current);
|
|
7729
|
+
return "";
|
|
7730
|
+
}
|
|
7731
|
+
var labelAttributeNode = isElement(current) ? current.getAttributeNode("aria-labelledby") : null;
|
|
7732
|
+
var labelElements = labelAttributeNode !== null && !consultedNodes.has(labelAttributeNode) ? queryIdRefs(current, "aria-labelledby") : [];
|
|
7733
|
+
if (compute === "name" && !context.isReferenced && labelElements.length > 0) {
|
|
7734
|
+
consultedNodes.add(labelAttributeNode);
|
|
7735
|
+
return labelElements.map(function(element) {
|
|
7736
|
+
return computeTextAlternative2(element, {
|
|
7737
|
+
isEmbeddedInLabel: context.isEmbeddedInLabel,
|
|
7738
|
+
isReferenced: true,
|
|
7739
|
+
// this isn't recursion as specified, otherwise we would skip
|
|
7740
|
+
// `aria-label` in
|
|
7741
|
+
// <input id="myself" aria-label="foo" aria-labelledby="myself"
|
|
7742
|
+
recursion: false
|
|
7743
|
+
});
|
|
7744
|
+
}).join(" ");
|
|
7745
|
+
}
|
|
7746
|
+
var skipToStep2E = context.recursion && isControl(current) && compute === "name";
|
|
7747
|
+
if (!skipToStep2E) {
|
|
7748
|
+
var ariaLabel = (isElement(current) && current.getAttribute("aria-label") || "").trim();
|
|
7749
|
+
if (ariaLabel !== "" && compute === "name") {
|
|
7750
|
+
consultedNodes.add(current);
|
|
7751
|
+
return ariaLabel;
|
|
7752
|
+
}
|
|
7753
|
+
if (!isMarkedPresentational(current)) {
|
|
7754
|
+
var elementTextAlternative = computeElementTextAlternative(current);
|
|
7755
|
+
if (elementTextAlternative !== null) {
|
|
7756
|
+
consultedNodes.add(current);
|
|
7757
|
+
return elementTextAlternative;
|
|
7758
|
+
}
|
|
7759
|
+
}
|
|
6117
7760
|
}
|
|
6118
|
-
|
|
6119
|
-
|
|
6120
|
-
|
|
6121
|
-
|
|
6122
|
-
|
|
6123
|
-
|
|
6124
|
-
|
|
6125
|
-
|
|
6126
|
-
|
|
7761
|
+
if (hasAnyConcreteRoles(current, ["menu"])) {
|
|
7762
|
+
consultedNodes.add(current);
|
|
7763
|
+
return "";
|
|
7764
|
+
}
|
|
7765
|
+
if (skipToStep2E || context.isEmbeddedInLabel || context.isReferenced) {
|
|
7766
|
+
if (hasAnyConcreteRoles(current, ["combobox", "listbox"])) {
|
|
7767
|
+
consultedNodes.add(current);
|
|
7768
|
+
var selectedOptions = querySelectedOptions(current);
|
|
7769
|
+
if (selectedOptions.length === 0) {
|
|
7770
|
+
return isHTMLInputElement(current) ? current.value : "";
|
|
7771
|
+
}
|
|
7772
|
+
return arrayFrom(selectedOptions).map(function(selectedOption) {
|
|
7773
|
+
return computeTextAlternative2(selectedOption, {
|
|
7774
|
+
isEmbeddedInLabel: context.isEmbeddedInLabel,
|
|
7775
|
+
isReferenced: false,
|
|
7776
|
+
recursion: true
|
|
7777
|
+
});
|
|
7778
|
+
}).join(" ");
|
|
7779
|
+
}
|
|
7780
|
+
if (hasAbstractRole(current, "range")) {
|
|
7781
|
+
consultedNodes.add(current);
|
|
7782
|
+
if (current.hasAttribute("aria-valuetext")) {
|
|
7783
|
+
return current.getAttribute("aria-valuetext");
|
|
7784
|
+
}
|
|
7785
|
+
if (current.hasAttribute("aria-valuenow")) {
|
|
7786
|
+
return current.getAttribute("aria-valuenow");
|
|
7787
|
+
}
|
|
7788
|
+
return current.getAttribute("value") || "";
|
|
7789
|
+
}
|
|
7790
|
+
if (hasAnyConcreteRoles(current, ["textbox"])) {
|
|
7791
|
+
consultedNodes.add(current);
|
|
7792
|
+
return getValueOfTextbox(current);
|
|
7793
|
+
}
|
|
7794
|
+
}
|
|
7795
|
+
if (allowsNameFromContent(current) || isElement(current) && context.isReferenced || isNativeHostLanguageTextAlternativeElement(current) || isDescendantOfNativeHostLanguageTextAlternativeElement()) {
|
|
7796
|
+
var accumulatedText2F = computeMiscTextAlternative(current, {
|
|
7797
|
+
isEmbeddedInLabel: context.isEmbeddedInLabel
|
|
7798
|
+
});
|
|
7799
|
+
if (accumulatedText2F !== "") {
|
|
7800
|
+
consultedNodes.add(current);
|
|
7801
|
+
return accumulatedText2F;
|
|
7802
|
+
}
|
|
7803
|
+
}
|
|
7804
|
+
if (current.nodeType === current.TEXT_NODE) {
|
|
7805
|
+
consultedNodes.add(current);
|
|
7806
|
+
return current.textContent || "";
|
|
7807
|
+
}
|
|
7808
|
+
if (context.recursion) {
|
|
7809
|
+
consultedNodes.add(current);
|
|
7810
|
+
return computeMiscTextAlternative(current, {
|
|
7811
|
+
isEmbeddedInLabel: context.isEmbeddedInLabel
|
|
7812
|
+
});
|
|
7813
|
+
}
|
|
7814
|
+
var tooltipAttributeValue = computeTooltipAttributeValue(current);
|
|
7815
|
+
if (tooltipAttributeValue !== null) {
|
|
7816
|
+
consultedNodes.add(current);
|
|
7817
|
+
return tooltipAttributeValue;
|
|
7818
|
+
}
|
|
7819
|
+
consultedNodes.add(current);
|
|
7820
|
+
return "";
|
|
6127
7821
|
}
|
|
7822
|
+
return asFlatString(computeTextAlternative2(root, {
|
|
7823
|
+
isEmbeddedInLabel: false,
|
|
7824
|
+
// by spec computeAccessibleDescription starts with the referenced elements as roots
|
|
7825
|
+
isReferenced: compute === "description",
|
|
7826
|
+
recursion: false
|
|
7827
|
+
}));
|
|
6128
7828
|
}
|
|
6129
|
-
function
|
|
6130
|
-
|
|
6131
|
-
if (!selectedFields.has(field.id) && !selectedFields.has(field.name)) continue;
|
|
6132
|
-
if (!NOTION_FILTERABLE_FIELD_TYPES.has(field.type)) continue;
|
|
6133
|
-
const options = getNotionFieldFilterOptions(field);
|
|
6134
|
-
if (!options.length) continue;
|
|
6135
|
-
upsertFilterGroup(groups, {
|
|
6136
|
-
kind: "notion",
|
|
6137
|
-
id: `notion:${field.id}`,
|
|
6138
|
-
fieldName: field.name,
|
|
6139
|
-
label: field.name,
|
|
6140
|
-
options
|
|
6141
|
-
});
|
|
6142
|
-
}
|
|
7829
|
+
function prohibitsNaming(node) {
|
|
7830
|
+
return hasAnyConcreteRoles(node, ["caption", "code", "deletion", "emphasis", "generic", "insertion", "none", "paragraph", "presentation", "strong", "subscript", "superscript"]);
|
|
6143
7831
|
}
|
|
6144
|
-
function
|
|
6145
|
-
|
|
6146
|
-
|
|
6147
|
-
|
|
6148
|
-
return integration.is_active !== false && config;
|
|
6149
|
-
});
|
|
6150
|
-
appendCustomFieldFilterGroups(groups, customFields);
|
|
6151
|
-
const jiraConfig = jiraIntegration ? getJiraConfig(jiraIntegration) : null;
|
|
6152
|
-
const configExposedFields = getExposedFields(jiraConfig);
|
|
6153
|
-
if (configExposedFields.length) {
|
|
6154
|
-
appendJiraFieldFilterGroups(
|
|
6155
|
-
groups,
|
|
6156
|
-
new Set(configExposedFields),
|
|
6157
|
-
getLastKnownJiraFields(jiraConfig)
|
|
6158
|
-
);
|
|
6159
|
-
}
|
|
6160
|
-
for (const integration of integrations) {
|
|
6161
|
-
if (integration.is_active === false) continue;
|
|
6162
|
-
const notionConfig = getNotionConfig(integration);
|
|
6163
|
-
if (!notionConfig) continue;
|
|
6164
|
-
appendNotionFieldFilterGroups(
|
|
6165
|
-
groups,
|
|
6166
|
-
new Set(getSelectedNotionFields(notionConfig)),
|
|
6167
|
-
getLastKnownNotionFields(notionConfig)
|
|
6168
|
-
);
|
|
7832
|
+
function computeAccessibleName(root) {
|
|
7833
|
+
var options = arguments.length > 1 && arguments[1] !== void 0 ? arguments[1] : {};
|
|
7834
|
+
if (prohibitsNaming(root)) {
|
|
7835
|
+
return "";
|
|
6169
7836
|
}
|
|
6170
|
-
return
|
|
7837
|
+
return computeTextAlternative(root, options);
|
|
6171
7838
|
}
|
|
6172
|
-
const STABLE_ATTR_KEYS = [
|
|
6173
|
-
"data-testid",
|
|
6174
|
-
"data-test",
|
|
6175
|
-
"data-qa",
|
|
6176
|
-
"data-cy",
|
|
6177
|
-
"name",
|
|
6178
|
-
"aria-label"
|
|
6179
|
-
];
|
|
6180
7839
|
const STATE_CLASS_RE = /^(is-|has-|js-)|^(active|open|selected|disabled|focus|hover|show|hidden|loading|dragging)$/i;
|
|
6181
7840
|
function looksHashed(segment) {
|
|
6182
7841
|
if (segment.includes("-")) return false;
|
|
@@ -6188,17 +7847,35 @@ function looksHashed(segment) {
|
|
|
6188
7847
|
}
|
|
6189
7848
|
return false;
|
|
6190
7849
|
}
|
|
7850
|
+
const GENERATED_CLASS_PATTERNS = [
|
|
7851
|
+
/^css-[a-z0-9]+(-|$)/i,
|
|
7852
|
+
// emotion / MUI. 라벨이 붙으면 css-1e6y48t-MuiButtonBase-root
|
|
7853
|
+
/^sc-[a-z0-9]+$/i,
|
|
7854
|
+
// styled-components
|
|
7855
|
+
/^makeStyles-.+-\d+$/,
|
|
7856
|
+
// JSS makeStyles
|
|
7857
|
+
/^jss\d+$/,
|
|
7858
|
+
// JSS 기본 생성기
|
|
7859
|
+
/^ng-tns-c\d+-\d+$/,
|
|
7860
|
+
// Angular 애니메이션
|
|
7861
|
+
/^_[\w-]+_[a-z0-9]{5}_\d+$/
|
|
7862
|
+
// Vite CSS Modules 기본값: _로컬_해시(base36 5자)_줄번호
|
|
7863
|
+
];
|
|
7864
|
+
function hasCssModulesHash(token) {
|
|
7865
|
+
const index = token.lastIndexOf("__");
|
|
7866
|
+
if (index <= 0) return false;
|
|
7867
|
+
const suffix = token.slice(index + 2);
|
|
7868
|
+
return suffix.length === 5 && /^[\w-]+$/.test(suffix) && /[A-Z0-9_-]/.test(suffix);
|
|
7869
|
+
}
|
|
6191
7870
|
function isUnstableToken(token) {
|
|
6192
7871
|
if (!token) return true;
|
|
6193
7872
|
if (token.includes(":") || token.includes("/")) return true;
|
|
6194
7873
|
if (token.includes("--")) return true;
|
|
6195
|
-
if (/^css-[a-z0-9]+$/i.test(token)) return true;
|
|
6196
|
-
if (/^sc-[a-z0-9]+$/i.test(token)) return true;
|
|
6197
7874
|
if (STATE_CLASS_RE.test(token)) return true;
|
|
6198
|
-
|
|
6199
|
-
if (
|
|
7875
|
+
if (GENERATED_CLASS_PATTERNS.some((pattern) => pattern.test(token))) return true;
|
|
7876
|
+
if (hasCssModulesHash(token)) return true;
|
|
6200
7877
|
if (looksHashed(token)) return true;
|
|
6201
|
-
return
|
|
7878
|
+
return token.split(/[-_]+/).some(looksHashed);
|
|
6202
7879
|
}
|
|
6203
7880
|
function isStableClassName(cls) {
|
|
6204
7881
|
return !isUnstableToken(cls);
|
|
@@ -6211,6 +7888,297 @@ function isUnstableId(id) {
|
|
|
6211
7888
|
if (looksHashed(id)) return true;
|
|
6212
7889
|
return false;
|
|
6213
7890
|
}
|
|
7891
|
+
function intersection(firstArr, secondArr) {
|
|
7892
|
+
const secondSet = new Set(secondArr);
|
|
7893
|
+
return firstArr.filter((item) => secondSet.has(item));
|
|
7894
|
+
}
|
|
7895
|
+
function uniq(arr) {
|
|
7896
|
+
return [...new Set(arr)];
|
|
7897
|
+
}
|
|
7898
|
+
function union(arr1, arr2) {
|
|
7899
|
+
return uniq(arr1.concat(arr2));
|
|
7900
|
+
}
|
|
7901
|
+
const LOCATOR_TEXT_MAX = 80;
|
|
7902
|
+
const RAW_SLICE_FACTOR = 8;
|
|
7903
|
+
const LOCATOR_RAW_TEXT_MAX = LOCATOR_TEXT_MAX * RAW_SLICE_FACTOR;
|
|
7904
|
+
function dropTrailingLoneSurrogate(value) {
|
|
7905
|
+
return /[\uD800-\uDBFF]$/.test(value) ? value.slice(0, -1) : value;
|
|
7906
|
+
}
|
|
7907
|
+
function cleanLocatorText(value, max2 = LOCATOR_TEXT_MAX) {
|
|
7908
|
+
if (!value) return void 0;
|
|
7909
|
+
const bounded = value.slice(0, max2 * RAW_SLICE_FACTOR);
|
|
7910
|
+
const cleaned = dropTrailingLoneSurrogate(
|
|
7911
|
+
redactSensitiveValues(bounded.replace(/\s+/g, " ").trim()).slice(0, max2)
|
|
7912
|
+
).trim();
|
|
7913
|
+
return cleaned || void 0;
|
|
7914
|
+
}
|
|
7915
|
+
function normalizeForCompare(value) {
|
|
7916
|
+
return value.toLowerCase().replace(/\d+/g, "#");
|
|
7917
|
+
}
|
|
7918
|
+
function jaccard(a2, b) {
|
|
7919
|
+
const all = union(uniq(a2), uniq(b));
|
|
7920
|
+
if (all.length === 0) return 0;
|
|
7921
|
+
return intersection(uniq(a2), uniq(b)).length / all.length;
|
|
7922
|
+
}
|
|
7923
|
+
function textSimilarity(a2, b) {
|
|
7924
|
+
const left = normalizeForCompare(a2);
|
|
7925
|
+
const right = normalizeForCompare(b);
|
|
7926
|
+
if (left === right) return 1;
|
|
7927
|
+
const [shorter, longer] = left.length <= right.length ? [left, right] : [right, left];
|
|
7928
|
+
const containment = shorter && longer.startsWith(shorter) ? shorter.length / longer.length : 0;
|
|
7929
|
+
return Math.max(jaccard(left.split(" "), right.split(" ")), containment);
|
|
7930
|
+
}
|
|
7931
|
+
function labelSimilarity(stored, observed) {
|
|
7932
|
+
const storedWords = new Set(normalizeForCompare(stored).split(" "));
|
|
7933
|
+
const observedWords = new Set(normalizeForCompare(observed).split(" "));
|
|
7934
|
+
const [smaller, larger] = storedWords.size <= observedWords.size ? [storedWords, observedWords] : [observedWords, storedWords];
|
|
7935
|
+
const isStrictSubset = larger.size > smaller.size && [...smaller].every((w) => larger.has(w));
|
|
7936
|
+
return isStrictSubset ? 0 : textSimilarity(stored, observed);
|
|
7937
|
+
}
|
|
7938
|
+
function digitGroups(value) {
|
|
7939
|
+
return (value.match(/\d+/g) ?? []).join(" ");
|
|
7940
|
+
}
|
|
7941
|
+
function sameDigitGroups(a2, b) {
|
|
7942
|
+
return digitGroups(a2) === digitGroups(b);
|
|
7943
|
+
}
|
|
7944
|
+
function labelSimilarityKeepingDigits(stored, observed) {
|
|
7945
|
+
const left = stored.replace(/\s+/g, " ").trim();
|
|
7946
|
+
const right = observed.replace(/\s+/g, " ").trim();
|
|
7947
|
+
return sameDigitGroups(left, right) ? labelSimilarity(left, right) : 0;
|
|
7948
|
+
}
|
|
7949
|
+
const TEST_ATTR_KEYS = ["data-testid", "data-test", "data-qa", "data-cy"];
|
|
7950
|
+
const OTHER_ATTR_KEYS = ["name", "type", "placeholder", "alt", "title", "href", "src"];
|
|
7951
|
+
const URL_ATTR_KEYS = /* @__PURE__ */ new Set(["href", "src"]);
|
|
7952
|
+
const ATTR_VALUE_MAX = 120;
|
|
7953
|
+
const CLASS_LIMIT = 5;
|
|
7954
|
+
const LOCATOR_SIZE_MAX = 1024;
|
|
7955
|
+
const CONTAINER_ROLES = /* @__PURE__ */ new Set(["dialog", "alertdialog", "menu", "listbox", "tooltip"]);
|
|
7956
|
+
const CONTAINER_SELECTOR = 'dialog, [role="dialog"], [role="alertdialog"], [role="menu"], [role="listbox"], [role="tooltip"]';
|
|
7957
|
+
const LANDMARK_ROLES = /* @__PURE__ */ new Set([
|
|
7958
|
+
"banner",
|
|
7959
|
+
"navigation",
|
|
7960
|
+
"main",
|
|
7961
|
+
"complementary",
|
|
7962
|
+
"contentinfo",
|
|
7963
|
+
"region",
|
|
7964
|
+
"form",
|
|
7965
|
+
"search"
|
|
7966
|
+
]);
|
|
7967
|
+
const NAMED_ONLY_LANDMARKS = /* @__PURE__ */ new Set(["region", "form"]);
|
|
7968
|
+
const ROW_SELECTOR = 'li, tr, [role="row"], [role="listitem"], [role="option"]';
|
|
7969
|
+
const HEADING_SELECTOR = 'h1, h2, h3, h4, h5, h6, [role="heading"]';
|
|
7970
|
+
function safeRole(element) {
|
|
7971
|
+
try {
|
|
7972
|
+
return getRole(element) ?? void 0;
|
|
7973
|
+
} catch {
|
|
7974
|
+
return void 0;
|
|
7975
|
+
}
|
|
7976
|
+
}
|
|
7977
|
+
function safeAccessibleName(element) {
|
|
7978
|
+
try {
|
|
7979
|
+
return cleanLocatorText(computeAccessibleName(element));
|
|
7980
|
+
} catch {
|
|
7981
|
+
return void 0;
|
|
7982
|
+
}
|
|
7983
|
+
}
|
|
7984
|
+
function landmarkRole(node) {
|
|
7985
|
+
const role = safeRole(node);
|
|
7986
|
+
if (role === void 0 || !LANDMARK_ROLES.has(role)) return void 0;
|
|
7987
|
+
if (NAMED_ONLY_LANDMARKS.has(role) && !safeAccessibleName(node)) return void 0;
|
|
7988
|
+
return role;
|
|
7989
|
+
}
|
|
7990
|
+
function collectHeadings(doc) {
|
|
7991
|
+
return Array.from(doc.querySelectorAll(HEADING_SELECTOR));
|
|
7992
|
+
}
|
|
7993
|
+
function matchesMask(element) {
|
|
7994
|
+
try {
|
|
7995
|
+
return element.matches(SESSION_REPLAY_FULL_MASK_SELECTOR);
|
|
7996
|
+
} catch {
|
|
7997
|
+
return true;
|
|
7998
|
+
}
|
|
7999
|
+
}
|
|
8000
|
+
function hasMaskedDescendant(root) {
|
|
8001
|
+
try {
|
|
8002
|
+
return root.querySelector(SESSION_REPLAY_FULL_MASK_SELECTOR) !== null;
|
|
8003
|
+
} catch {
|
|
8004
|
+
return true;
|
|
8005
|
+
}
|
|
8006
|
+
}
|
|
8007
|
+
function visibleText(root) {
|
|
8008
|
+
if (!hasMaskedDescendant(root)) return root.textContent ?? "";
|
|
8009
|
+
const walker = root.ownerDocument.createTreeWalker(
|
|
8010
|
+
root,
|
|
8011
|
+
NodeFilter.SHOW_ELEMENT | NodeFilter.SHOW_TEXT,
|
|
8012
|
+
(node) => node.nodeType === Node.ELEMENT_NODE && matchesMask(node) ? NodeFilter.FILTER_REJECT : NodeFilter.FILTER_ACCEPT
|
|
8013
|
+
);
|
|
8014
|
+
let text = "";
|
|
8015
|
+
for (let node = walker.nextNode(); node && text.length < LOCATOR_RAW_TEXT_MAX; ) {
|
|
8016
|
+
if (node.nodeType === Node.TEXT_NODE) text += node.nodeValue ?? "";
|
|
8017
|
+
node = walker.nextNode();
|
|
8018
|
+
}
|
|
8019
|
+
return text;
|
|
8020
|
+
}
|
|
8021
|
+
function closestAncestor(element, matches) {
|
|
8022
|
+
let current = element.parentElement;
|
|
8023
|
+
while (current) {
|
|
8024
|
+
if (matches(current)) return current;
|
|
8025
|
+
current = current.parentElement;
|
|
8026
|
+
}
|
|
8027
|
+
return null;
|
|
8028
|
+
}
|
|
8029
|
+
function nearestPrecedingHeading(element, headings) {
|
|
8030
|
+
let found = null;
|
|
8031
|
+
for (const heading of headings) {
|
|
8032
|
+
if (heading === element || heading.contains(element)) continue;
|
|
8033
|
+
if (heading.compareDocumentPosition(element) & Node.DOCUMENT_POSITION_FOLLOWING) {
|
|
8034
|
+
found = heading;
|
|
8035
|
+
} else {
|
|
8036
|
+
break;
|
|
8037
|
+
}
|
|
8038
|
+
}
|
|
8039
|
+
return found;
|
|
8040
|
+
}
|
|
8041
|
+
function rowTextOf(element) {
|
|
8042
|
+
const row = element.closest(ROW_SELECTOR);
|
|
8043
|
+
if (!row) return void 0;
|
|
8044
|
+
const label = Array.from(row.children).find(
|
|
8045
|
+
(cell) => !cell.contains(element) && visibleText(cell).trim()
|
|
8046
|
+
);
|
|
8047
|
+
const source = label ?? row;
|
|
8048
|
+
if (isElementFullyMasked(source)) return void 0;
|
|
8049
|
+
return cleanLocatorText(visibleText(source));
|
|
8050
|
+
}
|
|
8051
|
+
function urlPath(value, doc) {
|
|
8052
|
+
try {
|
|
8053
|
+
const url = new URL(value, doc.baseURI);
|
|
8054
|
+
return url.protocol === "http:" || url.protocol === "https:" ? url.pathname : void 0;
|
|
8055
|
+
} catch {
|
|
8056
|
+
return void 0;
|
|
8057
|
+
}
|
|
8058
|
+
}
|
|
8059
|
+
function attrValue(element, key) {
|
|
8060
|
+
const raw = element.getAttribute(key);
|
|
8061
|
+
if (!raw) return void 0;
|
|
8062
|
+
return cleanLocatorText(
|
|
8063
|
+
URL_ATTR_KEYS.has(key) ? urlPath(raw, element.ownerDocument) : raw,
|
|
8064
|
+
ATTR_VALUE_MAX
|
|
8065
|
+
);
|
|
8066
|
+
}
|
|
8067
|
+
function sharesUrlPath(element, key, value) {
|
|
8068
|
+
return Array.from(element.ownerDocument.getElementsByTagName(element.tagName)).some(
|
|
8069
|
+
(other) => other !== element && attrValue(other, key) === value
|
|
8070
|
+
);
|
|
8071
|
+
}
|
|
8072
|
+
function collectAttrs(element, masked, uniqueUrlAttrs) {
|
|
8073
|
+
const attrs = {};
|
|
8074
|
+
const keys = masked ? TEST_ATTR_KEYS : [...TEST_ATTR_KEYS, ...OTHER_ATTR_KEYS];
|
|
8075
|
+
for (const key of keys) {
|
|
8076
|
+
const value = attrValue(element, key);
|
|
8077
|
+
if (!value) continue;
|
|
8078
|
+
if (uniqueUrlAttrs && URL_ATTR_KEYS.has(key) && sharesUrlPath(element, key, value)) continue;
|
|
8079
|
+
attrs[key] = value;
|
|
8080
|
+
}
|
|
8081
|
+
return Object.keys(attrs).length ? attrs : void 0;
|
|
8082
|
+
}
|
|
8083
|
+
function collectClasses(element) {
|
|
8084
|
+
const classes = Array.from(element.classList).filter(isStableClassName).slice(0, CLASS_LIMIT);
|
|
8085
|
+
return classes.length ? classes : void 0;
|
|
8086
|
+
}
|
|
8087
|
+
function tagIndex(element, scope) {
|
|
8088
|
+
return Array.from(scope.getElementsByTagName(element.tagName)).indexOf(element);
|
|
8089
|
+
}
|
|
8090
|
+
function labelSources(element) {
|
|
8091
|
+
var _a;
|
|
8092
|
+
const doc = element.ownerDocument;
|
|
8093
|
+
const ids = ((_a = element.getAttribute("aria-labelledby")) == null ? void 0 : _a.split(/\s+/).filter(Boolean)) ?? [];
|
|
8094
|
+
const sources = ids.map((id) => doc.getElementById(id)).filter((node) => node !== null);
|
|
8095
|
+
const labels = element.labels;
|
|
8096
|
+
if (labels) sources.push(...Array.from(labels));
|
|
8097
|
+
const wrapping = element.closest("label");
|
|
8098
|
+
if (wrapping) sources.push(wrapping);
|
|
8099
|
+
return sources;
|
|
8100
|
+
}
|
|
8101
|
+
function labelSourceMasked(element) {
|
|
8102
|
+
return labelSources(element).some(
|
|
8103
|
+
(node) => isElementFullyMasked(node) || hasMaskedDescendant(node)
|
|
8104
|
+
);
|
|
8105
|
+
}
|
|
8106
|
+
function capSize(locator) {
|
|
8107
|
+
const size = () => JSON.stringify(locator).length;
|
|
8108
|
+
if (size() <= LOCATOR_SIZE_MAX) return locator;
|
|
8109
|
+
delete locator.classes;
|
|
8110
|
+
if (size() <= LOCATOR_SIZE_MAX) return locator;
|
|
8111
|
+
if (locator.context) delete locator.context.heading;
|
|
8112
|
+
if (size() <= LOCATOR_SIZE_MAX) return locator;
|
|
8113
|
+
if (locator.attrs) {
|
|
8114
|
+
for (const key of OTHER_ATTR_KEYS) delete locator.attrs[key];
|
|
8115
|
+
}
|
|
8116
|
+
if (size() <= LOCATOR_SIZE_MAX) return locator;
|
|
8117
|
+
delete locator.context;
|
|
8118
|
+
if (size() <= LOCATOR_SIZE_MAX) return locator;
|
|
8119
|
+
delete locator.text;
|
|
8120
|
+
return locator;
|
|
8121
|
+
}
|
|
8122
|
+
function buildElementLocator(element, options = {}) {
|
|
8123
|
+
try {
|
|
8124
|
+
return buildLocatorUnsafe(element, options);
|
|
8125
|
+
} catch {
|
|
8126
|
+
return { v: 1, tag: element.localName || element.tagName.toLowerCase() };
|
|
8127
|
+
}
|
|
8128
|
+
}
|
|
8129
|
+
function buildLocatorUnsafe(element, options) {
|
|
8130
|
+
const doc = element.ownerDocument;
|
|
8131
|
+
const masked = isElementFullyMasked(element);
|
|
8132
|
+
const locator = { v: 1, tag: element.tagName.toLowerCase() };
|
|
8133
|
+
const role = safeRole(element);
|
|
8134
|
+
if (role) locator.role = role;
|
|
8135
|
+
if (!masked) {
|
|
8136
|
+
const nameMasked = hasMaskedDescendant(element) || labelSourceMasked(element);
|
|
8137
|
+
if (options.includeName !== false && !nameMasked) {
|
|
8138
|
+
const name = safeAccessibleName(element);
|
|
8139
|
+
if (name) locator.name = name;
|
|
8140
|
+
}
|
|
8141
|
+
const text = cleanLocatorText(visibleText(element));
|
|
8142
|
+
if (text) locator.text = text;
|
|
8143
|
+
}
|
|
8144
|
+
const attrs = collectAttrs(element, masked, options.uniqueUrlAttrs !== false);
|
|
8145
|
+
if (attrs) locator.attrs = attrs;
|
|
8146
|
+
const classes = collectClasses(element);
|
|
8147
|
+
if (classes) locator.classes = classes;
|
|
8148
|
+
const container = closestAncestor(element, (node) => {
|
|
8149
|
+
const nodeRole = safeRole(node);
|
|
8150
|
+
return nodeRole !== void 0 && CONTAINER_ROLES.has(nodeRole);
|
|
8151
|
+
});
|
|
8152
|
+
const landmark = closestAncestor(element, (node) => landmarkRole(node) !== void 0);
|
|
8153
|
+
const heading = nearestPrecedingHeading(element, options.headings ?? collectHeadings(doc));
|
|
8154
|
+
const rowText = masked ? void 0 : rowTextOf(element);
|
|
8155
|
+
const context = {};
|
|
8156
|
+
if (container) {
|
|
8157
|
+
const containerName = isElementFullyMasked(container) || hasMaskedDescendant(container) || labelSourceMasked(container) ? void 0 : safeAccessibleName(container);
|
|
8158
|
+
context.container = {
|
|
8159
|
+
role: safeRole(container),
|
|
8160
|
+
...containerName ? { name: containerName } : {}
|
|
8161
|
+
};
|
|
8162
|
+
}
|
|
8163
|
+
if (landmark) context.landmark = landmarkRole(landmark);
|
|
8164
|
+
const headingText = heading && !isElementFullyMasked(heading) ? cleanLocatorText(visibleText(heading)) : void 0;
|
|
8165
|
+
if (headingText) context.heading = headingText;
|
|
8166
|
+
if (rowText) context.rowText = rowText;
|
|
8167
|
+
if (Object.keys(context).length) locator.context = context;
|
|
8168
|
+
if (options.includeRoleIndex !== false) {
|
|
8169
|
+
const index = tagIndex(element, container ?? doc);
|
|
8170
|
+
if (index >= 0) locator.roleIndex = index;
|
|
8171
|
+
}
|
|
8172
|
+
return capSize(locator);
|
|
8173
|
+
}
|
|
8174
|
+
const STABLE_ATTR_KEYS = [
|
|
8175
|
+
"data-testid",
|
|
8176
|
+
"data-test",
|
|
8177
|
+
"data-qa",
|
|
8178
|
+
"data-cy",
|
|
8179
|
+
"name",
|
|
8180
|
+
"aria-label"
|
|
8181
|
+
];
|
|
6214
8182
|
function cssAttrValue(value) {
|
|
6215
8183
|
return value.replace(/(["\\])/g, "\\$1");
|
|
6216
8184
|
}
|
|
@@ -6279,8 +8247,8 @@ function getElementSelector(element) {
|
|
|
6279
8247
|
const segments = [];
|
|
6280
8248
|
let current = element;
|
|
6281
8249
|
let depth = 0;
|
|
6282
|
-
const
|
|
6283
|
-
while (current && current.nodeType === Node.ELEMENT_NODE && depth <
|
|
8250
|
+
const MAX_DEPTH2 = 5;
|
|
8251
|
+
while (current && current.nodeType === Node.ELEMENT_NODE && depth < MAX_DEPTH2) {
|
|
6284
8252
|
segments.unshift(structuralSegment(current));
|
|
6285
8253
|
const candidate = segments.join(" > ");
|
|
6286
8254
|
if (matchesUniquely(candidate, element)) return candidate;
|
|
@@ -6320,9 +8288,15 @@ function getXPath(element) {
|
|
|
6320
8288
|
}
|
|
6321
8289
|
return parts.length ? `/html/body/${parts.join("/")}` : "/html/body";
|
|
6322
8290
|
}
|
|
6323
|
-
function getElementPosition(element, event) {
|
|
8291
|
+
function getElementPosition(element, event, options = {}) {
|
|
6324
8292
|
const rect = element.getBoundingClientRect();
|
|
6325
8293
|
const scroll = getScrollPosition();
|
|
8294
|
+
let component = null;
|
|
8295
|
+
try {
|
|
8296
|
+
component = (options.collectComponent ?? collectComponentPath)(element);
|
|
8297
|
+
} catch {
|
|
8298
|
+
component = null;
|
|
8299
|
+
}
|
|
6326
8300
|
return {
|
|
6327
8301
|
x: event ? event.clientX + scroll.x : rect.left + scroll.x,
|
|
6328
8302
|
y: event ? event.clientY + scroll.y : rect.top + scroll.y,
|
|
@@ -6334,7 +8308,9 @@ function getElementPosition(element, event) {
|
|
|
6334
8308
|
top: rect.top + scroll.y,
|
|
6335
8309
|
width: rect.width,
|
|
6336
8310
|
height: rect.height
|
|
6337
|
-
}
|
|
8311
|
+
},
|
|
8312
|
+
locator: buildElementLocator(element),
|
|
8313
|
+
...component ? { component } : {}
|
|
6338
8314
|
};
|
|
6339
8315
|
}
|
|
6340
8316
|
function findElementByXPath(xpath) {
|
|
@@ -7964,15 +9940,133 @@ var Anchor2 = PopoverAnchor$1;
|
|
|
7964
9940
|
var Trigger = PopoverTrigger$1;
|
|
7965
9941
|
var Portal = PopoverPortal;
|
|
7966
9942
|
var Content2 = PopoverContent$1;
|
|
7967
|
-
const
|
|
9943
|
+
const FLOATING_LAYER_SELECTOR = "[data-radix-popper-content-wrapper]";
|
|
9944
|
+
const OUTSIDE_EVENTS = ["pointerdown", "focusin"];
|
|
9945
|
+
const roots = /* @__PURE__ */ new Map();
|
|
9946
|
+
function isInsideLayer(path, content) {
|
|
9947
|
+
return path.some((node) => {
|
|
9948
|
+
if (!(node instanceof Element)) return false;
|
|
9949
|
+
if (node === content) return true;
|
|
9950
|
+
if (content.id && node.getAttribute("aria-controls") === content.id) return true;
|
|
9951
|
+
return node.matches(FLOATING_LAYER_SELECTOR) && !node.contains(content);
|
|
9952
|
+
});
|
|
9953
|
+
}
|
|
9954
|
+
function dismissTopLayer(root, event) {
|
|
9955
|
+
var _a;
|
|
9956
|
+
const layers = (_a = roots.get(root)) == null ? void 0 : _a.layers;
|
|
9957
|
+
const top = layers == null ? void 0 : layers[layers.length - 1];
|
|
9958
|
+
if (!top) return;
|
|
9959
|
+
if (event.type === "focusin" && !top.focusOutside) return;
|
|
9960
|
+
if (isInsideLayer(event.composedPath(), top.content)) return;
|
|
9961
|
+
top.dismiss();
|
|
9962
|
+
}
|
|
9963
|
+
function listen(root) {
|
|
9964
|
+
const doc = root.ownerDocument;
|
|
9965
|
+
const onRoot = (event) => dismissTopLayer(root, event);
|
|
9966
|
+
const onDocument = (event) => {
|
|
9967
|
+
if (!event.composedPath().includes(root)) dismissTopLayer(root, event);
|
|
9968
|
+
};
|
|
9969
|
+
OUTSIDE_EVENTS.forEach((type) => {
|
|
9970
|
+
root.addEventListener(type, onRoot);
|
|
9971
|
+
doc.addEventListener(type, onDocument);
|
|
9972
|
+
});
|
|
9973
|
+
return () => OUTSIDE_EVENTS.forEach((type) => {
|
|
9974
|
+
root.removeEventListener(type, onRoot);
|
|
9975
|
+
doc.removeEventListener(type, onDocument);
|
|
9976
|
+
});
|
|
9977
|
+
}
|
|
9978
|
+
function registerLayer(root, layer) {
|
|
9979
|
+
let entry = roots.get(root);
|
|
9980
|
+
if (!entry) {
|
|
9981
|
+
entry = { layers: [], detach: listen(root) };
|
|
9982
|
+
roots.set(root, entry);
|
|
9983
|
+
}
|
|
9984
|
+
entry.layers.push(layer);
|
|
9985
|
+
return () => {
|
|
9986
|
+
const current = roots.get(root);
|
|
9987
|
+
if (!current) return;
|
|
9988
|
+
current.layers = current.layers.filter((item) => item !== layer);
|
|
9989
|
+
if (current.layers.length === 0) {
|
|
9990
|
+
current.detach();
|
|
9991
|
+
roots.delete(root);
|
|
9992
|
+
}
|
|
9993
|
+
};
|
|
9994
|
+
}
|
|
9995
|
+
function useShadowOutsideDismiss(onDismiss, { focusOutside = true } = {}) {
|
|
9996
|
+
const onDismissRef = useRef(onDismiss);
|
|
9997
|
+
const detachRef = useRef(null);
|
|
9998
|
+
useEffect(() => {
|
|
9999
|
+
onDismissRef.current = onDismiss;
|
|
10000
|
+
}, [onDismiss]);
|
|
10001
|
+
useEffect(() => () => {
|
|
10002
|
+
var _a;
|
|
10003
|
+
return (_a = detachRef.current) == null ? void 0 : _a.call(detachRef);
|
|
10004
|
+
}, []);
|
|
10005
|
+
return useCallback(
|
|
10006
|
+
(content) => {
|
|
10007
|
+
var _a;
|
|
10008
|
+
(_a = detachRef.current) == null ? void 0 : _a.call(detachRef);
|
|
10009
|
+
detachRef.current = null;
|
|
10010
|
+
if (!content) return;
|
|
10011
|
+
const root = content.getRootNode();
|
|
10012
|
+
if (!(root instanceof ShadowRoot)) return;
|
|
10013
|
+
detachRef.current = registerLayer(root, {
|
|
10014
|
+
content,
|
|
10015
|
+
focusOutside,
|
|
10016
|
+
dismiss: () => {
|
|
10017
|
+
var _a2;
|
|
10018
|
+
return (_a2 = onDismissRef.current) == null ? void 0 : _a2.call(onDismissRef);
|
|
10019
|
+
}
|
|
10020
|
+
});
|
|
10021
|
+
},
|
|
10022
|
+
[focusOutside]
|
|
10023
|
+
);
|
|
10024
|
+
}
|
|
10025
|
+
function createShadowDismissRoot(Root3, options) {
|
|
10026
|
+
const DismissContext = React.createContext(void 0);
|
|
10027
|
+
function ShadowDismissRoot({ open, defaultOpen = false, onOpenChange, ...rest }) {
|
|
10028
|
+
const [uncontrolledOpen, setUncontrolledOpen] = React.useState(defaultOpen);
|
|
10029
|
+
const isControlled = open !== void 0;
|
|
10030
|
+
const setOpen = React.useCallback(
|
|
10031
|
+
(next) => {
|
|
10032
|
+
if (!isControlled) setUncontrolledOpen(next);
|
|
10033
|
+
onOpenChange == null ? void 0 : onOpenChange(next);
|
|
10034
|
+
},
|
|
10035
|
+
[isControlled, onOpenChange]
|
|
10036
|
+
);
|
|
10037
|
+
const dismiss2 = React.useCallback(() => setOpen(false), [setOpen]);
|
|
10038
|
+
return /* @__PURE__ */ jsx(DismissContext.Provider, { value: dismiss2, children: /* @__PURE__ */ jsx(
|
|
10039
|
+
Root3,
|
|
10040
|
+
{
|
|
10041
|
+
...rest,
|
|
10042
|
+
open: isControlled ? open : uncontrolledOpen,
|
|
10043
|
+
onOpenChange: setOpen
|
|
10044
|
+
}
|
|
10045
|
+
) });
|
|
10046
|
+
}
|
|
10047
|
+
function useContentDismissRef(forwardedRef) {
|
|
10048
|
+
const dismissRef = useShadowOutsideDismiss(React.useContext(DismissContext), options);
|
|
10049
|
+
return React.useCallback(
|
|
10050
|
+
(node) => {
|
|
10051
|
+
dismissRef(node);
|
|
10052
|
+
if (typeof forwardedRef === "function") forwardedRef(node);
|
|
10053
|
+
else if (forwardedRef) forwardedRef.current = node;
|
|
10054
|
+
},
|
|
10055
|
+
[dismissRef, forwardedRef]
|
|
10056
|
+
);
|
|
10057
|
+
}
|
|
10058
|
+
return [ShadowDismissRoot, useContentDismissRef];
|
|
10059
|
+
}
|
|
10060
|
+
const [Popover, usePopoverContentDismissRef] = createShadowDismissRoot(Root2);
|
|
7968
10061
|
const PopoverTrigger = Trigger;
|
|
7969
10062
|
const PopoverAnchor = Anchor2;
|
|
7970
10063
|
const PopoverContent = React.forwardRef(({ className, align = "center", sideOffset = 6, onEscapeKeyDown, ...props }, ref) => {
|
|
7971
10064
|
const portalContainer = usePortalContainer();
|
|
10065
|
+
const contentRef = usePopoverContentDismissRef(ref);
|
|
7972
10066
|
return /* @__PURE__ */ jsx(Portal, { container: portalContainer, children: /* @__PURE__ */ jsx(
|
|
7973
10067
|
Content2,
|
|
7974
10068
|
{
|
|
7975
|
-
ref,
|
|
10069
|
+
ref: contentRef,
|
|
7976
10070
|
align,
|
|
7977
10071
|
sideOffset,
|
|
7978
10072
|
onEscapeKeyDown: stopEscapePropagation(onEscapeKeyDown),
|
|
@@ -9354,7 +11448,9 @@ const X = createLucideIcon("X", [
|
|
|
9354
11448
|
["path", { d: "M18 6 6 18", key: "1bl5f8" }],
|
|
9355
11449
|
["path", { d: "m6 6 12 12", key: "d8bk6v" }]
|
|
9356
11450
|
]);
|
|
9357
|
-
const Select$1 = Select$2
|
|
11451
|
+
const [Select$1, useSelectContentDismissRef] = createShadowDismissRoot(Select$2, {
|
|
11452
|
+
focusOutside: false
|
|
11453
|
+
});
|
|
9358
11454
|
const SelectValue = SelectValue$1;
|
|
9359
11455
|
const SelectTrigger = React.forwardRef(({ className, children, ...props }, ref) => /* @__PURE__ */ jsxs(
|
|
9360
11456
|
SelectTrigger$1,
|
|
@@ -9406,10 +11502,11 @@ const SelectScrollDownButton = React.forwardRef(({ className, ...props }, ref) =
|
|
|
9406
11502
|
SelectScrollDownButton.displayName = SelectScrollDownButton$1.displayName;
|
|
9407
11503
|
const SelectContent = React.forwardRef(({ className, children, position = "popper", onEscapeKeyDown, ...props }, ref) => {
|
|
9408
11504
|
const portalContainer = usePortalContainer();
|
|
11505
|
+
const contentRef = useSelectContentDismissRef(ref);
|
|
9409
11506
|
return /* @__PURE__ */ jsx(SelectPortal, { container: portalContainer, children: /* @__PURE__ */ jsxs(
|
|
9410
11507
|
SelectContent$1,
|
|
9411
11508
|
{
|
|
9412
|
-
ref,
|
|
11509
|
+
ref: contentRef,
|
|
9413
11510
|
"data-testid": "select-content",
|
|
9414
11511
|
sideOffset: 6,
|
|
9415
11512
|
onEscapeKeyDown: stopEscapePropagation(onEscapeKeyDown),
|
|
@@ -13977,6 +16074,7 @@ function DatePicker({
|
|
|
13977
16074
|
};
|
|
13978
16075
|
const showClear = clearable && Boolean(value) && !disabled;
|
|
13979
16076
|
const selectedDate = fromIso(value);
|
|
16077
|
+
const dismissRef = useShadowOutsideDismiss(React.useCallback(() => setOpen(false), []));
|
|
13980
16078
|
return /* @__PURE__ */ jsxs(Root2, { open, onOpenChange: setOpen, children: [
|
|
13981
16079
|
/* @__PURE__ */ jsx(
|
|
13982
16080
|
DatePickerTriggerField,
|
|
@@ -13994,6 +16092,7 @@ function DatePicker({
|
|
|
13994
16092
|
/* @__PURE__ */ jsx(Portal, { container: portalContainer, children: /* @__PURE__ */ jsx(
|
|
13995
16093
|
Content2,
|
|
13996
16094
|
{
|
|
16095
|
+
ref: dismissRef,
|
|
13997
16096
|
align,
|
|
13998
16097
|
side: "bottom",
|
|
13999
16098
|
sideOffset: 6,
|
|
@@ -15109,50 +17208,6 @@ function useOnboardingFlow() {
|
|
|
15109
17208
|
[context]
|
|
15110
17209
|
);
|
|
15111
17210
|
}
|
|
15112
|
-
let nextId = 0;
|
|
15113
|
-
let toasts = [];
|
|
15114
|
-
const listeners = /* @__PURE__ */ new Set();
|
|
15115
|
-
function emit() {
|
|
15116
|
-
listeners.forEach((l2) => l2());
|
|
15117
|
-
}
|
|
15118
|
-
function addToast(message2, type, options) {
|
|
15119
|
-
const id = `toast-${++nextId}`;
|
|
15120
|
-
const t2 = {
|
|
15121
|
-
id,
|
|
15122
|
-
message: message2,
|
|
15123
|
-
type,
|
|
15124
|
-
action: options == null ? void 0 : options.action,
|
|
15125
|
-
duration: (options == null ? void 0 : options.duration) ?? 2e3,
|
|
15126
|
-
createdAt: Date.now()
|
|
15127
|
-
};
|
|
15128
|
-
toasts = [...toasts, t2];
|
|
15129
|
-
emit();
|
|
15130
|
-
return id;
|
|
15131
|
-
}
|
|
15132
|
-
function dismiss(id) {
|
|
15133
|
-
toasts = toasts.filter((t2) => t2.id !== id);
|
|
15134
|
-
emit();
|
|
15135
|
-
}
|
|
15136
|
-
function dismissAll() {
|
|
15137
|
-
toasts = [];
|
|
15138
|
-
emit();
|
|
15139
|
-
}
|
|
15140
|
-
function toast(message2, options) {
|
|
15141
|
-
return addToast(message2, "default", options);
|
|
15142
|
-
}
|
|
15143
|
-
toast.success = (message2, options) => addToast(message2, "success", options);
|
|
15144
|
-
toast.warning = (message2, options) => addToast(message2, "warning", options);
|
|
15145
|
-
toast.error = (message2, options) => addToast(message2, "error", options);
|
|
15146
|
-
toast.info = (message2, options) => addToast(message2, "info", options);
|
|
15147
|
-
toast.dismiss = dismiss;
|
|
15148
|
-
toast.dismissAll = dismissAll;
|
|
15149
|
-
function subscribe(listener) {
|
|
15150
|
-
listeners.add(listener);
|
|
15151
|
-
return () => listeners.delete(listener);
|
|
15152
|
-
}
|
|
15153
|
-
function getSnapshot() {
|
|
15154
|
-
return toasts;
|
|
15155
|
-
}
|
|
15156
17211
|
({
|
|
15157
17212
|
type: [NetworkLogType.XMLHTTPREQUEST, NetworkLogType.FETCH]
|
|
15158
17213
|
});
|
|
@@ -15294,20 +17349,6 @@ function filterExcludeServiceRelatedConsoleLogs(consoleLogs) {
|
|
|
15294
17349
|
return !isIssueStickerInternalConsoleLog(log) && !isIssueStickerServiceConsoleLog(log);
|
|
15295
17350
|
});
|
|
15296
17351
|
}
|
|
15297
|
-
const TRANSCRIPT_CONTENT_TYPE = "application/gzip";
|
|
15298
|
-
async function uploadSocketTranscript(api, snapshot, options = {}) {
|
|
15299
|
-
if (snapshot.sizeBytes > SOCKET_TRANSCRIPT_MAX_BYTES) return null;
|
|
15300
|
-
try {
|
|
15301
|
-
const file = new File([snapshot.payload], "socket-transcript.json.gz", {
|
|
15302
|
-
type: TRANSCRIPT_CONTENT_TYPE
|
|
15303
|
-
});
|
|
15304
|
-
const uploaded = await uploadSignedMediaWithApi(api, file, "socket", options);
|
|
15305
|
-
return { uploadId: uploaded.id };
|
|
15306
|
-
} catch (error) {
|
|
15307
|
-
devLogger.error("socketTranscriptUpload", "소켓 전사 업로드 실패:", error);
|
|
15308
|
-
return null;
|
|
15309
|
-
}
|
|
15310
|
-
}
|
|
15311
17352
|
function buildExternalUserPayload(user) {
|
|
15312
17353
|
if (!user) return {};
|
|
15313
17354
|
return {
|
|
@@ -15316,91 +17357,115 @@ function buildExternalUserPayload(user) {
|
|
|
15316
17357
|
};
|
|
15317
17358
|
}
|
|
15318
17359
|
export {
|
|
15319
|
-
|
|
15320
|
-
|
|
15321
|
-
|
|
17360
|
+
SelectValue as $,
|
|
17361
|
+
SessionReplayCaptureController as A,
|
|
17362
|
+
preloadModernScreenshot as B,
|
|
15322
17363
|
ConsoleCollector as C,
|
|
15323
|
-
|
|
15324
|
-
|
|
15325
|
-
|
|
15326
|
-
|
|
15327
|
-
|
|
17364
|
+
enqueueIssueUploads as D,
|
|
17365
|
+
normalizeUploadMimeType as E,
|
|
17366
|
+
toSessionReplayMeta as F,
|
|
17367
|
+
loadTusClient as G,
|
|
17368
|
+
createIndexedDbPendingUploadStore as H,
|
|
15328
17369
|
ISSUE_STICKER_INTERNAL_LOG_PREFIX as I,
|
|
15329
|
-
|
|
15330
|
-
|
|
15331
|
-
|
|
15332
|
-
|
|
17370
|
+
ATTACHMENT_UPLOAD_DB_NAME as J,
|
|
17371
|
+
createMediaUploadApi as K,
|
|
17372
|
+
toast as L,
|
|
17373
|
+
trimLogBackupPayload as M,
|
|
15333
17374
|
NetworkCollector as N,
|
|
15334
|
-
|
|
15335
|
-
|
|
15336
|
-
|
|
15337
|
-
|
|
17375
|
+
createSafeLogBackup as O,
|
|
17376
|
+
getLogBackupSessionId as P,
|
|
17377
|
+
createIndexedDbLogBackup as Q,
|
|
17378
|
+
Popover as R,
|
|
15338
17379
|
ScreenRecordingController as S,
|
|
15339
|
-
|
|
15340
|
-
|
|
15341
|
-
|
|
17380
|
+
PopoverTrigger as T,
|
|
17381
|
+
Input as U,
|
|
17382
|
+
VIDEO_UPLOAD_DB_NAME as V,
|
|
15342
17383
|
WebSocketCollector as W,
|
|
15343
|
-
|
|
15344
|
-
|
|
15345
|
-
|
|
15346
|
-
|
|
17384
|
+
PopoverContent as X,
|
|
17385
|
+
Label as Y,
|
|
17386
|
+
Select$1 as Z,
|
|
17387
|
+
SelectTrigger as _,
|
|
15347
17388
|
uploadSignedMediaWithApi as a,
|
|
15348
|
-
|
|
15349
|
-
|
|
15350
|
-
|
|
15351
|
-
|
|
15352
|
-
|
|
15353
|
-
|
|
15354
|
-
|
|
15355
|
-
|
|
15356
|
-
|
|
15357
|
-
|
|
15358
|
-
|
|
15359
|
-
|
|
15360
|
-
|
|
15361
|
-
|
|
15362
|
-
|
|
15363
|
-
|
|
15364
|
-
|
|
15365
|
-
|
|
15366
|
-
|
|
15367
|
-
|
|
15368
|
-
|
|
15369
|
-
|
|
15370
|
-
|
|
15371
|
-
|
|
15372
|
-
|
|
15373
|
-
|
|
15374
|
-
|
|
15375
|
-
|
|
15376
|
-
|
|
15377
|
-
|
|
15378
|
-
|
|
15379
|
-
|
|
15380
|
-
|
|
15381
|
-
|
|
15382
|
-
|
|
17389
|
+
SelectContent as a0,
|
|
17390
|
+
SelectItem as a1,
|
|
17391
|
+
JIRA_ISSUE_TYPE_VALUE_KEY as a2,
|
|
17392
|
+
getJiraMetadata as a3,
|
|
17393
|
+
buildJiraIssueFields as a4,
|
|
17394
|
+
getJiraIssueTypeLabel as a5,
|
|
17395
|
+
Button$1 as a6,
|
|
17396
|
+
Root2 as a7,
|
|
17397
|
+
Trigger as a8,
|
|
17398
|
+
ChevronDown as a9,
|
|
17399
|
+
useShadowOutsideDismiss as aA,
|
|
17400
|
+
getPrimaryNotionMapping as aB,
|
|
17401
|
+
buttonVariants as aC,
|
|
17402
|
+
TEST_ATTR_KEYS as aD,
|
|
17403
|
+
jaccard as aE,
|
|
17404
|
+
textSimilarity as aF,
|
|
17405
|
+
collectHeadings as aG,
|
|
17406
|
+
buildElementLocator as aH,
|
|
17407
|
+
normalizeForCompare as aI,
|
|
17408
|
+
CONTAINER_SELECTOR as aJ,
|
|
17409
|
+
safeRole as aK,
|
|
17410
|
+
safeAccessibleName as aL,
|
|
17411
|
+
sameDigitGroups as aM,
|
|
17412
|
+
labelSimilarityKeepingDigits as aN,
|
|
17413
|
+
landmarkRole as aO,
|
|
17414
|
+
findElementBySelector as aP,
|
|
17415
|
+
findElementByXPath as aQ,
|
|
17416
|
+
OnboardingProvider as aR,
|
|
17417
|
+
useOnboardingTarget as aS,
|
|
17418
|
+
useOnboardingFlow as aT,
|
|
17419
|
+
PopoverAnchor as aU,
|
|
17420
|
+
Portal as aa,
|
|
17421
|
+
Content2 as ab,
|
|
17422
|
+
Check as ac,
|
|
17423
|
+
X as ad,
|
|
17424
|
+
DatePicker as ae,
|
|
17425
|
+
getNotionPageMetadata as af,
|
|
17426
|
+
buildNotionFieldViews as ag,
|
|
17427
|
+
DirectUploadError as ah,
|
|
17428
|
+
getRegisteredUploadId as ai,
|
|
17429
|
+
getSnapshot as aj,
|
|
17430
|
+
subscribe as ak,
|
|
17431
|
+
ChevronRight as al,
|
|
17432
|
+
ReactRemoveScroll as am,
|
|
17433
|
+
hideOthers as an,
|
|
17434
|
+
useFocusGuards as ao,
|
|
17435
|
+
FocusScope as ap,
|
|
17436
|
+
createShadowDismissRoot as aq,
|
|
17437
|
+
getExposedJiraFields as ar,
|
|
17438
|
+
isJiraFieldExposed as as,
|
|
17439
|
+
normalizeExposedJiraFields as at,
|
|
17440
|
+
resolveJiraIssueTypeId as au,
|
|
17441
|
+
pickJiraIssueTypeFields as av,
|
|
17442
|
+
switchJiraIssueType as aw,
|
|
17443
|
+
getPrimaryJiraMapping as ax,
|
|
17444
|
+
createCollection as ay,
|
|
17445
|
+
useDirection as az,
|
|
17446
|
+
captureWithModernScreenshot as b,
|
|
17447
|
+
captureViewportWithElementCrop as c,
|
|
15383
17448
|
directUploadToSignedUrl as d,
|
|
15384
|
-
|
|
15385
|
-
|
|
17449
|
+
buildJiraCreateFields as e,
|
|
17450
|
+
buildNotionCreateFields as f,
|
|
15386
17451
|
getElementPosition as g,
|
|
15387
|
-
|
|
15388
|
-
|
|
15389
|
-
|
|
15390
|
-
|
|
15391
|
-
|
|
15392
|
-
|
|
15393
|
-
|
|
15394
|
-
|
|
17452
|
+
buildGuestOwnerQuery as h,
|
|
17453
|
+
composeIssueFilterGroups as i,
|
|
17454
|
+
buildIssueIntegrationsPayload as j,
|
|
17455
|
+
devLogger as k,
|
|
17456
|
+
filterExcludeServiceRelatedLogs as l,
|
|
17457
|
+
filterExcludeServiceRelatedConsoleLogs as m,
|
|
17458
|
+
compactNetworkLogsForTransport as n,
|
|
17459
|
+
CONSOLE_LOG_SUBMIT_LIMIT as o,
|
|
15395
17460
|
pristineFetch as p,
|
|
15396
|
-
|
|
15397
|
-
|
|
17461
|
+
buildExternalUserPayload as q,
|
|
17462
|
+
shrinkNetworkLogsToFit as r,
|
|
15398
17463
|
selectNetworkLogsForSubmit as s,
|
|
15399
|
-
|
|
17464
|
+
measureJsonBytes as t,
|
|
15400
17465
|
uploadScreenshotDataUrls as u,
|
|
15401
|
-
|
|
15402
|
-
|
|
15403
|
-
|
|
15404
|
-
|
|
15405
|
-
|
|
17466
|
+
sanitizeString as v,
|
|
17467
|
+
collectAttachmentUploads as w,
|
|
17468
|
+
hasReplayUpload as x,
|
|
17469
|
+
getCurrentPageUrl as y,
|
|
17470
|
+
createIssueUploadQueues as z
|
|
15406
17471
|
};
|