fix(plaza): use hero-generated thumbnails and portal cover URLs
Plaza posts now resolve cover_url to /u/{slug}/pages/{urlSlug}.thumbnail.png
instead of MindSpace workspace paths that fail on plaza.tkmind.cn. Public-html
and chat quick-plaza flows regenerate workspace thumbnails with force:true
after hero images are on disk so feed cards embed the generated art.
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
+83
-13
@@ -43,23 +43,61 @@ export function defaultPlazaCoverUrl(publicUrl) {
|
||||
if (!value) return '';
|
||||
const [pathname, suffix = ''] = value.split(/([?#].*)/, 2);
|
||||
if (!pathname) return '';
|
||||
const nextPath = pathname.endsWith('/') ? `${pathname}index.thumbnail.png` : `${pathname}.thumbnail.png`;
|
||||
const normalizedPath = pathname.replace(/\.html$/i, '');
|
||||
const nextPath = normalizedPath.endsWith('/')
|
||||
? `${normalizedPath}index.thumbnail.png`
|
||||
: `${normalizedPath}.thumbnail.png`;
|
||||
return `${nextPath}${suffix}`;
|
||||
}
|
||||
|
||||
function normalizePlazaThumbnailUrl(url) {
|
||||
const value = String(url ?? '').trim();
|
||||
if (!value) return value;
|
||||
return value.replace(/\.html\.thumbnail\.png(?=($|[?#]))/i, '.thumbnail.png');
|
||||
}
|
||||
|
||||
function isMindSpacePlazaCoverUrl(url) {
|
||||
const value = String(url ?? '').trim();
|
||||
return /\/MindSpace\//i.test(value);
|
||||
}
|
||||
|
||||
export function buildPlazaPublicationCoverPath(ownerSlug, urlSlug) {
|
||||
return `/u/${encodeURIComponent(ownerSlug)}/pages/${encodeURIComponent(urlSlug)}.thumbnail.png`;
|
||||
}
|
||||
|
||||
export function resolvePlazaPublicationCoverUrl({
|
||||
inputCoverUrl,
|
||||
publicUrl,
|
||||
publicationUrlSlug = null,
|
||||
userSlug = null,
|
||||
} = {}) {
|
||||
const fallback =
|
||||
publicationUrlSlug && userSlug
|
||||
? buildPlazaPublicationCoverPath(userSlug, publicationUrlSlug)
|
||||
: '';
|
||||
const explicit = String(inputCoverUrl ?? '').trim();
|
||||
if (!explicit) {
|
||||
return resolvePlazaCoverUrl(fallback, fallback.startsWith('/u/') ? '' : publicUrl);
|
||||
}
|
||||
if ((isMindSpacePlazaCoverUrl(explicit) || /\.html\.thumbnail\.png/i.test(explicit)) && fallback) {
|
||||
return resolvePlazaCoverUrl(fallback, '');
|
||||
}
|
||||
return resolvePlazaCoverUrl(explicit, publicUrl);
|
||||
}
|
||||
|
||||
export function resolvePlazaCoverUrl(inputCoverUrl, publicUrl) {
|
||||
const value = String(inputCoverUrl ?? '').trim();
|
||||
if (!value) return defaultPlazaCoverUrl(publicUrl);
|
||||
if (/^https?:\/\//i.test(value)) return value;
|
||||
if (!value) return normalizePlazaThumbnailUrl(defaultPlazaCoverUrl(publicUrl));
|
||||
if (/^https?:\/\//i.test(value)) return normalizePlazaThumbnailUrl(value);
|
||||
const base = String(publicUrl ?? '').trim();
|
||||
if (base && /^https?:\/\//i.test(base)) {
|
||||
try {
|
||||
return new URL(value, base).toString();
|
||||
return normalizePlazaThumbnailUrl(new URL(value, base).toString());
|
||||
} catch {
|
||||
return value;
|
||||
return normalizePlazaThumbnailUrl(value);
|
||||
}
|
||||
}
|
||||
return value;
|
||||
return normalizePlazaThumbnailUrl(value);
|
||||
}
|
||||
|
||||
function formatStats(row) {
|
||||
@@ -91,11 +129,18 @@ function promoteFreshPosts(items, now = Date.now()) {
|
||||
|
||||
function formatPostRow(row, { category, viewerReacted = null, publicationUrl = null } = {}) {
|
||||
const resolvedPublicationUrl = publicationUrl ?? row.public_url ?? null;
|
||||
const publicationUrlSlug =
|
||||
row.publication_url_slug ?? row.url_slug ?? null;
|
||||
return {
|
||||
id: row.id,
|
||||
title: row.title,
|
||||
summary: row.summary ?? '',
|
||||
cover_url: resolvePlazaCoverUrl(row.cover_url, resolvedPublicationUrl),
|
||||
cover_url: resolvePlazaPublicationCoverUrl({
|
||||
inputCoverUrl: row.cover_url,
|
||||
publicUrl: resolvedPublicationUrl,
|
||||
publicationUrlSlug,
|
||||
userSlug: row.user_slug,
|
||||
}),
|
||||
category: category ?? {
|
||||
id: row.category_id,
|
||||
name: row.category_name ?? '',
|
||||
@@ -123,6 +168,8 @@ export const plazaInternals = {
|
||||
clampLimit,
|
||||
normalizeSort,
|
||||
defaultPlazaCoverUrl,
|
||||
buildPlazaPublicationCoverPath,
|
||||
resolvePlazaPublicationCoverUrl,
|
||||
resolvePlazaCoverUrl,
|
||||
};
|
||||
|
||||
@@ -219,7 +266,7 @@ export function createPlazaPostService(
|
||||
|
||||
const loadPublicationContext = async (userId, publicationId) => {
|
||||
const [rows] = await pool.query(
|
||||
`SELECT pr.id, pr.user_id, pr.status, pr.public_url,
|
||||
`SELECT pr.id, pr.user_id, pr.status, pr.public_url, pr.url_slug,
|
||||
p.title, p.summary, p.cover_image_asset_id
|
||||
FROM h5_publish_records pr
|
||||
JOIN h5_page_records p ON p.id = pr.page_id
|
||||
@@ -327,7 +374,12 @@ export function createPlazaPostService(
|
||||
const categoryId = await resolveCategoryId(input?.category_id, input?.category_slug);
|
||||
const userSnapshot = await loadUserSnapshot(userId);
|
||||
const tags = normalizeTags(input?.tags);
|
||||
const coverUrl = resolvePlazaCoverUrl(input?.cover_url, publication.public_url);
|
||||
const coverUrl = resolvePlazaPublicationCoverUrl({
|
||||
inputCoverUrl: input?.cover_url,
|
||||
publicUrl: publication.public_url,
|
||||
publicationUrlSlug: publication.url_slug,
|
||||
userSlug: userSnapshot.user_slug,
|
||||
});
|
||||
const allowComment = input?.allow_comment == null ? true : Boolean(input.allow_comment);
|
||||
const now = Date.now();
|
||||
const postId = idFactory();
|
||||
@@ -391,7 +443,7 @@ export function createPlazaPostService(
|
||||
|
||||
const updatePost = async (userId, postId, input) => {
|
||||
const [rows] = await pool.query(
|
||||
`SELECT pp.*, pr.public_url
|
||||
`SELECT pp.*, pr.public_url, pr.url_slug AS publication_url_slug
|
||||
FROM plaza_posts pp
|
||||
JOIN h5_publish_records pr ON pr.id = pp.publication_id
|
||||
WHERE pp.id = ? AND pp.user_id = ? LIMIT 1`,
|
||||
@@ -413,7 +465,14 @@ export function createPlazaPostService(
|
||||
}
|
||||
if (input?.cover_url != null) {
|
||||
updates.push('cover_url = ?');
|
||||
params.push(resolvePlazaCoverUrl(input.cover_url, post.public_url));
|
||||
params.push(
|
||||
resolvePlazaPublicationCoverUrl({
|
||||
inputCoverUrl: input.cover_url,
|
||||
publicUrl: post.public_url,
|
||||
publicationUrlSlug: post.publication_url_slug,
|
||||
userSlug: post.user_slug,
|
||||
}),
|
||||
);
|
||||
}
|
||||
if (input?.allow_comment != null) {
|
||||
updates.push('allow_comment = ?');
|
||||
@@ -456,7 +515,8 @@ export function createPlazaPostService(
|
||||
const getPostById = async (postId, { viewerId = null, includeHidden = false } = {}) => {
|
||||
const statusClause = includeHidden ? '' : `AND pp.status = 'published'`;
|
||||
const [rows] = await pool.query(
|
||||
`SELECT pp.*, c.name AS category_name, c.slug AS category_slug, c.icon AS category_icon, pr.public_url
|
||||
`SELECT pp.*, c.name AS category_name, c.slug AS category_slug, c.icon AS category_icon,
|
||||
pr.public_url, pr.url_slug AS publication_url_slug
|
||||
FROM plaza_posts pp
|
||||
JOIN plaza_categories c ON c.id = pp.category_id
|
||||
JOIN h5_publish_records pr ON pr.id = pp.publication_id
|
||||
@@ -499,6 +559,15 @@ export function createPlazaPostService(
|
||||
if (!cursor && normalizedSort !== 'recommend' && plazaRedis?.getFeedCache) {
|
||||
const cached = await plazaRedis.getFeedCache(normalizedSort, categorySlug, null);
|
||||
if (cached?.posts) {
|
||||
cached.posts = cached.posts.map((post) => ({
|
||||
...post,
|
||||
cover_url: resolvePlazaPublicationCoverUrl({
|
||||
inputCoverUrl: post.cover_url,
|
||||
publicUrl: post.publication_url,
|
||||
publicationUrlSlug: post.publication_url_slug,
|
||||
userSlug: post.author?.slug ?? post.user_slug,
|
||||
}),
|
||||
}));
|
||||
if (viewerId && loadViewerReactions) {
|
||||
const reactionMap = await loadViewerReactions(
|
||||
viewerId,
|
||||
@@ -546,7 +615,8 @@ export function createPlazaPostService(
|
||||
: 'pp.hot_score DESC, pp.id DESC';
|
||||
|
||||
const [rows] = await pool.query(
|
||||
`SELECT pp.*, c.name AS category_name, c.slug AS category_slug, c.icon AS category_icon, pr.public_url
|
||||
`SELECT pp.*, c.name AS category_name, c.slug AS category_slug, c.icon AS category_icon,
|
||||
pr.public_url, pr.url_slug AS publication_url_slug
|
||||
FROM plaza_posts pp
|
||||
JOIN plaza_categories c ON c.id = pp.category_id
|
||||
JOIN h5_publish_records pr ON pr.id = pp.publication_id
|
||||
|
||||
Reference in New Issue
Block a user