Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
44 commits
Select commit Hold shift + click to select a range
995536e
Added functionality to allow share to Docmost PWA from Android
gpapp Dec 14, 2025
35b62c8
Added automatic version refresh
gpapp Dec 14, 2025
2a9a27d
Added share to a parent page as an option.
gpapp Dec 14, 2025
821fdd5
Fixed title line duplication
gpapp Dec 14, 2025
97ce787
Added simple page selector
gpapp Dec 14, 2025
c4ec8e4
Fixed title stripping
gpapp Dec 14, 2025
0ae57c3
Streamlined sharing logic to handle title/
gpapp Dec 14, 2025
cac2993
Fixed page name in share target
gpapp Dec 14, 2025
17591b2
Added replace=true, to avoid history poisoning
gpapp Dec 14, 2025
b5c6101
Moving content generation logic
gpapp Dec 14, 2025
d0aa2c5
Trimming content's first line if it matches the title
gpapp Dec 14, 2025
ff57530
Fixing manifest.json icon sizes
gpapp Dec 14, 2025
95c3238
Remembering last used workspace/page
gpapp Dec 14, 2025
717da91
Fixed event listener
gpapp Dec 15, 2025
0b18676
Sanitized input
gpapp Dec 15, 2025
3a58100
Added optional title parameter, not to rely on sanitized filename.
gpapp Dec 15, 2025
ad893bc
Added parentPageId validation
gpapp Dec 15, 2025
c0152c6
Coderabbit recommended sanity fixes
gpapp Dec 15, 2025
7ca8054
Fixed access rights for imports. New pages should require CRETE permisss
gpapp Dec 15, 2025
f162687
Resolved double import issue. Added search whitespace handling
gpapp Dec 15, 2025
c7a0781
Support progress feedback
gpapp Dec 28, 2025
7d8c5c6
Allow upload of large files
gpapp Dec 28, 2025
b8aa94c
Added postgres healthcheck to docker, .env variables to DB setup.
gpapp Dec 28, 2025
e0f8981
Display import progress
gpapp Dec 28, 2025
d5e2ddd
Allow import of invalid URLs, but adding logging.
gpapp Dec 28, 2025
3ee4493
Missed commit
gpapp Dec 28, 2025
135025c
Speed up build using cached pnpm
gpapp Dec 28, 2025
e2955b9
Fixed copying patches
gpapp Dec 28, 2025
6740e29
Handling malformed URLs gracefully
gpapp Dec 28, 2025
ecaa6a1
Adding typescript to editor-ext
gpapp Dec 28, 2025
91d69bb
Increased concurrency
gpapp Dec 28, 2025
4b2439e
Added frozen typescript version
gpapp Dec 28, 2025
c0a330b
Added editor dependencies
gpapp Dec 28, 2025
bb0bd73
Rolling back builder changes
gpapp Dec 28, 2025
529d7dc
Fixing build
gpapp Dec 28, 2025
d4e11c2
Build order fix
gpapp Dec 28, 2025
609dffe
Simplified Dockerfile ownership
gpapp Dec 28, 2025
6b949a1
Reduce build frequency
gpapp Dec 28, 2025
4b408e7
Reduced copy of files in Dockerfile
gpapp Dec 28, 2025
6646095
Fixing permission issue
gpapp Dec 28, 2025
b8c7d2d
Update docker-compose.yml
gpapp Dec 28, 2025
31716a0
feat: Implement attachment service for file and image uploads, suppor…
gpapp Dec 28, 2025
f7f3158
Merge branch 'main' into Support-for-large-file-import,-display-uploa…
gpapp Feb 4, 2026
a340a9a
Merge branch 'docmost:main' into Support-for-large-file-import,-displ…
gpapp Feb 6, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 7 additions & 2 deletions .env.example
Original file line number Diff line number Diff line change
@@ -1,13 +1,18 @@
# your domain, e.g https://example.com
APP_URL=http://localhost:3000
PORT=3000
APP_PORT=3000

# minimum of 32 characters. Generate one with: openssl rand -hex 32
APP_SECRET=REPLACE_WITH_LONG_SECRET

JWT_TOKEN_EXPIRES_IN=30d

DATABASE_URL="postgresql://postgres:password@localhost:5432/docmost?schema=public"

POSTGRES_DB="docmost"
POSTGRES_USER="docmost"
POSTGRES_PASSWORD="docmost"
POSTGRES_PASSWORD_URLENCODE="docmost"

