Re-implemented SPA behaviour
authorAdam Dullage <redacted>
Tue, 14 Jun 2022 20:26:55 +0000 (21:26 +0100)
committerAdam Dullage <redacted>
Tue, 14 Jun 2022 20:26:55 +0000 (21:26 +0100)
flatnotes/src/api.js
flatnotes/src/components/App.js
flatnotes/src/components/App.vue
flatnotes/src/constants.js

index 3b3c336c33c0ee6009403270edde8d49be84e38a..4436eb867194a93f8fca84d81800c10b5e8b32b3 100644 (file)
@@ -1,5 +1,7 @@
 import axios from "axios";
+
 import * as constants from "./constants";
+import EventBus from "./eventBus";
 
 const api = axios.create();
 
@@ -25,11 +27,11 @@ api.interceptors.response.use(
       typeof error.response !== "undefined" &&
       error.response.status === 401
     ) {
-      window.open(
+      EventBus.$emit(
+        "navigate",
         `/${constants.basePaths.login}?${constants.params.redirect}=${encodeURI(
           window.location.pathname + window.location.search
-        )}`,
-        "_self"
+        )}`
       );
     }
     return Promise.reject(error);
index 8aebd2514207ba2eed86bfb3d2bf05c5ca1b1302..e6a0312cf9022763b98e04fd5cc330e3f144ac75 100644 (file)
@@ -1,8 +1,6 @@
 import { Editor } from "@toast-ui/vue-editor";
 import { Viewer } from "@toast-ui/vue-editor";
 
-import codeSyntaxHighlight from "@toast-ui/editor-plugin-code-syntax-highlight/dist/toastui-editor-plugin-code-syntax-highlight-all.js";
-
 import api from "../api";
 import * as constants from "../constants";
 import { Note, SearchResult } from "./classes";
@@ -16,28 +14,7 @@ export default {
   },
 
   data: function() {
-    return {
-      views: {
-        login: 0,
-        home: 1,
-        note: 2,
-        search: 3,
-      },
-      currentView: 1,
-      usernameInput: null,
-      passwordInput: null,
-      rememberMeInput: false,
-      notes: null,
-      searchTerm: null,
-      draftSaveTimeout: null,
-      searchResults: null,
-      currentNote: null,
-      titleInput: null,
-      initialContent: null,
-      editMode: false,
-      viewerOptions: { plugins: [codeSyntaxHighlight] },
-      editorOptions: { plugins: [codeSyntaxHighlight] },
-    };
+    return constants.dataDefaults();
   },
 
   computed: {
@@ -85,6 +62,20 @@ export default {
       this.updateDocumentTitle();
     },
 
+    navigate: function(href, e) {
+      if (e != undefined && e.ctrlKey == true) {
+        window.open(href);
+      } else {
+        history.pushState(null, "", href);
+        this.resetData();
+        this.route();
+      }
+    },
+
+    resetData: function() {
+      Object.assign(this.$data, constants.dataDefaults());
+    },
+
     updateDocumentTitle: function() {
       let pageTitleSuffix = null;
       if (this.currentView == this.views.login) {
@@ -114,7 +105,7 @@ export default {
             localStorage.setItem("token", response.data.access_token);
           }
           let redirectPath = helpers.getSearchParam(constants.params.redirect);
-          window.open(redirectPath || "/", "_self");
+          parent.navigate(redirectPath || "/");
         })
         .finally(function() {
           parent.usernameInput = null;
@@ -126,7 +117,7 @@ export default {
     logout: function() {
       sessionStorage.removeItem("token");
       localStorage.removeItem("token");
-      window.open(`/${constants.basePaths.login}`, "_self");
+      this.navigate(`/${constants.basePaths.login}`);
     },
 
     getNotes: function() {
@@ -140,11 +131,10 @@ export default {
     },
 
     search: function() {
-      window.open(
+      this.navigate(
         `/${constants.basePaths.search}?${
           constants.params.searchTerm
-        }=${encodeURI(this.searchTerm)}`,
-        "_self"
+        }=${encodeURI(this.searchTerm)}`
       );
     },
 
