This commit is contained in:
Ryan Palmer
2024-09-04 12:52:26 +10:00
13 changed files with 1912 additions and 3200 deletions

View File

@ -1,7 +1,7 @@
{
"name": "client",
"private": true,
"version": "0.2.10",
"version": "0.3.0",
"scripts": {
"dev": "vite",
"build": "tsc && vite build",
@ -14,26 +14,26 @@
"@emoji-mart/data": "^1.2.1",
"@emoji-mart/react": "^1.1.1",
"@excalidraw/excalidraw": "^0.17.6",
"@mantine/core": "^7.11.0",
"@mantine/form": "^7.11.0",
"@mantine/hooks": "^7.11.0",
"@mantine/modals": "^7.11.0",
"@mantine/notifications": "^7.11.0",
"@mantine/spotlight": "^7.11.0",
"@tabler/icons-react": "^3.7.0",
"@tanstack/react-query": "^5.48.0",
"axios": "^1.7.2",
"@mantine/core": "^7.12.2",
"@mantine/form": "^7.12.2",
"@mantine/hooks": "^7.12.2",
"@mantine/modals": "^7.12.2",
"@mantine/notifications": "^7.12.2",
"@mantine/spotlight": "^7.12.2",
"@tabler/icons-react": "^3.14.0",
"@tanstack/react-query": "^5.53.2",
"axios": "^1.7.7",
"clsx": "^2.1.1",
"date-fns": "^3.6.0",
"emoji-mart": "^5.6.0",
"file-saver": "^2.0.5",
"jotai": "^2.8.3",
"jotai": "^2.9.3",
"jotai-optics": "^0.4.0",
"js-cookie": "^3.0.5",
"jwt-decode": "^4.0.0",
"katex": "^0.16.10",
"katex": "^0.16.11",
"lowlight": "^3.1.0",
"mermaid": "^11.0.1",
"mermaid": "^11.0.2",
"react": "^18.3.1",
"react-arborist": "^3.4.0",
"react-clear-modal": "^2.0.9",
@ -42,32 +42,32 @@
"react-error-boundary": "^4.0.13",
"react-helmet-async": "^2.0.5",
"react-moveable": "^0.56.0",
"react-router-dom": "^6.24.0",
"react-router-dom": "^6.26.1",
"socket.io-client": "^4.7.5",
"tippy.js": "^6.3.7",
"tiptap-extension-global-drag-handle": "^0.1.10",
"tiptap-extension-global-drag-handle": "^0.1.12",
"zod": "^3.23.8"
},
"devDependencies": {
"@tanstack/eslint-plugin-query": "^5.47.0",
"@tanstack/eslint-plugin-query": "^5.53.0",
"@types/file-saver": "^2.0.7",
"@types/js-cookie": "^3.0.6",
"@types/katex": "^0.16.7",
"@types/node": "20.14.9",
"@types/react": "^18.3.3",
"@types/node": "22.5.2",
"@types/react": "^18.3.5",
"@types/react-dom": "^18.3.0",
"@typescript-eslint/eslint-plugin": "^7.14.1",
"@typescript-eslint/parser": "^7.14.1",
"@typescript-eslint/eslint-plugin": "^8.3.0",
"@typescript-eslint/parser": "^8.3.0",
"@vitejs/plugin-react": "^4.3.1",
"eslint": "^9.5.0",
"eslint": "^9.9.1",
"eslint-plugin-react-hooks": "^4.6.2",
"eslint-plugin-react-refresh": "^0.4.7",
"eslint-plugin-react-refresh": "^0.4.11",
"optics-ts": "^2.4.1",
"postcss": "^8.4.38",
"postcss-preset-mantine": "^1.15.0",
"postcss": "^8.4.43",
"postcss-preset-mantine": "^1.17.0",
"postcss-simple-vars": "^7.0.1",
"prettier": "^3.3.2",
"typescript": "^5.5.2",
"vite": "^5.3.1"
"prettier": "^3.3.3",
"typescript": "^5.5.4",
"vite": "^5.4.2"
}
}

View File