REDIS_URL=redis://127.0.0.1:6379

# options: local | s3
Expand Down
46 changes: 25 additions & 21 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -1,15 +1,24 @@
FROM node:22-slim AS base
LABEL org.opencontainers.image.source="https://github.com/docmost/docmost"
ENV PNPM_HOME="/pnpm"
ENV PATH="$PNPM_HOME:$PATH"
RUN corepack enable && corepack prepare [email protected] --activate
WORKDIR /app

RUN npm install -g [email protected]

FROM base AS builder

WORKDIR /app
COPY .npmrc package.json pnpm*.yaml nx.json /app/
COPY apps/client/package.json /app/apps/client/package.json
COPY apps/server/package.json /app/apps/server/package.json
COPY packages/editor-ext/package.json /app/packages/editor-ext/package.json

COPY . .
COPY patches /app/patches

RUN --mount=type=cache,id=pnpm,target=/pnpm/store pnpm install --frozen-lockfile

RUN pnpm install --frozen-lockfile
COPY . .
RUN pnpm build

FROM base AS installer
Expand All @@ -18,32 +27,27 @@ RUN apt-get update \
&& apt-get install -y --no-install-recommends curl bash \
&& rm -rf /var/lib/apt/lists/*

WORKDIR /app

# Copy apps
COPY --from=builder /app/apps/server/dist /app/apps/server/dist
COPY --from=builder /app/apps/client/dist /app/apps/client/dist
COPY --from=builder /app/apps/server/package.json /app/apps/server/package.json

# Copy packages
COPY --from=builder /app/packages/editor-ext/dist /app/packages/editor-ext/dist
COPY --from=builder /app/packages/editor-ext/package.json /app/packages/editor-ext/package.json
RUN mkdir -p /app/data/storage && chown -R node:node /app

# Copy root package files
COPY --from=builder /app/package.json /app/package.json
COPY --from=builder /app/pnpm*.yaml /app/
COPY --from=builder /app/.npmrc /app/.npmrc
COPY --chown=node:node --from=builder /app/package.json /app/package.json
COPY --chown=node:node --from=builder /app/pnpm*.yaml /app/

# Copy patches
COPY --from=builder /app/patches /app/patches
COPY --chown=node:node --from=builder /app/patches /app/patches

RUN chown -R node:node /app
# Copy apps
COPY --chown=node:node --from=builder /app/apps/server/dist /app/apps/server/dist
COPY --chown=node:node --from=builder /app/apps/client/dist /app/apps/client/dist
COPY --chown=node:node --from=builder /app/apps/server/package.json /app/apps/server/package.json

USER node
# Copy packages
COPY --chown=node:node --from=builder /app/packages/editor-ext/dist /app/packages/editor-ext/dist
COPY --chown=node:node --from=builder /app/packages/editor-ext/package.json /app/packages/editor-ext/package.json

RUN pnpm install --frozen-lockfile --prod
USER node

RUN mkdir -p /app/data/storage
RUN --mount=type=cache,id=pnpm,target=/pnpm/store pnpm install --frozen-lockfile --prod

VOLUME ["/app/data/storage"]

Expand Down
16 changes: 13 additions & 3 deletions apps/client/public/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,22 @@
{
"src": "icons/app-icon-192x192.png",
"type": "image/png",
"sizes": "180x180 192x192"
"sizes": "192x192"
},
{
"src": "icons/app-icon-512x512.png",
"type": "image/png",
"sizes": "512x512"
}
]
}
],
"share_target": {
"action": "/share-target",
"method": "POST",
"enctype": "multipart/form-data",
"params": {
"title": "title",
"text": "text",
"url": "url"
}
}
}
41 changes: 41 additions & 0 deletions apps/client/public/sw.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
const VERSION = 'v4'; // Update this to trigger re-install

self.addEventListener('install', (event) => {
event.waitUntil(self.skipWaiting());
});

self.addEventListener('activate', (event) => {
event.waitUntil(clients.claim());
});

self.addEventListener('fetch', (event) => {
const url = new URL(event.request.url);

if (event.request.method === 'POST' && url.pathname === '/share-target') {
event.respondWith(
(async () => {
let data = { title: "", text: "", url: "" };
try {
const formData = await event.request.formData();
for (const [key, value] of formData.entries()) {
if (key in data && typeof value === "string") data[key] = value;
}
} catch (e) {
// If parsing fails, still redirect to UI (which can show "no content").
}

// Store in Cache API
const cache = await caches.open('share-target');
await cache.put(
'shared-content',
new Response(JSON.stringify(data), {
headers: { 'Content-Type': 'application/json' },
})
);

// Redirect to the client-side route
return Response.redirect('/share-target', 303);
})()
);
}
});
3 changes: 3 additions & 0 deletions apps/client/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ import SpaceTrash from "@/pages/space/space-trash.tsx";
import UserApiKeys from "@/ee/api-key/pages/user-api-keys";
import WorkspaceApiKeys from "@/ee/api-key/pages/workspace-api-keys";
import AiSettings from "@/ee/ai/pages/ai-settings.tsx";
import ShareTarget from "@/pages/share-target/share-target.tsx";

export default function App() {
const { t } = useTranslation();
Expand All @@ -59,6 +60,8 @@ export default function App() {
<Route path={"/setup/register"} element={<SetupWorkspace />} />
)}

<Route path={"/share-target"} element={<ShareTarget />} />

{isCloud() && (
<>
<Route path={"/create"} element={<CreateWorkspace />} />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,61 @@ import { notifications } from "@mantine/notifications";
import { getFileUploadSizeLimit } from "@/lib/config.ts";
import { formatBytes } from "@/lib";
import i18n from "@/i18n.ts";
import { Progress, Text } from "@mantine/core";
import React from "react";

export const uploadAttachmentAction = handleAttachmentUpload({
onUpload: async (file: File, pageId: string): Promise<any> => {
const notificationId = `upload-${file.name}-${Date.now()}`;
notifications.show({
id: notificationId,
title: i18n.t("Uploading attachment"),
message: i18n.t("Uploading {{fileName}}...", { fileName: file.name }),
loading: true,
autoClose: false,
withCloseButton: false,
});

try {
return await uploadFile(file, pageId);
const result = await uploadFile(file, pageId, undefined, (progressEvent) => {
const percentCompleted = Math.round(
(progressEvent.loaded * 100) / (progressEvent.total || file.size)
);

notifications.update({
id: notificationId,
title: i18n.t("Uploading attachment"),
message: (
<div>
<Text size="xs" mb={5}>
{i18n.t("Uploading {{fileName}}", { fileName: file.name })} (
{percentCompleted}%)
</Text>
<Progress
value={percentCompleted}
size="sm"
radius="xl"
animated
/>
</div>
),
loading: true,
autoClose: false,
withCloseButton: false,
});
});

notifications.hide(notificationId);
return result;
} catch (err) {
notifications.show({
notifications.update({
id: notificationId,
color: "red",
message: err?.response.data.message,
title: i18n.t("Upload failed"),
message: err?.response?.data?.message || i18n.t("Failed to upload attachment"),
loading: false,
autoClose: 5000,
withCloseButton: true,
});
throw err;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,61 @@ import { notifications } from "@mantine/notifications";
import { getFileUploadSizeLimit } from "@/lib/config.ts";
import { formatBytes } from "@/lib";
import i18n from "@/i18n.ts";
import { Progress, Text } from "@mantine/core";
import React from "react";

export const uploadImageAction = handleImageUpload({
onUpload: async (file: File, pageId: string): Promise<any> => {
const notificationId = `upload-${file.name}-${Date.now()}`;
notifications.show({
id: notificationId,
title: i18n.t("Uploading image"),
message: i18n.t("Uploading {{fileName}}...", { fileName: file.name }),
loading: true,
autoClose: false,
withCloseButton: false,
});

try {
return await uploadFile(file, pageId);
const result = await uploadFile(file, pageId, undefined, (progressEvent) => {
const percentCompleted = Math.round(
(progressEvent.loaded * 100) / (progressEvent.total || file.size)
);

notifications.update({
id: notificationId,
title: i18n.t("Uploading image"),
message: (
<div>
<Text size="xs" mb={5}>
{i18n.t("Uploading {{fileName}}", { fileName: file.name })} (
{percentCompleted}%)
</Text>
<Progress
value={percentCompleted}
size="sm"
radius="xl"
animated
/>
</div>
),
loading: true,
autoClose: false,
withCloseButton: false,
});
});

notifications.hide(notificationId);
return result;
} catch (err) {
notifications.show({
notifications.update({
id: notificationId,
color: "red",
message: err?.response.data.message,
title: i18n.t("Upload failed"),
message: err?.response?.data?.message || i18n.t("Failed to upload image"),
loading: false,
autoClose: 5000,
withCloseButton: true,
});
throw err;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,61 @@ import { notifications } from "@mantine/notifications";
import { getFileUploadSizeLimit } from "@/lib/config.ts";
import { formatBytes } from "@/lib";
import i18n from "@/i18n.ts";
import { Progress, Text } from "@mantine/core";
import React from "react";

export const uploadVideoAction = handleVideoUpload({
onUpload: async (file: File, pageId: string): Promise<any> => {
const notificationId = `upload-${file.name}-${Date.now()}`;
notifications.show({
id: notificationId,
title: i18n.t("Uploading video"),
message: i18n.t("Uploading {{fileName}}...", { fileName: file.name }),
loading: true,
autoClose: false,
withCloseButton: false,
});

try {
return await uploadFile(file, pageId);
const result = await uploadFile(file, pageId, undefined, (progressEvent) => {
const percentCompleted = Math.round(
(progressEvent.loaded * 100) / (progressEvent.total || file.size)
);

notifications.update({
id: notificationId,
title: i18n.t("Uploading video"),
message: (
<div>
<Text size="xs" mb={5}>
{i18n.t("Uploading {{fileName}}", { fileName: file.name })} (
{percentCompleted}%)
</Text>
<Progress
value={percentCompleted}
size="sm"
radius="xl"
animated
/>
</div>
),
loading: true,
autoClose: false,
withCloseButton: false,
});
});

notifications.hide(notificationId);
return result;
} catch (err) {
notifications.show({
notifications.update({
id: notificationId,
color: "red",
message: err?.response.data.message,
title: i18n.t("Upload failed"),
message: err?.response?.data?.message || i18n.t("Failed to upload video"),
loading: false,
autoClose: 5000,
withCloseButton: true,
});
throw err;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ export interface IFileTask {
type: "import" | "export";
source: string;
status: string;
progress?: number;
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

# First, let's verify the server-side type definition
cat apps/server/src/database/types/db.d.ts | head -100

Repository: docmost/docmost

Length of output: 2566


🏁 Script executed:

# Check the client-side type definition
cat apps/client/src/features/file-task/types/file-task.types.ts

Repository: docmost/docmost

Length of output: 432


🏁 Script executed:

# Search for progress field usage in client code
rg -n -A3 -B3 "\.progress\b" --type=ts --type=tsx -g 'apps/client/**'

Repository: docmost/docmost

Length of output: 86


🏁 Script executed:

# Search for progress field assignments or transformations
rg -n -A3 -B3 "progress:" --type=ts --type=tsx -g 'apps/client/**'

Repository: docmost/docmost

Length of output: 86


🏁 Script executed:

# Check if there are any null checks or optional chaining for progress
rg -n "progress\?" --type=ts --type=tsx -g 'apps/client/**'

Repository: docmost/docmost

Length of output: 86


🏁 Script executed:

# Search for FileTasks interface in db.d.ts
rg -n "interface FileTasks" apps/server/src/database/types/db.d.ts -A 20

Repository: docmost/docmost

Length of output: 671


🏁 Script executed:

# Search for progress field usage in client code (correct file type)
rg -n "\.progress" -t ts -g 'apps/client/**' -A 3 -B 3

Repository: docmost/docmost

Length of output: 782


🏁 Script executed:

# Search for progress field in assignments/declarations
rg -n "progress" -t ts -g 'apps/client/**' -A 2 -B 2

Repository: docmost/docmost

Length of output: 15046


🏁 Script executed:

# Check if there's any explicit null handling for progress
rg -n "progress.*null|null.*progress" -t ts -g 'apps/client/**'

Repository: docmost/docmost

Length of output: 41


Update type to allow null values from server.

The server-side FileTasks type defines progress: Generated<number | null> (db.d.ts:172), which can return null values. The client-side IFileTask type should reflect this as progress?: number | null instead of progress?: number. While the code at page-import-modal.tsx:204 already handles this case with const progress = fileTask.progress || 0, the type definition should be accurate to prevent type-checking issues in other code paths that consume this interface.

🤖 Prompt for AI Agents
In apps/client/src/features/file-task/types/file-task.types.ts around line 6,
the IFileTask progress field is currently declared as "progress?: number" but
the server can return null (Generated<number | null>); update the type to
"progress?: number | null" to reflect nullable responses and run TypeScript
checks to fix any call sites that assume non-null (e.g., coerce or default to 0
where needed).

fileName: string;
filePath: string;
fileSize: number;
Expand Down
Loading