mirror of
https://github.com/docmost/docmost.git
synced 2026-05-07 06:23:06 +08:00
refactor
This commit is contained in:
@@ -1,3 +1,4 @@
|
|||||||
|
import { useMemo } from "react";
|
||||||
import {
|
import {
|
||||||
useQuery,
|
useQuery,
|
||||||
useInfiniteQuery,
|
useInfiniteQuery,
|
||||||
@@ -8,10 +9,10 @@ import {
|
|||||||
addFavorite,
|
addFavorite,
|
||||||
removeFavorite,
|
removeFavorite,
|
||||||
getFavorites,
|
getFavorites,
|
||||||
|
getFavoriteIds,
|
||||||
ToggleFavoriteParams,
|
ToggleFavoriteParams,
|
||||||
} from "../services/favorite-service";
|
} from "../services/favorite-service";
|
||||||
import { IPagination } from "@/lib/types.ts";
|
import { FavoriteType } from "../types/favorite.types";
|
||||||
import { IFavorite, FavoriteType } from "../types/favorite.types";
|
|
||||||
|
|
||||||
export function useFavoritesQuery(type?: FavoriteType) {
|
export function useFavoritesQuery(type?: FavoriteType) {
|
||||||
return useInfiniteQuery({
|
return useInfiniteQuery({
|
||||||
@@ -26,23 +27,21 @@ export function useFavoritesQuery(type?: FavoriteType) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function useFavoriteIds(type: FavoriteType): Set<string> {
|
export function useFavoriteIds(type: FavoriteType): Set<string> {
|
||||||
const { data } = useQuery<IPagination<IFavorite>>({
|
const { data } = useQuery({
|
||||||
queryKey: ["favorite-ids", type],
|
queryKey: ["favorite-ids", type],
|
||||||
queryFn: () => getFavorites({ type, limit: 50 }),
|
queryFn: () => getFavoriteIds(type),
|
||||||
refetchOnMount: true,
|
refetchOnMount: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
const ids = new Set<string>();
|
const items = data?.items;
|
||||||
if (data?.items) {
|
return useMemo(() => new Set(items ?? []), [items]);
|
||||||
for (const fav of data.items) {
|
}
|
||||||
let id: string | undefined;
|
|
||||||
if (type === "page") id = fav.pageId;
|
function getEntityId(variables: ToggleFavoriteParams): string | undefined {
|
||||||
else if (type === "space") id = fav.spaceId;
|
if (variables.type === "page") return variables.pageId;
|
||||||
else if (type === "template") id = fav.templateId;
|
if (variables.type === "space") return variables.spaceId;
|
||||||
if (id) ids.add(id);
|
if (variables.type === "template") return variables.templateId;
|
||||||
}
|
return undefined;
|
||||||
}
|
|
||||||
return ids;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function useAddFavoriteMutation() {
|
export function useAddFavoriteMutation() {
|
||||||
@@ -51,9 +50,17 @@ export function useAddFavoriteMutation() {
|
|||||||
return useMutation<void, Error, ToggleFavoriteParams>({
|
return useMutation<void, Error, ToggleFavoriteParams>({
|
||||||
mutationFn: (data) => addFavorite(data),
|
mutationFn: (data) => addFavorite(data),
|
||||||
onSuccess: (_result, variables) => {
|
onSuccess: (_result, variables) => {
|
||||||
queryClient.invalidateQueries({
|
const entityId = getEntityId(variables);
|
||||||
queryKey: ["favorite-ids", variables.type],
|
if (entityId) {
|
||||||
});
|
queryClient.setQueryData(
|
||||||
|
["favorite-ids", variables.type],
|
||||||
|
(old: { items: string[]; meta: any } | undefined) => {
|
||||||
|
if (!old) return old;
|
||||||
|
if (old.items.includes(entityId)) return old;
|
||||||
|
return { ...old, items: [...old.items, entityId] };
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
queryClient.invalidateQueries({
|
queryClient.invalidateQueries({
|
||||||
queryKey: ["favorites", variables.type],
|
queryKey: ["favorites", variables.type],
|
||||||
});
|
});
|
||||||
@@ -67,9 +74,16 @@ export function useRemoveFavoriteMutation() {
|
|||||||
return useMutation<void, Error, ToggleFavoriteParams>({
|
return useMutation<void, Error, ToggleFavoriteParams>({
|
||||||
mutationFn: (data) => removeFavorite(data),
|
mutationFn: (data) => removeFavorite(data),
|
||||||
onSuccess: (_result, variables) => {
|
onSuccess: (_result, variables) => {
|
||||||
queryClient.invalidateQueries({
|
const entityId = getEntityId(variables);
|
||||||
queryKey: ["favorite-ids", variables.type],
|
if (entityId) {
|
||||||
});
|
queryClient.setQueryData(
|
||||||
|
["favorite-ids", variables.type],
|
||||||
|
(old: { items: string[]; meta: any } | undefined) => {
|
||||||
|
if (!old) return old;
|
||||||
|
return { ...old, items: old.items.filter((id) => id !== entityId) };
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
queryClient.invalidateQueries({
|
queryClient.invalidateQueries({
|
||||||
queryKey: ["favorites", variables.type],
|
queryKey: ["favorites", variables.type],
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -21,6 +21,11 @@ export async function removeFavorite(
|
|||||||
await api.post("/favorites/remove", params);
|
await api.post("/favorites/remove", params);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function getFavoriteIds(type: FavoriteType): Promise<IPagination<string>> {
|
||||||
|
const req = await api.post<IPagination<string>>("/favorites/ids", { type });
|
||||||
|
return req.data;
|
||||||
|
}
|
||||||
|
|
||||||
export async function getFavorites(params?: {
|
export async function getFavorites(params?: {
|
||||||
type?: FavoriteType;
|
type?: FavoriteType;
|
||||||
limit?: number;
|
limit?: number;
|
||||||
|
|||||||
@@ -16,6 +16,8 @@ import {
|
|||||||
IconPlus,
|
IconPlus,
|
||||||
IconSearch,
|
IconSearch,
|
||||||
IconSettings,
|
IconSettings,
|
||||||
|
IconStar,
|
||||||
|
IconStarFilled,
|
||||||
IconTrash,
|
IconTrash,
|
||||||
} from "@tabler/icons-react";
|
} from "@tabler/icons-react";
|
||||||
import {
|
import {
|
||||||
@@ -43,6 +45,11 @@ import PageImportModal from "@/features/page/components/page-import-modal.tsx";
|
|||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { SwitchSpace } from "./switch-space";
|
import { SwitchSpace } from "./switch-space";
|
||||||
import ExportModal from "@/components/common/export-modal";
|
import ExportModal from "@/components/common/export-modal";
|
||||||
|
import {
|
||||||
|
useFavoriteIds,
|
||||||
|
useAddFavoriteMutation,
|
||||||
|
useRemoveFavoriteMutation,
|
||||||
|
} from "@/features/favorite/queries/favorite-query";
|
||||||
import { mobileSidebarAtom } from "@/components/layouts/global/hooks/atoms/sidebar-atom.ts";
|
import { mobileSidebarAtom } from "@/components/layouts/global/hooks/atoms/sidebar-atom.ts";
|
||||||
import { useToggleSidebar } from "@/components/layouts/global/hooks/hooks/use-toggle-sidebar.ts";
|
import { useToggleSidebar } from "@/components/layouts/global/hooks/hooks/use-toggle-sidebar.ts";
|
||||||
import { searchSpotlight } from "@/features/search/constants";
|
import { searchSpotlight } from "@/features/search/constants";
|
||||||
@@ -56,7 +63,6 @@ export function SpaceSidebar() {
|
|||||||
const [mobileSidebarOpened] = useAtom(mobileSidebarAtom);
|
const [mobileSidebarOpened] = useAtom(mobileSidebarAtom);
|
||||||
const toggleMobileSidebar = useToggleSidebar(mobileSidebarAtom);
|
const toggleMobileSidebar = useToggleSidebar(mobileSidebarAtom);
|
||||||
|
|
||||||
|
|
||||||
const { spaceSlug } = useParams();
|
const { spaceSlug } = useParams();
|
||||||
const { data: space } = useGetSpaceBySlugQuery(spaceSlug);
|
const { data: space } = useGetSpaceBySlugQuery(spaceSlug);
|
||||||
|
|
||||||
@@ -82,7 +88,12 @@ export function SpaceSidebar() {
|
|||||||
marginBottom: 3,
|
marginBottom: 3,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Group gap={4} wrap="nowrap" justify="space-between" style={{ width: "100%" }}>
|
<Group
|
||||||
|
gap={4}
|
||||||
|
wrap="nowrap"
|
||||||
|
justify="space-between"
|
||||||
|
style={{ width: "100%" }}
|
||||||
|
>
|
||||||
<SwitchSpace
|
<SwitchSpace
|
||||||
spaceName={space?.name}
|
spaceName={space?.name}
|
||||||
spaceSlug={space?.slug}
|
spaceSlug={space?.slug}
|
||||||
@@ -241,6 +252,20 @@ function SpaceMenu({
|
|||||||
const unwatchMutation = useUnwatchSpaceMutation();
|
const unwatchMutation = useUnwatchSpaceMutation();
|
||||||
const isWatching = watchStatus?.watching ?? false;
|
const isWatching = watchStatus?.watching ?? false;
|
||||||
|
|
||||||
|
const favoriteIds = useFavoriteIds("space");
|
||||||
|
const addFavoriteMutation = useAddFavoriteMutation();
|
||||||
|
const removeFavoriteMutation = useRemoveFavoriteMutation();
|
||||||
|
const isFavorited = favoriteIds.has(spaceId);
|
||||||
|
|
||||||
|
const handleToggleFavorite = () => {
|
||||||
|
const params = { type: "space" as const, spaceId };
|
||||||
|
if (isFavorited) {
|
||||||
|
removeFavoriteMutation.mutate(params);
|
||||||
|
} else {
|
||||||
|
addFavoriteMutation.mutate(params);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const handleToggleWatch = () => {
|
const handleToggleWatch = () => {
|
||||||
if (isWatching) {
|
if (isWatching) {
|
||||||
unwatchMutation.mutate(spaceId);
|
unwatchMutation.mutate(spaceId);
|
||||||
@@ -265,6 +290,22 @@ function SpaceMenu({
|
|||||||
</Menu.Target>
|
</Menu.Target>
|
||||||
|
|
||||||
<Menu.Dropdown>
|
<Menu.Dropdown>
|
||||||
|
<Menu.Item
|
||||||
|
onClick={handleToggleFavorite}
|
||||||
|
leftSection={
|
||||||
|
isFavorited ? (
|
||||||
|
<IconStarFilled
|
||||||
|
size={16}
|
||||||
|
color="var(--mantine-color-yellow-filled)"
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<IconStar size={16} />
|
||||||
|
)
|
||||||
|
}
|
||||||
|
>
|
||||||
|
{isFavorited ? t("Remove from favorites") : t("Add to favorites")}
|
||||||
|
</Menu.Item>
|
||||||
|
|
||||||
<Menu.Item
|
<Menu.Item
|
||||||
onClick={handleToggleWatch}
|
onClick={handleToggleWatch}
|
||||||
leftSection={
|
leftSection={
|
||||||
|
|||||||
@@ -7,9 +7,15 @@ import {
|
|||||||
Space,
|
Space,
|
||||||
Menu,
|
Menu,
|
||||||
Anchor,
|
Anchor,
|
||||||
|
Tooltip,
|
||||||
} from "@mantine/core";
|
} from "@mantine/core";
|
||||||
import { IconDots, IconSettings } from "@tabler/icons-react";
|
import { IconDots, IconSettings, IconEye, IconEyeOff } from "@tabler/icons-react";
|
||||||
import StarButton from "@/features/favorite/components/star-button";
|
import StarButton from "@/features/favorite/components/star-button";
|
||||||
|
import {
|
||||||
|
useWatchedSpaceIds,
|
||||||
|
useWatchSpaceMutation,
|
||||||
|
useUnwatchSpaceMutation,
|
||||||
|
} from "@/features/space/queries/space-watcher-query";
|
||||||
import { Link } from "react-router-dom";
|
import { Link } from "react-router-dom";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import React, { useState } from "react";
|
import React, { useState } from "react";
|
||||||
@@ -26,6 +32,45 @@ import { CustomAvatar } from "@/components/ui/custom-avatar.tsx";
|
|||||||
import { AvatarIconType } from "@/features/attachments/types/attachment.types.ts";
|
import { AvatarIconType } from "@/features/attachments/types/attachment.types.ts";
|
||||||
import { AutoTooltipText } from "@/components/ui/auto-tooltip-text.tsx";
|
import { AutoTooltipText } from "@/components/ui/auto-tooltip-text.tsx";
|
||||||
|
|
||||||
|
function WatchButton({ spaceId, watchedIds, size = 16 }: { spaceId: string; watchedIds: Set<string>; size?: number }) {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const watchMutation = useWatchSpaceMutation();
|
||||||
|
const unwatchMutation = useUnwatchSpaceMutation();
|
||||||
|
const isWatching = watchedIds.has(spaceId);
|
||||||
|
const isPending = watchMutation.isPending || unwatchMutation.isPending;
|
||||||
|
|
||||||
|
const handleToggle = (e: React.MouseEvent) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
e.preventDefault();
|
||||||
|
if (isWatching) {
|
||||||
|
unwatchMutation.mutate(spaceId);
|
||||||
|
} else {
|
||||||
|
watchMutation.mutate(spaceId);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Tooltip
|
||||||
|
label={isWatching ? t("Stop watching space") : t("Watch space")}
|
||||||
|
openDelay={250}
|
||||||
|
withArrow
|
||||||
|
>
|
||||||
|
<ActionIcon
|
||||||
|
variant="subtle"
|
||||||
|
color={isWatching ? "blue" : "gray"}
|
||||||
|
onClick={handleToggle}
|
||||||
|
loading={isPending}
|
||||||
|
>
|
||||||
|
{isWatching ? (
|
||||||
|
<IconEyeOff size={size} stroke={2} />
|
||||||
|
) : (
|
||||||
|
<IconEye size={size} stroke={2} />
|
||||||
|
)}
|
||||||
|
</ActionIcon>
|
||||||
|
</Tooltip>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
interface AllSpacesListProps {
|
interface AllSpacesListProps {
|
||||||
spaces: any[];
|
spaces: any[];
|
||||||
onSearch: (query: string) => void;
|
onSearch: (query: string) => void;
|
||||||
@@ -44,6 +89,7 @@ export default function AllSpacesList({
|
|||||||
onPrev,
|
onPrev,
|
||||||
}: AllSpacesListProps) {
|
}: AllSpacesListProps) {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
const watchedIds = useWatchedSpaceIds();
|
||||||
const [settingsOpened, { open: openSettings, close: closeSettings }] =
|
const [settingsOpened, { open: openSettings, close: closeSettings }] =
|
||||||
useDisclosure(false);
|
useDisclosure(false);
|
||||||
const [selectedSpaceId, setSelectedSpaceId] = useState<string | null>(null);
|
const [selectedSpaceId, setSelectedSpaceId] = useState<string | null>(null);
|
||||||
@@ -65,7 +111,7 @@ export default function AllSpacesList({
|
|||||||
<Table.Tr>
|
<Table.Tr>
|
||||||
<Table.Th>{t("Space")}</Table.Th>
|
<Table.Th>{t("Space")}</Table.Th>
|
||||||
<Table.Th>{t("Members")}</Table.Th>
|
<Table.Th>{t("Members")}</Table.Th>
|
||||||
<Table.Th w={100}></Table.Th>
|
<Table.Th w={130}></Table.Th>
|
||||||
</Table.Tr>
|
</Table.Tr>
|
||||||
</Table.Thead>
|
</Table.Thead>
|
||||||
|
|
||||||
@@ -117,8 +163,9 @@ export default function AllSpacesList({
|
|||||||
</Text>
|
</Text>
|
||||||
</Table.Td>
|
</Table.Td>
|
||||||
<Table.Td>
|
<Table.Td>
|
||||||
<Group gap="xs" justify="flex-end">
|
<Group gap="xs" justify="flex-end" wrap="nowrap">
|
||||||
<StarButton type="space" spaceId={space.id} size={16} />
|
<StarButton type="space" spaceId={space.id} size={16} />
|
||||||
|
<WatchButton spaceId={space.id} watchedIds={watchedIds} size={16} />
|
||||||
<Menu position="bottom-end">
|
<Menu position="bottom-end">
|
||||||
<Menu.Target>
|
<Menu.Target>
|
||||||
<ActionIcon variant="subtle" color="gray">
|
<ActionIcon variant="subtle" color="gray">
|
||||||
|
|||||||
@@ -1,13 +1,27 @@
|
|||||||
|
import { useMemo } from "react";
|
||||||
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
|
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
|
||||||
import {
|
import {
|
||||||
watchSpace,
|
watchSpace,
|
||||||
unwatchSpace,
|
unwatchSpace,
|
||||||
getSpaceWatchStatus,
|
getSpaceWatchStatus,
|
||||||
|
getWatchedSpaceIds,
|
||||||
} from "@/features/space/services/space-watcher-service";
|
} from "@/features/space/services/space-watcher-service";
|
||||||
import { notifications } from "@mantine/notifications";
|
import { notifications } from "@mantine/notifications";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
|
|
||||||
const SPACE_WATCHER_KEY = "space-watcher";
|
const SPACE_WATCHER_KEY = "space-watcher";
|
||||||
|
const WATCHED_SPACE_IDS_KEY = "watched-space-ids";
|
||||||
|
|
||||||
|
export function useWatchedSpaceIds(): Set<string> {
|
||||||
|
const { data } = useQuery({
|
||||||
|
queryKey: [WATCHED_SPACE_IDS_KEY],
|
||||||
|
queryFn: () => getWatchedSpaceIds(),
|
||||||
|
refetchOnMount: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
const items = data?.items;
|
||||||
|
return useMemo(() => new Set(items ?? []), [items]);
|
||||||
|
}
|
||||||
|
|
||||||
export function useSpaceWatchStatusQuery(spaceId: string) {
|
export function useSpaceWatchStatusQuery(spaceId: string) {
|
||||||
return useQuery({
|
return useQuery({
|
||||||
@@ -27,6 +41,14 @@ export function useWatchSpaceMutation() {
|
|||||||
queryClient.setQueryData([SPACE_WATCHER_KEY, spaceId], {
|
queryClient.setQueryData([SPACE_WATCHER_KEY, spaceId], {
|
||||||
watching: true,
|
watching: true,
|
||||||
});
|
});
|
||||||
|
queryClient.setQueryData(
|
||||||
|
[WATCHED_SPACE_IDS_KEY],
|
||||||
|
(old: { items: string[]; meta: any } | undefined) => {
|
||||||
|
if (!old) return old;
|
||||||
|
if (old.items.includes(spaceId)) return old;
|
||||||
|
return { ...old, items: [...old.items, spaceId] };
|
||||||
|
},
|
||||||
|
);
|
||||||
notifications.show({ message: t("You are now watching this space") });
|
notifications.show({ message: t("You are now watching this space") });
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
@@ -41,6 +63,13 @@ export function useUnwatchSpaceMutation() {
|
|||||||
queryClient.setQueryData([SPACE_WATCHER_KEY, spaceId], {
|
queryClient.setQueryData([SPACE_WATCHER_KEY, spaceId], {
|
||||||
watching: false,
|
watching: false,
|
||||||
});
|
});
|
||||||
|
queryClient.setQueryData(
|
||||||
|
[WATCHED_SPACE_IDS_KEY],
|
||||||
|
(old: { items: string[]; meta: any } | undefined) => {
|
||||||
|
if (!old) return old;
|
||||||
|
return { ...old, items: old.items.filter((id) => id !== spaceId) };
|
||||||
|
},
|
||||||
|
);
|
||||||
notifications.show({
|
notifications.show({
|
||||||
message: t("You are no longer watching this space"),
|
message: t("You are no longer watching this space"),
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import api from "@/lib/api-client";
|
import api from "@/lib/api-client";
|
||||||
|
import { IPagination } from "@/lib/types";
|
||||||
|
|
||||||
export async function watchSpace(
|
export async function watchSpace(
|
||||||
spaceId: string,
|
spaceId: string,
|
||||||
@@ -18,6 +19,11 @@ export async function unwatchSpace(
|
|||||||
return req.data;
|
return req.data;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function getWatchedSpaceIds(): Promise<IPagination<string>> {
|
||||||
|
const req = await api.post<IPagination<string>>("/spaces/watched-ids");
|
||||||
|
return req.data;
|
||||||
|
}
|
||||||
|
|
||||||
export async function getSpaceWatchStatus(
|
export async function getSpaceWatchStatus(
|
||||||
spaceId: string,
|
spaceId: string,
|
||||||
): Promise<{ watching: boolean }> {
|
): Promise<{ watching: boolean }> {
|
||||||
|
|||||||
@@ -0,0 +1,8 @@
|
|||||||
|
import { IsIn, IsNotEmpty, IsString } from 'class-validator';
|
||||||
|
|
||||||
|
export class FavoriteIdsDto {
|
||||||
|
@IsString()
|
||||||
|
@IsNotEmpty()
|
||||||
|
@IsIn(['page', 'space', 'template'])
|
||||||
|
type: 'page' | 'space' | 'template';
|
||||||
|
}
|
||||||
@@ -11,6 +11,7 @@ import {
|
|||||||
} from '@nestjs/common';
|
} from '@nestjs/common';
|
||||||
import { FavoriteService } from './services/favorite.service';
|
import { FavoriteService } from './services/favorite.service';
|
||||||
import { AddFavoriteDto, RemoveFavoriteDto } from './dto/favorite.dto';
|
import { AddFavoriteDto, RemoveFavoriteDto } from './dto/favorite.dto';
|
||||||
|
import { FavoriteIdsDto } from './dto/favorite-ids.dto';
|
||||||
import { ListFavoritesDto } from './dto/list-favorites.dto';
|
import { ListFavoritesDto } from './dto/list-favorites.dto';
|
||||||
import { PaginationOptions } from '@docmost/db/pagination/pagination-options';
|
import { PaginationOptions } from '@docmost/db/pagination/pagination-options';
|
||||||
import { JwtAuthGuard } from '../../common/guards/jwt-auth.guard';
|
import { JwtAuthGuard } from '../../common/guards/jwt-auth.guard';
|
||||||
@@ -70,6 +71,20 @@ export class FavoriteController {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@HttpCode(HttpStatus.OK)
|
||||||
|
@Post('ids')
|
||||||
|
async getFavoriteIds(
|
||||||
|
@Body() dto: FavoriteIdsDto,
|
||||||
|
@AuthUser() user: User,
|
||||||
|
@AuthWorkspace() workspace: Workspace,
|
||||||
|
) {
|
||||||
|
return this.favoriteService.getFavoriteIds(
|
||||||
|
user.id,
|
||||||
|
workspace.id,
|
||||||
|
dto.type as FavoriteType,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
@HttpCode(HttpStatus.OK)
|
@HttpCode(HttpStatus.OK)
|
||||||
@Post()
|
@Post()
|
||||||
async getUserFavorites(
|
async getUserFavorites(
|
||||||
|
|||||||
@@ -16,6 +16,40 @@ export class FavoriteService {
|
|||||||
private readonly spaceMemberRepo: SpaceMemberRepo,
|
private readonly spaceMemberRepo: SpaceMemberRepo,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
|
async getFavoriteIds(
|
||||||
|
userId: string,
|
||||||
|
workspaceId: string,
|
||||||
|
type: FavoriteType,
|
||||||
|
) {
|
||||||
|
const result = await this.favoriteRepo.getFavoriteIds(
|
||||||
|
userId,
|
||||||
|
workspaceId,
|
||||||
|
type,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (result.items.length === 0) {
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (type === FavoriteType.PAGE) {
|
||||||
|
const accessibleIds =
|
||||||
|
await this.pagePermissionRepo.filterAccessiblePageIds({
|
||||||
|
pageIds: result.items,
|
||||||
|
userId,
|
||||||
|
});
|
||||||
|
const accessibleSet = new Set(accessibleIds);
|
||||||
|
result.items = result.items.filter((id) => accessibleSet.has(id));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (type === FavoriteType.SPACE) {
|
||||||
|
const userSpaceIds = await this.spaceMemberRepo.getUserSpaceIds(userId);
|
||||||
|
const spaceSet = new Set(userSpaceIds);
|
||||||
|
result.items = result.items.filter((id) => spaceSet.has(id));
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
async addFavorite(
|
async addFavorite(
|
||||||
userId: string,
|
userId: string,
|
||||||
workspaceId: string,
|
workspaceId: string,
|
||||||
|
|||||||
@@ -48,6 +48,15 @@ export class SpaceWatcherController {
|
|||||||
return space;
|
return space;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@HttpCode(HttpStatus.OK)
|
||||||
|
@Post('watched-ids')
|
||||||
|
async getWatchedSpaceIds(
|
||||||
|
@AuthUser() user: User,
|
||||||
|
@AuthWorkspace() workspace: Workspace,
|
||||||
|
) {
|
||||||
|
return this.watcherService.getWatchedSpaceIds(user.id, workspace.id);
|
||||||
|
}
|
||||||
|
|
||||||
@HttpCode(HttpStatus.OK)
|
@HttpCode(HttpStatus.OK)
|
||||||
@Post('watch')
|
@Post('watch')
|
||||||
async watchSpace(
|
async watchSpace(
|
||||||
|
|||||||
@@ -6,10 +6,14 @@ import {
|
|||||||
import { PaginationOptions } from '@docmost/db/pagination/pagination-options';
|
import { PaginationOptions } from '@docmost/db/pagination/pagination-options';
|
||||||
import { KyselyTransaction } from '@docmost/db/types/kysely.types';
|
import { KyselyTransaction } from '@docmost/db/types/kysely.types';
|
||||||
import { InsertableWatcher } from '@docmost/db/types/entity.types';
|
import { InsertableWatcher } from '@docmost/db/types/entity.types';
|
||||||
|
import { SpaceMemberRepo } from '@docmost/db/repos/space/space-member.repo';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class WatcherService {
|
export class WatcherService {
|
||||||
constructor(private readonly watcherRepo: WatcherRepo) {}
|
constructor(
|
||||||
|
private readonly watcherRepo: WatcherRepo,
|
||||||
|
private readonly spaceMemberRepo: SpaceMemberRepo,
|
||||||
|
) {}
|
||||||
|
|
||||||
async watchPage(
|
async watchPage(
|
||||||
userId: string,
|
userId: string,
|
||||||
@@ -84,6 +88,24 @@ export class WatcherService {
|
|||||||
return this.watcherRepo.deleteSpaceWatch(userId, spaceId);
|
return this.watcherRepo.deleteSpaceWatch(userId, spaceId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async getWatchedSpaceIds(userId: string, workspaceId: string) {
|
||||||
|
const result = await this.watcherRepo.getWatchedSpaceIds(userId, workspaceId);
|
||||||
|
|
||||||
|
const spaceIds = result.items.map((r) => r.spaceId);
|
||||||
|
|
||||||
|
if (spaceIds.length === 0) {
|
||||||
|
return { items: spaceIds, meta: result.meta };
|
||||||
|
}
|
||||||
|
|
||||||
|
const userSpaceIds = await this.spaceMemberRepo.getUserSpaceIds(userId);
|
||||||
|
const spaceSet = new Set(userSpaceIds);
|
||||||
|
|
||||||
|
return {
|
||||||
|
items: spaceIds.filter((id) => spaceSet.has(id)),
|
||||||
|
meta: result.meta,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
async isWatchingSpace(userId: string, spaceId: string): Promise<boolean> {
|
async isWatchingSpace(userId: string, spaceId: string): Promise<boolean> {
|
||||||
return this.watcherRepo.isWatchingSpace(userId, spaceId);
|
return this.watcherRepo.isWatchingSpace(userId, spaceId);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -62,6 +62,39 @@ export class FavoriteRepo {
|
|||||||
.execute();
|
.execute();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async getFavoriteIds(
|
||||||
|
userId: string,
|
||||||
|
workspaceId: string,
|
||||||
|
type: FavoriteType,
|
||||||
|
): Promise<{ items: string[]; meta: any }> {
|
||||||
|
const idColumn =
|
||||||
|
type === FavoriteType.PAGE
|
||||||
|
? 'pageId'
|
||||||
|
: type === FavoriteType.SPACE
|
||||||
|
? 'spaceId'
|
||||||
|
: 'templateId';
|
||||||
|
|
||||||
|
const query = this.db
|
||||||
|
.selectFrom('favorites')
|
||||||
|
.select(['favorites.id', `favorites.${idColumn} as entityId`])
|
||||||
|
.where('userId', '=', userId)
|
||||||
|
.where('workspaceId', '=', workspaceId)
|
||||||
|
.where('type', '=', type);
|
||||||
|
|
||||||
|
const result = await executeWithCursorPagination(query, {
|
||||||
|
perPage: 250,
|
||||||
|
fields: [{ expression: 'favorites.id', direction: 'desc' }],
|
||||||
|
parseCursor: (cursor) => ({ id: cursor.id }),
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
items: result.items
|
||||||
|
.map((r) => (r as any).entityId as string)
|
||||||
|
.filter(Boolean),
|
||||||
|
meta: result.meta,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
async findUserFavorites(
|
async findUserFavorites(
|
||||||
userId: string,
|
userId: string,
|
||||||
workspaceId: string,
|
workspaceId: string,
|
||||||
|
|||||||
@@ -207,6 +207,22 @@ export class WatcherRepo {
|
|||||||
.execute();
|
.execute();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async getWatchedSpaceIds(userId: string, workspaceId: string) {
|
||||||
|
const query = this.db
|
||||||
|
.selectFrom('watchers')
|
||||||
|
.select(['watchers.id', 'watchers.spaceId'])
|
||||||
|
.where('userId', '=', userId)
|
||||||
|
.where('workspaceId', '=', workspaceId)
|
||||||
|
.where('pageId', 'is', null)
|
||||||
|
.where('type', '=', WatcherType.SPACE);
|
||||||
|
|
||||||
|
return executeWithCursorPagination(query, {
|
||||||
|
perPage: 250,
|
||||||
|
fields: [{ expression: 'watchers.id', direction: 'asc' }],
|
||||||
|
parseCursor: (cursor) => ({ id: cursor.id }),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
async isWatchingSpace(userId: string, spaceId: string): Promise<boolean> {
|
async isWatchingSpace(userId: string, spaceId: string): Promise<boolean> {
|
||||||
const watcher = await this.db
|
const watcher = await this.db
|
||||||
.selectFrom('watchers')
|
.selectFrom('watchers')
|
||||||
|
|||||||
Reference in New Issue
Block a user