mirror of
https://github.com/docmost/docmost.git
synced 2026-05-08 23:33:09 +08:00
Compare commits
47 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 87590af0a0 | |||
| 66f11f85ec | |||
| f40f4daa1a | |||
| 4d5e23cad2 | |||
| 38d0556ac3 | |||
| 3e93e57fbf | |||
| 4fc35cecc7 | |||
| 6aff7b84f2 | |||
| 75673ad964 | |||
| 3157131bf2 | |||
| 793d61a13e | |||
| 81cceb483a | |||
| e755207c3b | |||
| 353ec2559a | |||
| f4a877081a | |||
| c4bf0ac0b5 | |||
| 40626578b1 | |||
| d65321f5e5 | |||
| c3488608a8 | |||
| 051bc80ab7 | |||
| 78d363febb | |||
| 8681d9a8c4 | |||
| b9543b01bd | |||
| 5510434221 | |||
| f671e7a3b9 | |||
| 974bcea690 | |||
| 601ed88931 | |||
| cfbaedcd63 | |||
| 5fc04aa7df | |||
| c357f169e1 | |||
| 1cbd2854bb | |||
| 3af1482a31 | |||
| d31d1f7bbd | |||
| cc0146d0cd | |||
| 83ce9cf240 | |||
| e7e85e9fdd | |||
| 2d710612b1 | |||
| a0814ef49a | |||
| bf17289ab2 | |||
| c2cd412ac7 | |||
| 71dfcf6bce | |||
| 23e8ab032e | |||
| 0e1d4e5eee | |||
| 6f83f32d5c | |||
| 63ea2f7663 | |||
| 66a3dad632 | |||
| 2adc6a60d2 |
@@ -172,10 +172,6 @@ function ImportFormatSelection({ spaceId, onClose }: ImportFormatSelection) {
|
|||||||
queryKey: ["root-sidebar-pages", fileTask.spaceId],
|
queryKey: ["root-sidebar-pages", fileTask.spaceId],
|
||||||
});
|
});
|
||||||
|
|
||||||
await queryClient.invalidateQueries({
|
|
||||||
queryKey: ["recent-changes", fileTask.spaceId],
|
|
||||||
});
|
|
||||||
|
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
emit({
|
emit({
|
||||||
operation: "refetchRootTreeNodeEvent",
|
operation: "refetchRootTreeNodeEvent",
|
||||||
|
|||||||
@@ -6,7 +6,6 @@ import { ISpace } from "../types/space.types";
|
|||||||
import { useNavigate } from "react-router-dom";
|
import { useNavigate } from "react-router-dom";
|
||||||
import APP_ROUTE from "@/lib/app-route";
|
import APP_ROUTE from "@/lib/app-route";
|
||||||
import { Trans, useTranslation } from "react-i18next";
|
import { Trans, useTranslation } from "react-i18next";
|
||||||
import { useState } from "react";
|
|
||||||
|
|
||||||
interface DeleteSpaceModalProps {
|
interface DeleteSpaceModalProps {
|
||||||
space: ISpace;
|
space: ISpace;
|
||||||
@@ -15,7 +14,6 @@ interface DeleteSpaceModalProps {
|
|||||||
export default function DeleteSpaceModal({ space }: DeleteSpaceModalProps) {
|
export default function DeleteSpaceModal({ space }: DeleteSpaceModalProps) {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const [opened, { open, close }] = useDisclosure(false);
|
const [opened, { open, close }] = useDisclosure(false);
|
||||||
const [isDeleting, setIsDeleting] = useState(false);
|
|
||||||
const deleteSpaceMutation = useDeleteSpaceMutation();
|
const deleteSpaceMutation = useDeleteSpaceMutation();
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
|
|
||||||
@@ -37,15 +35,12 @@ export default function DeleteSpaceModal({ space }: DeleteSpaceModalProps) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
setIsDeleting(true);
|
|
||||||
try {
|
try {
|
||||||
// pass slug too so we can clear the local cache
|
// pass slug too so we can clear the local cache
|
||||||
await deleteSpaceMutation.mutateAsync({ id: space.id, slug: space.slug });
|
await deleteSpaceMutation.mutateAsync({ id: space.id, slug: space.slug });
|
||||||
navigate(APP_ROUTE.HOME);
|
navigate(APP_ROUTE.HOME);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Failed to delete space", error);
|
console.error("Failed to delete space", error);
|
||||||
} finally {
|
|
||||||
setIsDeleting(false);
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -84,7 +79,7 @@ export default function DeleteSpaceModal({ space }: DeleteSpaceModalProps) {
|
|||||||
<Button onClick={close} variant="default">
|
<Button onClick={close} variant="default">
|
||||||
{t("Cancel")}
|
{t("Cancel")}
|
||||||
</Button>
|
</Button>
|
||||||
<Button onClick={handleDelete} color="red" loading={isDeleting}>
|
<Button onClick={handleDelete} color="red">
|
||||||
{t("Confirm")}
|
{t("Confirm")}
|
||||||
</Button>
|
</Button>
|
||||||
</Group>
|
</Group>
|
||||||
|
|||||||
@@ -41,8 +41,7 @@ import { generateHTML, generateJSON } from '../common/helpers/prosemirror/html';
|
|||||||
// see: https://github.com/ueberdosis/tiptap/issues/5352
|
// see: https://github.com/ueberdosis/tiptap/issues/5352
|
||||||
// see:https://github.com/ueberdosis/tiptap/issues/4089
|
// see:https://github.com/ueberdosis/tiptap/issues/4089
|
||||||
//import { generateJSON } from '@tiptap/html';
|
//import { generateJSON } from '@tiptap/html';
|
||||||
import { Node, Schema } from '@tiptap/pm/model';
|
import { Node } from '@tiptap/pm/model';
|
||||||
import { Logger } from '@nestjs/common';
|
|
||||||
|
|
||||||
export const tiptapExtensions = [
|
export const tiptapExtensions = [
|
||||||
StarterKit.configure({
|
StarterKit.configure({
|
||||||
@@ -111,53 +110,9 @@ export function jsonToText(tiptapJson: JSONContent) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function jsonToNode(tiptapJson: JSONContent) {
|
export function jsonToNode(tiptapJson: JSONContent) {
|
||||||
const schema = getSchema(tiptapExtensions);
|
return Node.fromJSON(getSchema(tiptapExtensions), tiptapJson);
|
||||||
try {
|
|
||||||
return Node.fromJSON(schema, tiptapJson);
|
|
||||||
} catch (error) {
|
|
||||||
if (
|
|
||||||
error instanceof RangeError &&
|
|
||||||
error.message.includes('Unknown node type')
|
|
||||||
) {
|
|
||||||
Logger.warn('Stripping unknown node types from document:', error.message);
|
|
||||||
const cleanedJson = stripUnknownNodes(tiptapJson, schema);
|
|
||||||
return Node.fromJSON(schema, cleanedJson);
|
|
||||||
}
|
|
||||||
throw error;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getPageId(documentName: string) {
|
export function getPageId(documentName: string) {
|
||||||
return documentName.split('.')[1];
|
return documentName.split('.')[1];
|
||||||
}
|
}
|
||||||
|
|
||||||
function stripUnknownNodes(
|
|
||||||
json: JSONContent,
|
|
||||||
schema: Schema,
|
|
||||||
): JSONContent | null {
|
|
||||||
if (!json || typeof json !== 'object') return json;
|
|
||||||
|
|
||||||
// Recursively clean children first, flattening any unwrapped content
|
|
||||||
if (json.content && Array.isArray(json.content)) {
|
|
||||||
const newContent: JSONContent[] = [];
|
|
||||||
for (const child of json.content) {
|
|
||||||
const cleaned = stripUnknownNodes(child, schema);
|
|
||||||
if (Array.isArray(cleaned)) {
|
|
||||||
newContent.push(...cleaned);
|
|
||||||
} else if (cleaned) {
|
|
||||||
newContent.push(cleaned);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
json.content = newContent;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check if this node is unknown AFTER processing children
|
|
||||||
if (json.type && !schema.nodes[json.type]) {
|
|
||||||
// Unwrap: return cleaned children directly instead of wrapping
|
|
||||||
return (
|
|
||||||
json.content && json.content.length > 0 ? json.content : null
|
|
||||||
) as any;
|
|
||||||
}
|
|
||||||
|
|
||||||
return json;
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -79,8 +79,6 @@ export class RedisSyncExtension<TCE extends CustomEvents> implements Extension {
|
|||||||
this.customEvents = (customEvents as any) ?? ({} as any as CustomEvents);
|
this.customEvents = (customEvents as any) ?? ({} as any as CustomEvents);
|
||||||
this.sub.subscribe(this.msgChannel, `${this.msgChannel}:${this.serverId}`);
|
this.sub.subscribe(this.msgChannel, `${this.msgChannel}:${this.serverId}`);
|
||||||
this.sub.on('messageBuffer', this.handleRedisMessage);
|
this.sub.on('messageBuffer', this.handleRedisMessage);
|
||||||
this.pub.on('error', () => {});
|
|
||||||
this.sub.on('error', () => {});
|
|
||||||
}
|
}
|
||||||
private getKey(documentName: string) {
|
private getKey(documentName: string) {
|
||||||
return `${this.lockPrefix}:${documentName}`;
|
return `${this.lockPrefix}:${documentName}`;
|
||||||
|
|||||||
@@ -19,8 +19,7 @@ async function bootstrap() {
|
|||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
{
|
{
|
||||||
logger: false,
|
bufferLogs: true,
|
||||||
bufferLogs: false,
|
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@@ -1,16 +0,0 @@
|
|||||||
export type ExportPageMetadata = {
|
|
||||||
pageId: string;
|
|
||||||
slugId: string;
|
|
||||||
icon: string | null;
|
|
||||||
position: string;
|
|
||||||
parentPath: string | null;
|
|
||||||
createdAt: string;
|
|
||||||
updatedAt: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
export type ExportMetadata = {
|
|
||||||
exportedAt: string;
|
|
||||||
source: 'docmost';
|
|
||||||
version: string;
|
|
||||||
pages: Record<string, ExportPageMetadata>;
|
|
||||||
};
|
|
||||||
@@ -422,8 +422,6 @@ export class PageRepo {
|
|||||||
'parentPageId',
|
'parentPageId',
|
||||||
'spaceId',
|
'spaceId',
|
||||||
'workspaceId',
|
'workspaceId',
|
||||||
'createdAt',
|
|
||||||
'updatedAt',
|
|
||||||
])
|
])
|
||||||
.$if(opts?.includeContent, (qb) => qb.select('content'))
|
.$if(opts?.includeContent, (qb) => qb.select('content'))
|
||||||
.where('id', '=', parentPageId)
|
.where('id', '=', parentPageId)
|
||||||
@@ -440,8 +438,6 @@ export class PageRepo {
|
|||||||
'p.parentPageId',
|
'p.parentPageId',
|
||||||
'p.spaceId',
|
'p.spaceId',
|
||||||
'p.workspaceId',
|
'p.workspaceId',
|
||||||
'p.createdAt',
|
|
||||||
'p.updatedAt',
|
|
||||||
])
|
])
|
||||||
.$if(opts?.includeContent, (qb) => qb.select('p.content'))
|
.$if(opts?.includeContent, (qb) => qb.select('p.content'))
|
||||||
.innerJoin('page_hierarchy as ph', 'p.parentPageId', 'ph.id')
|
.innerJoin('page_hierarchy as ph', 'p.parentPageId', 'ph.id')
|
||||||
|
|||||||
@@ -20,17 +20,11 @@ import {
|
|||||||
replaceInternalLinks,
|
replaceInternalLinks,
|
||||||
updateAttachmentUrlsToLocalPaths,
|
updateAttachmentUrlsToLocalPaths,
|
||||||
} from './utils';
|
} from './utils';
|
||||||
import {
|
|
||||||
ExportMetadata,
|
|
||||||
ExportPageMetadata,
|
|
||||||
} from '../../common/helpers/types/export-metadata.types';
|
|
||||||
import { PageRepo } from '@docmost/db/repos/page/page.repo';
|
import { PageRepo } from '@docmost/db/repos/page/page.repo';
|
||||||
import { Node } from '@tiptap/pm/model';
|
import { Node } from '@tiptap/pm/model';
|
||||||
import { EditorState } from '@tiptap/pm/state';
|
import { EditorState } from '@tiptap/pm/state';
|
||||||
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
||||||
import slugify = require('@sindresorhus/slugify');
|
import slugify = require('@sindresorhus/slugify');
|
||||||
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
||||||
const packageJson = require('../../../package.json');
|
|
||||||
import { EnvironmentService } from '../environment/environment.service';
|
import { EnvironmentService } from '../environment/environment.service';
|
||||||
import {
|
import {
|
||||||
getAttachmentIds,
|
getAttachmentIds,
|
||||||
@@ -161,17 +155,12 @@ export class ExportService {
|
|||||||
'pages.id',
|
'pages.id',
|
||||||
'pages.slugId',
|
'pages.slugId',
|
||||||
'pages.title',
|
'pages.title',
|
||||||
'pages.icon',
|
|
||||||
'pages.position',
|
|
||||||
'pages.content',
|
'pages.content',
|
||||||
'pages.parentPageId',
|
'pages.parentPageId',
|
||||||
'pages.spaceId',
|
'pages.spaceId',
|
||||||
'pages.workspaceId',
|
'pages.workspaceId',
|
||||||
'pages.createdAt',
|
|
||||||
'pages.updatedAt',
|
|
||||||
])
|
])
|
||||||
.where('spaceId', '=', spaceId)
|
.where('spaceId', '=', spaceId)
|
||||||
.where('deletedAt', 'is', null)
|
|
||||||
.execute();
|
.execute();
|
||||||
|
|
||||||
const tree = buildTree(pages as Page[]);
|
const tree = buildTree(pages as Page[]);
|
||||||
@@ -200,12 +189,10 @@ export class ExportService {
|
|||||||
includeAttachments: boolean,
|
includeAttachments: boolean,
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
const slugIdToPath: Record<string, string> = {};
|
const slugIdToPath: Record<string, string> = {};
|
||||||
const pageIdToFilePath: Record<string, string> = {};
|
|
||||||
const pagesMetadata: Record<string, ExportPageMetadata> = {};
|
|
||||||
|
|
||||||
computeLocalPath(tree, format, null, '', slugIdToPath);
|
computeLocalPath(tree, format, null, '', slugIdToPath);
|
||||||
|
|
||||||
const stack: { folder: JSZip; parentPageId: string | null }[] = [
|
const stack: { folder: JSZip; parentPageId: string }[] = [
|
||||||
{ folder: zip, parentPageId: null },
|
{ folder: zip, parentPageId: null },
|
||||||
];
|
];
|
||||||
|
|
||||||
@@ -245,35 +232,12 @@ export class ExportService {
|
|||||||
`${pageTitle}${getExportExtension(format)}`,
|
`${pageTitle}${getExportExtension(format)}`,
|
||||||
pageExportContent,
|
pageExportContent,
|
||||||
);
|
);
|
||||||
|
|
||||||
pageIdToFilePath[page.id] = currentPagePath;
|
|
||||||
|
|
||||||
const parentPath = parentPageId ? pageIdToFilePath[parentPageId] : null;
|
|
||||||
pagesMetadata[currentPagePath] = {
|
|
||||||
pageId: page.id,
|
|
||||||
slugId: page.slugId,
|
|
||||||
icon: page.icon ?? null,
|
|
||||||
position: page.position,
|
|
||||||
parentPath,
|
|
||||||
createdAt: page.createdAt?.toISOString() ?? new Date().toISOString(),
|
|
||||||
updatedAt: page.updatedAt?.toISOString() ?? new Date().toISOString(),
|
|
||||||
};
|
|
||||||
|
|
||||||
if (childPages.length > 0) {
|
if (childPages.length > 0) {
|
||||||
const pageFolder = folder.folder(pageTitle);
|
const pageFolder = folder.folder(pageTitle);
|
||||||
stack.push({ folder: pageFolder, parentPageId: page.id });
|
stack.push({ folder: pageFolder, parentPageId: page.id });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const metadata: ExportMetadata = {
|
|
||||||
exportedAt: new Date().toISOString(),
|
|
||||||
source: 'docmost',
|
|
||||||
version: packageJson.version,
|
|
||||||
pages: pagesMetadata,
|
|
||||||
};
|
|
||||||
|
|
||||||
zip.file('docmost-metadata.json', JSON.stringify(metadata, null, 2));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async zipAttachments(prosemirrorJson: any, spaceId: string, zip: JSZip) {
|
async zipAttachments(prosemirrorJson: any, spaceId: string, zip: JSZip) {
|
||||||
|
|||||||
@@ -15,5 +15,4 @@ export type ImportPageNode = {
|
|||||||
parentPageId: string | null;
|
parentPageId: string | null;
|
||||||
fileExtension: string;
|
fileExtension: string;
|
||||||
filePath: string;
|
filePath: string;
|
||||||
icon?: string | null;
|
|
||||||
};
|
};
|
||||||
@@ -24,8 +24,6 @@ import { formatImportHtml } from '../utils/import-formatter';
|
|||||||
import {
|
import {
|
||||||
buildAttachmentCandidates,
|
buildAttachmentCandidates,
|
||||||
collectMarkdownAndHtmlFiles,
|
collectMarkdownAndHtmlFiles,
|
||||||
encodeFilePath,
|
|
||||||
readDocmostMetadata,
|
|
||||||
stripNotionID,
|
stripNotionID,
|
||||||
} from '../utils/import.utils';
|
} from '../utils/import.utils';
|
||||||
import { executeTx } from '@docmost/db/utils';
|
import { executeTx } from '@docmost/db/utils';
|
||||||
@@ -156,7 +154,6 @@ export class FileImportTaskService {
|
|||||||
const { extractDir, fileTask } = opts;
|
const { extractDir, fileTask } = opts;
|
||||||
const allFiles = await collectMarkdownAndHtmlFiles(extractDir);
|
const allFiles = await collectMarkdownAndHtmlFiles(extractDir);
|
||||||
const attachmentCandidates = await buildAttachmentCandidates(extractDir);
|
const attachmentCandidates = await buildAttachmentCandidates(extractDir);
|
||||||
const docmostMetadata = await readDocmostMetadata(extractDir);
|
|
||||||
|
|
||||||
const pagesMap = new Map<string, ImportPageNode>();
|
const pagesMap = new Map<string, ImportPageNode>();
|
||||||
|
|
||||||
@@ -167,9 +164,6 @@ export class FileImportTaskService {
|
|||||||
.join('/'); // normalize to forward-slashes
|
.join('/'); // normalize to forward-slashes
|
||||||
const ext = path.extname(relPath).toLowerCase();
|
const ext = path.extname(relPath).toLowerCase();
|
||||||
|
|
||||||
const encodedPath = encodeFilePath(relPath);
|
|
||||||
const pageMetadata = docmostMetadata?.pages[encodedPath];
|
|
||||||
|
|
||||||
pagesMap.set(relPath, {
|
pagesMap.set(relPath, {
|
||||||
id: v7(),
|
id: v7(),
|
||||||
slugId: generateSlugId(),
|
slugId: generateSlugId(),
|
||||||
@@ -178,7 +172,6 @@ export class FileImportTaskService {
|
|||||||
parentPageId: null,
|
parentPageId: null,
|
||||||
fileExtension: ext,
|
fileExtension: ext,
|
||||||
filePath: relPath,
|
filePath: relPath,
|
||||||
icon: pageMetadata?.icon ?? null,
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -231,8 +224,6 @@ export class FileImportTaskService {
|
|||||||
|
|
||||||
if (!pagesMap.has(mdPath) && !pagesMap.has(htmlPath)) {
|
if (!pagesMap.has(mdPath) && !pagesMap.has(htmlPath)) {
|
||||||
const folderName = path.basename(folderPath);
|
const folderName = path.basename(folderPath);
|
||||||
const encodedMdPath = encodeFilePath(mdPath);
|
|
||||||
const placeholderMetadata = docmostMetadata?.pages[encodedMdPath];
|
|
||||||
pagesMap.set(mdPath, {
|
pagesMap.set(mdPath, {
|
||||||
id: v7(),
|
id: v7(),
|
||||||
slugId: generateSlugId(),
|
slugId: generateSlugId(),
|
||||||
@@ -241,7 +232,6 @@ export class FileImportTaskService {
|
|||||||
parentPageId: null,
|
parentPageId: null,
|
||||||
fileExtension: '.md',
|
fileExtension: '.md',
|
||||||
filePath: mdPath,
|
filePath: mdPath,
|
||||||
icon: placeholderMetadata?.icon ?? null,
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -276,39 +266,11 @@ export class FileImportTaskService {
|
|||||||
siblingsMap.set(page.parentPageId, group);
|
siblingsMap.set(page.parentPageId, group);
|
||||||
});
|
});
|
||||||
|
|
||||||
const encodedPathsMap = new Map<string, string>();
|
|
||||||
if (docmostMetadata) {
|
|
||||||
pagesMap.forEach((_, filePath) => {
|
|
||||||
encodedPathsMap.set(filePath, encodeFilePath(filePath));
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// Sort siblings by metadata position if available, otherwise alphabetically
|
|
||||||
const sortSiblings = (siblings: ImportPageNode[]) => {
|
|
||||||
if (docmostMetadata) {
|
|
||||||
siblings.sort((a, b) => {
|
|
||||||
const posA =
|
|
||||||
docmostMetadata.pages[encodedPathsMap.get(a.filePath)]?.position;
|
|
||||||
const posB =
|
|
||||||
docmostMetadata.pages[encodedPathsMap.get(b.filePath)]?.position;
|
|
||||||
if (posA && posB) {
|
|
||||||
// Use direct comparison to match PostgreSQL collation 'C' (byte order)
|
|
||||||
if (posA < posB) return -1;
|
|
||||||
if (posA > posB) return 1;
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
return a.name.localeCompare(b.name);
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
siblings.sort((a, b) => a.name.localeCompare(b.name));
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// get root pages
|
// get root pages
|
||||||
const rootSibs = siblingsMap.get(null);
|
const rootSibs = siblingsMap.get(null);
|
||||||
|
|
||||||
if (rootSibs?.length) {
|
if (rootSibs?.length) {
|
||||||
sortSiblings(rootSibs);
|
rootSibs.sort((a, b) => a.name.localeCompare(b.name));
|
||||||
|
|
||||||
// get first position key from the server
|
// get first position key from the server
|
||||||
const nextPosition = await this.pageService.nextPagePosition(
|
const nextPosition = await this.pageService.nextPagePosition(
|
||||||
@@ -330,7 +292,7 @@ export class FileImportTaskService {
|
|||||||
siblingsMap.forEach((sibs, parentId) => {
|
siblingsMap.forEach((sibs, parentId) => {
|
||||||
if (parentId === null) return; // root already done
|
if (parentId === null) return; // root already done
|
||||||
|
|
||||||
sortSiblings(sibs);
|
sibs.sort((a, b) => a.name.localeCompare(b.name));
|
||||||
|
|
||||||
let prevPos: string | null = null;
|
let prevPos: string | null = null;
|
||||||
for (const page of sibs) {
|
for (const page of sibs) {
|
||||||
@@ -464,7 +426,7 @@ export class FileImportTaskService {
|
|||||||
id: page.id,
|
id: page.id,
|
||||||
slugId: page.slugId,
|
slugId: page.slugId,
|
||||||
title: title || page.name,
|
title: title || page.name,
|
||||||
icon: page.icon || pageIcon || null,
|
icon: pageIcon || null,
|
||||||
content: prosemirrorJson,
|
content: prosemirrorJson,
|
||||||
textContent: jsonToText(prosemirrorJson),
|
textContent: jsonToText(prosemirrorJson),
|
||||||
ydoc: await this.importService.createYdoc(prosemirrorJson),
|
ydoc: await this.importService.createYdoc(prosemirrorJson),
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
import { Logger } from '@nestjs/common';
|
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';
|
||||||
import { ExportMetadata } from '../../../common/helpers/types/export-metadata.types';
|
|
||||||
|
|
||||||
export async function buildAttachmentCandidates(
|
export async function buildAttachmentCandidates(
|
||||||
extractDir: string,
|
extractDir: string,
|
||||||
@@ -36,15 +35,9 @@ export function resolveRelativeAttachmentPath(
|
|||||||
try {
|
try {
|
||||||
mainRel = decodeURIComponent(mainRel);
|
mainRel = decodeURIComponent(mainRel);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
Logger.warn(
|
Logger.warn(`URI malformed for attachment path: ${mainRel}. Falling back to raw path.`, 'ImportUtils');
|
||||||
`URI malformed for attachment path: ${mainRel}. Falling back to raw path.`,
|
|
||||||
'ImportUtils',
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
const fallback = path
|
const fallback = path.normalize(path.join(pageDir, mainRel)).split(path.sep).join('/');
|
||||||
.normalize(path.join(pageDir, mainRel))
|
|
||||||
.split(path.sep)
|
|
||||||
.join('/');
|
|
||||||
|
|
||||||
if (attachmentCandidates.has(mainRel)) {
|
if (attachmentCandidates.has(mainRel)) {
|
||||||
return mainRel;
|
return mainRel;
|
||||||
@@ -83,26 +76,3 @@ export function stripNotionID(fileName: string): string {
|
|||||||
const notionIdPattern = /[ -]?[a-z0-9]{32}$/i;
|
const notionIdPattern = /[ -]?[a-z0-9]{32}$/i;
|
||||||
return fileName.replace(notionIdPattern, '').trim();
|
return fileName.replace(notionIdPattern, '').trim();
|
||||||
}
|
}
|
||||||
|
|
||||||
export function encodeFilePath(filePath: string): string {
|
|
||||||
return filePath
|
|
||||||
.split('/')
|
|
||||||
.map((segment) => encodeURIComponent(segment))
|
|
||||||
.join('/');
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function readDocmostMetadata(
|
|
||||||
extractDir: string,
|
|
||||||
): Promise<ExportMetadata | null> {
|
|
||||||
const metadataPath = path.join(extractDir, 'docmost-metadata.json');
|
|
||||||
try {
|
|
||||||
const content = await fs.readFile(metadataPath, 'utf-8');
|
|
||||||
const metadata = JSON.parse(content) as ExportMetadata;
|
|
||||||
if (metadata.source === 'docmost' && metadata.pages) {
|
|
||||||
return metadata;
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
} catch {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -24,11 +24,7 @@ async function bootstrap() {
|
|||||||
}),
|
}),
|
||||||
{
|
{
|
||||||
rawBody: true,
|
rawBody: true,
|
||||||
// disable Nest logger so pino handles all logs
|
bufferLogs: true,
|
||||||
// bufferLogs must be false else pino will fail
|
|
||||||
// to log OnApplicationBootstrap logs
|
|
||||||
logger: false,
|
|
||||||
bufferLogs: false,
|
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -105,9 +101,7 @@ async function bootstrap() {
|
|||||||
|
|
||||||
const port = process.env.PORT || 3000;
|
const port = process.env.PORT || 3000;
|
||||||
await app.listen(port, '0.0.0.0', () => {
|
await app.listen(port, '0.0.0.0', () => {
|
||||||
logger.log(
|
logger.log(`Listening on http://127.0.0.1:${port} / ${process.env.APP_URL}`);
|
||||||
`Listening on http://127.0.0.1:${port} / ${process.env.APP_URL}`,
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -23,9 +23,6 @@ export class WsRedisIoAdapter extends IoAdapter {
|
|||||||
const pubClient = new Redis(process.env.REDIS_URL, options);
|
const pubClient = new Redis(process.env.REDIS_URL, options);
|
||||||
const subClient = new Redis(process.env.REDIS_URL, options);
|
const subClient = new Redis(process.env.REDIS_URL, options);
|
||||||
|
|
||||||
pubClient.on('error', (err) => () => {});
|
|
||||||
subClient.on('error', (err) => () => {});
|
|
||||||
|
|
||||||
this.adapterConstructor = createAdapter(pubClient, subClient);
|
this.adapterConstructor = createAdapter(pubClient, subClient);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user