mirror of
https://github.com/Tencent/WeKnora.git
synced 2026-10-28 02:53:29 +08:00
fix(preview): refine source citation highlights (#4139)
* fix(preview): refine source citation highlights * fix(preview): use shared radius token for source highlights
This commit is contained in:
@@ -217,18 +217,18 @@ select:-webkit-autofill {
|
||||
--app-accent-purple: #9d7cff;
|
||||
}
|
||||
|
||||
/* 引用原文定位:低饱和薄荷绿,文字轻染色,较大区域用浅底和细描边。 */
|
||||
/* 引用原文定位:纸面荧光笔色,文字与整块区域使用不同强度。 */
|
||||
:root {
|
||||
--app-source-highlight: #3b9b83;
|
||||
--app-source-highlight-bg: color-mix(in srgb, var(--app-source-highlight) 20%, transparent);
|
||||
--app-source-highlight-soft-bg: color-mix(in srgb, var(--app-source-highlight) 8%, transparent);
|
||||
--app-source-highlight-border: color-mix(in srgb, var(--app-source-highlight) 40%, transparent);
|
||||
--app-source-highlight: #d6a124;
|
||||
--app-source-highlight-bg: rgba(250, 204, 75, 0.34);
|
||||
--app-source-highlight-soft-bg: rgba(250, 204, 75, 0.09);
|
||||
--app-source-highlight-border: rgba(183, 130, 22, 0.5);
|
||||
}
|
||||
:root[theme-mode="dark"] {
|
||||
--app-source-highlight: #71c4aa;
|
||||
--app-source-highlight-bg: color-mix(in srgb, var(--app-source-highlight) 24%, transparent);
|
||||
--app-source-highlight-soft-bg: color-mix(in srgb, var(--app-source-highlight) 10%, transparent);
|
||||
--app-source-highlight-border: color-mix(in srgb, var(--app-source-highlight) 48%, transparent);
|
||||
--app-source-highlight: #e6b94f;
|
||||
--app-source-highlight-bg: rgba(230, 185, 79, 0.26);
|
||||
--app-source-highlight-soft-bg: rgba(230, 185, 79, 0.08);
|
||||
--app-source-highlight-border: rgba(230, 185, 79, 0.5);
|
||||
}
|
||||
|
||||
/* 全局旋转动画:spinner / 刷新图标统一用 `animation: wk-spin 0.8s linear infinite` */
|
||||
|
||||
@@ -1686,16 +1686,32 @@ mark.source-locate-mark {
|
||||
background-color: var(--app-source-highlight-bg);
|
||||
color: inherit;
|
||||
border-radius: var(--app-radius-xs);
|
||||
box-decoration-break: clone;
|
||||
-webkit-box-decoration-break: clone;
|
||||
}
|
||||
|
||||
.source-locate-block {
|
||||
background-color: var(--app-source-highlight-soft-bg) !important;
|
||||
outline: 1px solid var(--app-source-highlight-border);
|
||||
outline-offset: 2px;
|
||||
border-radius: var(--app-radius-xs);
|
||||
box-shadow: inset 3px 0 var(--app-source-highlight-border);
|
||||
}
|
||||
|
||||
/* Cell backgrounds can obscure a row's marker. Paint each cell once and
|
||||
keep the accent on the leading edge, without boxing every table row. */
|
||||
tr.source-locate-block {
|
||||
background-color: transparent !important;
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
tr.source-locate-block > td {
|
||||
background-color: var(--app-source-highlight-soft-bg) !important;
|
||||
}
|
||||
|
||||
tr.source-locate-block > td:first-child {
|
||||
box-shadow: inset 3px 0 var(--app-source-highlight-border);
|
||||
}
|
||||
|
||||
img.source-locate-block {
|
||||
outline: 1px solid var(--app-source-highlight-border);
|
||||
outline-offset: 2px;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -10,7 +10,7 @@ type PdfDocument = Awaited<ReturnType<PdfJs['getDocument']>['promise']>
|
||||
type PdfPage = Awaited<ReturnType<PdfDocument['getPage']>>
|
||||
|
||||
/** A highlight drawn on a page, in fractions of the page box. */
|
||||
type Mark = { left: number; top: number; width: number; height: number; kind: 'box' | 'text' | 'page' }
|
||||
type Mark = { left: number; top: number; width: number; height: number; kind: 'box' | 'text' | 'page'; target: number }
|
||||
|
||||
export type PdfLocateResult = { found: boolean; precise: boolean; page?: number; granularity?: string; reason?: string }
|
||||
|
||||
@@ -277,7 +277,7 @@ async function pageText(pageNumber: number): Promise<string> {
|
||||
}
|
||||
|
||||
/** Refine only within the recorded region, or at a verified text occurrence. */
|
||||
async function markTarget(target: PdfTarget, version: number): Promise<number | null> {
|
||||
async function markTarget(target: PdfTarget, version: number, targetIndex: number): Promise<number | null> {
|
||||
const pageNumber = target.page
|
||||
await renderPage(pageNumber)
|
||||
await rendered.get(pageNumber)?.done
|
||||
@@ -305,7 +305,7 @@ async function markTarget(target: PdfTarget, version: number): Promise<number |
|
||||
if (!lines?.length) return null
|
||||
let top = 1
|
||||
for (const r of lines) {
|
||||
const mark: Mark = { kind: 'text', left: (r.left - pageBox.left) / pageBox.width,
|
||||
const mark: Mark = { kind: 'text', target: targetIndex, left: (r.left - pageBox.left) / pageBox.width,
|
||||
top: (r.top - pageBox.top) / pageBox.height, width: r.width / pageBox.width, height: r.height / pageBox.height }
|
||||
addMark(pageNumber, mark)
|
||||
top = Math.min(top, mark.top)
|
||||
@@ -317,6 +317,7 @@ async function applyLocate(request: SourceLocateRequest | null | undefined) {
|
||||
const version = ++locateVersion
|
||||
clearMarks()
|
||||
locatedTargets.value = []
|
||||
selectedTarget.value = 0
|
||||
if (!request || !pdf.value) return
|
||||
try {
|
||||
const result = await resolvePdfSource(request, pageCount.value, pageText, () => version !== locateVersion)
|
||||
@@ -324,18 +325,20 @@ async function applyLocate(request: SourceLocateRequest | null | undefined) {
|
||||
const destinations: Array<{ page: number; top: number }> = []
|
||||
const levels: string[] = []
|
||||
for (const target of result.targets) {
|
||||
let top = await markTarget(target, version)
|
||||
// Use the navigation index: unresolved targets do not get a stop.
|
||||
const targetIndex = destinations.length
|
||||
let top = await markTarget(target, version, targetIndex)
|
||||
if (version !== locateVersion) return
|
||||
if (top !== null) levels.push('text')
|
||||
else if (target.bbox) {
|
||||
const [x0, y0, x1, y1] = target.bbox
|
||||
addMark(target.page, { kind: 'box', left: x0, top: y0, width: x1 - x0, height: y1 - y0 })
|
||||
addMark(target.page, { kind: 'box', target: targetIndex, left: x0, top: y0, width: x1 - x0, height: y1 - y0 })
|
||||
top = y0
|
||||
levels.push('block')
|
||||
} else if (target.granularity === 'page') {
|
||||
top = 0
|
||||
levels.push('page')
|
||||
addMark(target.page, { kind: 'page', left: 0, top: 0, width: 1, height: 1 })
|
||||
addMark(target.page, { kind: 'page', target: targetIndex, left: 0, top: 0, width: 1, height: 1 })
|
||||
}
|
||||
if (top !== null) destinations.push({ page: target.page, top })
|
||||
}
|
||||
@@ -445,7 +448,7 @@ defineExpose({ relocate: () => applyLocate(props.locate) })
|
||||
v-for="(mark, i) in marks[page] || []"
|
||||
:key="i"
|
||||
class="pdf-source-mark"
|
||||
:class="`pdf-source-mark--${mark.kind}`"
|
||||
:class="[`pdf-source-mark--${mark.kind}`, { 'is-active': mark.target === selectedTarget }]"
|
||||
:style="{
|
||||
left: `${mark.left * 100}%`,
|
||||
top: `${mark.top * 100}%`,
|
||||
@@ -511,6 +514,11 @@ defineExpose({ relocate: () => applyLocate(props.locate) })
|
||||
|
||||
.pdf-source-page {
|
||||
position: relative;
|
||||
isolation: isolate;
|
||||
// The PDF canvas stays white even when the surrounding app is dark.
|
||||
--app-source-highlight-bg: rgba(250, 204, 75, 0.34);
|
||||
--app-source-highlight-soft-bg: rgba(250, 204, 75, 0.09);
|
||||
--app-source-highlight-border: rgba(183, 130, 22, 0.5);
|
||||
// Block flow with auto margins keeps zoomed pages scrollable to the left.
|
||||
margin: 0 auto 12px;
|
||||
max-width: none;
|
||||
@@ -533,23 +541,35 @@ defineExpose({ relocate: () => applyLocate(props.locate) })
|
||||
inset: 0;
|
||||
z-index: 2;
|
||||
pointer-events: none;
|
||||
// Blend the whole layer once, so overlapping glyph rectangles do not
|
||||
// darken the printed text independently.
|
||||
mix-blend-mode: multiply;
|
||||
}
|
||||
|
||||
.pdf-source-mark {
|
||||
position: absolute;
|
||||
border-radius: var(--app-radius-xs);
|
||||
background: var(--app-source-highlight-bg);
|
||||
mix-blend-mode: multiply;
|
||||
background: var(--app-source-highlight-soft-bg);
|
||||
|
||||
&--text.is-active {
|
||||
background: var(--app-source-highlight-bg);
|
||||
}
|
||||
|
||||
&--box {
|
||||
background: var(--app-source-highlight-soft-bg);
|
||||
outline: 1px solid var(--app-source-highlight-border);
|
||||
|
||||
&.is-active {
|
||||
box-shadow: inset 3px 0 var(--app-source-highlight-border);
|
||||
}
|
||||
}
|
||||
|
||||
&--page {
|
||||
background: transparent;
|
||||
outline: 1px solid var(--app-source-highlight-border);
|
||||
mix-blend-mode: normal;
|
||||
box-shadow: inset 0 0 0 1px var(--app-source-highlight-border);
|
||||
|
||||
&.is-active {
|
||||
box-shadow: inset 3px 0 var(--app-source-highlight-border), inset 0 0 0 1px var(--app-source-highlight-border);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user