find feature
This commit is contained in:
@@ -56,6 +56,9 @@ onMounted(async () => {
|
|||||||
content: note.content || [],
|
content: note.content || [],
|
||||||
onUpdate: onUpdate,
|
onUpdate: onUpdate,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// Clear any highlights from find feature
|
||||||
|
editor.value.chain().selectAll().unsetHighlight().run()
|
||||||
})
|
})
|
||||||
onBeforeUnmount(() => {
|
onBeforeUnmount(() => {
|
||||||
editor.value?.destroy?.()
|
editor.value?.destroy?.()
|
||||||
@@ -152,10 +155,10 @@ defineExpose({
|
|||||||
background: currentColor;
|
background: currentColor;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
mark {
|
.highlighted {
|
||||||
background: var(--theme-accent);
|
background: var(--theme-accent);
|
||||||
color: var(--theme-bg);
|
color: var(--theme-bg);
|
||||||
padding: 0 0.2em;
|
border-radius: 0.2em;
|
||||||
}
|
}
|
||||||
|
|
||||||
ul[data-type='taskList'] {
|
ul[data-type='taskList'] {
|
||||||
|
|||||||
@@ -1,34 +1,20 @@
|
|||||||
<template>
|
<template>
|
||||||
<div v-if="visible" class="note-find">
|
<div v-if="visible" class="note-find">
|
||||||
<div class="find-bar">
|
<div class="find-bar">
|
||||||
<!-- <input
|
|
||||||
@keydown.enter="findNext"
|
|
||||||
@keydown.escape="close"
|
|
||||||
ref="inputRef"
|
|
||||||
/> -->
|
|
||||||
<search-input
|
<search-input
|
||||||
v-model="searchQuery"
|
v-model="searchQuery"
|
||||||
placeholder="Find..."
|
placeholder="Find..."
|
||||||
ref="inputRef"
|
ref="inputRef"
|
||||||
|
@keydown.escape="close"
|
||||||
/>
|
/>
|
||||||
<span class="match-count">
|
|
||||||
{{ currentMatch + 1 }} / {{ matches.length }}
|
|
||||||
</span>
|
|
||||||
<button @click="findPrev" :disabled="matches.length === 0">
|
|
||||||
↑
|
|
||||||
</button>
|
|
||||||
<button @click="findNext" :disabled="matches.length === 0">
|
|
||||||
↓
|
|
||||||
</button>
|
|
||||||
<button @click="close">✕</button>
|
<button @click="close">✕</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { ref, watch, nextTick } from 'vue'
|
import { ref, watch, nextTick, onBeforeUnmount, onMounted } from 'vue'
|
||||||
import SearchInput from '@/components/SearchInput.vue'
|
import SearchInput from '@/components/SearchInput.vue'
|
||||||
import { Highlight } from '@tiptap/extension-highlight'
|
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
editor: {
|
editor: {
|
||||||
@@ -45,7 +31,6 @@ const emit = defineEmits(['close'])
|
|||||||
|
|
||||||
const searchQuery = ref('')
|
const searchQuery = ref('')
|
||||||
const matches = ref([])
|
const matches = ref([])
|
||||||
const currentMatch = ref(0)
|
|
||||||
const inputRef = ref(null)
|
const inputRef = ref(null)
|
||||||
|
|
||||||
const findInDocument = () => {
|
const findInDocument = () => {
|
||||||
@@ -78,82 +63,25 @@ const findInDocument = () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
matches.value = foundMatches
|
matches.value = foundMatches
|
||||||
currentMatch.value = 0
|
highlightMatches()
|
||||||
|
|
||||||
if (foundMatches.length > 0) {
|
|
||||||
highlightMatch(0)
|
|
||||||
scrollToMatch(0)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const highlightMatch = (index) => {
|
const highlightMatches = () => {
|
||||||
if (!props.editor || !matches.value[index]) return
|
if (!props.editor || !matches.value) return
|
||||||
|
|
||||||
const { from, to } = matches.value[index]
|
matches.value.forEach(({ from, to }) => {
|
||||||
|
props.editor
|
||||||
props.editor
|
.chain()
|
||||||
.chain()
|
.setTextSelection({ from, to })
|
||||||
.setTextSelection({ from, to })
|
.setHighlight({ color: 'var(--theme-accent)' })
|
||||||
.setHighlight({ color: 'var(--theme-accent)' })
|
.run()
|
||||||
.run()
|
})
|
||||||
|
|
||||||
inputRef.value?.focus()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const clearHighlights = () => {
|
const clearHighlights = () => {
|
||||||
if (!props.editor) return
|
if (!props.editor) return
|
||||||
|
|
||||||
const { state } = props.editor
|
props.editor.chain().selectAll().unsetHighlight().run()
|
||||||
const { doc } = state
|
|
||||||
|
|
||||||
doc.descendants((node, pos) => {
|
|
||||||
if (node.marks && node.marks.length > 0) {
|
|
||||||
node.marks.forEach((mark) => {
|
|
||||||
if (mark.type.name === 'highlight') {
|
|
||||||
props.editor.chain().focus().unsetHighlight().run()
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
props.editor.commands.unsetHighlight()
|
|
||||||
}
|
|
||||||
|
|
||||||
const scrollToMatch = (index) => {
|
|
||||||
if (!props.editor || !matches.value[index]) return
|
|
||||||
|
|
||||||
const { from } = matches.value[index]
|
|
||||||
props.editor.commands.setTextSelection({ from, to: from })
|
|
||||||
|
|
||||||
const dom = props.editor.view.dom
|
|
||||||
const selection = window.getSelection()
|
|
||||||
|
|
||||||
if (selection && selection.rangeCount > 0) {
|
|
||||||
const range = selection.getRangeAt(0)
|
|
||||||
range.commonAncestorContainer.scrollIntoView({
|
|
||||||
behavior: 'smooth',
|
|
||||||
block: 'center',
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const findNext = () => {
|
|
||||||
if (matches.value.length === 0) return
|
|
||||||
|
|
||||||
clearHighlights()
|
|
||||||
currentMatch.value = (currentMatch.value + 1) % matches.value.length
|
|
||||||
highlightMatch(currentMatch.value)
|
|
||||||
scrollToMatch(currentMatch.value)
|
|
||||||
}
|
|
||||||
|
|
||||||
const findPrev = () => {
|
|
||||||
if (matches.value.length === 0) return
|
|
||||||
|
|
||||||
clearHighlights()
|
|
||||||
currentMatch.value =
|
|
||||||
(currentMatch.value - 1 + matches.value.length) % matches.value.length
|
|
||||||
highlightMatch(currentMatch.value)
|
|
||||||
scrollToMatch(currentMatch.value)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const close = () => {
|
const close = () => {
|
||||||
@@ -170,27 +98,25 @@ watch(
|
|||||||
await nextTick()
|
await nextTick()
|
||||||
inputRef.value?.focus()
|
inputRef.value?.focus()
|
||||||
if (props.editor && searchQuery.value) {
|
if (props.editor && searchQuery.value) {
|
||||||
console.log('visible change')
|
|
||||||
findInDocument()
|
findInDocument()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
watch(
|
watch(
|
||||||
() => props.editor,
|
() => props.editor,
|
||||||
(newEditor) => {
|
(newEditor) => {
|
||||||
if (newEditor && props.visible && searchQuery.value) {
|
if (newEditor && props.visible && searchQuery.value) {
|
||||||
console.log('editor change')
|
|
||||||
findInDocument()
|
findInDocument()
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
watch(searchQuery, () => {
|
watch(searchQuery, () => {
|
||||||
console.log('search change')
|
|
||||||
findInDocument()
|
findInDocument()
|
||||||
})
|
})
|
||||||
|
|
||||||
|
onMounted(() => clearHighlights())
|
||||||
|
onBeforeUnmount(() => clearHighlights())
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
|
|||||||
@@ -13,12 +13,6 @@
|
|||||||
>
|
>
|
||||||
Italic
|
Italic
|
||||||
</button>
|
</button>
|
||||||
<button
|
|
||||||
@click="editor.chain().focus().toggleHighlight().run()"
|
|
||||||
:class="{ active: editor.isActive('highlight') }"
|
|
||||||
>
|
|
||||||
Highlight
|
|
||||||
</button>
|
|
||||||
</div>
|
</div>
|
||||||
</bubble-menu>
|
</bubble-menu>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -33,7 +33,11 @@ export const extensions = [
|
|||||||
}),
|
}),
|
||||||
TaskList,
|
TaskList,
|
||||||
TaskItem,
|
TaskItem,
|
||||||
Highlight,
|
Highlight.configure({
|
||||||
|
HTMLAttributes: {
|
||||||
|
class: 'highlighted',
|
||||||
|
},
|
||||||
|
}),
|
||||||
Markdown,
|
Markdown,
|
||||||
CodeBlockLowlight.configure({
|
CodeBlockLowlight.configure({
|
||||||
lowlight,
|
lowlight,
|
||||||
|
|||||||
Reference in New Issue
Block a user