60 lines
2.6 KiB
TypeScript
60 lines
2.6 KiB
TypeScript
import type { LucideIcon } from 'lucide-react'
|
|
import type { ReactElement } from 'react'
|
|
import type { GroupStyle, Inline, SegmentGroup } from './types'
|
|
import { Repeat } from 'lucide-react'
|
|
import { useContext } from 'react'
|
|
import { DisplayNameContext, shownName } from './displayNames'
|
|
import { isCurrent, segmentOccurrence } from './occurrences'
|
|
import { HighlightContext } from './useOccurrences'
|
|
|
|
/**
|
|
* 按词性给每一段上色,颜色在 codeReader.css 的 .cr-role-* 里按主题切换;带源码下标的名字记在 data-offset 上,
|
|
* 跳到定义、高亮各处出现用;是眼下高亮着的名字的一处出现时 data-occurrence 标出底色,是当前那一处(点中的、用键盘跳到的)时
|
|
* 再标 data-current。起了中文名的名字显示中文名(data-name),悬停看原名,定义处本身在后面淡淡写原名;下标照旧,跳到定义、高亮照常。
|
|
* 框起来的一组词画成小框,框里的词同样处理。
|
|
*/
|
|
export function SegmentText({ segments }: { segments: Inline[] }): ReactElement {
|
|
const highlight = useContext(HighlightContext)
|
|
const names = useContext(DisplayNameContext)
|
|
return (
|
|
<>
|
|
{segments.map((piece, index) => {
|
|
if (!('role' in piece))
|
|
return <GroupBox key={index} group={piece} />
|
|
const shown = shownName(piece, names)
|
|
return (
|
|
<span
|
|
key={index}
|
|
className={`cr-role-${piece.role}`}
|
|
data-offset={piece.offset}
|
|
data-occurrence={highlight && segmentOccurrence(piece, highlight.occurrences)}
|
|
data-current={highlight && isCurrent(piece, highlight) ? '' : undefined}
|
|
data-name={shown?.name}
|
|
title={shown && `原名 ${piece.text}`}
|
|
>
|
|
{shown ? shown.name : piece.text}
|
|
{shown?.definition && <span className="cr-original">{piece.text}</span>}
|
|
</span>
|
|
)
|
|
})}
|
|
</>
|
|
)
|
|
}
|
|
|
|
/** 框的样式 → 框头图标;颜色在 codeReader.css 的 .cr-group[data-style] 里。 */
|
|
const GROUP_ICONS: Record<GroupStyle, LucideIcon> = { iteration: Repeat }
|
|
|
|
/** 框起来的一组词:嵌在前后文字里的小框,放不下时整个挪到下一行,比一行还长时在框里换行。 */
|
|
function GroupBox({ group }: { group: SegmentGroup }): ReactElement {
|
|
const Icon = GROUP_ICONS[group.style]
|
|
return (
|
|
<span className="cr-group" data-style={group.style}>
|
|
<span className="cr-group-head">
|
|
<Icon size={12} strokeWidth={2} aria-hidden />
|
|
{group.label}
|
|
</span>
|
|
<SegmentText segments={group.segments} />
|
|
</span>
|
|
)
|
|
}
|