@ -1,5 +1,5 @@
import React, { useEffect, useState } from 'react';
import { Group, Text, ScrollArea, ActionIcon, rem } from '@mantine/core';
import React, { useEffect, useState } from "react";
import { Group, Text, ScrollArea, ActionIcon, rem } from "@mantine/core";
import {
IconUser,
IconSettings,
@ -8,9 +8,9 @@ import {
IconUsersGroup,
IconSpaces,
IconBrush,
} from '@tabler/icons-react';
import { Link, useLocation, useNavigate } from 'react-router-dom';
import classes from './settings.module.css';
} from "@tabler/icons-react";
import { Link, useLocation, useNavigate } from "react-router-dom";
import classes from "./settings.module.css";
interface DataItem {
label: string;
@ -25,27 +25,27 @@ interface DataGroup {
const groupedData: DataGroup[] = [
{
heading: 'Account',
heading: "Account",
items: [
{ label: 'Profile', icon: IconUser, path: '/settings/account/profile' },
{ label: "Profile", icon: IconUser, path: "/settings/account/profile" },
{
label: 'Preferences',
label: "Preferences",
icon: IconBrush,
path: '/settings/account/preferences',
path: "/settings/account/preferences",
},
],
},
{
heading: 'Workspace',
heading: "Workspace",
items: [
{ label: 'General', icon: IconSettings, path: '/settings/workspace' },
{ label: "General", icon: IconSettings, path: "/settings/workspace" },
{
label: 'Members',
label: "Members",
icon: IconUsers,
path: '/settings/members',
path: "/settings/members",
},
{ label: 'Groups', icon: IconUsersGroup, path: '/settings/groups' },
{ label: 'Spaces', icon: IconSpaces, path: '/settings/spaces' },
{ label: "Groups", icon: IconUsersGroup, path: "/settings/groups" },
{ label: "Spaces", icon: IconSpaces, path: "/settings/spaces" },
],
},
];
@ -96,6 +96,7 @@ export default function SettingsSidebar() {
<div className={classes.version}>
<Text
className={classes.version}
size="sm"
c="dimmed"
component="a"
href="https://github.com/docmost/docmost/releases"

View File

@ -1,13 +1,14 @@
import React, { ReactNode } from "react";
import data from "@emoji-mart/data";
import Picker from "@emoji-mart/react";
import React, { ReactNode } from 'react';
import {
ActionIcon,
Popover,
Button,
useMantineColorScheme,
} from "@mantine/core";
import { useDisclosure } from "@mantine/hooks";
} from '@mantine/core';
import { useDisclosure } from '@mantine/hooks';
import { Suspense } from 'react';
const Picker = React.lazy(() => import('@emoji-mart/react'));
export interface EmojiPickerInterface {
onEmojiSelect: (emoji: any) => void;
@ -48,23 +49,25 @@ function EmojiPicker({
{icon}
</ActionIcon>
</Popover.Target>
<Popover.Dropdown bg="000" style={{ border: "none" }}>
<Picker
data={data}
onEmojiSelect={handleEmojiSelect}
perLine={8}
skinTonePosition="search"
theme={colorScheme}
/>
<Popover.Dropdown bg="000" style={{ border: 'none' }}>
<Suspense fallback={null}>
<Picker
data={async () => (await import('@emoji-mart/data')).default}
onEmojiSelect={handleEmojiSelect}
perLine={8}
skinTonePosition="search"
theme={colorScheme}
/>
</Suspense>
<Button
variant="default"
c="gray"
size="xs"
style={{
position: "absolute",
position: 'absolute',
zIndex: 2,
bottom: "1rem",
right: "1rem",
bottom: '1rem',
right: '1rem',
}}
onClick={handleRemoveEmoji}
>

View File

@ -1,15 +1,21 @@
import { NodeViewContent, NodeViewProps, NodeViewWrapper } from "@tiptap/react";
import { ActionIcon, CopyButton, Group, Select, Tooltip } from "@mantine/core";
import { useEffect, useState } from "react";
import { IconCheck, IconCopy } from "@tabler/icons-react";
import MermaidView from "@/features/editor/components/code-block/mermaid-view.tsx";
import classes from "./code-block.module.css";
import { NodeViewContent, NodeViewProps, NodeViewWrapper } from '@tiptap/react';
import { ActionIcon, CopyButton, Group, Select, Tooltip } from '@mantine/core';
import { useEffect, useState } from 'react';
import { IconCheck, IconCopy } from '@tabler/icons-react';
//import MermaidView from "@/features/editor/components/code-block/mermaid-view.tsx";
import classes from './code-block.module.css';
import React from 'react';
import { Suspense } from 'react';
const MermaidView = React.lazy(
() => import('@/features/editor/components/code-block/mermaid-view.tsx')
);
export default function CodeBlockView(props: NodeViewProps) {
const { node, updateAttributes, extension, editor, getPos } = props;
const { language } = node.attrs;
const [languageValue, setLanguageValue] = useState<string | null>(
language || null,
language || null
);
const [isSelected, setIsSelected] = useState(false);
@ -24,9 +30,9 @@ export default function CodeBlockView(props: NodeViewProps) {
setIsSelected(isNodeSelected);
};
editor.on("selectionUpdate", updateSelection);
editor.on('selectionUpdate', updateSelection);
return () => {
editor.off("selectionUpdate", updateSelection);
editor.off('selectionUpdate', updateSelection);
};
}, [editor, getPos(), node.nodeSize]);
@ -47,7 +53,7 @@ export default function CodeBlockView(props: NodeViewProps) {
value={languageValue}
onChange={changeLanguage}
searchable
style={{ maxWidth: "130px" }}
style={{ maxWidth: '130px' }}
classNames={{ input: classes.selectInput }}
disabled={!editor.isEditable}
/>
@ -55,12 +61,12 @@ export default function CodeBlockView(props: NodeViewProps) {
<CopyButton value={node?.textContent} timeout={2000}>
{({ copied, copy }) => (
<Tooltip
label={copied ? "Copied" : "Copy"}
label={copied ? 'Copied' : 'Copy'}
withArrow
position="right"
>
<ActionIcon
color={copied ? "teal" : "gray"}
color={copied ? 'teal' : 'gray'}
variant="subtle"
onClick={copy}
>
@ -74,15 +80,19 @@ export default function CodeBlockView(props: NodeViewProps) {
<pre
spellCheck="false"
hidden={
((language === "mermaid" && !editor.isEditable) ||
(language === "mermaid" && !isSelected)) &&
((language === 'mermaid' && !editor.isEditable) ||
(language === 'mermaid' && !isSelected)) &&
node.textContent.length > 0
}
>
<NodeViewContent as="code" className={`language-${language}`} />
</pre>
{language === "mermaid" && <MermaidView props={props} />}
{language === 'mermaid' && (
<Suspense fallback={null}>
<MermaidView props={props} />
</Suspense>
)}
</NodeViewWrapper>
);
}

View File

@ -9,7 +9,6 @@ import {
useComputedColorScheme,
} from '@mantine/core';
import { useState } from 'react';
import { Excalidraw, exportToSvg, loadFromBlob } from '@excalidraw/excalidraw';
import { uploadFile } from '@/features/page/services/page-service.ts';
import { svgStringToFile } from '@/lib';
import { useDisclosure } from '@mantine/hooks';
@ -19,6 +18,14 @@ import { IAttachment } from '@/lib/types';
import ReactClearModal from 'react-clear-modal';
import clsx from 'clsx';
import { IconEdit } from '@tabler/icons-react';
import { lazy } from 'react';
import { Suspense } from 'react';
const Excalidraw = lazy(() =>
import('@excalidraw/excalidraw').then((module) => ({
default: module.Excalidraw,
}))
);
export default function ExcalidrawView(props: NodeViewProps) {
const { node, updateAttributes, editor, selected } = props;
@ -43,6 +50,8 @@ export default function ExcalidrawView(props: NodeViewProps) {
cache: 'no-store',
});
const { loadFromBlob } = await import('@excalidraw/excalidraw');
const data = await loadFromBlob(await request.blob(), null, null);
setExcalidrawData(data);
}
@ -58,6 +67,8 @@ export default function ExcalidrawView(props: NodeViewProps) {
return;
}
const { exportToSvg } = await import('@excalidraw/excalidraw');
const svg = await exportToSvg({
elements: excalidrawAPI?.getSceneElements(),
appState: {
@ -129,13 +140,15 @@ export default function ExcalidrawView(props: NodeViewProps) {
</Button>
</Group>
<div style={{ height: '90vh' }}>
<Excalidraw
excalidrawAPI={(api) => setExcalidrawAPI(api)}
initialData={{
...excalidrawData,
scrollToContent: true,
}}
/>
<Suspense fallback={null}>
<Excalidraw
excalidrawAPI={(api) => setExcalidrawAPI(api)}
initialData={{
...excalidrawData,
scrollToContent: true,
}}
/>
</Suspense>
</div>
</ReactClearModal>

View File

@ -7,7 +7,6 @@ import {
IconH2,
IconH3,
IconInfoCircle,
IconLetterY,
IconList,
IconListNumbers,
IconListTree,
@ -18,6 +17,7 @@ import {
IconPhoto,
IconTable,
IconTypography,
IconMenu4
} from "@tabler/icons-react";
import {
CommandProps,
@ -139,12 +139,20 @@ const CommandGroups: SlashMenuGroupedItemsType = {
},
{
title: "Code",
description: "Capture a code snippet.",
description: "Insert code snippet.",
searchTerms: ["codeblock"],
icon: IconCode,
command: ({ editor, range }: CommandProps) =>
editor.chain().focus().deleteRange(range).toggleCodeBlock().run(),
},
{
title: "Divider",
description: "Insert horizontal rule divider",
searchTerms: ["horizontal rule", "hr"],
icon: IconMenu4,
command: ({ editor, range }: CommandProps) =>
editor.chain().focus().deleteRange(range).setHorizontalRule().run(),
},
{
title: "Image",
description: "Upload any image from your device.",

View File

@ -1,6 +1,6 @@
{
"name": "server",
"version": "0.2.10",
"version": "0.3.0",
"description": "",
"author": "",
"private": true,
@ -28,40 +28,38 @@
"test:e2e": "jest --config test/jest-e2e.json"
},
"dependencies": {
"@aws-sdk/client-s3": "^3.600.0",
"@aws-sdk/s3-request-presigner": "^3.600.0",
"@aws-sdk/client-s3": "^3.637.0",
"@aws-sdk/s3-request-presigner": "^3.637.0",
"@casl/ability": "^6.7.1",
"@fastify/cookie": "^9.3.1",
"@fastify/cookie": "^9.4.0",
"@fastify/multipart": "^8.3.0",
"@fastify/static": "^7.0.4",
"@nestjs/bullmq": "^10.1.1",
"@nestjs/common": "^10.3.9",
"@nestjs/config": "^3.2.2",
"@nestjs/core": "^10.3.9",
"@nestjs/bullmq": "^10.2.1",
"@nestjs/common": "^10.4.1",
"@nestjs/config": "^3.2.3",
"@nestjs/core": "^10.4.1",
"@nestjs/event-emitter": "^2.0.4",
"@nestjs/jwt": "^10.2.0",
"@nestjs/mapped-types": "^2.0.5",
"@nestjs/passport": "^10.0.3",
"@nestjs/platform-fastify": "^10.3.9",
"@nestjs/platform-socket.io": "^10.3.9",
"@nestjs/platform-fastify": "^10.4.1",
"@nestjs/platform-socket.io": "^10.4.1",
"@nestjs/terminus": "^10.2.3",
"@nestjs/websockets": "^10.3.9",
"@react-email/components": "0.0.19",
"@react-email/render": "^0.0.15",
"@nestjs/websockets": "^10.4.1",
"@react-email/components": "0.0.24",
"@react-email/render": "^1.0.1",
"@socket.io/redis-adapter": "^8.3.0",
"@types/pg": "^8.11.6",
"bcrypt": "^5.1.1",
"bullmq": "^5.8.2",
"bullmq": "^5.12.12",
"bytes": "^3.1.2",
"class-transformer": "^0.5.1",
"class-validator": "^0.14.1",
"fastify": "^4.28.0",
"fix-esm": "^1.0.1",
"fs-extra": "^11.2.0",
"happy-dom": "^14.12.3",
"kysely": "^0.27.3",
"happy-dom": "^15.7.3",
"kysely": "^0.27.4",
"kysely-migration-cli": "^0.4.2",
"marked": "^13.0.2",
"marked": "^13.0.3",
"mime-types": "^2.1.35",
"nanoid": "^5.0.7",
"nestjs-kysely": "^1.0.0",
@ -69,46 +67,47 @@
"passport-jwt": "^4.0.1",
"pg": "^8.12.0",
"pg-tsquery": "^8.4.2",
"postmark": "^4.0.4",
"postmark": "^4.0.5",
"react": "^18.3.1",
"redis": "^4.6.14",
"redis": "^4.7.0",
"reflect-metadata": "^0.2.2",
"rxjs": "^7.8.1",
"sanitize-filename-ts": "^1.0.2",
"socket.io": "^4.7.5",
"ws": "^8.17.1"
"ws": "^8.18.0"
},
"devDependencies": {
"@nestjs/cli": "^10.3.2",
"@nestjs/schematics": "^10.1.1",
"@nestjs/testing": "^10.3.9",
"@nestjs/cli": "^10.4.5",
"@nestjs/schematics": "^10.1.4",
"@nestjs/testing": "^10.4.1",
"@types/bcrypt": "^5.0.2",
"@types/bytes": "^3.1.4",
"@types/debounce": "^1.2.4",
"@types/fs-extra": "^11.0.4",
"@types/jest": "^29.5.12",
"@types/mime-types": "^2.1.4",
"@types/node": "^20.14.9",
"@types/node": "^22.5.2",
"@types/nodemailer": "^6.4.15",
"@types/passport-jwt": "^4.0.1",
"@types/pg": "^8.11.8",
"@types/supertest": "^6.0.2",
"@types/ws": "^8.5.10",
"@typescript-eslint/eslint-plugin": "^7.14.1",
"@typescript-eslint/parser": "^7.14.1",
"eslint": "^9.5.0",
"@types/ws": "^8.5.12",
"@typescript-eslint/eslint-plugin": "^8.3.0",
"@typescript-eslint/parser": "^8.3.0",
"eslint": "^9.9.1",
"eslint-config-prettier": "^9.1.0",
"eslint-plugin-prettier": "^5.1.3",
"eslint-plugin-prettier": "^5.2.1",
"jest": "^29.7.0",
"kysely-codegen": "^0.15.0",
"prettier": "^3.3.2",
"react-email": "^2.1.4",
"kysely-codegen": "^0.16.3",
"prettier": "^3.3.3",
"react-email": "^3.0.1",
"source-map-support": "^0.5.21",
"supertest": "^7.0.0",
"ts-jest": "^29.1.5",
"ts-jest": "^29.2.5",
"ts-loader": "^9.5.1",
"ts-node": "^10.9.2",
"tsconfig-paths": "^4.2.0",
"typescript": "^5.5.2"
"typescript": "^5.5.4"
},
"jest": {
"moduleFileExtensions": [

View File

@ -10,10 +10,7 @@ export function generateJSON(
const schema = getSchema(extensions);
const window = new Window();
const dom = new HappyDomParser(window).parseFromString(
html,
'text/html',
).body;
const dom = new HappyDomParser().parseFromString(html, 'text/html').body;
// @ts-ignore
return DOMParser.fromSchema(schema).parse(dom, options).toJSON();

View File

@ -5,10 +5,10 @@ export enum AttachmentType {
File = 'file',
}
export const validImageExtensions = ['.jpg', '.png', '.jpeg', 'gif'];
export const validImageExtensions = ['.jpg', '.png', '.jpeg'];
export const MAX_AVATAR_SIZE = '5MB';
export const InlineFileExtensions = [
export const inlineFileExtensions = [
'.jpg',
'.png',
'.jpeg',

View File

@ -30,6 +30,7 @@ import {
import { getMimeType } from '../../common/helpers';
import {
AttachmentType,
inlineFileExtensions,
MAX_AVATAR_SIZE,
MAX_FILE_SIZE,
} from './attachment.constants';
@ -177,6 +178,14 @@ export class AttachmentController {
'Content-Type': attachment.mimeType,
'Cache-Control': 'public, max-age=3600',
});
if (!inlineFileExtensions.includes(attachment.fileExt)) {
res.header(
'Content-Disposition',
`attachment; filename="${attachment.fileName}"`,
);
}
return res.send(fileStream);
} catch (err) {
this.logger.error(err);

View File

@ -19,8 +19,8 @@ export class MailService {
async sendEmail(message: MailMessage): Promise<void> {
if (message.template) {
// in case this method is used directly. we do not send the tsx template from queue
message.html = render(message.template, { pretty: true });
message.text = render(message.template, { plainText: true });
message.html = await render(message.template, { pretty: true });
message.text = await render(message.template, { plainText: true });
}
const sender = `${this.environmentService.getMailFromName()} <${this.environmentService.getMailFromAddress()}> `;
@ -30,8 +30,8 @@ export class MailService {
async sendToQueue(message: MailMessage): Promise<void> {
if (message.template) {
// transform the React object because it gets lost when sent via the queue
message.html = render(message.template, { pretty: true });
message.text = render(message.template, {
message.html = await render(message.template, { pretty: true });
message.text = await render(message.template, {
plainText: true,
});
delete message.template;

View File

@ -1,7 +1,7 @@
{
"name": "docmost",
"homepage": "https://docmost.com",
"version": "0.2.10",
"version": "0.3.0",
"private": true,
"scripts": {
"build": "nx run-many -t build",

4812
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff