/** * 附件直链的地址处理。 * * 库里存的 Markdown 必须是"干净"的:只记 `/api/v1/.../attachments//raw`,不带任何查询串。 * 原因有三条—— * 1. `` 发不出 Authorization 头,图片只能靠 URL 自身携带凭据; * 2. 凭据会过期(令牌 30 分钟、签名也是 30 分钟),写进正文就会几天后满篇红叉; * 3. 同一个字段在团队里要能共用,而每个人的令牌不一样。 * * 所以规则是:**存的时候剥干净,看的时候现配**。 * 优先用后端在附件列表里签好的 `raw_url`(短期签名,别人拿到也只能看这一张图的一小会儿), * 没拿到签名时才退回令牌(兼容老链接,前端已不再主动生成)。 */ import { session } from "./client"; const UUID = "[0-9a-fA-F-]{36}"; /** 附件直链:可能带 token 或 exp/sig,全部视为"要替换的查询串" */ const RAW_URL = new RegExp(`(/api/v1/workspaces/${UUID}/attachments/(${UUID})/raw)(?:\\?[^)"'\\s]*)?`, "g"); /** 附件 id → 可访问地址(后端签发的 raw_url) */ export type UrlResources = Record; function tokenSuffix(): string { const token = session.access; return token ? `?token=${encodeURIComponent(token)}` : ""; } /** 保存前调用:把正文里所有直链的查询串去掉。 */ export function stripAttachmentTokens(source: string): string { return (source ?? "").replace(RAW_URL, (_all, url: string) => url); } /** 渲染 / 装载前调用:按附件 id 换成当前可用的地址。 */ export function applyAttachmentUrls(source: string, resources?: UrlResources): string { const fallback = tokenSuffix(); return (source ?? "").replace(RAW_URL, (_all, url: string, _ws: string, id: string) => { const signed = resources?.[id]; if (signed) return signed; return fallback ? `${url}${fallback}` : url; }); } /** 兼容旧名字:只处理 HTML(已渲染的预览用),逻辑同 applyAttachmentUrls。 */ export const tokenizeAttachmentHtml = applyAttachmentUrls;