hocmai-feedback-widget 0.2.3 → 0.2.4

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 ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 HOCMAI
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,14 +1,39 @@
1
1
  # hocmai-feedback-widget
2
2
 
3
- Widget báo lỗi câu hỏi kèm chụp ảnh màn hình, tối ưu cho trang nhiều công thức **MathJax CHTML**. ESM, tương thích React ≥ 16.8.
3
+ > Widget báo lỗi câu hỏi kèm chụp ảnh màn hình, tối ưu cho trang nhiều công thức **MathJax CHTML**.
4
+
5
+ ![version](https://img.shields.io/badge/version-0.2.3-blue)
6
+ ![react](https://img.shields.io/badge/react-%E2%89%A516.8-61dafb)
7
+ ![module](https://img.shields.io/badge/module-ESM-f7df1e)
8
+ ![license](https://img.shields.io/badge/license-MIT-green)
9
+
10
+ Bộ chụp dùng [snapdom](https://github.com/zumerlab/snapdom) + nhúng font MathJax → chụp nhanh, đủ công thức ngay lần đầu, an toàn SSR.
11
+
12
+ ## Mục lục
13
+
14
+ - [Tính năng](#tính-năng)
15
+ - [Cài đặt](#cài-đặt)
16
+ - [Sử dụng](#sử-dụng)
17
+ - [API](#api)
18
+ - [Lưu ý](#lưu-ý)
19
+ - [License](#license)
20
+
21
+ ## Tính năng
22
+
23
+ - 📸 Chụp **toàn màn hình** hoặc **chọn vùng**, render đúng công thức MathJax CHTML.
24
+ - 📝 Mô tả lỗi bằng **textarea** hoặc **CKEditor** (tùy chọn).
25
+ - 🖼️ Tự upload ảnh và trả về URL (tối đa 3 ảnh).
26
+ - 🔌 API gọn: `FeedbackProvider` + hook `useFeedback`.
27
+ - 🧩 Core **không** phụ thuộc CKEditor — chỉ cài khi cần.
4
28
 
5
29
  ## Cài đặt
6
30
 
7
31
  ```bash
8
32
  npm install hocmai-feedback-widget
9
- # peer deps: react, react-dom
10
33
  ```
11
34
 
35
+ **Peer dependencies:** `react`, `react-dom` (>= 16.8).
36
+
12
37
  ## Sử dụng
13
38
 
14
39
  ### 1. Bọc app bằng `FeedbackProvider`
@@ -16,14 +41,17 @@ npm install hocmai-feedback-widget
16
41
  ```tsx
17
42
  import { FeedbackProvider, type FeedbackData } from 'hocmai-feedback-widget';
18
43
 
19
- <FeedbackProvider onSubmit={async (data: FeedbackData) => {
20
- await api.post('/questionReport/studentReport', data); // data.listImage là URL ảnh đã upload
21
- }}>
44
+ <FeedbackProvider
45
+ onSubmit={async (data: FeedbackData) => {
46
+ // data.listImage: mảng URL ảnh đã upload
47
+ await api.post('/questionReport/studentReport', data);
48
+ }}
49
+ >
22
50
  <App />
23
- </FeedbackProvider>
51
+ </FeedbackProvider>;
24
52
  ```
25
53
 
26
- `onSubmit` **không bắt buộc** ở Provider — có thể truyền theo từng lần ở `openFeedback` (xem dưới).
54
+ > `onSubmit` **không bắt buộc** ở Provider — có thể truyền theo từng lần ở `openFeedback` (xem mục 2).
27
55
 
28
56
  ### 2. Mở form từ component con bằng `useFeedback`
29
57
 
@@ -32,16 +60,16 @@ import { useFeedback } from 'hocmai-feedback-widget';
32
60
 
33
61
  const { openFeedback } = useFeedback();
34
62
 
35
- // Dùng onSubmit của Provider:
63
+ // a) Dùng onSubmit của Provider:
36
64
  openFeedback(questionId, { questionIdChild });
37
65
 
38
- // Hoặc truyền handler riêng cho lần gọi này (ghi đè Provider):
66
+ // b) Hoặc truyền handler riêng cho lần gọi này (ghi đè Provider):
39
67
  openFeedback(questionId, { questionIdChild }, async (data) => {
40
68
  await api.post('/questionReport/studentReport', data);
41
69
  });
42
70
  ```
43
71
 
44
- ### 3. (Tùy chọn) Rich-text editor cho ô mô tả `isCkEditor`
72
+ ### 3. Rich-text editor cho ô mô tả _(tùy chọn)_
45
73
 
46
74
  Bật `isCkEditor` **và** truyền `descriptionEditor` (editor đóng gói sẵn ở entry phụ):
47
75
 
@@ -51,47 +79,69 @@ import CkEditor from 'hocmai-feedback-widget/ckeditor';
51
79
 
52
80
  <FeedbackProvider onSubmit={handleSubmit} isCkEditor descriptionEditor={CkEditor}>
53
81
  <App />
54
- </FeedbackProvider>
82
+ </FeedbackProvider>;
55
83
  ```
56
84
 
57
- Phải cài thêm các peer deps (chỉ khi dùng CKEditor):
85
+ Cài thêm peer deps (chỉ khi dùng CKEditor):
58
86
 
59
87
  ```bash
60
- yarn add @ckeditor/ckeditor5-react hocmai-ckeditor-custom ckeditor5-classic-with-mathtype
88
+ yarn add @ckeditor/ckeditor5-react@5.0.0 hocmai-ckeditor-custom ckeditor5-classic-with-mathtype
61
89
  ```
62
90
 
63
- ### 4. (Tùy chọn) Chụp màn hình trực tiếp
91
+ **Lưu ý**: `@ckeditor/ckeditor5-react` chỉ hỗ trợ version 5, `hocmai-ckeditor-custom` có thể dùng version 0.0.2 hoặc 0.0.7 trở lên.
92
+
93
+ ### 4. Chụp màn hình trực tiếp _(tùy chọn)_
64
94
 
65
95
  ```ts
66
96
  import { captureScreenshot } from 'hocmai-feedback-widget';
67
97
 
68
- const png = await captureScreenshot(); // toàn viewport
69
- const region = await captureScreenshot({ x, y, width, height }); // 1 vùng (toạ độ viewport)
98
+ const png = await captureScreenshot(); // toàn viewport
99
+ const region = await captureScreenshot({ x, y, width, height }); // 1 vùng (toạ độ viewport)
70
100
  ```
71
101
 
72
102
  ## API
73
103
 
74
- **`FeedbackProvider`**
104
+ ### `FeedbackProvider`
105
+
106
+ | Prop | Kiểu | Bắt buộc | Mặc định | Mô tả |
107
+ | --- | --- | :---: | :---: | --- |
108
+ | `children` | `ReactNode` | ✓ | — | Nội dung app được bọc. |
109
+ | `onSubmit` | `(data: FeedbackData) => Promise<void> \| void` | — | — | Handler mặc định khi gửi báo lỗi. Có thể bỏ, truyền theo từng lần ở `openFeedback`. |
110
+ | `themeColor` | `string` | — | `#3b82f6` | Màu chủ đạo của nút/viền. |
111
+ | `isCkEditor` | `boolean` | — | `false` | Dùng rich-text editor cho ô mô tả (cần kèm `descriptionEditor`). |
112
+ | `descriptionEditor` | `ComponentType<CkEditorProps>` | — | — | Component editor cho ô mô tả, vd `import CkEditor from 'hocmai-feedback-widget/ckeditor'`. |
113
+ | `isEnableCaptureFull` | `boolean` | — | `true` | Hiện nút "Toàn màn hình". |
114
+ | `isEnableCaptureElement` | `boolean` | — | `true` | Hiện nút "Chọn vùng". |
115
+ | `uploadUrl` | `string` | — | endpoint LCMS | Nơi upload ảnh: nhận `{ base64 }`, trả `{ file_url }`. |
116
+
117
+ ### `useFeedback()`
75
118
 
76
- | Prop | Kiểu | Mô tả |
77
- |------|------|-------|
78
- | `onSubmit` | `(data: FeedbackData) => Promise<void> \| void` | Tùy chọn. Handler mặc định khi gửi. |
79
- | `isCkEditor` | `boolean` | Dùng rich-text editor cho ô mô tả (cần `descriptionEditor`). |
80
- | `descriptionEditor` | `ComponentType` | Editor truyền vào, vd `import CkEditor from 'hocmai-feedback-widget/ckeditor'`. |
81
- | `themeColor` | `string` | Màu chủ đạo (mặc định `#3b82f6`). |
119
+ Trả về:
82
120
 
83
- **`useFeedback()`** `{ openFeedback, closeFeedback, isOpen, activeQuestionId }`
84
- - `openFeedback(questionId, contextData?, onSubmit?)` mở form; `onSubmit?` ghi đè handler của Provider cho lần này.
121
+ | Thành phần | Kiểu | tả |
122
+ | --- | --- | --- |
123
+ | `openFeedback` | `(questionId, contextData?, onSubmit?) => void` | Mở form báo lỗi cho một câu hỏi. |
124
+ | `closeFeedback` | `() => void` | Đóng form. |
125
+ | `isOpen` | `boolean` | Form đang mở hay không. |
126
+ | `activeQuestionId` | `string \| number \| null` | ID câu hỏi đang mở form (`null` nếu đóng). |
85
127
 
86
- **`FeedbackData`**
128
+ Tham số của `openFeedback`:
129
+
130
+ | Tham số | Kiểu | Bắt buộc | Mô tả |
131
+ | --- | --- | :---: | --- |
132
+ | `questionId` | `string \| number` | ✓ | ID câu hỏi; tự điền vào ô "ID câu hỏi". |
133
+ | `contextData` | `object` | — | Dữ liệu kèm theo (trả lại trong `FeedbackData.contextData`). `contextData.questionIdChild` tự điền ô "ID câu hỏi con". |
134
+ | `onSubmit` | `(data: FeedbackData) => Promise<void> \| void` | — | Handler riêng cho lần gọi này, **ghi đè** `onSubmit` của Provider. |
135
+
136
+ ### `FeedbackData`
87
137
 
88
138
  ```ts
89
- {
139
+ interface FeedbackData {
90
140
  idQuestion: string | number;
91
141
  idChildQuestion?: string | number;
92
- errorType: string; // incorrect_content | display_error | missing_data | technical_issue | other
142
+ errorType: string; // incorrect_content | display_error | missing_data | technical_issue | other
93
143
  errorDescription: string; // text; là HTML nếu dùng CKEditor
94
- listImage: string[]; // URL ảnh đã upload (tối đa 3)
144
+ listImage: string[]; // URL ảnh đã upload (tối đa 3)
95
145
  deviceInfo: { os; osVersion; browser; browserVersion; deviceType };
96
146
  contextData?: any;
97
147
  }
@@ -99,11 +149,12 @@ const region = await captureScreenshot({ x, y, width, height }); // 1 vùng (to
99
149
 
100
150
  ## Lưu ý
101
151
 
102
- - **onSubmit** ưu tiên: handler theo lần gọi → handler ở Provider → không có thì chỉ cảnh báo (không crash).
103
- - **CKEditor**: `hocmai-ckeditor-custom` là build kiểu cũ → dùng `@ckeditor/ckeditor5-react` đời cũ (v5/v6), **không** dùng latest. Nên copy đúng version từ project gốc. Build này đã chứa core, **không cần** cài `@ckeditor/ckeditor5-core`.
152
+ - **Thứ tự `onSubmit`:** handler theo lần gọi → handler ở Provider → nếu không có thì chỉ cảnh báo (không crash).
153
+ - **CKEditor:** `hocmai-ckeditor-custom` là build kiểu cũ → dùng `@ckeditor/ckeditor5-react` đời cũ (v5/v6), **không** dùng latest. Nên copy đúng version từ project gốc. Build này đã chứa core nên **không cần** cài `@ckeditor/ckeditor5-core`.
104
154
  - Khi dùng CKEditor, `errorDescription` trả về là **HTML** — backend cần xử lý phù hợp.
105
- - Core **không** phụ thuộc CKEditor: app không dùng `isCkEditor` vẫn build bình thường, không cần cài deps CKEditor.
155
+ - Core **không** phụ thuộc CKEditor: app không dùng `isCkEditor` vẫn build bình thường.
106
156
  - An toàn **SSR** (không chạm `window`/`document` lúc import).
107
157
 
108
158
  ## License
109
- MIT
159
+
160
+ [MIT](./LICENSE)
@@ -23,6 +23,12 @@ interface FeedbackProviderProps {
23
23
  isCkEditor?: boolean;
24
24
  /** Editor component for the description (from `hocmai-feedback-widget/ckeditor` or your own). */
25
25
  descriptionEditor?: React.ComponentType<CkEditorProps>;
26
+ /** Show the "full screen" capture button. Default true. */
27
+ isEnableCaptureFull?: boolean;
28
+ /** Show the "select region" capture button. Default true. */
29
+ isEnableCaptureElement?: boolean;
30
+ /** Endpoint that accepts { base64 } and returns { file_url } for image uploads. */
31
+ uploadUrl?: string;
26
32
  }
27
33
  export declare const FeedbackProvider: React.FC<FeedbackProviderProps>;
28
34
  export {};
@@ -7003,8 +7003,8 @@ const rd = [
7003
7003
  alignItems: "center",
7004
7004
  justifyContent: "center",
7005
7005
  cursor: "zoom-out",
7006
- animation: "fb-fade-in 0.2s ease-out",
7007
- backdropFilter: "blur(4px)"
7006
+ animation: "fb-fade-in 0.15s ease-out",
7007
+ willChange: "opacity"
7008
7008
  },
7009
7009
  onClick: () => k(null),
7010
7010
  children: /* @__PURE__ */ _(
@@ -7013,6 +7013,7 @@ const rd = [
7013
7013
  src: E,
7014
7014
  alt: "Zoomed",
7015
7015
  draggable: !1,
7016
+ decoding: "async",
7016
7017
  onContextMenu: (I) => I.preventDefault(),
7017
7018
  style: {
7018
7019
  maxWidth: "90vw",
@@ -7066,7 +7067,7 @@ const rd = [
7066
7067
  )
7067
7068
  }
7068
7069
  ),
7069
- L && /* @__PURE__ */ _("div", { "data-hsa-report-ui": "true", style: { position: "fixed", top: 20, right: 20, background: "rgba(0,0,0,0.8)", color: "white", padding: "8px 12px", borderRadius: "8px", zIndex: 1e4, fontWeight: 500, backdropFilter: "blur(4px)", fontSize: "0.8rem" }, children: "Đang chụp..." }),
7070
+ L && /* @__PURE__ */ _("div", { "data-hsa-report-ui": "true", style: { position: "fixed", top: 20, right: 20, background: "rgba(0,0,0,0.8)", color: "white", padding: "8px 12px", borderRadius: "8px", zIndex: 1e4, fontWeight: 500, fontSize: "0.8rem" }, children: "Đang chụp..." }),
7070
7071
  X && !j && !E && /* @__PURE__ */ K("div", { className: "fb-widget-modal", "data-hsa-report-ui": "true", children: [
7071
7072
  /* @__PURE__ */ K("div", { style: { display: "flex", justifyContent: "space-between", alignItems: "center" }, children: [
7072
7073
  /* @__PURE__ */ _("h2", { style: { margin: 0, fontSize: "1rem", fontWeight: 700, color: "#111827" }, children: "Báo lỗi" }),
@@ -7504,36 +7505,42 @@ const rd = [
7504
7505
  onSubmit: t,
7505
7506
  themeColor: r,
7506
7507
  isCkEditor: n,
7507
- descriptionEditor: o
7508
+ descriptionEditor: o,
7509
+ isEnableCaptureFull: i,
7510
+ isEnableCaptureElement: a,
7511
+ uploadUrl: l
7508
7512
  }) => {
7509
- const [i, a] = ie(!1), [l, s] = ie(null), [u, c] = ie(void 0), [d, f] = ie(void 0), h = (g, y, x) => {
7510
- s(g), c(y), f(() => x), a(!0);
7511
- }, m = () => {
7512
- a(!1);
7513
- }, p = async (g) => {
7514
- const y = d ?? t;
7515
- if (!y) {
7513
+ const [s, u] = ie(!1), [c, d] = ie(null), [f, h] = ie(void 0), [m, p] = ie(void 0), g = (w, b, S) => {
7514
+ d(w), h(b), p(() => S), u(!0);
7515
+ }, y = () => {
7516
+ u(!1);
7517
+ }, x = async (w) => {
7518
+ const b = m ?? t;
7519
+ if (!b) {
7516
7520
  console.warn(
7517
7521
  "[feedback-widget] Bỏ qua gửi báo lỗi: chưa cung cấp onSubmit (ở FeedbackProvider hoặc openFeedback). Dữ liệu:",
7518
- g
7522
+ w
7519
7523
  );
7520
7524
  return;
7521
7525
  }
7522
- await y(g);
7526
+ await b(w);
7523
7527
  };
7524
- return /* @__PURE__ */ K(Oo.Provider, { value: { openFeedback: h, closeFeedback: m, isOpen: i, activeQuestionId: l }, children: [
7528
+ return /* @__PURE__ */ K(Oo.Provider, { value: { openFeedback: g, closeFeedback: y, isOpen: s, activeQuestionId: c }, children: [
7525
7529
  e,
7526
- l && /* @__PURE__ */ _(
7530
+ c && /* @__PURE__ */ _(
7527
7531
  od,
7528
7532
  {
7529
- questionId: l,
7530
- contextData: u,
7531
- onSubmit: p,
7533
+ questionId: c,
7534
+ contextData: f,
7535
+ onSubmit: x,
7532
7536
  themeColor: r,
7533
7537
  isCkEditor: n,
7534
7538
  descriptionEditor: o,
7535
- isOpen: i,
7536
- onClose: m,
7539
+ isEnableCaptureFull: i,
7540
+ isEnableCaptureElement: a,
7541
+ uploadUrl: l,
7542
+ isOpen: s,
7543
+ onClose: y,
7537
7544
  triggerComponent: /* @__PURE__ */ _($t, {})
7538
7545
  }
7539
7546
  )