diff --git a/README.md b/README.md index 0f1b647a..1b7b161a 100644 --- a/README.md +++ b/README.md @@ -9,9 +9,6 @@
-> [!NOTE] -> Docmost is currently in **beta**. We value your feedback as we progress towards a stable release. - ## Getting started To get started with Docmost, please refer to our [documentation](https://docmost.com/docs). diff --git a/apps/client/package.json b/apps/client/package.json index b64d910b..5b6ee046 100644 --- a/apps/client/package.json +++ b/apps/client/package.json @@ -1,7 +1,7 @@ { "name": "client", "private": true, - "version": "0.9.0", + "version": "0.10.0", "scripts": { "dev": "vite", "build": "tsc && vite build", diff --git a/apps/client/src/features/editor/page-editor.tsx b/apps/client/src/features/editor/page-editor.tsx index 2853875e..32845c6d 100644 --- a/apps/client/src/features/editor/page-editor.tsx +++ b/apps/client/src/features/editor/page-editor.tsx @@ -209,6 +209,7 @@ export default function PageEditor({ queryClient.setQueryData(["pages", slugId], { ...pageData, content: newContent, + updatedAt: new Date(), }); } }, 3000); diff --git a/apps/client/src/features/page-history/components/history-list.tsx b/apps/client/src/features/page-history/components/history-list.tsx index c21fb3b7..af178eac 100644 --- a/apps/client/src/features/page-history/components/history-list.tsx +++ b/apps/client/src/features/page-history/components/history-list.tsx @@ -17,6 +17,13 @@ import { import { modals } from "@mantine/modals"; import { notifications } from "@mantine/notifications"; import { useTranslation } from "react-i18next"; +import { useSpaceAbility } from "@/features/space/permissions/use-space-ability.ts"; +import { useSpaceQuery } from "@/features/space/queries/space-query.ts"; +import { useParams } from "react-router-dom"; +import { + SpaceCaslAction, + SpaceCaslSubject, +} from "@/features/space/permissions/permissions.type.ts"; interface Props { pageId: string; @@ -36,6 +43,11 @@ function HistoryList({ pageId }: Props) { const [mainEditorTitle] = useAtom(titleEditorAtom); const [, setHistoryModalOpen] = useAtom(historyAtoms); + const { spaceSlug } = useParams(); + const { data: space } = useSpaceQuery(spaceSlug); + const spaceRules = space?.membership?.permissions; + const spaceAbility = useSpaceAbility(spaceRules); + const confirmModal = () => modals.openConfirmModal({ title: t("Please confirm your action"), @@ -103,20 +115,26 @@ function HistoryList({ pageId }: Props) { ))} - - - - - - + {spaceAbility.cannot( + SpaceCaslAction.Manage, + SpaceCaslSubject.Page, + ) ? null : ( + <> + + + + + + + )} ); } diff --git a/apps/client/src/features/page/components/header/page-header-menu.tsx b/apps/client/src/features/page/components/header/page-header-menu.tsx index fefd3e28..93d10520 100644 --- a/apps/client/src/features/page/components/header/page-header-menu.tsx +++ b/apps/client/src/features/page/components/header/page-header-menu.tsx @@ -12,7 +12,7 @@ import { IconTrash, IconWifiOff, } from "@tabler/icons-react"; -import React from "react"; +import React, { useEffect } from "react"; import useToggleAside from "@/hooks/use-toggle-aside.tsx"; import { useAtom } from "jotai"; import { historyAtoms } from "@/features/page-history/atoms/history-atoms.ts"; @@ -34,6 +34,7 @@ import { } from "@/features/editor/atoms/editor-atoms.ts"; import { formattedDate, timeAgo } from "@/lib/time.ts"; import MovePageModal from "@/features/page/components/move-page-modal.tsx"; +import { useTimeAgo } from "@/hooks/use-time-ago.tsx"; interface PageHeaderMenuProps { readOnly?: boolean; @@ -102,6 +103,7 @@ function PageActionMenu({ readOnly }: PageActionMenuProps) { { open: openMovePageModal, close: closeMoveSpaceModal }, ] = useDisclosure(false); const [pageEditor] = useAtom(pageEditorAtom); + const pageUpdatedAt = useTimeAgo(page.updatedAt); const handleCopyLink = () => { const pageUrl = @@ -208,7 +210,7 @@ function PageActionMenu({ readOnly }: PageActionMenuProps) { diff --git a/apps/client/src/hooks/use-time-ago.tsx b/apps/client/src/hooks/use-time-ago.tsx new file mode 100644 index 00000000..03fa9eda --- /dev/null +++ b/apps/client/src/hooks/use-time-ago.tsx @@ -0,0 +1,16 @@ +import { timeAgo } from "@/lib/time.ts"; +import { useEffect, useState } from "react"; + +export function useTimeAgo(date: Date | string) { + const [value, setValue] = useState(() => timeAgo(new Date(date))); + + useEffect(() => { + const interval = setInterval(() => { + setValue(timeAgo(new Date(date))); + }, 5 * 1000); + + return () => clearInterval(interval); + }, [date]); + + return value; +} diff --git a/apps/server/package.json b/apps/server/package.json index a44cf59e..83a2e85f 100644 --- a/apps/server/package.json +++ b/apps/server/package.json @@ -1,6 +1,6 @@ { "name": "server", - "version": "0.9.0", + "version": "0.10.0", "description": "", "author": "", "private": true, diff --git a/apps/server/src/core/auth/services/auth.service.ts b/apps/server/src/core/auth/services/auth.service.ts index 19f02230..9c761ef3 100644 --- a/apps/server/src/core/auth/services/auth.service.ts +++ b/apps/server/src/core/auth/services/auth.service.ts @@ -47,13 +47,18 @@ export class AuthService { includePassword: true, }); + const errorMessage = 'email or password does not match'; + if (!user || user?.deletedAt) { + throw new UnauthorizedException(errorMessage); + } + const isPasswordMatch = await comparePasswordHash( loginDto.password, - user?.password, + user.password, ); - if (!user || !isPasswordMatch || user.deletedAt) { - throw new UnauthorizedException('email or password does not match'); + if (!isPasswordMatch) { + throw new UnauthorizedException(errorMessage); } user.lastLoginAt = new Date(); diff --git a/package.json b/package.json index 64cf9267..290e09a3 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "docmost", "homepage": "https://docmost.com", - "version": "0.9.0", + "version": "0.10.0", "private": true, "scripts": { "build": "nx run-many -t build", diff --git a/packages/editor-ext/src/lib/details/details.ts b/packages/editor-ext/src/lib/details/details.ts index 8a05f21c..b28c4de7 100644 --- a/packages/editor-ext/src/lib/details/details.ts +++ b/packages/editor-ext/src/lib/details/details.ts @@ -78,10 +78,13 @@ export const Details = Node.create({ dom.setAttribute("data-type", this.name); btn.setAttribute("data-type", `${this.name}Button`); div.setAttribute("data-type", `${this.name}Container`); - if (node.attrs.open) { - dom.setAttribute("open", "true"); - } else { - dom.removeAttribute("open"); + + if (editor.isEditable) { + if (node.attrs.open) { + dom.setAttribute("open", "true"); + } else { + dom.removeAttribute("open"); + } } ico.innerHTML = icon("right-line"); @@ -111,6 +114,7 @@ export const Details = Node.create({ if (updatedNode.type !== this.type) { return false; } + if (!editor.isEditable) return true; if (updatedNode.attrs.open) { dom.setAttribute("open", "true"); } else { @@ -132,9 +136,10 @@ export const Details = Node.create({ } const slice = state.doc.slice(range.start, range.end); - - if(slice.content.firstChild.type.name === "detailsSummary") return false; - + + if (slice.content.firstChild.type.name === "detailsSummary") + return false; + if ( !state.schema.nodes.detailsContent.contentMatch.matchFragment( slice.content,