mirror of
https://github.com/docmost/docmost.git
synced 2026-05-10 16:24:05 +08:00
Merge branch 'main' into perm-x
This commit is contained in:
@@ -1,20 +1,62 @@
|
||||
import api from "@/lib/api-client";
|
||||
import loadImage from "blueimp-load-image";
|
||||
import {
|
||||
AvatarIconType,
|
||||
IAttachment,
|
||||
} from "@/features/attachments/types/attachment.types.ts";
|
||||
|
||||
async function compressAndResizeIcon(
|
||||
file: File,
|
||||
type: AvatarIconType,
|
||||
): Promise<File> {
|
||||
const isPng = file.type === "image/png";
|
||||
|
||||
const { image: canvas } = await loadImage(file, {
|
||||
maxWidth: 300,
|
||||
maxHeight: 300,
|
||||
canvas: true,
|
||||
orientation: true,
|
||||
imageSmoothingQuality: "high",
|
||||
});
|
||||
|
||||
if (type === AvatarIconType.AVATAR || !isPng) {
|
||||
const ctx = (canvas as HTMLCanvasElement).getContext("2d")!;
|
||||
ctx.globalCompositeOperation = "destination-over";
|
||||
ctx.fillStyle = "#ffffff";
|
||||
ctx.fillRect(0, 0, canvas.width, canvas.height);
|
||||
ctx.globalCompositeOperation = "source-over";
|
||||
}
|
||||
|
||||
const outputType = isPng ? "image/png" : "image/jpeg";
|
||||
|
||||
return new Promise<File>((resolve, reject) => {
|
||||
(canvas as HTMLCanvasElement).toBlob(
|
||||
(blob) => {
|
||||
if (!blob) {
|
||||
reject(new Error("Failed to compress image"));
|
||||
return;
|
||||
}
|
||||
resolve(new File([blob], file.name, { type: outputType }));
|
||||
},
|
||||
outputType,
|
||||
isPng ? undefined : 0.85,
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
export async function uploadIcon(
|
||||
file: File,
|
||||
type: AvatarIconType,
|
||||
spaceId?: string,
|
||||
): Promise<IAttachment> {
|
||||
const processed = await compressAndResizeIcon(file, type);
|
||||
|
||||
const formData = new FormData();
|
||||
formData.append("type", type);
|
||||
if (spaceId) {
|
||||
formData.append("spaceId", spaceId);
|
||||
}
|
||||
formData.append("image", file);
|
||||
formData.append("image", processed);
|
||||
|
||||
return await api.post("/attachments/upload-image", formData, {
|
||||
headers: {
|
||||
|
||||
@@ -23,7 +23,7 @@ import {
|
||||
acceptInvitation,
|
||||
createWorkspace,
|
||||
} from "@/features/workspace/services/workspace-service.ts";
|
||||
import APP_ROUTE from "@/lib/app-route.ts";
|
||||
import APP_ROUTE, { getPostLoginRedirect } from "@/lib/app-route.ts";
|
||||
import { RESET } from "jotai/utils";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { isCloud } from "@/lib/config.ts";
|
||||
@@ -44,11 +44,11 @@ export default function useAuth() {
|
||||
|
||||
// Check if MFA is required
|
||||
if (response?.userHasMfa) {
|
||||
navigate(APP_ROUTE.AUTH.MFA_CHALLENGE);
|
||||
navigate(APP_ROUTE.AUTH.MFA_CHALLENGE + window.location.search);
|
||||
} else if (response?.requiresMfaSetup) {
|
||||
navigate(APP_ROUTE.AUTH.MFA_SETUP_REQUIRED);
|
||||
navigate(APP_ROUTE.AUTH.MFA_SETUP_REQUIRED + window.location.search);
|
||||
} else {
|
||||
navigate(APP_ROUTE.HOME);
|
||||
navigate(getPostLoginRedirect());
|
||||
}
|
||||
} catch (err) {
|
||||
setIsLoading(false);
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { useEffect } from "react";
|
||||
import useCurrentUser from "@/features/user/hooks/use-current-user.ts";
|
||||
import APP_ROUTE from "@/lib/app-route.ts";
|
||||
import { getPostLoginRedirect } from "@/lib/app-route.ts";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
|
||||
export function useRedirectIfAuthenticated() {
|
||||
@@ -9,7 +9,7 @@ export function useRedirectIfAuthenticated() {
|
||||
|
||||
useEffect(() => {
|
||||
if (data && data?.user) {
|
||||
navigate(APP_ROUTE.HOME);
|
||||
navigate(getPostLoginRedirect());
|
||||
}
|
||||
}, [isLoading, data]);
|
||||
}
|
||||
|
||||
@@ -16,7 +16,7 @@ import {
|
||||
import { useEffect, useRef, useState } from "react";
|
||||
import { Link, useParams } from "react-router-dom";
|
||||
import classes from "@/features/page/tree/styles/tree.module.css";
|
||||
import { ActionIcon, Box, Menu, rem } from "@mantine/core";
|
||||
import { ActionIcon, Box, Menu, rem, Text } from "@mantine/core";
|
||||
import {
|
||||
IconArrowRight,
|
||||
IconChevronDown,
|
||||
@@ -82,6 +82,7 @@ interface SpaceTreeProps {
|
||||
const openTreeNodesAtom = atom<OpenMap>({});
|
||||
|
||||
export default function SpaceTree({ spaceId, readOnly }: SpaceTreeProps) {
|
||||
const { t } = useTranslation();
|
||||
const { pageSlug } = useParams();
|
||||
const { data, setData, controllers } =
|
||||
useTreeMutation<TreeApi<SpaceTreeNode>>(spaceId);
|
||||
@@ -231,11 +232,18 @@ export default function SpaceTree({ spaceId, readOnly }: SpaceTreeProps) {
|
||||
};
|
||||
}, [setTreeApi]);
|
||||
|
||||
const filteredData = data.filter((node) => node?.spaceId === spaceId);
|
||||
|
||||
return (
|
||||
<div ref={mergedRef} className={classes.treeContainer}>
|
||||
{isDataLoaded && filteredData.length === 0 && (
|
||||
<Text size="xs" c="dimmed" py="xs" px="sm">
|
||||
{t("No pages yet")}
|
||||
</Text>
|
||||
)}
|
||||
{isRootReady && rootElement.current && (
|
||||
<Tree
|
||||
data={data.filter((node) => node?.spaceId === spaceId)}
|
||||
data={filteredData}
|
||||
disableDrag={readOnly}
|
||||
disableDrop={readOnly}
|
||||
disableEdit={readOnly}
|
||||
|
||||
Reference in New Issue
Block a user