2 changed files with 72 additions and 0 deletions
@ -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"; |
||||
@ -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…
Reference in new issue