pukaad-ui-lib 1.147.0 → 1.149.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/module.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "pukaad-ui-lib",
3
3
  "configKey": "pukaadUI",
4
- "version": "1.147.0",
4
+ "version": "1.149.0",
5
5
  "builder": {
6
6
  "@nuxt/module-builder": "1.0.2",
7
7
  "unbuild": "3.6.1"
@@ -138,7 +138,7 @@ const uploadImage = async (file) => {
138
138
  },
139
139
  body: JSON.stringify({
140
140
  file_name: [file.name],
141
- state: "blog"
141
+ state: "blog-cover"
142
142
  }),
143
143
  onRequest({ options }) {
144
144
  const { APP_TYPE } = config.public;
@@ -36,9 +36,9 @@ declare const __VLS_export: import("vue").DefineComponent<__VLS_PublicProps, {
36
36
  id: string;
37
37
  name: string;
38
38
  description: string;
39
+ limit: number;
39
40
  options: AutocompleteOption[] | string[] | number[];
40
41
  placeholder: string;
41
- limit: number;
42
42
  disabledErrorMessage: boolean;
43
43
  disabledBorder: boolean;
44
44
  showCounter: boolean;
@@ -36,9 +36,9 @@ declare const __VLS_export: import("vue").DefineComponent<__VLS_PublicProps, {
36
36
  id: string;
37
37
  name: string;
38
38
  description: string;
39
+ limit: number;
39
40
  options: AutocompleteOption[] | string[] | number[];
40
41
  placeholder: string;
41
- limit: number;
42
42
  disabledErrorMessage: boolean;
43
43
  disabledBorder: boolean;
44
44
  showCounter: boolean;
@@ -8,6 +8,7 @@ export interface InputContentProps {
8
8
  placeholder?: string;
9
9
  disabledBorder?: boolean;
10
10
  disableMedia?: boolean;
11
+ state?: "personal" | "business";
11
12
  }
12
13
  type __VLS_Props = InputContentProps;
13
14
  type __VLS_ModelProps = {
@@ -31,6 +32,7 @@ declare const __VLS_base: import("vue").DefineComponent<__VLS_PublicProps, {
31
32
  required: boolean;
32
33
  id: string;
33
34
  name: string;
35
+ state: "personal" | "business";
34
36
  disableMedia: boolean;
35
37
  }, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
36
38
  declare const __VLS_export: __VLS_WithSlots<typeof __VLS_base, __VLS_Slots>;
@@ -38,7 +38,9 @@
38
38
  <script setup>
39
39
  import { ref, onMounted, onUnmounted } from "vue";
40
40
  import { useNuxtApp } from "nuxt/app";
41
+ import { useApi } from "../../composables/useApi";
41
42
  const { $quill } = useNuxtApp();
43
+ const api = useApi();
42
44
  const contentRef = ref(null);
43
45
  let quillEditor = null;
44
46
  const props = defineProps({
@@ -50,7 +52,8 @@ const props = defineProps({
50
52
  height: { type: [String, Number], required: false, default: 288 },
51
53
  placeholder: { type: String, required: false },
52
54
  disabledBorder: { type: Boolean, required: false },
53
- disableMedia: { type: Boolean, required: false, default: false }
55
+ disableMedia: { type: Boolean, required: false, default: false },
56
+ state: { type: String, required: false, default: "personal" }
54
57
  });
55
58
  const modelValue = defineModel();
56
59
  const isDraggingMedia = ref(false);
@@ -69,6 +72,66 @@ defineExpose({
69
72
  validate,
70
73
  fieldRef
71
74
  });
75
+ const getUploadState = () => {
76
+ return props.state === "business" ? "business-blog-media" : "blog-media";
77
+ };
78
+ const uploadImageToPresigned = async (file) => {
79
+ const res = await api("/storage/presigned-urls", {
80
+ method: "POST",
81
+ body: {
82
+ file_name: [file.name],
83
+ state: getUploadState()
84
+ }
85
+ });
86
+ if (res.code !== 200 || !res.data?.items?.[0]) {
87
+ throw new Error(res.message || "Failed to generate upload URL");
88
+ }
89
+ const item = res.data.items[0];
90
+ await fetch(item.upload_url, {
91
+ method: "PUT",
92
+ body: file,
93
+ headers: {
94
+ "Content-Type": file.type
95
+ }
96
+ });
97
+ return item.public_url;
98
+ };
99
+ const insertUploadedImage = async (file) => {
100
+ if (!quillEditor) return;
101
+ try {
102
+ const url = await uploadImageToPresigned(file);
103
+ const range = quillEditor.getSelection(true);
104
+ quillEditor.insertEmbed(range.index, "image", url, "user");
105
+ quillEditor.setSelection(range.index + 1, 0);
106
+ } catch (error) {
107
+ console.error("Image upload failed:", error);
108
+ }
109
+ };
110
+ const handlePasteImage = (e) => {
111
+ if (props.disableMedia) return;
112
+ const clipboardData = e.clipboardData;
113
+ if (!clipboardData) return;
114
+ const imageFiles = Array.from(clipboardData.files).filter(
115
+ (file) => file.type.startsWith("image/")
116
+ );
117
+ if (imageFiles.length > 0) {
118
+ e.preventDefault();
119
+ imageFiles.forEach((file) => insertUploadedImage(file));
120
+ }
121
+ };
122
+ const handleDropUpload = (e) => {
123
+ if (props.disableMedia) return;
124
+ const dataTransfer = e.dataTransfer;
125
+ if (!dataTransfer) return;
126
+ const imageFiles = Array.from(dataTransfer.files).filter(
127
+ (file) => file.type.startsWith("image/")
128
+ );
129
+ if (imageFiles.length > 0) {
130
+ e.preventDefault();
131
+ e.stopPropagation();
132
+ imageFiles.forEach((file) => insertUploadedImage(file));
133
+ }
134
+ };
72
135
  onMounted(() => {
73
136
  if (contentRef.value) {
74
137
  const toolbarOptions = [
@@ -79,40 +142,59 @@ onMounted(() => {
79
142
  [{ indent: "-1" }, { indent: "+1" }],
80
143
  [{ color: [] }, { background: [] }],
81
144
  [{ align: [] }],
82
- props.disableMedia ? ["link"] : ["link", "image", "video"]
145
+ props.disableMedia ? ["link"] : ["link", "image"]
83
146
  ];
84
147
  const modules = {
85
- toolbar: toolbarOptions
86
- };
87
- if (props.disableMedia) {
88
- modules.clipboard = {
148
+ toolbar: toolbarOptions,
149
+ clipboard: {
89
150
  matchers: [
90
151
  [
91
- "IMG",
152
+ "VIDEO",
92
153
  () => {
93
154
  return { ops: [] };
94
155
  }
95
156
  ],
96
157
  [
97
- "VIDEO",
98
- () => {
99
- return { ops: [] };
158
+ "IMG",
159
+ (node, delta) => {
160
+ if (props.disableMedia) {
161
+ return { ops: [] };
162
+ }
163
+ const src = node.getAttribute("src");
164
+ if (src && src.startsWith("data:")) {
165
+ return { ops: [] };
166
+ }
167
+ return delta;
100
168
  }
101
169
  ]
102
170
  ]
103
- };
104
- }
171
+ }
172
+ };
105
173
  quillEditor = new $quill(contentRef.value, {
106
174
  theme: "snow",
107
175
  placeholder: props.placeholder,
108
176
  modules
109
177
  });
110
- if (props.disableMedia) {
111
- const toolbar = quillEditor.getModule("toolbar");
112
- if (toolbar && toolbar.addHandler) {
178
+ const toolbar = quillEditor.getModule("toolbar");
179
+ if (toolbar && toolbar.addHandler) {
180
+ if (props.disableMedia) {
113
181
  toolbar.addHandler("image", () => {
114
182
  });
115
- toolbar.addHandler("video", () => {
183
+ } else {
184
+ toolbar.addHandler("image", () => {
185
+ const fileInput = document.createElement("input");
186
+ fileInput.setAttribute("type", "file");
187
+ fileInput.setAttribute(
188
+ "accept",
189
+ "image/jpeg,image/png,image/webp,image/bmp,image/gif"
190
+ );
191
+ fileInput.addEventListener("change", () => {
192
+ const file = fileInput.files?.[0];
193
+ if (file) {
194
+ insertUploadedImage(file);
195
+ }
196
+ });
197
+ fileInput.click();
116
198
  });
117
199
  }
118
200
  }
@@ -123,10 +205,18 @@ onMounted(() => {
123
205
  if (source === "user") {
124
206
  if (!quillEditor) return;
125
207
  const currentDeltaJson = quillEditor.getContents();
126
- if (props.disableMedia && currentDeltaJson.ops) {
208
+ if (currentDeltaJson.ops) {
127
209
  const filteredOps = currentDeltaJson.ops.filter((op) => {
128
210
  if (op.insert && typeof op.insert === "object") {
129
- return !op.insert.image && !op.insert.video;
211
+ const isVideo = !!op.insert.video;
212
+ const isImage = !!op.insert.image;
213
+ if (props.disableMedia) {
214
+ return !isVideo && !isImage;
215
+ }
216
+ if (isImage && typeof op.insert.image === "string" && op.insert.image.startsWith("data:")) {
217
+ return false;
218
+ }
219
+ return !isVideo;
130
220
  }
131
221
  return true;
132
222
  });
@@ -141,12 +231,15 @@ onMounted(() => {
141
231
  }
142
232
  }
143
233
  });
234
+ quillEditor.root.addEventListener("paste", handlePasteImage);
144
235
  if (props.disableMedia) {
145
236
  const editorRoot = quillEditor.root;
146
237
  editorRoot.addEventListener("dragenter", handleDragEnter);
147
238
  editorRoot.addEventListener("dragover", handleDragOver);
148
239
  editorRoot.addEventListener("dragleave", handleDragLeave);
149
240
  editorRoot.addEventListener("drop", handleDrop);
241
+ } else {
242
+ quillEditor.root.addEventListener("drop", handleDropUpload);
150
243
  }
151
244
  }
152
245
  });
@@ -184,12 +277,15 @@ const handleDrop = (e) => {
184
277
  onUnmounted(() => {
185
278
  if (quillEditor && contentRef.value) {
186
279
  quillEditor.off("text-change");
280
+ quillEditor.root.removeEventListener("paste", handlePasteImage);
187
281
  if (props.disableMedia) {
188
282
  const editorRoot = quillEditor.root;
189
283
  editorRoot.removeEventListener("dragenter", handleDragEnter);
190
284
  editorRoot.removeEventListener("dragover", handleDragOver);
191
285
  editorRoot.removeEventListener("dragleave", handleDragLeave);
192
286
  editorRoot.removeEventListener("drop", handleDrop);
287
+ } else {
288
+ quillEditor.root.removeEventListener("drop", handleDropUpload);
193
289
  }
194
290
  const toolbar = contentRef.value.previousElementSibling;
195
291
  if (toolbar && toolbar.classList.contains("ql-toolbar")) {
@@ -8,6 +8,7 @@ export interface InputContentProps {
8
8
  placeholder?: string;
9
9
  disabledBorder?: boolean;
10
10
  disableMedia?: boolean;
11
+ state?: "personal" | "business";
11
12
  }
12
13
  type __VLS_Props = InputContentProps;
13
14
  type __VLS_ModelProps = {
@@ -31,6 +32,7 @@ declare const __VLS_base: import("vue").DefineComponent<__VLS_PublicProps, {
31
32
  required: boolean;
32
33
  id: string;
33
34
  name: string;
35
+ state: "personal" | "business";
34
36
  disableMedia: boolean;
35
37
  }, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
36
38
  declare const __VLS_export: __VLS_WithSlots<typeof __VLS_base, __VLS_Slots>;
@@ -31,8 +31,8 @@ declare const __VLS_base: import("vue").DefineComponent<__VLS_PublicProps, {}, {
31
31
  fullHeight: boolean;
32
32
  name: string;
33
33
  limit: number;
34
- disabledErrorMessage: boolean;
35
34
  accept: string;
35
+ disabledErrorMessage: boolean;
36
36
  labelIcon: string;
37
37
  disabledDrop: boolean;
38
38
  }, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
@@ -31,8 +31,8 @@ declare const __VLS_base: import("vue").DefineComponent<__VLS_PublicProps, {}, {
31
31
  fullHeight: boolean;
32
32
  name: string;
33
33
  limit: number;
34
- disabledErrorMessage: boolean;
35
34
  accept: string;
35
+ disabledErrorMessage: boolean;
36
36
  labelIcon: string;
37
37
  disabledDrop: boolean;
38
38
  }, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
@@ -18,8 +18,8 @@ declare const __VLS_export: import("vue").DefineComponent<__VLS_PublicProps, {},
18
18
  }>, {
19
19
  id: string;
20
20
  name: string;
21
- new: boolean;
22
21
  disabledForgotPassword: boolean;
22
+ new: boolean;
23
23
  }, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
24
24
  declare const _default: typeof __VLS_export;
25
25
  export default _default;
@@ -18,8 +18,8 @@ declare const __VLS_export: import("vue").DefineComponent<__VLS_PublicProps, {},
18
18
  }>, {
19
19
  id: string;
20
20
  name: string;
21
- new: boolean;
22
21
  disabledForgotPassword: boolean;
22
+ new: boolean;
23
23
  }, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
24
24
  declare const _default: typeof __VLS_export;
25
25
  export default _default;
@@ -12,9 +12,9 @@ declare const __VLS_export: import("vue").DefineComponent<__VLS_PublicProps, {},
12
12
  label: string;
13
13
  color: InputSliderColor;
14
14
  fullWidth: boolean;
15
+ step: number;
15
16
  max: number;
16
17
  min: number;
17
- step: number;
18
18
  lineHeight: number | string;
19
19
  appearance: boolean;
20
20
  thumbSize: number | string;
@@ -12,9 +12,9 @@ declare const __VLS_export: import("vue").DefineComponent<__VLS_PublicProps, {},
12
12
  label: string;
13
13
  color: InputSliderColor;
14
14
  fullWidth: boolean;
15
+ step: number;
15
16
  max: number;
16
17
  min: number;
17
- step: number;
18
18
  lineHeight: number | string;
19
19
  appearance: boolean;
20
20
  thumbSize: number | string;
@@ -26,8 +26,8 @@ declare const __VLS_export: import("vue").DefineComponent<__VLS_PublicProps, {},
26
26
  }>, {
27
27
  name: string;
28
28
  state: "user" | "admin";
29
- placeholder: string;
30
29
  limit: number;
30
+ placeholder: string;
31
31
  ignore: string[];
32
32
  }, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
33
33
  declare const _default: typeof __VLS_export;
@@ -26,8 +26,8 @@ declare const __VLS_export: import("vue").DefineComponent<__VLS_PublicProps, {},
26
26
  }>, {
27
27
  name: string;
28
28
  state: "user" | "admin";
29
- placeholder: string;
30
29
  limit: number;
30
+ placeholder: string;
31
31
  ignore: string[];
32
32
  }, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
33
33
  declare const _default: typeof __VLS_export;
@@ -24,8 +24,8 @@ declare const __VLS_export: import("vue").DefineComponent<__VLS_PublicProps, {},
24
24
  onClose?: (() => any) | undefined;
25
25
  }>, {
26
26
  title: string;
27
- disabledForgotPassword: boolean;
28
27
  confirmText: string;
28
+ disabledForgotPassword: boolean;
29
29
  }, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
30
30
  declare const _default: typeof __VLS_export;
31
31
  export default _default;
@@ -24,8 +24,8 @@ declare const __VLS_export: import("vue").DefineComponent<__VLS_PublicProps, {},
24
24
  onClose?: (() => any) | undefined;
25
25
  }>, {
26
26
  title: string;
27
- disabledForgotPassword: boolean;
28
27
  confirmText: string;
28
+ disabledForgotPassword: boolean;
29
29
  }, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
30
30
  declare const _default: typeof __VLS_export;
31
31
  export default _default;
@@ -6,17 +6,17 @@ export declare class Convert {
6
6
  convertNumber: (num: number) => string;
7
7
  /**
8
8
  * Convert date to Thai format
9
- * @example convertDate("2024-01-01") => "1 ม.ค. 2567"
9
+ * @example convertDate("2024-01-01") => "1 มกราคม 2567"
10
10
  */
11
11
  convertDate: (date: string | Date | null | undefined, format?: string) => string;
12
12
  /**
13
13
  * Convert date time to Thai format
14
- * @example convertDateTime("2024-01-01 12:00") => "1 ม.ค. 2567 12:00"
14
+ * @example convertDateTime("2024-01-01 12:00") => "1 มกราคม 2567 12:00"
15
15
  */
16
16
  convertDateTime: (date: string | Date | null | undefined, format?: string) => string;
17
17
  /**
18
18
  * Convert date to relative text
19
- * @example "เมื่อสักครู่", "10 นาทีที่ผ่านมา", "2 ชั่วโมงที่ผ่านมา", "17 ก.พ. 2569, 13:19"
19
+ * @example "เมื่อสักครู่", "10 นาทีที่ผ่านมา", "2 ชั่วโมงที่ผ่านมา", "17 กุมภาพันธ์ 2569, 13:19"
20
20
  */
21
21
  convertDateTorelativeText: (date: string | Date | null | undefined) => string;
22
22
  }
@@ -25,23 +25,23 @@ export class Convert {
25
25
  };
26
26
  /**
27
27
  * Convert date to Thai format
28
- * @example convertDate("2024-01-01") => "1 ม.ค. 2567"
28
+ * @example convertDate("2024-01-01") => "1 มกราคม 2567"
29
29
  */
30
- convertDate = (date, format = "D MMM BBBB") => {
30
+ convertDate = (date, format = "D MMMM BBBB") => {
31
31
  if (!date) return "-";
32
32
  return dayjs(date).format(format);
33
33
  };
34
34
  /**
35
35
  * Convert date time to Thai format
36
- * @example convertDateTime("2024-01-01 12:00") => "1 ม.ค. 2567 12:00"
36
+ * @example convertDateTime("2024-01-01 12:00") => "1 มกราคม 2567 12:00"
37
37
  */
38
- convertDateTime = (date, format = "D MMM BBBB, HH:mm") => {
38
+ convertDateTime = (date, format = "D MMMM BBBB, HH:mm") => {
39
39
  if (!date) return "-";
40
40
  return dayjs(date).format(format);
41
41
  };
42
42
  /**
43
43
  * Convert date to relative text
44
- * @example "เมื่อสักครู่", "10 นาทีที่ผ่านมา", "2 ชั่วโมงที่ผ่านมา", "17 ก.พ. 2569, 13:19"
44
+ * @example "เมื่อสักครู่", "10 นาทีที่ผ่านมา", "2 ชั่วโมงที่ผ่านมา", "17 กุมภาพันธ์ 2569, 13:19"
45
45
  */
46
46
  convertDateTorelativeText = (date) => {
47
47
  if (!date) return "-";
@@ -58,7 +58,7 @@ export class Convert {
58
58
  if (diffHour < 24) {
59
59
  return `${diffHour} \u0E0A\u0E31\u0E48\u0E27\u0E42\u0E21\u0E07\u0E17\u0E35\u0E48\u0E1C\u0E48\u0E32\u0E19\u0E21\u0E32`;
60
60
  }
61
- return this.convertDateTime(date, "D MMM BBBB, HH:mm");
61
+ return this.convertDateTime(date, "D MMMM BBBB, HH:mm");
62
62
  };
63
63
  }
64
64
  const convertInstance = new Convert();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pukaad-ui-lib",
3
- "version": "1.147.0",
3
+ "version": "1.149.0",
4
4
  "description": "pukaad-ui for MeMSG",
5
5
  "repository": {
6
6
  "type": "git",