mirror of
https://github.com/docmost/docmost.git
synced 2025-11-17 16:51:10 +10:00
Merge branch 'main' into feat/sharing
This commit is contained in:
@ -9,9 +9,6 @@
|
||||
</div>
|
||||
<br />
|
||||
|
||||
> [!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).
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "client",
|
||||
"private": true,
|
||||
"version": "0.9.0",
|
||||
"version": "0.10.0",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
"build": "tsc && vite build",
|
||||
|
||||
@ -209,6 +209,7 @@ export default function PageEditor({
|
||||
queryClient.setQueryData(["pages", slugId], {
|
||||
...pageData,
|
||||
content: newContent,
|
||||
updatedAt: new Date(),
|
||||
});
|
||||
}
|
||||
}, 3000);
|
||||
|
||||
@ -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,8 +115,12 @@ function HistoryList({ pageId }: Props) {
|
||||
))}
|
||||
</ScrollArea>
|
||||
|
||||
{spaceAbility.cannot(
|
||||
SpaceCaslAction.Manage,
|
||||
SpaceCaslSubject.Page,
|
||||
) ? null : (
|
||||
<>
|
||||
<Divider />
|
||||
|
||||
<Group p="xs" wrap="nowrap">
|
||||
<Button size="compact-md" onClick={confirmModal}>
|
||||
{t("Restore")}
|
||||
@ -117,6 +133,8 @@ function HistoryList({ pageId }: Props) {
|
||||
{t("Cancel")}
|
||||
</Button>
|
||||
</Group>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@ -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) {
|
||||
<Tooltip
|
||||
label={t("Edited by {{name}} {{time}}", {
|
||||
name: page.lastUpdatedBy.name,
|
||||
time: timeAgo(page.updatedAt),
|
||||
time: pageUpdatedAt,
|
||||
})}
|
||||
position="left-start"
|
||||
>
|
||||
|
||||
16
apps/client/src/hooks/use-time-ago.tsx
Normal file
16
apps/client/src/hooks/use-time-ago.tsx
Normal file
@ -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;
|
||||
}
|
||||
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "server",
|
||||
"version": "0.9.0",
|
||||
"version": "0.10.0",
|
||||
"description": "",
|
||||
"author": "",
|
||||
"private": true,
|
||||
|
||||
@ -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();
|
||||
|
||||
@ -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",
|
||||
|
||||
@ -78,11 +78,14 @@ export const Details = Node.create<DetailsOptions>({
|
||||
dom.setAttribute("data-type", this.name);
|
||||
btn.setAttribute("data-type", `${this.name}Button`);
|
||||
div.setAttribute("data-type", `${this.name}Container`);
|
||||
|
||||
if (editor.isEditable) {
|
||||
if (node.attrs.open) {
|
||||
dom.setAttribute("open", "true");
|
||||
} else {
|
||||
dom.removeAttribute("open");
|
||||
}
|
||||
}
|
||||
|
||||
ico.innerHTML = icon("right-line");
|
||||
btn.addEventListener("click", () => {
|
||||
@ -111,6 +114,7 @@ export const Details = Node.create<DetailsOptions>({
|
||||
if (updatedNode.type !== this.type) {
|
||||
return false;
|
||||
}
|
||||
if (!editor.isEditable) return true;
|
||||
if (updatedNode.attrs.open) {
|
||||
dom.setAttribute("open", "true");
|
||||
} else {
|
||||
@ -133,7 +137,8 @@ export const Details = Node.create<DetailsOptions>({
|
||||
|
||||
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(
|
||||
|
||||
Reference in New Issue
Block a user