You've already forked bilibili-subtitle
添加字幕搜索功能
This commit is contained in:
@@ -7,14 +7,30 @@ import {
|
||||
setFoldAll,
|
||||
setNeedScroll,
|
||||
setPage,
|
||||
setSearchText,
|
||||
setSegmentFold
|
||||
} from '../redux/envReducer'
|
||||
import {useAppDispatch, useAppSelector} from '../hooks/redux'
|
||||
import {AiOutlineAim, FaRegArrowAltCircleDown, IoWarning, MdExpand, RiFileCopy2Line, RiTranslate} from 'react-icons/all'
|
||||
import {
|
||||
AiOutlineAim,
|
||||
AiOutlineCloseCircle,
|
||||
FaRegArrowAltCircleDown,
|
||||
IoWarning,
|
||||
MdExpand,
|
||||
RiFileCopy2Line,
|
||||
RiTranslate
|
||||
} from 'react-icons/all'
|
||||
import classNames from 'classnames'
|
||||
import toast from 'react-hot-toast'
|
||||
import SegmentCard from './SegmentCard'
|
||||
import {HEADER_HEIGHT, PAGE_SETTINGS, SUMMARIZE_ALL_THRESHOLD, SUMMARIZE_TYPES, TITLE_HEIGHT} from '../const'
|
||||
import {
|
||||
HEADER_HEIGHT,
|
||||
PAGE_SETTINGS,
|
||||
SEARCH_BAR_HEIGHT,
|
||||
SUMMARIZE_ALL_THRESHOLD,
|
||||
SUMMARIZE_TYPES,
|
||||
TITLE_HEIGHT
|
||||
} from '../const'
|
||||
import {FaClipboardList} from 'react-icons/fa'
|
||||
import useTranslate from '../hooks/useTranslate'
|
||||
import {getSummarize} from '../util/biz_util'
|
||||
@@ -41,6 +57,7 @@ const Body = () => {
|
||||
const totalHeight = useAppSelector(state => state.env.totalHeight)
|
||||
const curSummaryType = useAppSelector(state => state.env.tempData.curSummaryType)
|
||||
const title = useAppSelector(state => state.env.title)
|
||||
const searchText = useAppSelector(state => state.env.searchText)
|
||||
|
||||
const normalCallback = useCallback(() => {
|
||||
dispatch(setCompact(false))
|
||||
@@ -118,6 +135,15 @@ const Body = () => {
|
||||
}
|
||||
}, [curSummaryType, segments, title])
|
||||
|
||||
const onSearchTextChange = useCallback((e: any) => {
|
||||
const searchText = e.target.value
|
||||
dispatch(setSearchText(searchText))
|
||||
}, [dispatch])
|
||||
|
||||
const onClearSearchText = useCallback(() => {
|
||||
dispatch(setSearchText(''))
|
||||
}, [dispatch])
|
||||
|
||||
// 自动滚动
|
||||
useEffect(() => {
|
||||
if (checkAutoScroll && curOffsetTop && autoScroll && !needScroll) {
|
||||
@@ -132,6 +158,7 @@ const Body = () => {
|
||||
}, [autoScroll, checkAutoScroll, curOffsetTop, dispatch, floatKeyPointsSegIdx, needScroll, totalHeight])
|
||||
|
||||
return <div className='relative'>
|
||||
{/* title */}
|
||||
<div className='absolute top-1 left-6 flex-center gap-1'>
|
||||
<AiOutlineAim className='cursor-pointer' onClick={posCallback} title='滚动到视频位置'/>
|
||||
{segments != null && segments.length > 0 &&
|
||||
@@ -159,19 +186,31 @@ const Body = () => {
|
||||
<IoWarning className='text-warning'/>
|
||||
</div>}
|
||||
</div>
|
||||
|
||||
{/* search */}
|
||||
{envData.searchEnabled && <div className='px-2 py-1 flex flex-col relative'>
|
||||
<input type='text' className='input input-xs bg-base-200' placeholder='搜索字幕内容' value={searchText} onChange={onSearchTextChange}/>
|
||||
{searchText && <button className='absolute top-1 right-2 btn btn-ghost btn-xs btn-circle text-base-content/75' onClick={onClearSearchText}><AiOutlineCloseCircle/></button>}
|
||||
</div>}
|
||||
|
||||
{/* auto scroll btn */}
|
||||
{!autoScroll && <div
|
||||
className='absolute z-[999] top-[96px] right-6 tooltip tooltip-left cursor-pointer rounded-full bg-primary/25 hover:bg-primary/75 text-primary-content p-1.5 text-xl'
|
||||
data-tip='开启自动滚动'
|
||||
onClick={onEnableAutoScroll}>
|
||||
<FaRegArrowAltCircleDown className={autoScroll ? 'text-accent' : ''}/>
|
||||
</div>}
|
||||
|
||||
{/* body */}
|
||||
<div ref={bodyRef} onWheel={onWheel}
|
||||
className={classNames('flex flex-col gap-1.5 overflow-y-auto select-text scroll-smooth', floatKeyPointsSegIdx != null && 'pb-[100px]')}
|
||||
style={{
|
||||
height: `${totalHeight - HEADER_HEIGHT - TITLE_HEIGHT}px`
|
||||
height: `${totalHeight - HEADER_HEIGHT - TITLE_HEIGHT - (envData.searchEnabled?SEARCH_BAR_HEIGHT:0)}px`
|
||||
}}
|
||||
>
|
||||
{segments?.map((segment, segmentIdx) => <SegmentCard key={segment.startIdx} segment={segment} segmentIdx={segmentIdx} bodyRef={bodyRef}/>)}
|
||||
|
||||
{/* tip */}
|
||||
<div className='flex flex-col items-center text-center pt-1 pb-2'>
|
||||
<div className='font-semibold text-accent'>💡<span className='underline underline-offset-4'>提示</span>💡</div>
|
||||
<div className='text-sm desc px-2'>可以尝试将<span className='text-amber-600 font-semibold'>概览</span>生成的内容粘贴到<span className='text-secondary/75 font-semibold'>视频评论</span>里,发布后看看有什么效果🥳</div>
|
||||
|
@@ -4,11 +4,7 @@ import {getDisplay, getTransText} from '../util/biz_util'
|
||||
import classNames from 'classnames'
|
||||
|
||||
const CompactSegmentItem = (props: {
|
||||
item: {
|
||||
from: number
|
||||
to: number
|
||||
content: string
|
||||
}
|
||||
item: TranscriptItem
|
||||
idx: number
|
||||
isIn: boolean
|
||||
last: boolean
|
||||
@@ -26,7 +22,7 @@ const CompactSegmentItem = (props: {
|
||||
<span className={'pl-1 pr-0.5 py-0.5 cursor-pointer rounded-sm hover:bg-base-200'} onClick={moveCallback}>
|
||||
<text className={classNames('font-medium', isIn ? 'text-primary underline' : '')}>{display.main}</text>
|
||||
{display.sub && <text className='desc'>({display.sub})</text>}</span>
|
||||
<span>{!last && ', '}</span>
|
||||
<span>{!last && ' '}</span>
|
||||
</div>
|
||||
}
|
||||
|
||||
|
@@ -5,11 +5,7 @@ import {getDisplay, getTransText} from '../util/biz_util'
|
||||
import classNames from 'classnames'
|
||||
|
||||
const NormalSegmentItem = (props: {
|
||||
item: {
|
||||
from: number
|
||||
to: number
|
||||
content: string
|
||||
}
|
||||
item: TranscriptItem
|
||||
idx: number
|
||||
isIn: boolean
|
||||
moveCallback: (event: any) => void
|
||||
|
@@ -1,4 +1,4 @@
|
||||
import React, {useCallback, useEffect, useRef} from 'react'
|
||||
import React, {useCallback, useEffect, useMemo, useRef} from 'react'
|
||||
import {useAppDispatch, useAppSelector} from '../hooks/redux'
|
||||
import useSubtitle from '../hooks/useSubtitle'
|
||||
import {setCheckAutoScroll, setCurOffsetTop, setNeedScroll} from '../redux/envReducer'
|
||||
@@ -7,21 +7,27 @@ import CompactSegmentItem from './CompactSegmentItem'
|
||||
|
||||
const SegmentItem = (props: {
|
||||
bodyRef: any
|
||||
item: {
|
||||
from: number
|
||||
to: number
|
||||
content: string
|
||||
}
|
||||
item: TranscriptItem
|
||||
idx: number
|
||||
isIn: boolean
|
||||
needScroll?: boolean
|
||||
last: boolean
|
||||
}) => {
|
||||
const dispatch = useAppDispatch()
|
||||
const {bodyRef, item, idx, isIn, needScroll, last} = props
|
||||
const dispatch = useAppDispatch()
|
||||
const ref = useRef<any>()
|
||||
const {move} = useSubtitle()
|
||||
|
||||
const compact = useAppSelector(state => state.env.compact)
|
||||
const searchText = useAppSelector(state => state.env.searchText)
|
||||
const searchResult = useAppSelector(state => state.env.searchResult)
|
||||
const display = useMemo(() => {
|
||||
if (searchText) {
|
||||
return searchResult.has(item.idx) ? 'inline' : 'none'
|
||||
} else {
|
||||
return 'inline'
|
||||
}
|
||||
}, [item.idx, searchResult, searchText])
|
||||
|
||||
const moveCallback = useCallback((event: any) => {
|
||||
if (event.altKey) { // 复制
|
||||
@@ -47,7 +53,9 @@ const SegmentItem = (props: {
|
||||
}
|
||||
}, [dispatch, isIn])
|
||||
|
||||
return <span ref={ref}>
|
||||
return <span ref={ref} style={{
|
||||
display
|
||||
}}>
|
||||
{compact
|
||||
? <CompactSegmentItem
|
||||
item={item}
|
||||
|
@@ -58,6 +58,8 @@ const Settings = () => {
|
||||
// const {value: autoScrollValue, onChange: setAutoScrollValue} = useEventChecked(envData.autoScroll)
|
||||
const {value: translateEnableValue, onChange: setTranslateEnableValue} = useEventChecked(envData.translateEnable)
|
||||
const {value: summarizeEnableValue, onChange: setSummarizeEnableValue} = useEventChecked(envData.summarizeEnable)
|
||||
const {value: searchEnabledValue, onChange: setSearchEnabledValue} = useEventChecked(envData.searchEnabled)
|
||||
const {value: cnSearchEnabledValue, onChange: setCnSearchEnabledValue} = useEventChecked(envData.cnSearchEnabled)
|
||||
const {value: summarizeFloatValue, onChange: setSummarizeFloatValue} = useEventChecked(envData.summarizeFloat)
|
||||
const [apiKeyValue, { onChange: onChangeApiKeyValue }] = useEventTarget({initialValue: envData.apiKey??''})
|
||||
const [serverUrlValue, setServerUrlValue] = useState(envData.serverUrl)
|
||||
@@ -112,10 +114,12 @@ const Settings = () => {
|
||||
fetchAmount: fetchAmountValue,
|
||||
fontSize: fontSizeValue,
|
||||
prompts: promptsValue,
|
||||
searchEnabled: searchEnabledValue,
|
||||
cnSearchEnabled: cnSearchEnabledValue,
|
||||
}))
|
||||
dispatch(setPage(PAGE_MAIN))
|
||||
toast.success('保存成功')
|
||||
}, [modelValue, promptsValue, fontSizeValue, apiKeyValue, autoExpandValue, dispatch, fetchAmountValue, hideOnDisableAutoTranslateValue, languageValue, serverUrlValue, summarizeEnableValue, summarizeFloatValue, summarizeLanguageValue, themeValue, transDisplayValue, translateEnableValue, wordsValue])
|
||||
}, [dispatch, autoExpandValue, apiKeyValue, serverUrlValue, modelValue, translateEnableValue, languageValue, hideOnDisableAutoTranslateValue, themeValue, transDisplayValue, summarizeEnableValue, summarizeFloatValue, summarizeLanguageValue, wordsValue, fetchAmountValue, fontSizeValue, promptsValue, searchEnabledValue, cnSearchEnabledValue])
|
||||
|
||||
const onCancel = useCallback(() => {
|
||||
dispatch(setPage(PAGE_MAIN))
|
||||
@@ -300,6 +304,18 @@ const Settings = () => {
|
||||
</div>
|
||||
</FormItem>
|
||||
</Section>
|
||||
<Section title={<div className='flex items-center'>
|
||||
搜索配置
|
||||
</div>}>
|
||||
<FormItem title='启用搜索' htmlFor='searchEnabled' tip='是否启用字幕搜索功能'>
|
||||
<input id='searchEnabled' type='checkbox' className='toggle toggle-primary' checked={searchEnabledValue}
|
||||
onChange={setSearchEnabledValue}/>
|
||||
</FormItem>
|
||||
<FormItem title='拼音搜索' htmlFor='cnSearchEnabled' tip='是否启用中文拼音搜索'>
|
||||
<input id='cnSearchEnabled' type='checkbox' className='toggle toggle-primary' checked={cnSearchEnabledValue}
|
||||
onChange={setCnSearchEnabledValue}/>
|
||||
</FormItem>
|
||||
</Section>
|
||||
<div className='flex justify-center gap-5'>
|
||||
<button className='btn btn-primary btn-sm' onClick={onSave}>保存</button>
|
||||
<button className='btn btn-sm' onClick={onCancel}>取消</button>
|
||||
|
Reference in New Issue
Block a user