wxz 3 months ago
commit 4963526743

@ -1,52 +1,72 @@
import React, {useCallback, useEffect, useState} from 'react';
import {Button, Dialog, DialogClose, DialogContent, LucideIcon} from '@tryghost/shade';
import {ObjectProperties} from '@tryghost/admin-x-framework/api/activitypub';
import {getAttachment} from '@components/feed/FeedItem';
import React, { useCallback, useEffect, useState } from 'react';
// 导入UI组件按钮、对话框及内容容器、Lucide图标库
import { Button, Dialog, DialogClose, DialogContent, LucideIcon } from '@tryghost/shade';
// 导入活动发布对象的类型定义
import { ObjectProperties } from '@tryghost/admin-x-framework/api/activitypub';
// 导入获取附件的工具函数
import { getAttachment } from '@components/feed/FeedItem';
// 定义灯箱中图片的类型接口
export interface LightboxImage {
url: string;
alt: string;
url: string; // 图片URL
alt: string; // 图片替代文本
}
// 定义灯箱状态的类型接口
export interface LightboxState {
images: LightboxImage[];
currentIndex: number;
isOpen: boolean;
images: LightboxImage[]; // 所有图片列表
currentIndex: number; // 当前显示图片的索引
isOpen: boolean; // 灯箱是否打开
}
/**
* Hook
* @param object -
* @returns
*/
export function useLightboxImages(object: ObjectProperties | null) {
// 初始化灯箱状态
const [lightboxState, setLightboxState] = useState<LightboxState>({
images: [],
currentIndex: 0,
isOpen: false
});
/**
*
* @param obj -
* @returns
*/
const getAllImagesFromAttachment = (obj: ObjectProperties): LightboxImage[] => {
// 获取对象的附件
const attachment = getAttachment(obj);
if (!attachment) {
return [];
}
// 处理附件为数组的情况
if (Array.isArray(attachment)) {
return attachment.map((item, index) => ({
url: item.url,
alt: item.name || `Image-${index}`
alt: item.name || `Image-${index}` // 用索引作为默认alt文本
}));
}
// 处理单个图片附件
if (attachment.mediaType?.startsWith('image/') || attachment.type === 'Image') {
return [{
url: attachment.url,
alt: attachment.name || 'Image'
alt: attachment.name || 'Image' // 用默认文本作为fallback
}];
}
// 处理对象中直接包含image字段的情况
if (obj.image) {
let imageUrl;
if (typeof obj.image === 'string') {
imageUrl = obj.image;
imageUrl = obj.image; // 图片URL直接是字符串
} else {
imageUrl = obj.image?.url;
imageUrl = obj.image?.url; // 图片是对象取其url属性
}
if (imageUrl) {
@ -57,17 +77,23 @@ export function useLightboxImages(object: ObjectProperties | null) {
}
}
return [];
return []; // 没有找到图片时返回空数组
};
/**
*
* @param clickedUrl - URL
*/
const openLightbox = (clickedUrl: string) => {
if (!object) {
return;
return; // 对象为空时不执行操作
}
// 获取所有图片并找到被点击图片的索引
const images = getAllImagesFromAttachment(object);
const clickedIndex = images.findIndex(img => img.url === clickedUrl);
// 找到对应图片时更新灯箱状态
if (clickedIndex !== -1) {
setLightboxState({
images,
@ -77,6 +103,9 @@ export function useLightboxImages(object: ObjectProperties | null) {
}
};
/**
*
*/
const closeLightbox = () => {
setLightboxState(prev => ({
...prev,
@ -84,6 +113,10 @@ export function useLightboxImages(object: ObjectProperties | null) {
}));
};
/**
*
* @param newIndex -
*/
const navigateToIndex = (newIndex: number) => {
setLightboxState(prev => ({
...prev,
@ -99,14 +132,18 @@ export function useLightboxImages(object: ObjectProperties | null) {
};
}
// 图片灯箱组件的属性接口
interface ImageLightboxProps {
images: LightboxImage[];
currentIndex: number;
isOpen: boolean;
onClose: () => void;
onNavigate: (newIndex: number) => void;
images: LightboxImage[]; // 图片列表
currentIndex: number; // 当前显示图片索引
isOpen: boolean; // 是否打开
onClose: () => void; // 关闭回调
onNavigate: (newIndex: number) => void; // 导航回调
}
/**
*
*/
const ImageLightbox: React.FC<ImageLightboxProps> = ({
images,
currentIndex,
@ -114,44 +151,65 @@ const ImageLightbox: React.FC<ImageLightboxProps> = ({
onClose,
onNavigate
}) => {
// 判断是否是第一张/最后一张图片
const isFirstImage = currentIndex === 0;
const isLastImage = currentIndex === images.length - 1;
/**
*
* 使useCallback
*/
const goToNext = useCallback(() => {
// 只有一张图片或已经是最后一张时不执行
if (images.length <= 1 || isLastImage) {
return;
}
// 计算下一张索引(循环导航)
const nextIndex = (currentIndex + 1) % images.length;
onNavigate(nextIndex);
}, [images.length, isLastImage, currentIndex, onNavigate]);
/**
*
* 使useCallback
*/
const goToPrev = useCallback(() => {
// 只有一张图片或已经是第一张时不执行
if (images.length <= 1 || isFirstImage) {
return;
}
// 计算上一张索引(循环导航)
const prevIndex = (currentIndex - 1 + images.length) % images.length;
onNavigate(prevIndex);
}, [images.length, isFirstImage, currentIndex, onNavigate]);
/**
*
*/
useEffect(() => {
const handleKeyDown = (e: KeyboardEvent) => {
if (!isOpen) {
return;
return; // 灯箱关闭时不处理
}
// 右箭头导航到下一张(非最后一张时)
if (e.key === 'ArrowRight' && !isLastImage) {
goToNext();
} else if (e.key === 'ArrowLeft' && !isFirstImage) {
}
// 左箭头导航到上一张(非第一张时)
else if (e.key === 'ArrowLeft' && !isFirstImage) {
goToPrev();
}
};
window.addEventListener('keydown', handleKeyDown);
// 组件卸载时移除事件监听
return () => {
window.removeEventListener('keydown', handleKeyDown);
};
}, [isOpen, currentIndex, images.length, goToNext, goToPrev, isLastImage, isFirstImage]);
// 灯箱未打开或没有图片时不渲染
if (!isOpen || images.length === 0) {
return null;
}
@ -159,37 +217,46 @@ const ImageLightbox: React.FC<ImageLightboxProps> = ({
return (
<Dialog
open={isOpen}
// 监听对话框状态变化,关闭时触发回调
onOpenChange={(open) => {
if (!open) {
onClose();
}
}}
>
<DialogContent className="top-[50%] h-[100vh] max-h-[100vh] w-[100vw] max-w-[100vw] translate-y-[-50%] items-center border-none bg-transparent p-0 shadow-none data-[state=closed]:zoom-out-100 data-[state=open]:zoom-in-100 data-[state=closed]:slide-out-to-top-[50%] data-[state=open]:slide-in-from-top-[50%]" onClick={() => onClose()}>
{/* 灯箱内容容器,全屏显示且居中 */}
<DialogContent
className="top-[50%] h-[100vh] max-h-[100vh] w-[100vw] max-w-[100vw] translate-y-[-50%] items-center border-none bg-transparent p-0 shadow-none data-[state=closed]:zoom-out-100 data-[state=open]:zoom-in-100 data-[state=closed]:slide-out-to-top-[50%] data-[state=open]:slide-in-from-top-[50%]"
onClick={() => onClose()} // 点击空白区域关闭
>
{/* 当前显示的图片 */}
<img
alt={images[currentIndex].alt}
className="mx-auto max-h-[90vh] max-w-[90vw] object-contain"
className="mx-auto max-h-[90vh] max-w-[90vw] object-contain" // 保持比例,限制最大尺寸
src={images[currentIndex].url}
onClick={e => e.stopPropagation()}
onClick={e => e.stopPropagation()} // 点击图片本身不关闭灯箱
/>
{/* 多张图片时显示导航按钮 */}
{images.length > 1 && (
<>
{/* 上一张按钮 */}
<Button
className="absolute left-5 top-1/2 size-11 -translate-y-1/2 rounded-full bg-black/50 p-0 pr-0.5 hover:bg-black/70"
disabled={isFirstImage}
disabled={isFirstImage} // 第一张时禁用
onClick={(e) => {
e.stopPropagation();
e.stopPropagation(); // 阻止事件冒泡(避免关闭灯箱)
goToPrev();
}}
>
<LucideIcon.ChevronLeft className="!size-6" />
<span className="sr-only">Previous image</span>
<span className="sr-only">Previous image</span> // 屏幕阅读器文本
</Button>
{/* 下一张按钮 */}
<Button
className="absolute right-5 top-1/2 size-11 -translate-y-1/2 rounded-full bg-black/50 p-0 pl-0.5 hover:bg-black/70"
disabled={isLastImage}
disabled={isLastImage} // 最后一张时禁用
onClick={(e) => {
e.stopPropagation();
goToNext();
@ -200,6 +267,8 @@ const ImageLightbox: React.FC<ImageLightboxProps> = ({
</Button>
</>
)}
{/* 关闭按钮 */}
<DialogClose asChild>
<Button className="absolute right-5 top-5 size-11 rounded-full bg-black/50 p-0 hover:bg-black/70">
<LucideIcon.X className="!size-5" />
@ -211,4 +280,4 @@ const ImageLightbox: React.FC<ImageLightboxProps> = ({
);
};
export default ImageLightbox;
export default ImageLightbox;

@ -1,13 +1,20 @@
import AdminXComponent from './admin-x-component';
import {inject as service} from '@ember/service';
// AdminXActivityPub 继承自 AdminXComponent提供与 ActivityPub社交网络功能相关的绑定和属性。
export default class AdminXActivityPub extends AdminXComponent {
// 注入 upgradeStatus 服务,用于查询或响应系统升级状态(保留以备使用或观察)。
@service upgradeStatus;
// 注入 settings 服务,包含站点设置(例如 socialWebEnabled 标志)。
@service settings;
// 指定该组件所属的 NPM 包名,供框架或调试时识别来源。
static packageName = '@tryghost/admin-x-activitypub';
// additionalProps 返回一个对象,这些属性会传递给子组件或渲染模板。
// 这里我们暴露了 activityPubEnabled基于 settings 服务中的 socialWebEnabled 标志。
additionalProps = () => ({
// activityPubEnabled: 布尔值指示是否启用了社交网络ActivityPub功能。
activityPubEnabled: this.settings.socialWebEnabled
});
}

Loading…
Cancel
Save