Added note view, navbar styling & API improvements
authorAdam Dullage <redacted>
Mon, 29 Apr 2024 19:58:35 +0000 (20:58 +0100)
committerAdam Dullage <redacted>
Mon, 29 Apr 2024 19:58:35 +0000 (20:58 +0100)
client/App.vue
client/api.js
client/classes.js [new file with mode: 0644]
client/partials/NavBar.vue
client/router.js
client/views/Home.vue
client/views/LogIn.vue
client/views/Note.vue [new file with mode: 0644]

index 09d4e1ea6be49441c4e7fb9d3cbdc739aab31905..4aeaec432c9fd314cedaa18771e72f8d327fa9d6 100644 (file)
@@ -21,8 +21,8 @@ const route = useRoute();
 \r
 onBeforeMount(() => {\r
   getConfig()\r
-    .then((response) => {\r
-      globalStore.authType = response.data.authType;\r
+    .then((data) => {\r
+      globalStore.authType = data.authType;\r
     })\r
     .catch(function (error) {\r
       if (!error.handled) {\r
index d56334d3998a0504d60ccbd56e397ae23e307339..4636dd44880fa8c9968e5b9cccefc798d110a5d9 100644 (file)
@@ -1,5 +1,6 @@
 import * as constants from "./constants.js";
 
+import { Note } from "./classes.js";
 import axios from "axios";
 import { getStoredToken } from "./tokenStorage.js";
 import router from "./router.js";
@@ -40,24 +41,41 @@ api.interceptors.response.use(
   },
 );
 
-export function getConfig() {
-  return api.get("/api/config");
+export async function getConfig() {
+  try {
+    const response = await api.get("/api/config");
+    return response.data;
+  } catch (error) {
+    return Promise.reject(error);
+  }
 }
 
-export function getToken(username, password, totp) {
-  return api.post("/api/token", {
-    username: username,
-    password: totp ? password + totp : password,
-  });
+export async function getToken(username, password, totp) {
+  try {
+    const response = await api.post("/api/token", {
+      username: username,
+      password: totp ? password + totp : password,
+    });
+    return response.data.access_token;
+  } catch (response) {
+    return Promise.reject(response);
+  }
 }
 
-export function getNotes(term, sort, order, limit) {
-  return api.get("/api/search", {
-    params: {
-      term: term,
-      sort: sort,
-      order: order,
-      limit: limit,
-    },
-  });
+export async function getNotes(term, sort, order, limit) {
+  try {
+    const response = await api.get("/api/search", {
+      params: {
+        term: term,
+        sort: sort,
+        order: order,
+        limit: limit,
+      },
+    });
+    return response.data.map(
+      (note) => new Note(note.title, note.lastModified, note.content),
+    );
+  } catch (response) {
+    return Promise.reject(response);
+  }
 }
diff --git a/client/classes.js b/client/classes.js
new file mode 100644 (file)
index 0000000..84f7679
--- /dev/null
@@ -0,0 +1,49 @@
+import router from "./router.js";
+
+class Note {
+  constructor(title, lastModified, content) {
+    this.title = title;
+    this.lastModified = lastModified;
+    this.content = content;
+  }
+
+  get href() {
+    return `${router.resolve({ name: "note", params: { title: this.title } }).href}`;
+  }
+
+  get lastModifiedAsDate() {
+    return new Date(this.lastModified * 1000);
+  }
+
+  get lastModifiedAsString() {
+    return this.lastModifiedAsDate.toLocaleString();
+  }
+}
+
+class SearchResult extends Note {
+  constructor(searchResult) {
+    super(searchResult.title, searchResult.lastModified);
+    this.score = searchResult.score;
+    this.titleHighlights = searchResult.titleHighlights;
+    this.contentHighlights = searchResult.contentHighlights;
+    this.tagMatches = searchResult.tagMatches;
+  }
+
+  get titleHighlightsOrTitle() {
+    return this.titleHighlights ? this.titleHighlights : this.title;
+  }
+
+  get includesHighlights() {
+    if (
+      this.titleHighlights ||
+      this.contentHighlights ||
+      (this.tagMatches != null && this.tagMatches.length)
+    ) {
+      return true;
+    } else {
+      return false;
+    }
+  }
+}
+
+export { Note, SearchResult };
index 778a7d33f9458cb679c5e1d1f0fde38a2aab3826..f7a55154efe3a6d401752e91aedbc0bfc3c032c7 100644 (file)
@@ -1,9 +1,9 @@
 <template>\r
-  <nav class="flex justify-between">\r
-    <RouterLink to="/">\r
-      <Logo :class="{ invisible: hideLogo }"></Logo>\r
+  <nav class="mb-4 flex justify-between align-top">\r
+    <RouterLink to="/" v-if="!hideLogo">\r
+      <Logo></Logo>\r
     </RouterLink>\r
-    <div class="flex">\r
+    <div class="flex grow items-start justify-end">\r
       <CustomButton\r
         :iconPath="mdilPlusCircle"\r
         label="New Note"\r
@@ -20,7 +20,7 @@ import { RouterLink, useRouter } from "vue-router";
 \r
 import CustomButton from "../components/CustomButton.vue";\r
 import Logo from "../components/Logo.vue";\r
-import { clearStoredToken } from "../tokenStorage.js"\r
+import { clearStoredToken } from "../tokenStorage.js";\r
 \r
 const router = useRouter();\r
 \r
index 4c14398d424ec85791f83d21ec4853d645672562..6ddb090d5abfbf4d470e9014cd0e1f8f14cc5aa8 100644 (file)
@@ -1,3 +1,5 @@
+import * as constants from "./constants.js";\r
+\r
 import { createRouter, createWebHistory } from "vue-router";\r
 \r
 const router = createRouter({\r
@@ -12,6 +14,13 @@ const router = createRouter({
       path: "/login",\r
       name: "login",\r
       component: () => import("./views/LogIn.vue"),\r
+      props: (route) => ({ [constants.params.redirect]: route.query.redirect }),\r
+    },\r
+    {\r
+      path: "/note/:title",\r
+      name: "note",\r
+      component: () => import("./views/Note.vue"),\r
+      props: true,\r
     },\r
   ],\r
 });\r
index d5c140c99fb25ebf4b0135cb6136d3d0b9a0b06e..0d3c85a1b62417cf3129306a0ebed755b2e6d44a 100644 (file)
@@ -10,7 +10,9 @@
         >
           RECENTLY MODIFIED
         </p>
-        <CustomButton v-for="note in notes" :label="note.title" />
+        <RouterLink v-for="note in notes" :to="note.href">
+          <CustomButton :label="note.title" />
+        </RouterLink>
       </div>
     </div>
   </div>
@@ -23,10 +25,11 @@ import { getNotes } from "../api.js";
 import CustomButton from "../components/CustomButton.vue";
 import Logo from "../components/Logo.vue";
 import SearchInput from "../partials/SearchInput.vue";
+import { RouterLink } from "vue-router";
 
 const notes = ref([]);
 
-getNotes("*", "lastModified", "desc", 5).then((response) => {
-  notes.value = response.data;
+getNotes("*", "lastModified", "desc", 5).then((data) => {
+  notes.value = data;
 });
 </script>
index abd887cb385b910728a64054d9010509e2e55c3a..b22c778706bf98381bd04f464e5f6d49bda2e1be 100644 (file)
@@ -49,7 +49,8 @@ import TextInput from "../components/TextInput.vue";
 import { authTypes } from "../constants.js";
 import { useGlobalStore } from "../globalStore.js";
 import { storeToken } from "../tokenStorage.js";
-import * as constants from "../constants.js";
+
+const props = defineProps({ redirect: String });
 
 const globalStore = useGlobalStore();
 const router = useRouter();
@@ -63,24 +64,24 @@ const rememberMe = ref(false);
 
 function logIn() {
   getToken(username.value, password.value, totp.value)
-    .then((response) => {
-      storeToken(response.data.access_token, rememberMe.value);
-      const redirectPath = route.query[constants.params.redirect];
-      if (redirectPath) {
-        router.push(redirectPath);
+    .then((access_token) => {
+      storeToken(access_token, rememberMe.value);
+      if (props.redirect) {
+        router.push(props.redirect);
       } else {
         router.push({ name: "home" });
       }
     })
-    .catch((error) => {
+    .catch((response) => {
       username.value = "";
       password.value = "";
       totp.value = "";
 
-      if (error.response?.status === 401) {
+      if (response.response?.status === 401) {
         toast.add({
           summary: "Login Failed",
           detail: "Please check your credentials and try again.",
+          severity: "error",
           closable: false,
           life: 5000,
         });
diff --git a/client/views/Note.vue b/client/views/Note.vue
new file mode 100644 (file)
index 0000000..97011b2
--- /dev/null
@@ -0,0 +1,9 @@
+<template>
+  {{ title }}
+</template>
+
+<script setup>
+defineProps({
+  title: String,
+});
+</script>
git clone https://git.99rst.org/PROJECT