From d41667b5997f0dfcb01648b31327e6a1dc0f15bd Mon Sep 17 00:00:00 2001 From: kangfenmao Date: Sat, 12 Oct 2024 09:53:20 +0800 Subject: [PATCH] feat: update release notes and add image preview component - Updated release notes to reflect changes including image preview and download. - Added interactive image preview component with toolbar for rotation, zooming, and downloading. - Added support for image previews in Markdown rendering. - Added functionality to download files from a URL with automatic filename detection and handling. --- electron-builder.yml | 5 +- .../src/pages/home/Markdown/ImagePreview.tsx | 62 +++++++++++++++++++ .../src/pages/home/Markdown/Markdown.tsx | 4 +- src/renderer/src/utils/download.ts | 61 ++++++++++++++++++ 4 files changed, 127 insertions(+), 5 deletions(-) create mode 100644 src/renderer/src/pages/home/Markdown/ImagePreview.tsx create mode 100644 src/renderer/src/utils/download.ts diff --git a/electron-builder.yml b/electron-builder.yml index 14f2770f..88710d6f 100644 --- a/electron-builder.yml +++ b/electron-builder.yml @@ -65,10 +65,7 @@ afterSign: scripts/notarize.js releaseInfo: releaseNotes: | 本次更新: - 支持更多的数学公式显示 - 可以通过搜索快速切换模型 - 增加 Bolt 小程序 - 修复 Azure OpenAI 默认模型无法使用问题 + 支持图片的预览和下载 近期更新: 增加 WebDAV 备份功能 by @DrayChou 增加话题历史记录 diff --git a/src/renderer/src/pages/home/Markdown/ImagePreview.tsx b/src/renderer/src/pages/home/Markdown/ImagePreview.tsx new file mode 100644 index 00000000..dbb62f70 --- /dev/null +++ b/src/renderer/src/pages/home/Markdown/ImagePreview.tsx @@ -0,0 +1,62 @@ +import { + DownloadOutlined, + RotateLeftOutlined, + RotateRightOutlined, + SwapOutlined, + UndoOutlined, + ZoomInOutlined, + ZoomOutOutlined +} from '@ant-design/icons' +import { download } from '@renderer/utils/download' +import { Image, Space } from 'antd' +import React from 'react' +import styled from 'styled-components' + +interface ImagePreviewProps extends React.ImgHTMLAttributes { + src: string +} + +const ImagePreview: React.FC = ({ src }) => { + return ( + ( + + + + + + + + + download(src)} /> + + ) + }} + /> + ) +} + +const ToobarWrapper = styled(Space)` + padding: 0px 24px; + color: #fff; + font-size: 20px; + background-color: rgba(0, 0, 0, 0.1); + border-radius: 100px; + .anticon { + padding: 12px; + cursor: pointer; + } + .anticon:hover { + opacity: 0.3; + } +` + +export default ImagePreview diff --git a/src/renderer/src/pages/home/Markdown/Markdown.tsx b/src/renderer/src/pages/home/Markdown/Markdown.tsx index 06d88680..d4bb34af 100644 --- a/src/renderer/src/pages/home/Markdown/Markdown.tsx +++ b/src/renderer/src/pages/home/Markdown/Markdown.tsx @@ -14,6 +14,7 @@ import remarkGfm from 'remark-gfm' import remarkMath from 'remark-math' import CodeBlock from './CodeBlock' +import ImagePreview from './ImagePreview' import Link from './Link' interface Props { @@ -25,7 +26,8 @@ const remarkPlugins = [remarkMath, remarkGfm] const components = { code: CodeBlock, - a: Link + a: Link, + img: ImagePreview } const Markdown: FC = ({ message }) => { diff --git a/src/renderer/src/utils/download.ts b/src/renderer/src/utils/download.ts new file mode 100644 index 00000000..79a77bf5 --- /dev/null +++ b/src/renderer/src/utils/download.ts @@ -0,0 +1,61 @@ +export const download = (url: string) => { + fetch(url) + .then((response) => { + // 尝试从Content-Disposition头获取文件名 + const contentDisposition = response.headers.get('Content-Disposition') + let filename = 'download' // 默认文件名 + + if (contentDisposition) { + const filenameMatch = contentDisposition.match(/filename="?(.+)"?/i) + if (filenameMatch) { + filename = filenameMatch[1] + } + } + + // 如果URL中有文件名,使用URL中的文件名 + const urlFilename = url.split('/').pop() + if (urlFilename && urlFilename.includes('.')) { + filename = urlFilename + } + + // 如果文件名没有后缀,根据Content-Type添加后缀 + if (!filename.includes('.')) { + const contentType = response.headers.get('Content-Type') + const extension = getExtensionFromMimeType(contentType) + filename += extension + } + + // 添加时间戳以确保文件名唯一 + const timestamp = Date.now() + const finalFilename = `${timestamp}_${filename}` + + return response.blob().then((blob) => ({ blob, finalFilename })) + }) + .then(({ blob, finalFilename }) => { + const blobUrl = URL.createObjectURL(new Blob([blob])) + const link = document.createElement('a') + link.href = blobUrl + link.download = finalFilename + document.body.appendChild(link) + link.click() + URL.revokeObjectURL(blobUrl) + link.remove() + }) +} + +// 辅助函数:根据MIME类型获取文件扩展名 +function getExtensionFromMimeType(mimeType: string | null): string { + if (!mimeType) return '.bin' // 默认二进制文件扩展名 + + const mimeToExtension: { [key: string]: string } = { + 'image/jpeg': '.jpg', + 'image/png': '.png', + 'image/gif': '.gif', + 'application/pdf': '.pdf', + 'text/plain': '.txt', + 'application/msword': '.doc', + 'application/vnd.openxmlformats-officedocument.wordprocessingml.document': '.docx' + } + + return mimeToExtension[mimeType] || '.bin' +}