mirror of
https://github.com/docmost/docmost.git
synced 2026-06-16 06:57:01 +08:00
Merge branch 'main' into feat/collab-redis
This commit is contained in:
@@ -118,7 +118,14 @@ export async function exportPage(data: IExportPageParams): Promise<void> {
|
|||||||
.split("filename=")[1]
|
.split("filename=")[1]
|
||||||
.replace(/"/g, "");
|
.replace(/"/g, "");
|
||||||
|
|
||||||
saveAs(req.data, decodeURIComponent(fileName));
|
let decodedFileName = fileName;
|
||||||
|
try {
|
||||||
|
decodedFileName = decodeURIComponent(fileName);
|
||||||
|
} catch (err) {
|
||||||
|
// fallback to raw filename
|
||||||
|
}
|
||||||
|
|
||||||
|
saveAs(req.data, decodedFileName);
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function importPage(file: File, spaceId: string) {
|
export async function importPage(file: File, spaceId: string) {
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import { useNavigate } from "react-router-dom";
|
|||||||
import { SpaceSelect } from "./space-select";
|
import { SpaceSelect } from "./space-select";
|
||||||
import { getSpaceUrl } from "@/lib/config";
|
import { getSpaceUrl } from "@/lib/config";
|
||||||
import { Button, Popover, Text } from "@mantine/core";
|
import { Button, Popover, Text } from "@mantine/core";
|
||||||
import { IconChevronDown } from "@tabler/icons-react";
|
import { IconChevronDown, IconChevronUp } from "@tabler/icons-react";
|
||||||
import { useDisclosure } from "@mantine/hooks";
|
import { useDisclosure } from "@mantine/hooks";
|
||||||
import { CustomAvatar } from "@/components/ui/custom-avatar.tsx";
|
import { CustomAvatar } from "@/components/ui/custom-avatar.tsx";
|
||||||
import { AvatarIconType } from "@/features/attachments/types/attachment.types.ts";
|
import { AvatarIconType } from "@/features/attachments/types/attachment.types.ts";
|
||||||
@@ -21,7 +21,7 @@ export function SwitchSpace({
|
|||||||
spaceIcon,
|
spaceIcon,
|
||||||
}: SwitchSpaceProps) {
|
}: SwitchSpaceProps) {
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const [opened, { close, open, toggle }] = useDisclosure(false);
|
const [opened, { close, toggle }] = useDisclosure(false);
|
||||||
|
|
||||||
const handleSelect = (value: string) => {
|
const handleSelect = (value: string) => {
|
||||||
if (value) {
|
if (value) {
|
||||||
@@ -44,9 +44,9 @@ export function SwitchSpace({
|
|||||||
variant="subtle"
|
variant="subtle"
|
||||||
fullWidth
|
fullWidth
|
||||||
justify="space-between"
|
justify="space-between"
|
||||||
rightSection={<IconChevronDown size={18} />}
|
rightSection={opened ? <IconChevronUp size={18} /> : <IconChevronDown size={18} />}
|
||||||
color="gray"
|
color="gray"
|
||||||
onClick={open}
|
onClick={toggle}
|
||||||
>
|
>
|
||||||
<CustomAvatar
|
<CustomAvatar
|
||||||
name={spaceName}
|
name={spaceName}
|
||||||
|
|||||||
@@ -69,5 +69,12 @@ export async function exportSpace(data: IExportSpaceParams): Promise<void> {
|
|||||||
.split("filename=")[1]
|
.split("filename=")[1]
|
||||||
.replace(/"/g, "");
|
.replace(/"/g, "");
|
||||||
|
|
||||||
saveAs(req.data, decodeURIComponent(fileName));
|
let decodedFileName = fileName;
|
||||||
|
try {
|
||||||
|
decodedFileName = decodeURIComponent(fileName);
|
||||||
|
} catch (err) {
|
||||||
|
// fallback to raw filename
|
||||||
|
}
|
||||||
|
|
||||||
|
saveAs(req.data, decodedFileName);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,13 +5,14 @@ const CONTEXTS_TO_IGNORE = [
|
|||||||
'InstanceLoader',
|
'InstanceLoader',
|
||||||
'RoutesResolver',
|
'RoutesResolver',
|
||||||
'RouterExplorer',
|
'RouterExplorer',
|
||||||
|
'LegacyRouteConverter',
|
||||||
'WebSocketsController',
|
'WebSocketsController',
|
||||||
];
|
];
|
||||||
|
|
||||||
export function createPinoConfig(): Params {
|
export function createPinoConfig(): Params {
|
||||||
const isProduction = process.env.NODE_ENV === 'production';
|
const isProduction = process.env.NODE_ENV?.toLowerCase() === 'production';
|
||||||
const isDebugMode = process.env.DEBUG_MODE === 'true';
|
const isDebugMode = process.env.DEBUG_MODE?.toLowerCase() === 'true';
|
||||||
const logHttp = process.env.LOG_HTTP === 'true';
|
const logHttp = process.env.LOG_HTTP?.toLowerCase() === 'true';
|
||||||
|
|
||||||
const level = isProduction && !isDebugMode ? 'info' : 'debug';
|
const level = isProduction && !isDebugMode ? 'info' : 'debug';
|
||||||
|
|
||||||
@@ -32,14 +33,20 @@ export function createPinoConfig(): Params {
|
|||||||
: undefined,
|
: undefined,
|
||||||
formatters: {
|
formatters: {
|
||||||
level: (label) => ({ level: label }),
|
level: (label) => ({ level: label }),
|
||||||
log: (object: Record<string, unknown>) => {
|
},
|
||||||
|
hooks: {
|
||||||
|
logMethod(inputArgs, method) {
|
||||||
if (isProduction && !isDebugMode) {
|
if (isProduction && !isDebugMode) {
|
||||||
const context = object['context'] as string | undefined;
|
for (const arg of inputArgs) {
|
||||||
if (context && CONTEXTS_TO_IGNORE.includes(context)) {
|
if (typeof arg === 'object' && arg !== null && 'context' in arg) {
|
||||||
return { filtered: true };
|
const context = (arg as Record<string, unknown>)['context'];
|
||||||
|
if (typeof context === 'string' && CONTEXTS_TO_IGNORE.includes(context)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return object;
|
return method.apply(this, inputArgs);
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
serializers: {
|
serializers: {
|
||||||
|
|||||||
+1
-1
Submodule apps/server/src/ee updated: b6844b019c...88e3d01f81
@@ -1,4 +1,5 @@
|
|||||||
import { jsonToNode } from 'src/collaboration/collaboration.util';
|
import { jsonToNode } from 'src/collaboration/collaboration.util';
|
||||||
|
import { Logger } from '@nestjs/common';
|
||||||
import { ExportFormat } from './dto/export-dto';
|
import { ExportFormat } from './dto/export-dto';
|
||||||
import { Node } from '@tiptap/pm/model';
|
import { Node } from '@tiptap/pm/model';
|
||||||
import { validate as isValidUUID } from 'uuid';
|
import { validate as isValidUUID } from 'uuid';
|
||||||
@@ -88,7 +89,7 @@ export function replaceInternalLinks(
|
|||||||
// if link and text are same, use page title
|
// if link and text are same, use page title
|
||||||
if (markLink === node.text) {
|
if (markLink === node.text) {
|
||||||
//@ts-expect-error
|
//@ts-expect-error
|
||||||
node.text = getInternalLinkPageName(relativePath);
|
node.text = getInternalLinkPageName(relativePath, currentPagePath);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -99,10 +100,19 @@ export function replaceInternalLinks(
|
|||||||
return doc.toJSON();
|
return doc.toJSON();
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getInternalLinkPageName(path: string): string {
|
export function getInternalLinkPageName(path: string, currentFilePath?: string): string {
|
||||||
return decodeURIComponent(
|
const name = path?.split('/').pop().split('.').slice(0, -1).join('.');
|
||||||
path?.split('/').pop().split('.').slice(0, -1).join('.'),
|
try {
|
||||||
);
|
return decodeURIComponent(name);
|
||||||
|
} catch (err) {
|
||||||
|
if (currentFilePath) {
|
||||||
|
Logger.warn(
|
||||||
|
`URI malformed in page ${currentFilePath}: ${name}. Falling back to raw name.`,
|
||||||
|
'ExportUtils',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return name;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function extractPageSlugId(input: string): string {
|
export function extractPageSlugId(input: string): string {
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { getEmbedUrlAndProvider } from '@docmost/editor-ext';
|
import { getEmbedUrlAndProvider } from '@docmost/editor-ext';
|
||||||
|
import { Logger } from '@nestjs/common';
|
||||||
import * as path from 'path';
|
import * as path from 'path';
|
||||||
import { v7 } from 'uuid';
|
import { v7 } from 'uuid';
|
||||||
import { InsertableBacklink } from '@docmost/db/types/entity.types';
|
import { InsertableBacklink } from '@docmost/db/types/entity.types';
|
||||||
@@ -280,8 +281,18 @@ export async function rewriteInternalLinksToMentionHtml(
|
|||||||
const $a = $(el);
|
const $a = $(el);
|
||||||
const raw = $a.attr('href')!;
|
const raw = $a.attr('href')!;
|
||||||
if (raw.startsWith('http') || raw.startsWith('/api/')) return;
|
if (raw.startsWith('http') || raw.startsWith('/api/')) return;
|
||||||
|
let decodedRaw = raw;
|
||||||
|
try {
|
||||||
|
decodedRaw = decodeURIComponent(raw);
|
||||||
|
} catch (err) {
|
||||||
|
Logger.warn(
|
||||||
|
`URI malformed in page ${currentFilePath}: ${raw}. Falling back to raw path.`,
|
||||||
|
'ImportFormatter',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
const resolved = normalize(
|
const resolved = normalize(
|
||||||
path.join(path.dirname(currentFilePath), decodeURIComponent(raw)),
|
path.join(path.dirname(currentFilePath), decodedRaw),
|
||||||
);
|
);
|
||||||
const meta = filePathToPageMetaMap.get(resolved);
|
const meta = filePathToPageMetaMap.get(resolved);
|
||||||
if (!meta) return;
|
if (!meta) return;
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import { Logger } from '@nestjs/common';
|
||||||
import { promises as fs } from 'fs';
|
import { promises as fs } from 'fs';
|
||||||
import * as path from 'path';
|
import * as path from 'path';
|
||||||
|
|
||||||
@@ -30,8 +31,13 @@ export function resolveRelativeAttachmentPath(
|
|||||||
pageDir: string,
|
pageDir: string,
|
||||||
attachmentCandidates: Map<string, string>,
|
attachmentCandidates: Map<string, string>,
|
||||||
): string | null {
|
): string | null {
|
||||||
const mainRel = decodeURIComponent(raw.replace(/^\.?\/+/, ''));
|
let mainRel = raw.replace(/^\.?\/+/, '');
|
||||||
const fallback = path.normalize(path.join(pageDir, mainRel));
|
try {
|
||||||
|
mainRel = decodeURIComponent(mainRel);
|
||||||
|
} catch (err) {
|
||||||
|
Logger.warn(`URI malformed for attachment path: ${mainRel}. Falling back to raw path.`, 'ImportUtils');
|
||||||
|
}
|
||||||
|
const fallback = path.normalize(path.join(pageDir, mainRel)).split(path.sep).join('/');
|
||||||
|
|
||||||
if (attachmentCandidates.has(mainRel)) {
|
if (attachmentCandidates.has(mainRel)) {
|
||||||
return mainRel;
|
return mainRel;
|
||||||
|
|||||||
Reference in New Issue
Block a user