@solhun/feedback-kit-web 0.5.0 → 0.6.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/dist/index.cjs +260 -51
- 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 +264 -52
- package/dist/index.js.map +1 -1
- package/package.json +3 -3
package/dist/index.d.cts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { ElementInfo, FeedbackPin, FeedbackScreenshot, FeedbackReport, SubmitOutcome, QueueStatus, ReportParts, ScreenshotCapture, ScreenshotReencode, WidgetControllerOpts, WidgetController, FeedbackStorage, ContextProviders } from '@solhun/feedback-kit-core';
|
|
2
|
-
export { COMMENT_MAX_CHARS, COMMENT_REQUIRED_MESSAGE, COMMENT_TOO_LONG_MESSAGE, ElementInfo, FLOATING_BUTTON_ID, FeedbackAdapter, FeedbackConfig, FeedbackContext, FeedbackPin, FeedbackReport, FeedbackStorage, FeedbackUser, MODAL_ACTION_PICK, ModalSubmitStatus, ReportModalController, ReportModalState, ResolvedConfig, SCREENSHOT_FAILED_MESSAGE, SOURCE_ATTR, SUBMIT_DONE_MESSAGE, SUBMIT_PENDING_MESSAGE, ScreenshotStatus, SubmitResult, Visibility, VisibilityEnv, VisibilityFn, WidgetController, WidgetPosition, WidgetScreen, WidgetState, denormalizePin, normalizePin, parseSourceAttr, resolveConfig, shouldShowWidget, sourceFromElement } from '@solhun/feedback-kit-core';
|
|
1
|
+
import { ElementInfo, FeedbackPin, FeedbackScreenshot, FeedbackHint, FeedbackReport, SubmitOutcome, QueueStatus, ReportParts, ScreenshotCapture, ScreenshotReencode, WidgetControllerOpts, HintProvider, WidgetController, FeedbackStorage, ContextProviders } from '@solhun/feedback-kit-core';
|
|
2
|
+
export { COMMENT_MAX_CHARS, COMMENT_REQUIRED_MESSAGE, COMMENT_TOO_LONG_MESSAGE, ElementInfo, ElementLike, FLOATING_BUTTON_ID, FeedbackAdapter, FeedbackConfig, FeedbackContext, FeedbackHint, FeedbackPin, FeedbackReport, FeedbackStorage, FeedbackUser, HintCatalog, HintElementRule, HintPlatform, HintProvider, HintRankInput, HintScope, MODAL_ACTION_HINT_TOGGLE, MODAL_ACTION_PICK, ModalSubmitStatus, ReportModalController, ReportModalState, ResolvedConfig, SCREENSHOT_FAILED_MESSAGE, SOURCE_ATTR, SUBMIT_DONE_MESSAGE, SUBMIT_PENDING_MESSAGE, ScreenshotStatus, SubmitResult, Visibility, VisibilityEnv, VisibilityFn, WidgetController, WidgetPosition, WidgetScreen, WidgetState, denormalizePin, normalizePin, parseSourceAttr, rankHints, resolveConfig, shouldShowWidget, sourceFromElement } from '@solhun/feedback-kit-core';
|
|
3
3
|
import * as react from 'react';
|
|
4
4
|
|
|
5
5
|
/** 요소 텍스트를 담을 때의 상한. 본문 전체가 실려 페이로드가 붓는 걸 막는다. */
|
|
@@ -96,6 +96,16 @@ interface AnnotationPopupState {
|
|
|
96
96
|
screenshotStatus: AnnotationShotStatus;
|
|
97
97
|
/** 저장 중인가. 두 번 눌러 같은 주석이 두 번 가는 것을 막는다. */
|
|
98
98
|
saving: boolean;
|
|
99
|
+
/**
|
|
100
|
+
* 자주 나오는 제보 제안 칩. 리포트 모달과 같은 계약(같은 `rankHints` 결과, 같은 접힘 규칙,
|
|
101
|
+
* 같은 `hintIds` 흐름)이다 — "들어온 문이 달라도 담기는 정보는 같다"가 이 위젯의 약속이고,
|
|
102
|
+
* 제안 칩도 예외가 아니다. `widget.ts` 가 만든 `HintProvider` 를 그대로 물려 쓴다
|
|
103
|
+
* (팝업이 자기 카탈로그를 따로 들고 있지 않는다).
|
|
104
|
+
*/
|
|
105
|
+
hints: readonly FeedbackHint[];
|
|
106
|
+
hintsExpanded: boolean;
|
|
107
|
+
hintsAvailable: boolean;
|
|
108
|
+
usedHintIds: readonly string[];
|
|
99
109
|
}
|
|
100
110
|
interface PickingState {
|
|
101
111
|
active: boolean;
|
|
@@ -136,6 +146,11 @@ interface ElementPickingOpts {
|
|
|
136
146
|
* 요소 하나 고를 때마다 모드가 꺼지면 매번 다시 켜야 한다.
|
|
137
147
|
*/
|
|
138
148
|
onPick?: (element: ElementInfo | null, point: FeedbackPin) => void;
|
|
149
|
+
/**
|
|
150
|
+
* 힌트 랭킹 계산. `widget.ts` 가 만든 `HintProvider` 의 카탈로그를 그 시점의 경로·요소로
|
|
151
|
+
* `rankHints` 한 결과를 돌려준다. 없으면(힌트 기능이 꺼져 있으면) 팝업에 칩이 안 뜬다.
|
|
152
|
+
*/
|
|
153
|
+
rankHintsFor?: ((element: ElementInfo | null) => readonly FeedbackHint[]) | null;
|
|
139
154
|
}
|
|
140
155
|
declare class ElementPickingController {
|
|
141
156
|
private readonly queue;
|
|
@@ -149,6 +164,7 @@ declare class ElementPickingController {
|
|
|
149
164
|
private readonly reencode;
|
|
150
165
|
private readonly screenshotLimitBytes;
|
|
151
166
|
private readonly unsubscribeQueue;
|
|
167
|
+
private readonly rankHintsFor;
|
|
152
168
|
private readonly listeners;
|
|
153
169
|
private active;
|
|
154
170
|
private attached;
|
|
@@ -162,6 +178,11 @@ declare class ElementPickingController {
|
|
|
162
178
|
private shotGeneration;
|
|
163
179
|
/** 지금 도는 자동 캡처. Enter 가 캡처보다 빨랐을 때 기다릴 대상. */
|
|
164
180
|
private capturing;
|
|
181
|
+
/**
|
|
182
|
+
* 사용자가 [제안 ▾] 를 직접 건드렸는가. 한 번이라도 건드리면 이후 타이핑에 의한 자동
|
|
183
|
+
* 접힘/펼침이 멈춘다 — 모달의 `userToggledHints` 와 같은 규칙. 팝업을 새로 열 때마다 리셋된다.
|
|
184
|
+
*/
|
|
185
|
+
private userToggledHints;
|
|
165
186
|
private readonly onClick;
|
|
166
187
|
private readonly onMouseOver;
|
|
167
188
|
constructor(opts: ElementPickingOpts);
|
|
@@ -189,6 +210,18 @@ declare class ElementPickingController {
|
|
|
189
210
|
y: number;
|
|
190
211
|
}): void;
|
|
191
212
|
setAnnotationComment(value: string): void;
|
|
213
|
+
/**
|
|
214
|
+
* 코멘트가 비었는지에 따라 칩 줄 상태를 갱신하는 조각. `setAnnotationComment`·`applyHint`
|
|
215
|
+
* 양쪽에서 쓴다 — 모달의 `hintsPatchFor` 와 같은 규칙이다.
|
|
216
|
+
*/
|
|
217
|
+
private hintsPatchFor;
|
|
218
|
+
/**
|
|
219
|
+
* 칩 누름. 이미 눌린 칩이면 아무 일도 하지 않는다. 코멘트가 비어 있으면 초안으로 치환,
|
|
220
|
+
* 아니면 줄바꿈 뒤 이어붙인다 — 모달의 `applyHint` 와 동일한 규칙이다.
|
|
221
|
+
*/
|
|
222
|
+
applyHint(hintId: string): void;
|
|
223
|
+
/** [제안 ▾] 토글. 방향과 무관하게 이후 자동 접힘/펼침을 멈춘다. */
|
|
224
|
+
toggleHints(): void;
|
|
192
225
|
/**
|
|
193
226
|
* 사용자가 붙여넣기(Cmd+V)나 드래그로 넣은 그림. 자동 캡처 결과를 덮는다.
|
|
194
227
|
*
|
|
@@ -268,6 +301,13 @@ interface WebWidgetOpts extends Omit<WidgetControllerOpts, "platform" | "initial
|
|
|
268
301
|
};
|
|
269
302
|
/** 요소를 고른 뒤 띄울 UI. 기본은 `popup`. */
|
|
270
303
|
pickTarget?: PickTarget;
|
|
304
|
+
/**
|
|
305
|
+
* 정적 힌트 목록(설정으로 직접 넘김). 주면 서버에서 받아오지 않는다 — 도입 초기·오프라인
|
|
306
|
+
* 데모·테스트용이다. 서버 목록과 합치지 않는다(둘을 섞으면 무엇이 보이는지 설명이 안 된다).
|
|
307
|
+
*/
|
|
308
|
+
hints?: readonly FeedbackHint[];
|
|
309
|
+
/** 힌트 조회 컨트롤러 주입(테스트용). 주면 큐 어댑터의 fetchHints 를 보지 않는다. */
|
|
310
|
+
hintProvider?: HintProvider;
|
|
271
311
|
}
|
|
272
312
|
interface WebWidget {
|
|
273
313
|
widget: WidgetController;
|
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { ElementInfo, FeedbackPin, FeedbackScreenshot, FeedbackReport, SubmitOutcome, QueueStatus, ReportParts, ScreenshotCapture, ScreenshotReencode, WidgetControllerOpts, WidgetController, FeedbackStorage, ContextProviders } from '@solhun/feedback-kit-core';
|
|
2
|
-
export { COMMENT_MAX_CHARS, COMMENT_REQUIRED_MESSAGE, COMMENT_TOO_LONG_MESSAGE, ElementInfo, FLOATING_BUTTON_ID, FeedbackAdapter, FeedbackConfig, FeedbackContext, FeedbackPin, FeedbackReport, FeedbackStorage, FeedbackUser, MODAL_ACTION_PICK, ModalSubmitStatus, ReportModalController, ReportModalState, ResolvedConfig, SCREENSHOT_FAILED_MESSAGE, SOURCE_ATTR, SUBMIT_DONE_MESSAGE, SUBMIT_PENDING_MESSAGE, ScreenshotStatus, SubmitResult, Visibility, VisibilityEnv, VisibilityFn, WidgetController, WidgetPosition, WidgetScreen, WidgetState, denormalizePin, normalizePin, parseSourceAttr, resolveConfig, shouldShowWidget, sourceFromElement } from '@solhun/feedback-kit-core';
|
|
1
|
+
import { ElementInfo, FeedbackPin, FeedbackScreenshot, FeedbackHint, FeedbackReport, SubmitOutcome, QueueStatus, ReportParts, ScreenshotCapture, ScreenshotReencode, WidgetControllerOpts, HintProvider, WidgetController, FeedbackStorage, ContextProviders } from '@solhun/feedback-kit-core';
|
|
2
|
+
export { COMMENT_MAX_CHARS, COMMENT_REQUIRED_MESSAGE, COMMENT_TOO_LONG_MESSAGE, ElementInfo, ElementLike, FLOATING_BUTTON_ID, FeedbackAdapter, FeedbackConfig, FeedbackContext, FeedbackHint, FeedbackPin, FeedbackReport, FeedbackStorage, FeedbackUser, HintCatalog, HintElementRule, HintPlatform, HintProvider, HintRankInput, HintScope, MODAL_ACTION_HINT_TOGGLE, MODAL_ACTION_PICK, ModalSubmitStatus, ReportModalController, ReportModalState, ResolvedConfig, SCREENSHOT_FAILED_MESSAGE, SOURCE_ATTR, SUBMIT_DONE_MESSAGE, SUBMIT_PENDING_MESSAGE, ScreenshotStatus, SubmitResult, Visibility, VisibilityEnv, VisibilityFn, WidgetController, WidgetPosition, WidgetScreen, WidgetState, denormalizePin, normalizePin, parseSourceAttr, rankHints, resolveConfig, shouldShowWidget, sourceFromElement } from '@solhun/feedback-kit-core';
|
|
3
3
|
import * as react from 'react';
|
|
4
4
|
|
|
5
5
|
/** 요소 텍스트를 담을 때의 상한. 본문 전체가 실려 페이로드가 붓는 걸 막는다. */
|
|
@@ -96,6 +96,16 @@ interface AnnotationPopupState {
|
|
|
96
96
|
screenshotStatus: AnnotationShotStatus;
|
|
97
97
|
/** 저장 중인가. 두 번 눌러 같은 주석이 두 번 가는 것을 막는다. */
|
|
98
98
|
saving: boolean;
|
|
99
|
+
/**
|
|
100
|
+
* 자주 나오는 제보 제안 칩. 리포트 모달과 같은 계약(같은 `rankHints` 결과, 같은 접힘 규칙,
|
|
101
|
+
* 같은 `hintIds` 흐름)이다 — "들어온 문이 달라도 담기는 정보는 같다"가 이 위젯의 약속이고,
|
|
102
|
+
* 제안 칩도 예외가 아니다. `widget.ts` 가 만든 `HintProvider` 를 그대로 물려 쓴다
|
|
103
|
+
* (팝업이 자기 카탈로그를 따로 들고 있지 않는다).
|
|
104
|
+
*/
|
|
105
|
+
hints: readonly FeedbackHint[];
|
|
106
|
+
hintsExpanded: boolean;
|
|
107
|
+
hintsAvailable: boolean;
|
|
108
|
+
usedHintIds: readonly string[];
|
|
99
109
|
}
|
|
100
110
|
interface PickingState {
|
|
101
111
|
active: boolean;
|
|
@@ -136,6 +146,11 @@ interface ElementPickingOpts {
|
|
|
136
146
|
* 요소 하나 고를 때마다 모드가 꺼지면 매번 다시 켜야 한다.
|
|
137
147
|
*/
|
|
138
148
|
onPick?: (element: ElementInfo | null, point: FeedbackPin) => void;
|
|
149
|
+
/**
|
|
150
|
+
* 힌트 랭킹 계산. `widget.ts` 가 만든 `HintProvider` 의 카탈로그를 그 시점의 경로·요소로
|
|
151
|
+
* `rankHints` 한 결과를 돌려준다. 없으면(힌트 기능이 꺼져 있으면) 팝업에 칩이 안 뜬다.
|
|
152
|
+
*/
|
|
153
|
+
rankHintsFor?: ((element: ElementInfo | null) => readonly FeedbackHint[]) | null;
|
|
139
154
|
}
|
|
140
155
|
declare class ElementPickingController {
|
|
141
156
|
private readonly queue;
|
|
@@ -149,6 +164,7 @@ declare class ElementPickingController {
|
|
|
149
164
|
private readonly reencode;
|
|
150
165
|
private readonly screenshotLimitBytes;
|
|
151
166
|
private readonly unsubscribeQueue;
|
|
167
|
+
private readonly rankHintsFor;
|
|
152
168
|
private readonly listeners;
|
|
153
169
|
private active;
|
|
154
170
|
private attached;
|
|
@@ -162,6 +178,11 @@ declare class ElementPickingController {
|
|
|
162
178
|
private shotGeneration;
|
|
163
179
|
/** 지금 도는 자동 캡처. Enter 가 캡처보다 빨랐을 때 기다릴 대상. */
|
|
164
180
|
private capturing;
|
|
181
|
+
/**
|
|
182
|
+
* 사용자가 [제안 ▾] 를 직접 건드렸는가. 한 번이라도 건드리면 이후 타이핑에 의한 자동
|
|
183
|
+
* 접힘/펼침이 멈춘다 — 모달의 `userToggledHints` 와 같은 규칙. 팝업을 새로 열 때마다 리셋된다.
|
|
184
|
+
*/
|
|
185
|
+
private userToggledHints;
|
|
165
186
|
private readonly onClick;
|
|
166
187
|
private readonly onMouseOver;
|
|
167
188
|
constructor(opts: ElementPickingOpts);
|
|
@@ -189,6 +210,18 @@ declare class ElementPickingController {
|
|
|
189
210
|
y: number;
|
|
190
211
|
}): void;
|
|
191
212
|
setAnnotationComment(value: string): void;
|
|
213
|
+
/**
|
|
214
|
+
* 코멘트가 비었는지에 따라 칩 줄 상태를 갱신하는 조각. `setAnnotationComment`·`applyHint`
|
|
215
|
+
* 양쪽에서 쓴다 — 모달의 `hintsPatchFor` 와 같은 규칙이다.
|
|
216
|
+
*/
|
|
217
|
+
private hintsPatchFor;
|
|
218
|
+
/**
|
|
219
|
+
* 칩 누름. 이미 눌린 칩이면 아무 일도 하지 않는다. 코멘트가 비어 있으면 초안으로 치환,
|
|
220
|
+
* 아니면 줄바꿈 뒤 이어붙인다 — 모달의 `applyHint` 와 동일한 규칙이다.
|
|
221
|
+
*/
|
|
222
|
+
applyHint(hintId: string): void;
|
|
223
|
+
/** [제안 ▾] 토글. 방향과 무관하게 이후 자동 접힘/펼침을 멈춘다. */
|
|
224
|
+
toggleHints(): void;
|
|
192
225
|
/**
|
|
193
226
|
* 사용자가 붙여넣기(Cmd+V)나 드래그로 넣은 그림. 자동 캡처 결과를 덮는다.
|
|
194
227
|
*
|
|
@@ -268,6 +301,13 @@ interface WebWidgetOpts extends Omit<WidgetControllerOpts, "platform" | "initial
|
|
|
268
301
|
};
|
|
269
302
|
/** 요소를 고른 뒤 띄울 UI. 기본은 `popup`. */
|
|
270
303
|
pickTarget?: PickTarget;
|
|
304
|
+
/**
|
|
305
|
+
* 정적 힌트 목록(설정으로 직접 넘김). 주면 서버에서 받아오지 않는다 — 도입 초기·오프라인
|
|
306
|
+
* 데모·테스트용이다. 서버 목록과 합치지 않는다(둘을 섞으면 무엇이 보이는지 설명이 안 된다).
|
|
307
|
+
*/
|
|
308
|
+
hints?: readonly FeedbackHint[];
|
|
309
|
+
/** 힌트 조회 컨트롤러 주입(테스트용). 주면 큐 어댑터의 fetchHints 를 보지 않는다. */
|
|
310
|
+
hintProvider?: HintProvider;
|
|
271
311
|
}
|
|
272
312
|
interface WebWidget {
|
|
273
313
|
widget: WidgetController;
|
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.0" : "dev";
|
|
819
977
|
|
|
820
978
|
// src/feedback-kit.tsx
|
|
821
979
|
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
@@ -1077,6 +1235,69 @@ function MarkerStatus({ status }) {
|
|
|
1077
1235
|
/* @__PURE__ */ jsx("span", { children: presentation.text })
|
|
1078
1236
|
] });
|
|
1079
1237
|
}
|
|
1238
|
+
function HintChips({
|
|
1239
|
+
hints,
|
|
1240
|
+
hintsExpanded,
|
|
1241
|
+
usedHintIds,
|
|
1242
|
+
disabled,
|
|
1243
|
+
onApply,
|
|
1244
|
+
onToggle
|
|
1245
|
+
}) {
|
|
1246
|
+
return /* @__PURE__ */ jsxs("div", { style: { marginBottom: 16 }, children: [
|
|
1247
|
+
hintsExpanded ? /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
1248
|
+
/* @__PURE__ */ jsx("p", { style: { margin: "0 0 7px", color: TOKENS.muted, fontSize: 12 }, children: "\uC774\uB7F0 \uAC74\uAC00\uC694?" }),
|
|
1249
|
+
/* @__PURE__ */ jsx(
|
|
1250
|
+
"div",
|
|
1251
|
+
{
|
|
1252
|
+
role: "group",
|
|
1253
|
+
"aria-label": "\uC790\uC8FC \uB098\uC624\uB294 \uC81C\uBCF4 \uC81C\uC548",
|
|
1254
|
+
style: { display: "flex", flexWrap: "wrap", gap: 6, marginBottom: 7 },
|
|
1255
|
+
children: hints.map((hint) => {
|
|
1256
|
+
const used = usedHintIds.includes(hint.id);
|
|
1257
|
+
return /* @__PURE__ */ jsx(
|
|
1258
|
+
"button",
|
|
1259
|
+
{
|
|
1260
|
+
type: "button",
|
|
1261
|
+
"data-fk-focus-id": `hint:${hint.id}`,
|
|
1262
|
+
disabled: used || disabled,
|
|
1263
|
+
"aria-pressed": used,
|
|
1264
|
+
onClick: () => onApply(hint.id),
|
|
1265
|
+
style: {
|
|
1266
|
+
...baseButton,
|
|
1267
|
+
minHeight: 30,
|
|
1268
|
+
padding: "5px 10px",
|
|
1269
|
+
fontSize: 12,
|
|
1270
|
+
fontWeight: 600,
|
|
1271
|
+
borderRadius: 999,
|
|
1272
|
+
...used ? {
|
|
1273
|
+
background: TOKENS.subtle,
|
|
1274
|
+
color: TOKENS.muted,
|
|
1275
|
+
borderColor: TOKENS.line,
|
|
1276
|
+
cursor: "default"
|
|
1277
|
+
} : {}
|
|
1278
|
+
},
|
|
1279
|
+
children: hint.label
|
|
1280
|
+
},
|
|
1281
|
+
hint.id
|
|
1282
|
+
);
|
|
1283
|
+
})
|
|
1284
|
+
}
|
|
1285
|
+
)
|
|
1286
|
+
] }) : null,
|
|
1287
|
+
/* @__PURE__ */ jsx(
|
|
1288
|
+
"button",
|
|
1289
|
+
{
|
|
1290
|
+
type: "button",
|
|
1291
|
+
"data-fk-focus-id": MODAL_ACTION_HINT_TOGGLE,
|
|
1292
|
+
"aria-expanded": hintsExpanded,
|
|
1293
|
+
disabled,
|
|
1294
|
+
onClick: onToggle,
|
|
1295
|
+
style: { ...baseButton, minHeight: 28, padding: "4px 10px", fontSize: 12 },
|
|
1296
|
+
children: hintsExpanded ? "\uC81C\uC548 \u25B4" : "\uC81C\uC548 \u25BE"
|
|
1297
|
+
}
|
|
1298
|
+
)
|
|
1299
|
+
] });
|
|
1300
|
+
}
|
|
1080
1301
|
function FeedbackKit(props) {
|
|
1081
1302
|
const rootRef = useRef(null);
|
|
1082
1303
|
const floatingButtonRef = useRef(null);
|
|
@@ -1099,7 +1320,10 @@ function FeedbackKit(props) {
|
|
|
1099
1320
|
store,
|
|
1100
1321
|
getPathname,
|
|
1101
1322
|
doc,
|
|
1102
|
-
getViewport
|
|
1323
|
+
getViewport,
|
|
1324
|
+
pickTarget,
|
|
1325
|
+
hints,
|
|
1326
|
+
hintProvider
|
|
1103
1327
|
} = props;
|
|
1104
1328
|
useEffect(() => {
|
|
1105
1329
|
const created = createWebWidget({
|
|
@@ -1111,7 +1335,10 @@ function FeedbackKit(props) {
|
|
|
1111
1335
|
store,
|
|
1112
1336
|
getPathname,
|
|
1113
1337
|
doc,
|
|
1114
|
-
getViewport
|
|
1338
|
+
getViewport,
|
|
1339
|
+
pickTarget,
|
|
1340
|
+
hints,
|
|
1341
|
+
hintProvider
|
|
1115
1342
|
});
|
|
1116
1343
|
setKit(created);
|
|
1117
1344
|
const unsubscribeWidget = created.widget.subscribe(setWidgetState);
|
|
@@ -1129,6 +1356,9 @@ function FeedbackKit(props) {
|
|
|
1129
1356
|
screenshotLimitBytes,
|
|
1130
1357
|
store,
|
|
1131
1358
|
getPathname,
|
|
1359
|
+
pickTarget,
|
|
1360
|
+
hints,
|
|
1361
|
+
hintProvider,
|
|
1132
1362
|
doc,
|
|
1133
1363
|
getViewport
|
|
1134
1364
|
]);
|
|
@@ -1427,6 +1657,17 @@ function FeedbackKit(props) {
|
|
|
1427
1657
|
)
|
|
1428
1658
|
] })
|
|
1429
1659
|
] }),
|
|
1660
|
+
modal.hintsAvailable ? /* @__PURE__ */ jsx(
|
|
1661
|
+
HintChips,
|
|
1662
|
+
{
|
|
1663
|
+
hints: modal.hints,
|
|
1664
|
+
hintsExpanded: modal.hintsExpanded,
|
|
1665
|
+
usedHintIds: modal.usedHintIds,
|
|
1666
|
+
disabled: draftLocked,
|
|
1667
|
+
onApply: (hintId) => kit.widget.modal.applyHint(hintId),
|
|
1668
|
+
onToggle: () => kit.widget.modal.toggleHints()
|
|
1669
|
+
}
|
|
1670
|
+
) : null,
|
|
1430
1671
|
/* @__PURE__ */ jsxs("label", { htmlFor: "feedback-kit-comment", style: { display: "block", fontWeight: 700 }, children: [
|
|
1431
1672
|
"\uCF54\uBA58\uD2B8 ",
|
|
1432
1673
|
/* @__PURE__ */ jsx("span", { "aria-hidden": "true", children: "*" })
|
|
@@ -1683,6 +1924,17 @@ function FeedbackKit(props) {
|
|
|
1683
1924
|
]
|
|
1684
1925
|
}
|
|
1685
1926
|
),
|
|
1927
|
+
popup.hintsAvailable ? /* @__PURE__ */ jsx(
|
|
1928
|
+
HintChips,
|
|
1929
|
+
{
|
|
1930
|
+
hints: popup.hints,
|
|
1931
|
+
hintsExpanded: popup.hintsExpanded,
|
|
1932
|
+
usedHintIds: popup.usedHintIds,
|
|
1933
|
+
disabled: popup.saving,
|
|
1934
|
+
onApply: (hintId) => kit.picking.applyHint(hintId),
|
|
1935
|
+
onToggle: () => kit.picking.toggleHints()
|
|
1936
|
+
}
|
|
1937
|
+
) : null,
|
|
1686
1938
|
/* @__PURE__ */ jsx("label", { htmlFor: "feedback-kit-annotation", style: visuallyHidden, children: "\uC774 \uC694\uC18C\uC5D0 \uB0A8\uAE38 \uC758\uACAC" }),
|
|
1687
1939
|
/* @__PURE__ */ jsx(
|
|
1688
1940
|
"textarea",
|
|
@@ -1783,49 +2035,6 @@ function FeedbackKit(props) {
|
|
|
1783
2035
|
);
|
|
1784
2036
|
}
|
|
1785
2037
|
|
|
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
2038
|
// src/providers.ts
|
|
1830
2039
|
function webContextProviders() {
|
|
1831
2040
|
return {
|
|
@@ -1855,8 +2064,10 @@ export {
|
|
|
1855
2064
|
ElementPickingController,
|
|
1856
2065
|
FLOATING_BUTTON_ID2 as FLOATING_BUTTON_ID,
|
|
1857
2066
|
FeedbackKit,
|
|
2067
|
+
HintProvider2 as HintProvider,
|
|
1858
2068
|
MARKER_KEY_PREFIX,
|
|
1859
2069
|
MAX_MARKERS_PER_PATH,
|
|
2070
|
+
MODAL_ACTION_HINT_TOGGLE2 as MODAL_ACTION_HINT_TOGGLE,
|
|
1860
2071
|
MODAL_ACTION_PICK2 as MODAL_ACTION_PICK,
|
|
1861
2072
|
MarkerStore,
|
|
1862
2073
|
OWN_UI_ATTR,
|
|
@@ -1877,6 +2088,7 @@ export {
|
|
|
1877
2088
|
isOwnUi2 as isOwnUi,
|
|
1878
2089
|
normalizePin2 as normalizePin,
|
|
1879
2090
|
parseSourceAttr,
|
|
2091
|
+
rankHints2 as rankHints,
|
|
1880
2092
|
reactComponentPath,
|
|
1881
2093
|
reactComponentSummary,
|
|
1882
2094
|
reencodeWebScreenshot,
|