mirror of
https://github.com/docmost/docmost.git
synced 2026-09-01 03:15:32 +08:00
typesense support
This commit is contained in:
+3
-11
@@ -1,6 +1,6 @@
|
|||||||
import { ActionIcon, Dialog, Flex, Text, Tooltip } from "@mantine/core";
|
import { ActionIcon, Dialog, Flex, Text, Tooltip } from "@mantine/core";
|
||||||
import { IconArrowNarrowDown, IconArrowNarrowUp, IconX } from "@tabler/icons-react";
|
import { IconArrowNarrowDown, IconArrowNarrowUp, IconX } from "@tabler/icons-react";
|
||||||
import { Editor, useEditor } from "@tiptap/react";
|
import { useEditor } from "@tiptap/react";
|
||||||
import { isEditorReady } from "@docmost/editor-ext";
|
import { isEditorReady } from "@docmost/editor-ext";
|
||||||
import React, { useEffect, useState } from "react";
|
import React, { useEffect, useState } from "react";
|
||||||
import classes from "./search-replace.module.css";
|
import classes from "./search-replace.module.css";
|
||||||
@@ -20,19 +20,11 @@ interface SearchNavigationEvent extends CustomEvent {
|
|||||||
function SearchNavigationDialog({ editor }: SearchNavigationDialogProps) {
|
function SearchNavigationDialog({ editor }: SearchNavigationDialogProps) {
|
||||||
const {t} = useTranslation()
|
const {t} = useTranslation()
|
||||||
const [open, setOpen] = useState(false);
|
const [open, setOpen] = useState(false);
|
||||||
const [searchTerms, setSearchTerms] = useState<string[]>([]);
|
|
||||||
const [resultState, setResultState] = useState({
|
const [resultState, setResultState] = useState({
|
||||||
resultIndex: 0,
|
resultIndex: 0,
|
||||||
resultsLength: 0,
|
resultsLength: 0,
|
||||||
});
|
});
|
||||||
|
|
||||||
const handleSetSearchTerms = (editor: Editor, terms: string[]) => {
|
|
||||||
setSearchTerms(terms);
|
|
||||||
if (isEditorReady(editor)) {
|
|
||||||
editor.commands.setSearchTerms(terms);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const goToSelection = () => {
|
const goToSelection = () => {
|
||||||
if (!isEditorReady(editor)) return;
|
if (!isEditorReady(editor)) return;
|
||||||
|
|
||||||
@@ -73,7 +65,7 @@ function SearchNavigationDialog({ editor }: SearchNavigationDialogProps) {
|
|||||||
if (!terms?.length || !isEditorReady(editor)) return;
|
if (!terms?.length || !isEditorReady(editor)) return;
|
||||||
|
|
||||||
setOpen(true);
|
setOpen(true);
|
||||||
handleSetSearchTerms(editor, terms);
|
editor.commands.setSearchTerms(terms);
|
||||||
editor.commands.setWholeWord(wholeWord);
|
editor.commands.setWholeWord(wholeWord);
|
||||||
editor.commands.resetIndex();
|
editor.commands.resetIndex();
|
||||||
|
|
||||||
@@ -116,7 +108,7 @@ function SearchNavigationDialog({ editor }: SearchNavigationDialogProps) {
|
|||||||
}, [editor, open]);
|
}, [editor, open]);
|
||||||
|
|
||||||
const close = () => {
|
const close = () => {
|
||||||
handleSetSearchTerms(editor, [""]);
|
editor.commands.setSearchTerms([""]);
|
||||||
setOpen(false);
|
setOpen(false);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -196,7 +196,6 @@ function CollabPageEditor({
|
|||||||
const documentState = useDocumentVisibility();
|
const documentState = useDocumentVisibility();
|
||||||
const { pageSlug } = useParams();
|
const { pageSlug } = useParams();
|
||||||
const [searchParams] = useSearchParams();
|
const [searchParams] = useSearchParams();
|
||||||
const searchQueries = searchParams.getAll("q");
|
|
||||||
const slugId = extractPageSlugId(pageSlug);
|
const slugId = extractPageSlugId(pageSlug);
|
||||||
const currentPageEditMode = useAtomValue(currentPageEditModeAtom);
|
const currentPageEditMode = useAtomValue(currentPageEditModeAtom);
|
||||||
const canScroll = useCallback(
|
const canScroll = useCallback(
|
||||||
@@ -419,6 +418,8 @@ function CollabPageEditor({
|
|||||||
const [showStatic, setShowStatic] = useState(true);
|
const [showStatic, setShowStatic] = useState(true);
|
||||||
|
|
||||||
const appliedSearchRef = useRef(false);
|
const appliedSearchRef = useRef(false);
|
||||||
|
const searchQueries = searchParams.getAll("q");
|
||||||
|
const match = searchParams.get("m");
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!editor || editor.isDestroyed) return;
|
if (!editor || editor.isDestroyed) return;
|
||||||
@@ -430,7 +431,7 @@ function CollabPageEditor({
|
|||||||
|
|
||||||
document.dispatchEvent(
|
document.dispatchEvent(
|
||||||
new CustomEvent("openSearchNavigationDialog", {
|
new CustomEvent("openSearchNavigationDialog", {
|
||||||
detail: { searchTerms: searchQueries, wholeWord: true },
|
detail: { searchTerms: searchQueries, wholeWord: match === "whole" },
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@@ -25,12 +25,34 @@ const buildPageSlug = (pageSlugId: string, pageTitle?: string): string => {
|
|||||||
return `${titleSlug}-${pageSlugId}`;
|
return `${titleSlug}-${pageSlugId}`;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
function appendSearchParams(
|
||||||
|
url: string,
|
||||||
|
search?: string[],
|
||||||
|
wholeWord?: boolean,
|
||||||
|
): string {
|
||||||
|
const params = new URLSearchParams();
|
||||||
|
|
||||||
|
search
|
||||||
|
?.map((term) => term.trim())
|
||||||
|
.filter(Boolean)
|
||||||
|
.forEach((term) => params.append("q", term));
|
||||||
|
|
||||||
|
if (wholeWord) {
|
||||||
|
params.set("m", "whole");
|
||||||
|
}
|
||||||
|
|
||||||
|
const queryString = params.toString();
|
||||||
|
|
||||||
|
return queryString ? `${url}?${queryString}` : url;
|
||||||
|
}
|
||||||
|
|
||||||
export const buildPageUrl = (
|
export const buildPageUrl = (
|
||||||
spaceName: string,
|
spaceName: string,
|
||||||
pageSlugId: string,
|
pageSlugId: string,
|
||||||
pageTitle?: string,
|
pageTitle?: string,
|
||||||
anchorId?: string,
|
anchorId?: string,
|
||||||
search?: string[],
|
search?: string[],
|
||||||
|
wholeWord?: boolean,
|
||||||
): string => {
|
): string => {
|
||||||
let url: string;
|
let url: string;
|
||||||
if (spaceName === undefined) {
|
if (spaceName === undefined) {
|
||||||
@@ -39,20 +61,7 @@ export const buildPageUrl = (
|
|||||||
url = `/s/${spaceName}/p/${buildPageSlug(pageSlugId, pageTitle)}`;
|
url = `/s/${spaceName}/p/${buildPageSlug(pageSlugId, pageTitle)}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (search?.length > 0) {
|
url = appendSearchParams(url, search, wholeWord);
|
||||||
const params = new URLSearchParams();
|
|
||||||
|
|
||||||
search
|
|
||||||
.map((term) => term.trim())
|
|
||||||
.filter(Boolean)
|
|
||||||
.forEach((term) => params.append("q", term));
|
|
||||||
|
|
||||||
const queryString = params.toString();
|
|
||||||
|
|
||||||
if (queryString) {
|
|
||||||
url += `?${queryString}`;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return anchorId ? `${url}#${anchorId}` : url;
|
return anchorId ? `${url}#${anchorId}` : url;
|
||||||
};
|
};
|
||||||
@@ -63,8 +72,9 @@ export const buildSharedPageUrl = (opts: {
|
|||||||
pageTitle?: string;
|
pageTitle?: string;
|
||||||
anchorId?: string;
|
anchorId?: string;
|
||||||
search?: string[];
|
search?: string[];
|
||||||
|
wholeWord?: boolean;
|
||||||
}): string => {
|
}): string => {
|
||||||
const { shareId, pageSlugId, pageTitle, anchorId, search } = opts;
|
const { shareId, pageSlugId, pageTitle, anchorId, search, wholeWord } = opts;
|
||||||
let url: string;
|
let url: string;
|
||||||
if (!shareId) {
|
if (!shareId) {
|
||||||
url = `/share/p/${buildPageSlug(pageSlugId, pageTitle)}`;
|
url = `/share/p/${buildPageSlug(pageSlugId, pageTitle)}`;
|
||||||
@@ -72,20 +82,7 @@ export const buildSharedPageUrl = (opts: {
|
|||||||
url = `/share/${shareId}/p/${buildPageSlug(pageSlugId, pageTitle)}`;
|
url = `/share/${shareId}/p/${buildPageSlug(pageSlugId, pageTitle)}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (search?.length > 0) {
|
url = appendSearchParams(url, search, wholeWord);
|
||||||
const params = new URLSearchParams();
|
|
||||||
|
|
||||||
search
|
|
||||||
.map((term) => term.trim())
|
|
||||||
.filter(Boolean)
|
|
||||||
.forEach((term) => params.append("q", term));
|
|
||||||
|
|
||||||
const queryString = params.toString();
|
|
||||||
|
|
||||||
if (queryString) {
|
|
||||||
url += `?${queryString}`;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return anchorId ? `${url}#${anchorId}` : url;
|
return anchorId ? `${url}#${anchorId}` : url;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -102,7 +102,8 @@ export function SearchResultItem({
|
|||||||
pageResult.slugId,
|
pageResult.slugId,
|
||||||
pageResult.title,
|
pageResult.title,
|
||||||
undefined,
|
undefined,
|
||||||
pageResult.matchedText
|
pageResult.matchedText,
|
||||||
|
pageResult.wholeWord
|
||||||
)}
|
)}
|
||||||
style={{ userSelect: "none" }}
|
style={{ userSelect: "none" }}
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ export interface IPageSearch {
|
|||||||
rank: string;
|
rank: string;
|
||||||
highlight: string;
|
highlight: string;
|
||||||
matchedText: string[];
|
matchedText: string[];
|
||||||
|
wholeWord: boolean;
|
||||||
space: Partial<ISpace>;
|
space: Partial<ISpace>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -8,7 +8,8 @@ export class SearchResponseDto {
|
|||||||
creatorId: string;
|
creatorId: string;
|
||||||
rank: number;
|
rank: number;
|
||||||
highlight: string;
|
highlight: string;
|
||||||
matchedText?: string[];
|
matchedText: string[];
|
||||||
|
wholeWord: boolean;
|
||||||
createdAt: Date;
|
createdAt: Date;
|
||||||
updatedAt: Date;
|
updatedAt: Date;
|
||||||
space: Partial<Space>;
|
space: Partial<Space>;
|
||||||
|
|||||||
@@ -149,11 +149,16 @@ export class SearchService {
|
|||||||
.replace(/\r\n|\r|\n/g, ' ')
|
.replace(/\r\n|\r|\n/g, ' ')
|
||||||
.replace(/\s+/g, ' ');
|
.replace(/\s+/g, ' ');
|
||||||
|
|
||||||
result.matchedText = Array.from(
|
result.matchedText = [
|
||||||
|
...new Set(
|
||||||
|
Array.from(
|
||||||
result.highlight.matchAll(/<b>([^<]*)<\/b>/gi),
|
result.highlight.matchAll(/<b>([^<]*)<\/b>/gi),
|
||||||
(match) => match[1],
|
(match) => match[1],
|
||||||
);
|
),
|
||||||
|
),
|
||||||
|
];
|
||||||
|
|
||||||
|
result.wholeWord = true
|
||||||
return result;
|
return result;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user