blob: 996da7f6f942e5fa0e09b7581381a0259c75ddad [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import i18next from 'i18next';
import pattern from '@/common/pattern';
import { USER_AGENT_NAMES } from '@/common/constants';
import type * as Type from '@/common/interface';
const Diff = require('diff');
function thousandthDivision(num) {
const reg = /\d{1,3}(?=(\d{3})+$)/g;
return `${num}`.replace(reg, '$&,');
}
function formatCount($num: number): string {
let res = String($num);
if (!Number.isFinite($num)) {
res = '0';
} else if ($num < 10000) {
res = thousandthDivision($num);
} else if ($num < 1000000) {
res = `${Math.round($num / 100) / 10}k`;
} else if ($num >= 1000000) {
res = `${Math.round($num / 100000) / 10}m`;
}
return res;
}
function scrollToElementTop(element) {
if (!element) {
return;
}
const offset = 120;
const bodyRect = document.body.getBoundingClientRect().top;
const elementRect = element.getBoundingClientRect().top;
const elementPosition = elementRect - bodyRect;
const offsetPosition = elementPosition - offset;
window.scrollTo({
top: offsetPosition,
behavior: 'instant' as ScrollBehavior,
});
}
function scrollElementIntoView(element) {
if (!element) {
return;
}
element.scrollIntoView({
behavior: 'smooth',
block: 'center',
inline: 'center',
});
}
const scrollToDocTop = () => {
setTimeout(() => {
window.scrollTo({
top: 0,
left: 0,
behavior: 'instant' as ScrollBehavior,
});
});
};
const bgFadeOut = (el) => {
if (el && !el.classList.contains('bg-fade-out')) {
el.classList.add('bg-fade-out');
setTimeout(() => {
el.classList.remove('bg-fade-out');
}, 3200);
}
};
/**
* Extract user info from markdown
* @param markdown string
* @returns Array<{displayName: string, userName: string}>
*/
function matchedUsers(markdown) {
const globalReg = /\B@([\w|]+)/g;
const reg = /\B@([\w\\_\\.]+)/;
const users = markdown.match(globalReg);
if (!users) {
return [];
}
return users.map((user) => {
const matched = user.match(reg);
return {
userName: matched[1],
};
});
}
/**
* Identify user information from markdown
* @param markdown string
* @returns string
*/
function parseUserInfo(markdown) {
const globalReg = /\B@([\w\\_\\.\\-]+)/g;
return markdown.replace(globalReg, '[@$1](/users/$1)');
}
function parseEditMentionUser(markdown) {
const globalReg = /\[@([^\]]+)\]\([^)]+\)/g;
return markdown.replace(globalReg, '@$1');
}
function formatUptime(value) {
const t = i18next.t.bind(i18next);
const second = parseInt(value, 10);
if (second > 60 * 60 && second < 60 * 60 * 24) {
const hour = second / 3600;
return `${Math.floor(hour)} ${
hour > 1 ? t('dates.hours') : t('dates.hour')
}`;
}
if (second > 60 * 60 * 24) {
const day = second / 3600 / 24;
return `${Math.floor(day)} ${day > 1 ? t('dates.days') : t('dates.day')}`;
}
return `< 1 ${t('dates.hour')}`;
}
function escapeRemove(str: string) {
if (!str || typeof str !== 'string') return str;
let temp: HTMLDivElement | null = document.createElement('div');
temp.innerHTML = str;
const output = temp?.innerText || temp.textContent;
temp = null;
return output;
}
function handleFormError(
error: { list: Type.FieldError[] },
data: any,
keymap?: Array<{ from: string; to: string }>,
) {
if (error.list?.length > 0) {
error.list.forEach((item) => {
if (keymap?.length) {
const key = keymap.find((k) => k.from === item.error_field);
if (key) {
item.error_field = key.to;
}
}
const errorFieldObject = data[item.error_field];
if (errorFieldObject) {
errorFieldObject.isInvalid = true;
errorFieldObject.errorMsg = item.error_msg;
}
});
}
return data;
}
function escapeHtml(str: string) {
const tagsToReplace = {
'&': '&amp;',
'<': '&lt;',
'>': '&gt;',
'"': '&quot;',
"'": '&#39;',
'`': '&#96;',
};
return str.replace(/[&<>"'`]/g, (tag) => tagsToReplace[tag] || tag);
}
function formatDiffPart(part: any, className: string): string {
if (part.value.replace(/\n/g, '').length <= 0) {
if (part.value.match(/\n/g)?.length > 1) {
const value = part.value.replace(/\n/, '');
return `<span class="${className}"> </span><div><span class="${className}">${value.replace(
/\n/g,
' ',
)}</span></div>`;
}
return `<div><span class="${className}">${part.value.replace(
/\n/g,
' ',
)}</span></div>`;
}
return `<span class="${className}">${part.value}</span>`;
}
function diffText(newText: string, oldText?: string): string {
if (!newText) {
return '';
}
if (typeof oldText !== 'string') {
return escapeHtml(newText);
}
let result = [];
const diff = Diff.diffChars(escapeHtml(oldText), escapeHtml(newText));
result = diff.map((part) => {
if (part.added) {
return formatDiffPart(part, 'review-text-add');
}
if (part.removed) {
return formatDiffPart(part, 'review-text-delete text-decoration-none');
}
return part.value;
});
return result.join('');
}
function base64ToSvg(base64: string, svgClassName?: string) {
try {
// base64 to svg xml
const svgxml = atob(base64);
// svg add class
const parser = new DOMParser();
const doc = parser.parseFromString(svgxml, 'image/svg+xml');
const parseError = doc.querySelector('parsererror');
const svg = doc.querySelector('svg');
let str = '';
if (svg && !parseError) {
if (svgClassName) {
svg.setAttribute('class', svgClassName);
}
// svg.classList.add('me-2');
// transform svg to string
const serializer = new XMLSerializer();
str = serializer.serializeToString(doc);
}
return str;
} catch (error) {
return '';
}
}
// Determine whether the user is in WeChat or Enterprise WeChat or DingTalk, and return the corresponding type
function getUaType() {
const ua = navigator.userAgent.toLowerCase();
if (pattern.uaWeCom.test(ua)) {
return USER_AGENT_NAMES.WeCom;
}
if (pattern.uaWeChat.test(ua)) {
return USER_AGENT_NAMES.WeChat;
}
if (pattern.uaDingTalk.test(ua)) {
return USER_AGENT_NAMES.DingTalk;
}
return null;
}
function changeTheme(mode: 'default' | 'light' | 'dark' | 'system') {
const htmlTag = document.querySelector('html') as HTMLHtmlElement;
if (mode === 'system') {
const systemThemeQuery = window.matchMedia('(prefers-color-scheme: dark)');
if (systemThemeQuery.matches) {
htmlTag.setAttribute('data-bs-theme', 'dark');
} else {
htmlTag.setAttribute('data-bs-theme', 'light');
}
} else {
htmlTag.setAttribute('data-bs-theme', mode);
}
}
function isDarkTheme() {
const htmlTag = document.querySelector('html') as HTMLHtmlElement;
return htmlTag.getAttribute('data-bs-theme') === 'dark';
}
export {
thousandthDivision,
formatCount,
scrollElementIntoView,
scrollToElementTop,
scrollToDocTop,
bgFadeOut,
matchedUsers,
parseUserInfo,
parseEditMentionUser,
formatUptime,
escapeRemove,
handleFormError,
diffText,
base64ToSvg,
getUaType,
changeTheme,
isDarkTheme,
};