pukaad-ui-lib 1.193.0 → 1.195.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.193.0",
4
+ "version": "1.195.0",
5
5
  "builder": {
6
6
  "@nuxt/module-builder": "1.0.2",
7
7
  "unbuild": "3.6.1"
@@ -33,6 +33,7 @@
33
33
  <script setup>
34
34
  import { computed, reactive, ref, watch } from "vue";
35
35
  import { useApi } from "../../composables/useApi";
36
+ const api = useApi();
36
37
  const isOpenModalShare = ref(false);
37
38
  const props = defineProps({
38
39
  itemCount: { type: Object, required: false, default: () => ({
@@ -119,7 +120,6 @@ async function toggleLike() {
119
120
  counts.liked += reaction.liked ? 1 : -1;
120
121
  if (props.state === "blog" && props.id) {
121
122
  try {
122
- const api = useApi();
123
123
  await api(`/blogs/${props.id}/like`, {
124
124
  method: reaction.liked ? "POST" : "DELETE"
125
125
  });
@@ -12,10 +12,9 @@
12
12
  class="w-full min-h-[36px] px-[12px] py-[5px] rounded-md border border-mercury font-body-large focus:outline-none focus:ring-2 focus:ring-primary break-words bg-white empty:before:content-[attr(data-placeholder)] empty:before:text-gray"
13
13
  data-placeholder="แสดงความคิดเห็น"
14
14
  @focus="isShowMainActions = true"
15
- @input="
16
- mainCommentInput = $event.target.innerText.trim()
17
- "
15
+ @input="onMainCommentInput"
18
16
  @keydown.enter.exact.prevent="onSendComment"
17
+ @keydown.shift.enter.prevent="onInsertLineBreak"
19
18
  />
20
19
  </div>
21
20
  <div v-if="isShowMainActions" class="flex justify-end gap-[8px]">
@@ -55,13 +54,13 @@
55
54
  :ref="
56
55
  (el) => registerContentRef(el, `comment-${cmt.id}`)
57
56
  "
58
- class="font-body-large"
57
+ class="font-body-large whitespace-pre-wrap"
59
58
  >
60
59
  <template
61
60
  v-if="
62
61
  isContentExpanded(`comment-${cmt.id}`) || !isContentOverflowing(`comment-${cmt.id}`)
63
62
  "
64
- >{{ cmt.content }}</template
63
+ >{{ normalizeContent(cmt.content) }}</template
65
64
  >
66
65
  <template v-else
67
66
  >{{ getTruncatedText(`comment-${cmt.id}`) }}...<span
@@ -135,6 +134,7 @@
135
134
  data-placeholder="เขียนการตอบกลับ..."
136
135
  @input="onReplyInput($event, cmt.id)"
137
136
  @keydown.enter.exact.prevent="onSendReplyComment(cmt)"
137
+ @keydown.shift.enter.prevent="onInsertLineBreak"
138
138
  />
139
139
  </div>
140
140
  </div>
@@ -189,7 +189,7 @@
189
189
  :ref="
190
190
  (el) => registerContentRef(el, `reply-${reply.id}`)
191
191
  "
192
- class="font-body-large"
192
+ class="font-body-large whitespace-pre-wrap"
193
193
  >
194
194
  <template
195
195
  v-if="
@@ -200,9 +200,14 @@
200
200
  @click="onViewProfileComment(reply.reply_to.path_name)"
201
201
  class="text-primary cursor-pointer mr-1"
202
202
  >{{ reply.reply_to.name }}</span
203
- >{{ reply.content }}</template
203
+ >{{ normalizeContent(reply.content) }}</template
204
204
  >
205
205
  <template v-else
206
+ ><span
207
+ v-if="reply.reply_to"
208
+ @click="onViewProfileComment(reply.reply_to.path_name)"
209
+ class="text-primary cursor-pointer mr-1"
210
+ >{{ reply.reply_to.name }}</span
206
211
  >{{ getTruncatedText(`reply-${reply.id}`) }}...<span
207
212
  class="text-primary cursor-pointer"
208
213
  @click="toggleContentExpand(`reply-${reply.id}`)"
@@ -252,6 +257,7 @@
252
257
  data-placeholder="เขียนการตอบกลับ..."
253
258
  @input="onReplyInput($event, reply.id)"
254
259
  @keydown.enter.exact.prevent="onSendReplyComment(cmt)"
260
+ @keydown.shift.enter.prevent="onInsertLineBreak"
255
261
  />
256
262
  </div>
257
263
  </div>
@@ -395,46 +401,62 @@ const onToggleReplyComment = (id) => {
395
401
  const isContentExpanded = (id) => expandedContentIds.value.includes(id);
396
402
  const isContentOverflowing = (id) => typeof truncatedTexts.value[id] === "string";
397
403
  const getTruncatedText = (id) => truncatedTexts.value[id];
404
+ const normalizeContent = (text) => text.replace(/\n{3,}/g, "\n\n");
405
+ const measureContent = (el, id) => {
406
+ const rect = el.getBoundingClientRect();
407
+ const width = rect.width;
408
+ if (!width) return;
409
+ const fullText = el.textContent ?? "";
410
+ const style = getComputedStyle(el);
411
+ const lineHeight = parseFloat(style.lineHeight) || parseFloat(style.fontSize) * 1.5;
412
+ const maxH = lineHeight * 3;
413
+ const clone = document.createElement("div");
414
+ clone.style.cssText = [
415
+ `visibility:hidden`,
416
+ `position:fixed`,
417
+ `top:-9999px`,
418
+ `width:${width}px`,
419
+ `font-size:${style.fontSize}`,
420
+ `font-family:${style.fontFamily}`,
421
+ `font-weight:${style.fontWeight}`,
422
+ `line-height:${style.lineHeight}`,
423
+ `letter-spacing:${style.letterSpacing}`,
424
+ `word-break:break-word`,
425
+ `white-space:pre-wrap`
426
+ ].join(";");
427
+ clone.textContent = fullText;
428
+ document.body.appendChild(clone);
429
+ if (clone.scrollHeight <= maxH + 2) {
430
+ clone.remove();
431
+ truncatedTexts.value[id] = null;
432
+ return;
433
+ }
434
+ let lo = 0, hi = fullText.length;
435
+ while (lo < hi) {
436
+ const mid = Math.floor((lo + hi + 1) / 2);
437
+ clone.textContent = fullText.slice(0, mid) + "...\u0E14\u0E39\u0E40\u0E1E\u0E34\u0E48\u0E21\u0E40\u0E15\u0E34\u0E21";
438
+ if (clone.scrollHeight <= maxH + 2) lo = mid;
439
+ else hi = mid - 1;
440
+ }
441
+ clone.remove();
442
+ truncatedTexts.value[id] = fullText.slice(0, lo);
443
+ };
398
444
  const registerContentRef = (el, id) => {
399
445
  if (!el || id in truncatedTexts.value) return;
400
446
  nextTick(() => {
401
447
  const rect = el.getBoundingClientRect();
402
448
  const width = rect.width;
403
- if (!width) return;
404
- const fullText = el.textContent ?? "";
405
- const style = getComputedStyle(el);
406
- const lineHeight = parseFloat(style.lineHeight) || parseFloat(style.fontSize) * 1.5;
407
- const maxH = lineHeight * 3;
408
- const clone = document.createElement("div");
409
- clone.style.cssText = [
410
- `visibility:hidden`,
411
- `position:fixed`,
412
- `top:-9999px`,
413
- `width:${width}px`,
414
- `font-size:${style.fontSize}`,
415
- `font-family:${style.fontFamily}`,
416
- `font-weight:${style.fontWeight}`,
417
- `line-height:${style.lineHeight}`,
418
- `letter-spacing:${style.letterSpacing}`,
419
- `word-break:break-word`,
420
- `white-space:normal`
421
- ].join(";");
422
- clone.textContent = fullText;
423
- document.body.appendChild(clone);
424
- if (clone.scrollHeight <= maxH + 2) {
425
- clone.remove();
426
- truncatedTexts.value[id] = null;
449
+ if (!width) {
450
+ const ro = new ResizeObserver((_, obs) => {
451
+ if (el.getBoundingClientRect().width > 0) {
452
+ obs.disconnect();
453
+ measureContent(el, id);
454
+ }
455
+ });
456
+ ro.observe(el);
427
457
  return;
428
458
  }
429
- let lo = 0, hi = fullText.length;
430
- while (lo < hi) {
431
- const mid = Math.floor((lo + hi + 1) / 2);
432
- clone.textContent = fullText.slice(0, mid) + "...\u0E14\u0E39\u0E40\u0E1E\u0E34\u0E48\u0E21\u0E40\u0E15\u0E34\u0E21";
433
- if (clone.scrollHeight <= maxH + 2) lo = mid;
434
- else hi = mid - 1;
435
- }
436
- clone.remove();
437
- truncatedTexts.value[id] = fullText.slice(0, lo);
459
+ measureContent(el, id);
438
460
  });
439
461
  };
440
462
  const toggleContentExpand = (id) => {
@@ -473,7 +495,7 @@ const setReplyInputRef = (el, id) => {
473
495
  };
474
496
  const onReplyComment = (id, user) => {
475
497
  replyingToId.value = id;
476
- replyToUser.value = user;
498
+ replyToUser.value = user.id !== currentUser.id ? user : null;
477
499
  replyTextMap.value[id] = "";
478
500
  nextTick(() => {
479
501
  const el = replyInputRefs.value[id];
@@ -498,16 +520,52 @@ const onReplyComment = (id, user) => {
498
520
  el.focus();
499
521
  });
500
522
  };
523
+ const readTextWithBreaks = (el, skipMention = false) => {
524
+ const nodes = Array.from(el.childNodes);
525
+ while (nodes.length && nodes.at(-1)?.nodeName === "BR") nodes.pop();
526
+ let text = "";
527
+ nodes.forEach((node) => {
528
+ if (skipMention && node.dataset?.type === "mention")
529
+ return;
530
+ if (node.nodeName === "BR") {
531
+ text += "\n";
532
+ return;
533
+ }
534
+ if (node.nodeName === "DIV") {
535
+ text += "\n" + node.innerText;
536
+ return;
537
+ }
538
+ text += node.textContent ?? "";
539
+ });
540
+ return text.replace(/\n{3,}/g, "\n\n").replace(/^\u00A0/, "").trim();
541
+ };
542
+ const onInsertLineBreak = (event) => {
543
+ const el = event.target;
544
+ const sel = window.getSelection();
545
+ if (!sel || sel.rangeCount === 0) return;
546
+ const range = sel.getRangeAt(0);
547
+ range.deleteContents();
548
+ const br = document.createElement("br");
549
+ range.insertNode(br);
550
+ if (!br.nextSibling || br.nextSibling.nodeName !== "BR" && br.nextSibling.textContent === "") {
551
+ const phantom = document.createElement("br");
552
+ br.after(phantom);
553
+ }
554
+ range.setStartAfter(br);
555
+ range.collapse(true);
556
+ sel.removeAllRanges();
557
+ sel.addRange(range);
558
+ el.dispatchEvent(new Event("input", { bubbles: true }));
559
+ };
560
+ const onMainCommentInput = (event) => {
561
+ const el = event.target;
562
+ mainCommentInput.value = readTextWithBreaks(el);
563
+ };
501
564
  const onReplyInput = (event, id) => {
502
565
  const el = event.target;
503
566
  const chipExists = !!el.querySelector('[data-type="mention"]');
504
567
  if (!chipExists) replyToUser.value = null;
505
- let text = "";
506
- el.childNodes.forEach((node) => {
507
- if (node.dataset?.type === "mention") return;
508
- text += node.textContent;
509
- });
510
- replyTextMap.value[id] = text.replace(/^\u00A0/, "").trim();
568
+ replyTextMap.value[id] = readTextWithBreaks(el, true);
511
569
  };
512
570
  const onCancelReplyComment = () => {
513
571
  const id = replyingToId.value;
@@ -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>;
@@ -22,8 +22,8 @@ declare const __VLS_export: import("vue").DefineComponent<__VLS_PublicProps, {
22
22
  }>, {
23
23
  id: string;
24
24
  name: string;
25
- new: boolean;
26
25
  disabledForgotPassword: boolean;
26
+ new: boolean;
27
27
  }, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
28
28
  declare const _default: typeof __VLS_export;
29
29
  export default _default;
@@ -22,8 +22,8 @@ declare const __VLS_export: import("vue").DefineComponent<__VLS_PublicProps, {
22
22
  }>, {
23
23
  id: string;
24
24
  name: string;
25
- new: boolean;
26
25
  disabledForgotPassword: boolean;
26
+ new: boolean;
27
27
  }, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
28
28
  declare const _default: typeof __VLS_export;
29
29
  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;
@@ -45,10 +45,10 @@ declare const __VLS_base: import("vue").DefineComponent<__VLS_PublicProps, {
45
45
  id: string;
46
46
  name: string;
47
47
  limit: number;
48
+ resize: "none" | "both" | "horizontal" | "vertical";
48
49
  disabledErrorMessage: boolean;
49
50
  disabledBorder: boolean;
50
51
  showCounter: boolean;
51
- resize: "none" | "both" | "horizontal" | "vertical";
52
52
  readonly: boolean;
53
53
  rows: number;
54
54
  heightScroll: boolean;
@@ -45,10 +45,10 @@ declare const __VLS_base: import("vue").DefineComponent<__VLS_PublicProps, {
45
45
  id: string;
46
46
  name: string;
47
47
  limit: number;
48
+ resize: "none" | "both" | "horizontal" | "vertical";
48
49
  disabledErrorMessage: boolean;
49
50
  disabledBorder: boolean;
50
51
  showCounter: boolean;
51
- resize: "none" | "both" | "horizontal" | "vertical";
52
52
  readonly: boolean;
53
53
  rows: number;
54
54
  heightScroll: boolean;
@@ -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;
@@ -28,8 +28,8 @@ declare const __VLS_export: import("vue").DefineComponent<__VLS_PublicProps, {},
28
28
  }>, {
29
29
  title: string;
30
30
  mode: "login" | "secure";
31
- disabledForgotPassword: boolean;
32
31
  confirmText: string;
32
+ disabledForgotPassword: boolean;
33
33
  }, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
34
34
  declare const _default: typeof __VLS_export;
35
35
  export default _default;
@@ -28,8 +28,8 @@ declare const __VLS_export: import("vue").DefineComponent<__VLS_PublicProps, {},
28
28
  }>, {
29
29
  title: string;
30
30
  mode: "login" | "secure";
31
- disabledForgotPassword: boolean;
32
31
  confirmText: string;
32
+ disabledForgotPassword: boolean;
33
33
  }, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
34
34
  declare const _default: typeof __VLS_export;
35
35
  export default _default;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pukaad-ui-lib",
3
- "version": "1.193.0",
3
+ "version": "1.195.0",
4
4
  "description": "pukaad-ui for MeMSG",
5
5
  "repository": {
6
6
  "type": "git",