@solhun/feedback-kit-web 0.1.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/LICENSE +21 -0
- package/dist/index.cjs +1414 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +229 -0
- package/dist/index.d.ts +229 -0
- package/dist/index.js +1374 -0
- package/dist/index.js.map +1 -0
- package/package.json +45 -0
- package/src/element-info.ts +138 -0
- package/src/feedback-kit.test.ts +227 -0
- package/src/feedback-kit.tsx +778 -0
- package/src/index.ts +97 -0
- package/src/marker-store.ts +153 -0
- package/src/picking.test.ts +249 -0
- package/src/picking.ts +370 -0
- package/src/providers.ts +43 -0
- package/src/screen-map.test.ts +455 -0
- package/src/screenshot.test.ts +47 -0
- package/src/screenshot.ts +90 -0
- package/src/storage.test.ts +65 -0
- package/src/storage.ts +69 -0
- package/src/widget.ts +70 -0
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,229 @@
|
|
|
1
|
+
import { ElementInfo, FeedbackPin, 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';
|
|
3
|
+
import * as react from 'react';
|
|
4
|
+
|
|
5
|
+
/** 요소 텍스트를 담을 때의 상한. 본문 전체가 실려 페이로드가 붓는 걸 막는다. */
|
|
6
|
+
declare const ELEMENT_TEXT_MAX_CHARS = 200;
|
|
7
|
+
/** 선택자 경로를 몇 단계까지 거슬러 올라갈지. 너무 깊으면 오히려 잘 깨진다. */
|
|
8
|
+
declare const SELECTOR_MAX_DEPTH = 8;
|
|
9
|
+
/**
|
|
10
|
+
* 요소까지의 CSS 선택자 경로.
|
|
11
|
+
*
|
|
12
|
+
* id 를 만나면 거기서 멈춘다 — 문서에서 유일하다고 가정할 수 있는 지점이라 그 위는 군더더기다.
|
|
13
|
+
* 클래스는 쓰지 않는다: 유틸리티 클래스(`px-4` 같은 것)가 섞이면 선택자가 길기만 하고
|
|
14
|
+
* 재현성은 오히려 떨어진다.
|
|
15
|
+
*/
|
|
16
|
+
declare function cssSelectorPath(el: Element | null | undefined): string | null;
|
|
17
|
+
/** 눈에 보이는 텍스트를 한 줄로 눌러 담는다. */
|
|
18
|
+
declare function visibleText(el: Element): string | null;
|
|
19
|
+
/** 지목된 요소를 `ElementInfo` 로 옮긴다. 실패해도 던지지 않는다. */
|
|
20
|
+
declare function describeElement(el: Element | null | undefined): ElementInfo | null;
|
|
21
|
+
|
|
22
|
+
/** 지목 모드 on/off 플래그. */
|
|
23
|
+
declare const PICKING_MODE_KEY = "feedback-kit:picking-mode";
|
|
24
|
+
/** 경로별 마커 목록. 실제 키는 여기에 pathname 을 이어 붙인다. */
|
|
25
|
+
declare const MARKER_KEY_PREFIX = "feedback-kit:markers:";
|
|
26
|
+
/** 한 경로에 남겨 둘 마커 상한. 넘으면 오래된 것부터 버린다. */
|
|
27
|
+
declare const MAX_MARKERS_PER_PATH = 50;
|
|
28
|
+
/** 마커에 보이는 전송 상태. `sending` → `done`, 실패하면 `pending`. */
|
|
29
|
+
type MarkerStatus = "sending" | "done" | "pending";
|
|
30
|
+
interface StoredMarker {
|
|
31
|
+
/** 제보의 `clientSubmissionId` 와 같다 — 전송 상태를 되짚어 갱신하려고 맞춰 둔다. */
|
|
32
|
+
id: string;
|
|
33
|
+
/** 뷰포트 기준 상대 좌표(0~1). 해상도가 달라도 같은 지점을 가리킨다. */
|
|
34
|
+
x: number;
|
|
35
|
+
y: number;
|
|
36
|
+
selector: string | null;
|
|
37
|
+
comment: string;
|
|
38
|
+
status: MarkerStatus;
|
|
39
|
+
/** 생성 시각(ISO). 상한을 넘겼을 때 무엇을 버릴지 정하는 기준. */
|
|
40
|
+
at: string;
|
|
41
|
+
}
|
|
42
|
+
/** `localStorage` 가 만족하는 최소 계약. 테스트에서 갈아 끼운다. */
|
|
43
|
+
interface StorageLike {
|
|
44
|
+
getItem(key: string): string | null;
|
|
45
|
+
setItem(key: string, value: string): void;
|
|
46
|
+
removeItem(key: string): void;
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* 저장소 접근을 한 곳으로 모은다.
|
|
50
|
+
*
|
|
51
|
+
* 저장소가 없거나(SSR) 던지는 환경(프라이빗 모드·용량 초과)에서도 위젯이 죽으면 안 되므로
|
|
52
|
+
* 모든 접근을 try/catch 로 감싸고 실패는 조용히 흘린다. 저장 실패는 "마커가 안 남는다" 정도의
|
|
53
|
+
* 불편이지 제보 자체를 막을 이유가 아니다.
|
|
54
|
+
*/
|
|
55
|
+
declare class MarkerStore {
|
|
56
|
+
private readonly storage;
|
|
57
|
+
constructor(storage?: StorageLike | null);
|
|
58
|
+
private read;
|
|
59
|
+
private write;
|
|
60
|
+
private drop;
|
|
61
|
+
isPickingActive(): boolean;
|
|
62
|
+
setPickingActive(active: boolean): void;
|
|
63
|
+
list(pathname: string): StoredMarker[];
|
|
64
|
+
/** 같은 id 가 있으면 덮어쓰고, 없으면 뒤에 붙인다. */
|
|
65
|
+
upsert(pathname: string, marker: StoredMarker): StoredMarker[];
|
|
66
|
+
/** 전송 상태만 바꾼다. 없는 id 면 아무것도 하지 않는다. */
|
|
67
|
+
updateStatus(pathname: string, id: string, status: MarkerStatus): StoredMarker[];
|
|
68
|
+
clear(pathname: string): void;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/** 위젯 자신의 UI 를 표시하는 속성. 이 안쪽 클릭은 가로채지 않는다. */
|
|
72
|
+
declare const OWN_UI_ATTR = "data-feedback-kit";
|
|
73
|
+
/** 지목 컨트롤러가 큐에게 요구하는 최소 계약. */
|
|
74
|
+
interface PickingQueueLike {
|
|
75
|
+
submit(report: FeedbackReport): Promise<SubmitOutcome>;
|
|
76
|
+
subscribe?(listener: (status: QueueStatus) => void): () => void;
|
|
77
|
+
getOutcome?(clientSubmissionId: string): Pick<SubmitOutcome, "delivered" | "id"> | null;
|
|
78
|
+
}
|
|
79
|
+
interface AnnotationPopupState {
|
|
80
|
+
element: ElementInfo | null;
|
|
81
|
+
/** 클릭한 지점의 뷰포트 상대 좌표(0~1). */
|
|
82
|
+
point: FeedbackPin;
|
|
83
|
+
comment: string;
|
|
84
|
+
canSave: boolean;
|
|
85
|
+
}
|
|
86
|
+
interface PickingState {
|
|
87
|
+
active: boolean;
|
|
88
|
+
/** 지금 마우스가 올라가 있는 요소. 하이라이트를 그리는 근거. */
|
|
89
|
+
hovered: ElementInfo | null;
|
|
90
|
+
popup: AnnotationPopupState | null;
|
|
91
|
+
/** 현재 경로에 남아 있는 마커들. */
|
|
92
|
+
markers: readonly StoredMarker[];
|
|
93
|
+
}
|
|
94
|
+
type PickingListener = (state: PickingState) => void;
|
|
95
|
+
interface ElementPickingOpts {
|
|
96
|
+
queue: PickingQueueLike;
|
|
97
|
+
createReport: (parts: ReportParts) => Promise<FeedbackReport>;
|
|
98
|
+
store?: MarkerStore;
|
|
99
|
+
/** 현재 경로. 마커는 경로별로 나눠 저장한다. */
|
|
100
|
+
getPathname?: () => string;
|
|
101
|
+
/** 이벤트를 들을 문서. 기본은 전역 `document`. */
|
|
102
|
+
doc?: Document | null;
|
|
103
|
+
/** 좌표를 정규화할 기준 크기. 기본은 현재 뷰포트. */
|
|
104
|
+
getViewport?: () => {
|
|
105
|
+
width: number;
|
|
106
|
+
height: number;
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
declare class ElementPickingController {
|
|
110
|
+
private readonly queue;
|
|
111
|
+
private readonly createReport;
|
|
112
|
+
private readonly store;
|
|
113
|
+
private readonly getPathname;
|
|
114
|
+
private readonly doc;
|
|
115
|
+
private readonly getViewport;
|
|
116
|
+
private readonly unsubscribeQueue;
|
|
117
|
+
private readonly listeners;
|
|
118
|
+
private active;
|
|
119
|
+
private attached;
|
|
120
|
+
private hovered;
|
|
121
|
+
private popup;
|
|
122
|
+
private markers;
|
|
123
|
+
private pathWatch;
|
|
124
|
+
private lastPathname;
|
|
125
|
+
private saving;
|
|
126
|
+
private readonly onClick;
|
|
127
|
+
private readonly onMouseOver;
|
|
128
|
+
constructor(opts: ElementPickingOpts);
|
|
129
|
+
getState(): PickingState;
|
|
130
|
+
get isActive(): boolean;
|
|
131
|
+
subscribe(listener: PickingListener): () => void;
|
|
132
|
+
start(): void;
|
|
133
|
+
/** [지목 종료]. 저장된 플래그까지 지워서 새로고침해도 다시 켜지지 않게 한다. */
|
|
134
|
+
stop(): void;
|
|
135
|
+
/**
|
|
136
|
+
* 저장돼 있던 모드를 되살린다. 새로고침·페이지 이동 직후에 한 번 부른다.
|
|
137
|
+
* @returns 되살아났으면 true.
|
|
138
|
+
*/
|
|
139
|
+
restore(): boolean;
|
|
140
|
+
/** 경로가 바뀌었을 때 그 경로의 마커로 갈아 끼운다. */
|
|
141
|
+
syncPath(): readonly StoredMarker[];
|
|
142
|
+
dispose(): void;
|
|
143
|
+
private attach;
|
|
144
|
+
private detach;
|
|
145
|
+
private handleMouseOver;
|
|
146
|
+
private handleClick;
|
|
147
|
+
/** 클릭 지점 기준으로 주석 팝업을 연다. 좌표는 해상도 무관한 상대값으로 접어 둔다. */
|
|
148
|
+
openAnnotation(element: Element | null, clientPoint: {
|
|
149
|
+
x: number;
|
|
150
|
+
y: number;
|
|
151
|
+
}): void;
|
|
152
|
+
setAnnotationComment(value: string): void;
|
|
153
|
+
/** 취소 — 마커도 제보도 남기지 않는다. 모드는 켜진 채로 둔다. */
|
|
154
|
+
cancelAnnotation(): void;
|
|
155
|
+
/**
|
|
156
|
+
* 주석 저장. 마커를 먼저 "전송 중"으로 찍고 나서 보낸다 — 사용자는 결과를 기다리지 않고
|
|
157
|
+
* 다음 요소로 넘어갈 수 있어야 한다.
|
|
158
|
+
*
|
|
159
|
+
* @returns 검증에 걸려 아무것도 하지 않았으면 `null`.
|
|
160
|
+
*/
|
|
161
|
+
saveAnnotation(): Promise<SubmitOutcome | null>;
|
|
162
|
+
private startPathWatch;
|
|
163
|
+
private stopPathWatch;
|
|
164
|
+
/** 전역 pending 수가 아니라 마커와 같은 clientSubmissionId의 확정 성공만 완료로 바꾼다. */
|
|
165
|
+
private reconcileMarkerOutcomes;
|
|
166
|
+
private emit;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
/** 캡처에서 빼야 할 노드인가(위젯 자신의 UI). 결과에 자기 모달이 찍히면 안 된다. */
|
|
170
|
+
declare function isOwnUi(node: Element): boolean;
|
|
171
|
+
/**
|
|
172
|
+
* 웹 기본 캡처 — 현재 viewport 를 JPEG 로.
|
|
173
|
+
*
|
|
174
|
+
* 실패하면 **null 을 돌려준다**(던지지 않는다). 코어가 "캡처 실패" 상태로 바꾸고
|
|
175
|
+
* 사용자는 파일로 첨부할 수 있다 — 그림 때문에 제보 자체를 잃지 않는다.
|
|
176
|
+
*/
|
|
177
|
+
declare const captureWebScreenshot: ScreenshotCapture;
|
|
178
|
+
/** 8 MiB 정책이 요구할 때 기존 이미지를 JPEG 품질 단계로 다시 인코딩한다. */
|
|
179
|
+
declare const reencodeWebScreenshot: ScreenshotReencode;
|
|
180
|
+
|
|
181
|
+
interface WebWidgetOpts extends Omit<WidgetControllerOpts, "platform" | "initialPicking" | "onPickingChange"> {
|
|
182
|
+
/** 지목 모드 플래그와 마커를 담아 둘 저장소. 기본은 `localStorage`. */
|
|
183
|
+
store?: MarkerStore;
|
|
184
|
+
/** 현재 경로. 마커를 경로별로 나눠 담는 기준. */
|
|
185
|
+
getPathname?: () => string;
|
|
186
|
+
/** 지목 이벤트를 들을 문서. 기본은 전역 `document`. */
|
|
187
|
+
doc?: Document | null;
|
|
188
|
+
getViewport?: () => {
|
|
189
|
+
width: number;
|
|
190
|
+
height: number;
|
|
191
|
+
};
|
|
192
|
+
}
|
|
193
|
+
interface WebWidget {
|
|
194
|
+
widget: WidgetController;
|
|
195
|
+
picking: ElementPickingController;
|
|
196
|
+
store: MarkerStore;
|
|
197
|
+
dispose(): void;
|
|
198
|
+
}
|
|
199
|
+
declare function createWebWidget(opts: WebWidgetOpts): WebWidget;
|
|
200
|
+
|
|
201
|
+
type FeedbackKitProps = WebWidgetOpts;
|
|
202
|
+
/**
|
|
203
|
+
* `createWebWidget`의 헤드리스 상태를 실제 DOM으로 그리는 공개 React 진입점.
|
|
204
|
+
* 큐·제보 조립·스크린샷 캡처는 모두 props로 주입된 기존 계약을 그대로 사용한다.
|
|
205
|
+
*/
|
|
206
|
+
declare function FeedbackKit(props: FeedbackKitProps): react.JSX.Element;
|
|
207
|
+
|
|
208
|
+
interface SyncStorage {
|
|
209
|
+
getItem(key: string): string | null;
|
|
210
|
+
setItem(key: string, value: string): void;
|
|
211
|
+
removeItem(key: string): void;
|
|
212
|
+
}
|
|
213
|
+
/**
|
|
214
|
+
* 웹 기본 FeedbackStorage.
|
|
215
|
+
*
|
|
216
|
+
* 쓰기가 쿼터를 넘기면 그대로 던진다 — 큐가 그 신호를 받아 스크린샷을 떼어내고
|
|
217
|
+
* 본문만 남기는 축소 경로를 타기 때문에, 여기서 삼키면 안 된다.
|
|
218
|
+
*/
|
|
219
|
+
declare function createWebStorage(backing?: SyncStorage): FeedbackStorage;
|
|
220
|
+
|
|
221
|
+
/**
|
|
222
|
+
* 브라우저 전역에서 뽑을 수 있는 기본 제공자들.
|
|
223
|
+
*
|
|
224
|
+
* SSR·비브라우저에서 불려도 던지지 않는다(`null` 을 돌려준다) — 위젯이 마운트되기 전
|
|
225
|
+
* 서버 렌더 단계에서 실행될 수 있기 때문이다.
|
|
226
|
+
*/
|
|
227
|
+
declare function webContextProviders(): ContextProviders;
|
|
228
|
+
|
|
229
|
+
export { type AnnotationPopupState, ELEMENT_TEXT_MAX_CHARS, ElementPickingController, type ElementPickingOpts, FeedbackKit, type FeedbackKitProps, MARKER_KEY_PREFIX, MAX_MARKERS_PER_PATH, type MarkerStatus, MarkerStore, OWN_UI_ATTR, PICKING_MODE_KEY, type PickingListener, type PickingQueueLike, type PickingState, SELECTOR_MAX_DEPTH, type StorageLike, type StoredMarker, type WebWidget, type WebWidgetOpts, captureWebScreenshot, createWebStorage, createWebWidget, cssSelectorPath, describeElement, isOwnUi, reencodeWebScreenshot, visibleText, webContextProviders };
|