makethisbetter 1.0.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 +62 -0
- package/README.md +113 -0
- package/dist/annotate/draw.d.ts +21 -0
- package/dist/annotate/draw.d.ts.map +1 -0
- package/dist/annotate/draw.test.d.ts +2 -0
- package/dist/annotate/draw.test.d.ts.map +1 -0
- package/dist/annotate/highlighter.d.ts +11 -0
- package/dist/annotate/highlighter.d.ts.map +1 -0
- package/dist/annotate/highlighter.test.d.ts +2 -0
- package/dist/annotate/highlighter.test.d.ts.map +1 -0
- package/dist/annotate/overlay.d.ts +17 -0
- package/dist/annotate/overlay.d.ts.map +1 -0
- package/dist/annotate/overlay.test.d.ts +2 -0
- package/dist/annotate/overlay.test.d.ts.map +1 -0
- package/dist/annotate/pin.d.ts +10 -0
- package/dist/annotate/pin.d.ts.map +1 -0
- package/dist/annotate/pin.test.d.ts +2 -0
- package/dist/annotate/pin.test.d.ts.map +1 -0
- package/dist/annotate/session.d.ts +20 -0
- package/dist/annotate/session.d.ts.map +1 -0
- package/dist/annotate/session.test.d.ts +2 -0
- package/dist/annotate/session.test.d.ts.map +1 -0
- package/dist/annotate/toolbar.d.ts +17 -0
- package/dist/annotate/toolbar.d.ts.map +1 -0
- package/dist/annotate/toolbar.test.d.ts +2 -0
- package/dist/annotate/toolbar.test.d.ts.map +1 -0
- package/dist/api/client.d.ts +10 -0
- package/dist/api/client.d.ts.map +1 -0
- package/dist/api/client.test.d.ts +2 -0
- package/dist/api/client.test.d.ts.map +1 -0
- package/dist/context/collector.d.ts +10 -0
- package/dist/context/collector.d.ts.map +1 -0
- package/dist/context/collector.test.d.ts +2 -0
- package/dist/context/collector.test.d.ts.map +1 -0
- package/dist/context/console.d.ts +10 -0
- package/dist/context/console.d.ts.map +1 -0
- package/dist/context/console.test.d.ts +2 -0
- package/dist/context/console.test.d.ts.map +1 -0
- package/dist/context/error-source.d.ts +21 -0
- package/dist/context/error-source.d.ts.map +1 -0
- package/dist/context/error-source.test.d.ts +2 -0
- package/dist/context/error-source.test.d.ts.map +1 -0
- package/dist/context/frustration.d.ts +27 -0
- package/dist/context/frustration.d.ts.map +1 -0
- package/dist/context/frustration.test.d.ts +2 -0
- package/dist/context/frustration.test.d.ts.map +1 -0
- package/dist/global.d.ts +3 -0
- package/dist/global.d.ts.map +1 -0
- package/dist/i18n/en.d.ts +47 -0
- package/dist/i18n/en.d.ts.map +1 -0
- package/dist/i18n/index.d.ts +4 -0
- package/dist/i18n/index.d.ts.map +1 -0
- package/dist/i18n/index.test.d.ts +2 -0
- package/dist/i18n/index.test.d.ts.map +1 -0
- package/dist/i18n/zh-CN.d.ts +47 -0
- package/dist/i18n/zh-CN.d.ts.map +1 -0
- package/dist/index.d.mts +8 -0
- package/dist/index.d.ts +8 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/makethisbetter.cjs.js +84 -0
- package/dist/makethisbetter.cjs.js.map +1 -0
- package/dist/makethisbetter.esm.js +1433 -0
- package/dist/makethisbetter.esm.js.map +1 -0
- package/dist/makethisbetter.js +84 -0
- package/dist/makethisbetter.js.map +1 -0
- package/dist/popup/comment.d.ts +31 -0
- package/dist/popup/comment.d.ts.map +1 -0
- package/dist/popup/comment.test.d.ts +2 -0
- package/dist/popup/comment.test.d.ts.map +1 -0
- package/dist/popup/success.d.ts +8 -0
- package/dist/popup/success.d.ts.map +1 -0
- package/dist/popup/success.test.d.ts +2 -0
- package/dist/popup/success.test.d.ts.map +1 -0
- package/dist/record/control-bar.d.ts +12 -0
- package/dist/record/control-bar.d.ts.map +1 -0
- package/dist/record/control-bar.test.d.ts +2 -0
- package/dist/record/control-bar.test.d.ts.map +1 -0
- package/dist/record/session.d.ts +26 -0
- package/dist/record/session.d.ts.map +1 -0
- package/dist/record/session.test.d.ts +2 -0
- package/dist/record/session.test.d.ts.map +1 -0
- package/dist/screenshot/capture.d.ts +3 -0
- package/dist/screenshot/capture.d.ts.map +1 -0
- package/dist/screenshot/capture.test.d.ts +2 -0
- package/dist/screenshot/capture.test.d.ts.map +1 -0
- package/dist/styles.d.ts +9 -0
- package/dist/styles.d.ts.map +1 -0
- package/dist/types.d.ts +45 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/widget/controller.d.ts +37 -0
- package/dist/widget/controller.d.ts.map +1 -0
- package/dist/widget/controller.test.d.ts +2 -0
- package/dist/widget/controller.test.d.ts.map +1 -0
- package/dist/widget/frustration-prompt.d.ts +11 -0
- package/dist/widget/frustration-prompt.d.ts.map +1 -0
- package/dist/widget/frustration-prompt.test.d.ts +2 -0
- package/dist/widget/frustration-prompt.test.d.ts.map +1 -0
- package/dist/widget/payload.d.ts +5 -0
- package/dist/widget/payload.d.ts.map +1 -0
- package/dist/widget/payload.test.d.ts +2 -0
- package/dist/widget/payload.test.d.ts.map +1 -0
- package/dist/widget/pet.d.ts +12 -0
- package/dist/widget/pet.d.ts.map +1 -0
- package/dist/widget/pet.test.d.ts +2 -0
- package/dist/widget/pet.test.d.ts.map +1 -0
- package/dist/widget/shadow.d.ts +11 -0
- package/dist/widget/shadow.d.ts.map +1 -0
- package/dist/widget/shadow.test.d.ts +2 -0
- package/dist/widget/shadow.test.d.ts.map +1 -0
- package/dist/widget/tab.d.ts +10 -0
- package/dist/widget/tab.d.ts.map +1 -0
- package/dist/widget/tab.test.d.ts +2 -0
- package/dist/widget/tab.test.d.ts.map +1 -0
- package/package.json +64 -0
- package/src/annotate/draw.test.ts +168 -0
- package/src/annotate/draw.ts +108 -0
- package/src/annotate/highlighter.test.ts +47 -0
- package/src/annotate/highlighter.ts +84 -0
- package/src/annotate/overlay.test.ts +91 -0
- package/src/annotate/overlay.ts +44 -0
- package/src/annotate/pin.test.ts +87 -0
- package/src/annotate/pin.ts +28 -0
- package/src/annotate/session.test.ts +219 -0
- package/src/annotate/session.ts +104 -0
- package/src/annotate/toolbar.test.ts +132 -0
- package/src/annotate/toolbar.ts +81 -0
- package/src/api/client.test.ts +75 -0
- package/src/api/client.ts +77 -0
- package/src/context/collector.test.ts +14 -0
- package/src/context/collector.ts +41 -0
- package/src/context/console.test.ts +44 -0
- package/src/context/console.ts +34 -0
- package/src/context/error-source.test.ts +115 -0
- package/src/context/error-source.ts +62 -0
- package/src/context/frustration.test.ts +134 -0
- package/src/context/frustration.ts +145 -0
- package/src/global.ts +3 -0
- package/src/i18n/en.ts +47 -0
- package/src/i18n/index.test.ts +24 -0
- package/src/i18n/index.ts +16 -0
- package/src/i18n/zh-CN.ts +47 -0
- package/src/index.ts +32 -0
- package/src/popup/comment.test.ts +73 -0
- package/src/popup/comment.ts +177 -0
- package/src/popup/success.test.ts +75 -0
- package/src/popup/success.ts +33 -0
- package/src/record/control-bar.test.ts +110 -0
- package/src/record/control-bar.ts +46 -0
- package/src/record/session.test.ts +153 -0
- package/src/record/session.ts +139 -0
- package/src/screenshot/capture.test.ts +68 -0
- package/src/screenshot/capture.ts +96 -0
- package/src/styles/widget.css +847 -0
- package/src/styles.ts +9 -0
- package/src/types.ts +46 -0
- package/src/vite-env.d.ts +6 -0
- package/src/widget/controller.test.ts +62 -0
- package/src/widget/controller.ts +249 -0
- package/src/widget/frustration-prompt.test.ts +86 -0
- package/src/widget/frustration-prompt.ts +34 -0
- package/src/widget/payload.test.ts +167 -0
- package/src/widget/payload.ts +31 -0
- package/src/widget/pet.test.ts +63 -0
- package/src/widget/pet.ts +67 -0
- package/src/widget/shadow.test.ts +25 -0
- package/src/widget/shadow.ts +41 -0
- package/src/widget/tab.test.ts +92 -0
- package/src/widget/tab.ts +31 -0
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import type { MakeThisBetterConfig } from '../types';
|
|
2
|
+
export declare class WidgetController {
|
|
3
|
+
private config;
|
|
4
|
+
private shadow;
|
|
5
|
+
private tab;
|
|
6
|
+
private pet;
|
|
7
|
+
private toolbar;
|
|
8
|
+
private session;
|
|
9
|
+
private popup;
|
|
10
|
+
private success;
|
|
11
|
+
private frustrationPrompt;
|
|
12
|
+
private apiClient;
|
|
13
|
+
private consoleCollector;
|
|
14
|
+
private frustrationDetector;
|
|
15
|
+
private messages;
|
|
16
|
+
private mode;
|
|
17
|
+
private currentAnnotation;
|
|
18
|
+
private recordSession;
|
|
19
|
+
private recordBar;
|
|
20
|
+
private currentRecording;
|
|
21
|
+
constructor(config: MakeThisBetterConfig);
|
|
22
|
+
private buildUI;
|
|
23
|
+
private handleTabClick;
|
|
24
|
+
private handleFrustration;
|
|
25
|
+
private dismissFrustrationPrompt;
|
|
26
|
+
private enterAnnotationMode;
|
|
27
|
+
private handleToolbarModeChange;
|
|
28
|
+
private enterRecordingMode;
|
|
29
|
+
private stopRecording;
|
|
30
|
+
private exitRecordingMode;
|
|
31
|
+
private showPopup;
|
|
32
|
+
private handleSubmit;
|
|
33
|
+
private showSuccess;
|
|
34
|
+
private exitAll;
|
|
35
|
+
destroy(): void;
|
|
36
|
+
}
|
|
37
|
+
//# sourceMappingURL=controller.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"controller.d.ts","sourceRoot":"","sources":["../../src/widget/controller.ts"],"names":[],"mappings":"AAmBA,OAAO,KAAK,EAAE,oBAAoB,EAAc,MAAM,UAAU,CAAA;AAIhE,qBAAa,gBAAgB;IAC3B,OAAO,CAAC,MAAM,CAAsB;IACpC,OAAO,CAAC,MAAM,CAAkB;IAChC,OAAO,CAAC,GAAG,CAA2B;IACtC,OAAO,CAAC,GAAG,CAAwB;IACnC,OAAO,CAAC,OAAO,CAAiC;IAChD,OAAO,CAAC,OAAO,CAAiC;IAChD,OAAO,CAAC,KAAK,CAA4B;IACzC,OAAO,CAAC,OAAO,CAA2B;IAC1C,OAAO,CAAC,iBAAiB,CAAqC;IAC9D,OAAO,CAAC,SAAS,CAAW;IAC5B,OAAO,CAAC,gBAAgB,CAAuB;IAC/C,OAAO,CAAC,mBAAmB,CAAmC;IAC9D,OAAO,CAAC,QAAQ,CAAgC;IAChD,OAAO,CAAC,IAAI,CAAe;IAC3B,OAAO,CAAC,iBAAiB,CAA0B;IACnD,OAAO,CAAC,aAAa,CAA6B;IAClD,OAAO,CAAC,SAAS,CAAgC;IACjD,OAAO,CAAC,gBAAgB,CAA+B;gBAE3C,MAAM,EAAE,oBAAoB;IAcxC,OAAO,CAAC,OAAO;IAef,OAAO,CAAC,cAAc;IAStB,OAAO,CAAC,iBAAiB;IAYzB,OAAO,CAAC,wBAAwB;IAKhC,OAAO,CAAC,mBAAmB;IAiB3B,OAAO,CAAC,uBAAuB;YAejB,kBAAkB;IAgBhC,OAAO,CAAC,aAAa;IAWrB,OAAO,CAAC,iBAAiB;IAOzB,OAAO,CAAC,SAAS;YAcH,YAAY;IAkC1B,OAAO,CAAC,WAAW;IAUnB,OAAO,CAAC,OAAO;IAef,OAAO,IAAI,IAAI;CAWhB"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"controller.test.d.ts","sourceRoot":"","sources":["../../src/widget/controller.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { ShadowContainer } from './shadow';
|
|
2
|
+
import type { I18nMessages } from '../i18n';
|
|
3
|
+
export declare class FrustrationPromptCard {
|
|
4
|
+
private el;
|
|
5
|
+
constructor(shadow: ShadowContainer, messages: I18nMessages, handlers: {
|
|
6
|
+
onTell: () => void;
|
|
7
|
+
onDismiss: () => void;
|
|
8
|
+
});
|
|
9
|
+
destroy(): void;
|
|
10
|
+
}
|
|
11
|
+
//# sourceMappingURL=frustration-prompt.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"frustration-prompt.d.ts","sourceRoot":"","sources":["../../src/widget/frustration-prompt.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,UAAU,CAAA;AAC/C,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,SAAS,CAAA;AAI3C,qBAAa,qBAAqB;IAChC,OAAO,CAAC,EAAE,CAAgB;gBAGxB,MAAM,EAAE,eAAe,EACvB,QAAQ,EAAE,YAAY,EACtB,QAAQ,EAAE;QAAE,MAAM,EAAE,MAAM,IAAI,CAAC;QAAC,SAAS,EAAE,MAAM,IAAI,CAAA;KAAE;IAmBzD,OAAO,IAAI,IAAI;CAGhB"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"frustration-prompt.test.d.ts","sourceRoot":"","sources":["../../src/widget/frustration-prompt.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { PageContext } from '../context/collector';
|
|
2
|
+
import type { Annotation, FeedbackPayload, MakeThisBetterConfig } from '../types';
|
|
3
|
+
import type { RecordingResult } from '../record/session';
|
|
4
|
+
export declare function buildPayload(description: string, pageContext: PageContext, consoleErrors: string[], annotation: Annotation | null, user: MakeThisBetterConfig['user'], recording?: RecordingResult): FeedbackPayload;
|
|
5
|
+
//# sourceMappingURL=payload.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"payload.d.ts","sourceRoot":"","sources":["../../src/widget/payload.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,sBAAsB,CAAA;AACvD,OAAO,KAAK,EAAE,UAAU,EAAE,eAAe,EAAE,oBAAoB,EAAE,MAAM,UAAU,CAAA;AACjF,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAA;AAExD,wBAAgB,YAAY,CAC1B,WAAW,EAAE,MAAM,EACnB,WAAW,EAAE,WAAW,EACxB,aAAa,EAAE,MAAM,EAAE,EACvB,UAAU,EAAE,UAAU,GAAG,IAAI,EAC7B,IAAI,EAAE,oBAAoB,CAAC,MAAM,CAAC,EAClC,SAAS,CAAC,EAAE,eAAe,GAC1B,eAAe,CAmBjB"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"payload.test.d.ts","sourceRoot":"","sources":["../../src/widget/payload.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { ShadowContainer } from './shadow';
|
|
2
|
+
import type { I18nMessages } from '../i18n';
|
|
3
|
+
export declare class PetEntry {
|
|
4
|
+
private el;
|
|
5
|
+
private bubble;
|
|
6
|
+
private onClick;
|
|
7
|
+
constructor(shadow: ShadowContainer, messages: I18nMessages, onClick: () => void);
|
|
8
|
+
private buildSVG;
|
|
9
|
+
setActive(active: boolean): void;
|
|
10
|
+
destroy(): void;
|
|
11
|
+
}
|
|
12
|
+
//# sourceMappingURL=pet.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pet.d.ts","sourceRoot":"","sources":["../../src/widget/pet.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,UAAU,CAAA;AAC/C,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,SAAS,CAAA;AAG3C,qBAAa,QAAQ;IACnB,OAAO,CAAC,EAAE,CAAgB;IAC1B,OAAO,CAAC,MAAM,CAAgB;IAC9B,OAAO,CAAC,OAAO,CAAY;gBAGzB,MAAM,EAAE,eAAe,EACvB,QAAQ,EAAE,YAAY,EACtB,OAAO,EAAE,MAAM,IAAI;IAyBrB,OAAO,CAAC,QAAQ;IAkBhB,SAAS,CAAC,MAAM,EAAE,OAAO,GAAG,IAAI;IAOhC,OAAO,IAAI,IAAI;CAIhB"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pet.test.d.ts","sourceRoot":"","sources":["../../src/widget/pet.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { MakeThisBetterConfig } from '../types';
|
|
2
|
+
export declare class ShadowContainer {
|
|
3
|
+
private host;
|
|
4
|
+
readonly root: ShadowRoot;
|
|
5
|
+
constructor(theme?: MakeThisBetterConfig['theme']);
|
|
6
|
+
el<T extends HTMLElement = HTMLElement>(tag: string, className?: string): T;
|
|
7
|
+
append(...nodes: Node[]): void;
|
|
8
|
+
remove(...nodes: Node[]): void;
|
|
9
|
+
destroy(): void;
|
|
10
|
+
}
|
|
11
|
+
//# sourceMappingURL=shadow.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"shadow.d.ts","sourceRoot":"","sources":["../../src/widget/shadow.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,UAAU,CAAA;AAEpD,qBAAa,eAAe;IAC1B,OAAO,CAAC,IAAI,CAAa;IACzB,QAAQ,CAAC,IAAI,EAAE,UAAU,CAAA;gBAEb,KAAK,GAAE,oBAAoB,CAAC,OAAO,CAAU;IAczD,EAAE,CAAC,CAAC,SAAS,WAAW,GAAG,WAAW,EAAE,GAAG,EAAE,MAAM,EAAE,SAAS,CAAC,EAAE,MAAM,GAAG,CAAC;IAM3E,MAAM,CAAC,GAAG,KAAK,EAAE,IAAI,EAAE,GAAG,IAAI;IAI9B,MAAM,CAAC,GAAG,KAAK,EAAE,IAAI,EAAE,GAAG,IAAI;IAM9B,OAAO,IAAI,IAAI;CAGhB"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"shadow.test.d.ts","sourceRoot":"","sources":["../../src/widget/shadow.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { ShadowContainer } from './shadow';
|
|
2
|
+
import type { I18nMessages } from '../i18n';
|
|
3
|
+
export declare class FeedbackTab {
|
|
4
|
+
private el;
|
|
5
|
+
private onClick;
|
|
6
|
+
constructor(shadow: ShadowContainer, messages: I18nMessages, position: 'left' | 'right', onClick: () => void);
|
|
7
|
+
setActive(active: boolean): void;
|
|
8
|
+
destroy(): void;
|
|
9
|
+
}
|
|
10
|
+
//# sourceMappingURL=tab.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tab.d.ts","sourceRoot":"","sources":["../../src/widget/tab.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,UAAU,CAAA;AAC/C,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,SAAS,CAAA;AAE3C,qBAAa,WAAW;IACtB,OAAO,CAAC,EAAE,CAAmB;IAC7B,OAAO,CAAC,OAAO,CAAY;gBAGzB,MAAM,EAAE,eAAe,EACvB,QAAQ,EAAE,YAAY,EACtB,QAAQ,EAAE,MAAM,GAAG,OAAO,EAC1B,OAAO,EAAE,MAAM,IAAI;IAWrB,SAAS,CAAC,MAAM,EAAE,OAAO,GAAG,IAAI;IAIhC,OAAO,IAAI,IAAI;CAIhB"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tab.test.d.ts","sourceRoot":"","sources":["../../src/widget/tab.test.ts"],"names":[],"mappings":""}
|
package/package.json
ADDED
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "makethisbetter",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Make This Better JS Widget SDK — embed user feedback collection in any website",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./dist/makethisbetter.cjs.js",
|
|
7
|
+
"module": "./dist/makethisbetter.esm.js",
|
|
8
|
+
"types": "./dist/index.d.ts",
|
|
9
|
+
"unpkg": "./dist/makethisbetter.js",
|
|
10
|
+
"jsdelivr": "./dist/makethisbetter.js",
|
|
11
|
+
"sideEffects": false,
|
|
12
|
+
"exports": {
|
|
13
|
+
".": {
|
|
14
|
+
"import": {
|
|
15
|
+
"types": "./dist/index.d.mts",
|
|
16
|
+
"default": "./dist/makethisbetter.esm.js"
|
|
17
|
+
},
|
|
18
|
+
"require": {
|
|
19
|
+
"types": "./dist/index.d.ts",
|
|
20
|
+
"default": "./dist/makethisbetter.cjs.js"
|
|
21
|
+
}
|
|
22
|
+
},
|
|
23
|
+
"./package.json": "./package.json"
|
|
24
|
+
},
|
|
25
|
+
"files": [
|
|
26
|
+
"dist",
|
|
27
|
+
"src"
|
|
28
|
+
],
|
|
29
|
+
"scripts": {
|
|
30
|
+
"dev": "vite serve demo",
|
|
31
|
+
"build": "vite build && vite build --config vite.iife.config.ts && tsc --emitDeclarationOnly --declaration --declarationDir dist && cp dist/index.d.ts dist/index.d.mts",
|
|
32
|
+
"test": "vitest run",
|
|
33
|
+
"test:watch": "vitest",
|
|
34
|
+
"type-check": "tsc --noEmit",
|
|
35
|
+
"lint": "tsc --noEmit"
|
|
36
|
+
},
|
|
37
|
+
"devDependencies": {
|
|
38
|
+
"@vitest/coverage-v8": "^1.5.0",
|
|
39
|
+
"jsdom": "^24.0.0",
|
|
40
|
+
"rrweb": "^2.1.0",
|
|
41
|
+
"typescript": "^5.4.0",
|
|
42
|
+
"vite": "^5.2.0",
|
|
43
|
+
"vitest": "^1.5.0"
|
|
44
|
+
},
|
|
45
|
+
"dependencies": {
|
|
46
|
+
"html-to-image": "^1.11.11"
|
|
47
|
+
},
|
|
48
|
+
"peerDependencies": {
|
|
49
|
+
"rrweb": "^2.1.0"
|
|
50
|
+
},
|
|
51
|
+
"peerDependenciesMeta": {
|
|
52
|
+
"rrweb": {
|
|
53
|
+
"optional": true
|
|
54
|
+
}
|
|
55
|
+
},
|
|
56
|
+
"keywords": [
|
|
57
|
+
"feedback",
|
|
58
|
+
"widget",
|
|
59
|
+
"user-feedback",
|
|
60
|
+
"bug-reporting",
|
|
61
|
+
"annotation"
|
|
62
|
+
],
|
|
63
|
+
"license": "BSL-1.1"
|
|
64
|
+
}
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
import { afterEach, describe, it, expect, vi } from 'vitest'
|
|
2
|
+
import { ShadowContainer } from '../widget/shadow'
|
|
3
|
+
import { DrawLayer } from './draw'
|
|
4
|
+
|
|
5
|
+
const SVG_SEL = 'svg[class="mtb-draw-svg"]'
|
|
6
|
+
|
|
7
|
+
describe('DrawLayer', () => {
|
|
8
|
+
let shadow: ShadowContainer
|
|
9
|
+
let onDrawComplete: ReturnType<typeof vi.fn>
|
|
10
|
+
let draw: DrawLayer
|
|
11
|
+
|
|
12
|
+
function setup() {
|
|
13
|
+
shadow = new ShadowContainer()
|
|
14
|
+
onDrawComplete = vi.fn()
|
|
15
|
+
draw = new DrawLayer(shadow, onDrawComplete)
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
afterEach(() => {
|
|
19
|
+
draw.destroy()
|
|
20
|
+
shadow.destroy()
|
|
21
|
+
document.body.innerHTML = ''
|
|
22
|
+
})
|
|
23
|
+
|
|
24
|
+
describe('constructor', () => {
|
|
25
|
+
it('appends an SVG element to the shadow root', () => {
|
|
26
|
+
setup()
|
|
27
|
+
const svg = shadow.root.querySelector(SVG_SEL)
|
|
28
|
+
expect(svg).toBeTruthy()
|
|
29
|
+
expect(svg!.tagName).toBe('svg')
|
|
30
|
+
})
|
|
31
|
+
})
|
|
32
|
+
|
|
33
|
+
describe('startDraw', () => {
|
|
34
|
+
it('creates a path element in the SVG', () => {
|
|
35
|
+
setup()
|
|
36
|
+
draw.startDraw(10, 20)
|
|
37
|
+
const path = shadow.root.querySelector(`${SVG_SEL} path`)
|
|
38
|
+
expect(path).toBeTruthy()
|
|
39
|
+
expect(path!.tagName).toBe('path')
|
|
40
|
+
})
|
|
41
|
+
|
|
42
|
+
it('sets isCurrentlyDrawing to true', () => {
|
|
43
|
+
setup()
|
|
44
|
+
expect(draw.isCurrentlyDrawing()).toBe(false)
|
|
45
|
+
draw.startDraw(10, 20)
|
|
46
|
+
expect(draw.isCurrentlyDrawing()).toBe(true)
|
|
47
|
+
})
|
|
48
|
+
|
|
49
|
+
it('initializes point count to 1', () => {
|
|
50
|
+
setup()
|
|
51
|
+
draw.startDraw(10, 20)
|
|
52
|
+
expect(draw.getPointCount()).toBe(1)
|
|
53
|
+
})
|
|
54
|
+
})
|
|
55
|
+
|
|
56
|
+
describe('continueDraw', () => {
|
|
57
|
+
it('adds points and updates the path d attribute', () => {
|
|
58
|
+
setup()
|
|
59
|
+
draw.startDraw(10, 20)
|
|
60
|
+
draw.continueDraw(30, 40)
|
|
61
|
+
const path = shadow.root.querySelector(`${SVG_SEL} path`)!
|
|
62
|
+
expect(path.getAttribute('d')).toContain('M 10 20')
|
|
63
|
+
expect(path.getAttribute('d')).toContain('L 30 40')
|
|
64
|
+
expect(draw.getPointCount()).toBe(2)
|
|
65
|
+
})
|
|
66
|
+
|
|
67
|
+
it('does nothing when not drawing', () => {
|
|
68
|
+
setup()
|
|
69
|
+
draw.continueDraw(30, 40)
|
|
70
|
+
expect(draw.getPointCount()).toBe(0)
|
|
71
|
+
})
|
|
72
|
+
})
|
|
73
|
+
|
|
74
|
+
describe('endDraw', () => {
|
|
75
|
+
it('calls onDrawComplete with a draw annotation when enough points', () => {
|
|
76
|
+
setup()
|
|
77
|
+
draw.startDraw(0, 0)
|
|
78
|
+
draw.continueDraw(10, 10)
|
|
79
|
+
draw.endDraw()
|
|
80
|
+
|
|
81
|
+
expect(onDrawComplete).toHaveBeenCalledOnce()
|
|
82
|
+
const annotation = onDrawComplete.mock.calls[0][0]
|
|
83
|
+
expect(annotation.type).toBe('draw')
|
|
84
|
+
expect(annotation.drawPath).toContain('M 0 0')
|
|
85
|
+
expect(annotation.drawPath).toContain('L 10 10')
|
|
86
|
+
expect(annotation.x).toBe(5)
|
|
87
|
+
expect(annotation.y).toBe(5)
|
|
88
|
+
})
|
|
89
|
+
|
|
90
|
+
it('removes the path and does not call callback with fewer than 2 points', () => {
|
|
91
|
+
setup()
|
|
92
|
+
draw.startDraw(10, 20)
|
|
93
|
+
draw.endDraw()
|
|
94
|
+
|
|
95
|
+
expect(onDrawComplete).not.toHaveBeenCalled()
|
|
96
|
+
expect(draw.isCurrentlyDrawing()).toBe(false)
|
|
97
|
+
})
|
|
98
|
+
|
|
99
|
+
it('does nothing when not currently drawing', () => {
|
|
100
|
+
setup()
|
|
101
|
+
draw.endDraw()
|
|
102
|
+
expect(onDrawComplete).not.toHaveBeenCalled()
|
|
103
|
+
})
|
|
104
|
+
|
|
105
|
+
it('sets isCurrentlyDrawing to false after ending', () => {
|
|
106
|
+
setup()
|
|
107
|
+
draw.startDraw(0, 0)
|
|
108
|
+
draw.continueDraw(10, 10)
|
|
109
|
+
draw.endDraw()
|
|
110
|
+
expect(draw.isCurrentlyDrawing()).toBe(false)
|
|
111
|
+
})
|
|
112
|
+
})
|
|
113
|
+
|
|
114
|
+
describe('clearAll', () => {
|
|
115
|
+
it('removes all SVG children and resets state', () => {
|
|
116
|
+
setup()
|
|
117
|
+
draw.startDraw(0, 0)
|
|
118
|
+
draw.continueDraw(10, 10)
|
|
119
|
+
draw.clearAll()
|
|
120
|
+
|
|
121
|
+
expect(shadow.root.querySelector(SVG_SEL)!.childElementCount).toBe(0)
|
|
122
|
+
expect(draw.isCurrentlyDrawing()).toBe(false)
|
|
123
|
+
expect(draw.getPointCount()).toBe(0)
|
|
124
|
+
})
|
|
125
|
+
})
|
|
126
|
+
|
|
127
|
+
describe('cancelCurrentDraw', () => {
|
|
128
|
+
it('removes the current path and resets drawing state', () => {
|
|
129
|
+
setup()
|
|
130
|
+
draw.startDraw(0, 0)
|
|
131
|
+
draw.continueDraw(10, 10)
|
|
132
|
+
draw.cancelCurrentDraw()
|
|
133
|
+
|
|
134
|
+
expect(draw.isCurrentlyDrawing()).toBe(false)
|
|
135
|
+
expect(draw.getPointCount()).toBe(0)
|
|
136
|
+
const svg = shadow.root.querySelector(SVG_SEL)!
|
|
137
|
+
expect(svg.querySelector('path')).toBeNull()
|
|
138
|
+
})
|
|
139
|
+
|
|
140
|
+
it('is safe to call when not drawing', () => {
|
|
141
|
+
setup()
|
|
142
|
+
expect(() => draw.cancelCurrentDraw()).not.toThrow()
|
|
143
|
+
})
|
|
144
|
+
})
|
|
145
|
+
|
|
146
|
+
describe('destroy', () => {
|
|
147
|
+
it('removes the SVG element from the shadow root', () => {
|
|
148
|
+
setup()
|
|
149
|
+
draw.destroy()
|
|
150
|
+
expect(shadow.root.querySelector(SVG_SEL)).toBeNull()
|
|
151
|
+
})
|
|
152
|
+
})
|
|
153
|
+
|
|
154
|
+
describe('getCenter calculation', () => {
|
|
155
|
+
it('computes the center of the bounding box of all points', () => {
|
|
156
|
+
setup()
|
|
157
|
+
draw.startDraw(0, 0)
|
|
158
|
+
draw.continueDraw(100, 0)
|
|
159
|
+
draw.continueDraw(100, 100)
|
|
160
|
+
draw.continueDraw(0, 100)
|
|
161
|
+
draw.endDraw()
|
|
162
|
+
|
|
163
|
+
const annotation = onDrawComplete.mock.calls[0][0]
|
|
164
|
+
expect(annotation.x).toBe(50)
|
|
165
|
+
expect(annotation.y).toBe(50)
|
|
166
|
+
})
|
|
167
|
+
})
|
|
168
|
+
})
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import type { ShadowContainer } from '../widget/shadow'
|
|
2
|
+
import type { Annotation } from '../types'
|
|
3
|
+
import { DRAW_STROKE } from '../styles'
|
|
4
|
+
|
|
5
|
+
export class DrawLayer {
|
|
6
|
+
private svg: SVGSVGElement
|
|
7
|
+
private path: SVGPathElement | null = null
|
|
8
|
+
private points: [number, number][] = []
|
|
9
|
+
private isDrawing = false
|
|
10
|
+
private onDrawComplete: (annotation: Annotation) => void
|
|
11
|
+
|
|
12
|
+
constructor(shadow: ShadowContainer, onDrawComplete: (annotation: Annotation) => void) {
|
|
13
|
+
this.onDrawComplete = onDrawComplete
|
|
14
|
+
|
|
15
|
+
this.svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg')
|
|
16
|
+
this.svg.setAttribute('class', 'mtb-draw-svg')
|
|
17
|
+
shadow.append(this.svg)
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
startDraw(x: number, y: number): void {
|
|
21
|
+
this.isDrawing = true
|
|
22
|
+
this.points = [[x, y]]
|
|
23
|
+
|
|
24
|
+
this.path = document.createElementNS('http://www.w3.org/2000/svg', 'path')
|
|
25
|
+
this.path.setAttribute('fill', 'none')
|
|
26
|
+
this.path.setAttribute('stroke', DRAW_STROKE.color)
|
|
27
|
+
this.path.setAttribute('stroke-width', DRAW_STROKE.width)
|
|
28
|
+
this.path.setAttribute('stroke-linecap', DRAW_STROKE.linecap)
|
|
29
|
+
this.path.setAttribute('stroke-linejoin', DRAW_STROKE.linejoin)
|
|
30
|
+
this.path.setAttribute('style', `filter:${DRAW_STROKE.filter}`)
|
|
31
|
+
this.svg.appendChild(this.path)
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
continueDraw(x: number, y: number): void {
|
|
35
|
+
if (!this.isDrawing || !this.path) return
|
|
36
|
+
this.points.push([x, y])
|
|
37
|
+
this.path.setAttribute('d', this.buildPathD())
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
endDraw(): void {
|
|
41
|
+
if (!this.isDrawing || this.points.length < 2) {
|
|
42
|
+
this.isDrawing = false
|
|
43
|
+
if (this.path) {
|
|
44
|
+
this.path.remove()
|
|
45
|
+
this.path = null
|
|
46
|
+
}
|
|
47
|
+
return
|
|
48
|
+
}
|
|
49
|
+
this.isDrawing = false
|
|
50
|
+
|
|
51
|
+
const center = this.getCenter()
|
|
52
|
+
const pathD = this.buildPathD()
|
|
53
|
+
|
|
54
|
+
const annotation: Annotation = {
|
|
55
|
+
type: 'draw',
|
|
56
|
+
x: center[0],
|
|
57
|
+
y: center[1],
|
|
58
|
+
drawPath: pathD,
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
this.onDrawComplete(annotation)
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
private buildPathD(): string {
|
|
65
|
+
if (this.points.length === 0) return ''
|
|
66
|
+
const [fx, fy] = this.points[0]
|
|
67
|
+
let d = `M ${fx} ${fy}`
|
|
68
|
+
for (let i = 1; i < this.points.length; i++) {
|
|
69
|
+
const [x, y] = this.points[i]
|
|
70
|
+
d += ` L ${x} ${y}`
|
|
71
|
+
}
|
|
72
|
+
return d
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
private getCenter(): [number, number] {
|
|
76
|
+
const xs = this.points.map(p => p[0])
|
|
77
|
+
const ys = this.points.map(p => p[1])
|
|
78
|
+
const cx = (Math.min(...xs) + Math.max(...xs)) / 2
|
|
79
|
+
const cy = (Math.min(...ys) + Math.max(...ys)) / 2
|
|
80
|
+
return [cx, cy]
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
clearAll(): void {
|
|
84
|
+
while (this.svg.firstChild) this.svg.removeChild(this.svg.firstChild)
|
|
85
|
+
this.path = null
|
|
86
|
+
this.isDrawing = false
|
|
87
|
+
this.points = []
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
cancelCurrentDraw(): void {
|
|
91
|
+
this.path?.remove()
|
|
92
|
+
this.path = null
|
|
93
|
+
this.isDrawing = false
|
|
94
|
+
this.points = []
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
isCurrentlyDrawing(): boolean {
|
|
98
|
+
return this.isDrawing
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
getPointCount(): number {
|
|
102
|
+
return this.points.length
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
destroy(): void {
|
|
106
|
+
this.svg.remove()
|
|
107
|
+
}
|
|
108
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest'
|
|
2
|
+
import { getElementName, getCssSelector } from './highlighter'
|
|
3
|
+
|
|
4
|
+
describe('getElementName', () => {
|
|
5
|
+
it('returns aria-label when present', () => {
|
|
6
|
+
const el = document.createElement('button')
|
|
7
|
+
el.setAttribute('aria-label', 'Close dialog')
|
|
8
|
+
expect(getElementName(el)).toBe('Close dialog')
|
|
9
|
+
})
|
|
10
|
+
|
|
11
|
+
it('returns text content when no label', () => {
|
|
12
|
+
const el = document.createElement('button')
|
|
13
|
+
el.textContent = 'Export PDF'
|
|
14
|
+
expect(getElementName(el)).toBe('Export PDF')
|
|
15
|
+
})
|
|
16
|
+
|
|
17
|
+
it('returns tag name as fallback', () => {
|
|
18
|
+
const el = document.createElementNS('http://www.w3.org/2000/svg', 'svg')
|
|
19
|
+
expect(getElementName(el)).toBe('svg')
|
|
20
|
+
})
|
|
21
|
+
|
|
22
|
+
it('truncates long text content', () => {
|
|
23
|
+
const el = document.createElement('p')
|
|
24
|
+
el.textContent = 'a'.repeat(100)
|
|
25
|
+
expect(getElementName(el).length).toBeLessThanOrEqual(40)
|
|
26
|
+
})
|
|
27
|
+
})
|
|
28
|
+
|
|
29
|
+
describe('getCssSelector', () => {
|
|
30
|
+
it('returns id selector when element has id', () => {
|
|
31
|
+
const el = document.createElement('div')
|
|
32
|
+
el.id = 'main'
|
|
33
|
+
document.body.appendChild(el)
|
|
34
|
+
expect(getCssSelector(el)).toBe('#main')
|
|
35
|
+
document.body.removeChild(el)
|
|
36
|
+
})
|
|
37
|
+
|
|
38
|
+
it('returns tag selector for unknown element', () => {
|
|
39
|
+
const el = document.createElement('span')
|
|
40
|
+
const parent = document.createElement('div')
|
|
41
|
+
parent.appendChild(el)
|
|
42
|
+
document.body.appendChild(parent)
|
|
43
|
+
const selector = getCssSelector(el)
|
|
44
|
+
expect(selector).toContain('span')
|
|
45
|
+
document.body.removeChild(parent)
|
|
46
|
+
})
|
|
47
|
+
})
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import type { ShadowContainer } from '../widget/shadow'
|
|
2
|
+
|
|
3
|
+
export class ElementHighlighter {
|
|
4
|
+
private highlightEl: HTMLDivElement
|
|
5
|
+
|
|
6
|
+
constructor(shadow: ShadowContainer) {
|
|
7
|
+
this.highlightEl = shadow.el<HTMLDivElement>('div', 'mtb-highlight')
|
|
8
|
+
this.hide()
|
|
9
|
+
shadow.append(this.highlightEl)
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
highlight(el: Element): void {
|
|
13
|
+
const rect = el.getBoundingClientRect()
|
|
14
|
+
this.highlightEl.style.top = `${rect.top - 2}px`
|
|
15
|
+
this.highlightEl.style.left = `${rect.left - 2}px`
|
|
16
|
+
this.highlightEl.style.width = `${rect.width + 4}px`
|
|
17
|
+
this.highlightEl.style.height = `${rect.height + 4}px`
|
|
18
|
+
this.highlightEl.style.display = 'block'
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
hide(): void {
|
|
22
|
+
this.highlightEl.style.display = 'none'
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
destroy(): void {
|
|
26
|
+
this.highlightEl.remove()
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export function getElementName(el: Element): string {
|
|
31
|
+
if (el instanceof HTMLElement) {
|
|
32
|
+
const label = el.getAttribute('aria-label')
|
|
33
|
+
if (label) return label
|
|
34
|
+
|
|
35
|
+
const title = el.getAttribute('title')
|
|
36
|
+
if (title) return title
|
|
37
|
+
|
|
38
|
+
const text = el.textContent?.trim().slice(0, 40)
|
|
39
|
+
if (text) return text
|
|
40
|
+
|
|
41
|
+
const placeholder = (el as HTMLInputElement).placeholder
|
|
42
|
+
if (placeholder) return placeholder
|
|
43
|
+
|
|
44
|
+
const alt = (el as HTMLImageElement).alt
|
|
45
|
+
if (alt) return alt
|
|
46
|
+
|
|
47
|
+
const dataMark = el.getAttribute('data-mark')
|
|
48
|
+
if (dataMark) return dataMark
|
|
49
|
+
}
|
|
50
|
+
return el.tagName.toLowerCase()
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export function getCssSelector(el: Element): string {
|
|
54
|
+
const parts: string[] = []
|
|
55
|
+
let current: Element | null = el
|
|
56
|
+
while (current && current !== document.body && parts.length < 5) {
|
|
57
|
+
let selector = current.tagName.toLowerCase()
|
|
58
|
+
if (current.id) {
|
|
59
|
+
selector = `#${current.id}`
|
|
60
|
+
parts.unshift(selector)
|
|
61
|
+
break
|
|
62
|
+
}
|
|
63
|
+
const classNames = Array.from(current.classList)
|
|
64
|
+
.filter(c => !c.startsWith('mtb-'))
|
|
65
|
+
.slice(0, 2)
|
|
66
|
+
.join('.')
|
|
67
|
+
if (classNames) selector += `.${classNames}`
|
|
68
|
+
|
|
69
|
+
const parent = current.parentElement
|
|
70
|
+
if (parent) {
|
|
71
|
+
const siblings = Array.from(parent.children).filter(
|
|
72
|
+
c => c.tagName === current!.tagName,
|
|
73
|
+
)
|
|
74
|
+
if (siblings.length > 1) {
|
|
75
|
+
const idx = siblings.indexOf(current) + 1
|
|
76
|
+
selector += `:nth-child(${idx})`
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
parts.unshift(selector)
|
|
81
|
+
current = current.parentElement
|
|
82
|
+
}
|
|
83
|
+
return parts.join(' > ')
|
|
84
|
+
}
|