diff --git a/apps/web/src/components/editor/header.js b/apps/web/src/components/editor/header.js
index a9be1bce5..bb4099460 100644
--- a/apps/web/src/components/editor/header.js
+++ b/apps/web/src/components/editor/header.js
@@ -1,5 +1,4 @@
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
-import TitleBox from "./title-box";
import { useStore } from "../../stores/editor-store";
import { Input } from "@rebass/forms";
import * as Icon from "../icons";
@@ -18,8 +17,6 @@ function Header({ readonly }) {
return (
<>
-
-
{!readonly && id && (
{tags?.map((tag) => (
diff --git a/apps/web/src/components/editor/index.tsx b/apps/web/src/components/editor/index.tsx
index 92530cfaa..ebd59da4d 100644
--- a/apps/web/src/components/editor/index.tsx
+++ b/apps/web/src/components/editor/index.tsx
@@ -22,6 +22,7 @@ import { downloadAttachment } from "../../common/attachments";
import { EV, EVENTS } from "notes-core/common";
import { db } from "../../common/db";
import useMobile from "../../utils/use-mobile";
+import Titlebox from "./title-box";
function updateWordCount(counter?: CharacterCounter) {
AppEventManager.publish(
@@ -38,8 +39,16 @@ function onEditorChange(noteId: string, sessionId: string, content: string) {
data: content,
});
}
+
+function onTitleChange(noteId: string, title: string) {
+ if (!title) return;
+
+ editorstore.get().setTitle(noteId, title);
+}
+
const debouncedUpdateWordCount = debounce(updateWordCount, 1000);
const debouncedOnEditorChange = debounceWithId(onEditorChange, 100);
+const debouncedOnTitleChange = debounceWithId(onTitleChange, 100);
export default function EditorManager({
noteId,
@@ -52,6 +61,7 @@ export default function EditorManager({
const isOldSession = !nonce && !!noteId;
const [content, setContent] = useState("");
+ const [title, setTitle] = useState("");
const arePropertiesVisible = useStore((store) => store.arePropertiesVisible);
const toggleProperties = useStore((store) => store.toggleProperties);
const isPreviewMode = useStore(
@@ -61,7 +71,6 @@ export default function EditorManager({
(store) => store.session.readonly || isPreviewMode
);
const [dropRef, overlayRef] = useDragOverlay();
- const editor = useEditorInstance();
// TODO move this somewhere more appropriate
// const init = useStore((store) => store.init);
@@ -70,6 +79,7 @@ export default function EditorManager({
(async function () {
await editorstore.newSession(nonce);
setContent("");
+ setTitle("");
})();
}, [isNewSession, nonce]);
@@ -87,13 +97,13 @@ export default function EditorManager({
if (!isOldSession) return;
(async function () {
- await editorstore.openSession(noteId);
+ await editorstore.get().openSession(noteId);
- let content = await editorstore.get().getSessionContent();
+ const { getSessionContent, session } = editorstore.get();
+ const content = await getSessionContent();
+
+ setTitle(session.title);
setContent(content?.data);
- // editorstore.set(
- // (state: any) => (state.session.state = SESSION_STATES.stale)
- // );
if (noteId && content) await db.attachments?.downloadImages(noteId);
})();
}, [noteId, isOldSession]);
@@ -112,60 +122,26 @@ export default function EditorManager({
>
{isPreviewMode && }
toggleProperties(false)}
/>
{arePropertiesVisible && }
-
- {
- if (!editor) return;
- for (let file of e.dataTransfer.files) {
- const result = await attachFile(file);
- if (!result) continue;
- editor.attachFile(result);
- }
- }}
- >
-
-
-
- Drop your files here to attach
-
-
-
+
);
}
type EditorProps = {
+ title: string;
readonly?: boolean;
focusMode?: boolean;
content: string;
onRequestFocus?: () => void;
};
-function Editor({ content, readonly, focusMode, onRequestFocus }: EditorProps) {
+function Editor(props: EditorProps) {
+ const { content, readonly, focusMode, onRequestFocus, title } = props;
const editor = useEditorInstance();
const isMobile = useMobile();
@@ -238,6 +214,14 @@ function Editor({ content, readonly, focusMode, onRequestFocus }: EditorProps) {
}}
/>
)}
+ {
+ const { sessionId, id } = editorstore.get().session;
+ debouncedOnTitleChange(sessionId, id, title);
+ }}
+ title={title}
+ />
;
+};
+function DropZone(props: DropZoneProps) {
+ const { overlayRef } = props;
+ const editor = useEditorInstance();
+
+ return (
+ {
+ if (!editor) return;
+ for (let file of e.dataTransfer.files) {
+ const result = await attachFile(file);
+ if (!result) continue;
+ editor.attachFile(result);
+ }
+ }}
+ >
+
+
+
+ Drop your files here to attach
+
+
+
+ );
+}
+
function useDragOverlay() {
const dropElementRef = useRef();
const overlayRef = useRef();
diff --git a/apps/web/src/components/editor/tiptap.tsx b/apps/web/src/components/editor/tiptap.tsx
index ccfca5506..aad46d83c 100644
--- a/apps/web/src/components/editor/tiptap.tsx
+++ b/apps/web/src/components/editor/tiptap.tsx
@@ -3,7 +3,7 @@ import { useTheme } from "emotion-theming";
import { Toolbar, useTiptap, PortalProvider, Editor } from "notesnook-editor";
import { Box, Flex } from "rebass";
import "notesnook-editor/dist/styles.css";
-import { PropsWithChildren, useEffect, useRef } from "react";
+import { PropsWithChildren, useEffect, useRef, useState } from "react";
import useMobile from "../../utils/use-mobile";
import { Attachment } from "./plugins/picker";
import { CharacterCounter, IEditor } from "./types";
@@ -13,6 +13,7 @@ import { AttachmentType } from "notesnook-editor/dist/extensions/attachment";
import { getCurrentPreset } from "../../common/toolbar-config";
type TipTapProps = {
+ editorContainer: HTMLElement;
onChange?: (content: string, counter?: CharacterCounter) => void;
onInsertAttachment?: (type: AttachmentType) => void;
onDownloadAttachment?: (attachment: Attachment) => void;
@@ -30,10 +31,10 @@ function TipTap(props: TipTapProps) {
onFocus = () => {},
content,
toolbarContainerId,
+ editorContainer,
readonly,
} = props;
- const editorContentRef = useRef();
const theme: Theme = useTheme();
const isMobile = useMobile();
const counter = useRef();
@@ -43,7 +44,7 @@ function TipTap(props: TipTapProps) {
const editor = useTiptap(
{
- element: editorContentRef.current,
+ element: editorContainer,
editable: !readonly,
content,
autofocus: "start",
@@ -84,7 +85,7 @@ function TipTap(props: TipTapProps) {
return true;
},
},
- [content, readonly, theme]
+ [content, readonly]
);
useEffect(() => {
@@ -98,7 +99,7 @@ function TipTap(props: TipTapProps) {
}, [toggleSearch, editor?.storage.searchreplace?.isSearching]);
return (
-
+ <>
-
-
+ >
);
}
+function TiptapWrapper(props: Omit) {
+ const [isReady, setIsReady] = useState(false);
+ const editorContainerRef = useRef();
+ useEffect(() => {
+ setIsReady(true);
+ }, []);
+
+ return (
+
+
+ {isReady && editorContainerRef.current ? (
+
+ ) : null}
+
+
+
+ );
+}
+export default TiptapWrapper;
+
function Portal(props: PropsWithChildren<{ containerId?: string }>) {
const { containerId, children } = props;
const container = containerId && document.getElementById(containerId);
@@ -131,15 +151,6 @@ function Portal(props: PropsWithChildren<{ containerId?: string }>) {
);
}
-function TiptapProvider(props: TipTapProps) {
- return (
-
-
-
- );
-}
-export default TiptapProvider;
-
function toIEditor(editor: Editor): IEditor {
return {
focus: () => editor.commands.focus("start"),
diff --git a/apps/web/src/components/editor/title-box.tsx b/apps/web/src/components/editor/title-box.tsx
index 1a10738f7..eed73d02a 100644
--- a/apps/web/src/components/editor/title-box.tsx
+++ b/apps/web/src/components/editor/title-box.tsx
@@ -1,36 +1,20 @@
-import React, { useEffect, useRef, useState } from "react";
+import React, { useEffect, useState } from "react";
import { Input } from "@rebass/forms";
-import { useStore, store, SESSION_STATES } from "../../stores/editor-store";
type TitleBoxProps = {
readonly: boolean;
+ title: string;
+ setTitle: (title: string) => void;
};
function TitleBox(props: TitleBoxProps) {
- const { readonly } = props;
-
- const state = useStore((store) => store.session.state);
- const sessionId = useStore((store) => store.session.id);
- const title = useStore((store) => store.session.title);
- const setTitle = useStore((store) => store.setTitle);
-
+ const { readonly, setTitle, title } = props;
const [currentTitle, setCurrentTitle] = useState();
- const [placeholder, setPlaceholder] = useState();
useEffect(() => {
- const noteTitle = store.get().session.title;
- if (state === SESSION_STATES.new && noteTitle !== currentTitle) {
- setCurrentTitle("");
- setPlaceholder("");
- } else if (state === SESSION_STATES.stale) {
- setCurrentTitle(noteTitle);
- }
- // We do not want to update when currentTitle changes.
- }, [state, sessionId]);
-
- useEffect(() => {
- if (currentTitle !== title) setPlaceholder(title);
+ // if (currentTitle !== title) setPlaceholder(title);
// We do not want to update when currentTitle changes.
+ setCurrentTitle(title);
}, [title]);
return (
@@ -39,7 +23,7 @@ function TitleBox(props: TitleBoxProps) {
variant="clean"
data-test-id="editor-title"
className="editorTitle"
- placeholder={placeholder || "Note title"}
+ placeholder={"Note title"}
width="100%"
readOnly={readonly}
sx={{
@@ -50,12 +34,16 @@ function TitleBox(props: TitleBoxProps) {
}}
onChange={(e) => {
setCurrentTitle(e.target.value);
- setTitle(sessionId, e.target.value);
+ setTitle(e.target.value);
}}
/>
);
}
export default React.memo(TitleBox, (prevProps, nextProps) => {
- return prevProps.readonly === nextProps.readonly;
+ return (
+ prevProps.readonly === nextProps.readonly &&
+ prevProps.title === nextProps.title &&
+ prevProps.setTitle === nextProps.setTitle
+ );
});
diff --git a/apps/web/src/navigation/index.js b/apps/web/src/navigation/index.js
index 92969c721..cc12af741 100644
--- a/apps/web/src/navigation/index.js
+++ b/apps/web/src/navigation/index.js
@@ -35,26 +35,10 @@ export function hashNavigate(
if (addNonce) url += `/${++last}`;
window.history[`${replace ? "replace" : "push"}State`](null, null, `#${url}`);
- if (notify) dispatchEvent(new HashChangeEvent("hashchange"));
- // if (typeof url !== "string") {
- // throw new Error(`"url" must be a string, was provided a(n) ${typeof url}`);
- // }
- // if (Array.isArray(replaceOrQuery)) {
- // throw new Error(
- // '"replaceOrQuery" must be boolean, object, or URLSearchParams'
- // );
- // }
- // if (replaceOrQuery !== null && typeof replaceOrQuery === "object") {
- // url += "?" + new URLSearchParams(replaceOrQuery).toString();
- // } else if (replace === undefined && replaceOrQuery !== undefined) {
- // replace = replaceOrQuery;
- // } else if (replace === undefined && replaceOrQuery === undefined) {
- // replace = false;
- // }
-
- // window.history[`${replace ? "replace" : "push"}State`](null, null, url);
- // dispatchEvent(new PopStateEvent("popstate", null));
+ const event = new HashChangeEvent("hashchange");
+ event.notify = notify;
+ dispatchEvent(event);
}
export function useQueryParams(parseFn = parseQuery) {
diff --git a/apps/web/src/stores/editor-store.js b/apps/web/src/stores/editor-store.js
index bfe4c6d74..d547c9579 100644
--- a/apps/web/src/stores/editor-store.js
+++ b/apps/web/src/stores/editor-store.js
@@ -237,12 +237,12 @@ class EditorStore extends BaseStore {
appStore.setIsEditorOpen(false);
};
- setTitle = (sessionId, title) => {
- return this.saveSession(sessionId, { title });
+ setTitle = (noteId, title) => {
+ return this.saveSession(noteId, { title });
};
- toggle = (sessionId, name, value) => {
- return this.saveSession(sessionId, { [name]: value });
+ toggle = (noteId, name, value) => {
+ return this.saveSession(noteId, { [name]: value });
};
saveSessionContent = (noteId, sessionId, content) => {
diff --git a/apps/web/src/utils/useHashLocation.js b/apps/web/src/utils/use-hash-location.ts
similarity index 66%
rename from apps/web/src/utils/useHashLocation.js
rename to apps/web/src/utils/use-hash-location.ts
index 5de311772..98554d924 100644
--- a/apps/web/src/utils/useHashLocation.js
+++ b/apps/web/src/utils/use-hash-location.ts
@@ -5,7 +5,7 @@ import { hashNavigate } from "../navigation";
// (excluding the leading '#' symbol)
const currentLocation = () => {
const location = window.location.hash.replace(/^#/, "") || "/";
- let end = location.indexOf("?");
+ let end: number | undefined = location.indexOf("?");
if (end <= -1) end = undefined;
return location.substring(0, end);
};
@@ -17,14 +17,22 @@ const currentQuery = () => {
);
};
+type HashLocation = { location: string; update: boolean };
export default function useHashLocation() {
- const [loc, setLoc] = useState(currentLocation());
+ const [loc, setLoc] = useState({
+ location: currentLocation(),
+ update: true,
+ });
const [queryParams, setQueryParams] = useState(currentQuery());
useEffect(() => {
// this function is called whenever the hash changes
- const handler = () => {
- setLoc(currentLocation());
+ const handler = (e: HashChangeEvent) => {
+ const notify = (e as any).notify === undefined ? true : (e as any).notify;
+ setLoc({
+ location: currentLocation(),
+ update: notify,
+ });
setQueryParams(currentQuery());
};
@@ -32,5 +40,5 @@ export default function useHashLocation() {
window.addEventListener("hashchange", handler);
return () => window.removeEventListener("hashchange", handler);
}, []);
- return [loc, queryParams, hashNavigate];
+ return [loc, queryParams, hashNavigate] as const;
}
diff --git a/apps/web/src/utils/use-hash-routes.js b/apps/web/src/utils/use-hash-routes.js
index b87180cd7..6dae05b01 100644
--- a/apps/web/src/utils/use-hash-routes.js
+++ b/apps/web/src/utils/use-hash-routes.js
@@ -3,7 +3,9 @@ import useHashLocation from "./use-hash-location";
var lastRoute = null;
export default function useHashRoutes(routes) {
- const [location] = useHashLocation();
+ const [{ location, update }] = useHashLocation();
+ if (!update) return lastRoute;
+
const matcher = makeMatcher();
for (var key in routes) {
const [match, params] = matcher(key, location);
diff --git a/apps/web/src/utils/useHashRoute.js b/apps/web/src/utils/useHashRoute.js
deleted file mode 100644
index cc5830dd3..000000000
--- a/apps/web/src/utils/useHashRoute.js
+++ /dev/null
@@ -1,8 +0,0 @@
-import makeMatcher from "wouter/matcher";
-import useHashLocation from "./use-hash-location";
-
-export default function useHashRoute(pattern) {
- const [location] = useHashLocation();
- const matcher = makeMatcher();
- return matcher(pattern, location);
-}