Compare commits

..

6 Commits

Author SHA1 Message Date
Philipinho 744d88acac tiptap 3.17.1 2026-01-24 19:45:37 +00:00
Philipinho 7002001340 fix history editor css 2026-01-24 19:07:55 +00:00
Philipinho c72ee1ee8e fix search shortcut 2026-01-24 18:43:45 +00:00
Philipinho b82fdd5f50 Merge branch 'main' into tiptap3-migration 2026-01-24 17:59:57 +00:00
Philip Okugbe 98f71c95fe feat: stream file serving (#1865) 2026-01-24 17:54:56 +00:00
Philipinho 31e66ecf90 fix suggestion menu exit bug 2026-01-24 06:49:33 +00:00
17 changed files with 1035 additions and 1576 deletions
+10 -10
View File
@@ -42,15 +42,15 @@
"mermaid": "^11.12.2", "mermaid": "^11.12.2",
"mitt": "^3.0.1", "mitt": "^3.0.1",
"posthog-js": "^1.255.1", "posthog-js": "^1.255.1",
"react": "^19.2.3", "react": "^18.3.1",
"react-arborist": "3.4.0", "react-arborist": "3.4.0",
"react-clear-modal": "^2.0.17", "react-clear-modal": "^2.0.17",
"react-dom": "^19.2.3", "react-dom": "^18.3.1",
"react-drawio": "^1.0.7", "react-drawio": "^1.0.7",
"react-error-boundary": "^6.1.0", "react-error-boundary": "^4.1.2",
"react-helmet-async": "^2.0.5", "react-helmet-async": "^2.0.5",
"react-i18next": "^15.0.1", "react-i18next": "^15.0.1",
"react-router-dom": "^7.13.0", "react-router-dom": "^7.12.0",
"semver": "^7.7.3", "semver": "^7.7.3",
"socket.io-client": "^4.8.3", "socket.io-client": "^4.8.3",
"tiptap-extension-global-drag-handle": "^0.1.18", "tiptap-extension-global-drag-handle": "^0.1.18",
@@ -63,13 +63,13 @@
"@types/js-cookie": "^3.0.6", "@types/js-cookie": "^3.0.6",
"@types/katex": "^0.16.7", "@types/katex": "^0.16.7",
"@types/node": "22.19.1", "@types/node": "22.19.1",
"@types/react": "^19.2.9", "@types/react": "^18.3.12",
"@types/react-dom": "^19.2.3", "@types/react-dom": "^18.3.1",
"@vitejs/plugin-react": "^5.1.2", "@vitejs/plugin-react": "^5.1.1",
"eslint": "^9.15.0", "eslint": "^9.15.0",
"eslint-plugin-react": "^7.37.5", "eslint-plugin-react": "^7.37.2",
"eslint-plugin-react-hooks": "^7.0.1", "eslint-plugin-react-hooks": "^5.1.0",
"eslint-plugin-react-refresh": "^0.4.26", "eslint-plugin-react-refresh": "^0.4.16",
"globals": "^15.13.0", "globals": "^15.13.0",
"optics-ts": "^2.4.1", "optics-ts": "^2.4.1",
"postcss": "^8.4.49", "postcss": "^8.4.49",
@@ -164,7 +164,7 @@ const MentionList = forwardRef<any, MentionListProps>((props, ref) => {
const enterHandler = () => { const enterHandler = () => {
if (!renderItems.length) return; if (!renderItems.length) return;
if (renderItems[selectedIndex].entityType !== "header") { if (renderItems[selectedIndex]?.entityType !== "header") {
selectItem(selectedIndex); selectItem(selectedIndex);
} }
}; };
@@ -77,7 +77,7 @@ const mentionRenderItems = () => {
{ {
placement: "bottom-start", placement: "bottom-start",
middleware: [offset(0), flip(), shift()], middleware: [offset(0), flip(), shift()],
} },
).then(({ x, y }) => { ).then(({ x, y }) => {
Object.assign(element.style, { Object.assign(element.style, {
left: `${x}px`, left: `${x}px`,
@@ -86,7 +86,7 @@ const mentionRenderItems = () => {
zIndex: "9999", zIndex: "9999",
}); });
}); });
} },
); );
}, },
onUpdate: (props: { onUpdate: (props: {
@@ -115,23 +115,30 @@ const mentionRenderItems = () => {
// destroy component if space is greater 3 without a match // destroy component if space is greater 3 without a match
if ( if (
whitespaceCount > 3 && whitespaceCount > 4 &&
//@ts-ignore //@ts-ignore
props.editor.storage.mentionItems.length === 0 props.editor.storage.mentionItems.length === 1
) { ) {
destroy(); destroy();
return; return;
} }
// fallback exit
if (whitespaceCount > 7) {
destroy();
return;
}
}, },
onKeyDown: (props: { event: KeyboardEvent }) => { onKeyDown: (props: { event: KeyboardEvent }) => {
if (props.event.key) if (props.event.key === "Escape") {
if ( destroy();
props.event.key === "Escape" || return true;
(props.event.key === "Enter" && !component) }
) {
destroy(); if (props.event.key === "Enter" && !component) {
return false; destroy();
} return false;
}
return (component?.ref as any)?.onKeyDown(props); return (component?.ref as any)?.onKeyDown(props);
}, },
onExit: () => { onExit: () => {
@@ -1,7 +1,7 @@
import { BubbleMenu as BaseBubbleMenu } from "@tiptap/react/menus"; import { BubbleMenu as BaseBubbleMenu } from "@tiptap/react/menus";
import { posToDOMRect, findParentNode } from "@tiptap/react"; import { posToDOMRect, findParentNode } from "@tiptap/react";
import { Node as PMNode } from "@tiptap/pm/model"; import { Node as PMNode } from "@tiptap/pm/model";
import React, { JSX, useCallback } from "react"; import React, { useCallback } from "react";
import { ActionIcon, Tooltip } from "@mantine/core"; import { ActionIcon, Tooltip } from "@mantine/core";
import { IconTrash } from "@tabler/icons-react"; import { IconTrash } from "@tabler/icons-react";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
@@ -1,4 +1,4 @@
import React, { JSX, useCallback } from "react"; import React, { useCallback } from "react";
import { import {
EditorMenuProps, EditorMenuProps,
ShouldShowProps, ShouldShowProps,
@@ -1,6 +1,6 @@
import { posToDOMRect, findParentNode } from "@tiptap/react"; import { posToDOMRect, findParentNode } from "@tiptap/react";
import { Node as PMNode } from "@tiptap/pm/model"; import { Node as PMNode } from "@tiptap/pm/model";
import React, { JSX, useCallback } from "react"; import React, { useCallback } from "react";
import { import {
EditorMenuProps, EditorMenuProps,
ShouldShowProps, ShouldShowProps,
@@ -248,7 +248,7 @@ export const mainExtensions = [
Escape: () => { Escape: () => {
const event = new CustomEvent("closeFindDialogFromEditor", {}); const event = new CustomEvent("closeFindDialogFromEditor", {});
document.dispatchEvent(event); document.dispatchEvent(event);
return true; return false;
}, },
}; };
}, },
@@ -1,8 +1,9 @@
import '@/features/editor/styles/index.css'; import "@/features/editor/styles/index.css";
import React, { useEffect } from 'react'; import React, { useEffect } from "react";
import { EditorContent, useEditor } from '@tiptap/react'; import { EditorContent, useEditor } from "@tiptap/react";
import { mainExtensions } from '@/features/editor/extensions/extensions'; import { mainExtensions } from "@/features/editor/extensions/extensions";
import { Title } from '@mantine/core'; import { Title } from "@mantine/core";
import classes from "./history.module.css";
export interface HistoryEditorProps { export interface HistoryEditorProps {
title: string; title: string;
@@ -26,7 +27,9 @@ export function HistoryEditor({ title, content }: HistoryEditorProps) {
<div> <div>
<Title order={1}>{title}</Title> <Title order={1}>{title}</Title>
{editor && <EditorContent editor={editor} />} {editor && (
<EditorContent editor={editor} className={classes.historyEditor} />
)}
</div> </div>
</> </>
); );
@@ -1,37 +1,49 @@
.history { .history {
display: block; display: block;
width: 100%; width: 100%;
padding: var(--mantine-spacing-md); padding: var(--mantine-spacing-md);
color: light-dark(var(--mantine-color-black), var(--mantine-color-dark-0)); color: light-dark(var(--mantine-color-black), var(--mantine-color-dark-0));
@mixin hover { @mixin hover {
background-color: light-dark(var(--mantine-color-gray-2), var(--mantine-color-dark-8)); background-color: light-dark(
} var(--mantine-color-gray-2),
var(--mantine-color-dark-8)
);
}
}
.historyEditor {
:global(.ProseMirror) {
padding: 0 !important;
}
} }
.active { .active {
background-color: light-dark(var(--mantine-color-gray-2), var(--mantine-color-dark-8)); background-color: light-dark(
var(--mantine-color-gray-2),
var(--mantine-color-dark-8)
);
} }
.sidebar { .sidebar {
max-height: rem(700px); max-height: rem(700px);
width: rem(250px); width: rem(250px);
padding: var(--mantine-spacing-sm); padding: var(--mantine-spacing-sm);
display: flex; display: flex;
flex-direction: column; flex-direction: column;
border-right: rem(1px) solid border-right: rem(1px) solid
light-dark(var(--mantine-color-gray-3), var(--mantine-color-dark-4)); light-dark(var(--mantine-color-gray-3), var(--mantine-color-dark-4));
} }
.sidebarFlex { .sidebarFlex {
display: flex; display: flex;
} }
.sidebarMain { .sidebarMain {
flex: 1; flex: 1;
} }
.sidebarRightSection { .sidebarRightSection {
flex: 1; flex: 1;
padding: rem(16px) rem(40px); padding: rem(16px) rem(40px);
} }
@@ -60,6 +60,7 @@ export default function PageHeaderMenu({ readOnly }: PageHeaderMenuProps) {
const event = new CustomEvent("closeFindDialogFromEditor", {}); const event = new CustomEvent("closeFindDialogFromEditor", {});
document.dispatchEvent(event); document.dispatchEvent(event);
}, },
{ preventDefault: false },
], ],
], ],
[], [],
@@ -280,7 +281,9 @@ function ConnectionWarning() {
const timeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null); const timeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null);
useEffect(() => { useEffect(() => {
const isDisconnected = ["disconnected", "connecting"].includes(yjsConnectionStatus); const isDisconnected = ["disconnected", "connecting"].includes(
yjsConnectionStatus,
);
if (isDisconnected) { if (isDisconnected) {
if (!timeoutRef.current) { if (!timeoutRef.current) {
@@ -94,9 +94,9 @@ export default function SpaceTree({ spaceId, readOnly }: SpaceTreeProps) {
spaceId, spaceId,
}); });
const [, setTreeApi] = useAtom<TreeApi<SpaceTreeNode>>(treeApiAtom); const [, setTreeApi] = useAtom<TreeApi<SpaceTreeNode>>(treeApiAtom);
const treeApiRef = useRef<TreeApi<SpaceTreeNode>>(null); const treeApiRef = useRef<TreeApi<SpaceTreeNode>>();
const [openTreeNodes, setOpenTreeNodes] = useAtom<OpenMap>(openTreeNodesAtom); const [openTreeNodes, setOpenTreeNodes] = useAtom<OpenMap>(openTreeNodesAtom);
const rootElement = useRef<HTMLDivElement>(null); const rootElement = useRef<HTMLDivElement>();
const [isRootReady, setIsRootReady] = useState(false); const [isRootReady, setIsRootReady] = useState(false);
const { ref: sizeRef, width, height } = useElementSize(); const { ref: sizeRef, width, height } = useElementSize();
const mergedRef = useMergedRef((element) => { const mergedRef = useMergedRef((element) => {
@@ -36,7 +36,7 @@ export default function SharedTree({ sharedPageTree }: SharedTree) {
const [tree, setTree] = useState< const [tree, setTree] = useState<
TreeApi<SharedPageTreeNode> | null | undefined TreeApi<SharedPageTreeNode> | null | undefined
>(null); >(null);
const rootElement = useRef<HTMLDivElement>(null); const rootElement = useRef<HTMLDivElement>();
const { ref: sizeRef, width, height } = useElementSize(); const { ref: sizeRef, width, height } = useElementSize();
const mergedRef = useMergedRef(rootElement, sizeRef); const mergedRef = useMergedRef(rootElement, sizeRef);
const { pageSlug } = useParams(); const { pageSlug } = useParams();
@@ -181,7 +181,9 @@ export class AttachmentController {
} }
try { try {
const fileStream = await this.storageService.read(attachment.filePath); const fileStream = await this.storageService.readStream(
attachment.filePath,
);
res.headers({ res.headers({
'Content-Type': attachment.mimeType, 'Content-Type': attachment.mimeType,
'Cache-Control': 'private, max-age=3600', 'Cache-Control': 'private, max-age=3600',
@@ -241,7 +243,9 @@ export class AttachmentController {
} }
try { try {
const fileStream = await this.storageService.read(attachment.filePath); const fileStream = await this.storageService.readStream(
attachment.filePath,
);
res.headers({ res.headers({
'Content-Type': attachment.mimeType, 'Content-Type': attachment.mimeType,
'Cache-Control': 'public, max-age=3600', 'Cache-Control': 'public, max-age=3600',
@@ -367,14 +371,14 @@ export class AttachmentController {
const filePath = `${getAttachmentFolderPath(attachmentType, workspace.id)}/${fileName}`; const filePath = `${getAttachmentFolderPath(attachmentType, workspace.id)}/${fileName}`;
try { try {
const fileStream = await this.storageService.read(filePath); const fileStream = await this.storageService.readStream(filePath);
res.headers({ res.headers({
'Content-Type': getMimeType(filePath), 'Content-Type': getMimeType(filePath),
'Cache-Control': 'private, max-age=86400', 'Cache-Control': 'private, max-age=86400',
}); });
return res.send(fileStream); return res.send(fileStream);
} catch (err) { } catch (err) {
// this.logger.error(err); // this.logger.error(err);
throw new NotFoundException('File not found'); throw new NotFoundException('File not found');
} }
} }
@@ -55,7 +55,7 @@ export class ExportController {
throw new ForbiddenException(); throw new ForbiddenException();
} }
const zipFileBuffer = await this.exportService.exportPages( const zipFileStream = await this.exportService.exportPages(
dto.pageId, dto.pageId,
dto.format, dto.format,
dto.includeAttachments, dto.includeAttachments,
@@ -70,7 +70,7 @@ export class ExportController {
'attachment; filename="' + encodeURIComponent(fileName) + '"', 'attachment; filename="' + encodeURIComponent(fileName) + '"',
}); });
res.send(zipFileBuffer); res.send(zipFileStream);
} }
@UseGuards(JwtAuthGuard) @UseGuards(JwtAuthGuard)
@@ -100,6 +100,6 @@ export class ExportController {
'"', '"',
}); });
res.send(exportFile.fileBuffer); res.send(exportFile.fileStream);
} }
} }
@@ -177,7 +177,7 @@ export class ExportService {
const fileName = `${space.name}-space-export.zip`; const fileName = `${space.name}-space-export.zip`;
return { return {
fileBuffer: zipFile, fileStream: zipFile,
fileName, fileName,
}; };
} }
+27 -27
View File
@@ -30,33 +30,33 @@
"@joplin/turndown": "^4.0.74", "@joplin/turndown": "^4.0.74",
"@joplin/turndown-plugin-gfm": "^1.0.56", "@joplin/turndown-plugin-gfm": "^1.0.56",
"@sindresorhus/slugify": "1.1.0", "@sindresorhus/slugify": "1.1.0",
"@tiptap/core": "3.17.0", "@tiptap/core": "3.17.1",
"@tiptap/extension-code-block": "3.17.0", "@tiptap/extension-code-block": "3.17.1",
"@tiptap/extension-collaboration": "3.17.0", "@tiptap/extension-collaboration": "3.17.1",
"@tiptap/extension-collaboration-caret": "3.17.0", "@tiptap/extension-collaboration-caret": "3.17.1",
"@tiptap/extension-color": "3.17.0", "@tiptap/extension-color": "3.17.1",
"@tiptap/extension-document": "3.17.0", "@tiptap/extension-document": "3.17.1",
"@tiptap/extension-heading": "3.17.0", "@tiptap/extension-heading": "3.17.1",
"@tiptap/extension-highlight": "3.17.0", "@tiptap/extension-highlight": "3.17.1",
"@tiptap/extension-history": "3.17.0", "@tiptap/extension-history": "3.17.1",
"@tiptap/extension-image": "3.17.0", "@tiptap/extension-image": "3.17.1",
"@tiptap/extension-link": "3.17.0", "@tiptap/extension-link": "3.17.1",
"@tiptap/extension-list": "3.17.0", "@tiptap/extension-list": "3.17.1",
"@tiptap/extension-placeholder": "3.17.0", "@tiptap/extension-placeholder": "3.17.1",
"@tiptap/extension-subscript": "3.17.0", "@tiptap/extension-subscript": "3.17.1",
"@tiptap/extension-superscript": "3.17.0", "@tiptap/extension-superscript": "3.17.1",
"@tiptap/extension-table": "3.17.0", "@tiptap/extension-table": "3.17.1",
"@tiptap/extension-text": "3.17.0", "@tiptap/extension-text": "3.17.1",
"@tiptap/extension-text-align": "3.17.0", "@tiptap/extension-text-align": "3.17.1",
"@tiptap/extension-text-style": "3.17.0", "@tiptap/extension-text-style": "3.17.1",
"@tiptap/extension-typography": "3.17.0", "@tiptap/extension-typography": "3.17.1",
"@tiptap/extension-unique-id": "^3.17.0", "@tiptap/extension-unique-id": "^3.17.1",
"@tiptap/extension-youtube": "3.17.0", "@tiptap/extension-youtube": "3.17.1",
"@tiptap/html": "3.17.0", "@tiptap/html": "3.17.1",
"@tiptap/pm": "3.17.0", "@tiptap/pm": "3.17.1",
"@tiptap/react": "3.17.0", "@tiptap/react": "3.17.1",
"@tiptap/starter-kit": "3.17.0", "@tiptap/starter-kit": "3.17.1",
"@tiptap/suggestion": "3.17.0", "@tiptap/suggestion": "3.17.1",
"@types/qrcode": "^1.5.5", "@types/qrcode": "^1.5.5",
"bytes": "^3.1.2", "bytes": "^3.1.2",
"cross-env": "^7.0.3", "cross-env": "^7.0.3",
+915 -1485
View File
File diff suppressed because it is too large Load Diff