diff --git a/apps/web/src/common/index.js b/apps/web/src/common/index.js index 0afd1d133..d009413fe 100644 --- a/apps/web/src/common/index.js +++ b/apps/web/src/common/index.js @@ -142,6 +142,7 @@ export async function verifyAccount() { export const AppEventManager = new EventManager(); export const AppEvents = { UPDATE_ATTACHMENT_PROGRESS: "updateAttachmentProgress", + UPDATE_WORD_COUNT: "updateWordCount", }; export function totalSubscriptionConsumed(user) { diff --git a/apps/web/src/components/editor/footer.js b/apps/web/src/components/editor/footer.js index 7f3f18353..c4f5b51ea 100644 --- a/apps/web/src/components/editor/footer.js +++ b/apps/web/src/components/editor/footer.js @@ -1,13 +1,25 @@ -import React from "react"; +import React, { useEffect, useState } from "react"; import { Flex, Text } from "rebass"; +import { AppEventManager, AppEvents } from "../../common"; import { useStore } from "../../stores/editor-store"; import { timeConverter } from "../../utils/time"; function EditorFooter() { + const [totalWords, setTotalWords] = useState(0); const dateEdited = useStore((store) => store.session.dateEdited); const id = useStore((store) => store.session.id); - const totalWords = useStore((store) => store.session.totalWords); const isSaving = useStore((store) => store.session.isSaving); + + useEffect(() => { + const updateWordCountEvent = AppEventManager.subscribe( + AppEvents.UPDATE_WORD_COUNT, + (count) => setTotalWords(count) + ); + return () => { + updateWordCountEvent.unsubscribe(); + }; + }, []); + if (!id) return null; return ( diff --git a/apps/web/src/components/editor/index.js b/apps/web/src/components/editor/index.js index 4fc7851b3..3625c9d4f 100644 --- a/apps/web/src/components/editor/index.js +++ b/apps/web/src/components/editor/index.js @@ -21,15 +21,22 @@ import useTablet from "../../utils/use-tablet"; import Toolbar from "./toolbar"; import EditorLoading from "./loading"; import { db } from "../../common/db"; +import { AppEventManager, AppEvents } from "../../common"; +import debounce from "just-debounce-it"; const ReactMCE = React.lazy(() => import("./tinymce")); +function updateWordCount(editor) { + if (!editor.countWords) return; + AppEventManager.publish(AppEvents.UPDATE_WORD_COUNT, editor.countWords()); +} +const debouncedUpdateWordCount = debounce(updateWordCount, 1000); + function Editor({ noteId, nonce }) { const editorRef = useRef(); const [isEditorLoading, setIsEditorLoading] = useState(true); const sessionId = useStore((store) => store.session.id); const contentType = useStore((store) => store.session.content?.type); - const setSession = useStore((store) => store.setSession); const saveSession = useStore((store) => store.saveSession); const newSession = useStore((store) => store.newSession); const openSession = useStore((store) => store.openSession); @@ -50,7 +57,6 @@ function Editor({ noteId, nonce }) { const startSession = useCallback( async function startSession(noteId) { - console.log("starting session", nonce, noteId); if (noteId === 0) newSession(nonce); else if (noteId) { await openSession(noteId); @@ -117,7 +123,6 @@ function Editor({ noteId, nonce }) { })(); }, [startSession, noteId, nonce]); - // if (!isSessionReady) return ; return ( toggleProperties(false)} onSave={saveSession} sessionId={sessionId} - initialValue={editorstore.get()?.session?.content?.data} - onChange={(content) => { + onChange={(content, editor) => { if (!content || content === "


") return; setSession((state) => { @@ -210,9 +214,10 @@ function Editor({ noteId, nonce }) { data: content, }; }); + + debouncedUpdateWordCount(editor); }} changeInterval={100} - onWordCountChanged={updateWordCount} onInit={(editor) => { editor.focus(); setTimeout(() => { diff --git a/apps/web/src/components/editor/tinymce.js b/apps/web/src/components/editor/tinymce.js index cf202c892..1c2feac2e 100644 --- a/apps/web/src/components/editor/tinymce.js +++ b/apps/web/src/components/editor/tinymce.js @@ -2,7 +2,7 @@ import React, { useEffect } from "react"; import "./editor.css"; import "@streetwritersco/tinymce-plugins/codeblock/styles.css"; import "@streetwritersco/tinymce-plugins/collapsibleheaders/styles.css"; -import "tinymce/tinymce"; +import "tinymce/tinymce.js"; // eslint-disable-next-line import/no-webpack-loader-syntax import "file-loader?name=static/js/icons/default/icons.js&esModule=false!tinymce/icons/default/icons.min.js"; // eslint-disable-next-line import/no-webpack-loader-syntax @@ -45,7 +45,7 @@ import { useIsUserPremium } from "../../hooks/use-is-user-premium"; import { AppEventManager, AppEvents } from "../../common"; import { EV, EVENTS } from "notes-core/common"; import { downloadAttachment } from "../../common/attachments"; -import debounce from "just-debounce"; +import debounce from "just-debounce-it"; const markdownPatterns = [ { start: "```", replacement: "

" },
@@ -124,17 +124,15 @@ const plugins = {
   pro: "textpattern picker",
 };
 
-const changeEvents = "change input compositionend paste";
+const changeEvents = "change keyup input compositionend paste";
 
 function TinyMCE(props) {
   const {
     changeInterval,
     onChange,
-    onWordCountChanged,
     onSave,
     placeholder,
     simple,
-    initialValue,
     onFocus,
     editorRef,
     onInit,
@@ -142,6 +140,7 @@ function TinyMCE(props) {
   } = props;
   const [oldSkin, newSkin] = useSkin();
   const isUserPremium = useIsUserPremium();
+
   const tinymceRef = editorRef;
   useEffect(() => {
     if (!tinymceRef.current.editor.dom) return;
@@ -188,7 +187,6 @@ function TinyMCE(props) {
       id={sessionId}
       ref={tinymceRef}
       onFocus={onFocus}
-      initialValue={initialValue}
       init={{
         //experimental
         keep_styles: false,
@@ -225,40 +223,46 @@ function TinyMCE(props) {
         imagetools_toolbar:
           "rotateleft rotateright | flipv fliph | alignleft aligncenter alignright",
         init_instance_callback: (editor) => {
-          editor.serializer.addTempAttr("data-progress");
-          clearTimeout(editor.changeTimeout);
           onInit && onInit(editor);
-          console.log("init");
         },
         setup: (editor) => {
-          editor.on("tap", (e) => {
+          function onTap(e) {
             if (
               e.target.classList.contains("mce-content-body") &&
               !e.target.innerText.length > 0
             ) {
               e.preventDefault();
             }
-          });
+          }
 
-          editor.on("ScrollIntoView", (e) => {
-            e.preventDefault();
-            e.elm.scrollIntoView({
-              behavior: "smooth",
-              block: "nearest",
-            });
-          });
-
-          const onEditorChange = debounce(() => {
-            if (onWordCountChanged) onWordCountChanged(editor.countWords());
+          const onEditorChange = debounce((e) => {
+            if (editor.isLoading) {
+              editor.isLoading = false;
+              return;
+            }
 
             if (!editor.getHTML) return;
-
             editor.getHTML().then((html) => {
               onChange(html, editor);
             });
           }, changeInterval);
 
+          function onScrollIntoView(e) {
+            e.preventDefault();
+            e.elm.scrollIntoView({
+              behavior: "smooth",
+              block: "nearest",
+            });
+          }
+
+          editor.on("ScrollIntoView", onScrollIntoView);
+          editor.on("tap", onTap);
           editor.on(changeEvents, onEditorChange);
+          editor.on("remove", () => {
+            editor.off("ScrollIntoView", onScrollIntoView);
+            editor.off("tap", onTap);
+            editor.off(changeEvents, onEditorChange);
+          });
         },
         toolbar_persist: true,
         toolbar_sticky: false,
diff --git a/apps/web/src/stores/editor-store.js b/apps/web/src/stores/editor-store.js
index 959464ac7..6c53b3145 100644
--- a/apps/web/src/stores/editor-store.js
+++ b/apps/web/src/stores/editor-store.js
@@ -29,7 +29,6 @@ const getDefaultSession = () => {
     context: undefined,
     color: undefined,
     dateEdited: 0,
-    totalWords: 0,
     attachments: [],
     content: {
       type: "tiny",
@@ -65,7 +64,6 @@ class EditorStore extends BaseStore {
         ...note,
         id: undefined, // NOTE: we give a session id only after the note is opened.
         content: note.content,
-        totalWords: state.session.totalWords,
         state: SESSION_STATES.unlocked,
       };
     });
@@ -108,7 +106,6 @@ class EditorStore extends BaseStore {
         ...defaultSession,
         ...note,
         content: content || defaultSession.content,
-        totalWords: state.session.totalWords,
         state: SESSION_STATES.new,
         attachments: db.attachments.ofNote(note.id, "files") || [],
       };
@@ -234,12 +231,6 @@ class EditorStore extends BaseStore {
     );
   };
 
-  updateWordCount = (count) => {
-    this.set((state) => {
-      state.session.totalWords = count;
-    });
-  };
-
   /**
    * @private internal
    * @param {Boolean} isLocked