@@ -196,7 +186,7 @@ export default {
     toggleEditMode: function() {
       // To Edit Mode
       if (this.editMode == false) {
-        this.titleInput = this.currentNote.title;
+        this.titleInput = this.currentNote.title || "New Note";
         let draftContent = localStorage.getItem(this.currentNote.filename);
         // Draft
         if (draftContent && confirm("Do you want to resume the saved draft?")) {
@@ -304,7 +294,7 @@ export default {
       ) {
         api
           .delete(`/api/notes/${this.currentNote.filename}`)
-          .then(window.open("/", "_self"));
+          .then(this.navigate("/"));
       }
     },
 
@@ -333,7 +323,7 @@ export default {
   },
 
   created: function() {
-    EventBus.$on("logout", this.logout);
+    EventBus.$on("navigate", this.navigate);
     document.addEventListener("keydown", this.keyboardShortcuts);
 
     let token = localStorage.getItem("token");
@@ -343,4 +333,8 @@ export default {
 
     this.route();
   },
+
+  mounted: function() {
+    window.addEventListener("popstate", this.route);
+  },
 };
index 7fb9fff1be71f35332808678f91f555c02f88a01..83edafadb7374fcec04b5b339c7fd304f528c516 100644 (file)
@@ -3,7 +3,9 @@
     <div>
       <!-- Header -->
       <div class="mt-4 mb-4">
-        <h1 class="h1 clickable-link text-center"><a href="/">flatnotes</a></h1>
+        <h1 class="h1 clickable-link text-center">
+          <a href="/" @click.prevent="navigate('/', $event)">flatnotes</a>
+        </h1>
       </div>
 
       <!-- Login -->
@@ -68,7 +70,7 @@
         </button>
 
         <!-- Close -->
-        <a href="/">
+        <a href="/" @click.prevent="navigate('/')">
           <button
             v-if="currentView == 2 && editMode == false"
             type="button"
 
         <!-- Note Loaded -->
         <div v-else>
-          <h2 v-if="editMode == false" class="mb-4">{{currentNote.title}}</h2>
-          <input v-else type="text" class="h2 title-input" v-model="titleInput" />
+          <h2 v-if="editMode == false" class="mb-4">{{ currentNote.title }}</h2>
+          <input
+            v-else
+            type="text"
+            class="h2 title-input"
+            v-model="titleInput"
+          />
 
           <!-- Viewer -->
           <div class="mb-4 note">
             class="mb-5"
           >
             <p class="h5 text-center clickable-link">
-              <a v-html="result.titleHighlightsOrTitle" :href="result.href"></a>
+              <a
+                v-html="result.titleHighlightsOrTitle"
+                :href="result.href"
+                @click.prevent="navigate(result.href, $event)"
+              ></a>
             </p>
             <p
               class="text-center text-muted"
             class="text-center clickable-link mb-2"
             :key="note.filename"
           >
-            <a :href="note.href">{{ note.title }}</a>
+            <a :href="note.href" @click.prevent="navigate(note.href, $event)">{{
+              note.title
+            }}</a>
           </p>
         </div>
       </div>
index 03a6d212f337cfe9126c16662af1495cc945083b..4fb6edaba94de75db5a785503fe6582c7f5f8c2f 100644 (file)
@@ -1,3 +1,5 @@
+import codeSyntaxHighlight from "@toast-ui/editor-plugin-code-syntax-highlight/dist/toastui-editor-plugin-code-syntax-highlight-all.js";
+
 export const markdownExt = "md";
 
 // Base Paths
@@ -5,3 +7,38 @@ export const basePaths = { login: "login", note: "note", search: "search" };
 
 // Params
 export const params = { searchTerm: "term", redirect: "redirect" };
+
+// Initial State
+export const dataDefaults = function() {
+  return {
+    // Views
+    views: {
+      login: 0,
+      home: 1,
+      note: 2,
+      search: 3,
+    },
+
+    // State
+    currentView: 1,
+    editMode: false,
+    draftSaveTimeout: null,
+
+    // Login Data
+    usernameInput: null,
+    passwordInput: null,
+    rememberMeInput: false,
+
+    // Note Data
+    notes: null,
+    searchTerm: null,
+    searchResults: null,
+    currentNote: null,
+    titleInput: null,
+    initialContent: null,
+
+    // Toast UI Plugins
+    viewerOptions: { plugins: [codeSyntaxHighlight] },
+    editorOptions: { plugins: [codeSyntaxHighlight] },
+  };
+};
git clone https://git.99rst.org/PROJECT