mirror of
https://github.com/MoonTechLab/LunaTV.git
synced 2026-03-12 08:47:28 +08:00
feat: optimize prefer and VideoCard
This commit is contained in:
431
CHANGELOG.md
431
CHANGELOG.md
@@ -1,431 +0,0 @@
|
|||||||
<!-- //!STARTERCONF Remove this file, this is used as the starter changelog -->
|
|
||||||
|
|
||||||
# ts-nextjs-tailwind-starter changelog
|
|
||||||
|
|
||||||
This changelog is manually generated and not accurate with the package.json, only to show the changes since the last release.
|
|
||||||
|
|
||||||
## 1.0.0 - 2023-07-17
|
|
||||||
|
|
||||||
### New Features
|
|
||||||
|
|
||||||
- #### Next.js App Router
|
|
||||||
|
|
||||||
Now uses the new app directory structure.
|
|
||||||
|
|
||||||
### Improvements & Bug Fixes
|
|
||||||
|
|
||||||
- #### Rename `clsxm` to `cn`
|
|
||||||
|
|
||||||
For better support with shadcn/ui
|
|
||||||
|
|
||||||
- #### Faster Lint Actions
|
|
||||||
|
|
||||||
Lint jobs is now merged into one for faster performance, also updated the concurrency rule
|
|
||||||
|
|
||||||
## 0.5.4 - 2022-07-22
|
|
||||||
|
|
||||||
### New Features
|
|
||||||
|
|
||||||
- #### Release Please
|
|
||||||
|
|
||||||
Standard Version is now deprecated, and ts-nextjs-tailwind-starter is now using release please. Activate them on `.github/workflows/release-please`
|
|
||||||
|
|
||||||
### Improvements & Bug Fixes
|
|
||||||
|
|
||||||
- #### More Efficient Lint Actions
|
|
||||||
|
|
||||||
Lint workflow is now cached and will cancel previous run if there are 2 concurrent runs.
|
|
||||||
|
|
||||||
## 0.5.3 - 2022-02-27
|
|
||||||
|
|
||||||
### New Features
|
|
||||||
|
|
||||||
- #### Shimmer for NextImage and Skeleton
|
|
||||||
|
|
||||||
Addition of shimmer & blur placeholder for NextImage, and new Skeleton Component with shimmer effect.
|
|
||||||
|
|
||||||
https://user-images.githubusercontent.com/55318172/155867729-8c3176ad-ede4-4443-b42b-780517615e5a.mp4
|
|
||||||
|
|
||||||
|
|
||||||
- #### Support for SVGR
|
|
||||||
|
|
||||||
You can directly import SVG like
|
|
||||||
|
|
||||||
```tsx
|
|
||||||
import Vercel from '~/svg/Vercel.svg';
|
|
||||||
|
|
||||||
<Vercel className='text-5xl' />
|
|
||||||
```
|
|
||||||
|
|
||||||
- #### Public Folder Path Mapping
|
|
||||||
|
|
||||||
Easily access public folder with `~/` prefix.
|
|
||||||
|
|
||||||
- #### Tailwind CSS Prettier Sorter
|
|
||||||
|
|
||||||
ts-nextjs-tailwind-starter now use first-party plugin `prettier-plugin-tailwindcss`
|
|
||||||
|
|
||||||
### Improvements & Bug Fixes
|
|
||||||
|
|
||||||
- #### Layout Declared Twice
|
|
||||||
|
|
||||||
Fix issue where adding elements to Layout ends up rendering them twice
|
|
||||||
|
|
||||||
- #### ESLint Curly Brace Rule
|
|
||||||
|
|
||||||
New autofixable rule
|
|
||||||
|
|
||||||
```tsx
|
|
||||||
props={'hi'}
|
|
||||||
|
|
||||||
will become
|
|
||||||
|
|
||||||
props='hi'
|
|
||||||
```
|
|
||||||
|
|
||||||
## 0.5.2 - 2021-12-30
|
|
||||||
|
|
||||||
### New Features
|
|
||||||
|
|
||||||
- #### New Component: PrimaryLink
|
|
||||||
|
|
||||||
Add a link component with accent color on top of UnstyledLink.
|
|
||||||
|
|
||||||
### Improvements & Bug Fixes
|
|
||||||
|
|
||||||
- #### Unused Import ESlint Autofix
|
|
||||||
|
|
||||||
Unused import will automatically be removed by the ESlint autofix.
|
|
||||||
|
|
||||||
- #### Renamed CustomLink to UnderlineLink
|
|
||||||
|
|
||||||
This is to compensate the new PrimaryLink component
|
|
||||||
|
|
||||||
- #### Primary Button & ButtonLink Shade
|
|
||||||
|
|
||||||
The shade of the button is now using the `500` instead of `400`.
|
|
||||||
|
|
||||||
|
|
||||||
## 0.5.1 - 2021-12-26
|
|
||||||
|
|
||||||
### New Features
|
|
||||||
|
|
||||||
- #### New Snippets Wrap: clsx and fragment `<></>`
|
|
||||||
|
|
||||||
You can select text then wrap it with clsx or React.Fragment shorthand.
|
|
||||||
|
|
||||||
https://user-images.githubusercontent.com/55318172/147401848-3db5dba0-ef71-4f25-9f47-c7908beba69e.mp4
|
|
||||||
|
|
||||||
|
|
||||||
## 0.5.0 - 2021-12-21
|
|
||||||
|
|
||||||
### New Features
|
|
||||||
|
|
||||||
- #### Expansion Pack
|
|
||||||
You can easily add expansion such as React Hook Form + Components, Storybook, and more just using a single command line.
|
|
||||||
|
|
||||||
https://user-images.githubusercontent.com/55318172/146631994-e1cac137-1664-4cfe-950b-a96decc1eaa6.mp4
|
|
||||||
|
|
||||||
Check out the [expansion pack repository](https://github.com/theodorusclarence/expansion-pack) for the commands
|
|
||||||
|
|
||||||
### Improvements & Bug Fixes
|
|
||||||
|
|
||||||
- #### Can't Use Layout Fill on NextImage
|
|
||||||
|
|
||||||
Using layout fill will make the width and height optional
|
|
||||||
|
|
||||||
- #### Vertically center Button & ButtonLink
|
|
||||||
|
|
||||||
Adds `items-center` to make the button vertically centered
|
|
||||||
|
|
||||||
|
|
||||||
## 0.4.1 - 2021-12-12
|
|
||||||
|
|
||||||
### New Features
|
|
||||||
|
|
||||||
- #### Tailwind CSS v3
|
|
||||||
|
|
||||||
The color palette configuration is also updated accordingly.
|
|
||||||
|
|
||||||
## 0.4.0 - 2021-12-02
|
|
||||||
|
|
||||||
### New Features
|
|
||||||
|
|
||||||
- #### Button & ButtonLink Variants
|
|
||||||
|
|
||||||
- New Variant: **Outline** and **Ghost**, you can also add `isDarkBg` prop if you are using these variants with dark background.
|
|
||||||
- Animated Underline style on **Primary**, **Dark**, **Light** is removed
|
|
||||||
- Added `ring-primary-500` on `focus-visible`
|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
- #### ArrowLink
|
|
||||||
|
|
||||||
|
|
||||||
Adds an animated arrow, this component is Polymorphic, the default element is `CustomLink`, you can extend it with `as` prop.
|
|
||||||
|
|
||||||
```tsx
|
|
||||||
<ArrowLink
|
|
||||||
as={ButtonLink}
|
|
||||||
variant='light'
|
|
||||||
href='/'
|
|
||||||
>
|
|
||||||
Register now
|
|
||||||
</ArrowLink>
|
|
||||||
```
|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
- #### Change default theme to white
|
|
||||||
|
|
||||||
|  |  |
|
|
||||||
| - | - |
|
|
||||||
|
|
||||||
### Improvements & Bug Fixes
|
|
||||||
|
|
||||||
- #### Split Next.js Link Props Type
|
|
||||||
|
|
||||||
Now, to add props to Next.js `<Link>` component, you can use `nextLinkProps`.
|
|
||||||
|
|
||||||
```tsx
|
|
||||||
<UnstyledLink
|
|
||||||
href='/'
|
|
||||||
nextLinkProps={{
|
|
||||||
shallow: true,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
Link
|
|
||||||
</UnstyledLink>
|
|
||||||
```
|
|
||||||
|
|
||||||
The rest of `<a>` props can be directly added as a prop.
|
|
||||||
|
|
||||||
- #### Add Motion Safe to Animations
|
|
||||||
|
|
||||||
All components animation respect user preference about motion.
|
|
||||||
|
|
||||||
## 0.3.0 - 2021-12-01
|
|
||||||
|
|
||||||
### New Features
|
|
||||||
|
|
||||||
- #### Create Branch & Auto Resolve Issue Actions
|
|
||||||
|
|
||||||
|  <br> Auto Create Branch |  <br> Auto Resolve |
|
|
||||||
| :--: | :--: |
|
|
||||||
|
|
||||||
You have to install the app for your organization/account/repository from the [GitHub Marketplace](https://github.com/marketplace/create-issue-branch) for this to work.
|
|
||||||
|
|
||||||
The branch will be created on **assign** with format `i${number}-${issue_title_lowercase}`.
|
|
||||||
|
|
||||||
- #### Custom Tailwind CSS Class Sorter
|
|
||||||
|
|
||||||
Classes are sorted using [prettier-plugin-sort-class-names](https://github.com/PutziSan/prettier-plugin-sort-class-names) with custom class order on [this file](https://github.com/theodorusclarence/ts-nextjs-tailwind-starter/blob/main/prettier-plugin-sort-class-names-order) and custom variant order on [prettierrc](https://github.com/theodorusclarence/ts-nextjs-tailwind-starter/blob/main/.prettierrc.js)
|
|
||||||
|
|
||||||
With this plugin, we can now safely check the order of the classes using the preconfigured lint action.
|
|
||||||
|
|
||||||
## 0.2.0 - 2021-11-10
|
|
||||||
|
|
||||||
### New Features
|
|
||||||
|
|
||||||
- #### Jest
|
|
||||||
|
|
||||||
Jest is configured and will be run every push on Github Actions
|
|
||||||
|
|
||||||
- #### Lint Github Action
|
|
||||||
|
|
||||||
1. **ESLint** - will fail if there are any warning and error.
|
|
||||||
2. **Type Check** - will fail on `tsc` error.
|
|
||||||
3. **Prettier Check** - will fail if there are any formatting error.
|
|
||||||
4. **Test** - will fail if there are any test failure.
|
|
||||||
|
|
||||||
## 0.1.0
|
|
||||||
|
|
||||||
### New Features
|
|
||||||
|
|
||||||
- #### Installed Packages
|
|
||||||
|
|
||||||
1. [clsx](https://bundlephobia.com/package/clsx@latest), utility for constructing `className` strings conditionally.
|
|
||||||
2. [react-icons](https://bundlephobia.com/package/react-icons@latest), svg react icons of popular icon packs.
|
|
||||||
|
|
||||||
- #### UnstyledLink Component
|
|
||||||
|
|
||||||
Used as a component for Next.js Link. Will render out Next/Link if the href started with `/` or `#`, else will render an `a` tag with `target='_blank'`. Also add a cursor style for outside links
|
|
||||||
|
|
||||||
- #### CustomLink Component
|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
|
|
||||||
- #### Absolute Import
|
|
||||||
|
|
||||||
You can import without using relative path
|
|
||||||
|
|
||||||
```tsx
|
|
||||||
import Nav from '../../../components/Nav';
|
|
||||||
|
|
||||||
simplified to
|
|
||||||
|
|
||||||
import Nav from '@/components/Nav';
|
|
||||||
```
|
|
||||||
|
|
||||||
- #### Seo Component
|
|
||||||
|
|
||||||
Configure the default in `src/components/Seo.tsx`. If you want to use the default, just add `<Seo />` on top of your page.
|
|
||||||
|
|
||||||
You can also customize it per page by overriding the title, description as props
|
|
||||||
|
|
||||||
```tsx
|
|
||||||
<Seo title='Next.js Tailwind Starter' description='your description' />
|
|
||||||
```
|
|
||||||
|
|
||||||
or if you want to still keep the title like `%s | Next.js Tailwind Starter`, you can use `templateTitle` props.
|
|
||||||
|
|
||||||
- #### Custom 404 Page
|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
- #### Workspace Snippets
|
|
||||||
|
|
||||||
Snippets such as React import, useState, useEffect, React Component. [View more](/.vscode/typescriptreact.code-snippets)
|
|
||||||
|
|
||||||
- #### Husky, Prettier, Lint, and Commitlint Configured
|
|
||||||
|
|
||||||
3 Husky hooks including:
|
|
||||||
|
|
||||||
1. pre-commit, running `next lint` and format the code using prettier
|
|
||||||
2. commit-msg, running commitlint to ensure the use of [Conventional Commit](https://theodorusclarence.com/library/conventional-commit-readme) for commit messages
|
|
||||||
3. post-merge, running `yarn` every `git pull` or after merge to ensure all new packages are installed and ready to go
|
|
||||||
|
|
||||||
- #### Default Favicon Declaration
|
|
||||||
|
|
||||||
Use [Favicon Generator](https://www.favicon-generator.org/) and then overwrite the files in `/public/favicon`
|
|
||||||
|
|
||||||
- #### Default Tailwind CSS Base Styles
|
|
||||||
|
|
||||||
There are default styles for responsive heading sizes, and `.layout` to support a max-width for larger screen size. Find more about it on [my blog post](https://theodorusclarence.com/blog/tailwindcss-best-practice#1-using-layout-class-or-container)
|
|
||||||
|
|
||||||
- #### Open Graph Generator
|
|
||||||
|
|
||||||
|  |  |
|
|
||||||
| --------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------- |
|
|
||||||
|
|
||||||
Open Graph is generated using [og.thcl.dev](https://og.thcl.dev), but please fork and self-host if your website is going to have a lot of traffic.
|
|
||||||
|
|
||||||
Check out the [repository](https://github.com/theodorusclarence/og) to see the API parameters.
|
|
||||||
|
|
||||||
- #### Preloaded & Self Hosted Inter Fonts
|
|
||||||
|
|
||||||
Inter fonts is a variable fonts that is self hosted and preloaded.
|
|
||||||
|
|
||||||
## Snippets
|
|
||||||
|
|
||||||
This starter is equipped with workspace-snippets, it is encouraged to use it, especially the `np` and `rc`
|
|
||||||
|
|
||||||
### Next.js Page
|
|
||||||
|
|
||||||
File inside `src/pages` will be the webpage route, there are 2 things that need to be added in Next.js page:
|
|
||||||
|
|
||||||
1. Seo component
|
|
||||||
2. Layout class to give constraint to viewport width. [Read more about layout class](https://theodorusclarence.com/blog/tailwindcss-best-practice#1-using-layout-class-or-container).
|
|
||||||
|
|
||||||
Snippets: `np`
|
|
||||||
|
|
||||||
```tsx
|
|
||||||
import * as React from 'react';
|
|
||||||
import Seo from '@/components/Seo';
|
|
||||||
export default function TestPage() {
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<Seo templateTitle='Test' />
|
|
||||||
<main>
|
|
||||||
<section className=''>
|
|
||||||
<div className='layout'></div>
|
|
||||||
</section>
|
|
||||||
</main>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### React Components
|
|
||||||
|
|
||||||
To make a new component, It is encouraged to use `export default function`. Because when we need to rename it, we only need to do it once.
|
|
||||||
|
|
||||||
Snippets: `rc`
|
|
||||||
|
|
||||||
```tsx
|
|
||||||
import * as React from 'react';
|
|
||||||
export default function Component() {
|
|
||||||
return <div></div>;
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
#### Import React
|
|
||||||
|
|
||||||
Snippets: `ir`
|
|
||||||
|
|
||||||
```tsx
|
|
||||||
import * as React from 'react';
|
|
||||||
```
|
|
||||||
|
|
||||||
#### Import Next Image
|
|
||||||
|
|
||||||
Snippets: `imimg`
|
|
||||||
|
|
||||||
```tsx
|
|
||||||
import Image from 'next/image';
|
|
||||||
```
|
|
||||||
|
|
||||||
#### Import Next Link
|
|
||||||
|
|
||||||
Snippets: `iml`
|
|
||||||
|
|
||||||
```tsx
|
|
||||||
import Link from 'next/link';
|
|
||||||
```
|
|
||||||
|
|
||||||
#### useState Hook
|
|
||||||
|
|
||||||
Snippets: `us`
|
|
||||||
|
|
||||||
```tsx
|
|
||||||
const [state, setState] = React.useState(initialState);
|
|
||||||
```
|
|
||||||
|
|
||||||
#### useEffect Hook
|
|
||||||
|
|
||||||
Snippets: `uf`
|
|
||||||
|
|
||||||
```tsx
|
|
||||||
React.useEffect(() => {}, []);
|
|
||||||
```
|
|
||||||
|
|
||||||
#### useReducer Hook
|
|
||||||
|
|
||||||
Snippets: `ur`
|
|
||||||
|
|
||||||
```tsx
|
|
||||||
const [state, dispatch] = React.useReducer(someReducer, {});
|
|
||||||
```
|
|
||||||
|
|
||||||
#### useRef Hook
|
|
||||||
|
|
||||||
Snippets: `urf`
|
|
||||||
|
|
||||||
```tsx
|
|
||||||
const someRef = React.useRef();
|
|
||||||
```
|
|
||||||
|
|
||||||
#### Region Comment
|
|
||||||
|
|
||||||
It is really useful when we need to group code. It is also collapsible in VSCode
|
|
||||||
|
|
||||||
Snippets: `reg`
|
|
||||||
|
|
||||||
```tsx
|
|
||||||
//#region //*============== FORM SUBMIT
|
|
||||||
//#endregion //*============== FORM SUBMIT
|
|
||||||
```
|
|
||||||
|
|
||||||
You should also use [Better Comments](https://marketplace.visualstudio.com/items?itemName=aaron-bond.better-comments) extension.
|
|
||||||
|
|
||||||
@@ -176,10 +176,11 @@ function PlayPageClient() {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const firstEpisodeUrl = source.episodes[0];
|
const episodeUrl =
|
||||||
const testResult = await getVideoResolutionFromM3u8(
|
source.episodes.length > 1
|
||||||
firstEpisodeUrl
|
? source.episodes[1]
|
||||||
);
|
: source.episodes[0];
|
||||||
|
const testResult = await getVideoResolutionFromM3u8(episodeUrl);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
source,
|
source,
|
||||||
|
|||||||
@@ -108,14 +108,17 @@ const EpisodeSelector: React.FC<EpisodeSelectorProps> = ({
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 获取第一集的URL
|
// 获取第一集的URL
|
||||||
const firstEpisodeUrl = source.episodes?.[0];
|
if (!source.episodes || source.episodes.length === 0) {
|
||||||
if (!firstEpisodeUrl) return;
|
return;
|
||||||
|
}
|
||||||
|
const episodeUrl =
|
||||||
|
source.episodes.length > 1 ? source.episodes[1] : source.episodes[0];
|
||||||
|
|
||||||
// 标记为已尝试
|
// 标记为已尝试
|
||||||
setAttemptedSources((prev) => new Set(prev).add(sourceKey));
|
setAttemptedSources((prev) => new Set(prev).add(sourceKey));
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const info = await getVideoResolutionFromM3u8(firstEpisodeUrl);
|
const info = await getVideoResolutionFromM3u8(episodeUrl);
|
||||||
setVideoInfoMap((prev) => new Map(prev).set(sourceKey, info));
|
setVideoInfoMap((prev) => new Map(prev).set(sourceKey, info));
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
// 失败时保存错误状态
|
// 失败时保存错误状态
|
||||||
|
|||||||
@@ -46,7 +46,6 @@ export default function VideoCard({
|
|||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const [favorited, setFavorited] = useState(false);
|
const [favorited, setFavorited] = useState(false);
|
||||||
const [isLoaded, setIsLoaded] = useState(false);
|
const [isLoaded, setIsLoaded] = useState(false);
|
||||||
const [isDeleting, setIsDeleting] = useState(false);
|
|
||||||
|
|
||||||
const isAggregate = from === 'search' && !!items?.length;
|
const isAggregate = from === 'search' && !!items?.length;
|
||||||
|
|
||||||
@@ -119,7 +118,7 @@ export default function VideoCard({
|
|||||||
}, [from, actualSource, actualId]);
|
}, [from, actualSource, actualId]);
|
||||||
|
|
||||||
const handleToggleFavorite = useCallback(
|
const handleToggleFavorite = useCallback(
|
||||||
async (e: React.MouseEvent<HTMLDivElement>) => {
|
async (e: React.MouseEvent) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
|
|
||||||
@@ -152,7 +151,7 @@ export default function VideoCard({
|
|||||||
);
|
);
|
||||||
|
|
||||||
const handleDeleteRecord = useCallback(
|
const handleDeleteRecord = useCallback(
|
||||||
async (e: React.MouseEvent<HTMLDivElement>) => {
|
async (e: React.MouseEvent) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
|
|
||||||
@@ -160,7 +159,6 @@ export default function VideoCard({
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
await deletePlayRecord(actualSource, actualId);
|
await deletePlayRecord(actualSource, actualId);
|
||||||
setIsDeleting(true);
|
|
||||||
onDelete?.();
|
onDelete?.();
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
throw new Error('删除播放记录失败');
|
throw new Error('删除播放记录失败');
|
||||||
@@ -239,11 +237,7 @@ export default function VideoCard({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={`group relative w-full rounded-lg bg-transparent transition-all duration-300 transform ${
|
className='group relative w-full rounded-lg bg-transparent transition-all duration-300 transform hover:-translate-y-1 hover:scale-[1.02]'
|
||||||
isDeleting
|
|
||||||
? 'opacity-0 scale-90 translate-y-4'
|
|
||||||
: 'hover:-translate-y-1 hover:scale-[1.02]'
|
|
||||||
}`}
|
|
||||||
onClick={handleClick}
|
onClick={handleClick}
|
||||||
>
|
>
|
||||||
{/* 海报容器 */}
|
{/* 海报容器 */}
|
||||||
@@ -278,38 +272,25 @@ export default function VideoCard({
|
|||||||
{(config.showHeart || config.showCheckCircle) && (
|
{(config.showHeart || config.showCheckCircle) && (
|
||||||
<div className='absolute bottom-3 right-3 flex items-center gap-3 transform translate-y-2 opacity-0 group-hover:translate-y-0 group-hover:opacity-100 transition-all duration-300 ease-out'>
|
<div className='absolute bottom-3 right-3 flex items-center gap-3 transform translate-y-2 opacity-0 group-hover:translate-y-0 group-hover:opacity-100 transition-all duration-300 ease-out'>
|
||||||
{config.showCheckCircle && (
|
{config.showCheckCircle && (
|
||||||
<button
|
<CheckCircle
|
||||||
onClick={(e: React.MouseEvent<HTMLButtonElement>) =>
|
onClick={handleDeleteRecord}
|
||||||
handleDeleteRecord(
|
size={20}
|
||||||
e as unknown as React.MouseEvent<HTMLDivElement>
|
className='rounded-full transition-all duration-300ms transform hover:scale-110 text-white hover:stroke-green-500'
|
||||||
)
|
aria-label='标记为已看'
|
||||||
}
|
/>
|
||||||
title='标记为已看'
|
|
||||||
className='p-1.5 rounded-full transition-all duration-300 transform hover:scale-110 hover:bg-white/30'
|
|
||||||
>
|
|
||||||
<CheckCircle size={20} className='text-white' />
|
|
||||||
</button>
|
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{config.showHeart && (
|
{config.showHeart && (
|
||||||
<button
|
<Heart
|
||||||
onClick={(e: React.MouseEvent<HTMLButtonElement>) =>
|
onClick={handleToggleFavorite}
|
||||||
handleToggleFavorite(
|
size={20}
|
||||||
e as unknown as React.MouseEvent<HTMLDivElement>
|
className={`rounded-full transition-all duration-300ms transform hover:scale-110 ${
|
||||||
)
|
favorited
|
||||||
}
|
? 'fill-red-600 stroke-red-600'
|
||||||
title={favorited ? '取消收藏' : '加入收藏'}
|
: 'fill-transparent stroke-white hover:stroke-red-400'
|
||||||
className='p-1.5 rounded-full transition-all duration-300 transform hover:scale-110 hover:bg-white/30'
|
}`}
|
||||||
>
|
aria-label={favorited ? '取消收藏' : '加入收藏'}
|
||||||
<Heart
|
/>
|
||||||
size={20}
|
|
||||||
className={`transition-all duration-300 ${
|
|
||||||
favorited
|
|
||||||
? 'fill-red-600 stroke-red-600'
|
|
||||||
: 'fill-transparent stroke-white hover:stroke-red-400'
|
|
||||||
}`}
|
|
||||||
/>
|
|
||||||
</button>
|
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|||||||
Reference in New Issue
Block a user