mirror of
https://github.com/docmost/docmost.git
synced 2026-09-11 07:56:54 +08:00
feat(beta): public spaces (#2473)
This commit is contained in:
@@ -85,8 +85,8 @@ export class ShareSeoController {
|
||||
|
||||
const html = fs.readFileSync(indexFilePath, 'utf8');
|
||||
const transformedHtml = html
|
||||
.replace(/<title>[\s\S]*?<\/title>/i, `<title>${metaTitle}</title>`)
|
||||
.replace(metaTagVar, metaTags);
|
||||
.replace(/<title>[\s\S]*?<\/title>/i, () => `<title>${metaTitle}</title>`)
|
||||
.replace(metaTagVar, () => metaTags);
|
||||
|
||||
res.type('text/html').send(transformedHtml);
|
||||
}
|
||||
|
||||
@@ -365,35 +365,9 @@ export class ShareService {
|
||||
workspaceId,
|
||||
);
|
||||
|
||||
// Sanitize each item's content for public delivery
|
||||
// generate per-attachment tokens scoped to the source page
|
||||
// and strip comment marks.
|
||||
const tokenized = await Promise.all(
|
||||
items.map(async (item) => {
|
||||
if ('status' in item) return item;
|
||||
const doc = await this.prepareContentForShare(
|
||||
item.content,
|
||||
item.sourcePageId,
|
||||
workspaceId,
|
||||
);
|
||||
return { ...item, content: doc?.toJSON() ?? item.content };
|
||||
}),
|
||||
);
|
||||
|
||||
// Collapse `not_found` to `no_access` for share viewers so the response
|
||||
// can't be used to tell "page is shared but transclusion id doesn't
|
||||
// match" from "page isn't shared at all".
|
||||
const sanitized = tokenized.map((item) =>
|
||||
'status' in item && item.status === 'not_found'
|
||||
? {
|
||||
sourcePageId: item.sourcePageId,
|
||||
transclusionId: item.transclusionId,
|
||||
status: 'no_access' as const,
|
||||
}
|
||||
: item,
|
||||
);
|
||||
|
||||
return { items: sanitized };
|
||||
return {
|
||||
items: await this.sanitizeTransclusionItemsForPublic(items, workspaceId),
|
||||
};
|
||||
}
|
||||
|
||||
async isSharingAllowed(
|
||||
@@ -430,6 +404,38 @@ export class ShareService {
|
||||
return doc?.toJSON() ?? page.content;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sanitization tail shared by every public transclusion surface: tokenize
|
||||
* each content item against its source page, then hide lookup misses.
|
||||
*/
|
||||
async sanitizeTransclusionItemsForPublic(
|
||||
items: TransclusionLookup[],
|
||||
workspaceId: string,
|
||||
): Promise<TransclusionLookup[]> {
|
||||
const tokenized = await Promise.all(
|
||||
items.map(async (item) => {
|
||||
if ('status' in item) return item;
|
||||
const doc = await this.prepareContentForShare(
|
||||
item.content,
|
||||
item.sourcePageId,
|
||||
workspaceId,
|
||||
);
|
||||
return { ...item, content: doc?.toJSON() ?? item.content };
|
||||
}),
|
||||
);
|
||||
|
||||
// Collapse not_found to no_access so hidden sources are indistinguishable from missing ids.
|
||||
return tokenized.map((item) =>
|
||||
'status' in item && item.status === 'not_found'
|
||||
? {
|
||||
sourcePageId: item.sourcePageId,
|
||||
transclusionId: item.transclusionId,
|
||||
status: 'no_access' as const,
|
||||
}
|
||||
: item,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepare a ProseMirror JSON doc for delivery to a public share viewer.
|
||||
* Performs the two transforms required by the share threat model:
|
||||
|
||||
Reference in New Issue
Block a user