mirror of
https://github.com/docmost/docmost.git
synced 2026-05-08 23:33:09 +08:00
Compare commits
14 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 6f92ecedf6 | |||
| 7e16c39f9a | |||
| 03714a8bec | |||
| 3003befe07 | |||
| 5c3942c159 | |||
| e0809e7104 | |||
| da6793ac87 | |||
| 08e94eb3c1 | |||
| 5a14186f1c | |||
| 6a0bb8d4cb | |||
| fba9f4cb2b | |||
| d8f7c4a822 | |||
| 202685b39f | |||
| fc4a428208 |
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "client",
|
||||
"private": true,
|
||||
"version": "0.25.0-beta.1",
|
||||
"version": "0.25.1",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
"build": "tsc && vite build",
|
||||
|
||||
@@ -123,6 +123,8 @@
|
||||
"page": "Seite",
|
||||
"Page deleted successfully": "Seite erfolgreich gelöscht",
|
||||
"Page history": "Seitengeschichte",
|
||||
"Select version": "Version auswählen",
|
||||
"Highlight changes": "Änderungen hervorheben",
|
||||
"Page import is in progress. Please do not close this tab.": "Der Seitenimport läuft. Bitte schließen Sie diesen Tab nicht.",
|
||||
"Pages": "Seiten",
|
||||
"pages": "Seiten",
|
||||
|
||||
@@ -123,10 +123,7 @@
|
||||
"page": "page",
|
||||
"Page deleted successfully": "Page deleted successfully",
|
||||
"Page history": "Page history",
|
||||
"Version history for": "Version history for",
|
||||
"document": "document",
|
||||
"Select version": "Select version",
|
||||
"Close": "Close",
|
||||
"Highlight changes": "Highlight changes",
|
||||
"Page import is in progress. Please do not close this tab.": "Page import is in progress. Please do not close this tab.",
|
||||
"Pages": "Pages",
|
||||
|
||||
@@ -123,6 +123,8 @@
|
||||
"page": "página",
|
||||
"Page deleted successfully": "Página eliminada con éxito",
|
||||
"Page history": "Historial de la página",
|
||||
"Select version": "Seleccionar versión",
|
||||
"Highlight changes": "Resaltar cambios",
|
||||
"Page import is in progress. Please do not close this tab.": "La importación de la página está en curso. Por favor, no cierre esta pestaña.",
|
||||
"Pages": "Páginas",
|
||||
"pages": "páginas",
|
||||
|
||||
@@ -123,6 +123,8 @@
|
||||
"page": "page",
|
||||
"Page deleted successfully": "Page supprimée avec succès",
|
||||
"Page history": "Historique de la page",
|
||||
"Select version": "Sélectionner la version",
|
||||
"Highlight changes": "Mettre en évidence les changements",
|
||||
"Page import is in progress. Please do not close this tab.": "L'importation de la page est en cours. Veuillez ne pas fermer cet onglet.",
|
||||
"Pages": "Pages",
|
||||
"pages": "pages",
|
||||
|
||||
@@ -123,6 +123,8 @@
|
||||
"page": "pagina",
|
||||
"Page deleted successfully": "Pagina eliminata con successo",
|
||||
"Page history": "Cronologia della pagina",
|
||||
"Select version": "Seleziona versione",
|
||||
"Highlight changes": "Evidenzia modifiche",
|
||||
"Page import is in progress. Please do not close this tab.": "L'importazione della pagina è in corso. Si prega di non chiudere questa scheda.",
|
||||
"Pages": "Pagine",
|
||||
"pages": "pagine",
|
||||
|
||||
@@ -123,6 +123,8 @@
|
||||
"page": "ページ",
|
||||
"Page deleted successfully": "ページを削除しました",
|
||||
"Page history": "ページ履歴",
|
||||
"Select version": "バージョンを選択",
|
||||
"Highlight changes": "変更を強調表示",
|
||||
"Page import is in progress. Please do not close this tab.": "ページをインポート中です。このタブを閉じないでください",
|
||||
"Pages": "ページ",
|
||||
"pages": "ページ",
|
||||
|
||||
@@ -123,6 +123,8 @@
|
||||
"page": "페이지",
|
||||
"Page deleted successfully": "페이지 삭제 완료",
|
||||
"Page history": "페이지 기록",
|
||||
"Select version": "버전 선택",
|
||||
"Highlight changes": "변경 사항 강조",
|
||||
"Page import is in progress. Please do not close this tab.": "페이지 가져오기가 진행 중입니다. 이 탭을 닫지 마세요.",
|
||||
"Pages": "페이지",
|
||||
"pages": "페이지",
|
||||
|
||||
@@ -123,6 +123,8 @@
|
||||
"page": "pagina",
|
||||
"Page deleted successfully": "Pagina succesvol verwijderd",
|
||||
"Page history": "Pagina geschiedenis",
|
||||
"Select version": "Selecteer versie",
|
||||
"Highlight changes": "Wijzigingen markeren",
|
||||
"Page import is in progress. Please do not close this tab.": "Importeren van pagina's is bezig. Sluit dit tabblad niet.",
|
||||
"Pages": "Pagina's",
|
||||
"pages": "pagina's",
|
||||
|
||||
@@ -123,6 +123,8 @@
|
||||
"page": "página",
|
||||
"Page deleted successfully": "Página excluída com sucesso",
|
||||
"Page history": "Histórico da página",
|
||||
"Select version": "Selecionar versão",
|
||||
"Highlight changes": "Destacar alterações",
|
||||
"Page import is in progress. Please do not close this tab.": "A importação da página está em andamento. Por favor, não feche esta aba.",
|
||||
"Pages": "Páginas",
|
||||
"pages": "páginas",
|
||||
|
||||
@@ -123,6 +123,8 @@
|
||||
"page": "страница",
|
||||
"Page deleted successfully": "Страница успешно удалена",
|
||||
"Page history": "История страницы",
|
||||
"Select version": "Выбрать версию",
|
||||
"Highlight changes": "Выделить изменения",
|
||||
"Page import is in progress. Please do not close this tab.": "Импорт страницы в процессе. Пожалуйста, не закрывайте эту вкладку.",
|
||||
"Pages": "Страницы",
|
||||
"pages": "страницы",
|
||||
|
||||
@@ -123,6 +123,8 @@
|
||||
"page": "сторінка",
|
||||
"Page deleted successfully": "Сторінку успішно видалено",
|
||||
"Page history": "Історія сторінки",
|
||||
"Select version": "Вибрати версію",
|
||||
"Highlight changes": "Підсвітити зміни",
|
||||
"Page import is in progress. Please do not close this tab.": "Імпорт сторінки в процесі. Будь ласка, не закривайте цю вкладку.",
|
||||
"Pages": "Сторінки",
|
||||
"pages": "сторінки",
|
||||
|
||||
@@ -123,6 +123,8 @@
|
||||
"page": "个页面",
|
||||
"Page deleted successfully": "页面已成功删除",
|
||||
"Page history": "页面历史",
|
||||
"Select version": "选择版本",
|
||||
"Highlight changes": "突出显示更改",
|
||||
"Page import is in progress. Please do not close this tab.": "页面导入正在进行中。请不要关闭此标签页。",
|
||||
"Pages": "页面",
|
||||
"pages": "个页面",
|
||||
|
||||
@@ -170,6 +170,8 @@ const CommandGroups: SlashMenuGroupedItemsType = {
|
||||
input.type = "file";
|
||||
input.accept = "image/*";
|
||||
input.multiple = true;
|
||||
input.style.display = "none";
|
||||
document.body.appendChild(input);
|
||||
input.onchange = async () => {
|
||||
if (input.files?.length) {
|
||||
for (const file of input.files) {
|
||||
@@ -179,8 +181,7 @@ const CommandGroups: SlashMenuGroupedItemsType = {
|
||||
}
|
||||
}
|
||||
|
||||
// Reset the input value to allow uploading the same file again if needed
|
||||
input.value = "";
|
||||
input.remove();
|
||||
};
|
||||
input.click();
|
||||
},
|
||||
@@ -202,6 +203,8 @@ const CommandGroups: SlashMenuGroupedItemsType = {
|
||||
input.type = "file";
|
||||
input.accept = "video/*";
|
||||
input.multiple = true;
|
||||
input.style.display = "none";
|
||||
document.body.appendChild(input);
|
||||
input.onchange = async () => {
|
||||
if (input.files?.length) {
|
||||
for (const file of input.files) {
|
||||
@@ -211,8 +214,7 @@ const CommandGroups: SlashMenuGroupedItemsType = {
|
||||
}
|
||||
}
|
||||
|
||||
// Reset the input value to allow uploading the same file again if needed
|
||||
input.value = "";
|
||||
input.remove();
|
||||
};
|
||||
input.click();
|
||||
},
|
||||
@@ -234,6 +236,8 @@ const CommandGroups: SlashMenuGroupedItemsType = {
|
||||
input.type = "file";
|
||||
input.accept = "";
|
||||
input.multiple = true;
|
||||
input.style.display = "none";
|
||||
document.body.appendChild(input);
|
||||
input.onchange = async () => {
|
||||
if (input.files?.length) {
|
||||
for (const file of input.files) {
|
||||
@@ -243,8 +247,7 @@ const CommandGroups: SlashMenuGroupedItemsType = {
|
||||
}
|
||||
}
|
||||
|
||||
// Reset the input value to allow uploading the same file again if needed
|
||||
input.value = "";
|
||||
input.remove();
|
||||
};
|
||||
input.click();
|
||||
},
|
||||
|
||||
@@ -3,7 +3,7 @@ import React, { useCallback, useEffect, useMemo, useRef } from "react";
|
||||
import { EditorProvider } from "@tiptap/react";
|
||||
import { mainExtensions } from "@/features/editor/extensions/extensions";
|
||||
import { Document } from "@tiptap/extension-document";
|
||||
import { Heading, generateNodeId, UniqueID } from "@docmost/editor-ext";
|
||||
import { Heading, UniqueID } from "@docmost/editor-ext";
|
||||
import { Text } from "@tiptap/extension-text";
|
||||
import { Placeholder } from "@tiptap/extension-placeholder";
|
||||
import { useAtom } from "jotai";
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
}
|
||||
|
||||
.mantine-AppShell-main {
|
||||
padding-top: 0 !important;
|
||||
padding: 0 !important;
|
||||
min-height: auto !important;
|
||||
}
|
||||
|
||||
|
||||
@@ -157,7 +157,9 @@ export function TitleEditor({
|
||||
|
||||
useEffect(() => {
|
||||
setTimeout(() => {
|
||||
titleEditor?.commands.focus("end");
|
||||
// guard against Cannot access view['hasFocus'] error
|
||||
if (!titleEditor?.isInitialized) return;
|
||||
titleEditor?.commands?.focus("end");
|
||||
}, 500);
|
||||
}, [titleEditor]);
|
||||
|
||||
|
||||
@@ -1,38 +0,0 @@
|
||||
.diffSummary {
|
||||
border: rem(1px) solid
|
||||
light-dark(var(--mantine-color-gray-3), var(--mantine-color-dark-4));
|
||||
border-radius: rem(10px);
|
||||
padding: rem(12px);
|
||||
background: light-dark(
|
||||
var(--mantine-color-gray-0),
|
||||
var(--mantine-color-dark-7)
|
||||
);
|
||||
}
|
||||
|
||||
:global(.history-diff-added) {
|
||||
background: light-dark(#e1f3f2, #01654a) !important;
|
||||
color: light-dark(#007b69, #cafff7) !important;
|
||||
-webkit-box-decoration-break: clone;
|
||||
box-decoration-break: clone;
|
||||
}
|
||||
|
||||
:global(.history-diff-deleted) {
|
||||
text-decoration: line-through;
|
||||
color: light-dark(var(--mantine-color-red-7), var(--mantine-color-red-4));
|
||||
background: light-dark(var(--mantine-color-red-0), rgba(255, 0, 0, 0.1));
|
||||
border-radius: rem(2px);
|
||||
padding: 0 rem(2px);
|
||||
}
|
||||
|
||||
:global(.history-diff-node-added) {
|
||||
outline: rem(2px) solid light-dark(var(--mantine-color-teal-5), var(--mantine-color-teal-7));
|
||||
outline-offset: rem(2px);
|
||||
border-radius: rem(4px);
|
||||
}
|
||||
|
||||
:global(.history-diff-node-deleted) {
|
||||
opacity: 0.5;
|
||||
outline: rem(2px) dashed light-dark(var(--mantine-color-red-4), var(--mantine-color-red-6));
|
||||
outline-offset: rem(4px);
|
||||
border-radius: rem(4px);
|
||||
}
|
||||
@@ -16,6 +16,36 @@
|
||||
:global(.ProseMirror) {
|
||||
padding: 0 !important;
|
||||
}
|
||||
|
||||
& :global(.history-diff-added) {
|
||||
background: light-dark(#e1f3f2, #01654a) !important;
|
||||
color: light-dark(#007b69, #cafff7) !important;
|
||||
-webkit-box-decoration-break: clone;
|
||||
box-decoration-break: clone;
|
||||
}
|
||||
|
||||
& :global(.history-diff-deleted) {
|
||||
text-decoration: line-through;
|
||||
color: light-dark(var(--mantine-color-red-7), var(--mantine-color-red-4));
|
||||
background: light-dark(var(--mantine-color-red-0), rgba(255, 0, 0, 0.1));
|
||||
border-radius: rem(2px);
|
||||
padding: 0 rem(2px);
|
||||
}
|
||||
|
||||
& :global(.history-diff-node-added) {
|
||||
outline: rem(2px) solid
|
||||
light-dark(var(--mantine-color-teal-5), var(--mantine-color-teal-7));
|
||||
outline-offset: rem(2px);
|
||||
border-radius: rem(4px);
|
||||
}
|
||||
|
||||
& :global(.history-diff-node-deleted) {
|
||||
opacity: 0.5;
|
||||
outline: rem(2px) dashed
|
||||
light-dark(var(--mantine-color-red-4), var(--mantine-color-red-6));
|
||||
outline-offset: rem(4px);
|
||||
border-radius: rem(4px);
|
||||
}
|
||||
}
|
||||
|
||||
.active {
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import "@/features/editor/styles/index.css";
|
||||
import "./css/history-diff.module.css";
|
||||
import { useEffect } from "react";
|
||||
import { EditorContent, useEditor } from "@tiptap/react";
|
||||
import { mainExtensions } from "@/features/editor/extensions/extensions";
|
||||
|
||||
@@ -157,7 +157,7 @@ function HistoryList({ pageId }: Props) {
|
||||
size="compact-md"
|
||||
onClick={() => setHistoryModalOpen(false)}
|
||||
>
|
||||
{t("Close")}
|
||||
{t("Cancel")}
|
||||
</Button>
|
||||
<Button size="compact-md" onClick={confirmRestore}>
|
||||
{t("Restore")}
|
||||
|
||||
@@ -157,7 +157,7 @@ export default function HistoryModalMobile({ pageId, pageTitle }: Props) {
|
||||
{canRestore && (
|
||||
<Group className={classes.actionButtons} justify="flex-end" gap="sm">
|
||||
<Button variant="default" onClick={() => setHistoryModalOpen(false)}>
|
||||
{t("Close")}
|
||||
{t("Cancel")}
|
||||
</Button>
|
||||
<Button onClick={confirmRestore}>{t("Restore")}</Button>
|
||||
</Group>
|
||||
|
||||
+11
-11
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "server",
|
||||
"version": "0.25.0-beta.1",
|
||||
"version": "0.25.1",
|
||||
"description": "",
|
||||
"author": "",
|
||||
"private": true,
|
||||
@@ -33,28 +33,28 @@
|
||||
"@ai-sdk/google": "^3.0.9",
|
||||
"@ai-sdk/openai": "^3.0.11",
|
||||
"@ai-sdk/openai-compatible": "^2.0.12",
|
||||
"@aws-sdk/client-s3": "3.701.0",
|
||||
"@aws-sdk/lib-storage": "3.701.0",
|
||||
"@aws-sdk/s3-request-presigner": "3.701.0",
|
||||
"@aws-sdk/client-s3": "3.982.0",
|
||||
"@aws-sdk/lib-storage": "3.982.0",
|
||||
"@aws-sdk/s3-request-presigner": "3.982.0",
|
||||
"@fastify/cookie": "^11.0.2",
|
||||
"@fastify/multipart": "^9.3.0",
|
||||
"@fastify/static": "^8.3.0",
|
||||
"@langchain/core": "1.1.13",
|
||||
"@fastify/multipart": "^9.4.0",
|
||||
"@fastify/static": "^9.0.0",
|
||||
"@langchain/core": "1.1.18",
|
||||
"@langchain/textsplitters": "1.0.1",
|
||||
"@nestjs-labs/nestjs-ioredis": "^11.0.4",
|
||||
"@nestjs/bullmq": "^11.0.4",
|
||||
"@nestjs/common": "^11.1.11",
|
||||
"@nestjs/config": "^4.0.2",
|
||||
"@nestjs/core": "^11.1.11",
|
||||
"@nestjs/core": "^11.1.13",
|
||||
"@nestjs/event-emitter": "^3.0.1",
|
||||
"@nestjs/jwt": "11.0.0",
|
||||
"@nestjs/mapped-types": "^2.1.0",
|
||||
"@nestjs/passport": "^11.0.5",
|
||||
"@nestjs/platform-fastify": "^11.1.11",
|
||||
"@nestjs/platform-socket.io": "^11.1.11",
|
||||
"@nestjs/platform-fastify": "^11.1.13",
|
||||
"@nestjs/platform-socket.io": "^11.1.13",
|
||||
"@nestjs/schedule": "^6.1.0",
|
||||
"@nestjs/terminus": "^11.0.0",
|
||||
"@nestjs/websockets": "^11.1.11",
|
||||
"@nestjs/websockets": "^11.1.13",
|
||||
"@node-saml/passport-saml": "^5.1.0",
|
||||
"@react-email/components": "0.0.28",
|
||||
"@react-email/render": "1.0.2",
|
||||
|
||||
@@ -17,13 +17,13 @@ import {
|
||||
UseInterceptors,
|
||||
} from '@nestjs/common';
|
||||
import { AttachmentService } from './services/attachment.service';
|
||||
import { FastifyReply } from 'fastify';
|
||||
import { FastifyReply, FastifyRequest } from 'fastify';
|
||||
import { FileInterceptor } from '../../common/interceptors/file.interceptor';
|
||||
import * as bytes from 'bytes';
|
||||
import { AuthUser } from '../../common/decorators/auth-user.decorator';
|
||||
import { AuthWorkspace } from '../../common/decorators/auth-workspace.decorator';
|
||||
import { JwtAuthGuard } from '../../common/guards/jwt-auth.guard';
|
||||
import { User, Workspace } from '@docmost/db/types/entity.types';
|
||||
import { Attachment, User, Workspace } from '@docmost/db/types/entity.types';
|
||||
import { StorageService } from '../../integrations/storage/storage.service';
|
||||
import {
|
||||
getAttachmentFolderPath,
|
||||
@@ -151,6 +151,7 @@ export class AttachmentController {
|
||||
@UseGuards(JwtAuthGuard)
|
||||
@Get('/files/:fileId/:fileName')
|
||||
async getFile(
|
||||
@Req() req: FastifyRequest,
|
||||
@Res() res: FastifyReply,
|
||||
@AuthUser() user: User,
|
||||
@AuthWorkspace() workspace: Workspace,
|
||||
@@ -181,22 +182,7 @@ export class AttachmentController {
|
||||
}
|
||||
|
||||
try {
|
||||
const fileStream = await this.storageService.readStream(
|
||||
attachment.filePath,
|
||||
);
|
||||
res.headers({
|
||||
'Content-Type': attachment.mimeType,
|
||||
'Cache-Control': 'private, max-age=3600',
|
||||
});
|
||||
|
||||
if (!inlineFileExtensions.includes(attachment.fileExt)) {
|
||||
res.header(
|
||||
'Content-Disposition',
|
||||
`attachment; filename="${encodeURIComponent(attachment.fileName)}"`,
|
||||
);
|
||||
}
|
||||
|
||||
return res.send(fileStream);
|
||||
return await this.sendFileResponse(req, res, attachment, 'private');
|
||||
} catch (err) {
|
||||
this.logger.error(err);
|
||||
throw new NotFoundException('File not found');
|
||||
@@ -205,6 +191,7 @@ export class AttachmentController {
|
||||
|
||||
@Get('/files/public/:fileId/:fileName')
|
||||
async getPublicFile(
|
||||
@Req() req: FastifyRequest,
|
||||
@Res() res: FastifyReply,
|
||||
@AuthWorkspace() workspace: Workspace,
|
||||
@Param('fileId') fileId: string,
|
||||
@@ -243,22 +230,7 @@ export class AttachmentController {
|
||||
}
|
||||
|
||||
try {
|
||||
const fileStream = await this.storageService.readStream(
|
||||
attachment.filePath,
|
||||
);
|
||||
res.headers({
|
||||
'Content-Type': attachment.mimeType,
|
||||
'Cache-Control': 'public, max-age=3600',
|
||||
});
|
||||
|
||||
if (!inlineFileExtensions.includes(attachment.fileExt)) {
|
||||
res.header(
|
||||
'Content-Disposition',
|
||||
`attachment; filename="${encodeURIComponent(attachment.fileName)}"`,
|
||||
);
|
||||
}
|
||||
|
||||
return res.send(fileStream);
|
||||
return await this.sendFileResponse(req, res, attachment, 'public');
|
||||
} catch (err) {
|
||||
this.logger.error(err);
|
||||
throw new NotFoundException('File not found');
|
||||
@@ -433,4 +405,69 @@ export class AttachmentController {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
private async sendFileResponse(
|
||||
req: FastifyRequest,
|
||||
res: FastifyReply,
|
||||
attachment: Attachment,
|
||||
cacheScope: 'private' | 'public',
|
||||
) {
|
||||
const fileSize = Number(attachment.fileSize);
|
||||
const rangeHeader = req.headers.range;
|
||||
|
||||
res.header('Accept-Ranges', 'bytes');
|
||||
|
||||
if (!inlineFileExtensions.includes(attachment.fileExt)) {
|
||||
res.header(
|
||||
'Content-Disposition',
|
||||
`attachment; filename="${encodeURIComponent(attachment.fileName)}"`,
|
||||
);
|
||||
}
|
||||
|
||||
if (rangeHeader && fileSize) {
|
||||
const match = rangeHeader.match(/bytes=(\d+)-(\d*)/);
|
||||
if (match) {
|
||||
const start = parseInt(match[1], 10);
|
||||
const end = match[2]
|
||||
? Math.min(parseInt(match[2], 10), fileSize - 1)
|
||||
: fileSize - 1;
|
||||
|
||||
if (start >= fileSize || start > end) {
|
||||
res.status(416);
|
||||
res.header('Content-Range', `bytes */${fileSize}`);
|
||||
return res.send();
|
||||
}
|
||||
|
||||
const fileStream = await this.storageService.readRangeStream(
|
||||
attachment.filePath,
|
||||
{ start, end },
|
||||
);
|
||||
|
||||
res.status(206);
|
||||
res.headers({
|
||||
'Content-Type': attachment.mimeType,
|
||||
'Content-Range': `bytes ${start}-${end}/${fileSize}`,
|
||||
'Content-Length': end - start + 1,
|
||||
'Cache-Control': `${cacheScope}, max-age=3600`,
|
||||
});
|
||||
|
||||
return res.send(fileStream);
|
||||
}
|
||||
}
|
||||
|
||||
const fileStream = await this.storageService.readStream(
|
||||
attachment.filePath,
|
||||
);
|
||||
|
||||
res.headers({
|
||||
'Content-Type': attachment.mimeType,
|
||||
'Cache-Control': `${cacheScope}, max-age=3600`,
|
||||
});
|
||||
|
||||
if (fileSize) {
|
||||
res.header('Content-Length', fileSize);
|
||||
}
|
||||
|
||||
return res.send(fileStream);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -73,6 +73,20 @@ export class LocalDriver implements StorageDriver {
|
||||
}
|
||||
}
|
||||
|
||||
async readRangeStream(
|
||||
filePath: string,
|
||||
range: { start: number; end: number },
|
||||
): Promise<Readable> {
|
||||
try {
|
||||
return createReadStream(this._fullPath(filePath), {
|
||||
start: range.start,
|
||||
end: range.end,
|
||||
});
|
||||
} catch (err) {
|
||||
throw new Error(`Failed to read file: ${(err as Error).message}`);
|
||||
}
|
||||
}
|
||||
|
||||
async exists(filePath: string): Promise<boolean> {
|
||||
try {
|
||||
return await fs.pathExists(this._fullPath(filePath));
|
||||
|
||||
@@ -13,6 +13,7 @@ import { Readable } from 'stream';
|
||||
import { getSignedUrl } from '@aws-sdk/s3-request-presigner';
|
||||
import { getMimeType } from '../../../common/helpers';
|
||||
import { Upload } from '@aws-sdk/lib-storage';
|
||||
import { Logger } from '@nestjs/common';
|
||||
|
||||
export class S3Driver implements StorageDriver {
|
||||
private readonly s3Client: S3Client;
|
||||
@@ -39,6 +40,7 @@ export class S3Driver implements StorageDriver {
|
||||
|
||||
await upload.done();
|
||||
} catch (err) {
|
||||
Logger.error(err);
|
||||
throw new Error(`Failed to upload file: ${(err as Error).message}`);
|
||||
}
|
||||
}
|
||||
@@ -73,6 +75,7 @@ export class S3Driver implements StorageDriver {
|
||||
|
||||
await upload.done();
|
||||
} catch (err) {
|
||||
Logger.error(err);
|
||||
throw new Error(`Failed to upload file: ${(err as Error).message}`);
|
||||
} finally {
|
||||
if (shouldDestroyClient && clientToUse) {
|
||||
@@ -127,6 +130,25 @@ export class S3Driver implements StorageDriver {
|
||||
}
|
||||
}
|
||||
|
||||
async readRangeStream(
|
||||
filePath: string,
|
||||
range: { start: number; end: number },
|
||||
): Promise<Readable> {
|
||||
try {
|
||||
const command = new GetObjectCommand({
|
||||
Bucket: this.config.bucket,
|
||||
Key: filePath,
|
||||
Range: `bytes=${range.start}-${range.end}`,
|
||||
});
|
||||
|
||||
const response = await this.s3Client.send(command);
|
||||
|
||||
return response.Body as Readable;
|
||||
} catch (err) {
|
||||
throw new Error(`Failed to read file from S3: ${(err as Error).message}`);
|
||||
}
|
||||
}
|
||||
|
||||
async exists(filePath: string): Promise<boolean> {
|
||||
try {
|
||||
const command = new HeadObjectCommand({
|
||||
|
||||
@@ -11,6 +11,11 @@ export interface StorageDriver {
|
||||
|
||||
readStream(filePath: string): Promise<Readable>;
|
||||
|
||||
readRangeStream(
|
||||
filePath: string,
|
||||
range: { start: number; end: number },
|
||||
): Promise<Readable>;
|
||||
|
||||
exists(filePath: string): Promise<boolean>;
|
||||
|
||||
getUrl(filePath: string): string;
|
||||
|
||||
@@ -33,6 +33,13 @@ export class StorageService {
|
||||
return this.storageDriver.readStream(filePath);
|
||||
}
|
||||
|
||||
async readRangeStream(
|
||||
filePath: string,
|
||||
range: { start: number; end: number },
|
||||
): Promise<Readable> {
|
||||
return this.storageDriver.readRangeStream(filePath, range);
|
||||
}
|
||||
|
||||
async exists(filePath: string): Promise<boolean> {
|
||||
return this.storageDriver.exists(filePath);
|
||||
}
|
||||
|
||||
+4
-4
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "docmost",
|
||||
"homepage": "https://docmost.com",
|
||||
"version": "0.25.0-beta.1",
|
||||
"version": "0.25.1",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"build": "nx run-many -t build",
|
||||
@@ -23,9 +23,9 @@
|
||||
"@casl/ability": "6.8.0",
|
||||
"@docmost/editor-ext": "workspace:*",
|
||||
"@floating-ui/dom": "^1.7.3",
|
||||
"@hocuspocus/provider": "3.4.3",
|
||||
"@hocuspocus/server": "3.4.3",
|
||||
"@hocuspocus/transformer": "3.4.3",
|
||||
"@hocuspocus/provider": "3.4.4",
|
||||
"@hocuspocus/server": "3.4.4",
|
||||
"@hocuspocus/transformer": "3.4.4",
|
||||
"@joplin/turndown": "^4.0.74",
|
||||
"@joplin/turndown-plugin-gfm": "^1.0.56",
|
||||
"@sindresorhus/slugify": "1.1.0",
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import { imageDimensionsFromStream } from "image-dimensions";
|
||||
import { MediaUploadOptions, UploadFn } from "../media-utils";
|
||||
import { IAttachment } from "../types";
|
||||
import { generateNodeId } from "../utils";
|
||||
import { Node } from "@tiptap/pm/model";
|
||||
import { Command } from "@tiptap/core";
|
||||
import { imageDimensionsFromData } from 'image-dimensions';
|
||||
import { MediaUploadOptions, UploadFn } from '../media-utils';
|
||||
import { IAttachment } from '../types';
|
||||
import { generateNodeId } from '../utils';
|
||||
import { Node } from '@tiptap/pm/model';
|
||||
import { Command } from '@tiptap/core';
|
||||
|
||||
const findImageNodeByPlaceholderId = (
|
||||
doc: Node,
|
||||
@@ -14,7 +14,7 @@ const findImageNodeByPlaceholderId = (
|
||||
doc.descendants((node, pos) => {
|
||||
if (result) return false;
|
||||
if (
|
||||
node.type.name === "image" &&
|
||||
node.type.name === 'image' &&
|
||||
node.attrs.placeholder?.id === placeholderId
|
||||
) {
|
||||
result = { node, pos };
|
||||
@@ -34,7 +34,11 @@ const handleImageUpload =
|
||||
if (!validated) return;
|
||||
|
||||
const objectUrl = URL.createObjectURL(file);
|
||||
const imageDimensions = await imageDimensionsFromStream(file.stream());
|
||||
|
||||
const imageDimensions = imageDimensionsFromData(
|
||||
new Uint8Array(await file.arrayBuffer()),
|
||||
);
|
||||
|
||||
const placeholderId = generateNodeId();
|
||||
const aspectRatio = imageDimensions
|
||||
? imageDimensions.width / imageDimensions.height
|
||||
|
||||
Generated
+1094
-1083
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user