mirror of
https://github.com/docmost/docmost.git
synced 2026-08-22 20:02:11 +10:00
Merge branch 'main' into confluence-importer
This commit is contained in:
@@ -10,7 +10,12 @@ const api: AxiosInstance = axios.create({
|
||||
api.interceptors.response.use(
|
||||
(response) => {
|
||||
// we need the response headers for these endpoints
|
||||
const exemptEndpoints = ["/api/pages/export", "/api/spaces/export"];
|
||||
const exemptEndpoints = [
|
||||
"/api/pages/export",
|
||||
"/api/spaces/export",
|
||||
"/api/docx-export",
|
||||
"/api/bases/export-csv",
|
||||
];
|
||||
if (response.request.responseURL) {
|
||||
const path = new URL(response.request.responseURL)?.pathname;
|
||||
if (path && exemptEndpoints.includes(path)) {
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
import { isAxiosError } from "axios";
|
||||
|
||||
export function getApiErrorMessage(
|
||||
error: unknown,
|
||||
fallback = "An error occurred",
|
||||
): string {
|
||||
if (isAxiosError(error)) {
|
||||
const message = error.response?.data?.message;
|
||||
if (Array.isArray(message)) {
|
||||
const joined = message.filter(Boolean).join(", ");
|
||||
if (joined) return joined;
|
||||
} else if (typeof message === "string" && message.trim()) {
|
||||
return message;
|
||||
}
|
||||
}
|
||||
return fallback;
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
import { format as dateFnsFormat, type Locale } from "date-fns";
|
||||
import {
|
||||
de,
|
||||
enUS,
|
||||
es,
|
||||
fr,
|
||||
it,
|
||||
ja,
|
||||
ko,
|
||||
nl,
|
||||
ptBR,
|
||||
ru,
|
||||
uk,
|
||||
zhCN,
|
||||
} from "date-fns/locale";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import i18n from "@/i18n.ts";
|
||||
|
||||
const LOCALE_MAP: Record<string, Locale> = {
|
||||
"de-DE": de,
|
||||
"en-US": enUS,
|
||||
"es-ES": es,
|
||||
"fr-FR": fr,
|
||||
"it-IT": it,
|
||||
"ja-JP": ja,
|
||||
"ko-KR": ko,
|
||||
"nl-NL": nl,
|
||||
"pt-BR": ptBR,
|
||||
"ru-RU": ru,
|
||||
"uk-UA": uk,
|
||||
"zh-CN": zhCN,
|
||||
};
|
||||
|
||||
export function getDateFnsLocale(language?: string): Locale {
|
||||
const lang = language ?? i18n.language ?? "en-US";
|
||||
return LOCALE_MAP[lang] ?? LOCALE_MAP[lang.split("-")[0]] ?? enUS;
|
||||
}
|
||||
|
||||
export function useDateFnsLocale(): Locale {
|
||||
const { i18n: instance } = useTranslation();
|
||||
return getDateFnsLocale(instance.language);
|
||||
}
|
||||
|
||||
function isEnglishLocale(locale: Locale): boolean {
|
||||
return locale.code === "en-US" || locale.code?.startsWith("en") === true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Picks `enUSPattern` for the English locale and `localizedPattern` for every
|
||||
* other locale. Keeps existing en-US output byte-identical while letting other
|
||||
* languages use date-fns localized format tokens (P, PP, p, PPp, …).
|
||||
*/
|
||||
export function formatLocalized(
|
||||
date: Date | number | string,
|
||||
enUSPattern: string,
|
||||
localizedPattern: string,
|
||||
locale?: Locale,
|
||||
): string {
|
||||
const effective = locale ?? getDateFnsLocale();
|
||||
const pattern = isEnglishLocale(effective) ? enUSPattern : localizedPattern;
|
||||
return dateFnsFormat(new Date(date), pattern, { locale: effective });
|
||||
}
|
||||
@@ -1 +1,2 @@
|
||||
export * from "./utils";
|
||||
export * from "./api-error";
|
||||
|
||||
@@ -1,17 +1,25 @@
|
||||
import { formatDistanceStrict } from "date-fns";
|
||||
import { format, isToday, isYesterday } from "date-fns";
|
||||
import { formatDistanceStrict, isToday, isYesterday } from "date-fns";
|
||||
import i18n from "@/i18n.ts";
|
||||
import { formatLocalized, getDateFnsLocale } from "@/lib/date-locale.ts";
|
||||
|
||||
export function timeAgo(date: Date) {
|
||||
return formatDistanceStrict(new Date(date), new Date(), { addSuffix: true });
|
||||
return formatDistanceStrict(new Date(date), new Date(), {
|
||||
addSuffix: true,
|
||||
locale: getDateFnsLocale(),
|
||||
});
|
||||
}
|
||||
|
||||
export function formattedDate(date: Date) {
|
||||
const locale = getDateFnsLocale();
|
||||
if (isToday(date)) {
|
||||
return i18n.t("Today, {{time}}", { time: format(date, "h:mma") });
|
||||
return i18n.t("Today, {{time}}", {
|
||||
time: formatLocalized(date, "h:mma", "p", locale),
|
||||
});
|
||||
} else if (isYesterday(date)) {
|
||||
return i18n.t("Yesterday, {{time}}", { time: format(date, "h:mma") });
|
||||
return i18n.t("Yesterday, {{time}}", {
|
||||
time: formatLocalized(date, "h:mma", "p", locale),
|
||||
});
|
||||
} else {
|
||||
return format(date, "MMM dd, yyyy, h:mma");
|
||||
return formatLocalized(date, "MMM dd, yyyy, h:mma", "PPp", locale);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import { validate as isValidUUID } from "uuid";
|
||||
import { ActionIcon } from "@mantine/core";
|
||||
import { IconFileDescription } from "@tabler/icons-react";
|
||||
import { ReactNode } from "react";
|
||||
import { TFunction } from "i18next";
|
||||
@@ -87,9 +86,11 @@ export function capitalizeFirstChar(string: string) {
|
||||
export function getPageIcon(icon: string, size = 18): string | ReactNode {
|
||||
return (
|
||||
icon || (
|
||||
<ActionIcon variant="transparent" color="gray" size={size}>
|
||||
<IconFileDescription size={size} />
|
||||
</ActionIcon>
|
||||
<IconFileDescription
|
||||
size={size}
|
||||
color="var(--mantine-color-gray-6)"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user