Redesign search results page
authorAdam Dullage <redacted>
Wed, 17 Aug 2022 17:46:45 +0000 (18:46 +0100)
committerAdam Dullage <redacted>
Wed, 17 Aug 2022 17:46:45 +0000 (18:46 +0100)
flatnotes/src/components/App.js
flatnotes/src/components/App.vue
flatnotes/src/components/LoadingIndicator.vue
flatnotes/src/components/NoteViewerEditor.vue
flatnotes/src/components/SearchInput.vue
flatnotes/src/components/SearchResults.vue [new file with mode: 0644]
flatnotes/src/constants.js
flatnotes/src/index.html
flatnotes/src/main.scss

index 8aabe52baa8944c3218f3f000d7bde0e2c96c3e3..813b5eff82d39ecc193e1da663c3d2719d615589 100644 (file)
@@ -10,8 +10,7 @@ import NavBar from "./NavBar";
 import NoteViewerEditor from "./NoteViewerEditor";
 import RecentlyModified from "./RecentlyModified";
 import SearchInput from "./SearchInput";
-import { SearchResult } from "../classes";
-import api from "../api";
+import SearchResults from "./SearchResults";
 
 export default {
   name: "App",
@@ -24,10 +23,22 @@ export default {
     SearchInput,
     Logo,
     NoteViewerEditor,
+    SearchResults,
   },
 
   data: function() {
-    return constants.dataDefaults();
+    return {
+      views: {
+        login: 0,
+        home: 1,
+        note: 2,
+        search: 3,
+      },
+      currentView: 1,
+
+      noteTitle: null,
+      searchTerm: null,
+    };
   },
 
   methods: {
@@ -50,7 +61,6 @@ export default {
       else if (basePath == constants.basePaths.search) {
         this.updateDocumentTitle("Search");
         this.searchTerm = helpers.getSearchParam(constants.params.searchTerm);
-        this.getSearchResults();
         this.currentView = this.views.search;
       }
 
@@ -79,15 +89,12 @@ export default {
         window.open(href);
       } else {
         history.pushState(null, "", href);
-        this.resetData();
+        this.noteTitle = null;
+        this.searchTerm = null;
         this.route();
       }
     },
 
-    resetData: function() {
-      Object.assign(this.$data, constants.dataDefaults());
-    },
-
     updateDocumentTitle: function(suffix) {
       window.document.title = (suffix ? `${suffix} - ` : "") + "flatnotes";
     },
@@ -98,38 +105,6 @@ export default {
       this.navigate(`/${constants.basePaths.login}`);
     },
 
-    getSearchResults: function() {
-      let parent = this;
-      this.searchFailed = false;
-      api
-        .get("/api/search", { params: { term: this.searchTerm } })
-        .then(function(response) {
-          parent.searchResults = [];
-          if (response.data.length == 0) {
-            parent.searchFailedIcon = "search";
-            parent.searchFailedMessage = "No Results";
-            parent.searchFailed = true;
-          } else {
-            response.data.forEach(function(result) {
-              parent.searchResults.push(
-                new SearchResult(
-                  result.title,
-                  result.lastModified,
-                  result.titleHighlights,
-                  result.contentHighlights
-                )
-              );
-            });
-          }
-        })
-        .catch(function(error) {
-          if (!error.handled) {
-            parent.searchFailed = true;
-            parent.unhandledServerErrorToast();
-          }
-        });
-    },
-
     newNote: function() {
       this.navigate(`/${constants.basePaths.new}`);
     },
@@ -143,11 +118,13 @@ export default {
     },
 
     focusSearchInput: function() {
-      document.getElementById("search-input").focus();
+      let input = document.getElementById("search-input");
+      input.focus();
+      input.select();
     },
 
     openSearch: function() {
-      if (this.currentView == this.views.home) {
+      if ([this.views.home, this.views.search].includes(this.currentView)) {
         this.focusSearchInput();
         EventBus.$emit("highlight-search-input");
       } else if (this.currentView != this.views.login) {
index fc90a094e3bfa89d2f1a9d807d6b1fc746d22c9b..9667281b11b29cf02ed87038d5e1007f40664b99 100644 (file)
@@ -11,7 +11,7 @@
     <!-- Nav Bar -->
     <NavBar
       v-if="currentView != views.login"
-      class="mt-3 w-100"
+      class="w-100 mb-5"
       :show-logo="currentView != views.home"
       @navigate-home="navigate('/')"
       @new-note="newNote()"
       "
     >
       <Logo class="mb-3"></Logo>
-      <SearchInput class="search-input mb-4"></SearchInput>
+      <SearchInput
+        :initial-value="searchTerm"
+        class="search-input mb-4"
+      ></SearchInput>
       <RecentlyModified class="recently-modified"></RecentlyModified>
     </div>
 
     <!-- Search Results -->
-    <div v-if="currentView == views.search" class="w-100 pt-5 flex-grow-1">
-      <!-- Searching -->
-      <div
-        v-if="searchResults == null || searchResults.length == 0"
-        class="h-100 d-flex flex-column justify-content-center"
-      >
-        <LoadingIndicator
-          :failed="searchFailed"
-          :failedBootstrapIcon="searchFailedIcon"
-          :failedMessage="searchFailedMessage"
-        />
-      </div>
-
-      <!-- Search Results Loaded -->
-      <div v-else>
-        <div v-for="result in searchResults" :key="result.title" class="mb-5">
-          <p class="h5 text-center clickable-link">
-            <a
-              v-html="result.titleHighlightsOrTitle"
-              :href="result.href"
-              @click.prevent="navigate(result.href, $event)"
-            ></a>
-          </p>
-          <p
-            class="text-center text-muted"
-            v-html="result.contentHighlights"
-          ></p>
-        </div>
-      </div>
+    <div
+      v-if="currentView == views.search"
+      class="flex-grow-1 search-results-view"
+    >
+      <SearchInput
+        :initial-value="searchTerm"
+        class="search-input mb-4"
+      ></SearchInput>
+      <SearchResults :search-term="searchTerm" class="h-100"></SearchResults>
     </div>
 
     <!-- Note -->
index 00ac052145fb17c74da1c1cd745433125bf6892a..39195440a8e35ee73d440582249cdedade5d72f7 100644 (file)
@@ -2,7 +2,10 @@
   <div>
     <div v-if="showLoader && !failed" class="loader"></div>
     <div v-else-if="failed" class="d-flex flex-column align-items-center">
-      <b-icon class="failed-icon mb-3" :icon="failedBootstrapIcon"></b-icon>
+      <b-icon
+        class="failed-icon mb-3"
+        :icon="failedBootstrapIcon || 'cloud-slash'"
+      ></b-icon>
       <p>{{ failedMessage }}</p>
     </div>
   </div>
 <style lang="scss" scoped>
 @import "../colours";
 
+p {
+  color: $muted-text;
+}
+
 .failed-icon {
   color: $logo-key-colour;
   font-size: 60px;
@@ -82,7 +89,7 @@ export default {
   props: {
     showLoader: { type: Boolean, default: true },
     failed: { type: Boolean },
-    failedBootstrapIcon: { type: String, default: "cloud-slash" },
+    failedBootstrapIcon: { type: String },
     failedMessage: { type: String, default: "Loading Failed" },
   },
 };
index 800e8d1d779328a200511f26db65a3ef82e887e4..00d027f6145cd3fb52ae978af35cb008b0a3d4e7 100644 (file)
@@ -1,6 +1,6 @@
 <template>
   <!-- Note -->
-  <div class="pt-5 pb-4">
+  <div class="pb-4">
     <!-- Loading -->
     <div
       v-if="currentNote == null"
index 63429dc6c29c56fdc7b6e96c6920fdb647d3c1a2..22749d66a7feb626a30fbd3ff8f1992d5037951c 100644 (file)
@@ -49,6 +49,8 @@ import * as constants from "../constants";
 import EventBus from "../eventBus";
 
 export default {
+  props: { initialValue: { type: String } },
+
   data: function () {
     return {
       searchTermInput: null,
@@ -56,6 +58,12 @@ export default {
     };
   },
 
+  watch: {
+    initialValue: function () {
+      this.init();
+    },
+  },
+
   methods: {
     search: function () {
       if (this.searchTermInput) {
@@ -84,10 +92,15 @@ export default {
         parent.includeHighlightClass = false;
       }, 1500);
     },
+
+    init: function () {
+      this.searchTermInput = this.initialValue;
+    },
   },
 
   created: function () {
     EventBus.$on("highlight-search-input", this.highlightSearchInput);
+    this.init();
   },
 };
 </script>
diff --git a/flatnotes/src/components/SearchResults.vue b/flatnotes/src/components/SearchResults.vue
new file mode 100644 (file)
index 0000000..96e1bf0
--- /dev/null
@@ -0,0 +1,119 @@
+<template>
+  <div>
+    <!-- Searching -->
+    <div
+      v-if="searchResults == null || searchResults.length == 0"
+      class="h-100 d-flex flex-column justify-content-center"
+    >
+      <LoadingIndicator
+        :failed="searchFailed"
+        :failedBootstrapIcon="searchFailedIcon"
+        :failedMessage="searchFailedMessage"
+      />
+    </div>
+
+    <!-- Search Results Loaded -->
+    <div v-else>
+      <div v-for="result in searchResults" :key="result.title" class="mb-4">
+        <p class="font-weight-bold mb-0">
+          <a
+            v-html="result.titleHighlightsOrTitle"
+            :href="result.href"
+            @click.prevent="openNote(result.href)"
+          ></a>
+        </p>
+        <p class="result-contents" v-html="result.contentHighlights"></p>
+      </div>
+    </div>
+  </div>
+</template>
+
+<style lang="scss">
+@import "../colours";
+
+.result-contents {
+  color: $muted-text;
+}
+.match {
+  font-weight: bold;
+  color: $logo-key-colour;
+}
+</style>
+
+<script>
+import EventBus from "../eventBus";
+import LoadingIndicator from "./LoadingIndicator";
+import { SearchResult } from "../classes";
+import api from "../api";
+
+export default {
+  components: {
+    LoadingIndicator,
+  },
+
+  props: {
+    searchTerm: { type: String, required: true },
+  },
+
+  data: function () {
+    return {
+      searchFailed: false,
+      searchFailedMessage: "Failed to load Search Results",
+      searchFailedIcon: null,
+      searchResults: null,
+    };
+  },
+
+  watch: {
+    searchTerm: function () {
+      this.init();
+    },
+  },
+
+  methods: {
+    getSearchResults: function () {
+      let parent = this;
+      this.searchFailed = false;
+      api
+        .get("/api/search", { params: { term: this.searchTerm } })
+        .then(function (response) {
+          parent.searchResults = [];
+          if (response.data.length == 0) {
+            parent.searchFailedIcon = "search";
+            parent.searchFailedMessage = "No Results";
+            parent.searchFailed = true;
+          } else {
+            response.data.forEach(function (result) {
+              parent.searchResults.push(
+                new SearchResult(
+                  result.title,
+                  result.lastModified,
+                  result.titleHighlights,
+                  result.contentHighlights
+                )
+              );
+            });
+          }
+        })
+        .catch(function (error) {
+          if (!error.handled) {
+            parent.searchFailed = true;
+            EventBus.$emit("unhandledServerError");
+          }
+        });
+    },
+
+    openNote: function (href) {
+      EventBus.$emit("navigate", href);
+    },
+
+    init: function () {
+      this.getSearchResults();
+    },
+  },
+
+  created: function () {
+    this.init();
+  },
+};
+</script>
index 93e2596ddf738a3c652f09714ffd80205d007993..92fb975bee34fc1cf89e434611fd845008fb5394 100644 (file)
@@ -8,29 +8,3 @@ export const basePaths = {
 
 // 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,
-
-    // Note Data
-    noteTitle: null,
-
-    // Search Result Data
-    searchFailed: false,
-    searchFailedMessage: "Failed to load Search Results",
-    searchFailedIcon: null,
-    searchTerm: null,
-    searchResults: null,
-  };
-};
index bb33b6452b655bdbfb26d4a1b1f4ccfe42e922cf..a15291e410b975adc14bc95b923818727a0e1fec 100644 (file)
@@ -20,7 +20,7 @@
 
 </head>
 
-<body class="h-100">
+<body class="h-100 pt-3">
   <div id="app"></div>
   <script src="index.js"></script>
 </body>
index 2174eb0b974244829ea8b26bd2eddfc5af181ae1..1675c9bf4cb1c4c47af07f70ca5599516515d85d 100644 (file)
@@ -17,14 +17,11 @@ body {
   font-family: "Inter", sans-serif;
 }
 
-.clickable-link {
-  cursor: pointer;
+a {
+  color: inherit;
   &:hover {
-    font-weight: bold;
-  }
-  a {
     text-decoration: none;
-    color: inherit;
+    filter: opacity(70%);
   }
 }
 
@@ -37,6 +34,10 @@ body {
   max-width: 500px;
 }
 
+.search-results-view {
+  max-width: 700px;
+}
+
 .search-input {
   box-shadow: 0 0 20px $drop-shadow;
 }
@@ -58,5 +59,5 @@ body {
   }
 }
 .bttn:hover {
-  background-color: $button-background
+  background-color: $button-background;
 }
git clone https://git.99rst.org/PROJECT