重构作品分享链路

统一发布分享弹窗为作品分享卡片

支持下载分享卡与小程序九宫切图保存

小程序复制链接改为可直达作品详情的 web-view 路径

修复本地 dev Rust 构建绕过损坏 sccache

补充分享链路与 dev 启动文档和测试
This commit is contained in:
2026-06-11 21:32:29 +08:00
parent ccb5023197
commit c5763fdf25
37 changed files with 1958 additions and 305 deletions

View File

@@ -0,0 +1,129 @@
const ALLOWED_TARGET_PATHS = new Set(['/works/detail']);
function trimTrailingSlash(value) {
return String(value || '').trim().replace(/\/+$/u, '');
}
function appendQuery(url, query) {
const rawUrl = String(url || '');
const pairs = Object.keys(query)
.filter((key) => query[key])
.map(
(key) =>
`${encodeURIComponent(key)}=${encodeURIComponent(String(query[key]))}`,
);
if (pairs.length === 0) {
return rawUrl;
}
const hashIndex = rawUrl.indexOf('#');
const baseUrl = hashIndex >= 0 ? rawUrl.slice(0, hashIndex) : rawUrl;
const hash = hashIndex >= 0 ? rawUrl.slice(hashIndex) : '';
return `${baseUrl}${baseUrl.includes('?') ? '&' : '?'}${pairs.join('&')}${hash}`;
}
function appendHashParams(url, params) {
const nextKeys = new Set(Object.keys(params).filter((key) => params[key]));
const pairs = Object.keys(params)
.filter((key) => params[key])
.map(
(key) =>
`${encodeURIComponent(key)}=${encodeURIComponent(String(params[key]))}`,
);
if (pairs.length === 0) {
return url;
}
const hashIndex = url.indexOf('#');
const baseUrl = hashIndex >= 0 ? url.slice(0, hashIndex) : url;
const rawHash = hashIndex >= 0 ? url.slice(hashIndex + 1) : '';
const keptHashParts = rawHash.split('&').filter((part) => {
if (!part) {
return false;
}
const [rawKey = ''] = part.split('=');
try {
return !nextKeys.has(decodeURIComponent(rawKey));
} catch (_error) {
return !nextKeys.has(rawKey);
}
});
return `${baseUrl}#${keptHashParts.concat(pairs).join('&')}`;
}
function normalizeTargetPath(value) {
const trimmed = String(value || '').trim();
if (!trimmed.startsWith('/')) {
return '';
}
const normalized = trimmed.replace(/\/+$/u, '') || '/';
return ALLOWED_TARGET_PATHS.has(normalized) ? normalized : '';
}
function resolveLaunchTargetQuery(query) {
const targetPath = normalizeTargetPath(query && query.targetPath);
const work = String((query && query.work) || '').trim();
if (!targetPath || !work) {
return {};
}
return {
targetPath,
work,
};
}
function appendLaunchTargetToEntryUrl(entryUrl, query) {
const launchTarget = resolveLaunchTargetQuery(query);
if (!launchTarget.targetPath) {
return entryUrl;
}
const rawEntryUrl = String(entryUrl || '').trim();
const hashIndex = rawEntryUrl.indexOf('#');
const entryWithoutHash =
hashIndex >= 0 ? rawEntryUrl.slice(0, hashIndex) : rawEntryUrl;
const hash = hashIndex >= 0 ? rawEntryUrl.slice(hashIndex) : '';
const queryIndex = entryWithoutHash.indexOf('?');
const entryBase =
queryIndex >= 0 ? entryWithoutHash.slice(0, queryIndex) : entryWithoutHash;
const entrySearch =
queryIndex >= 0 ? entryWithoutHash.slice(queryIndex) : '';
const targetUrl = `${trimTrailingSlash(entryBase)}${launchTarget.targetPath}${entrySearch}${hash}`;
return appendQuery(targetUrl, {
work: launchTarget.work,
});
}
function resolveWebViewUrlFromRuntimeConfig(
authResult,
launchQuery = {},
runtimeConfig = {},
) {
const entryUrl = appendLaunchTargetToEntryUrl(
String(runtimeConfig.webViewEntryUrl || '').trim(),
launchQuery,
);
const sourcedUrl = appendQuery(entryUrl, runtimeConfig.sourceQuery || {});
if (!authResult || !authResult.token) {
return sourcedUrl;
}
return appendHashParams(sourcedUrl, {
auth_provider: 'wechat',
auth_token: authResult.token,
auth_binding_status: authResult.bindingStatus,
});
}
module.exports = {
appendHashParams,
appendLaunchTargetToEntryUrl,
appendQuery,
normalizeTargetPath,
resolveLaunchTargetQuery,
resolveWebViewUrlFromRuntimeConfig,
};