You've already forked bilibili-subtitle
fix
This commit is contained in:
305
src/components/MoreBtn.tsx
Normal file
305
src/components/MoreBtn.tsx
Normal file
@@ -0,0 +1,305 @@
|
||||
import React, {MouseEvent, useCallback, useContext, useRef, useState} from 'react'
|
||||
import {useClickAway} from 'ahooks'
|
||||
import {
|
||||
AiFillWechat,
|
||||
BsFillChatDotsFill,
|
||||
FiMoreVertical,
|
||||
ImDownload3,
|
||||
IoMdSettings,
|
||||
RiFileCopy2Line
|
||||
} from 'react-icons/all'
|
||||
import Popover from '../components/Popover'
|
||||
import {Placement} from '@popperjs/core/lib/enums'
|
||||
import {useAppDispatch, useAppSelector} from '../hooks/redux'
|
||||
import {setEnvData, setTempData} from '../redux/envReducer'
|
||||
import {EventBusContext} from '../Router'
|
||||
import {EVENT_EXPAND, MESSAGE_TO_INJECT_DOWNLOAD_AUDIO} from '../consts/const'
|
||||
import {formatSrtTime, formatTime, formatVttTime} from '../utils/util'
|
||||
import {downloadText, openUrl} from '@kky002/kky-util'
|
||||
import toast from 'react-hot-toast'
|
||||
import {getSummarize} from '../utils/biz_util'
|
||||
import useMessage from '../messaging/useMessage'
|
||||
interface Props {
|
||||
placement: Placement
|
||||
}
|
||||
|
||||
const DownloadTypes = [
|
||||
{
|
||||
type: 'text',
|
||||
name: '列表',
|
||||
},
|
||||
{
|
||||
type: 'textWithTime',
|
||||
name: '列表(带时间)',
|
||||
},
|
||||
{
|
||||
type: 'article',
|
||||
name: '文章',
|
||||
},
|
||||
{
|
||||
type: 'srt',
|
||||
name: 'srt',
|
||||
},
|
||||
{
|
||||
type: 'vtt',
|
||||
name: 'vtt',
|
||||
},
|
||||
{
|
||||
type: 'json',
|
||||
name: '原始json',
|
||||
},
|
||||
{
|
||||
type: 'summarize',
|
||||
name: '总结',
|
||||
},
|
||||
]
|
||||
|
||||
const MoreBtn = (props: Props) => {
|
||||
const {placement} = props
|
||||
const dispatch = useAppDispatch()
|
||||
|
||||
const moreRef = useRef(null)
|
||||
const data = useAppSelector(state => state.env.data)
|
||||
const envReady = useAppSelector(state => state.env.envReady)
|
||||
const envData = useAppSelector(state => state.env.envData)
|
||||
const downloadType = useAppSelector(state => state.env.tempData.downloadType)
|
||||
const [moreVisible, setMoreVisible] = useState(false)
|
||||
const eventBus = useContext(EventBusContext)
|
||||
const segments = useAppSelector(state => state.env.segments)
|
||||
const url = useAppSelector(state => state.env.url)
|
||||
const title = useAppSelector(state => state.env.title)
|
||||
const curSummaryType = useAppSelector(state => state.env.tempData.curSummaryType)
|
||||
|
||||
const {sendInject} = useMessage()
|
||||
|
||||
const downloadCallback = useCallback((download: boolean) => {
|
||||
if (data == null) {
|
||||
return
|
||||
}
|
||||
|
||||
let fileName = title
|
||||
let s, suffix
|
||||
if (!downloadType || downloadType === 'text') {
|
||||
s = `${title??'无标题'}\n${url??'无链接'}\n\n`
|
||||
for (const item of data.body) {
|
||||
s += item.content + '\n'
|
||||
}
|
||||
suffix = 'txt'
|
||||
} else if (downloadType === 'textWithTime') {
|
||||
s = `${title??'无标题'}\n${url??'无链接'}\n\n`
|
||||
for (const item of data.body) {
|
||||
s += formatTime(item.from) + ' ' + item.content + '\n'
|
||||
}
|
||||
suffix = 'txt'
|
||||
} else if (downloadType === 'article') {
|
||||
s = `${title??'无标题'}\n${url??'无链接'}\n\n`
|
||||
for (const item of data.body) {
|
||||
s += item.content + ', '
|
||||
}
|
||||
s = s.substring(0, s.length - 1) // remove last ','
|
||||
suffix = 'txt'
|
||||
} else if (downloadType === 'srt') {
|
||||
/**
|
||||
* 1
|
||||
* 00:05:00,400 --> 00:05:15,300
|
||||
* This is an example of
|
||||
* a subtitle.
|
||||
*
|
||||
* 2
|
||||
* 00:05:16,400 --> 00:05:25,300
|
||||
* This is an example of
|
||||
* a subtitle - 2nd subtitle.
|
||||
*/
|
||||
s = ''
|
||||
for (const item of data.body) {
|
||||
const ss = (item.idx + 1) + '\n' + formatSrtTime(item.from) + ' --> ' + formatSrtTime(item.to) + '\n' + ((item.content?.trim()) ?? '') + '\n\n'
|
||||
s += ss
|
||||
}
|
||||
s = s.substring(0, s.length - 1)// remove last '\n'
|
||||
suffix = 'srt'
|
||||
} else if (downloadType === 'vtt') {
|
||||
/**
|
||||
* WEBVTT title
|
||||
*
|
||||
* 1
|
||||
* 00:05:00.400 --> 00:05:15.300
|
||||
* This is an example of
|
||||
* a subtitle.
|
||||
*
|
||||
* 2
|
||||
* 00:05:16.400 --> 00:05:25.300
|
||||
* This is an example of
|
||||
* a subtitle - 2nd subtitle.
|
||||
*/
|
||||
s = `WEBVTT ${title ?? ''}\n\n`
|
||||
for (const item of data.body) {
|
||||
const ss = (item.idx + 1) + '\n' + formatVttTime(item.from) + ' --> ' + formatVttTime(item.to) + '\n' + ((item.content?.trim()) ?? '') + '\n\n'
|
||||
s += ss
|
||||
}
|
||||
s = s.substring(0, s.length - 1)// remove last '\n'
|
||||
suffix = 'vtt'
|
||||
} else if (downloadType === 'json') {
|
||||
s = JSON.stringify(data)
|
||||
suffix = 'json'
|
||||
} else if (downloadType === 'summarize') {
|
||||
s = `${title??'无标题'}\n${url??'无链接'}\n\n`
|
||||
const [success, content] = getSummarize(title, segments, curSummaryType)
|
||||
if (!success) return
|
||||
s += content
|
||||
fileName += ' - 总结'
|
||||
suffix = 'txt'
|
||||
} else {
|
||||
return
|
||||
}
|
||||
if (download) {
|
||||
downloadText(s, fileName+'.'+suffix)
|
||||
} else {
|
||||
navigator.clipboard.writeText(s).then(() => {
|
||||
toast.success('复制成功')
|
||||
}).catch(console.error)
|
||||
}
|
||||
setMoreVisible(false)
|
||||
}, [curSummaryType, data, downloadType, segments, title, url])
|
||||
|
||||
const downloadAudioCallback = useCallback(() => {
|
||||
sendInject(MESSAGE_TO_INJECT_DOWNLOAD_AUDIO, {})
|
||||
}, [])
|
||||
|
||||
const selectCallback = useCallback((e: any) => {
|
||||
dispatch(setTempData({
|
||||
downloadType: e.target.value,
|
||||
}))
|
||||
}, [dispatch])
|
||||
|
||||
const preventCallback = useCallback((e: any) => {
|
||||
e.stopPropagation()
|
||||
}, [])
|
||||
|
||||
const moreCallback = useCallback((e: MouseEvent) => {
|
||||
e.stopPropagation()
|
||||
if (!envData.flagDot) {
|
||||
dispatch(setEnvData({
|
||||
...envData,
|
||||
flagDot: true,
|
||||
}))
|
||||
}
|
||||
setMoreVisible(!moreVisible)
|
||||
// 显示菜单时自动展开,防止菜单显示不全
|
||||
if (!moreVisible) {
|
||||
eventBus.emit({
|
||||
type: EVENT_EXPAND
|
||||
})
|
||||
}
|
||||
}, [dispatch, envData, eventBus, moreVisible])
|
||||
useClickAway(() => {
|
||||
setMoreVisible(false)
|
||||
}, moreRef)
|
||||
|
||||
return <>
|
||||
<div ref={moreRef} onClick={moreCallback}>
|
||||
<div className='indicator flex items-center'>
|
||||
{envReady && !envData.flagDot && <span className="indicator-item bg-secondary w-1.5 h-1.5 rounded-full"></span>}
|
||||
<FiMoreVertical className='desc transform ease-in duration-300 hover:text-primary' title='更多'/>
|
||||
</div>
|
||||
</div>
|
||||
{moreVisible &&
|
||||
<Popover refElement={moreRef.current} className='bg-neutral text-neutral-content py-1 z-[1000]' options={{
|
||||
placement
|
||||
}}>
|
||||
<ul className='menu menu-compact'>
|
||||
<li className='hover:bg-accent'>
|
||||
<a className='flex items-center' onClick={(e) => {
|
||||
e.preventDefault()
|
||||
e.stopPropagation()
|
||||
downloadCallback(false)
|
||||
}}>
|
||||
<RiFileCopy2Line className='w-[20px] h-[20px] text-primary/75 bg-white rounded-sm p-0.5'/>
|
||||
复制
|
||||
<select className='select select-ghost select-xs' value={downloadType} onChange={selectCallback}
|
||||
onClick={preventCallback}>
|
||||
{DownloadTypes?.map((item: any) => <option key={item.type} value={item.type}>{item.name}</option>)}
|
||||
</select>
|
||||
</a>
|
||||
</li>
|
||||
<li className='hover:bg-accent'>
|
||||
<a className='flex items-center' onClick={(e) => {
|
||||
e.preventDefault()
|
||||
e.stopPropagation()
|
||||
downloadCallback(true)
|
||||
}}>
|
||||
<ImDownload3 className='w-[20px] h-[20px] text-primary/75 bg-white rounded-sm p-0.5'/>
|
||||
下载
|
||||
<select className='select select-ghost select-xs' value={downloadType} onChange={selectCallback}
|
||||
onClick={preventCallback}>
|
||||
{DownloadTypes?.map((item: any) => <option key={item.type} value={item.type}>{item.name}</option>)}
|
||||
</select>
|
||||
</a>
|
||||
</li>
|
||||
<li className='hover:bg-accent'>
|
||||
<a className='flex items-center' onClick={(e) => {
|
||||
e.preventDefault()
|
||||
e.stopPropagation()
|
||||
downloadAudioCallback()
|
||||
}}>
|
||||
<ImDownload3 className='w-[20px] h-[20px] text-primary/75 bg-white rounded-sm p-0.5'/>
|
||||
下载音频(m4s)
|
||||
</a>
|
||||
</li>
|
||||
<li className='hover:bg-accent'>
|
||||
<a className='flex items-center' onClick={(e) => {
|
||||
e.preventDefault()
|
||||
e.stopPropagation()
|
||||
openUrl('https://jq.qq.com/?_wv=1027&k=RJyFABPF')
|
||||
}}>
|
||||
<BsFillChatDotsFill className='w-[20px] h-[20px] text-primary/75 bg-white rounded-sm p-0.5'/>
|
||||
QQ交流群(194536885)
|
||||
</a>
|
||||
</li>
|
||||
<li className='hover:bg-accent'>
|
||||
<a className='flex items-center' onClick={(e) => {
|
||||
e.preventDefault()
|
||||
e.stopPropagation()
|
||||
openUrl('https://static.ssstab.com/images/indiekky_public.png')
|
||||
}}>
|
||||
<AiFillWechat className='w-[20px] h-[20px] text-primary/75 bg-white rounded-sm p-0.5'/>
|
||||
微信公众号(IndieKKY)
|
||||
</a>
|
||||
</li>
|
||||
{/* <li className='hover:bg-accent'> */}
|
||||
{/* <a className='flex items-center' onClick={(e) => { */}
|
||||
{/* e.preventDefault() */}
|
||||
{/* e.stopPropagation() */}
|
||||
{/* openUrl('https://bibigpt.co/r/bilibili') */}
|
||||
{/* }}> */}
|
||||
{/* <img alt='BibiGPT' src='/bibigpt.png' className='w-[20px] h-[20px] bg-white rounded-sm p-0.5'/> */}
|
||||
{/* BibiGPT */}
|
||||
{/* </a> */}
|
||||
{/* </li> */}
|
||||
{/* <li className='hover:bg-accent'> */}
|
||||
{/* <a className='flex items-center' onClick={(e) => { */}
|
||||
{/* e.preventDefault() */}
|
||||
{/* e.stopPropagation() */}
|
||||
{/* openUrl('https://chromewebstore.google.com/detail/fiaeclpicddpifeflpmlgmbjgaedladf') */}
|
||||
{/* }}> */}
|
||||
{/* <img alt='youtube subtitle' src='/youtube-caption.png' */}
|
||||
{/* className='w-[20px] h-[20px] bg-white rounded-sm p-0.5'/> */}
|
||||
{/* Youtube Caption */}
|
||||
{/* </a> */}
|
||||
{/* </li> */}
|
||||
<li className='hover:bg-accent'>
|
||||
<a className='flex items-center' onClick={(e) => {
|
||||
chrome.runtime.openOptionsPage()
|
||||
setMoreVisible(false)
|
||||
e.preventDefault()
|
||||
e.stopPropagation()
|
||||
}}>
|
||||
<IoMdSettings className='w-[20px] h-[20px] text-primary/75 bg-white rounded-sm p-0.5'/>
|
||||
选项
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</Popover>}
|
||||
</>
|
||||
}
|
||||
|
||||
export default MoreBtn
|
Reference in New Issue
Block a user