From d6ceaa91c49c96ddd3b8a193a5b26afbf8da703a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9C=9B?= <1501034130@qq.com> Date: Thu, 10 Oct 2024 12:53:16 +0800 Subject: [PATCH] =?UTF-8?q?=E7=BC=96=E8=BE=91=E5=99=A8=E6=9F=A5=E6=89=BE?= =?UTF-8?q?=E6=97=B6=E6=94=AF=E6=8C=81=E6=98=BE=E7=A4=BA=E5=BD=93=E5=89=8D?= =?UTF-8?q?=E9=AB=98=E4=BA=AE=E9=A1=B9=E7=B4=A2=E5=BC=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../editor/EditorFindAndReplace.vue | 33 ++++++++++++----- src/components/editor/index.ts | 36 ++++++++----------- src/components/editor/search.ts | 23 ++++++++++++ src/styles/Default.css | 4 ++- 4 files changed, 64 insertions(+), 32 deletions(-) diff --git a/src/components/editor/EditorFindAndReplace.vue b/src/components/editor/EditorFindAndReplace.vue index b14f21a..eaec4e7 100644 --- a/src/components/editor/EditorFindAndReplace.vue +++ b/src/components/editor/EditorFindAndReplace.vue @@ -7,13 +7,6 @@
-
- - - - - 匹配数目:{{ matchingCount }} -
+
+ + + + +
+ 第 {{ matchingIndex }} 个, + 共 {{ matchingCount }} 个 +
+
(""); const replace = ref(""); +const matchingIndex = ref(0); const matchingCount = ref(0); const enableReplace = ref(false); @@ -92,9 +96,19 @@ const _queryString = (keyword: string, replace?: string) => { props.editor.find(keyword); } + matchingIndex.value = props.editor.getCurrentMatchIndex() + 1; matchingCount.value = props.editor.getMatchCount(); }; +const findNextOrPrev = (dir: -1 | 1) => { + if (dir > 0) props.editor.findNext(); + else props.editor.findPrev(); + + props.editor.focus(); + + matchingIndex.value = props.editor.getCurrentMatchIndex() + 1; +}; + const replaceCurrent = () => { props.editor.replaceCurrent(); props.editor.findNext(); @@ -115,6 +129,7 @@ watchEffect(() => { onUnmounted(() => { props.editor.clearFind(); props.editor.off("update", updateMatchingCount); + props.editor.focus(); }); onMounted(() => { diff --git a/src/components/editor/index.ts b/src/components/editor/index.ts index 9d3b09c..4b05e03 100644 --- a/src/components/editor/index.ts +++ b/src/components/editor/index.ts @@ -23,10 +23,11 @@ import { getSearchState, getMatchHighlights, SearchQuery, - findNext, - findPrev, replaceCurrent, replaceAll, + findPrevNoWrap, + findNextNoWrap, + getSearchMatchingRanges, } from "./search"; interface EventArgument { @@ -343,8 +344,6 @@ export class Editor { public clearFind() { this.find(""); - - this.view!.focus(); } public getSearchState() { @@ -352,37 +351,30 @@ export class Editor { } public getMatchCount() { - if (!this.getSearchState()) return 0; - - const decos = getMatchHighlights(this.state); - let i = 0; - let sum = 0; + return getSearchMatchingRanges(this.state)?.length || 0; + } - // @ts-ignore - const decoArray = decos.children as any[]; + public getCurrentMatchIndex() { + const ranges = getSearchMatchingRanges(this.state); + const selection = this.state.selection; - for (; i < decoArray.length; i += 1) { - if (typeof decoArray[i] === "number") continue; - sum += decoArray[i].local.length; - } + if (!ranges) return -1; - return sum; + return ranges.findIndex( + ({ from, to }) => from === selection.from && to === selection.to + ); } public findPrev() { if (!this.getSearchState()) return; - this.view!.focus(); - - return findPrev(this.state, this.view!.dispatch); + return findPrevNoWrap(this.state, this.view!.dispatch); } public findNext() { if (!this.getSearchState()) return; - this.view!.focus(); - - return findNext(this.state, this.view!.dispatch); + return findNextNoWrap(this.state, this.view!.dispatch); } public replaceCurrent() { diff --git a/src/components/editor/search.ts b/src/components/editor/search.ts index a4231ec..0d63fb6 100644 --- a/src/components/editor/search.ts +++ b/src/components/editor/search.ts @@ -540,6 +540,29 @@ export function getSearchState(state: EditorState): return searchKey.getState(state); } +/// Get the ranges of matching texts in the current active search. +/// Will return `undefined` is the search plugin isn't active. +export function getSearchMatchingRanges(state: EditorState): { from: number, to: number }[] | undefined { + const searchState = searchKey.getState(state) + return searchState && searchState.deco.find().map(({ from, to }) => ({ from, to })) +} + +/// Get the number of matching texts in the current active search. +/// Will return `undefined` is the search plugin isn't active. +export function getSearchMatchesCount(state: EditorState): number | undefined { + return getSearchMatchingRanges(state)?.length +} + +/// Get the index of the match of the current active search +/// that has the same range of the current selection. +/// Will return -1 if no matching text matches the selection, +/// or `undefined` is the search plugin isn't active. +export function getSearchCurrentMatchIndex(state: EditorState): number | undefined { + const ranges = getSearchMatchingRanges(state) + const { from: selFrom, to: selTo } = state.selection + return ranges && ranges.findIndex(({ from, to }) => from === selFrom && to === selTo) +} + /// Access the decoration set holding the currently highlighted search /// matches in the document. export function getMatchHighlights(state: EditorState) { diff --git a/src/styles/Default.css b/src/styles/Default.css index f292f92..4eaf1f4 100644 --- a/src/styles/Default.css +++ b/src/styles/Default.css @@ -509,10 +509,12 @@ label { .ProseMirror-search-match { background-color: var(--p-primary-color); + color: white; } .ProseMirror-active-search-match { - background-color: var(--p-primary-color); + background-color: blue; + color: white; } /* Make sure li selections wrap around markers */