readme
This commit is contained in:
@@ -2,6 +2,7 @@
|
||||
|
||||
import { useState, useMemo, useSyncExternalStore } from "react";
|
||||
import { useTranslation } from "@/app/digital/i18n/navigation";
|
||||
import { mediaUrl } from "@/app/lib/media";
|
||||
import type { ResourceData } from "@/app/digital/lib/theme-data";
|
||||
import DigitalThemeIcon, { type IconName } from "./DigitalThemeIcon";
|
||||
|
||||
@@ -98,7 +99,7 @@ export default function ResourceNavigation({ data }: ResourceNavigationProps) {
|
||||
style={{ aspectRatio: "2/3" }}
|
||||
>
|
||||
<img
|
||||
src={book.cover}
|
||||
src={mediaUrl(book.cover)}
|
||||
alt={book.title}
|
||||
className="h-full w-full object-cover transition-transform duration-300 group-hover:scale-105"
|
||||
/>
|
||||
@@ -167,7 +168,7 @@ export default function ResourceNavigation({ data }: ResourceNavigationProps) {
|
||||
style={{ aspectRatio: "1/1" }}
|
||||
>
|
||||
<img
|
||||
src={product.image}
|
||||
src={mediaUrl(product.image)}
|
||||
alt={product.name}
|
||||
className="h-full w-full object-contain transition-transform duration-300 group-hover:scale-105"
|
||||
/>
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
"use client";
|
||||
|
||||
import { useCallback, useEffect, useRef, useState } from "react";
|
||||
import { mediaUrl } from "@/app/lib/media";
|
||||
|
||||
const STORAGE_PREFIX = "lms-video-";
|
||||
const SAVE_INTERVAL = 5000;
|
||||
@@ -215,8 +216,8 @@ export function VideoPlayer({ src, poster, title, className = "", videoId, onCom
|
||||
>
|
||||
<video
|
||||
ref={videoRef}
|
||||
src={src}
|
||||
poster={poster}
|
||||
src={mediaUrl(src)}
|
||||
poster={mediaUrl(poster)}
|
||||
title={title}
|
||||
className="aspect-video w-full object-contain"
|
||||
onClick={togglePlay}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { marked } from "marked";
|
||||
import { mediaUrl } from "@/app/lib/media";
|
||||
|
||||
export interface EbookChunk {
|
||||
html: string;
|
||||
@@ -10,9 +11,21 @@ export interface EbookData {
|
||||
headers: string[];
|
||||
}
|
||||
|
||||
function rewriteMediaTag(tag: string): string {
|
||||
return tag.replace(/\s(src|poster)=(["'])(.*?)\2/gi, (match, attr: string, quote: string, value: string) => {
|
||||
const rewritten = mediaUrl(value);
|
||||
return rewritten ? ` ${attr}=${quote}${rewritten}${quote}` : match;
|
||||
});
|
||||
}
|
||||
|
||||
function rewriteEmbeddedMedia(html: string): string {
|
||||
return html.replace(/<(img|video|audio|source)\b[^>]*>/gi, rewriteMediaTag);
|
||||
}
|
||||
|
||||
export function parseEbookMarkdown(source: string): EbookData {
|
||||
const html = marked.parse(source, { breaks: true, gfm: true }) as string;
|
||||
const lazyHtml = html.replace(/<img /g, '<img loading="lazy" decoding="async" ');
|
||||
const mediaHtml = rewriteEmbeddedMedia(html);
|
||||
const lazyHtml = mediaHtml.replace(/<img /g, '<img loading="lazy" decoding="async" ');
|
||||
|
||||
let rawChunks = lazyHtml.split(/(?=<h1[\s>])/i).filter((c) => c.trim());
|
||||
if (rawChunks.length <= 1) {
|
||||
|
||||
@@ -6,12 +6,14 @@
|
||||
import * as Minio from "minio";
|
||||
import { getMinioConfig } from "./config";
|
||||
import { getThemeConfig } from "@/config/digital/site.config";
|
||||
import type { MinioConfig } from "./config";
|
||||
|
||||
const ensuredBuckets = new Set<string>();
|
||||
|
||||
/** 获取 MinIO 客户端实例(使用主题配置) */
|
||||
export function getMinioClient(): Minio.Client {
|
||||
export function getMinioClient(config?: MinioConfig): Minio.Client {
|
||||
const theme = getThemeConfig();
|
||||
const cfg = getMinioConfig(theme.services?.minio);
|
||||
|
||||
const cfg = config || getMinioConfig(theme.services?.minio);
|
||||
return new Minio.Client({
|
||||
endPoint: cfg.endPoint,
|
||||
port: cfg.port,
|
||||
@@ -23,6 +25,29 @@ export function getMinioClient(): Minio.Client {
|
||||
});
|
||||
}
|
||||
|
||||
export async function ensurePublicBucket(client: Minio.Client, bucket: string): Promise<void> {
|
||||
if (ensuredBuckets.has(bucket)) return;
|
||||
const exists = await client.bucketExists(bucket);
|
||||
if (!exists) {
|
||||
await client.makeBucket(bucket);
|
||||
}
|
||||
await client.setBucketPolicy(
|
||||
bucket,
|
||||
JSON.stringify({
|
||||
Version: "2012-10-17",
|
||||
Statement: [
|
||||
{
|
||||
Effect: "Allow",
|
||||
Principal: { AWS: ["*"] },
|
||||
Action: ["s3:GetObject"],
|
||||
Resource: [`arn:aws:s3:::${bucket}/*`],
|
||||
},
|
||||
],
|
||||
})
|
||||
);
|
||||
ensuredBuckets.add(bucket);
|
||||
}
|
||||
|
||||
export interface UploadResult {
|
||||
url: string;
|
||||
objectKey: string;
|
||||
@@ -46,7 +71,8 @@ export async function uploadBuffer(
|
||||
throw new Error("MinIO 存储未启用");
|
||||
}
|
||||
|
||||
const client = getMinioClient();
|
||||
const client = getMinioClient(cfg);
|
||||
await ensurePublicBucket(client, cfg.bucket);
|
||||
await client.putObject(cfg.bucket, objectKey, buffer, buffer.length, {
|
||||
"Content-Type": contentType,
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user