Add full-text search
- Index notes in a separate, rebuildable SQLite database (index.db) using FTS5 trigram, synced from watcher snapshots so external edits are picked up; terms under 3 characters fall back to LIKE - GET /api/v1/search?q= returns matching notes with the first matching line and a snippet; path matches rank first - Sidebar search panel (Ctrl+Shift+F) with highlighted terms and keyboard navigation; opening a result jumps to and selects the match - Restore scroll position on tab switch with scroll snapshots; restoring scrollTop drifted by a few lines after line heights were re-estimated
This commit is contained in:
1 parent
a11e0ec498
commit
55f0ee174e
13 files changed
+792
-20
No files matched your search
@@ -1,16 +1,23 @@
|
||||
<script setup lang="ts">
|
||||
import { EditorState } from '@codemirror/state'
|
||||
import { EditorState, type StateEffect, type Text } from '@codemirror/state'
|
||||
import type { EditorView } from '@codemirror/view'
|
||||
import { computed, onBeforeUnmount, onMounted, ref, watch } from 'vue'
|
||||
import { createEditorView } from '../editor'
|
||||
import { activeTab, attachView, editorStateOf, noteRefOf, storeViewState, ws } from './workspace'
|
||||
import { activeTab, attachView, editorStateOf, noteRefOf, revealPending, storeViewState, ws } from './workspace'
|
||||
|
||||
const host = ref<HTMLElement>()
|
||||
const tab = computed(activeTab)
|
||||
let view: EditorView
|
||||
// 当前显示的笔记(以身份对象标识,重命名后不变)及各笔记的滚动位置
|
||||
// 当前显示的笔记(以身份对象标识,重命名后不变)及各笔记的滚动位置。
|
||||
// 优先用滚动快照(锚定文档位置):重设状态后视口外的行高会重新估算,单靠 scrollTop 会偏移几行;
|
||||
// 快照只对同一文档有效,笔记在后台被外部修改时退回 scrollTop
|
||||
interface ScrollPosition {
|
||||
doc: Text
|
||||
snapshot: StateEffect<unknown>
|
||||
top: number
|
||||
}
|
||||
let shown: { path: string } | undefined
|
||||
const scrollTops = new WeakMap<object, number>()
|
||||
const scrollPositions = new WeakMap<object, ScrollPosition>()
|
||||
|
||||
function show() {
|
||||
const path = ws.active
|
||||
@@ -18,7 +25,7 @@ function show() {
|
||||
const state = path ? editorStateOf(path) : undefined
|
||||
if (shown && shown !== ref) {
|
||||
storeViewState(shown.path, view.state)
|
||||
scrollTops.set(shown, view.scrollDOM.scrollTop)
|
||||
scrollPositions.set(shown, { doc: view.state.doc, snapshot: view.scrollSnapshot(), top: view.scrollDOM.scrollTop })
|
||||
}
|
||||
if (!ref || !state) {
|
||||
shown = undefined
|
||||
@@ -27,8 +34,13 @@ function show() {
|
||||
// 切换笔记,或该笔记的状态在别处被整体替换(如恢复草稿)时才重设
|
||||
if (shown !== ref || state.doc !== view.state.doc) {
|
||||
view.setState(state)
|
||||
const top = scrollTops.get(ref) ?? 0
|
||||
requestAnimationFrame(() => (view.scrollDOM.scrollTop = top))
|
||||
const pos = scrollPositions.get(ref)
|
||||
// 等一帧:v-show 在此回调之后才取消 display:none,隐藏状态下无法滚动和聚焦
|
||||
requestAnimationFrame(() => {
|
||||
if (shown !== ref || revealPending(view, ref.path)) return
|
||||
if (pos?.doc === view.state.doc) view.dispatch({ effects: pos.snapshot })
|
||||
else view.scrollDOM.scrollTop = pos?.top ?? 0
|
||||
})
|
||||
}
|
||||
shown = ref
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user