@solhun/feedback-kit-web 0.5.0 → 0.6.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.cjs +316 -59
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +42 -2
- package/dist/index.d.ts +42 -2
- package/dist/index.js +320 -60
- package/dist/index.js.map +1 -1
- package/package.json +3 -3
package/dist/index.cjs
CHANGED
|
@@ -37,8 +37,10 @@ __export(index_exports, {
|
|
|
37
37
|
ElementPickingController: () => ElementPickingController,
|
|
38
38
|
FLOATING_BUTTON_ID: () => import_feedback_kit_core6.FLOATING_BUTTON_ID,
|
|
39
39
|
FeedbackKit: () => FeedbackKit,
|
|
40
|
+
HintProvider: () => import_feedback_kit_core7.HintProvider,
|
|
40
41
|
MARKER_KEY_PREFIX: () => MARKER_KEY_PREFIX,
|
|
41
42
|
MAX_MARKERS_PER_PATH: () => MAX_MARKERS_PER_PATH,
|
|
43
|
+
MODAL_ACTION_HINT_TOGGLE: () => import_feedback_kit_core6.MODAL_ACTION_HINT_TOGGLE,
|
|
42
44
|
MODAL_ACTION_PICK: () => import_feedback_kit_core6.MODAL_ACTION_PICK,
|
|
43
45
|
MarkerStore: () => MarkerStore,
|
|
44
46
|
OWN_UI_ATTR: () => OWN_UI_ATTR,
|
|
@@ -59,6 +61,7 @@ __export(index_exports, {
|
|
|
59
61
|
isOwnUi: () => isOwnUi2,
|
|
60
62
|
normalizePin: () => import_feedback_kit_core6.normalizePin,
|
|
61
63
|
parseSourceAttr: () => import_feedback_kit_core5.parseSourceAttr,
|
|
64
|
+
rankHints: () => import_feedback_kit_core7.rankHints,
|
|
62
65
|
reactComponentPath: () => reactComponentPath,
|
|
63
66
|
reactComponentSummary: () => reactComponentSummary,
|
|
64
67
|
reencodeWebScreenshot: () => reencodeWebScreenshot,
|
|
@@ -72,6 +75,7 @@ module.exports = __toCommonJS(index_exports);
|
|
|
72
75
|
var import_feedback_kit_core4 = require("@solhun/feedback-kit-core");
|
|
73
76
|
var import_feedback_kit_core5 = require("@solhun/feedback-kit-core");
|
|
74
77
|
var import_feedback_kit_core6 = require("@solhun/feedback-kit-core");
|
|
78
|
+
var import_feedback_kit_core7 = require("@solhun/feedback-kit-core");
|
|
75
79
|
|
|
76
80
|
// src/react-tree.ts
|
|
77
81
|
var FRAMEWORK_INTERNALS = /* @__PURE__ */ new Set([
|
|
@@ -381,6 +385,11 @@ var ElementPickingController = class {
|
|
|
381
385
|
this.shotGeneration = 0;
|
|
382
386
|
/** 지금 도는 자동 캡처. Enter 가 캡처보다 빨랐을 때 기다릴 대상. */
|
|
383
387
|
this.capturing = null;
|
|
388
|
+
/**
|
|
389
|
+
* 사용자가 [제안 ▾] 를 직접 건드렸는가. 한 번이라도 건드리면 이후 타이핑에 의한 자동
|
|
390
|
+
* 접힘/펼침이 멈춘다 — 모달의 `userToggledHints` 와 같은 규칙. 팝업을 새로 열 때마다 리셋된다.
|
|
391
|
+
*/
|
|
392
|
+
this.userToggledHints = false;
|
|
384
393
|
this.onClick = (event) => this.handleClick(event);
|
|
385
394
|
this.onMouseOver = (event) => this.handleMouseOver(event);
|
|
386
395
|
this.queue = opts.queue;
|
|
@@ -393,6 +402,7 @@ var ElementPickingController = class {
|
|
|
393
402
|
this.capture = opts.capture ?? null;
|
|
394
403
|
this.reencode = opts.reencode ?? null;
|
|
395
404
|
this.screenshotLimitBytes = opts.screenshotLimitBytes;
|
|
405
|
+
this.rankHintsFor = opts.rankHintsFor ?? null;
|
|
396
406
|
this.lastPathname = this.getPathname();
|
|
397
407
|
this.markers = this.store.list(this.lastPathname);
|
|
398
408
|
this.unsubscribeQueue = this.queue.subscribe?.(() => {
|
|
@@ -507,14 +517,22 @@ var ElementPickingController = class {
|
|
|
507
517
|
this.onPick(describeElement(element), point);
|
|
508
518
|
return;
|
|
509
519
|
}
|
|
520
|
+
const info = describeElement(element);
|
|
521
|
+
this.userToggledHints = false;
|
|
522
|
+
const hints = this.rankHintsFor ? this.rankHintsFor(info) : [];
|
|
510
523
|
this.popup = {
|
|
511
|
-
element:
|
|
524
|
+
element: info,
|
|
512
525
|
point,
|
|
513
526
|
comment: "",
|
|
514
527
|
canSave: false,
|
|
515
528
|
screenshot: null,
|
|
516
529
|
screenshotStatus: this.capture ? "capturing" : "none",
|
|
517
|
-
saving: false
|
|
530
|
+
saving: false,
|
|
531
|
+
hints,
|
|
532
|
+
// 코멘트가 비어 있을 때만 펼친다 — 모달과 같은 규칙(계약 원문). 방금 연 팝업은 항상 빈다.
|
|
533
|
+
hintsExpanded: true,
|
|
534
|
+
hintsAvailable: hints.length > 0,
|
|
535
|
+
usedHintIds: []
|
|
518
536
|
};
|
|
519
537
|
this.emit();
|
|
520
538
|
if (this.capture) this.capturing = this.runCapture();
|
|
@@ -524,10 +542,48 @@ var ElementPickingController = class {
|
|
|
524
542
|
this.popup = {
|
|
525
543
|
...this.popup,
|
|
526
544
|
comment: value,
|
|
527
|
-
canSave: this.isSavable(value)
|
|
545
|
+
canSave: this.isSavable(value),
|
|
546
|
+
...this.hintsPatchFor(value)
|
|
528
547
|
};
|
|
529
548
|
this.emit();
|
|
530
549
|
}
|
|
550
|
+
/**
|
|
551
|
+
* 코멘트가 비었는지에 따라 칩 줄 상태를 갱신하는 조각. `setAnnotationComment`·`applyHint`
|
|
552
|
+
* 양쪽에서 쓴다 — 모달의 `hintsPatchFor` 와 같은 규칙이다.
|
|
553
|
+
*/
|
|
554
|
+
hintsPatchFor(nextComment) {
|
|
555
|
+
if (nextComment.trim().length === 0) {
|
|
556
|
+
return { hintsExpanded: true, usedHintIds: [] };
|
|
557
|
+
}
|
|
558
|
+
return this.userToggledHints ? {} : { hintsExpanded: false };
|
|
559
|
+
}
|
|
560
|
+
/**
|
|
561
|
+
* 칩 누름. 이미 눌린 칩이면 아무 일도 하지 않는다. 코멘트가 비어 있으면 초안으로 치환,
|
|
562
|
+
* 아니면 줄바꿈 뒤 이어붙인다 — 모달의 `applyHint` 와 동일한 규칙이다.
|
|
563
|
+
*/
|
|
564
|
+
applyHint(hintId) {
|
|
565
|
+
if (!this.popup || this.popup.saving) return;
|
|
566
|
+
if (this.popup.usedHintIds.includes(hintId)) return;
|
|
567
|
+
const hint = this.popup.hints.find((h) => h.id === hintId);
|
|
568
|
+
if (!hint) return;
|
|
569
|
+
const comment = this.popup.comment.length === 0 ? hint.draft : `${this.popup.comment}
|
|
570
|
+
${hint.draft}`;
|
|
571
|
+
this.popup = {
|
|
572
|
+
...this.popup,
|
|
573
|
+
comment,
|
|
574
|
+
canSave: this.isSavable(comment),
|
|
575
|
+
usedHintIds: [...this.popup.usedHintIds, hintId],
|
|
576
|
+
...this.hintsPatchFor(comment)
|
|
577
|
+
};
|
|
578
|
+
this.emit();
|
|
579
|
+
}
|
|
580
|
+
/** [제안 ▾] 토글. 방향과 무관하게 이후 자동 접힘/펼침을 멈춘다. */
|
|
581
|
+
toggleHints() {
|
|
582
|
+
if (!this.popup) return;
|
|
583
|
+
this.userToggledHints = true;
|
|
584
|
+
this.popup = { ...this.popup, hintsExpanded: !this.popup.hintsExpanded };
|
|
585
|
+
this.emit();
|
|
586
|
+
}
|
|
531
587
|
/**
|
|
532
588
|
* 사용자가 붙여넣기(Cmd+V)나 드래그로 넣은 그림. 자동 캡처 결과를 덮는다.
|
|
533
589
|
*
|
|
@@ -634,7 +690,9 @@ var ElementPickingController = class {
|
|
|
634
690
|
priority: "unset",
|
|
635
691
|
screenshot: settled.screenshot,
|
|
636
692
|
pin: settled.point,
|
|
637
|
-
element: settled.element
|
|
693
|
+
element: settled.element,
|
|
694
|
+
// 눌린 칩 기록. 리포트 모달과 같은 필드 — 들어온 문이 달라도 채택 신호는 같은 값으로 쌓인다.
|
|
695
|
+
hintIds: settled.usedHintIds
|
|
638
696
|
};
|
|
639
697
|
let report;
|
|
640
698
|
try {
|
|
@@ -795,11 +853,100 @@ var reencodeWebScreenshot = async (shot, quality) => {
|
|
|
795
853
|
|
|
796
854
|
// src/widget.ts
|
|
797
855
|
var import_feedback_kit_core2 = require("@solhun/feedback-kit-core");
|
|
856
|
+
|
|
857
|
+
// src/storage.ts
|
|
858
|
+
function memoryStorage() {
|
|
859
|
+
const map = /* @__PURE__ */ new Map();
|
|
860
|
+
return {
|
|
861
|
+
getItem: (key) => map.get(key) ?? null,
|
|
862
|
+
setItem: (key, value) => void map.set(key, value),
|
|
863
|
+
removeItem: (key) => void map.delete(key)
|
|
864
|
+
};
|
|
865
|
+
}
|
|
866
|
+
function pickBacking() {
|
|
867
|
+
try {
|
|
868
|
+
const ls = globalThis.localStorage;
|
|
869
|
+
if (!ls) return memoryStorage();
|
|
870
|
+
const probe = "__feedback_kit_probe__";
|
|
871
|
+
ls.setItem(probe, "1");
|
|
872
|
+
ls.removeItem(probe);
|
|
873
|
+
return ls;
|
|
874
|
+
} catch {
|
|
875
|
+
return memoryStorage();
|
|
876
|
+
}
|
|
877
|
+
}
|
|
878
|
+
function createWebStorage(backing) {
|
|
879
|
+
const store = backing ?? pickBacking();
|
|
880
|
+
return {
|
|
881
|
+
async get(key) {
|
|
882
|
+
try {
|
|
883
|
+
return store.getItem(key);
|
|
884
|
+
} catch {
|
|
885
|
+
return null;
|
|
886
|
+
}
|
|
887
|
+
},
|
|
888
|
+
async set(key, value) {
|
|
889
|
+
store.setItem(key, value);
|
|
890
|
+
},
|
|
891
|
+
async remove(key) {
|
|
892
|
+
try {
|
|
893
|
+
store.removeItem(key);
|
|
894
|
+
} catch {
|
|
895
|
+
}
|
|
896
|
+
}
|
|
897
|
+
};
|
|
898
|
+
}
|
|
899
|
+
|
|
900
|
+
// src/widget.ts
|
|
901
|
+
function stripQueryHash(path) {
|
|
902
|
+
const cut = [path.indexOf("?"), path.indexOf("#")].filter((i) => i >= 0);
|
|
903
|
+
return cut.length > 0 ? path.slice(0, Math.min(...cut)) : path;
|
|
904
|
+
}
|
|
905
|
+
function defaultPathname2() {
|
|
906
|
+
const loc = globalThis.location;
|
|
907
|
+
return loc?.pathname ?? "/";
|
|
908
|
+
}
|
|
909
|
+
function hintSourceFrom(queue) {
|
|
910
|
+
const adapter = queue.adapter;
|
|
911
|
+
if (!adapter || typeof adapter.fetchHints !== "function") return null;
|
|
912
|
+
const cacheKey = adapter.hintsCacheKey ?? "default";
|
|
913
|
+
return { fetchHints: adapter.fetchHints.bind(adapter), cacheKey };
|
|
914
|
+
}
|
|
915
|
+
function resolveHintProvider(opts) {
|
|
916
|
+
if (opts.hintProvider) return opts.hintProvider;
|
|
917
|
+
if (opts.hints) {
|
|
918
|
+
return new import_feedback_kit_core2.HintProvider({ source: null, staticHints: opts.hints, cacheKey: "static" });
|
|
919
|
+
}
|
|
920
|
+
const found = hintSourceFrom(opts.queue);
|
|
921
|
+
if (!found) return null;
|
|
922
|
+
return new import_feedback_kit_core2.HintProvider({
|
|
923
|
+
source: { fetchHints: (etag) => found.fetchHints(etag) },
|
|
924
|
+
storage: createWebStorage(),
|
|
925
|
+
cacheKey: found.cacheKey
|
|
926
|
+
});
|
|
927
|
+
}
|
|
928
|
+
var POPUP_HINT_DISPLAY_MAX = 3;
|
|
929
|
+
function rankFor(hintProvider, opts, element, limitOverride) {
|
|
930
|
+
const catalog = hintProvider.catalog();
|
|
931
|
+
if (!catalog) return [];
|
|
932
|
+
const input = {
|
|
933
|
+
platform: "web",
|
|
934
|
+
path: stripQueryHash(opts.getPathname ? opts.getPathname() : defaultPathname2()),
|
|
935
|
+
element,
|
|
936
|
+
components: element ? element.attributes["react-components"] ?? null : null
|
|
937
|
+
};
|
|
938
|
+
const limit = limitOverride !== void 0 ? Math.min(limitOverride, catalog.maxDisplay) : catalog.maxDisplay;
|
|
939
|
+
return (0, import_feedback_kit_core2.rankHints)(catalog.hints, input, limit);
|
|
940
|
+
}
|
|
798
941
|
function createWebWidget(opts) {
|
|
799
942
|
const store = opts.store ?? new MarkerStore();
|
|
800
943
|
let widgetRef = null;
|
|
801
944
|
const capture = opts.capture === void 0 ? captureWebScreenshot : opts.capture;
|
|
802
945
|
const reencode = opts.reencode === void 0 ? reencodeWebScreenshot : opts.reencode;
|
|
946
|
+
const hintProvider = resolveHintProvider(opts);
|
|
947
|
+
if (hintProvider) {
|
|
948
|
+
void hintProvider.load().then(() => hintProvider.refresh());
|
|
949
|
+
}
|
|
803
950
|
const picking = new ElementPickingController({
|
|
804
951
|
queue: opts.queue,
|
|
805
952
|
createReport: opts.createReport,
|
|
@@ -815,7 +962,8 @@ function createWebWidget(opts) {
|
|
|
815
962
|
// 모드는 어느 쪽이든 켜진 채로 둬서 한 번 켜고 여러 요소를 연달아 지목할 수 있다.
|
|
816
963
|
onPick: opts.pickTarget === "modal" ? (element, point) => {
|
|
817
964
|
void widgetRef?.openReport({ element, pin: point });
|
|
818
|
-
} : void 0
|
|
965
|
+
} : void 0,
|
|
966
|
+
rankHintsFor: hintProvider ? (element) => rankFor(hintProvider, opts, element, POPUP_HINT_DISPLAY_MAX) : null
|
|
819
967
|
});
|
|
820
968
|
const widget = new import_feedback_kit_core2.WidgetController({
|
|
821
969
|
...opts,
|
|
@@ -830,6 +978,13 @@ function createWebWidget(opts) {
|
|
|
830
978
|
}
|
|
831
979
|
});
|
|
832
980
|
widgetRef = widget;
|
|
981
|
+
const originalOpenReport = widget.openReport.bind(widget);
|
|
982
|
+
widget.openReport = async (openOpts = {}) => {
|
|
983
|
+
if (hintProvider) {
|
|
984
|
+
widget.modal.setHints(rankFor(hintProvider, opts, openOpts.element ?? null));
|
|
985
|
+
}
|
|
986
|
+
await originalOpenReport(openOpts);
|
|
987
|
+
};
|
|
833
988
|
const originalSubmit = widget.submitReport.bind(widget);
|
|
834
989
|
widget.submitReport = async () => {
|
|
835
990
|
const outcome = await originalSubmit();
|
|
@@ -854,7 +1009,7 @@ var import_feedback_kit_core3 = require("@solhun/feedback-kit-core");
|
|
|
854
1009
|
var import_react = require("react");
|
|
855
1010
|
|
|
856
1011
|
// src/version.ts
|
|
857
|
-
var VERSION = true ? "0.
|
|
1012
|
+
var VERSION = true ? "0.6.1" : "dev";
|
|
858
1013
|
|
|
859
1014
|
// src/feedback-kit.tsx
|
|
860
1015
|
var import_jsx_runtime = require("react/jsx-runtime");
|
|
@@ -917,26 +1072,41 @@ async function transcodeToJpeg(dataUrl) {
|
|
|
917
1072
|
return null;
|
|
918
1073
|
}
|
|
919
1074
|
}
|
|
1075
|
+
function looksLikeImageName(name) {
|
|
1076
|
+
return /\.(png|jpe?g|gif|webp|bmp|avif|heic|heif)$/i.test(name);
|
|
1077
|
+
}
|
|
1078
|
+
function isImageFile(file) {
|
|
1079
|
+
if (file.type.startsWith("image/")) return true;
|
|
1080
|
+
return (file.type === "" || file.type === "application/octet-stream") && looksLikeImageName(file.name);
|
|
1081
|
+
}
|
|
1082
|
+
function contentTypeOf(file) {
|
|
1083
|
+
if (file.type !== "") return file.type;
|
|
1084
|
+
const name = file instanceof File ? file.name : "";
|
|
1085
|
+
if (/\.png$/i.test(name)) return "image/png";
|
|
1086
|
+
if (/\.jpe?g$/i.test(name)) return "image/jpeg";
|
|
1087
|
+
return "";
|
|
1088
|
+
}
|
|
920
1089
|
async function readImageFile(file) {
|
|
921
|
-
|
|
1090
|
+
const named = file instanceof File ? file : null;
|
|
1091
|
+
if (!file.type.startsWith("image/") && !(named !== null && isImageFile(named))) return null;
|
|
922
1092
|
const dataUrl = await readDataUrl(file);
|
|
923
1093
|
if (!dataUrl) return null;
|
|
924
|
-
|
|
1094
|
+
const type = contentTypeOf(file);
|
|
1095
|
+
if (type === "image/png" || type === "image/jpeg") {
|
|
925
1096
|
const base64 = base64Of(dataUrl);
|
|
926
|
-
return base64 ? { base64, contentType:
|
|
1097
|
+
return base64 ? { base64, contentType: type } : null;
|
|
927
1098
|
}
|
|
928
1099
|
return transcodeToJpeg(dataUrl);
|
|
929
1100
|
}
|
|
930
1101
|
function firstImageOf(transfer) {
|
|
931
1102
|
if (!transfer) return null;
|
|
932
1103
|
for (const item of Array.from(transfer.items ?? [])) {
|
|
933
|
-
if (item.kind
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
}
|
|
1104
|
+
if (item.kind !== "file") continue;
|
|
1105
|
+
const file = item.getAsFile();
|
|
1106
|
+
if (file && isImageFile(file)) return file;
|
|
937
1107
|
}
|
|
938
1108
|
for (const file of Array.from(transfer.files ?? [])) {
|
|
939
|
-
if (file
|
|
1109
|
+
if (isImageFile(file)) return file;
|
|
940
1110
|
}
|
|
941
1111
|
return null;
|
|
942
1112
|
}
|
|
@@ -1116,6 +1286,69 @@ function MarkerStatus({ status }) {
|
|
|
1116
1286
|
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { children: presentation.text })
|
|
1117
1287
|
] });
|
|
1118
1288
|
}
|
|
1289
|
+
function HintChips({
|
|
1290
|
+
hints,
|
|
1291
|
+
hintsExpanded,
|
|
1292
|
+
usedHintIds,
|
|
1293
|
+
disabled,
|
|
1294
|
+
onApply,
|
|
1295
|
+
onToggle
|
|
1296
|
+
}) {
|
|
1297
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: { marginBottom: 16 }, children: [
|
|
1298
|
+
hintsExpanded ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
|
|
1299
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { style: { margin: "0 0 7px", color: TOKENS.muted, fontSize: 12 }, children: "\uC774\uB7F0 \uAC74\uAC00\uC694?" }),
|
|
1300
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
1301
|
+
"div",
|
|
1302
|
+
{
|
|
1303
|
+
role: "group",
|
|
1304
|
+
"aria-label": "\uC790\uC8FC \uB098\uC624\uB294 \uC81C\uBCF4 \uC81C\uC548",
|
|
1305
|
+
style: { display: "flex", flexWrap: "wrap", gap: 6, marginBottom: 7 },
|
|
1306
|
+
children: hints.map((hint) => {
|
|
1307
|
+
const used = usedHintIds.includes(hint.id);
|
|
1308
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
1309
|
+
"button",
|
|
1310
|
+
{
|
|
1311
|
+
type: "button",
|
|
1312
|
+
"data-fk-focus-id": `hint:${hint.id}`,
|
|
1313
|
+
disabled: used || disabled,
|
|
1314
|
+
"aria-pressed": used,
|
|
1315
|
+
onClick: () => onApply(hint.id),
|
|
1316
|
+
style: {
|
|
1317
|
+
...baseButton,
|
|
1318
|
+
minHeight: 30,
|
|
1319
|
+
padding: "5px 10px",
|
|
1320
|
+
fontSize: 12,
|
|
1321
|
+
fontWeight: 600,
|
|
1322
|
+
borderRadius: 999,
|
|
1323
|
+
...used ? {
|
|
1324
|
+
background: TOKENS.subtle,
|
|
1325
|
+
color: TOKENS.muted,
|
|
1326
|
+
borderColor: TOKENS.line,
|
|
1327
|
+
cursor: "default"
|
|
1328
|
+
} : {}
|
|
1329
|
+
},
|
|
1330
|
+
children: hint.label
|
|
1331
|
+
},
|
|
1332
|
+
hint.id
|
|
1333
|
+
);
|
|
1334
|
+
})
|
|
1335
|
+
}
|
|
1336
|
+
)
|
|
1337
|
+
] }) : null,
|
|
1338
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
1339
|
+
"button",
|
|
1340
|
+
{
|
|
1341
|
+
type: "button",
|
|
1342
|
+
"data-fk-focus-id": import_feedback_kit_core3.MODAL_ACTION_HINT_TOGGLE,
|
|
1343
|
+
"aria-expanded": hintsExpanded,
|
|
1344
|
+
disabled,
|
|
1345
|
+
onClick: onToggle,
|
|
1346
|
+
style: { ...baseButton, minHeight: 28, padding: "4px 10px", fontSize: 12 },
|
|
1347
|
+
children: hintsExpanded ? "\uC81C\uC548 \u25B4" : "\uC81C\uC548 \u25BE"
|
|
1348
|
+
}
|
|
1349
|
+
)
|
|
1350
|
+
] });
|
|
1351
|
+
}
|
|
1119
1352
|
function FeedbackKit(props) {
|
|
1120
1353
|
const rootRef = (0, import_react.useRef)(null);
|
|
1121
1354
|
const floatingButtonRef = (0, import_react.useRef)(null);
|
|
@@ -1138,7 +1371,10 @@ function FeedbackKit(props) {
|
|
|
1138
1371
|
store,
|
|
1139
1372
|
getPathname,
|
|
1140
1373
|
doc,
|
|
1141
|
-
getViewport
|
|
1374
|
+
getViewport,
|
|
1375
|
+
pickTarget,
|
|
1376
|
+
hints,
|
|
1377
|
+
hintProvider
|
|
1142
1378
|
} = props;
|
|
1143
1379
|
(0, import_react.useEffect)(() => {
|
|
1144
1380
|
const created = createWebWidget({
|
|
@@ -1150,7 +1386,10 @@ function FeedbackKit(props) {
|
|
|
1150
1386
|
store,
|
|
1151
1387
|
getPathname,
|
|
1152
1388
|
doc,
|
|
1153
|
-
getViewport
|
|
1389
|
+
getViewport,
|
|
1390
|
+
pickTarget,
|
|
1391
|
+
hints,
|
|
1392
|
+
hintProvider
|
|
1154
1393
|
});
|
|
1155
1394
|
setKit(created);
|
|
1156
1395
|
const unsubscribeWidget = created.widget.subscribe(setWidgetState);
|
|
@@ -1168,6 +1407,9 @@ function FeedbackKit(props) {
|
|
|
1168
1407
|
screenshotLimitBytes,
|
|
1169
1408
|
store,
|
|
1170
1409
|
getPathname,
|
|
1410
|
+
pickTarget,
|
|
1411
|
+
hints,
|
|
1412
|
+
hintProvider,
|
|
1171
1413
|
doc,
|
|
1172
1414
|
getViewport
|
|
1173
1415
|
]);
|
|
@@ -1206,6 +1448,23 @@ function FeedbackKit(props) {
|
|
|
1206
1448
|
document.addEventListener("keydown", onKeyDownCapture, true);
|
|
1207
1449
|
return () => document.removeEventListener("keydown", onKeyDownCapture, true);
|
|
1208
1450
|
}, [kit, widgetState, pickingState.popup]);
|
|
1451
|
+
(0, import_react.useEffect)(() => {
|
|
1452
|
+
if (!kit || !widgetState?.modal.open) return;
|
|
1453
|
+
if (pickingState.popup) return;
|
|
1454
|
+
const locked = widgetState.modal.submitStatus === "sending" || widgetState.modal.submitStatus === "pending";
|
|
1455
|
+
if (locked) return;
|
|
1456
|
+
const controller = kit;
|
|
1457
|
+
async function onPaste(event) {
|
|
1458
|
+
const file = firstImageOf(event.clipboardData);
|
|
1459
|
+
if (!file) return;
|
|
1460
|
+
event.preventDefault();
|
|
1461
|
+
const shot = await readImageFile(file);
|
|
1462
|
+
if (shot) await controller.widget.modal.attachFile(shot);
|
|
1463
|
+
}
|
|
1464
|
+
const handler = (event) => void onPaste(event);
|
|
1465
|
+
document.addEventListener("paste", handler);
|
|
1466
|
+
return () => document.removeEventListener("paste", handler);
|
|
1467
|
+
}, [kit, widgetState, pickingState.popup]);
|
|
1209
1468
|
if (!kit || !widgetState) {
|
|
1210
1469
|
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { ...OWN_UI_PROPS, "data-feedback-kit-loading": "true" });
|
|
1211
1470
|
}
|
|
@@ -1243,6 +1502,14 @@ function FeedbackKit(props) {
|
|
|
1243
1502
|
}
|
|
1244
1503
|
input.value = "";
|
|
1245
1504
|
}
|
|
1505
|
+
async function attachToModal(transfer) {
|
|
1506
|
+
const file = firstImageOf(transfer);
|
|
1507
|
+
if (!file) return false;
|
|
1508
|
+
const screenshot = await readImageFile(file);
|
|
1509
|
+
if (!screenshot) return false;
|
|
1510
|
+
await activeKit.widget.modal.attachFile(screenshot);
|
|
1511
|
+
return true;
|
|
1512
|
+
}
|
|
1246
1513
|
async function attachToPopup(transfer) {
|
|
1247
1514
|
const file = firstImageOf(transfer);
|
|
1248
1515
|
if (!file) return false;
|
|
@@ -1343,6 +1610,14 @@ function FeedbackKit(props) {
|
|
|
1343
1610
|
"aria-modal": "true",
|
|
1344
1611
|
"aria-labelledby": "feedback-kit-dialog-title",
|
|
1345
1612
|
onKeyDown: handleModalKeyDown,
|
|
1613
|
+
onDragOver: (event) => {
|
|
1614
|
+
if (!draftLocked && firstImageOf(event.dataTransfer)) event.preventDefault();
|
|
1615
|
+
},
|
|
1616
|
+
onDrop: (event) => {
|
|
1617
|
+
if (draftLocked || !firstImageOf(event.dataTransfer)) return;
|
|
1618
|
+
event.preventDefault();
|
|
1619
|
+
void attachToModal(event.dataTransfer);
|
|
1620
|
+
},
|
|
1346
1621
|
style: {
|
|
1347
1622
|
width: "min(520px, 100%)",
|
|
1348
1623
|
maxHeight: "min(760px, calc(100vh - 40px))",
|
|
@@ -1466,6 +1741,17 @@ function FeedbackKit(props) {
|
|
|
1466
1741
|
)
|
|
1467
1742
|
] })
|
|
1468
1743
|
] }),
|
|
1744
|
+
modal.hintsAvailable ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
1745
|
+
HintChips,
|
|
1746
|
+
{
|
|
1747
|
+
hints: modal.hints,
|
|
1748
|
+
hintsExpanded: modal.hintsExpanded,
|
|
1749
|
+
usedHintIds: modal.usedHintIds,
|
|
1750
|
+
disabled: draftLocked,
|
|
1751
|
+
onApply: (hintId) => kit.widget.modal.applyHint(hintId),
|
|
1752
|
+
onToggle: () => kit.widget.modal.toggleHints()
|
|
1753
|
+
}
|
|
1754
|
+
) : null,
|
|
1469
1755
|
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("label", { htmlFor: "feedback-kit-comment", style: { display: "block", fontWeight: 700 }, children: [
|
|
1470
1756
|
"\uCF54\uBA58\uD2B8 ",
|
|
1471
1757
|
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { "aria-hidden": "true", children: "*" })
|
|
@@ -1722,6 +2008,17 @@ function FeedbackKit(props) {
|
|
|
1722
2008
|
]
|
|
1723
2009
|
}
|
|
1724
2010
|
),
|
|
2011
|
+
popup.hintsAvailable ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
2012
|
+
HintChips,
|
|
2013
|
+
{
|
|
2014
|
+
hints: popup.hints,
|
|
2015
|
+
hintsExpanded: popup.hintsExpanded,
|
|
2016
|
+
usedHintIds: popup.usedHintIds,
|
|
2017
|
+
disabled: popup.saving,
|
|
2018
|
+
onApply: (hintId) => kit.picking.applyHint(hintId),
|
|
2019
|
+
onToggle: () => kit.picking.toggleHints()
|
|
2020
|
+
}
|
|
2021
|
+
) : null,
|
|
1725
2022
|
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("label", { htmlFor: "feedback-kit-annotation", style: visuallyHidden, children: "\uC774 \uC694\uC18C\uC5D0 \uB0A8\uAE38 \uC758\uACAC" }),
|
|
1726
2023
|
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
1727
2024
|
"textarea",
|
|
@@ -1822,49 +2119,6 @@ function FeedbackKit(props) {
|
|
|
1822
2119
|
);
|
|
1823
2120
|
}
|
|
1824
2121
|
|
|
1825
|
-
// src/storage.ts
|
|
1826
|
-
function memoryStorage() {
|
|
1827
|
-
const map = /* @__PURE__ */ new Map();
|
|
1828
|
-
return {
|
|
1829
|
-
getItem: (key) => map.get(key) ?? null,
|
|
1830
|
-
setItem: (key, value) => void map.set(key, value),
|
|
1831
|
-
removeItem: (key) => void map.delete(key)
|
|
1832
|
-
};
|
|
1833
|
-
}
|
|
1834
|
-
function pickBacking() {
|
|
1835
|
-
try {
|
|
1836
|
-
const ls = globalThis.localStorage;
|
|
1837
|
-
if (!ls) return memoryStorage();
|
|
1838
|
-
const probe = "__feedback_kit_probe__";
|
|
1839
|
-
ls.setItem(probe, "1");
|
|
1840
|
-
ls.removeItem(probe);
|
|
1841
|
-
return ls;
|
|
1842
|
-
} catch {
|
|
1843
|
-
return memoryStorage();
|
|
1844
|
-
}
|
|
1845
|
-
}
|
|
1846
|
-
function createWebStorage(backing) {
|
|
1847
|
-
const store = backing ?? pickBacking();
|
|
1848
|
-
return {
|
|
1849
|
-
async get(key) {
|
|
1850
|
-
try {
|
|
1851
|
-
return store.getItem(key);
|
|
1852
|
-
} catch {
|
|
1853
|
-
return null;
|
|
1854
|
-
}
|
|
1855
|
-
},
|
|
1856
|
-
async set(key, value) {
|
|
1857
|
-
store.setItem(key, value);
|
|
1858
|
-
},
|
|
1859
|
-
async remove(key) {
|
|
1860
|
-
try {
|
|
1861
|
-
store.removeItem(key);
|
|
1862
|
-
} catch {
|
|
1863
|
-
}
|
|
1864
|
-
}
|
|
1865
|
-
};
|
|
1866
|
-
}
|
|
1867
|
-
|
|
1868
2122
|
// src/providers.ts
|
|
1869
2123
|
function webContextProviders() {
|
|
1870
2124
|
return {
|
|
@@ -1895,8 +2149,10 @@ function webContextProviders() {
|
|
|
1895
2149
|
ElementPickingController,
|
|
1896
2150
|
FLOATING_BUTTON_ID,
|
|
1897
2151
|
FeedbackKit,
|
|
2152
|
+
HintProvider,
|
|
1898
2153
|
MARKER_KEY_PREFIX,
|
|
1899
2154
|
MAX_MARKERS_PER_PATH,
|
|
2155
|
+
MODAL_ACTION_HINT_TOGGLE,
|
|
1900
2156
|
MODAL_ACTION_PICK,
|
|
1901
2157
|
MarkerStore,
|
|
1902
2158
|
OWN_UI_ATTR,
|
|
@@ -1917,6 +2173,7 @@ function webContextProviders() {
|
|
|
1917
2173
|
isOwnUi,
|
|
1918
2174
|
normalizePin,
|
|
1919
2175
|
parseSourceAttr,
|
|
2176
|
+
rankHints,
|
|
1920
2177
|
reactComponentPath,
|
|
1921
2178
|
reactComponentSummary,
|
|
1922
2179
|
reencodeWebScreenshot,
|