mirror of
https://github.com/streetwriters/notesnook.git
synced 2025-12-16 19:57:52 +01:00
mobile: search and sorting
This commit is contained in:
committed by
Abdullah Atta
parent
8dac907ead
commit
5660178760
181
apps/mobile/app/components/list-items/search-result/index.tsx
Normal file
181
apps/mobile/app/components/list-items/search-result/index.tsx
Normal file
@@ -0,0 +1,181 @@
|
||||
/*
|
||||
This file is part of the Notesnook project (https://notesnook.com/)
|
||||
|
||||
Copyright (C) 2023 Streetwriters (Private) Limited
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
import React from "react";
|
||||
import { HighlightedResult } from "@notesnook/core";
|
||||
import { View } from "react-native";
|
||||
import { Pressable } from "../../ui/pressable";
|
||||
import Paragraph from "../../ui/typography/paragraph";
|
||||
import { useThemeColors } from "@notesnook/theme";
|
||||
import { DefaultAppStyles } from "../../../utils/styles";
|
||||
import Heading from "../../ui/typography/heading";
|
||||
import { AppFontSize } from "../../../utils/size";
|
||||
import { Properties } from "../../properties";
|
||||
import { db } from "../../../common/database";
|
||||
import { eSendEvent } from "../../../services/event-manager";
|
||||
import { eOnLoadNote } from "../../../utils/events";
|
||||
import { IconButton } from "../../ui/icon-button";
|
||||
import { fluidTabsRef } from "../../../utils/global-refs";
|
||||
type SearchResultProps = {
|
||||
item: HighlightedResult;
|
||||
};
|
||||
|
||||
export const SearchResult = (props: SearchResultProps) => {
|
||||
const [expanded, setExpanded] = React.useState(true);
|
||||
const { colors } = useThemeColors();
|
||||
|
||||
const openNote = async (index?: number) => {
|
||||
const note = await db.notes.note(props.item.id);
|
||||
eSendEvent(eOnLoadNote, {
|
||||
item: {
|
||||
...note,
|
||||
content: props.item.rawContent
|
||||
? {
|
||||
data: props.item.rawContent || "",
|
||||
type: "tiptap"
|
||||
}
|
||||
: undefined
|
||||
},
|
||||
searchResultIndex: index
|
||||
});
|
||||
fluidTabsRef.current?.goToPage("editor");
|
||||
};
|
||||
|
||||
return (
|
||||
<Pressable
|
||||
style={{
|
||||
alignSelf: "flex-start",
|
||||
alignItems: "flex-start",
|
||||
paddingHorizontal: DefaultAppStyles.GAP,
|
||||
paddingVertical: DefaultAppStyles.GAP_VERTICAL_SMALL,
|
||||
borderRadius: 0
|
||||
}}
|
||||
onLongPress={async () => {
|
||||
const note = await db.notes.note(props.item.id);
|
||||
Properties.present(note);
|
||||
}}
|
||||
onPress={async () => openNote()}
|
||||
>
|
||||
<View
|
||||
style={{
|
||||
flexDirection: "row",
|
||||
justifyContent: "space-between",
|
||||
alignItems: "center",
|
||||
width: "100%"
|
||||
}}
|
||||
>
|
||||
<View
|
||||
style={{
|
||||
flexDirection: "row",
|
||||
alignItems: "center",
|
||||
gap: DefaultAppStyles.GAP_SMALL / 2,
|
||||
flexShrink: 1
|
||||
}}
|
||||
>
|
||||
{props.item.content?.length ? (
|
||||
<IconButton
|
||||
name={!expanded ? "chevron-right" : "chevron-down"}
|
||||
onPress={() => setExpanded((prev) => !prev)}
|
||||
size={AppFontSize.md + 2}
|
||||
color={colors.secondary.icon}
|
||||
style={{
|
||||
width: 23,
|
||||
height: 23
|
||||
}}
|
||||
/>
|
||||
) : null}
|
||||
<Heading
|
||||
size={AppFontSize.sm}
|
||||
style={{
|
||||
flexShrink: 1
|
||||
}}
|
||||
>
|
||||
{props.item.title.map((title) => (
|
||||
<>
|
||||
{title.prefix}
|
||||
<Heading
|
||||
size={AppFontSize.sm}
|
||||
style={{
|
||||
backgroundColor: colors.secondary.accent,
|
||||
color: colors.secondary.accentForeground
|
||||
}}
|
||||
>
|
||||
{title.match}
|
||||
</Heading>
|
||||
{title.suffix}
|
||||
</>
|
||||
))}
|
||||
</Heading>
|
||||
</View>
|
||||
{props.item.content?.length ? (
|
||||
<Paragraph size={AppFontSize.xxs} color={colors.secondary.paragraph}>
|
||||
{props.item.content.length}
|
||||
</Paragraph>
|
||||
) : null}
|
||||
</View>
|
||||
|
||||
{expanded &&
|
||||
props.item.content.map((content, index) => (
|
||||
<Pressable
|
||||
key={props.item.id + index}
|
||||
style={{
|
||||
alignSelf: "flex-start",
|
||||
alignItems: "flex-start",
|
||||
paddingVertical: DefaultAppStyles.GAP_VERTICAL_SMALL,
|
||||
borderColor: colors.primary.border,
|
||||
borderWidth: 0,
|
||||
borderRadius: 0,
|
||||
borderBottomWidth: 1,
|
||||
borderBottomColor: colors.primary.border,
|
||||
width: "100%"
|
||||
}}
|
||||
onLongPress={async () => {
|
||||
const note = await db.notes.note(props.item.id);
|
||||
Properties.present(note);
|
||||
}}
|
||||
onPress={() => {
|
||||
let activeIndex = 0;
|
||||
for (let i = 0; i <= index; i++) {
|
||||
activeIndex += props.item.content[i].length;
|
||||
}
|
||||
console.log(activeIndex);
|
||||
openNote(activeIndex);
|
||||
}}
|
||||
>
|
||||
<Paragraph>
|
||||
{content.map((match) => (
|
||||
<>
|
||||
{match.prefix}
|
||||
<Paragraph
|
||||
size={AppFontSize.sm}
|
||||
style={{
|
||||
backgroundColor: colors.secondary.accent,
|
||||
color: colors.secondary.accentForeground
|
||||
}}
|
||||
>
|
||||
{match.match}
|
||||
</Paragraph>
|
||||
{match.suffix}
|
||||
</>
|
||||
))}
|
||||
</Paragraph>
|
||||
</Pressable>
|
||||
))}
|
||||
</Pressable>
|
||||
);
|
||||
};
|
||||
@@ -150,7 +150,7 @@ export default function List(props: ListProps) {
|
||||
flex: 1
|
||||
}}
|
||||
>
|
||||
{props.data?.placeholders?.length === 0 ? (
|
||||
{props.data?.placeholders?.length === 0 || !props.data ? (
|
||||
<>
|
||||
{props.CustomLisHeader ? (
|
||||
props.CustomLisHeader
|
||||
|
||||
@@ -27,6 +27,7 @@ import {
|
||||
GroupHeader,
|
||||
GroupOptions,
|
||||
GroupingKey,
|
||||
HighlightedResult,
|
||||
Item,
|
||||
ItemType,
|
||||
Note,
|
||||
@@ -49,6 +50,7 @@ import { NoteWrapper } from "../list-items/note/wrapper";
|
||||
import { NotebookWrapper } from "../list-items/notebook/wrapper";
|
||||
import ReminderItem from "../list-items/reminder";
|
||||
import TagItem from "../list-items/tag";
|
||||
import { SearchResult } from "../list-items/search-result";
|
||||
|
||||
type ListItemWrapperProps<TItem = Item> = {
|
||||
group?: GroupingKey;
|
||||
@@ -153,7 +155,9 @@ export function ListItemWrapper(props: ListItemWrapperProps) {
|
||||
},
|
||||
items?.cacheItem(index) ? 100 : 0
|
||||
);
|
||||
} catch (e) {}
|
||||
} catch (e) {
|
||||
/** empty */
|
||||
}
|
||||
})();
|
||||
}, [index, items, refreshItem]);
|
||||
|
||||
@@ -196,7 +200,7 @@ export function ListItemWrapper(props: ListItemWrapperProps) {
|
||||
notebooks={notebooks.current}
|
||||
reminder={reminder.current}
|
||||
attachmentsCount={attachmentsCount.current}
|
||||
date={getDate(item, group)}
|
||||
date={getDate(item as Note, group)}
|
||||
isRenderedInActionSheet={isSheet}
|
||||
index={index}
|
||||
locked={locked.current}
|
||||
@@ -227,7 +231,7 @@ export function ListItemWrapper(props: ListItemWrapperProps) {
|
||||
<NotebookWrapper
|
||||
item={item as Notebook}
|
||||
totalNotes={totalNotes.current}
|
||||
date={getDate(item, group)}
|
||||
date={getDate(item as Notebook, group)}
|
||||
index={index}
|
||||
/>
|
||||
</>
|
||||
@@ -285,12 +289,34 @@ export function ListItemWrapper(props: ListItemWrapperProps) {
|
||||
/>
|
||||
</>
|
||||
);
|
||||
case "searchResult":
|
||||
return (
|
||||
<>
|
||||
{groupHeader && previousIndex.current === index && !isSheet ? (
|
||||
<SectionHeader
|
||||
screen={props.renderedInRoute}
|
||||
item={groupHeader}
|
||||
index={index}
|
||||
dataType={item.type}
|
||||
color={props.customAccentColor}
|
||||
groupOptions={groupOptions}
|
||||
onOpenJumpToDialog={() => {
|
||||
eSendEvent(eOpenJumpToDialog, {
|
||||
ref: props.scrollRef,
|
||||
data: items
|
||||
});
|
||||
}}
|
||||
/>
|
||||
) : null}
|
||||
<SearchResult item={item as HighlightedResult} />
|
||||
</>
|
||||
);
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
function getDate(item: Item, groupType?: GroupingKey): number {
|
||||
function getDate(item: Notebook | Note, groupType?: GroupingKey): number {
|
||||
return (
|
||||
getSortValue(
|
||||
groupType
|
||||
|
||||
@@ -236,6 +236,11 @@ class Commands {
|
||||
const tabId = useTabStore.getState().currentTab;
|
||||
return this.sendCommand("scrollIntoViewById", id, tabId);
|
||||
};
|
||||
|
||||
scrollToSearchResult = (index: number) => {
|
||||
const tabId = useTabStore.getState().currentTab;
|
||||
return this.sendCommand("scrollToSearchResult", index, tabId);
|
||||
};
|
||||
}
|
||||
|
||||
export default Commands;
|
||||
|
||||
@@ -469,8 +469,7 @@ export const useEditor = (
|
||||
}
|
||||
) => {
|
||||
currentNotes.current[note.id] = note;
|
||||
const locked = note && (await db.vaults.itemExists(note));
|
||||
if ((locked || note.content) && note.content?.data) {
|
||||
if (note.content && note.content?.data) {
|
||||
currentContents.current[note.id] = {
|
||||
data: note.content?.data,
|
||||
type: note.content?.type || "tiptap",
|
||||
@@ -498,6 +497,7 @@ export const useEditor = (
|
||||
session?: TabSessionItem;
|
||||
newTab?: boolean;
|
||||
refresh?: boolean;
|
||||
searchResultIndex?: number;
|
||||
}) => {
|
||||
loadNoteMutex.runExclusive(async () => {
|
||||
if (!event) return;
|
||||
@@ -683,13 +683,17 @@ export const useEditor = (
|
||||
{
|
||||
data: currentContents.current[item.id]?.data || "",
|
||||
scrollTop: tab?.session?.scrollTop,
|
||||
selection: tab?.session?.selection
|
||||
selection: tab?.session?.selection,
|
||||
searchResultIndex: event.searchResultIndex
|
||||
},
|
||||
tabId,
|
||||
10000
|
||||
);
|
||||
|
||||
setTimeout(() => {
|
||||
if (event.searchResultIndex !== undefined) {
|
||||
commands.scrollToSearchResult(event.searchResultIndex);
|
||||
}
|
||||
if (blockIdRef.current) {
|
||||
commands.scrollIntoViewById(blockIdRef.current);
|
||||
blockIdRef.current = undefined;
|
||||
|
||||
@@ -17,7 +17,13 @@ You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { Item, Note, VirtualizedGrouping } from "@notesnook/core";
|
||||
import {
|
||||
FilteredSelector,
|
||||
Item,
|
||||
Note,
|
||||
VirtualizedGrouping
|
||||
} from "@notesnook/core";
|
||||
import { strings } from "@notesnook/intl";
|
||||
import React, { useEffect, useRef, useState } from "react";
|
||||
import { DatabaseLogger, db } from "../../common/database";
|
||||
import List from "../../components/list";
|
||||
@@ -28,20 +34,20 @@ import { NavigationProps } from "../../services/navigation";
|
||||
import useNavigationStore from "../../stores/use-navigation-store";
|
||||
import { eGroupOptionsUpdated, eOnRefreshSearch } from "../../utils/events";
|
||||
import { SearchBar } from "./search-bar";
|
||||
import { FilteredSelector } from "@notesnook/core";
|
||||
import { strings } from "@notesnook/intl";
|
||||
export const Search = ({ route, navigation }: NavigationProps<"Search">) => {
|
||||
const [results, setResults] = useState<VirtualizedGrouping<Item>>();
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [searchStatus, setSearchStatus] = useState<string>();
|
||||
const currentQuery = useRef<string>();
|
||||
const timer = useRef<NodeJS.Timeout>();
|
||||
const isFocused = useNavigationFocus(navigation, {
|
||||
useNavigationFocus(navigation, {
|
||||
onFocus: (prev) => {
|
||||
useNavigationStore.getState().setFocusedRouteId(route.name);
|
||||
return !prev?.current;
|
||||
},
|
||||
onBlur: () => false
|
||||
onBlur: () => {
|
||||
return false;
|
||||
}
|
||||
});
|
||||
|
||||
const onSearch = React.useCallback(
|
||||
@@ -57,12 +63,14 @@ export const Search = ({ route, navigation }: NavigationProps<"Search">) => {
|
||||
setLoading(true);
|
||||
let results: VirtualizedGrouping<Item> | undefined;
|
||||
const groupOptions = db.settings.getGroupOptions("search");
|
||||
|
||||
switch (route.params.type) {
|
||||
case "note":
|
||||
results = await db.lookup
|
||||
.notes(query, route.params.items as FilteredSelector<Note>)
|
||||
.sorted(groupOptions);
|
||||
results = await db.lookup.notes(
|
||||
query,
|
||||
groupOptions,
|
||||
route.params.items as FilteredSelector<Note>
|
||||
);
|
||||
|
||||
break;
|
||||
case "notebook":
|
||||
results = await db.lookup.notebooks(query).sorted(groupOptions);
|
||||
@@ -143,7 +151,7 @@ export const Search = ({ route, navigation }: NavigationProps<"Search">) => {
|
||||
clearTimeout(timer.current);
|
||||
timer.current = setTimeout(() => {
|
||||
onSearch(query);
|
||||
}, 300);
|
||||
}, 500);
|
||||
}}
|
||||
loading={loading}
|
||||
/>
|
||||
|
||||
11
apps/mobile/package-lock.json
generated
11
apps/mobile/package-lock.json
generated
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "@notesnook/mobile",
|
||||
"version": "3.1.0-beta.2",
|
||||
"version": "3.1.1",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "@notesnook/mobile",
|
||||
"version": "3.1.0-beta.2",
|
||||
"version": "3.1.1",
|
||||
"hasInstallScript": true,
|
||||
"license": "GPL-3.0-or-later",
|
||||
"workspaces": [
|
||||
@@ -1069,8 +1069,7 @@
|
||||
"prismjs": "^1.29.0",
|
||||
"qclone": "^1.2.0",
|
||||
"rfdc": "^1.3.0",
|
||||
"spark-md5": "^3.0.2",
|
||||
"sqlite-better-trigram": "0.0.2"
|
||||
"spark-md5": "^3.0.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@notesnook/crypto": "file:../crypto",
|
||||
@@ -1096,6 +1095,8 @@
|
||||
"nanoid": "5.0.7",
|
||||
"otplib": "^12.0.1",
|
||||
"refractor": "^4.8.1",
|
||||
"sqlite-better-trigram": "^0.0.3",
|
||||
"sqlite3-fts5-html": "^0.0.3",
|
||||
"vitest": "2.1.8",
|
||||
"vitest-fetch-mock": "^0.2.2",
|
||||
"ws": "^8.13.0"
|
||||
@@ -3727,7 +3728,7 @@
|
||||
"hasInstallScript": true,
|
||||
"license": "GPL-3.0-or-later",
|
||||
"dependencies": {
|
||||
"@notesnook-importer/core": "^2.1.1",
|
||||
"@notesnook-importer/core": "^2.2.2",
|
||||
"@notesnook/common": "file:../common",
|
||||
"@notesnook/intl": "file:../intl",
|
||||
"@notesnook/theme": "file:../theme",
|
||||
|
||||
2
packages/common/package-lock.json
generated
2
packages/common/package-lock.json
generated
@@ -83,7 +83,7 @@
|
||||
"otplib": "^12.0.1",
|
||||
"refractor": "^4.8.1",
|
||||
"sqlite-better-trigram": "^0.0.3",
|
||||
"sqlite3-fts5-html": "^0.0.2",
|
||||
"sqlite3-fts5-html": "^0.0.3",
|
||||
"vitest": "2.1.8",
|
||||
"vitest-fetch-mock": "^0.2.2",
|
||||
"ws": "^8.13.0"
|
||||
|
||||
@@ -93,7 +93,7 @@ export type Collections = {
|
||||
|
||||
export type CollectionType = keyof Collections;
|
||||
|
||||
export type ItemType = ValueOf<Collections>;
|
||||
export type ItemType = ValueOf<Collections> | "searchResult";
|
||||
|
||||
export type Item = ValueOf<ItemMap>;
|
||||
export type GroupableItem = ValueOf<
|
||||
@@ -130,6 +130,7 @@ export type ItemMap = {
|
||||
sessioncontent: SessionContentItem;
|
||||
settingitem: SettingItem;
|
||||
vault: Vault;
|
||||
searchResult: HighlightedResult;
|
||||
|
||||
/**
|
||||
* @deprecated only kept here for migration purposes
|
||||
@@ -493,9 +494,8 @@ export type Match = {
|
||||
match: string;
|
||||
suffix: string;
|
||||
};
|
||||
export interface HighlightedResult {
|
||||
type: "searchResult";
|
||||
id: string;
|
||||
|
||||
export interface HighlightedResult extends BaseItem<"searchResult"> {
|
||||
rawContent?: string;
|
||||
title: Match[];
|
||||
content: Match[][];
|
||||
|
||||
2
packages/editor-mobile/package-lock.json
generated
2
packages/editor-mobile/package-lock.json
generated
@@ -63,7 +63,7 @@
|
||||
"hasInstallScript": true,
|
||||
"license": "GPL-3.0-or-later",
|
||||
"dependencies": {
|
||||
"@notesnook-importer/core": "^2.1.1",
|
||||
"@notesnook-importer/core": "^2.2.2",
|
||||
"@notesnook/common": "file:../common",
|
||||
"@notesnook/intl": "file:../intl",
|
||||
"@notesnook/theme": "file:../theme",
|
||||
|
||||
@@ -201,6 +201,9 @@ const Tiptap = ({
|
||||
copyToClipboard: (text) => {
|
||||
globalThis.editorControllers[tab.id]?.copyToClipboard(text);
|
||||
},
|
||||
onFocus: () => {
|
||||
getContentDiv().classList.remove("searching");
|
||||
},
|
||||
onSelectionUpdate: () => {
|
||||
if (tabRef.current.session?.noteId) {
|
||||
clearTimeout(noteStateUpdateTimer.current);
|
||||
@@ -245,7 +248,11 @@ const Tiptap = ({
|
||||
]);
|
||||
|
||||
const update = useCallback(
|
||||
(scrollTop?: number, selection?: { to: number; from: number }) => {
|
||||
(
|
||||
scrollTop?: number,
|
||||
selection?: { to: number; from: number },
|
||||
searchResultIndex?: number
|
||||
) => {
|
||||
setTick((tick) => tick + 1);
|
||||
globalThis.editorControllers[tabRef.current.id]?.setTitlePlaceholder(
|
||||
strings.noteTitle()
|
||||
@@ -253,7 +260,13 @@ const Tiptap = ({
|
||||
setTimeout(() => {
|
||||
editorControllers[tabRef.current.id]?.setLoading(false);
|
||||
setTimeout(() => {
|
||||
if (searchResultIndex !== undefined) {
|
||||
globalThis.editorControllers[
|
||||
tabRef.current.id
|
||||
]?.scrollToSearchResult(searchResultIndex);
|
||||
} else {
|
||||
restoreNoteSelection(scrollTop, selection);
|
||||
}
|
||||
}, 300);
|
||||
}, 1);
|
||||
},
|
||||
@@ -270,6 +283,9 @@ const Tiptap = ({
|
||||
scrollTop: () => containerRef.current?.scrollTop || 0,
|
||||
scrollTo: (top) => {
|
||||
containerRef.current?.scrollTo({ top, behavior: "auto" });
|
||||
},
|
||||
getContentDiv() {
|
||||
return getContentDiv();
|
||||
}
|
||||
});
|
||||
const controllerRef = useRef(controller);
|
||||
@@ -888,7 +904,7 @@ const TiptapProvider = (): JSX.Element => {
|
||||
return contentRef.current;
|
||||
}
|
||||
const editorContainer = document.createElement("div");
|
||||
editorContainer.classList.add("selectable", "main-editor");
|
||||
editorContainer.classList.add("selectable", "main-editor", "searching");
|
||||
editorContainer.style.flex = "1";
|
||||
editorContainer.style.cursor = "text";
|
||||
editorContainer.style.padding = "0px 12px";
|
||||
@@ -897,6 +913,7 @@ const TiptapProvider = (): JSX.Element => {
|
||||
editorContainer.style.fontFamily =
|
||||
getFontById(settings.fontFamily)?.font || "sans-serif";
|
||||
contentRef.current = editorContainer;
|
||||
|
||||
return editorContainer;
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, []);
|
||||
|
||||
@@ -101,20 +101,25 @@ export type EditorController = {
|
||||
passwordInputRef: MutableRefObject<HTMLInputElement | null>;
|
||||
focusPassInput: () => void;
|
||||
blurPassInput: () => void;
|
||||
scrollToSearchResult: (index: number) => void;
|
||||
getContentDiv: () => HTMLElement | null;
|
||||
};
|
||||
export function useEditorController({
|
||||
update,
|
||||
getTableOfContents,
|
||||
scrollTo,
|
||||
scrollTop
|
||||
scrollTop,
|
||||
getContentDiv
|
||||
}: {
|
||||
update: (
|
||||
scrollTop?: number,
|
||||
selection?: { to: number; from: number }
|
||||
selection?: { to: number; from: number },
|
||||
searchResultIndex?: number
|
||||
) => void;
|
||||
getTableOfContents: () => any[];
|
||||
scrollTo: (top: number) => void;
|
||||
scrollTop: () => number;
|
||||
getContentDiv: () => HTMLElement | null;
|
||||
}): EditorController {
|
||||
const passwordInputRef = useRef<HTMLInputElement | null>(null);
|
||||
const tab = useTabContext();
|
||||
@@ -360,7 +365,7 @@ export function useEditorController({
|
||||
htmlContentRef.current = value.data;
|
||||
logger("info", "LOADING NOTE HTML");
|
||||
if (!editor) break;
|
||||
update(value.scrollTop, value.selection);
|
||||
update(value.scrollTop, value.selection, value.searchResultIndex);
|
||||
setTimeout(() => {
|
||||
countWords(0);
|
||||
}, 300);
|
||||
@@ -449,9 +454,23 @@ export function useEditorController({
|
||||
});
|
||||
};
|
||||
|
||||
const scrollToSearchResult = useCallback((index: number) => {
|
||||
const marks = document.getElementsByTagName("nn-search-result");
|
||||
if (marks.length > index) {
|
||||
const mark = marks[index];
|
||||
if (mark) {
|
||||
mark.scrollIntoView({
|
||||
behavior: "instant",
|
||||
block: "start"
|
||||
});
|
||||
}
|
||||
}
|
||||
}, []);
|
||||
|
||||
return {
|
||||
getTableOfContents: getTableOfContents,
|
||||
scrollIntoView: (id: string) => scrollIntoViewById(id),
|
||||
scrollToSearchResult: scrollToSearchResult,
|
||||
contentChange,
|
||||
selectionChange,
|
||||
titleChange,
|
||||
@@ -471,6 +490,7 @@ export function useEditorController({
|
||||
countWords,
|
||||
copyToClipboard,
|
||||
getAttachmentData,
|
||||
getContentDiv,
|
||||
updateTab: () => {
|
||||
// When the tab is focused, we apply any updates to content that were recieved when
|
||||
// the tab was not focused.
|
||||
|
||||
@@ -196,5 +196,9 @@ globalThis.commands = {
|
||||
|
||||
scrollIntoViewById: (id: string, tabId: string) => {
|
||||
return editorControllers[tabId]?.scrollIntoView(id) || [];
|
||||
},
|
||||
scrollToSearchResult: (index: number, tabId: string) => {
|
||||
editorControllers[tabId]?.getContentDiv()?.classList.add("searching");
|
||||
editorControllers[tabId]?.scrollToSearchResult(index);
|
||||
}
|
||||
};
|
||||
|
||||
66
packages/theme/package-lock.json
generated
66
packages/theme/package-lock.json
generated
@@ -529,39 +529,6 @@
|
||||
"polished": "^4.0.5"
|
||||
}
|
||||
},
|
||||
"node_modules/@theme-ui/color-modes": {
|
||||
"version": "0.16.2",
|
||||
"resolved": "https://registry.npmjs.org/@theme-ui/color-modes/-/color-modes-0.16.2.tgz",
|
||||
"integrity": "sha512-jWEWx53lxNgWCT38i/kwLV2rsvJz8lVZgi5oImnVwYba9VejXD23q1ckbNFJHosQ8KKXY87ht0KPC6BQFIiHtQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@theme-ui/core": "^0.16.2",
|
||||
"@theme-ui/css": "^0.16.2",
|
||||
"deepmerge": "^4.2.2"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@emotion/react": "^11.11.1",
|
||||
"react": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@theme-ui/color-modes/node_modules/@theme-ui/core": {
|
||||
"version": "0.16.2",
|
||||
"resolved": "https://registry.npmjs.org/@theme-ui/core/-/core-0.16.2.tgz",
|
||||
"integrity": "sha512-bBd/ltbwO9vIUjF1jtlOX6XN0IIOdf1vzBp2JCKsSOqdfn84m+XL8OogIe/zOhQ+aM94Nrq4+32tFJc8sFav4Q==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@theme-ui/css": "^0.16.2",
|
||||
"deepmerge": "^4.2.2"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@emotion/react": "^11.11.1",
|
||||
"react": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@theme-ui/components": {
|
||||
"version": "0.16.1",
|
||||
"resolved": "https://registry.npmjs.org/@theme-ui/components/-/components-0.16.1.tgz",
|
||||
@@ -610,39 +577,6 @@
|
||||
"@emotion/react": "^11.11.1"
|
||||
}
|
||||
},
|
||||
"node_modules/@theme-ui/theme-provider": {
|
||||
"version": "0.16.2",
|
||||
"resolved": "https://registry.npmjs.org/@theme-ui/theme-provider/-/theme-provider-0.16.2.tgz",
|
||||
"integrity": "sha512-LRnVevODcGqO0JyLJ3wht+PV3ZoZcJ7XXLJAJWDoGeII4vZcPQKwVy4Lpz/juHsZppQxKcB3U+sQDGBnP25irQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@theme-ui/color-modes": "^0.16.2",
|
||||
"@theme-ui/core": "^0.16.2",
|
||||
"@theme-ui/css": "^0.16.2"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@emotion/react": "^11.11.1",
|
||||
"react": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@theme-ui/theme-provider/node_modules/@theme-ui/core": {
|
||||
"version": "0.16.2",
|
||||
"resolved": "https://registry.npmjs.org/@theme-ui/core/-/core-0.16.2.tgz",
|
||||
"integrity": "sha512-bBd/ltbwO9vIUjF1jtlOX6XN0IIOdf1vzBp2JCKsSOqdfn84m+XL8OogIe/zOhQ+aM94Nrq4+32tFJc8sFav4Q==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@theme-ui/css": "^0.16.2",
|
||||
"deepmerge": "^4.2.2"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@emotion/react": "^11.11.1",
|
||||
"react": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@trpc/server": {
|
||||
"version": "10.45.2",
|
||||
"resolved": "https://registry.npmjs.org/@trpc/server/-/server-10.45.2.tgz",
|
||||
|
||||
66
packages/ui/package-lock.json
generated
66
packages/ui/package-lock.json
generated
@@ -560,39 +560,6 @@
|
||||
"@styled-system/css": "^5.1.5"
|
||||
}
|
||||
},
|
||||
"node_modules/@theme-ui/color-modes": {
|
||||
"version": "0.16.2",
|
||||
"resolved": "https://registry.npmjs.org/@theme-ui/color-modes/-/color-modes-0.16.2.tgz",
|
||||
"integrity": "sha512-jWEWx53lxNgWCT38i/kwLV2rsvJz8lVZgi5oImnVwYba9VejXD23q1ckbNFJHosQ8KKXY87ht0KPC6BQFIiHtQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@theme-ui/core": "^0.16.2",
|
||||
"@theme-ui/css": "^0.16.2",
|
||||
"deepmerge": "^4.2.2"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@emotion/react": "^11.11.1",
|
||||
"react": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@theme-ui/color-modes/node_modules/@theme-ui/core": {
|
||||
"version": "0.16.2",
|
||||
"resolved": "https://registry.npmjs.org/@theme-ui/core/-/core-0.16.2.tgz",
|
||||
"integrity": "sha512-bBd/ltbwO9vIUjF1jtlOX6XN0IIOdf1vzBp2JCKsSOqdfn84m+XL8OogIe/zOhQ+aM94Nrq4+32tFJc8sFav4Q==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@theme-ui/css": "^0.16.2",
|
||||
"deepmerge": "^4.2.2"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@emotion/react": "^11.11.1",
|
||||
"react": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@theme-ui/components": {
|
||||
"version": "0.16.1",
|
||||
"resolved": "https://registry.npmjs.org/@theme-ui/components/-/components-0.16.1.tgz",
|
||||
@@ -641,39 +608,6 @@
|
||||
"@emotion/react": "^11.11.1"
|
||||
}
|
||||
},
|
||||
"node_modules/@theme-ui/theme-provider": {
|
||||
"version": "0.16.2",
|
||||
"resolved": "https://registry.npmjs.org/@theme-ui/theme-provider/-/theme-provider-0.16.2.tgz",
|
||||
"integrity": "sha512-LRnVevODcGqO0JyLJ3wht+PV3ZoZcJ7XXLJAJWDoGeII4vZcPQKwVy4Lpz/juHsZppQxKcB3U+sQDGBnP25irQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@theme-ui/color-modes": "^0.16.2",
|
||||
"@theme-ui/core": "^0.16.2",
|
||||
"@theme-ui/css": "^0.16.2"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@emotion/react": "^11.11.1",
|
||||
"react": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@theme-ui/theme-provider/node_modules/@theme-ui/core": {
|
||||
"version": "0.16.2",
|
||||
"resolved": "https://registry.npmjs.org/@theme-ui/core/-/core-0.16.2.tgz",
|
||||
"integrity": "sha512-bBd/ltbwO9vIUjF1jtlOX6XN0IIOdf1vzBp2JCKsSOqdfn84m+XL8OogIe/zOhQ+aM94Nrq4+32tFJc8sFav4Q==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@theme-ui/css": "^0.16.2",
|
||||
"deepmerge": "^4.2.2"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@emotion/react": "^11.11.1",
|
||||
"react": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/parse-json": {
|
||||
"version": "4.0.2",
|
||||
"resolved": "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.2.tgz",
|
||||
|
||||
Reference in New Issue
Block a user