@zenify-omni/chat-js 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/README.md +28 -0
- package/api/campaign.js +19 -0
- package/api/chat.js +518 -0
- package/api/chatroom.js +254 -0
- package/api/contact.js +45 -0
- package/api/crm-organization.js +101 -0
- package/api/customer.js +143 -0
- package/api/facebook-labels.js +11 -0
- package/api/field.js +11 -0
- package/api/file.js +17 -0
- package/api/filter.js +50 -0
- package/api/group.js +11 -0
- package/api/iam.js +23 -0
- package/api/index.js +54 -0
- package/api/orgTree.js +45 -0
- package/api/post.js +111 -0
- package/api/queue.js +15 -0
- package/api/report.js +422 -0
- package/api/send.js +21 -0
- package/api/session-event.js +30 -0
- package/api/session.js +249 -0
- package/api/settings/assign-task.js +10 -0
- package/api/settings/channel.js +136 -0
- package/api/settings/chat-layout.js +37 -0
- package/api/settings/chat-timeout.js +10 -0
- package/api/settings/common.js +32 -0
- package/api/settings/config.js +19 -0
- package/api/settings/index.js +24 -0
- package/api/settings/ivr.js +25 -0
- package/api/settings/queue-v3.js +56 -0
- package/api/settings/queue.js +48 -0
- package/api/settings/routing.js +32 -0
- package/api/settings/service.js +31 -0
- package/api/settings.js +567 -0
- package/api/shipping.js +19 -0
- package/api/shopee.js +21 -0
- package/api/smart-qc/index.js +4 -0
- package/api/smart-qc/keyword.js +10 -0
- package/api/tag.js +11 -0
- package/api/user.js +177 -0
- package/client.js +391 -0
- package/config/env.js +76 -0
- package/constants/advance-filter.js +358 -0
- package/constants/auto-assign-tag.js +13 -0
- package/constants/chat-message.js +423 -0
- package/constants/chat-room-tag.js +7 -0
- package/constants/chat-source.js +68 -0
- package/constants/chatbot.js +58 -0
- package/constants/common.js +4 -0
- package/constants/crm-organization.js +75 -0
- package/constants/distribute.js +8 -0
- package/constants/distribution-log.js +14 -0
- package/constants/events.js +27 -0
- package/constants/file-upload.js +86 -0
- package/constants/lead.js +111 -0
- package/constants/mask-data-config.js +16 -0
- package/constants/month.js +78 -0
- package/constants/quick-filter.js +49 -0
- package/constants/regex.js +34 -0
- package/constants/setting.js +62 -0
- package/constants/shipping.js +12 -0
- package/constants/social.js +21 -0
- package/constants/sortable-fields.js +146 -0
- package/constants/tenant_config.js +154 -0
- package/constants/use-query-key.js +470 -0
- package/constants/user.js +293 -0
- package/exceptions/axios-error.js +24 -0
- package/exceptions/https-error.js +28 -0
- package/exceptions/validator-error.js +20 -0
- package/lib/avatar-color.js +31 -0
- package/lib/i18n/index.js +85 -0
- package/lib/i18n/language-store.js +74 -0
- package/lib/i18n/locales/en/campaign.json +3 -0
- package/lib/i18n/locales/en/chat.json +656 -0
- package/lib/i18n/locales/en/common.json +1846 -0
- package/lib/i18n/locales/en/customer.json +20 -0
- package/lib/i18n/locales/en/lead.json +5 -0
- package/lib/i18n/locales/en/lumi.json +4 -0
- package/lib/i18n/locales/en/notification.json +107 -0
- package/lib/i18n/locales/en/org.json +4 -0
- package/lib/i18n/locales/en/posts.json +8 -0
- package/lib/i18n/locales/en/settings.json +39 -0
- package/lib/i18n/locales/vi/campaign.json +3 -0
- package/lib/i18n/locales/vi/chat.json +660 -0
- package/lib/i18n/locales/vi/common.json +1846 -0
- package/lib/i18n/locales/vi/customer.json +20 -0
- package/lib/i18n/locales/vi/lead.json +5 -0
- package/lib/i18n/locales/vi/lumi.json +4 -0
- package/lib/i18n/locales/vi/notification.json +107 -0
- package/lib/i18n/locales/vi/org.json +4 -0
- package/lib/i18n/locales/vi/posts.json +8 -0
- package/lib/i18n/locales/vi/settings.json +39 -0
- package/lib/i18n/server-label.js +33 -0
- package/lib/phone.js +70 -0
- package/lib/user-avatar.js +21 -0
- package/package.json +45 -0
- package/socket/index.js +30 -0
- package/transport-registry.js +77 -0
- package/utils/access-detail.js +119 -0
- package/utils/auth.js +205 -0
- package/utils/chunk-reload-guard.js +4 -0
- package/utils/concurrency-limiter.js +30 -0
- package/utils/crm-organization.js +0 -0
- package/utils/currency.js +14 -0
- package/utils/customers.js +42 -0
- package/utils/data.js +178 -0
- package/utils/date.js +144 -0
- package/utils/debounced-invalidate.js +15 -0
- package/utils/emoticon.js +134 -0
- package/utils/export.js +7 -0
- package/utils/field-layout.js +33 -0
- package/utils/filter-adapters.js +9 -0
- package/utils/filter.js +18 -0
- package/utils/formatters.js +6 -0
- package/utils/image-compression.js +169 -0
- package/utils/leads.js +13 -0
- package/utils/microlink.js +20 -0
- package/utils/org-tree.js +108 -0
- package/utils/request.js +461 -0
- package/utils/session-end.js +19 -0
- package/utils/string.js +154 -0
- package/utils/table-display-config.js +80 -0
- package/utils/validation.js +49 -0
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
const EMOTICON_MAP = [
|
|
2
|
+
// 9-character (Zalo)
|
|
3
|
+
[":handclap", "\u{1F44F}"],
|
|
4
|
+
// 8-character (Zalo)
|
|
5
|
+
["/-thanks", "\u{1F64F}"],
|
|
6
|
+
["/-strong", "\u{1F44D}"],
|
|
7
|
+
// 7-character (Zalo)
|
|
8
|
+
["/-heart", "\u2764\uFE0F"],
|
|
9
|
+
["/-share", "\u{1F4E4}"],
|
|
10
|
+
["/-break", "\u{1F494}"],
|
|
11
|
+
["/-punch", "\u{1F44A}"],
|
|
12
|
+
["/-loveu", "\u{1F970}"],
|
|
13
|
+
// 6-character (Zalo)
|
|
14
|
+
["/-bome", "\u{1F4A3}"],
|
|
15
|
+
["/-beer", "\u{1F37A}"],
|
|
16
|
+
["/-weak", "\u{1F44E}"],
|
|
17
|
+
["/-rose", "\u{1F339}"],
|
|
18
|
+
["/-shit", "\u{1F4A9}"],
|
|
19
|
+
["/-fade", "\u{1F635}"],
|
|
20
|
+
// 5-character (Zalo)
|
|
21
|
+
[":-bye", "\u{1F44B}"],
|
|
22
|
+
["/-bad", "\u{1F44E}"],
|
|
23
|
+
[":-dig", "\u{1F924}"],
|
|
24
|
+
[":wipe", "\u{1F605}"],
|
|
25
|
+
// 4-character
|
|
26
|
+
[">:-(", "\u{1F621}"],
|
|
27
|
+
[":)))", "\u{1F601}"],
|
|
28
|
+
[":(((", "\u{1F62D}"],
|
|
29
|
+
[":-))", "\u{1F604}"],
|
|
30
|
+
["/-ok", "\u{1F44C}"],
|
|
31
|
+
["/-no", "\u{1F645}"],
|
|
32
|
+
["/-li", "\u2600\uFE0F"],
|
|
33
|
+
["/-bd", "\u{1F382}"],
|
|
34
|
+
[":-((", "\u{1F62D}"],
|
|
35
|
+
["_()_", "\u{1F64F}"],
|
|
36
|
+
[":--|", "\u{1F610}"],
|
|
37
|
+
// 3-character
|
|
38
|
+
["/-v", "\u270C\uFE0F"],
|
|
39
|
+
[">:(", "\u{1F621}"],
|
|
40
|
+
[":'(", "\u{1F62D}"],
|
|
41
|
+
[":')", "\u{1F602}"],
|
|
42
|
+
["=))", "\u{1F602}"],
|
|
43
|
+
[":))", "\u{1F604}"],
|
|
44
|
+
[":((", "\u{1F622}"],
|
|
45
|
+
[";))", "\u{1F60F}"],
|
|
46
|
+
[":-)", "\u{1F642}"],
|
|
47
|
+
[":-(", "\u{1F614}"],
|
|
48
|
+
[":-P", "\u{1F61B}"],
|
|
49
|
+
[":-p", "\u{1F61B}"],
|
|
50
|
+
[":-*", "\u{1F618}"],
|
|
51
|
+
[":-|", "\u{1F610}"],
|
|
52
|
+
[":-D", "\u{1F604}"],
|
|
53
|
+
[":-O", "\u{1F62E}"],
|
|
54
|
+
[":-o", "\u{1F62E}"],
|
|
55
|
+
[":-h", "\u{1F620}"],
|
|
56
|
+
[":-f", "\u{1F923}"],
|
|
57
|
+
[":-l", "\u{1F611}"],
|
|
58
|
+
[":-r", "\u{1F60F}"],
|
|
59
|
+
[":-<", "\u{1F61E}"],
|
|
60
|
+
[";-/", "\u{1F615}"],
|
|
61
|
+
[";xx", "\u{1F60D}"],
|
|
62
|
+
[";-d", "\u{1F601}"],
|
|
63
|
+
[";-a", "\u{1F628}"],
|
|
64
|
+
[";-s", "\u{1F633}"],
|
|
65
|
+
[";-x", "\u{1F910}"],
|
|
66
|
+
["p-(", "\u{1F623}"],
|
|
67
|
+
["--b", "\u{1F613}"],
|
|
68
|
+
["x-)", "\u{1F60E}"],
|
|
69
|
+
["b-)", "\u{1F60E}"],
|
|
70
|
+
["8-)", "\u{1F913}"],
|
|
71
|
+
["|-)", "\u{1F634}"],
|
|
72
|
+
["&-(", "\u{1F630}"],
|
|
73
|
+
[">-|", "\u{1F624}"],
|
|
74
|
+
["$-)", "\u{1F911}"],
|
|
75
|
+
["B-)", "\u{1F60E}"],
|
|
76
|
+
[";-)", "\u{1F609}"],
|
|
77
|
+
["O_O", "\u{1F633}"],
|
|
78
|
+
["o_o", "\u{1F633}"],
|
|
79
|
+
["T_T", "\u{1F62D}"],
|
|
80
|
+
["T.T", "\u{1F62D}"],
|
|
81
|
+
["^_^", "\u{1F60A}"],
|
|
82
|
+
["*_*", "\u{1F929}"],
|
|
83
|
+
["-_-", "\u{1F611}"],
|
|
84
|
+
["~_~", "\u{1F634}"],
|
|
85
|
+
["o.O", "\u{1F928}"],
|
|
86
|
+
["O.O", "\u{1F633}"],
|
|
87
|
+
["</3", "\u{1F494}"],
|
|
88
|
+
// 2-character
|
|
89
|
+
[":>", "\u{1F604}"],
|
|
90
|
+
[":D", "\u{1F604}"],
|
|
91
|
+
[":)", "\u{1F642}"],
|
|
92
|
+
[":(", "\u{1F61E}"],
|
|
93
|
+
[";)", "\u{1F609}"],
|
|
94
|
+
[":P", "\u{1F61B}"],
|
|
95
|
+
[":p", "\u{1F61B}"],
|
|
96
|
+
[":O", "\u{1F62E}"],
|
|
97
|
+
[":o", "\u{1F62E}"],
|
|
98
|
+
[":*", "\u{1F618}"],
|
|
99
|
+
[":|", "\u{1F610}"],
|
|
100
|
+
[":v", "\u{1F605}"],
|
|
101
|
+
[":3", "\u{1F63A}"],
|
|
102
|
+
["=)", "\u{1F642}"],
|
|
103
|
+
["=(", "\u{1F61E}"],
|
|
104
|
+
["=P", "\u{1F61C}"],
|
|
105
|
+
["=p", "\u{1F61C}"],
|
|
106
|
+
["=*", "\u{1F617}"],
|
|
107
|
+
["=D", "\u{1F601}"],
|
|
108
|
+
["^^", "\u{1F60A}"],
|
|
109
|
+
["<3", "\u2764\uFE0F"],
|
|
110
|
+
["B)", "\u{1F60E}"],
|
|
111
|
+
["8)", "\u{1F60E}"]
|
|
112
|
+
// ['XD', '😆'],
|
|
113
|
+
// ['xD', '😆'],
|
|
114
|
+
];
|
|
115
|
+
const EMOTICON_REGEX = new RegExp(
|
|
116
|
+
"(?<=^|\\s)(?:" + EMOTICON_MAP.map(([e]) => e.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")).join(
|
|
117
|
+
"|"
|
|
118
|
+
) + ")(?=\\s|$)",
|
|
119
|
+
"g"
|
|
120
|
+
);
|
|
121
|
+
const EMOTICON_LOOKUP = new Map(EMOTICON_MAP);
|
|
122
|
+
const URL_LIKE = /https?:\/\/\S+|[-\w.]{2,256}\.[a-z]{2,7}\S*/gi;
|
|
123
|
+
function replaceEmoticons(html) {
|
|
124
|
+
if (!html) return html;
|
|
125
|
+
return html.split(/(<[^>]*>)/).map((part, i) => {
|
|
126
|
+
if (i % 2 !== 0) return part;
|
|
127
|
+
return part.replace(URL_LIKE, "\0$&\0").split("\0").map(
|
|
128
|
+
(seg, j) => j % 2 === 0 ? seg.replace(EMOTICON_REGEX, (m) => EMOTICON_LOOKUP.get(m) ?? m) : seg
|
|
129
|
+
).join("");
|
|
130
|
+
}).join("");
|
|
131
|
+
}
|
|
132
|
+
export {
|
|
133
|
+
replaceEmoticons
|
|
134
|
+
};
|
package/utils/export.js
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
function asKeyList(value) {
|
|
2
|
+
return Array.isArray(value) ? value : [];
|
|
3
|
+
}
|
|
4
|
+
function normalizeFieldOverride(raw) {
|
|
5
|
+
if (Array.isArray(raw)) return { items: raw, hiddenKeys: [] };
|
|
6
|
+
if (raw && Array.isArray(raw.items)) {
|
|
7
|
+
return {
|
|
8
|
+
items: raw.items,
|
|
9
|
+
hiddenKeys: asKeyList(raw.hiddenKeys)
|
|
10
|
+
};
|
|
11
|
+
}
|
|
12
|
+
return null;
|
|
13
|
+
}
|
|
14
|
+
function mergeFieldOrder(rawOverride, liveDefaultKeys) {
|
|
15
|
+
const override = normalizeFieldOverride(rawOverride);
|
|
16
|
+
const defaultKeys = asKeyList(liveDefaultKeys);
|
|
17
|
+
if (!override) return defaultKeys;
|
|
18
|
+
const known = /* @__PURE__ */ new Set([...override.items, ...override.hiddenKeys]);
|
|
19
|
+
const kept = override.items.filter((key) => defaultKeys.includes(key));
|
|
20
|
+
const added = defaultKeys.filter((key) => !known.has(key));
|
|
21
|
+
return [...kept, ...added];
|
|
22
|
+
}
|
|
23
|
+
function toFieldOverride(shownKeys, hiddenByKey) {
|
|
24
|
+
return {
|
|
25
|
+
items: asKeyList(shownKeys),
|
|
26
|
+
hiddenKeys: Object.keys(hiddenByKey || {}).filter((k) => hiddenByKey[k])
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
export {
|
|
30
|
+
mergeFieldOrder,
|
|
31
|
+
normalizeFieldOverride,
|
|
32
|
+
toFieldOverride
|
|
33
|
+
};
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { allUser as getAllUsers } from "../api/user.js";
|
|
2
|
+
const handleSearchUsers = async (keyword) => {
|
|
3
|
+
if (keyword.length < 2) return [];
|
|
4
|
+
const { data } = await getAllUsers({ keyword });
|
|
5
|
+
return data?.list?.map((user) => ({
|
|
6
|
+
value: user._id,
|
|
7
|
+
label: user.name
|
|
8
|
+
})) ?? [];
|
|
9
|
+
};
|
package/utils/filter.js
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
const toOptionId = (value) => {
|
|
2
|
+
if (value && typeof value === "object") {
|
|
3
|
+
return value.value?.toString();
|
|
4
|
+
}
|
|
5
|
+
return value?.toString();
|
|
6
|
+
};
|
|
7
|
+
const isEmptyRuleValue = (value) => {
|
|
8
|
+
if (value === null || value === void 0 || value === "") return true;
|
|
9
|
+
if (Array.isArray(value)) return value.length === 0;
|
|
10
|
+
if (value instanceof Date) return false;
|
|
11
|
+
if (typeof value === "object") {
|
|
12
|
+
return Object.values(value).every(
|
|
13
|
+
(item) => item === null || item === void 0 || item === ""
|
|
14
|
+
);
|
|
15
|
+
}
|
|
16
|
+
return false;
|
|
17
|
+
};
|
|
18
|
+
const SYSTEM_FILTER_FIELD_KEYS = /* @__PURE__ */ new Set(["ticket_form_id"]);
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
import imageCompression from "browser-image-compression";
|
|
2
|
+
const QUALITY_FLOOR = 0.5;
|
|
3
|
+
const DIMENSION_FLOOR = 0.7;
|
|
4
|
+
const QUALITY_STEPS = [
|
|
5
|
+
0.9,
|
|
6
|
+
0.85,
|
|
7
|
+
0.8,
|
|
8
|
+
0.75,
|
|
9
|
+
0.7,
|
|
10
|
+
0.65,
|
|
11
|
+
0.6,
|
|
12
|
+
0.55,
|
|
13
|
+
QUALITY_FLOOR
|
|
14
|
+
];
|
|
15
|
+
const DIMENSION_STEPS = [0.95, 0.9, 0.85, 0.8, 0.75, DIMENSION_FLOOR];
|
|
16
|
+
const ALPHA_SAMPLE_TARGET = 1e5;
|
|
17
|
+
const MAX_OUTPUT_EDGE = 2048;
|
|
18
|
+
const ALPHA_CAPABLE_TYPES = ["image/png", "image/webp"];
|
|
19
|
+
async function detectTransparency(file) {
|
|
20
|
+
if (!ALPHA_CAPABLE_TYPES.includes(file.type)) return false;
|
|
21
|
+
try {
|
|
22
|
+
const bitmap = await createImageBitmap(file);
|
|
23
|
+
const canvas = document.createElement("canvas");
|
|
24
|
+
canvas.width = bitmap.width;
|
|
25
|
+
canvas.height = bitmap.height;
|
|
26
|
+
const ctx = canvas.getContext("2d");
|
|
27
|
+
ctx.drawImage(bitmap, 0, 0);
|
|
28
|
+
bitmap.close();
|
|
29
|
+
const { data } = ctx.getImageData(0, 0, canvas.width, canvas.height);
|
|
30
|
+
const pixelCount = data.length / 4;
|
|
31
|
+
const stride = Math.max(1, Math.floor(pixelCount / ALPHA_SAMPLE_TARGET));
|
|
32
|
+
for (let p = 0; p < pixelCount; p += stride) {
|
|
33
|
+
if (data[p * 4 + 3] < 255) return true;
|
|
34
|
+
}
|
|
35
|
+
return false;
|
|
36
|
+
} catch {
|
|
37
|
+
return true;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
async function getLargestEdge(file) {
|
|
41
|
+
try {
|
|
42
|
+
const bitmap = await createImageBitmap(file);
|
|
43
|
+
const edge = Math.max(bitmap.width, bitmap.height);
|
|
44
|
+
bitmap.close();
|
|
45
|
+
return edge;
|
|
46
|
+
} catch {
|
|
47
|
+
return 0;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
function normalizeOutput(file, type) {
|
|
51
|
+
const ext = type === "image/png" ? "png" : "jpg";
|
|
52
|
+
const base = (file.name || "image").replace(/\.[^.]+$/, "");
|
|
53
|
+
const newName = `${base}.${ext}`;
|
|
54
|
+
if (file instanceof File && file.name === newName && file.type === type) {
|
|
55
|
+
return file;
|
|
56
|
+
}
|
|
57
|
+
return new File([file], newName, { type });
|
|
58
|
+
}
|
|
59
|
+
async function flattenOnWhite(file) {
|
|
60
|
+
const bitmap = await createImageBitmap(file);
|
|
61
|
+
const canvas = document.createElement("canvas");
|
|
62
|
+
canvas.width = bitmap.width;
|
|
63
|
+
canvas.height = bitmap.height;
|
|
64
|
+
const ctx = canvas.getContext("2d");
|
|
65
|
+
ctx.fillStyle = "#ffffff";
|
|
66
|
+
ctx.fillRect(0, 0, canvas.width, canvas.height);
|
|
67
|
+
ctx.drawImage(bitmap, 0, 0);
|
|
68
|
+
bitmap.close();
|
|
69
|
+
const blob = await new Promise(
|
|
70
|
+
(resolve) => canvas.toBlob(resolve, "image/jpeg", 0.92)
|
|
71
|
+
);
|
|
72
|
+
if (!blob) throw new Error("canvas.toBlob returned null");
|
|
73
|
+
const base = (file.name || "image").replace(/\.[^.]+$/, "");
|
|
74
|
+
return new File([blob], `${base}.jpg`, { type: "image/jpeg" });
|
|
75
|
+
}
|
|
76
|
+
async function compressOnce(file, { quality, maxWidthOrHeight, fileType }) {
|
|
77
|
+
const options = {
|
|
78
|
+
maxSizeMB: Number.MAX_SAFE_INTEGER,
|
|
79
|
+
initialQuality: quality,
|
|
80
|
+
useWebWorker: true,
|
|
81
|
+
preserveExif: false,
|
|
82
|
+
fileType,
|
|
83
|
+
alwaysKeepResolution: maxWidthOrHeight == null
|
|
84
|
+
};
|
|
85
|
+
if (maxWidthOrHeight != null) options.maxWidthOrHeight = maxWidthOrHeight;
|
|
86
|
+
const out = await imageCompression(file, options);
|
|
87
|
+
return normalizeOutput(out, fileType);
|
|
88
|
+
}
|
|
89
|
+
async function compressImageForChannel(file, { maxSizeBytes, maxEdge = MAX_OUTPUT_EDGE }) {
|
|
90
|
+
const originalSize = file.size;
|
|
91
|
+
let working = file;
|
|
92
|
+
const largestEdge = await getLargestEdge(working);
|
|
93
|
+
const needsEdgeReduce = largestEdge > 0 && maxEdge > 0 && largestEdge > maxEdge;
|
|
94
|
+
const withinSize = !maxSizeBytes || working.size <= maxSizeBytes;
|
|
95
|
+
const isSafeFormat = working.type === "image/jpeg" || working.type === "image/png";
|
|
96
|
+
if (isSafeFormat && withinSize && !needsEdgeReduce) {
|
|
97
|
+
return {
|
|
98
|
+
file: working,
|
|
99
|
+
wasCompressed: working !== file,
|
|
100
|
+
stillTooLarge: false,
|
|
101
|
+
originalSize,
|
|
102
|
+
finalSize: working.size
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
let outputType = "image/jpeg";
|
|
106
|
+
if (await detectTransparency(working)) {
|
|
107
|
+
try {
|
|
108
|
+
working = await flattenOnWhite(working);
|
|
109
|
+
} catch {
|
|
110
|
+
outputType = "image/png";
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
const measuredEdge = largestEdge > 0 ? largestEdge : maxEdge;
|
|
114
|
+
const baseEdge = needsEdgeReduce ? maxEdge : measuredEdge;
|
|
115
|
+
const fits = (size) => !maxSizeBytes || size <= maxSizeBytes;
|
|
116
|
+
let smallest = working;
|
|
117
|
+
try {
|
|
118
|
+
for (const quality of QUALITY_STEPS) {
|
|
119
|
+
const out = await compressOnce(working, {
|
|
120
|
+
quality,
|
|
121
|
+
maxWidthOrHeight: baseEdge > 0 ? baseEdge : void 0,
|
|
122
|
+
fileType: outputType
|
|
123
|
+
});
|
|
124
|
+
if (out.size < smallest.size) smallest = out;
|
|
125
|
+
if (fits(out.size)) {
|
|
126
|
+
return {
|
|
127
|
+
file: out,
|
|
128
|
+
wasCompressed: true,
|
|
129
|
+
stillTooLarge: false,
|
|
130
|
+
originalSize,
|
|
131
|
+
finalSize: out.size
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
if (baseEdge > 0) {
|
|
136
|
+
for (const scale of DIMENSION_STEPS) {
|
|
137
|
+
const out = await compressOnce(working, {
|
|
138
|
+
quality: QUALITY_FLOOR,
|
|
139
|
+
maxWidthOrHeight: Math.round(baseEdge * scale),
|
|
140
|
+
fileType: outputType
|
|
141
|
+
});
|
|
142
|
+
if (out.size < smallest.size) smallest = out;
|
|
143
|
+
if (fits(out.size)) {
|
|
144
|
+
return {
|
|
145
|
+
file: out,
|
|
146
|
+
wasCompressed: true,
|
|
147
|
+
stillTooLarge: false,
|
|
148
|
+
originalSize,
|
|
149
|
+
finalSize: out.size
|
|
150
|
+
};
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
} catch {
|
|
155
|
+
}
|
|
156
|
+
return {
|
|
157
|
+
file: smallest,
|
|
158
|
+
wasCompressed: smallest !== file,
|
|
159
|
+
stillTooLarge: maxSizeBytes ? smallest.size > maxSizeBytes : false,
|
|
160
|
+
originalSize,
|
|
161
|
+
finalSize: smallest.size
|
|
162
|
+
};
|
|
163
|
+
}
|
|
164
|
+
export {
|
|
165
|
+
DIMENSION_FLOOR,
|
|
166
|
+
MAX_OUTPUT_EDGE,
|
|
167
|
+
QUALITY_FLOOR,
|
|
168
|
+
compressImageForChannel
|
|
169
|
+
};
|
package/utils/leads.js
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { UserRole } from "../constants/user.js";
|
|
2
|
+
import { getUser } from "./auth.js";
|
|
3
|
+
const isLeadEditableByUser = (lead) => {
|
|
4
|
+
const user = getUser();
|
|
5
|
+
if (!lead || !user) return false;
|
|
6
|
+
if ([UserRole.ADMINISTRATOR, UserRole.SUPERVISOR].includes(user.role)) {
|
|
7
|
+
return true;
|
|
8
|
+
}
|
|
9
|
+
return String(user._id) === String(lead.lead_owner_id);
|
|
10
|
+
};
|
|
11
|
+
export {
|
|
12
|
+
isLeadEditableByUser
|
|
13
|
+
};
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
const fetchMicrolinkPreview = async (url) => {
|
|
2
|
+
const fullUrl = url?.startsWith("http") ? url : `https://${url}`;
|
|
3
|
+
const response = await fetch(
|
|
4
|
+
`https://api.microlink.io?url=${encodeURIComponent(fullUrl)}`
|
|
5
|
+
);
|
|
6
|
+
if (!response.ok) {
|
|
7
|
+
throw new Error(`Microlink API error: ${response.status}`);
|
|
8
|
+
}
|
|
9
|
+
const json = await response.json();
|
|
10
|
+
return json?.data ?? null;
|
|
11
|
+
};
|
|
12
|
+
const LINK_PREVIEW_QUERY_OPTIONS = {
|
|
13
|
+
staleTime: Infinity,
|
|
14
|
+
gcTime: 30 * 60 * 1e3,
|
|
15
|
+
retry: 1
|
|
16
|
+
};
|
|
17
|
+
export {
|
|
18
|
+
LINK_PREVIEW_QUERY_OPTIONS,
|
|
19
|
+
fetchMicrolinkPreview
|
|
20
|
+
};
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
function eqId(a, b) {
|
|
2
|
+
if (a == null || b == null) return false;
|
|
3
|
+
return Number(a) === Number(b);
|
|
4
|
+
}
|
|
5
|
+
function normalizeNode(n) {
|
|
6
|
+
return {
|
|
7
|
+
...n,
|
|
8
|
+
id: Number(n.id),
|
|
9
|
+
parent_id: Number(n.parent_id) || 0,
|
|
10
|
+
path: n.path || String(n.id)
|
|
11
|
+
};
|
|
12
|
+
}
|
|
13
|
+
function buildTree(list) {
|
|
14
|
+
if (!list?.length) return [];
|
|
15
|
+
const nodes = list.map(normalizeNode);
|
|
16
|
+
const idSet = new Set(nodes.map((n) => n.id));
|
|
17
|
+
const ROOT = 0;
|
|
18
|
+
const byParent = /* @__PURE__ */ new Map();
|
|
19
|
+
for (const n of nodes) {
|
|
20
|
+
const parentKey = n.parent_id === 0 || !idSet.has(n.parent_id) ? ROOT : n.parent_id;
|
|
21
|
+
const arr = byParent.get(parentKey) || [];
|
|
22
|
+
arr.push(n);
|
|
23
|
+
byParent.set(parentKey, arr);
|
|
24
|
+
}
|
|
25
|
+
const make = (parentId) => {
|
|
26
|
+
const children = byParent.get(parentId) || [];
|
|
27
|
+
return children.slice().sort((a, b) => a.name.localeCompare(b.name, "vi")).map((n) => {
|
|
28
|
+
const kids = make(Number(n.id));
|
|
29
|
+
return { ...n, children: kids.length > 0 ? kids : void 0 };
|
|
30
|
+
});
|
|
31
|
+
};
|
|
32
|
+
return make(ROOT);
|
|
33
|
+
}
|
|
34
|
+
function findNode(list, id) {
|
|
35
|
+
if (!list?.length || id == null) return null;
|
|
36
|
+
return list.find((n) => eqId(n.id, id)) || null;
|
|
37
|
+
}
|
|
38
|
+
function getDescendants(list, id) {
|
|
39
|
+
if (!list?.length || id == null) return [];
|
|
40
|
+
const childrenByParent = /* @__PURE__ */ new Map();
|
|
41
|
+
for (const n of list) {
|
|
42
|
+
const p = Number(n.parent_id);
|
|
43
|
+
if (!childrenByParent.has(p)) childrenByParent.set(p, []);
|
|
44
|
+
childrenByParent.get(p).push(n);
|
|
45
|
+
}
|
|
46
|
+
const out = [];
|
|
47
|
+
const seen = /* @__PURE__ */ new Set();
|
|
48
|
+
const stack = [...childrenByParent.get(Number(id)) || []];
|
|
49
|
+
while (stack.length) {
|
|
50
|
+
const node = stack.pop();
|
|
51
|
+
const nid = Number(node.id);
|
|
52
|
+
if (seen.has(nid)) continue;
|
|
53
|
+
seen.add(nid);
|
|
54
|
+
out.push(node);
|
|
55
|
+
const kids = childrenByParent.get(nid);
|
|
56
|
+
if (kids) stack.push(...kids);
|
|
57
|
+
}
|
|
58
|
+
return out;
|
|
59
|
+
}
|
|
60
|
+
function getPathIds(node) {
|
|
61
|
+
if (!node) return [];
|
|
62
|
+
const raw = node.path || String(node.id);
|
|
63
|
+
return raw.split(",").map((s) => Number(s)).filter((n) => !Number.isNaN(n));
|
|
64
|
+
}
|
|
65
|
+
function getPathLabels(list, node, separator = " \u203A ") {
|
|
66
|
+
if (!node) return "";
|
|
67
|
+
return getPathIds(node).map((id) => findNode(list, id)?.name).filter(Boolean).join(separator);
|
|
68
|
+
}
|
|
69
|
+
function getGroupDisplayName(groupId, list, fallbackName = "") {
|
|
70
|
+
if (groupId == null) return fallbackName || "";
|
|
71
|
+
if (!list?.length) return fallbackName || "";
|
|
72
|
+
const node = findNode(list, groupId);
|
|
73
|
+
if (!node) return fallbackName || "";
|
|
74
|
+
return getPathLabels(list, node);
|
|
75
|
+
}
|
|
76
|
+
function getGroupNodeName(groupId, list, fallbackName = "") {
|
|
77
|
+
if (groupId == null) return fallbackName || "";
|
|
78
|
+
if (!list?.length) return fallbackName || "";
|
|
79
|
+
const node = findNode(list, groupId);
|
|
80
|
+
return node?.name || fallbackName || "";
|
|
81
|
+
}
|
|
82
|
+
function filterBySearch(list, query) {
|
|
83
|
+
if (!query?.trim()) return null;
|
|
84
|
+
const q = query.trim().toLowerCase();
|
|
85
|
+
const matched = list.filter((n) => n.name?.toLowerCase().includes(q));
|
|
86
|
+
if (!matched.length) return /* @__PURE__ */ new Set();
|
|
87
|
+
const visible = /* @__PURE__ */ new Set();
|
|
88
|
+
for (const node of matched) {
|
|
89
|
+
visible.add(Number(node.id));
|
|
90
|
+
for (const id of getPathIds(node)) visible.add(id);
|
|
91
|
+
for (const child of getDescendants(list, node.id)) {
|
|
92
|
+
visible.add(Number(child.id));
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
return visible;
|
|
96
|
+
}
|
|
97
|
+
export {
|
|
98
|
+
buildTree,
|
|
99
|
+
eqId,
|
|
100
|
+
filterBySearch,
|
|
101
|
+
findNode,
|
|
102
|
+
getDescendants,
|
|
103
|
+
getGroupDisplayName,
|
|
104
|
+
getGroupNodeName,
|
|
105
|
+
getPathIds,
|
|
106
|
+
getPathLabels,
|
|
107
|
+
normalizeNode
|
|
108
|
+
};
|