@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.js
CHANGED
|
@@ -7,6 +7,7 @@ import {
|
|
|
7
7
|
COMMENT_TOO_LONG_MESSAGE,
|
|
8
8
|
denormalizePin,
|
|
9
9
|
FLOATING_BUTTON_ID as FLOATING_BUTTON_ID2,
|
|
10
|
+
MODAL_ACTION_HINT_TOGGLE as MODAL_ACTION_HINT_TOGGLE2,
|
|
10
11
|
MODAL_ACTION_PICK as MODAL_ACTION_PICK2,
|
|
11
12
|
normalizePin as normalizePin2,
|
|
12
13
|
ReportModalController,
|
|
@@ -15,6 +16,7 @@ import {
|
|
|
15
16
|
SUBMIT_PENDING_MESSAGE,
|
|
16
17
|
WidgetController as WidgetController2
|
|
17
18
|
} from "@solhun/feedback-kit-core";
|
|
19
|
+
import { HintProvider as HintProvider2, rankHints as rankHints2 } from "@solhun/feedback-kit-core";
|
|
18
20
|
|
|
19
21
|
// src/react-tree.ts
|
|
20
22
|
var FRAMEWORK_INTERNALS = /* @__PURE__ */ new Set([
|
|
@@ -328,6 +330,11 @@ var ElementPickingController = class {
|
|
|
328
330
|
this.shotGeneration = 0;
|
|
329
331
|
/** 지금 도는 자동 캡처. Enter 가 캡처보다 빨랐을 때 기다릴 대상. */
|
|
330
332
|
this.capturing = null;
|
|
333
|
+
/**
|
|
334
|
+
* 사용자가 [제안 ▾] 를 직접 건드렸는가. 한 번이라도 건드리면 이후 타이핑에 의한 자동
|
|
335
|
+
* 접힘/펼침이 멈춘다 — 모달의 `userToggledHints` 와 같은 규칙. 팝업을 새로 열 때마다 리셋된다.
|
|
336
|
+
*/
|
|
337
|
+
this.userToggledHints = false;
|
|
331
338
|
this.onClick = (event) => this.handleClick(event);
|
|
332
339
|
this.onMouseOver = (event) => this.handleMouseOver(event);
|
|
333
340
|
this.queue = opts.queue;
|
|
@@ -340,6 +347,7 @@ var ElementPickingController = class {
|
|
|
340
347
|
this.capture = opts.capture ?? null;
|
|
341
348
|
this.reencode = opts.reencode ?? null;
|
|
342
349
|
this.screenshotLimitBytes = opts.screenshotLimitBytes;
|
|
350
|
+
this.rankHintsFor = opts.rankHintsFor ?? null;
|
|
343
351
|
this.lastPathname = this.getPathname();
|
|
344
352
|
this.markers = this.store.list(this.lastPathname);
|
|
345
353
|
this.unsubscribeQueue = this.queue.subscribe?.(() => {
|
|
@@ -454,14 +462,22 @@ var ElementPickingController = class {
|
|
|
454
462
|
this.onPick(describeElement(element), point);
|
|
455
463
|
return;
|
|
456
464
|
}
|
|
465
|
+
const info = describeElement(element);
|
|
466
|
+
this.userToggledHints = false;
|
|
467
|
+
const hints = this.rankHintsFor ? this.rankHintsFor(info) : [];
|
|
457
468
|
this.popup = {
|
|
458
|
-
element:
|
|
469
|
+
element: info,
|
|
459
470
|
point,
|
|
460
471
|
comment: "",
|
|
461
472
|
canSave: false,
|
|
462
473
|
screenshot: null,
|
|
463
474
|
screenshotStatus: this.capture ? "capturing" : "none",
|
|
464
|
-
saving: false
|
|
475
|
+
saving: false,
|
|
476
|
+
hints,
|
|
477
|
+
// 코멘트가 비어 있을 때만 펼친다 — 모달과 같은 규칙(계약 원문). 방금 연 팝업은 항상 빈다.
|
|
478
|
+
hintsExpanded: true,
|
|
479
|
+
hintsAvailable: hints.length > 0,
|
|
480
|
+
usedHintIds: []
|
|
465
481
|
};
|
|
466
482
|
this.emit();
|
|
467
483
|
if (this.capture) this.capturing = this.runCapture();
|
|
@@ -471,10 +487,48 @@ var ElementPickingController = class {
|
|
|
471
487
|
this.popup = {
|
|
472
488
|
...this.popup,
|
|
473
489
|
comment: value,
|
|
474
|
-
canSave: this.isSavable(value)
|
|
490
|
+
canSave: this.isSavable(value),
|
|
491
|
+
...this.hintsPatchFor(value)
|
|
475
492
|
};
|
|
476
493
|
this.emit();
|
|
477
494
|
}
|
|
495
|
+
/**
|
|
496
|
+
* 코멘트가 비었는지에 따라 칩 줄 상태를 갱신하는 조각. `setAnnotationComment`·`applyHint`
|
|
497
|
+
* 양쪽에서 쓴다 — 모달의 `hintsPatchFor` 와 같은 규칙이다.
|
|
498
|
+
*/
|
|
499
|
+
hintsPatchFor(nextComment) {
|
|
500
|
+
if (nextComment.trim().length === 0) {
|
|
501
|
+
return { hintsExpanded: true, usedHintIds: [] };
|
|
502
|
+
}
|
|
503
|
+
return this.userToggledHints ? {} : { hintsExpanded: false };
|
|
504
|
+
}
|
|
505
|
+
/**
|
|
506
|
+
* 칩 누름. 이미 눌린 칩이면 아무 일도 하지 않는다. 코멘트가 비어 있으면 초안으로 치환,
|
|
507
|
+
* 아니면 줄바꿈 뒤 이어붙인다 — 모달의 `applyHint` 와 동일한 규칙이다.
|
|
508
|
+
*/
|
|
509
|
+
applyHint(hintId) {
|
|
510
|
+
if (!this.popup || this.popup.saving) return;
|
|
511
|
+
if (this.popup.usedHintIds.includes(hintId)) return;
|
|
512
|
+
const hint = this.popup.hints.find((h) => h.id === hintId);
|
|
513
|
+
if (!hint) return;
|
|
514
|
+
const comment = this.popup.comment.length === 0 ? hint.draft : `${this.popup.comment}
|
|
515
|
+
${hint.draft}`;
|
|
516
|
+
this.popup = {
|
|
517
|
+
...this.popup,
|
|
518
|
+
comment,
|
|
519
|
+
canSave: this.isSavable(comment),
|
|
520
|
+
usedHintIds: [...this.popup.usedHintIds, hintId],
|
|
521
|
+
...this.hintsPatchFor(comment)
|
|
522
|
+
};
|
|
523
|
+
this.emit();
|
|
524
|
+
}
|
|
525
|
+
/** [제안 ▾] 토글. 방향과 무관하게 이후 자동 접힘/펼침을 멈춘다. */
|
|
526
|
+
toggleHints() {
|
|
527
|
+
if (!this.popup) return;
|
|
528
|
+
this.userToggledHints = true;
|
|
529
|
+
this.popup = { ...this.popup, hintsExpanded: !this.popup.hintsExpanded };
|
|
530
|
+
this.emit();
|
|
531
|
+
}
|
|
478
532
|
/**
|
|
479
533
|
* 사용자가 붙여넣기(Cmd+V)나 드래그로 넣은 그림. 자동 캡처 결과를 덮는다.
|
|
480
534
|
*
|
|
@@ -581,7 +635,9 @@ var ElementPickingController = class {
|
|
|
581
635
|
priority: "unset",
|
|
582
636
|
screenshot: settled.screenshot,
|
|
583
637
|
pin: settled.point,
|
|
584
|
-
element: settled.element
|
|
638
|
+
element: settled.element,
|
|
639
|
+
// 눌린 칩 기록. 리포트 모달과 같은 필드 — 들어온 문이 달라도 채택 신호는 같은 값으로 쌓인다.
|
|
640
|
+
hintIds: settled.usedHintIds
|
|
585
641
|
};
|
|
586
642
|
let report;
|
|
587
643
|
try {
|
|
@@ -741,12 +797,105 @@ var reencodeWebScreenshot = async (shot, quality) => {
|
|
|
741
797
|
};
|
|
742
798
|
|
|
743
799
|
// src/widget.ts
|
|
744
|
-
import {
|
|
800
|
+
import {
|
|
801
|
+
WidgetController,
|
|
802
|
+
HintProvider,
|
|
803
|
+
rankHints
|
|
804
|
+
} from "@solhun/feedback-kit-core";
|
|
805
|
+
|
|
806
|
+
// src/storage.ts
|
|
807
|
+
function memoryStorage() {
|
|
808
|
+
const map = /* @__PURE__ */ new Map();
|
|
809
|
+
return {
|
|
810
|
+
getItem: (key) => map.get(key) ?? null,
|
|
811
|
+
setItem: (key, value) => void map.set(key, value),
|
|
812
|
+
removeItem: (key) => void map.delete(key)
|
|
813
|
+
};
|
|
814
|
+
}
|
|
815
|
+
function pickBacking() {
|
|
816
|
+
try {
|
|
817
|
+
const ls = globalThis.localStorage;
|
|
818
|
+
if (!ls) return memoryStorage();
|
|
819
|
+
const probe = "__feedback_kit_probe__";
|
|
820
|
+
ls.setItem(probe, "1");
|
|
821
|
+
ls.removeItem(probe);
|
|
822
|
+
return ls;
|
|
823
|
+
} catch {
|
|
824
|
+
return memoryStorage();
|
|
825
|
+
}
|
|
826
|
+
}
|
|
827
|
+
function createWebStorage(backing) {
|
|
828
|
+
const store = backing ?? pickBacking();
|
|
829
|
+
return {
|
|
830
|
+
async get(key) {
|
|
831
|
+
try {
|
|
832
|
+
return store.getItem(key);
|
|
833
|
+
} catch {
|
|
834
|
+
return null;
|
|
835
|
+
}
|
|
836
|
+
},
|
|
837
|
+
async set(key, value) {
|
|
838
|
+
store.setItem(key, value);
|
|
839
|
+
},
|
|
840
|
+
async remove(key) {
|
|
841
|
+
try {
|
|
842
|
+
store.removeItem(key);
|
|
843
|
+
} catch {
|
|
844
|
+
}
|
|
845
|
+
}
|
|
846
|
+
};
|
|
847
|
+
}
|
|
848
|
+
|
|
849
|
+
// src/widget.ts
|
|
850
|
+
function stripQueryHash(path) {
|
|
851
|
+
const cut = [path.indexOf("?"), path.indexOf("#")].filter((i) => i >= 0);
|
|
852
|
+
return cut.length > 0 ? path.slice(0, Math.min(...cut)) : path;
|
|
853
|
+
}
|
|
854
|
+
function defaultPathname2() {
|
|
855
|
+
const loc = globalThis.location;
|
|
856
|
+
return loc?.pathname ?? "/";
|
|
857
|
+
}
|
|
858
|
+
function hintSourceFrom(queue) {
|
|
859
|
+
const adapter = queue.adapter;
|
|
860
|
+
if (!adapter || typeof adapter.fetchHints !== "function") return null;
|
|
861
|
+
const cacheKey = adapter.hintsCacheKey ?? "default";
|
|
862
|
+
return { fetchHints: adapter.fetchHints.bind(adapter), cacheKey };
|
|
863
|
+
}
|
|
864
|
+
function resolveHintProvider(opts) {
|
|
865
|
+
if (opts.hintProvider) return opts.hintProvider;
|
|
866
|
+
if (opts.hints) {
|
|
867
|
+
return new HintProvider({ source: null, staticHints: opts.hints, cacheKey: "static" });
|
|
868
|
+
}
|
|
869
|
+
const found = hintSourceFrom(opts.queue);
|
|
870
|
+
if (!found) return null;
|
|
871
|
+
return new HintProvider({
|
|
872
|
+
source: { fetchHints: (etag) => found.fetchHints(etag) },
|
|
873
|
+
storage: createWebStorage(),
|
|
874
|
+
cacheKey: found.cacheKey
|
|
875
|
+
});
|
|
876
|
+
}
|
|
877
|
+
var POPUP_HINT_DISPLAY_MAX = 3;
|
|
878
|
+
function rankFor(hintProvider, opts, element, limitOverride) {
|
|
879
|
+
const catalog = hintProvider.catalog();
|
|
880
|
+
if (!catalog) return [];
|
|
881
|
+
const input = {
|
|
882
|
+
platform: "web",
|
|
883
|
+
path: stripQueryHash(opts.getPathname ? opts.getPathname() : defaultPathname2()),
|
|
884
|
+
element,
|
|
885
|
+
components: element ? element.attributes["react-components"] ?? null : null
|
|
886
|
+
};
|
|
887
|
+
const limit = limitOverride !== void 0 ? Math.min(limitOverride, catalog.maxDisplay) : catalog.maxDisplay;
|
|
888
|
+
return rankHints(catalog.hints, input, limit);
|
|
889
|
+
}
|
|
745
890
|
function createWebWidget(opts) {
|
|
746
891
|
const store = opts.store ?? new MarkerStore();
|
|
747
892
|
let widgetRef = null;
|
|
748
893
|
const capture = opts.capture === void 0 ? captureWebScreenshot : opts.capture;
|
|
749
894
|
const reencode = opts.reencode === void 0 ? reencodeWebScreenshot : opts.reencode;
|
|
895
|
+
const hintProvider = resolveHintProvider(opts);
|
|
896
|
+
if (hintProvider) {
|
|
897
|
+
void hintProvider.load().then(() => hintProvider.refresh());
|
|
898
|
+
}
|
|
750
899
|
const picking = new ElementPickingController({
|
|
751
900
|
queue: opts.queue,
|
|
752
901
|
createReport: opts.createReport,
|
|
@@ -762,7 +911,8 @@ function createWebWidget(opts) {
|
|
|
762
911
|
// 모드는 어느 쪽이든 켜진 채로 둬서 한 번 켜고 여러 요소를 연달아 지목할 수 있다.
|
|
763
912
|
onPick: opts.pickTarget === "modal" ? (element, point) => {
|
|
764
913
|
void widgetRef?.openReport({ element, pin: point });
|
|
765
|
-
} : void 0
|
|
914
|
+
} : void 0,
|
|
915
|
+
rankHintsFor: hintProvider ? (element) => rankFor(hintProvider, opts, element, POPUP_HINT_DISPLAY_MAX) : null
|
|
766
916
|
});
|
|
767
917
|
const widget = new WidgetController({
|
|
768
918
|
...opts,
|
|
@@ -777,6 +927,13 @@ function createWebWidget(opts) {
|
|
|
777
927
|
}
|
|
778
928
|
});
|
|
779
929
|
widgetRef = widget;
|
|
930
|
+
const originalOpenReport = widget.openReport.bind(widget);
|
|
931
|
+
widget.openReport = async (openOpts = {}) => {
|
|
932
|
+
if (hintProvider) {
|
|
933
|
+
widget.modal.setHints(rankFor(hintProvider, opts, openOpts.element ?? null));
|
|
934
|
+
}
|
|
935
|
+
await originalOpenReport(openOpts);
|
|
936
|
+
};
|
|
780
937
|
const originalSubmit = widget.submitReport.bind(widget);
|
|
781
938
|
widget.submitReport = async () => {
|
|
782
939
|
const outcome = await originalSubmit();
|
|
@@ -801,6 +958,7 @@ import {
|
|
|
801
958
|
COMMENT_MAX_CHARS as COMMENT_MAX_CHARS2,
|
|
802
959
|
FLOATING_BUTTON_ID,
|
|
803
960
|
MODAL_ACTION_CANCEL,
|
|
961
|
+
MODAL_ACTION_HINT_TOGGLE,
|
|
804
962
|
MODAL_ACTION_PICK,
|
|
805
963
|
MODAL_ACTION_REMOVE_SCREENSHOT,
|
|
806
964
|
MODAL_ACTION_RETRY,
|
|
@@ -815,7 +973,7 @@ import {
|
|
|
815
973
|
} from "react";
|
|
816
974
|
|
|
817
975
|
// src/version.ts
|
|
818
|
-
var VERSION = true ? "0.
|
|
976
|
+
var VERSION = true ? "0.6.1" : "dev";
|
|
819
977
|
|
|
820
978
|
// src/feedback-kit.tsx
|
|
821
979
|
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
@@ -878,26 +1036,41 @@ async function transcodeToJpeg(dataUrl) {
|
|
|
878
1036
|
return null;
|
|
879
1037
|
}
|
|
880
1038
|
}
|
|
1039
|
+
function looksLikeImageName(name) {
|
|
1040
|
+
return /\.(png|jpe?g|gif|webp|bmp|avif|heic|heif)$/i.test(name);
|
|
1041
|
+
}
|
|
1042
|
+
function isImageFile(file) {
|
|
1043
|
+
if (file.type.startsWith("image/")) return true;
|
|
1044
|
+
return (file.type === "" || file.type === "application/octet-stream") && looksLikeImageName(file.name);
|
|
1045
|
+
}
|
|
1046
|
+
function contentTypeOf(file) {
|
|
1047
|
+
if (file.type !== "") return file.type;
|
|
1048
|
+
const name = file instanceof File ? file.name : "";
|
|
1049
|
+
if (/\.png$/i.test(name)) return "image/png";
|
|
1050
|
+
if (/\.jpe?g$/i.test(name)) return "image/jpeg";
|
|
1051
|
+
return "";
|
|
1052
|
+
}
|
|
881
1053
|
async function readImageFile(file) {
|
|
882
|
-
|
|
1054
|
+
const named = file instanceof File ? file : null;
|
|
1055
|
+
if (!file.type.startsWith("image/") && !(named !== null && isImageFile(named))) return null;
|
|
883
1056
|
const dataUrl = await readDataUrl(file);
|
|
884
1057
|
if (!dataUrl) return null;
|
|
885
|
-
|
|
1058
|
+
const type = contentTypeOf(file);
|
|
1059
|
+
if (type === "image/png" || type === "image/jpeg") {
|
|
886
1060
|
const base64 = base64Of(dataUrl);
|
|
887
|
-
return base64 ? { base64, contentType:
|
|
1061
|
+
return base64 ? { base64, contentType: type } : null;
|
|
888
1062
|
}
|
|
889
1063
|
return transcodeToJpeg(dataUrl);
|
|
890
1064
|
}
|
|
891
1065
|
function firstImageOf(transfer) {
|
|
892
1066
|
if (!transfer) return null;
|
|
893
1067
|
for (const item of Array.from(transfer.items ?? [])) {
|
|
894
|
-
if (item.kind
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
}
|
|
1068
|
+
if (item.kind !== "file") continue;
|
|
1069
|
+
const file = item.getAsFile();
|
|
1070
|
+
if (file && isImageFile(file)) return file;
|
|
898
1071
|
}
|
|
899
1072
|
for (const file of Array.from(transfer.files ?? [])) {
|
|
900
|
-
if (file
|
|
1073
|
+
if (isImageFile(file)) return file;
|
|
901
1074
|
}
|
|
902
1075
|
return null;
|
|
903
1076
|
}
|
|
@@ -1077,6 +1250,69 @@ function MarkerStatus({ status }) {
|
|
|
1077
1250
|
/* @__PURE__ */ jsx("span", { children: presentation.text })
|
|
1078
1251
|
] });
|
|
1079
1252
|
}
|
|
1253
|
+
function HintChips({
|
|
1254
|
+
hints,
|
|
1255
|
+
hintsExpanded,
|
|
1256
|
+
usedHintIds,
|
|
1257
|
+
disabled,
|
|
1258
|
+
onApply,
|
|
1259
|
+
onToggle
|
|
1260
|
+
}) {
|
|
1261
|
+
return /* @__PURE__ */ jsxs("div", { style: { marginBottom: 16 }, children: [
|
|
1262
|
+
hintsExpanded ? /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
1263
|
+
/* @__PURE__ */ jsx("p", { style: { margin: "0 0 7px", color: TOKENS.muted, fontSize: 12 }, children: "\uC774\uB7F0 \uAC74\uAC00\uC694?" }),
|
|
1264
|
+
/* @__PURE__ */ jsx(
|
|
1265
|
+
"div",
|
|
1266
|
+
{
|
|
1267
|
+
role: "group",
|
|
1268
|
+
"aria-label": "\uC790\uC8FC \uB098\uC624\uB294 \uC81C\uBCF4 \uC81C\uC548",
|
|
1269
|
+
style: { display: "flex", flexWrap: "wrap", gap: 6, marginBottom: 7 },
|
|
1270
|
+
children: hints.map((hint) => {
|
|
1271
|
+
const used = usedHintIds.includes(hint.id);
|
|
1272
|
+
return /* @__PURE__ */ jsx(
|
|
1273
|
+
"button",
|
|
1274
|
+
{
|
|
1275
|
+
type: "button",
|
|
1276
|
+
"data-fk-focus-id": `hint:${hint.id}`,
|
|
1277
|
+
disabled: used || disabled,
|
|
1278
|
+
"aria-pressed": used,
|
|
1279
|
+
onClick: () => onApply(hint.id),
|
|
1280
|
+
style: {
|
|
1281
|
+
...baseButton,
|
|
1282
|
+
minHeight: 30,
|
|
1283
|
+
padding: "5px 10px",
|
|
1284
|
+
fontSize: 12,
|
|
1285
|
+
fontWeight: 600,
|
|
1286
|
+
borderRadius: 999,
|
|
1287
|
+
...used ? {
|
|
1288
|
+
background: TOKENS.subtle,
|
|
1289
|
+
color: TOKENS.muted,
|
|
1290
|
+
borderColor: TOKENS.line,
|
|
1291
|
+
cursor: "default"
|
|
1292
|
+
} : {}
|
|
1293
|
+
},
|
|
1294
|
+
children: hint.label
|
|
1295
|
+
},
|
|
1296
|
+
hint.id
|
|
1297
|
+
);
|
|
1298
|
+
})
|
|
1299
|
+
}
|
|
1300
|
+
)
|
|
1301
|
+
] }) : null,
|
|
1302
|
+
/* @__PURE__ */ jsx(
|
|
1303
|
+
"button",
|
|
1304
|
+
{
|
|
1305
|
+
type: "button",
|
|
1306
|
+
"data-fk-focus-id": MODAL_ACTION_HINT_TOGGLE,
|
|
1307
|
+
"aria-expanded": hintsExpanded,
|
|
1308
|
+
disabled,
|
|
1309
|
+
onClick: onToggle,
|
|
1310
|
+
style: { ...baseButton, minHeight: 28, padding: "4px 10px", fontSize: 12 },
|
|
1311
|
+
children: hintsExpanded ? "\uC81C\uC548 \u25B4" : "\uC81C\uC548 \u25BE"
|
|
1312
|
+
}
|
|
1313
|
+
)
|
|
1314
|
+
] });
|
|
1315
|
+
}
|
|
1080
1316
|
function FeedbackKit(props) {
|
|
1081
1317
|
const rootRef = useRef(null);
|
|
1082
1318
|
const floatingButtonRef = useRef(null);
|
|
@@ -1099,7 +1335,10 @@ function FeedbackKit(props) {
|
|
|
1099
1335
|
store,
|
|
1100
1336
|
getPathname,
|
|
1101
1337
|
doc,
|
|
1102
|
-
getViewport
|
|
1338
|
+
getViewport,
|
|
1339
|
+
pickTarget,
|
|
1340
|
+
hints,
|
|
1341
|
+
hintProvider
|
|
1103
1342
|
} = props;
|
|
1104
1343
|
useEffect(() => {
|
|
1105
1344
|
const created = createWebWidget({
|
|
@@ -1111,7 +1350,10 @@ function FeedbackKit(props) {
|
|
|
1111
1350
|
store,
|
|
1112
1351
|
getPathname,
|
|
1113
1352
|
doc,
|
|
1114
|
-
getViewport
|
|
1353
|
+
getViewport,
|
|
1354
|
+
pickTarget,
|
|
1355
|
+
hints,
|
|
1356
|
+
hintProvider
|
|
1115
1357
|
});
|
|
1116
1358
|
setKit(created);
|
|
1117
1359
|
const unsubscribeWidget = created.widget.subscribe(setWidgetState);
|
|
@@ -1129,6 +1371,9 @@ function FeedbackKit(props) {
|
|
|
1129
1371
|
screenshotLimitBytes,
|
|
1130
1372
|
store,
|
|
1131
1373
|
getPathname,
|
|
1374
|
+
pickTarget,
|
|
1375
|
+
hints,
|
|
1376
|
+
hintProvider,
|
|
1132
1377
|
doc,
|
|
1133
1378
|
getViewport
|
|
1134
1379
|
]);
|
|
@@ -1167,6 +1412,23 @@ function FeedbackKit(props) {
|
|
|
1167
1412
|
document.addEventListener("keydown", onKeyDownCapture, true);
|
|
1168
1413
|
return () => document.removeEventListener("keydown", onKeyDownCapture, true);
|
|
1169
1414
|
}, [kit, widgetState, pickingState.popup]);
|
|
1415
|
+
useEffect(() => {
|
|
1416
|
+
if (!kit || !widgetState?.modal.open) return;
|
|
1417
|
+
if (pickingState.popup) return;
|
|
1418
|
+
const locked = widgetState.modal.submitStatus === "sending" || widgetState.modal.submitStatus === "pending";
|
|
1419
|
+
if (locked) return;
|
|
1420
|
+
const controller = kit;
|
|
1421
|
+
async function onPaste(event) {
|
|
1422
|
+
const file = firstImageOf(event.clipboardData);
|
|
1423
|
+
if (!file) return;
|
|
1424
|
+
event.preventDefault();
|
|
1425
|
+
const shot = await readImageFile(file);
|
|
1426
|
+
if (shot) await controller.widget.modal.attachFile(shot);
|
|
1427
|
+
}
|
|
1428
|
+
const handler = (event) => void onPaste(event);
|
|
1429
|
+
document.addEventListener("paste", handler);
|
|
1430
|
+
return () => document.removeEventListener("paste", handler);
|
|
1431
|
+
}, [kit, widgetState, pickingState.popup]);
|
|
1170
1432
|
if (!kit || !widgetState) {
|
|
1171
1433
|
return /* @__PURE__ */ jsx("div", { ...OWN_UI_PROPS, "data-feedback-kit-loading": "true" });
|
|
1172
1434
|
}
|
|
@@ -1204,6 +1466,14 @@ function FeedbackKit(props) {
|
|
|
1204
1466
|
}
|
|
1205
1467
|
input.value = "";
|
|
1206
1468
|
}
|
|
1469
|
+
async function attachToModal(transfer) {
|
|
1470
|
+
const file = firstImageOf(transfer);
|
|
1471
|
+
if (!file) return false;
|
|
1472
|
+
const screenshot = await readImageFile(file);
|
|
1473
|
+
if (!screenshot) return false;
|
|
1474
|
+
await activeKit.widget.modal.attachFile(screenshot);
|
|
1475
|
+
return true;
|
|
1476
|
+
}
|
|
1207
1477
|
async function attachToPopup(transfer) {
|
|
1208
1478
|
const file = firstImageOf(transfer);
|
|
1209
1479
|
if (!file) return false;
|
|
@@ -1304,6 +1574,14 @@ function FeedbackKit(props) {
|
|
|
1304
1574
|
"aria-modal": "true",
|
|
1305
1575
|
"aria-labelledby": "feedback-kit-dialog-title",
|
|
1306
1576
|
onKeyDown: handleModalKeyDown,
|
|
1577
|
+
onDragOver: (event) => {
|
|
1578
|
+
if (!draftLocked && firstImageOf(event.dataTransfer)) event.preventDefault();
|
|
1579
|
+
},
|
|
1580
|
+
onDrop: (event) => {
|
|
1581
|
+
if (draftLocked || !firstImageOf(event.dataTransfer)) return;
|
|
1582
|
+
event.preventDefault();
|
|
1583
|
+
void attachToModal(event.dataTransfer);
|
|
1584
|
+
},
|
|
1307
1585
|
style: {
|
|
1308
1586
|
width: "min(520px, 100%)",
|
|
1309
1587
|
maxHeight: "min(760px, calc(100vh - 40px))",
|
|
@@ -1427,6 +1705,17 @@ function FeedbackKit(props) {
|
|
|
1427
1705
|
)
|
|
1428
1706
|
] })
|
|
1429
1707
|
] }),
|
|
1708
|
+
modal.hintsAvailable ? /* @__PURE__ */ jsx(
|
|
1709
|
+
HintChips,
|
|
1710
|
+
{
|
|
1711
|
+
hints: modal.hints,
|
|
1712
|
+
hintsExpanded: modal.hintsExpanded,
|
|
1713
|
+
usedHintIds: modal.usedHintIds,
|
|
1714
|
+
disabled: draftLocked,
|
|
1715
|
+
onApply: (hintId) => kit.widget.modal.applyHint(hintId),
|
|
1716
|
+
onToggle: () => kit.widget.modal.toggleHints()
|
|
1717
|
+
}
|
|
1718
|
+
) : null,
|
|
1430
1719
|
/* @__PURE__ */ jsxs("label", { htmlFor: "feedback-kit-comment", style: { display: "block", fontWeight: 700 }, children: [
|
|
1431
1720
|
"\uCF54\uBA58\uD2B8 ",
|
|
1432
1721
|
/* @__PURE__ */ jsx("span", { "aria-hidden": "true", children: "*" })
|
|
@@ -1683,6 +1972,17 @@ function FeedbackKit(props) {
|
|
|
1683
1972
|
]
|
|
1684
1973
|
}
|
|
1685
1974
|
),
|
|
1975
|
+
popup.hintsAvailable ? /* @__PURE__ */ jsx(
|
|
1976
|
+
HintChips,
|
|
1977
|
+
{
|
|
1978
|
+
hints: popup.hints,
|
|
1979
|
+
hintsExpanded: popup.hintsExpanded,
|
|
1980
|
+
usedHintIds: popup.usedHintIds,
|
|
1981
|
+
disabled: popup.saving,
|
|
1982
|
+
onApply: (hintId) => kit.picking.applyHint(hintId),
|
|
1983
|
+
onToggle: () => kit.picking.toggleHints()
|
|
1984
|
+
}
|
|
1985
|
+
) : null,
|
|
1686
1986
|
/* @__PURE__ */ jsx("label", { htmlFor: "feedback-kit-annotation", style: visuallyHidden, children: "\uC774 \uC694\uC18C\uC5D0 \uB0A8\uAE38 \uC758\uACAC" }),
|
|
1687
1987
|
/* @__PURE__ */ jsx(
|
|
1688
1988
|
"textarea",
|
|
@@ -1783,49 +2083,6 @@ function FeedbackKit(props) {
|
|
|
1783
2083
|
);
|
|
1784
2084
|
}
|
|
1785
2085
|
|
|
1786
|
-
// src/storage.ts
|
|
1787
|
-
function memoryStorage() {
|
|
1788
|
-
const map = /* @__PURE__ */ new Map();
|
|
1789
|
-
return {
|
|
1790
|
-
getItem: (key) => map.get(key) ?? null,
|
|
1791
|
-
setItem: (key, value) => void map.set(key, value),
|
|
1792
|
-
removeItem: (key) => void map.delete(key)
|
|
1793
|
-
};
|
|
1794
|
-
}
|
|
1795
|
-
function pickBacking() {
|
|
1796
|
-
try {
|
|
1797
|
-
const ls = globalThis.localStorage;
|
|
1798
|
-
if (!ls) return memoryStorage();
|
|
1799
|
-
const probe = "__feedback_kit_probe__";
|
|
1800
|
-
ls.setItem(probe, "1");
|
|
1801
|
-
ls.removeItem(probe);
|
|
1802
|
-
return ls;
|
|
1803
|
-
} catch {
|
|
1804
|
-
return memoryStorage();
|
|
1805
|
-
}
|
|
1806
|
-
}
|
|
1807
|
-
function createWebStorage(backing) {
|
|
1808
|
-
const store = backing ?? pickBacking();
|
|
1809
|
-
return {
|
|
1810
|
-
async get(key) {
|
|
1811
|
-
try {
|
|
1812
|
-
return store.getItem(key);
|
|
1813
|
-
} catch {
|
|
1814
|
-
return null;
|
|
1815
|
-
}
|
|
1816
|
-
},
|
|
1817
|
-
async set(key, value) {
|
|
1818
|
-
store.setItem(key, value);
|
|
1819
|
-
},
|
|
1820
|
-
async remove(key) {
|
|
1821
|
-
try {
|
|
1822
|
-
store.removeItem(key);
|
|
1823
|
-
} catch {
|
|
1824
|
-
}
|
|
1825
|
-
}
|
|
1826
|
-
};
|
|
1827
|
-
}
|
|
1828
|
-
|
|
1829
2086
|
// src/providers.ts
|
|
1830
2087
|
function webContextProviders() {
|
|
1831
2088
|
return {
|
|
@@ -1855,8 +2112,10 @@ export {
|
|
|
1855
2112
|
ElementPickingController,
|
|
1856
2113
|
FLOATING_BUTTON_ID2 as FLOATING_BUTTON_ID,
|
|
1857
2114
|
FeedbackKit,
|
|
2115
|
+
HintProvider2 as HintProvider,
|
|
1858
2116
|
MARKER_KEY_PREFIX,
|
|
1859
2117
|
MAX_MARKERS_PER_PATH,
|
|
2118
|
+
MODAL_ACTION_HINT_TOGGLE2 as MODAL_ACTION_HINT_TOGGLE,
|
|
1860
2119
|
MODAL_ACTION_PICK2 as MODAL_ACTION_PICK,
|
|
1861
2120
|
MarkerStore,
|
|
1862
2121
|
OWN_UI_ATTR,
|
|
@@ -1877,6 +2136,7 @@ export {
|
|
|
1877
2136
|
isOwnUi2 as isOwnUi,
|
|
1878
2137
|
normalizePin2 as normalizePin,
|
|
1879
2138
|
parseSourceAttr,
|
|
2139
|
+
rankHints2 as rankHints,
|
|
1880
2140
|
reactComponentPath,
|
|
1881
2141
|
reactComponentSummary,
|
|
1882
2142
|
reencodeWebScreenshot,
|