d8eaef3fbd
Add public-page WeChat draft button, publication standard conversion for poem/prose/generic pages, and quick push APIs. Default Studio release SSH targets to john@180.159.29.143 with 105 edge upstream via 10.10.0.2. Co-authored-by: Cursor <cursoragent@cursor.com>
163 lines
4.7 KiB
JavaScript
163 lines
4.7 KiB
JavaScript
import path from 'node:path';
|
|
import { normalizeWorkspaceRelativePath } from './mindspace-workspace-relative-path.mjs';
|
|
import { validatePageForWechatDraft } from './mindspace-wechat-page-draft.mjs';
|
|
|
|
function normalizePublicHtmlRelativePath(relativePath) {
|
|
const normalized = normalizeWorkspaceRelativePath(relativePath);
|
|
if (!normalized || !normalized.startsWith('public/') || !normalized.toLowerCase().endsWith('.html')) {
|
|
throw Object.assign(new Error('仅支持 workspace 公开 HTML 页面'), { code: 'invalid_public_html_path' });
|
|
}
|
|
return normalized;
|
|
}
|
|
|
|
function titleFromPublicHtml(html, relativePath) {
|
|
const match = String(html ?? '').match(/<title[^>]*>([\s\S]*?)<\/title>/i);
|
|
const fromTitle = match?.[1]?.replace(/\s+/g, ' ').trim();
|
|
if (fromTitle) return fromTitle;
|
|
const basename = path.basename(String(relativePath ?? ''), '.html');
|
|
return basename || 'MindSpace 页面';
|
|
}
|
|
|
|
async function resolvePublicHtmlContext({
|
|
user,
|
|
relativePath,
|
|
mindSpacePages,
|
|
mindSpacePublications,
|
|
readWorkspaceHtml,
|
|
}) {
|
|
if (!mindSpacePages) {
|
|
throw Object.assign(new Error('MindSpace 页面服务未启用'), { code: 'mindspace_unavailable' });
|
|
}
|
|
if (typeof readWorkspaceHtml !== 'function') {
|
|
throw Object.assign(new Error('MindSpace 页面读取服务不可用'), { code: 'mindspace_read_unavailable' });
|
|
}
|
|
|
|
const normalized = normalizePublicHtmlRelativePath(relativePath);
|
|
const { html: content } = await readWorkspaceHtml({
|
|
userId: user.id,
|
|
relativePath: normalized,
|
|
});
|
|
|
|
const page = await mindSpacePages.findPageByRelativePath(user.id, normalized);
|
|
if (!page?.id) {
|
|
throw Object.assign(new Error('无法识别页面对应的 MindSpace 记录'), { code: 'page_not_found' });
|
|
}
|
|
|
|
let publicUrl = '';
|
|
if (mindSpacePublications?.getCurrent) {
|
|
const publication = await mindSpacePublications.getCurrent(user.id, page.id);
|
|
publicUrl = publication?.publicUrl ?? '';
|
|
}
|
|
|
|
return {
|
|
normalized,
|
|
content,
|
|
page,
|
|
pageTitle: page.title || titleFromPublicHtml(content, normalized),
|
|
pageSummary: page.summary ?? '',
|
|
publicUrl,
|
|
};
|
|
}
|
|
|
|
export async function getQuickWechatDraftFromPublicHtmlStatus({
|
|
user,
|
|
relativePath,
|
|
mindSpacePages,
|
|
mindSpacePublications,
|
|
readWorkspaceHtml,
|
|
getWechatMpConfig,
|
|
getWechatPageDraft,
|
|
}) {
|
|
const configService = getWechatMpConfig?.();
|
|
const config = configService
|
|
? await configService.getConfig(user.id)
|
|
: { configured: false };
|
|
|
|
let context = null;
|
|
let validation = null;
|
|
try {
|
|
context = await resolvePublicHtmlContext({
|
|
user,
|
|
relativePath,
|
|
mindSpacePages,
|
|
mindSpacePublications,
|
|
readWorkspaceHtml,
|
|
});
|
|
validation = validatePageForWechatDraft(context.content, {
|
|
pageTitle: context.pageTitle,
|
|
pageSummary: context.pageSummary,
|
|
publicUrl: context.publicUrl,
|
|
});
|
|
} catch (error) {
|
|
if (error?.code === 'page_not_found' || error?.code === 'invalid_public_html_path') {
|
|
return {
|
|
configured: Boolean(config.configured),
|
|
pageId: null,
|
|
canPush: false,
|
|
validation: {
|
|
ok: false,
|
|
profile: 'unknown',
|
|
blockers: [error.message],
|
|
warnings: [],
|
|
estimatedContentLength: 0,
|
|
},
|
|
};
|
|
}
|
|
throw error;
|
|
}
|
|
|
|
let latestRun = null;
|
|
const draftService = getWechatPageDraft?.();
|
|
if (draftService?.listRuns) {
|
|
const runs = await draftService.listRuns(user.id, { pageId: context.page.id, limit: 1 });
|
|
latestRun = runs[0] ?? null;
|
|
}
|
|
|
|
return {
|
|
configured: Boolean(config.configured),
|
|
pageId: context.page.id,
|
|
pageTitle: context.pageTitle,
|
|
canPush: Boolean(config.configured) && validation.ok,
|
|
validation,
|
|
latestRun,
|
|
};
|
|
}
|
|
|
|
export async function quickWechatDraftFromPublicHtml({
|
|
user,
|
|
relativePath,
|
|
mindSpacePages,
|
|
mindSpacePublications,
|
|
readWorkspaceHtml,
|
|
getWechatPageDraft,
|
|
}) {
|
|
const draftService = getWechatPageDraft?.();
|
|
if (!draftService?.pushPageDraft) {
|
|
throw Object.assign(new Error('公众号草稿推送未启用'), { code: 'wechat_draft_unavailable' });
|
|
}
|
|
|
|
const context = await resolvePublicHtmlContext({
|
|
user,
|
|
relativePath,
|
|
mindSpacePages,
|
|
mindSpacePublications,
|
|
readWorkspaceHtml,
|
|
});
|
|
const validation = validatePageForWechatDraft(context.content, {
|
|
pageTitle: context.pageTitle,
|
|
pageSummary: context.pageSummary,
|
|
publicUrl: context.publicUrl,
|
|
});
|
|
if (!validation.ok) {
|
|
throw Object.assign(new Error(validation.blockers[0] ?? '页面不符合公众号草稿推送要求'), {
|
|
code: 'wechat_draft_validation_failed',
|
|
validation,
|
|
});
|
|
}
|
|
|
|
return draftService.pushPageDraft(user.id, context.page.id, {
|
|
triggeredBy: user.id,
|
|
skipValidation: true,
|
|
});
|
|
}
|