groups typesense
This commit is contained in:
parent
f2521b9ff3
commit
efd752e410
|
|
@ -47,8 +47,38 @@ const title = computed(() =>
|
|||
origin.value || (props.document?.id as string) || 'Entrelínea'
|
||||
)
|
||||
|
||||
function formatEntrelineaText(html: string): string {
|
||||
if (!html) return ''
|
||||
|
||||
const sPrefix = "font-family:'Times New Roman',serif;font-style:italic;color:#c00000"
|
||||
const sBracket = "font-family:'Times New Roman',serif;font-style:italic;color:#c00000"
|
||||
const sInner = "font-family:'Times New Roman',serif;font-style:italic;color:#c00000;text-decoration:underline"
|
||||
|
||||
const wrapLine = (content: string, isFirst: boolean): string =>
|
||||
(isFirst ? `<span style="${sPrefix}">[WSS] </span>` : '') +
|
||||
`<span style="${sBracket}">«</span>` +
|
||||
`<span style="${sInner}">${content}</span>` +
|
||||
`<span style="${sBracket}">»</span>`
|
||||
|
||||
if (/<p[\s>]/i.test(html)) {
|
||||
const lines = html
|
||||
.split(/<p(?:[^>]*)>/i)
|
||||
.map(part => part.replace(/<\/p>/gi, '').trim())
|
||||
.filter(Boolean)
|
||||
if (lines.length) return lines.map((l, i) => wrapLine(l, i === 0)).join('<br>')
|
||||
return html
|
||||
}
|
||||
|
||||
if (/<br/i.test(html)) {
|
||||
const parts = html.split(/<br\s*\/?>/i).map(p => p.trim()).filter(Boolean)
|
||||
if (parts.length) return parts.map((l, i) => wrapLine(l, i === 0)).join('<br>')
|
||||
}
|
||||
|
||||
return wrapLine(html.trim(), true)
|
||||
}
|
||||
|
||||
const bodyHtml = computed<string>(() =>
|
||||
props.highlightedText || props.document?.text || ''
|
||||
formatEntrelineaText(props.highlightedText || props.document?.text || '')
|
||||
)
|
||||
|
||||
/* -------------------------------------------------------------------------- */
|
||||
|
|
|
|||
|
|
@ -79,9 +79,20 @@ export interface TypesenseParagraphHit {
|
|||
text_match?: number
|
||||
}
|
||||
|
||||
interface TypesenseGroupedHit {
|
||||
group_key: string[]
|
||||
hits: TypesenseParagraphHit[]
|
||||
}
|
||||
|
||||
interface TypesenseSearchResponse {
|
||||
found: number
|
||||
hits?: TypesenseParagraphHit[]
|
||||
grouped_hits?: TypesenseGroupedHit[]
|
||||
}
|
||||
|
||||
interface SearchGroup {
|
||||
docId: string
|
||||
firstHit: TypesenseParagraphHit
|
||||
allHits: TypesenseParagraphHit[]
|
||||
}
|
||||
|
||||
interface BrowseItem {
|
||||
|
|
@ -97,31 +108,18 @@ interface DisplayGroup {
|
|||
|
||||
// ---- State ----------------------------------------------------------------
|
||||
|
||||
// Modo búsqueda (con query): hits de párrafos agrupados por documento
|
||||
const hits = ref<TypesenseParagraphHit[]>([])
|
||||
// Modo búsqueda (con query): grupos devueltos por Typesense
|
||||
const groupedHits = ref<SearchGroup[]>([])
|
||||
const total = ref(0)
|
||||
const currentPage = ref(1)
|
||||
|
||||
const hasMore = computed(() =>
|
||||
settings.paginationType === 'infinite_scroll' ? hits.value.length < total.value : false
|
||||
settings.paginationType === 'infinite_scroll' ? groupedHits.value.length < total.value : false
|
||||
)
|
||||
|
||||
// Progressive display (sólo en scroll infinito)
|
||||
const visibleGroupCount = ref(10)
|
||||
|
||||
const groupedHits = computed(() => {
|
||||
const map = new Map<string, TypesenseParagraphHit[]>()
|
||||
for (const hit of hits.value) {
|
||||
const id = hit.document.document_id
|
||||
if (!map.has(id)) map.set(id, [])
|
||||
map.get(id)!.push(hit)
|
||||
}
|
||||
return [...map.entries()].map(([docId, docHits]) => ({
|
||||
docId,
|
||||
firstHit: docHits[0]!
|
||||
}))
|
||||
})
|
||||
|
||||
const visibleGroups = computed(() =>
|
||||
settings.paginationType === 'infinite_scroll'
|
||||
? groupedHits.value.slice(0, visibleGroupCount.value)
|
||||
|
|
@ -243,9 +241,12 @@ async function runSearch(q: string, page = 1, append = false) {
|
|||
page: typePage,
|
||||
highlightFullFields: QUERY_BY,
|
||||
highlightFields: QUERY_BY,
|
||||
highlightFullFields: QUERY_BY,
|
||||
highlightFields: QUERY_BY,
|
||||
highlightStartTag: '<mark class="search-match">',
|
||||
highlightEndTag: '</mark>',
|
||||
snippetThreshold: 100
|
||||
highlightAffixNumTokens: 30,
|
||||
groupBy: 'document_id'
|
||||
}]
|
||||
}
|
||||
})
|
||||
|
|
@ -253,16 +254,21 @@ async function runSearch(q: string, page = 1, append = false) {
|
|||
if (seq !== searchSeq) return
|
||||
|
||||
const res = (multi?.results?.[0] ?? {}) as TypesenseSearchResponse
|
||||
const newHits = res?.hits ?? []
|
||||
const rawGroups = res?.grouped_hits ?? []
|
||||
const newGroups: SearchGroup[] = rawGroups.map(g => ({
|
||||
docId: g.group_key[0]!,
|
||||
firstHit: g.hits[0]!,
|
||||
allHits: g.hits
|
||||
}))
|
||||
|
||||
if (!append) docCache.value = {}
|
||||
const newDocIds = [...new Set(newHits.map(h => h.document.document_id).filter(Boolean))]
|
||||
const newDocIds = newGroups.map(g => g.docId).filter(Boolean)
|
||||
await fetchDocumentMeta(newDocIds)
|
||||
|
||||
if (seq !== searchSeq) return
|
||||
|
||||
hits.value = append ? hits.value.concat(newHits) : newHits
|
||||
total.value = res?.found ?? hits.value.length
|
||||
groupedHits.value = append ? groupedHits.value.concat(newGroups) : newGroups
|
||||
total.value = res?.found ?? groupedHits.value.length
|
||||
currentPage.value = typePage
|
||||
if (!append) activePage.value = page
|
||||
} catch (err: unknown) {
|
||||
|
|
@ -270,7 +276,7 @@ async function runSearch(q: string, page = 1, append = false) {
|
|||
console.error('Typesense error', err)
|
||||
errorMsg.value = (err as Error)?.message || 'Error al buscar.'
|
||||
if (!append) {
|
||||
hits.value = []
|
||||
groupedHits.value = []
|
||||
total.value = 0
|
||||
}
|
||||
} finally {
|
||||
|
|
@ -360,7 +366,7 @@ function goToPage(p: number) {
|
|||
browseItems.value = []
|
||||
runBrowse(p, false)
|
||||
} else {
|
||||
hits.value = []
|
||||
groupedHits.value = []
|
||||
runSearch(query.value, p, false)
|
||||
}
|
||||
}
|
||||
|
|
@ -399,7 +405,7 @@ onBeforeUnmount(() => { if (timeoutId) clearTimeout(timeoutId) })
|
|||
watch(debouncedQuery, (q) => {
|
||||
activePage.value = 1
|
||||
if (!q.trim()) {
|
||||
hits.value = []
|
||||
groupedHits.value = []
|
||||
total.value = 0
|
||||
currentPage.value = 1
|
||||
visibleGroupCount.value = 10
|
||||
|
|
@ -411,7 +417,7 @@ watch(debouncedQuery, (q) => {
|
|||
browseItems.value = []
|
||||
browseTotal.value = 0
|
||||
browsePage.value = 1
|
||||
hits.value = []
|
||||
groupedHits.value = []
|
||||
total.value = 0
|
||||
currentPage.value = 1
|
||||
visibleGroupCount.value = 10
|
||||
|
|
@ -498,9 +504,8 @@ async function fetchDocumentParagraphs(docId: string) {
|
|||
async function selectGroup(group: DisplayGroup) {
|
||||
selectedDocId.value = group.docId
|
||||
selectedHit.value = group.firstHit
|
||||
selectedMatchingHits.value = group.firstHit
|
||||
? hits.value.filter(h => h.document.document_id === group.docId)
|
||||
: []
|
||||
const searchGroup = groupedHits.value.find(g => g.docId === group.docId)
|
||||
selectedMatchingHits.value = searchGroup?.allHits ?? []
|
||||
fetchFullDocument(group.docId)
|
||||
fetchDocumentParagraphs(group.docId)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -79,9 +79,20 @@ export interface TypesenseParagraphHit {
|
|||
text_match?: number
|
||||
}
|
||||
|
||||
interface TypesenseGroupedHit {
|
||||
groupKey: string[]
|
||||
hits: TypesenseParagraphHit[]
|
||||
}
|
||||
|
||||
interface TypesenseSearchResponse {
|
||||
found: number
|
||||
hits?: TypesenseParagraphHit[]
|
||||
groupedHits?: TypesenseGroupedHit[]
|
||||
}
|
||||
|
||||
interface SearchGroup {
|
||||
docId: string
|
||||
firstHit: TypesenseParagraphHit
|
||||
allHits: TypesenseParagraphHit[]
|
||||
}
|
||||
|
||||
interface BrowseItem {
|
||||
|
|
@ -97,31 +108,18 @@ interface DisplayGroup {
|
|||
|
||||
// ---- State ----------------------------------------------------------------
|
||||
|
||||
// Modo búsqueda (con query): hits de párrafos agrupados por documento
|
||||
const hits = ref<TypesenseParagraphHit[]>([])
|
||||
// Modo búsqueda (con query): grupos devueltos por Typesense
|
||||
const groupedHits = ref<SearchGroup[]>([])
|
||||
const total = ref(0)
|
||||
const currentPage = ref(1)
|
||||
|
||||
const hasMore = computed(() =>
|
||||
settings.paginationType === 'infinite_scroll' ? hits.value.length < total.value : false
|
||||
settings.paginationType === 'infinite_scroll' ? groupedHits.value.length < total.value : false
|
||||
)
|
||||
|
||||
// Progressive display (sólo en scroll infinito)
|
||||
const visibleGroupCount = ref(10)
|
||||
|
||||
const groupedHits = computed(() => {
|
||||
const map = new Map<string, TypesenseParagraphHit[]>()
|
||||
for (const hit of hits.value) {
|
||||
const id = hit.document.document_id
|
||||
if (!map.has(id)) map.set(id, [])
|
||||
map.get(id)!.push(hit)
|
||||
}
|
||||
return [...map.entries()].map(([docId, docHits]) => ({
|
||||
docId,
|
||||
firstHit: docHits[0]!
|
||||
}))
|
||||
})
|
||||
|
||||
const visibleGroups = computed(() =>
|
||||
settings.paginationType === 'infinite_scroll'
|
||||
? groupedHits.value.slice(0, visibleGroupCount.value)
|
||||
|
|
@ -245,24 +243,32 @@ async function runSearch(q: string, page = 1, append = false) {
|
|||
highlightFields: QUERY_BY,
|
||||
highlightStartTag: '<mark class="search-match">',
|
||||
highlightEndTag: '</mark>',
|
||||
highlightAffixNumTokens: 30
|
||||
highlightAffixNumTokens: 30,
|
||||
groupBy: 'document_id'
|
||||
}]
|
||||
}
|
||||
})
|
||||
|
||||
console.log('Search response', multi)
|
||||
if (seq !== searchSeq) return
|
||||
|
||||
const res = (multi?.results?.[0] ?? {}) as TypesenseSearchResponse
|
||||
const newHits = res?.hits ?? []
|
||||
const rawGroups = res?.groupedHits ?? []
|
||||
|
||||
console.log('Raw groups', rawGroups)
|
||||
const newGroups: SearchGroup[] = rawGroups.map(g => ({
|
||||
docId: g.groupKey[0]!,
|
||||
firstHit: g.hits[0]!,
|
||||
allHits: g.hits
|
||||
}))
|
||||
|
||||
if (!append) docCache.value = {}
|
||||
const newDocIds = [...new Set(newHits.map(h => h.document.document_id).filter(Boolean))]
|
||||
const newDocIds = newGroups.map(g => g.docId).filter(Boolean)
|
||||
await fetchDocumentMeta(newDocIds)
|
||||
|
||||
if (seq !== searchSeq) return
|
||||
|
||||
hits.value = append ? hits.value.concat(newHits) : newHits
|
||||
total.value = res?.found ?? hits.value.length
|
||||
groupedHits.value = append ? groupedHits.value.concat(newGroups) : newGroups
|
||||
total.value = res?.found ?? groupedHits.value.length
|
||||
currentPage.value = typePage
|
||||
if (!append) activePage.value = page
|
||||
} catch (err: unknown) {
|
||||
|
|
@ -270,7 +276,7 @@ async function runSearch(q: string, page = 1, append = false) {
|
|||
console.error('Typesense error', err)
|
||||
errorMsg.value = (err as Error)?.message || 'Error al buscar.'
|
||||
if (!append) {
|
||||
hits.value = []
|
||||
groupedHits.value = []
|
||||
total.value = 0
|
||||
}
|
||||
} finally {
|
||||
|
|
@ -320,7 +326,6 @@ async function runBrowse(page = 1, append = false) {
|
|||
})
|
||||
|
||||
if (seq !== searchSeq) return
|
||||
|
||||
const result = (multi?.results?.[0] as { found?: number, hits?: Array<{ document: DocMeta }> } | undefined)
|
||||
const newItems = (result?.hits ?? []).map(h => ({
|
||||
docId: h.document.id!,
|
||||
|
|
@ -360,7 +365,7 @@ function goToPage(p: number) {
|
|||
browseItems.value = []
|
||||
runBrowse(p, false)
|
||||
} else {
|
||||
hits.value = []
|
||||
groupedHits.value = []
|
||||
runSearch(query.value, p, false)
|
||||
}
|
||||
}
|
||||
|
|
@ -399,7 +404,7 @@ onBeforeUnmount(() => { if (timeoutId) clearTimeout(timeoutId) })
|
|||
watch(debouncedQuery, (q) => {
|
||||
activePage.value = 1
|
||||
if (!q.trim()) {
|
||||
hits.value = []
|
||||
groupedHits.value = []
|
||||
total.value = 0
|
||||
currentPage.value = 1
|
||||
visibleGroupCount.value = 10
|
||||
|
|
@ -411,7 +416,7 @@ watch(debouncedQuery, (q) => {
|
|||
browseItems.value = []
|
||||
browseTotal.value = 0
|
||||
browsePage.value = 1
|
||||
hits.value = []
|
||||
groupedHits.value = []
|
||||
total.value = 0
|
||||
currentPage.value = 1
|
||||
visibleGroupCount.value = 10
|
||||
|
|
@ -498,9 +503,8 @@ async function fetchDocumentParagraphs(docId: string) {
|
|||
async function selectGroup(group: DisplayGroup) {
|
||||
selectedDocId.value = group.docId
|
||||
selectedHit.value = group.firstHit
|
||||
selectedMatchingHits.value = group.firstHit
|
||||
? hits.value.filter(h => h.document.document_id === group.docId)
|
||||
: []
|
||||
const searchGroup = groupedHits.value.find(g => g.docId === group.docId)
|
||||
selectedMatchingHits.value = searchGroup?.allHits ?? []
|
||||
fetchFullDocument(group.docId)
|
||||
fetchDocumentParagraphs(group.docId)
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue