Merge branch 'docmost:main' into main

This commit is contained in:
Orion
2024-09-03 09:17:00 +10:00
committed by GitHub
13 changed files with 1912 additions and 3200 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

4812
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff