Browse Source

feat(media): constants and markdown/cover URL extraction

Made-with: Cursor
main
npmrun 7 hours ago
parent
commit
36a3fbfe1d
  1. 11
      server/constants/media.ts
  2. 61
      server/utils/post-media-urls.ts

11
server/constants/media.ts

@ -0,0 +1,11 @@
/** 与 `upload` 返回及静态路径一致,无前导 host */
export const POST_MEDIA_PUBLIC_PREFIX = "/public/assets/";
/** 从未引用:created_at 起算;曾引用:dereferenced_at 起算 */
export const MEDIA_ORPHAN_GRACE_HOURS_NEVER_REF = 24;
export const MEDIA_ORPHAN_GRACE_HOURS_AFTER_DEREF = 24;
export const MEDIA_IMAGE_MAX_WIDTH_PX = 1920;
export const MEDIA_WEBP_QUALITY = 82;
export const RELATIVE_ASSETS_DIR = "public/assets";

61
server/utils/post-media-urls.ts

@ -0,0 +1,61 @@
import { POST_MEDIA_PUBLIC_PREFIX } from "#server/constants/media";
const MD_IMG_RE = /!\[[^\]]*]\(([^)]+)\)/g;
function normalizeUrl(raw: string): string {
const t = raw.trim().replace(/^<|>$/g, "").split(/\s+/)[0] ?? "";
if (t.startsWith(POST_MEDIA_PUBLIC_PREFIX)) {
return t;
}
return "";
}
/** 从 markdown 中提取本站图片 URL(去重,顺序稳定) */
export function extractMediaUrlsFromMarkdown(markdown: string): string[] {
const out: string[] = [];
const seen = new Set<string>();
let m: RegExpExecArray | null;
const re = new RegExp(MD_IMG_RE.source, MD_IMG_RE.flags);
while ((m = re.exec(markdown)) !== null) {
const u = normalizeUrl(m[1] ?? "");
if (u && !seen.has(u)) {
seen.add(u);
out.push(u);
}
}
return out;
}
export function extractMediaUrlsFromCover(coverUrl: string | null | undefined): string[] {
if (!coverUrl) {
return [];
}
const u = normalizeUrl(coverUrl);
return u ? [u] : [];
}
export function mergePostMediaUrls(bodyMarkdown: string, coverUrl: string | null | undefined): string[] {
const a = extractMediaUrlsFromMarkdown(bodyMarkdown);
const b = extractMediaUrlsFromCover(coverUrl);
const seen = new Set<string>(a);
for (const u of b) {
if (!seen.has(u)) {
seen.add(u);
a.push(u);
}
}
return a;
}
/** `/public/assets/foo.webp` → `foo.webp` */
export function publicAssetUrlToStorageKey(url: string): string | null {
const t = url.trim();
if (!t.startsWith(POST_MEDIA_PUBLIC_PREFIX)) {
return null;
}
const key = t.slice(POST_MEDIA_PUBLIC_PREFIX.length).replace(/^\/+/, "");
if (!key || key.includes("..") || key.includes("/") || key.includes("\\")) {
return null;
}
return key;
}
Loading…
Cancel
Save