Compare commits

...

2 Commits

Author SHA1 Message Date
58ace4514b feat: 新闻编辑 2024-12-16 19:14:39 +08:00
0592d97e39 feat: update 2024-12-16 16:54:06 +08:00
15 changed files with 316 additions and 168 deletions

View File

@ -24,6 +24,7 @@
"clsx": "^2.1.1",
"dayjs": "^1.11.11",
"file-saver": "^2.0.5",
"flv.js": "^1.6.2",
"jszip": "^3.10.1",
"qs": "^6.12.1",
"react": "^18.3.1",

View File

@ -1,3 +1,5 @@
@use "./libs" as *;
:root {
font-family: -apple-system, "PingFang SC", 'Microsoft YaHei', sans-serif;
line-height: 1.5;
@ -25,32 +27,35 @@
}
::-webkit-scrollbar-thumb {
background: #999;
background: #ccc;
height: 10px;
border-radius: 5px;
&:hover {
background: #666;
background: #999;
cursor: pointer;
}
}
.btn {
@apply px-5 py-2 rounded-md bg-white border text-sm;
&:hover {
@apply bg-gray-100;
}
&.btn-primary {
@apply bg-blue-500 text-white border-blue-500;
@layer base {
.btn {
@apply px-5 py-2 rounded-md bg-white border text-sm;
&:hover {
@apply bg-blue-600;
@apply bg-gray-100;
}
&.btn-primary {
@apply bg-blue-500 text-white border-blue-500;
&:hover {
@apply bg-blue-600;
}
}
}
}
.card {
@apply bg-white rounded-lg p-5 my-10;
.card {
@apply bg-white rounded-lg p-5 my-10;
}
}
.radio-icon, .checkbox-icon {
@ -121,31 +126,41 @@
}
}
}
.page-live{
.live-player{
.page-live {
.live-player {
max-height: calc(100vh - var(--app-header-header) - 130px);
overflow: hidden;
iframe{
iframe {
width: 100%;
height: 100%;
overflow: hidden;
}
}
}
.video-item-shadow {
box-shadow: 0 0 6px 0 var(--tw-shadow-color);
//filter: drop-shadow(0 0 6px var(--tw-shadow-color));
}
.video-list-sort-container{
.video-list-sort-container {
min-height: 300px;
max-height: calc(100vh - var(--app-header-header) - 300px);
overflow: auto;
padding-right: 10px;
}
.live-video-list-sort-container{
.live-video-list-sort-container {
min-height: 300px;
padding-right: 10px;
max-height: calc(100vh - var(--app-header-header) - 200px);
overflow: auto;
}
.app-main-navigation {
@include media-breakpoint-down(md) {
display: none;
}
}

22
src/assets/libs.scss Normal file
View File

@ -0,0 +1,22 @@
@mixin media-breakpoint-down($name) {
@if $name == sm {
@media (max-width: 767px) {
@content;
}
}
@if $name == md {
@media (max-width: 991px) {
@content;
}
}
@if $name == lg {
@media (max-width: 1199px) {
@content;
}
}
@if $name == xl {
@media (max-width: 1399px) {
@content;
}
}
}

View File

@ -80,7 +80,6 @@ export default function ArticleBlock(
<div>
<div className={clsx(index == 0 ? '' : styles.blockItem, 'flex')}>
<BlockText
isFirstBlock={true}
onChange={(block) => handleBlockChange(0, block)}
data={blocks[0]}
isFirstBlock={index == 0}
@ -98,7 +97,7 @@ export default function ArticleBlock(
{editable && <div className="ml-2 flex flex-col justify-between ">
{
index > 0 ? <Popconfirm
title={<div style={{minWidth: 150}}><span>?</span></div>}
title={<div style={{minWidth: 150}}><span>?</span></div>}
onConfirm={onRemove}
okText="删除"
cancelText="取消"

View File

@ -11,19 +11,46 @@ type Props = {
errorMessage?: string;
}
function pushBlocksToGroup(blocks: BlockContent[],groups: BlockContent[][]){
const lastGroup = groups[groups.length - 1]
if (lastGroup && lastGroup.filter(s=>s.type == 'text') == 0) {
// 如果上一个group中没有文本则直接合并
lastGroup.push(...blocks)
} else {
groups.push(blocks)
}
}
function rebuildGroups(groups: BlockContent[][]) {
if (groups.length < 2) {
Array(2 - groups.length).fill([{type: 'text', content: ''}]).forEach((it) => {
groups.push(it)
const _groups: BlockContent[][] = [];
if (!groups || groups.length == 0) return _groups;
groups.forEach((blocks,index) => {
if(!blocks) return;
if (blocks.length == 1) {
if(index == 0) _groups.push(blocks)
else pushBlocksToGroup(blocks,_groups)
} else {
if(index == 0){
_groups.push([blocks[0]])
_groups.push(blocks.slice(1))
}else{
pushBlocksToGroup(blocks,_groups)
}
}
});
if (_groups.length < 2) {
Array(2 - _groups.length).fill([{type: 'text', content: ''}]).forEach((it) => {
_groups.push(it)
})
}
return groups;
console.log('rebuildGroups', _groups)
return _groups;
}
export default function ArticleGroup({groups: _groups, editable, onChange,errorMessage}: Props) {
export default function ArticleGroup({groups: _groups, editable, onChange, errorMessage}: Props) {
const groups = rebuildGroups(_groups)
/**
*

View File

@ -2,7 +2,7 @@ import {useSortable} from "@dnd-kit/sortable";
import {useSetState} from "ahooks";
import React, {useEffect} from "react";
import {clsx} from "clsx";
import {Popconfirm} from "antd";
import {Image, Popconfirm} from "antd";
import {CheckCircleFilled, MenuOutlined, MinusCircleFilled} from "@ant-design/icons";
import ImageCover from '@/assets/images/cover.png'
@ -28,7 +28,7 @@ export const VideoListItem = (
// index,
id, video, onPlay, onRemove, checked,
onCheckedChange, onEdit, active, editable,
className,
className, sortable
}: Props) => {
const {
attributes, listeners,
@ -45,23 +45,23 @@ export const VideoListItem = (
className={`video-item flex items-center gap-3 ${className}`}
ref={setNodeRef} style={{transform: `translateY(${transform?.y || 0}px)`,}}>
{/*{index && index > 0 && <div className="flex items-center px-2">*/}
{/* <div className="index-value w-[40px] h-[40px] flex items-center justify-center bg-gray-100 rounded-3xl">{index}</div>*/}
{/*</div>}*/}
{/* <div className="index-value w-[40px] h-[40px] flex items-center justify-center bg-gray-100 rounded-3xl">{index}</div>*/}
{/*</div>}*/}
<div
className={`video-item-info flex gap-2 flex-1 bg-gray-100 h-[80px] overflow-hidden rounded-lg p-3 shadow-blue-500 ${active ? 'video-item-shadow' : ''}`}>
<div className={'video-title leading-7 flex-1'}>{video.title || video.video_title}</div>
<div className={'video-item-cover'}>
<img className="w-[100px] rounded-md" src={video.cover_url || ImageCover} alt={video.video_title}/>
<div className={'video-item-cover bg-white rounded-md overflow-hidden'}>
<img className="w-[100px] h-[56px] object-cover" src={video.cover_url || video.cover || ImageCover} alt={video.video_title}/>
</div>
</div>
{editable &&
<div className="operation flex items-center ml-2 gap-3 text-lg text-gray-400">
{!active ? <button className="hover:text-blue-500 cursor-move" {...attributes} {...listeners}>
<MenuOutlined/>
</button> : <button disabled className="cursor-not-allowed"><MenuOutlined/></button>}
{onPlay &&
<button className="hover:text-blue-500" onClick={onPlay} style={{fontSize: '1.3em'}}><IconPlay/>
</button>}
<div className="operation flex items-center ml-2 gap-3 text-lg text-gray-400">
{sortable && (!active ? <button className="hover:text-blue-500 cursor-move" {...attributes} {...listeners}>
<MenuOutlined/>
</button> : <button disabled className="cursor-not-allowed"><MenuOutlined/></button>)}
{onPlay &&
<button className="hover:text-blue-500" onClick={onPlay} style={{fontSize: '1.3em'}}><IconPlay/>
</button>}
{editable && <>
{onEdit &&
<button className="hover:text-blue-500" onClick={onEdit} style={{fontSize: '1.1em'}}><IconEdit/>
</button>}
@ -73,15 +73,14 @@ export const VideoListItem = (
}
}}><CheckCircleFilled className={clsx({'text-blue-500': state.checked})}/></button>
{onRemove && <Popconfirm
title="提示"
description={<div style={{minWidth: 150}}><span>?</span></div>}
title={<div style={{minWidth: 150}}><span>?</span></div>}
onConfirm={onRemove}
okText="删除"
cancelText="取消"
>
<button className="hover:text-blue-500"><MinusCircleFilled/></button>
</Popconfirm>}
</div>
}
</>}
</div>
</div>
}

View File

@ -4,11 +4,12 @@ import {SortableContext, arrayMove} from '@dnd-kit/sortable';
import {DndContext} from "@dnd-kit/core";
import {VideoListItem} from "@/components/video/video-list-item.tsx";
import {getList, playState} from "@/service/api/live.ts";
import {deleteByIds, getList, modifyOrder, playState} from "@/service/api/live.ts";
import styles from './style.module.scss'
import {set} from "lodash";
import {showToast} from "@/components/message.ts";
import {showErrorToast, showToast} from "@/components/message.ts";
import ButtonBatch from "@/components/button-batch.tsx";
export default function LiveIndex() {
const [videoData, setVideoData] = useState<LiveVideoInfo[]>([])
@ -38,6 +39,7 @@ export default function LiveIndex() {
})
}
}
const activeToNext = (index?: number) => {
const endToFirst = index != undefined && index > -1 ? false : state.activeIndex >= videoData.length - 1
const activeIndex = index != undefined && index > -1 ? index : (endToFirst ? 0 : state.activeIndex + 1)
@ -60,105 +62,51 @@ export default function LiveIndex() {
})
});
}
useEffect(() => {
const loadList = () => {
getList().then(res => {
res.list = [
{
id: 11,
video_id: 1,
video_title: '333专家分析丨叙土边境曼比季地理位置为何如此重要',
cover_url: 'https://gachafun.oss-cn-beijing.aliyuncs.com/2021/08/13/1628840269744.jpg',
video_oss_url: 'https://gachafun.oss-cn-beijing.aliyuncs.com/2021/08/13/1628840269744.mp4',
video_duration: 100,
status: 1,
order_no: '1'
},
{
id: 0,
video_id: 1,
video_title: '333专家分析丨叙土边境曼比季地理位置为何如此重要',
cover_url: 'https://gachafun.oss-cn-beijing.aliyuncs.com/2021/08/13/1628840269744.jpg',
video_oss_url: 'https://gachafun.oss-cn-beijing.aliyuncs.com/2021/08/13/1628840269744.mp4',
video_duration: 100,
status: 1,
order_no: '1'
},
{
id: 10,
video_id: 1,
video_title: '333专家分析丨叙土边境曼比季地理位置为何如此重要',
cover_url: 'https://gachafun.oss-cn-beijing.aliyuncs.com/2021/08/13/1628840269744.jpg',
video_oss_url: 'https://gachafun.oss-cn-beijing.aliyuncs.com/2021/08/13/1628840269744.mp4',
video_duration: 100,
status: 1,
order_no: '1'
},
{
id: 4,
video_id: 1,
video_title: '333专家分析丨叙土边境曼比季地理位置为何如此重要',
cover_url: 'https://gachafun.oss-cn-beijing.aliyuncs.com/2021/08/13/1628840269744.jpg',
video_oss_url: 'https://gachafun.oss-cn-beijing.aliyuncs.com/2021/08/13/1628840269744.mp4',
video_duration: 100,
status: 1,
order_no: '1'
},
{
id: 5,
video_id: 1,
video_title: '333专家分析丨叙土边境曼比季地理位置为何如此重要',
cover_url: 'https://gachafun.oss-cn-beijing.aliyuncs.com/2021/08/13/1628840269744.jpg',
video_oss_url: 'https://gachafun.oss-cn-beijing.aliyuncs.com/2021/08/13/1628840269744.mp4',
video_duration: 100,
status: 1,
order_no: '1'
},
{
id: 6,
video_id: 1,
video_title: '333专家分析丨叙土边境曼比季地理位置为何如此重要',
cover_url: 'https://gachafun.oss-cn-beijing.aliyuncs.com/2021/08/13/1628840269744.jpg',
video_oss_url: 'https://gachafun.oss-cn-beijing.aliyuncs.com/2021/08/13/1628840269744.mp4',
video_duration: 100,
status: 1,
order_no: '1'
},
{
id: 7,
video_id: 1,
video_title: '333专家分析丨叙土边境曼比季地理位置为何如此重要',
cover_url: 'https://gachafun.oss-cn-beijing.aliyuncs.com/2021/08/13/1628840269744.jpg',
video_oss_url: 'https://gachafun.oss-cn-beijing.aliyuncs.com/2021/08/13/1628840269744.mp4',
video_duration: 100,
status: 1,
order_no: '1'
}
]
console.log('origin list', res.list.map(s => s.id))
setVideoData(res.list || [])
initPlayingState(res.list || [])
})
}, [])
const processDeleteVideo = async (_idArray: number[]) => {
message.info('删除成功!!!' + _idArray.join(''));
setCheckedIdArray([])
initPlayingState(res.list)
});
}
const handleDeleteBatch = () => {
modal.confirm({
title: '提示',
content: '是否要删除选择的视频?',
onOk: () => processDeleteVideo(checkedIdArray)
})
useEffect(loadList, [])
const processDeleteVideo = async (ids: number[]) => {
deleteByIds(ids).then(()=>{
showToast('删除成功!','success')
loadList()
}).catch(showErrorToast)
}
const handleConfirm = () => {
modal.confirm({
title: '提示',
content: '是否采纳全部编辑操作?',
content: '是否采纳移动视频位置操作?',
onOk: () => {
showToast('编辑成功!!!', 'info');
//showToast('编辑成功!!!', 'info');
modifyOrder(videoData.map(s => s.id)).then(() => {
setEditable(false)
loadList()
}).catch(() => {
showToast('调整视频顺序失败,请重试!')
})
// showToast('编辑成功!!!', 'info');
// console.log('origin list', videoData.map(s => s.id))
}
})
}
const handleCancelConfirm = () => {
modal.confirm({
title: '提示',
content: '是否取消移动视频位置操作?',
onOk: () => {
showToast('退出并清除移动视频位置操作!', 'info');
loadList()
setEditable(false)
},
})
}
return (<div className="container py-10 page-live">
{contextHolder}
@ -177,14 +125,20 @@ export default function LiveIndex() {
{editable ? <>
<div className="flex gap-2">
<Button type="primary" onClick={handleConfirm}></Button>
<Button onClick={() => setEditable(false)}>退</Button>
</div>
<div>
<span className="cursor-pointer" onClick={handleDeleteBatch}></span>
<Button onClick={handleCancelConfirm}>退</Button>
</div>
</> : <div>
<Button type="primary" onClick={() => setEditable(true)}></Button>
<Button type="primary" onClick={() => setEditable(true)}></Button>
</div>}
{!editable && <div>
<ButtonBatch
selected={checkedIdArray}
emptyMessage={`请选择要删除的新闻视频`}
confirmMessage={`是否删除当前的${checkedIdArray.length}个新闻视频?`}
onSuccess={loadList}
onProcess={processDeleteVideo}
></ButtonBatch>
</div>}
</div>
<div className="live-video-list-sort-container">
<div className="flex">
@ -201,22 +155,22 @@ export default function LiveIndex() {
const {active, over} = e;
if (over && active.id !== over.id) {
let oldIndex = -1, newIndex = -1;
const originArr = [...videoData]
// const originArr = [...videoData]
setVideoData((items) => {
oldIndex = items.findIndex(s => s.id == active.id);
newIndex = items.findIndex(s => s.id == over.id);
return arrayMove(items, oldIndex, newIndex);
});
modal.confirm({
title: '提示',
content: '是否要移动到指定位置',
onCancel: () => {
setVideoData(originArr);
},
onOk: () => {
setVideoData([...videoData])
}
})
// modal.confirm({
// title: '提示',
// content: '是否要移动到指定位置',
// onCancel: () => {
// setVideoData(originArr);
// },
// onOk: () => {
// setVideoData([...videoData])
// }
// })
}
}}>
<SortableContext items={videoData}>
@ -225,16 +179,18 @@ export default function LiveIndex() {
video={v}
index={index + 1}
id={v.id}
active={state.activeIndex == index}
key={index}
active={state.activeIndex == index}
className={`list-item-${index} mt-3 mb-2`}
checked={checkedIdArray.includes(v.id)}
onCheckedChange={(checked) => {
setCheckedIdArray(idArray => {
return checked ? idArray.concat(v.id) : idArray.filter(id => id != v.id);
})
}}
onRemove={() => processDeleteVideo([v.id])}
editable={editable}
editable={!editable}
sortable={editable}
/>))}
</SortableContext>
</DndContext>

View File

@ -1,13 +1,12 @@
import {useState} from "react";
import {Checkbox, Empty, Modal, Pagination, Space} from "antd";
import {useRequest, useSetState} from "ahooks";
import {useRequest} from "ahooks";
import {Card} from "@/components/card";
import {getList} from "@/service/api/article.ts";
import SearchPanel from "@/pages/news/components/search-panel.tsx";
import styles from './style.module.scss'
import {getById} from "@/service/api/news.ts";
import {getById,getList} from "@/service/api/news.ts";
import {showLoading} from "@/components/message.ts";
import {formatTime} from "@/util/strings.ts";
import ButtonPushNews2Article from "@/pages/news/components/button-push-news2article.tsx";

90
src/pages/test.tsx Normal file
View File

@ -0,0 +1,90 @@
import {useRef, useState} from "react";
import {Button} from "antd";
import FlvJs from "flv.js";
const list = [
{
"id": 10,
"cover_url": "",
"video_id": 51,
"video_title": "以军称在加沙地带打死一名哈马斯高级官员",
"video_duration": 31910,
"video_oss_url": "https://staticplus.gachafun.com/ai-collect/composite_video/2024-12-14/1185251497659736064.flv",
"status": 4,
"order_no": ""
},
{
"id": 8,
"cover_url": "",
"video_id": 43,
"video_title": "历时12天史上第三人 尹锡悦总统弹劾案获通过 一文梳理韩国政坛众生相",
"video_duration": 728840,
"video_oss_url": "https://staticplus.gachafun.com/ai-collect/composite_video/2024-12-14/1185229869001351168.flv",
"status": 4,
"order_no": ""
},
{
"id": 9,
"cover_url": "",
"video_id": 44,
"video_title": "推动房地产市场止跌回稳,发力重点在哪里?",
"video_duration": 57500,
"video_oss_url": "https://staticplus.gachafun.com/ai-collect/composite_video/2024-12-14/1185229857764810752.flv",
"status": 4,
"order_no": ""
},
{
"id": 11,
"cover_url": "",
"video_id": 52,
"video_title": "以军称在加沙地带打死一名哈马斯高级官员",
"video_duration": 37980,
"video_oss_url": "https://staticplus.gachafun.com/ai-collect/composite_video/2024-12-14/1185251495390617600.flv",
"status": 4,
"order_no": ""
}
]
const cache:{
flvPlayer?: FlvJs.Player
} = {
}
export default function Test() {
const videoRef = useRef<HTMLVideoElement | null>(null)
const [index, setIndex] = useState(-1)
const load = (url: string) => {
if (FlvJs.isSupported()) {
if(cache.flvPlayer){
cache.flvPlayer.pause()
cache.flvPlayer.unload()
}
cache.flvPlayer = FlvJs.createPlayer({
type: 'flv',
url: url
})
cache.flvPlayer.attachMediaElement(videoRef.current!)
cache.flvPlayer.load()
cache.flvPlayer.play()
}
// const url = 'https://staticplus.gachafun.com/ai-collect/composite_video/2024-12-14/1185229869001351168.flv'
// if (videoRef.current) {
// videoRef.current!.src = url
// videoRef.current?.play()
// }
}
const play = () => {
const next = index >= list.length - 1 ? 0 : index + 1
load(list[next].video_oss_url)
setIndex(next)
}
return (<div className="test container m-auto">
<div className="my-10">
<video controls className="border w-[400px] max-h-[600px]" ref={videoRef}></video>
</div>
<Button onClick={play}>load {index > -1 ? <span>
{index} {list[index].video_title}
</span>:''}</Button>
</div>)
}

View File

@ -10,11 +10,11 @@ import {VideoListItem} from "@/components/video/video-list-item.tsx";
import ArticleEditModal from "@/components/article/edit-modal.tsx";
import {deleteByIds, getList, modifyOrder, push2room} from "@/service/api/video.ts";
import {formatDuration} from "@/util/strings.ts";
import ButtonPush2Room from "@/pages/video/components/button-push2room.tsx";
import ButtonBatch from "@/components/button-batch.tsx";
import {showErrorToast, showToast} from "@/components/message.ts";
import {showToast} from "@/components/message.ts";
import FlvJs from "flv.js";
const cache:{flvPlayer?: FlvJs.Player} = {}
export default function VideoIndex() {
const [editId, setEditId] = useState(-1)
const [videoData, setVideoData] = useState<VideoInfo[]>([])
@ -29,7 +29,6 @@ export default function VideoIndex() {
// 加载列表
const loadList = () => {
getList().then((ret) => {
console.log('origin list', ret.list.map(s => s.id))
setCheckedIdArray([])
setVideoData(ret.list || [])
setState({checkedAll: false, playingIndex: -1})
@ -40,6 +39,21 @@ export default function VideoIndex() {
const playVideo = (video: VideoInfo, playingIndex: number) => {
if (videoRef.current && video.oss_video_url) {
setState({playingIndex})
if (FlvJs.isSupported()) {
// 已经有播放实例 则销毁
if(cache.flvPlayer){
cache.flvPlayer.pause()
cache.flvPlayer.unload()
}
cache.flvPlayer = FlvJs.createPlayer({
type: 'flv',
url: video.oss_video_url
})
cache.flvPlayer.attachMediaElement(videoRef.current!)
cache.flvPlayer.load()
cache.flvPlayer.play()
}
videoRef.current!.src = video.oss_video_url
}
}
@ -51,7 +65,6 @@ export default function VideoIndex() {
})
}
const handleModifySort = () => {
setVideoData((items) => {
modifyOrder(items.map(s => s.id)).catch(() => {
showToast('调整视频顺序失败,请重试!')
@ -81,7 +94,10 @@ export default function VideoIndex() {
selected={checkedIdArray}
emptyMessage={`请选择要删除的新闻视频`}
confirmMessage={`是否删除当前的${checkedIdArray.length}个新闻视频?`}
onSuccess={loadList}
onSuccess={()=>{
showToast('删除成功!','success')
loadList()
}}
></ButtonBatch>
<button className="ml-5 hover:text-blue-300 text-gray-400 text-lg"
@ -144,7 +160,8 @@ export default function VideoIndex() {
onEdit={() => {
setEditId(v.article_id)
}}
editable
editable={true}
sortable={true}
/>))}
</SortableContext>
</DndContext>

View File

@ -36,7 +36,7 @@ const NavItems = [
]
export function DashboardNavigation() {
return (<div className={'flex'}>
return (<div className={'flex app-main-navigation'}>
{NavItems.map((it, idx) => (
<NavLink to={it.path} key={idx} className={clsx('nav-item cursor-pointer items-center')}>
<span className="menu-text ml-1">{it.name}</span>

View File

@ -1,6 +1,7 @@
import {RouteObject} from "react-router-dom";
import ErrorBoundary from "@/routes/error.tsx";
import UserAuth from "@/pages/user";
import Test from "@/pages/test";
import CreateIndex from "../pages/video";
import LibraryIndex from "@/pages/library";
import LiveIndex from "@/pages/live";
@ -13,6 +14,10 @@ const routes: RouteObject[] = [
path: '/user',
element: <UserAuth/>,
},
{
path: '/test',
element: <Test/>,
},
{
path: '/',
element: <DashboardLayout/>,

View File

@ -12,9 +12,9 @@ export function getList() {
}
export function modifyOrder(ids: Id[]) {
return post('/video/order', {ids})
return post('/room/order', {ids})
}
export function deleteById(ids: Id[]) {
return post('/video/remove', {ids})
export function deleteByIds(ids: Id[]) {
return post('/room/remove', {ids})
}

View File

@ -1,7 +1,7 @@
import {post} from "@/service/request.ts";
export function getList(data: ApiArticleSearchParams & ApiRequestPageParams) {
return post<DataList<ListCrawlerNewsItem>>({url: '/article/search', data})
return post<DataList<ListCrawlerNewsItem>>({url: '/spider/search', data})
}
export function getById(id: Id) {

View File

@ -1412,6 +1412,11 @@ es-errors@^1.3.0:
resolved "https://registry.npmmirror.com/es-errors/-/es-errors-1.3.0.tgz#05f75a25dab98e4fb1dcd5e1472c0546d5057c8f"
integrity sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==
es6-promise@^4.2.8:
version "4.2.8"
resolved "https://registry.npmmirror.com/es6-promise/-/es6-promise-4.2.8.tgz#4eb21594c972bc40553d276e510539143db53e0a"
integrity sha512-HJDGx5daxeIvxdBxvG2cb9g4tEvwIk3i8+nhX0yGrYmZUzbkdg8QbDevheDB8gd0//uPj4c1EQua8Q+MViT0/w==
esbuild@^0.21.3:
version "0.21.5"
resolved "https://registry.npmmirror.com/esbuild/-/esbuild-0.21.5.tgz#9ca301b120922959b766360d8ac830da0d02997d"
@ -1625,6 +1630,14 @@ flatted@^3.2.9:
resolved "https://registry.npmmirror.com/flatted/-/flatted-3.3.1.tgz#21db470729a6734d4997002f439cb308987f567a"
integrity sha512-X8cqMLLie7KsNUDSdzeN8FYK9rEt4Dt67OsG/DNGnYTSDBG4uFAJFBnUeiV+zCVAvwFy56IjM9sH51jVaEhNxw==
flv.js@^1.6.2:
version "1.6.2"
resolved "https://registry.npmmirror.com/flv.js/-/flv.js-1.6.2.tgz#fa3340fe3f7ee01d3977f7876aee66b8436e5922"
integrity sha512-xre4gUbX1MPtgQRKj2pxJENp/RnaHaxYvy3YToVVCrSmAWUu85b9mug6pTXF6zakUjNP2lFWZ1rkSX7gxhB/2A==
dependencies:
es6-promise "^4.2.8"
webworkify-webpack "^2.1.5"
follow-redirects@^1.15.6:
version "1.15.9"
resolved "https://registry.npmmirror.com/follow-redirects/-/follow-redirects-1.15.9.tgz#a604fa10e443bf98ca94228d9eebcc2e8a2c8ee1"
@ -3163,6 +3176,11 @@ vite@^5.2.0:
optionalDependencies:
fsevents "~2.3.3"
webworkify-webpack@^2.1.5:
version "2.1.5"
resolved "https://registry.npmmirror.com/webworkify-webpack/-/webworkify-webpack-2.1.5.tgz#bf4336624c0626cbe85cf1ffde157f7aa90b1d1c"
integrity sha512-2akF8FIyUvbiBBdD+RoHpoTbHMQF2HwjcxfDvgztAX5YwbZNyrtfUMgvfgFVsgDhDPVTlkbb5vyasqDHfIDPQw==
which@^2.0.1:
version "2.0.2"
resolved "https://registry.npmmirror.com/which/-/which-2.0.2.tgz#7c6a8dd0a636a0327e10b59c9286eee93f3f51b1"