2022-08-31 06:33:37 +05:00
|
|
|
/*
|
|
|
|
|
This file is part of the Notesnook project (https://notesnook.com/)
|
|
|
|
|
|
2023-01-16 13:44:52 +05:00
|
|
|
Copyright (C) 2023 Streetwriters (Private) Limited
|
2022-08-31 06:33:37 +05:00
|
|
|
|
|
|
|
|
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/>.
|
|
|
|
|
*/
|
2022-08-30 16:13:11 +05:00
|
|
|
|
2024-01-04 11:38:40 +05:00
|
|
|
import { Editor, scrollIntoViewById } from "@notesnook/editor";
|
2023-08-04 20:58:24 +05:00
|
|
|
import {
|
|
|
|
|
ThemeDefinition,
|
|
|
|
|
useThemeColors,
|
|
|
|
|
useThemeEngineStore
|
|
|
|
|
} from "@notesnook/theme";
|
2022-07-04 16:14:26 +05:00
|
|
|
import {
|
|
|
|
|
MutableRefObject,
|
|
|
|
|
useCallback,
|
|
|
|
|
useEffect,
|
|
|
|
|
useRef,
|
2022-08-30 11:05:10 +05:00
|
|
|
useState
|
2022-07-04 16:14:26 +05:00
|
|
|
} from "react";
|
2024-01-26 22:00:54 +05:00
|
|
|
import { EventTypes, isReactNative, post, randId, saveTheme } from "../utils";
|
2023-08-01 12:07:21 +05:00
|
|
|
import { injectCss, transform } from "../utils/css";
|
2023-12-21 10:14:53 +05:00
|
|
|
import { useTabContext, useTabStore } from "./useTabStore";
|
|
|
|
|
|
2022-07-04 16:14:26 +05:00
|
|
|
type Attachment = {
|
|
|
|
|
hash: string;
|
|
|
|
|
filename: string;
|
2023-05-29 23:01:53 +05:00
|
|
|
mime: string;
|
2022-07-04 16:14:26 +05:00
|
|
|
size: number;
|
|
|
|
|
};
|
|
|
|
|
|
2022-06-23 19:14:55 +05:00
|
|
|
export type Selection = {
|
|
|
|
|
[name: string]: {
|
|
|
|
|
text?: string;
|
|
|
|
|
length?: number;
|
2022-08-30 11:05:10 +05:00
|
|
|
attributes?: Record<string, unknown>;
|
2022-06-23 19:14:55 +05:00
|
|
|
type?: "mark" | "node";
|
|
|
|
|
};
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
type Timers = {
|
|
|
|
|
selectionChange: NodeJS.Timeout | null;
|
|
|
|
|
change: NodeJS.Timeout | null;
|
2023-04-10 18:15:59 +05:00
|
|
|
wordCounter: NodeJS.Timeout | null;
|
2022-06-23 19:14:55 +05:00
|
|
|
};
|
|
|
|
|
|
2023-06-19 19:04:53 +05:00
|
|
|
function isInViewport(element: any) {
|
|
|
|
|
const rect = element.getBoundingClientRect();
|
|
|
|
|
return (
|
|
|
|
|
rect.top >= 0 &&
|
|
|
|
|
rect.left >= 0 &&
|
|
|
|
|
rect.bottom <=
|
|
|
|
|
(window.innerHeight || document.documentElement.clientHeight) &&
|
|
|
|
|
rect.right <= (window.innerWidth || document.documentElement.clientWidth)
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function scrollIntoView(editor: Editor) {
|
|
|
|
|
setTimeout(() => {
|
|
|
|
|
const node = editor?.state.selection.$from;
|
|
|
|
|
const dom = node ? editor?.view?.domAtPos(node.pos) : null;
|
|
|
|
|
let domNode = dom?.node;
|
|
|
|
|
|
|
|
|
|
if (domNode) {
|
|
|
|
|
if (domNode.nodeType === Node.TEXT_NODE && domNode.parentNode) {
|
|
|
|
|
domNode = domNode.parentNode;
|
|
|
|
|
}
|
|
|
|
|
if (isInViewport(domNode)) return;
|
|
|
|
|
(domNode as HTMLElement).scrollIntoView({
|
|
|
|
|
behavior: "smooth",
|
|
|
|
|
block: "end"
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
}, 100);
|
|
|
|
|
}
|
|
|
|
|
|
2022-06-23 19:14:55 +05:00
|
|
|
export type EditorController = {
|
|
|
|
|
selectionChange: (editor: Editor) => void;
|
|
|
|
|
titleChange: (title: string) => void;
|
2024-01-29 18:17:23 +05:00
|
|
|
contentChange: (editor: Editor, ignoreEdit?: boolean) => void;
|
2022-06-23 19:14:55 +05:00
|
|
|
scroll: (event: React.UIEvent<HTMLDivElement, UIEvent>) => void;
|
|
|
|
|
title: string;
|
|
|
|
|
setTitle: React.Dispatch<React.SetStateAction<string>>;
|
2022-06-27 18:39:59 +05:00
|
|
|
openFilePicker: (type: "image" | "file" | "camera") => void;
|
2022-06-23 19:14:55 +05:00
|
|
|
downloadAttachment: (attachment: Attachment) => void;
|
2023-03-17 14:43:53 +05:00
|
|
|
previewAttachment: (attachment: Attachment) => void;
|
2022-07-04 16:14:26 +05:00
|
|
|
content: MutableRefObject<string | null>;
|
2022-07-07 18:40:21 +05:00
|
|
|
onUpdate: () => void;
|
2022-07-08 23:52:45 +05:00
|
|
|
titlePlaceholder: string;
|
2022-09-16 12:10:06 +05:00
|
|
|
openLink: (url: string) => boolean;
|
2022-07-08 23:52:45 +05:00
|
|
|
setTitlePlaceholder: React.Dispatch<React.SetStateAction<string>>;
|
2023-04-18 01:05:06 +05:00
|
|
|
countWords: (ms: number) => void;
|
2023-07-06 11:10:09 +05:00
|
|
|
copyToClipboard: (text: string) => void;
|
2024-03-15 07:43:03 +05:00
|
|
|
getAttachmentData: (attachment: Partial<Attachment>) => Promise<string>;
|
2023-12-25 15:02:34 +05:00
|
|
|
updateTab: () => void;
|
|
|
|
|
loading: boolean;
|
|
|
|
|
setLoading: (value: boolean) => void;
|
2024-01-04 11:38:40 +05:00
|
|
|
getTableOfContents: () => any[];
|
|
|
|
|
scrollIntoView: (id: string) => void;
|
2024-03-08 13:00:10 +05:00
|
|
|
passwordInputRef: MutableRefObject<HTMLInputElement | null>;
|
|
|
|
|
focusPassInput: () => void;
|
|
|
|
|
blurPassInput: () => void;
|
2022-06-23 19:14:55 +05:00
|
|
|
};
|
2024-01-04 11:38:40 +05:00
|
|
|
export function useEditorController({
|
|
|
|
|
update,
|
|
|
|
|
getTableOfContents
|
|
|
|
|
}: {
|
|
|
|
|
update: () => void;
|
|
|
|
|
getTableOfContents: () => any[];
|
|
|
|
|
}): EditorController {
|
2024-03-08 13:00:10 +05:00
|
|
|
const passwordInputRef = useRef<HTMLInputElement | null>(null);
|
2023-12-21 10:14:53 +05:00
|
|
|
const tab = useTabContext();
|
2023-12-25 15:02:34 +05:00
|
|
|
const [loading, setLoading] = useState(true);
|
2023-08-01 12:07:21 +05:00
|
|
|
const setTheme = useThemeEngineStore((store) => store.setTheme);
|
|
|
|
|
const { colors } = useThemeColors("editor");
|
2022-06-23 19:14:55 +05:00
|
|
|
const [title, setTitle] = useState("");
|
2022-07-08 23:52:45 +05:00
|
|
|
const [titlePlaceholder, setTitlePlaceholder] = useState("Note title");
|
2022-07-04 16:14:26 +05:00
|
|
|
const htmlContentRef = useRef<string | null>(null);
|
2023-12-25 15:02:34 +05:00
|
|
|
const updateTabOnFocus = useRef(false);
|
2022-06-23 19:14:55 +05:00
|
|
|
const timers = useRef<Timers>({
|
|
|
|
|
selectionChange: null,
|
2023-04-10 18:15:59 +05:00
|
|
|
change: null,
|
|
|
|
|
wordCounter: null
|
2022-06-23 19:14:55 +05:00
|
|
|
});
|
|
|
|
|
|
2023-12-25 15:02:34 +05:00
|
|
|
if (!tab.noteId && loading) {
|
|
|
|
|
setLoading(false);
|
|
|
|
|
}
|
|
|
|
|
|
2022-08-30 11:05:10 +05:00
|
|
|
const selectionChange = useCallback((_editor: Editor) => {}, []);
|
2022-06-23 19:14:55 +05:00
|
|
|
|
2023-12-21 10:14:53 +05:00
|
|
|
const titleChange = useCallback(
|
|
|
|
|
(title: string) => {
|
|
|
|
|
post(EventTypes.contentchange, undefined, tab.id, tab.noteId);
|
|
|
|
|
post(EventTypes.title, title, tab.id, tab.noteId);
|
|
|
|
|
},
|
|
|
|
|
[tab.id, tab.noteId]
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
const countWords = useCallback(
|
|
|
|
|
(ms = 300) => {
|
|
|
|
|
if (typeof timers.current.wordCounter === "number")
|
|
|
|
|
clearTimeout(timers.current.wordCounter);
|
|
|
|
|
timers.current.wordCounter = setTimeout(() => {
|
|
|
|
|
statusBars[tab.id]?.current?.updateWords();
|
|
|
|
|
}, ms);
|
|
|
|
|
},
|
|
|
|
|
[tab.id]
|
|
|
|
|
);
|
2022-06-23 19:14:55 +05:00
|
|
|
|
2023-08-01 12:07:21 +05:00
|
|
|
useEffect(() => {
|
|
|
|
|
injectCss(transform(colors));
|
|
|
|
|
}, [colors]);
|
|
|
|
|
|
2023-04-18 01:05:06 +05:00
|
|
|
const contentChange = useCallback(
|
2024-02-03 12:35:28 +05:00
|
|
|
(editor: Editor, ignoreEdit?: boolean) => {
|
2024-03-26 08:57:04 +05:00
|
|
|
if (editorControllers[tab.id]?.loading) return;
|
2023-04-18 01:05:06 +05:00
|
|
|
const currentSessionId = globalThis.sessionId;
|
2023-12-25 10:00:02 +05:00
|
|
|
post(EventTypes.contentchange, undefined, tab.id, tab.noteId);
|
2023-04-18 01:05:06 +05:00
|
|
|
if (!editor) return;
|
|
|
|
|
if (typeof timers.current.change === "number") {
|
|
|
|
|
clearTimeout(timers.current?.change);
|
|
|
|
|
}
|
2023-12-21 10:14:53 +05:00
|
|
|
timers.current.change = setTimeout(() => {
|
|
|
|
|
htmlContentRef.current = editor.getHTML();
|
|
|
|
|
post(
|
|
|
|
|
EventTypes.content,
|
|
|
|
|
{
|
|
|
|
|
html: htmlContentRef.current,
|
|
|
|
|
ignoreEdit: ignoreEdit
|
|
|
|
|
},
|
|
|
|
|
tab.id,
|
|
|
|
|
tab.noteId,
|
|
|
|
|
currentSessionId
|
|
|
|
|
);
|
|
|
|
|
}, 300);
|
2023-04-18 01:05:06 +05:00
|
|
|
|
|
|
|
|
countWords(5000);
|
|
|
|
|
},
|
2023-12-21 10:14:53 +05:00
|
|
|
[countWords, tab.id, tab.noteId]
|
2023-04-18 01:05:06 +05:00
|
|
|
);
|
|
|
|
|
|
2022-06-23 19:14:55 +05:00
|
|
|
const scroll = useCallback(
|
2023-12-21 10:14:53 +05:00
|
|
|
(_event: React.UIEvent<HTMLDivElement, UIEvent>) => {
|
2024-03-26 08:57:04 +05:00
|
|
|
const noteId = useTabStore
|
|
|
|
|
.getState()
|
|
|
|
|
.getNoteIdForTab(useTabStore.getState().currentTab);
|
|
|
|
|
if (noteId) {
|
|
|
|
|
useTabStore.getState().setNoteState(noteId, {
|
2023-12-25 15:02:34 +05:00
|
|
|
top: _event.currentTarget.scrollTop
|
|
|
|
|
});
|
|
|
|
|
}
|
2023-12-21 10:14:53 +05:00
|
|
|
},
|
2024-03-26 08:57:04 +05:00
|
|
|
[]
|
2022-06-23 19:14:55 +05:00
|
|
|
);
|
|
|
|
|
|
2022-07-25 15:44:53 +05:00
|
|
|
const onUpdate = useCallback(() => {
|
2022-07-08 18:33:54 +05:00
|
|
|
update();
|
2024-03-26 08:57:04 +05:00
|
|
|
logger("info", "Updating content...");
|
2022-07-25 15:44:53 +05:00
|
|
|
}, [update]);
|
2022-07-07 18:40:21 +05:00
|
|
|
|
2024-03-26 08:57:04 +05:00
|
|
|
useEffect(() => {
|
|
|
|
|
if (tab.locked) {
|
|
|
|
|
htmlContentRef.current = "";
|
|
|
|
|
setLoading(true);
|
|
|
|
|
onUpdate();
|
|
|
|
|
}
|
|
|
|
|
}, [tab.locked, onUpdate]);
|
|
|
|
|
|
2022-06-23 19:14:55 +05:00
|
|
|
const onMessage = useCallback(
|
2022-08-30 11:05:10 +05:00
|
|
|
(event: Event & { data?: string }) => {
|
|
|
|
|
if (event?.data?.[0] !== "{") return;
|
|
|
|
|
const message = JSON.parse(event.data);
|
|
|
|
|
const type = message.type;
|
|
|
|
|
const value = message.value;
|
2023-12-21 10:14:53 +05:00
|
|
|
|
2023-12-22 08:23:42 +05:00
|
|
|
if (message.tabId !== tab.id && type !== "native:status") {
|
2023-12-21 10:14:53 +05:00
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const editor = editors[tab.id];
|
2022-06-23 19:14:55 +05:00
|
|
|
switch (type) {
|
2022-09-20 18:33:55 +05:00
|
|
|
case "native:updatehtml": {
|
|
|
|
|
htmlContentRef.current = value;
|
2023-12-25 15:02:34 +05:00
|
|
|
if (tab.id !== useTabStore.getState().currentTab) {
|
|
|
|
|
updateTabOnFocus.current = true;
|
|
|
|
|
} else {
|
|
|
|
|
if (!editor) break;
|
|
|
|
|
const { from, to } = editor.state.selection;
|
|
|
|
|
editor?.commands.setContent(htmlContentRef.current, false, {
|
|
|
|
|
preserveWhitespace: true
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
editor.commands.setTextSelection({
|
|
|
|
|
from,
|
|
|
|
|
to
|
|
|
|
|
});
|
|
|
|
|
countWords(0);
|
|
|
|
|
}
|
2023-12-21 10:14:53 +05:00
|
|
|
|
2022-09-20 18:33:55 +05:00
|
|
|
break;
|
|
|
|
|
}
|
2022-06-23 19:14:55 +05:00
|
|
|
case "native:html":
|
2022-07-04 16:14:26 +05:00
|
|
|
htmlContentRef.current = value;
|
2024-03-12 11:53:52 +05:00
|
|
|
logger("info", "LOADING NOTE HTML");
|
2023-12-25 15:02:34 +05:00
|
|
|
if (!editor) break;
|
2022-07-08 18:33:54 +05:00
|
|
|
update();
|
2023-12-25 15:02:34 +05:00
|
|
|
countWords(0);
|
2022-06-23 19:14:55 +05:00
|
|
|
break;
|
|
|
|
|
case "native:theme":
|
2023-08-01 12:07:21 +05:00
|
|
|
setTheme(message.value);
|
2023-08-04 20:58:24 +05:00
|
|
|
setTimeout(() => {
|
|
|
|
|
saveTheme(message.value as ThemeDefinition);
|
|
|
|
|
});
|
2022-06-23 19:14:55 +05:00
|
|
|
break;
|
|
|
|
|
case "native:title":
|
|
|
|
|
setTitle(value);
|
|
|
|
|
break;
|
|
|
|
|
case "native:titleplaceholder":
|
2022-07-08 23:52:45 +05:00
|
|
|
setTitlePlaceholder(value);
|
2022-06-23 19:14:55 +05:00
|
|
|
break;
|
|
|
|
|
case "native:status":
|
|
|
|
|
break;
|
2023-06-19 19:04:53 +05:00
|
|
|
case "native:keyboardShown":
|
2024-01-24 18:58:14 +05:00
|
|
|
if (editor) {
|
|
|
|
|
scrollIntoView(editor as any);
|
2023-06-19 19:04:53 +05:00
|
|
|
}
|
|
|
|
|
break;
|
2024-01-26 22:00:54 +05:00
|
|
|
case "native:attachment-data":
|
|
|
|
|
if (pendingResolvers[value.resolverId]) {
|
|
|
|
|
logger("info", "resolved data for attachment", value.resolverId);
|
|
|
|
|
pendingResolvers[value.resolverId](value.data);
|
|
|
|
|
}
|
|
|
|
|
break;
|
2022-06-23 19:14:55 +05:00
|
|
|
default:
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
post(type); // Notify that message was delivered successfully.
|
|
|
|
|
},
|
2023-12-21 10:14:53 +05:00
|
|
|
[tab, update, countWords, setTheme]
|
2022-06-23 19:14:55 +05:00
|
|
|
);
|
|
|
|
|
|
|
|
|
|
useEffect(() => {
|
|
|
|
|
if (!isReactNative()) return; // Subscribe only in react native webview.
|
2022-08-30 11:05:10 +05:00
|
|
|
const isSafari = navigator.vendor.match(/apple/i);
|
|
|
|
|
let root: Document | Window = document;
|
2022-06-23 19:14:55 +05:00
|
|
|
if (isSafari) {
|
|
|
|
|
root = window;
|
|
|
|
|
}
|
|
|
|
|
root.addEventListener("message", onMessage);
|
|
|
|
|
|
|
|
|
|
return () => {
|
|
|
|
|
root.removeEventListener("message", onMessage);
|
|
|
|
|
};
|
|
|
|
|
}, [onMessage]);
|
|
|
|
|
|
2023-12-21 10:14:53 +05:00
|
|
|
const openFilePicker = useCallback(
|
|
|
|
|
(type: "image" | "file" | "camera") => {
|
|
|
|
|
post(EventTypes.filepicker, type, tab.id, tab.noteId);
|
|
|
|
|
},
|
|
|
|
|
[tab.id, tab.noteId]
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
const downloadAttachment = useCallback(
|
|
|
|
|
(attachment: Attachment) => {
|
|
|
|
|
post(EventTypes.download, attachment, tab.id, tab.noteId);
|
|
|
|
|
},
|
|
|
|
|
[tab.id, tab.noteId]
|
|
|
|
|
);
|
|
|
|
|
const previewAttachment = useCallback(
|
|
|
|
|
(attachment: Attachment) => {
|
|
|
|
|
post(EventTypes.previewAttachment, attachment, tab.id, tab.noteId);
|
|
|
|
|
},
|
|
|
|
|
[tab.id, tab.noteId]
|
|
|
|
|
);
|
|
|
|
|
const openLink = useCallback(
|
|
|
|
|
(url: string) => {
|
|
|
|
|
post(EventTypes.link, url, tab.id, tab.noteId);
|
|
|
|
|
return true;
|
|
|
|
|
},
|
|
|
|
|
[tab.id, tab.noteId]
|
|
|
|
|
);
|
2022-09-16 12:10:06 +05:00
|
|
|
|
2023-07-06 11:10:09 +05:00
|
|
|
const copyToClipboard = (text: string) => {
|
|
|
|
|
post(EventTypes.copyToClipboard, text);
|
|
|
|
|
};
|
|
|
|
|
|
2024-03-15 07:43:03 +05:00
|
|
|
const getAttachmentData = (attachment: Partial<Attachment>) => {
|
2024-01-26 22:00:54 +05:00
|
|
|
return new Promise<string>((resolve, reject) => {
|
|
|
|
|
const resolverId = randId("get_attachment_data");
|
|
|
|
|
pendingResolvers[resolverId] = (data) => {
|
|
|
|
|
delete pendingResolvers[resolverId];
|
|
|
|
|
resolve(data);
|
|
|
|
|
};
|
|
|
|
|
post(EventTypes.getAttachmentData, {
|
|
|
|
|
attachment,
|
|
|
|
|
resolverId: resolverId
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
};
|
|
|
|
|
|
2022-06-23 19:14:55 +05:00
|
|
|
return {
|
2024-01-04 11:38:40 +05:00
|
|
|
getTableOfContents: getTableOfContents,
|
|
|
|
|
scrollIntoView: (id: string) => scrollIntoViewById(id),
|
2022-06-23 19:14:55 +05:00
|
|
|
contentChange,
|
|
|
|
|
selectionChange,
|
|
|
|
|
titleChange,
|
|
|
|
|
scroll,
|
2023-12-25 15:02:34 +05:00
|
|
|
loading,
|
|
|
|
|
setLoading,
|
2022-06-23 19:14:55 +05:00
|
|
|
title,
|
|
|
|
|
setTitle,
|
2022-07-08 23:52:45 +05:00
|
|
|
titlePlaceholder,
|
|
|
|
|
setTitlePlaceholder,
|
2022-06-23 19:14:55 +05:00
|
|
|
openFilePicker,
|
|
|
|
|
downloadAttachment,
|
2023-03-17 14:43:53 +05:00
|
|
|
previewAttachment,
|
2022-07-04 16:14:26 +05:00
|
|
|
content: htmlContentRef,
|
2022-09-16 12:10:06 +05:00
|
|
|
openLink,
|
2023-04-18 01:05:06 +05:00
|
|
|
onUpdate: onUpdate,
|
2023-07-06 11:10:09 +05:00
|
|
|
countWords,
|
2024-01-26 22:00:54 +05:00
|
|
|
copyToClipboard,
|
2023-12-25 15:02:34 +05:00
|
|
|
getAttachmentData,
|
|
|
|
|
updateTab: () => {
|
|
|
|
|
// When the tab is focused, we apply any updates to content that were recieved when
|
|
|
|
|
// the tab was not focused.
|
|
|
|
|
updateTabOnFocus.current = false;
|
|
|
|
|
setTimeout(() => {
|
|
|
|
|
if (!updateTabOnFocus.current) return;
|
|
|
|
|
const editor = editors[tab.id];
|
|
|
|
|
if (!editor) return;
|
|
|
|
|
const { from, to } = editor.state.selection;
|
|
|
|
|
editor?.commands.setContent(htmlContentRef.current, false, {
|
|
|
|
|
preserveWhitespace: true
|
|
|
|
|
});
|
|
|
|
|
editor.commands.setTextSelection({
|
|
|
|
|
from,
|
|
|
|
|
to
|
|
|
|
|
});
|
|
|
|
|
countWords();
|
|
|
|
|
logger("info", `Tab ${tab.id} updated.`);
|
|
|
|
|
}, 1);
|
2024-03-08 13:00:10 +05:00
|
|
|
},
|
|
|
|
|
passwordInputRef,
|
|
|
|
|
focusPassInput: () => {
|
|
|
|
|
logger("info", "focus pass input...");
|
|
|
|
|
passwordInputRef.current?.focus();
|
|
|
|
|
},
|
|
|
|
|
blurPassInput: () => {
|
|
|
|
|
passwordInputRef.current?.blur();
|
2023-12-25 15:02:34 +05:00
|
|
|
}
|
2022-06-23 19:14:55 +05:00
|
|
|
};
|
|
|
|
|
}
|