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,