Vue 3
authorAdam Dullage <redacted>
Tue, 23 Apr 2024 07:19:47 +0000 (08:19 +0100)
committerAdam Dullage <redacted>
Tue, 23 Apr 2024 07:19:47 +0000 (08:19 +0100)
38 files changed:
client/App.vue [new file with mode: 0644]
client/api.js [deleted file]
client/assets/apple-touch-icon.png [deleted file]
client/assets/favicon-16x16.png [deleted file]
client/assets/favicon-32x32.png [deleted file]
client/assets/favicon.ico [deleted file]
client/assets/fonts/Poppins/OFL.txt [deleted file]
client/assets/fonts/Poppins/Poppins-Regular.ttf [deleted file]
client/autolinkParsers.js [deleted file]
client/classes.js [deleted file]
client/colours.scss [deleted file]
client/components/App.js [deleted file]
client/components/App.vue [deleted file]
client/components/LoadingIndicator.vue [deleted file]
client/components/Login.vue [deleted file]
client/components/Logo.vue [deleted file]
client/components/NavBar.vue [deleted file]
client/components/NoteViewerEditor.vue [deleted file]
client/components/RecentlyModified.vue [deleted file]
client/components/SearchInput.vue [deleted file]
client/components/SearchResults.vue [deleted file]
client/constants.js [deleted file]
client/eventBus.js [deleted file]
client/global.scss [deleted file]
client/helpers.js [deleted file]
client/index.html
client/index.js
client/public/android-chrome-192x192.png [deleted file]
client/public/android-chrome-512x512.png [deleted file]
client/public/safari-pinned-tab.svg [deleted file]
client/router.js [new file with mode: 0644]
client/site.webmanifest [deleted file]
client/toastui-editor-overrides.scss [deleted file]
client/tokenStorage.js [deleted file]
client/views/HomeView.vue [new file with mode: 0644]
package-lock.json
package.json
vite.config.js

diff --git a/client/App.vue b/client/App.vue
new file mode 100644 (file)
index 0000000..dcd008a
--- /dev/null
@@ -0,0 +1,7 @@
+<script setup>
+import { RouterView } from "vue-router";
+</script>
+
+<template>
+  <RouterView />
+</template>
diff --git a/client/api.js b/client/api.js
deleted file mode 100644 (file)
index c9ffba7..0000000
+++ /dev/null
@@ -1,44 +0,0 @@
-import * as constants from "./constants.js";
-
-import EventBus from "./eventBus.js";
-import axios from "axios";
-import { getToken } from "./tokenStorage.js";
-
-const api = axios.create();
-
-api.interceptors.request.use(
-  function (config) {
-    if (config.url !== "/api/token") {
-      const token = getToken();
-      config.headers.Authorization = `Bearer ${token}`;
-    }
-    return config;
-  },
-  function (error) {
-    return Promise.reject(error);
-  }
-);
-
-api.interceptors.response.use(
-  function (response) {
-    return response;
-  },
-  function (error) {
-    if (
-      typeof error.response !== "undefined" &&
-      error.response.status === 401 &&
-      error.response.config.url !== "/api/token"
-    ) {
-      EventBus.$emit(
-        "navigate",
-        `${constants.basePaths.login}?${constants.params.redirect}=${encodeURI(
-          window.location.pathname + window.location.search
-        )}`
-      );
-      error.handled = true;
-    }
-    return Promise.reject(error);
-  }
-);
-
-export default api;
diff --git a/client/assets/apple-touch-icon.png b/client/assets/apple-touch-icon.png
deleted file mode 100644 (file)
index ca88d02..0000000
Binary files a/client/assets/apple-touch-icon.png and /dev/null differ
diff --git a/client/assets/favicon-16x16.png b/client/assets/favicon-16x16.png
deleted file mode 100644 (file)
index 654c02c..0000000
Binary files a/client/assets/favicon-16x16.png and /dev/null differ
diff --git a/client/assets/favicon-32x32.png b/client/assets/favicon-32x32.png
deleted file mode 100644 (file)
index f4e5ae6..0000000
Binary files a/client/assets/favicon-32x32.png and /dev/null differ
diff --git a/client/assets/favicon.ico b/client/assets/favicon.ico
deleted file mode 100644 (file)
index 675d561..0000000
Binary files a/client/assets/favicon.ico and /dev/null differ
diff --git a/client/assets/fonts/Poppins/OFL.txt b/client/assets/fonts/Poppins/OFL.txt
deleted file mode 100644 (file)
index 76df3b5..0000000
+++ /dev/null
@@ -1,93 +0,0 @@
-Copyright 2020 The Poppins Project Authors (https://github.com/itfoundry/Poppins)
-
-This Font Software is licensed under the SIL Open Font License, Version 1.1.
-This license is copied below, and is also available with a FAQ at:
-http://scripts.sil.org/OFL
-
-
------------------------------------------------------------
-SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
------------------------------------------------------------
-
-PREAMBLE
-The goals of the Open Font License (OFL) are to stimulate worldwide
-development of collaborative font projects, to support the font creation
-efforts of academic and linguistic communities, and to provide a free and
-open framework in which fonts may be shared and improved in partnership
-with others.
-
-The OFL allows the licensed fonts to be used, studied, modified and
-redistributed freely as long as they are not sold by themselves. The
-fonts, including any derivative works, can be bundled, embedded, 
-redistributed and/or sold with any software provided that any reserved
-names are not used by derivative works. The fonts and derivatives,
-however, cannot be released under any other type of license. The
-requirement for fonts to remain under this license does not apply
-to any document created using the fonts or their derivatives.
-
-DEFINITIONS
-"Font Software" refers to the set of files released by the Copyright
-Holder(s) under this license and clearly marked as such. This may
-include source files, build scripts and documentation.
-
-"Reserved Font Name" refers to any names specified as such after the
-copyright statement(s).
-
-"Original Version" refers to the collection of Font Software components as
-distributed by the Copyright Holder(s).
-
-"Modified Version" refers to any derivative made by adding to, deleting,
-or substituting -- in part or in whole -- any of the components of the
-Original Version, by changing formats or by porting the Font Software to a
-new environment.
-
-"Author" refers to any designer, engineer, programmer, technical
-writer or other person who contributed to the Font Software.
-
-PERMISSION & CONDITIONS
-Permission is hereby granted, free of charge, to any person obtaining
-a copy of the Font Software, to use, study, copy, merge, embed, modify,
-redistribute, and sell modified and unmodified copies of the Font
-Software, subject to the following conditions:
-
-1) Neither the Font Software nor any of its individual components,
-in Original or Modified Versions, may be sold by itself.
-
-2) Original or Modified Versions of the Font Software may be bundled,
-redistributed and/or sold with any software, provided that each copy
-contains the above copyright notice and this license. These can be
-included either as stand-alone text files, human-readable headers or
-in the appropriate machine-readable metadata fields within text or
-binary files as long as those fields can be easily viewed by the user.
-
-3) No Modified Version of the Font Software may use the Reserved Font
-Name(s) unless explicit written permission is granted by the corresponding
-Copyright Holder. This restriction only applies to the primary font name as
-presented to the users.
-
-4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
-Software shall not be used to promote, endorse or advertise any
-Modified Version, except to acknowledge the contribution(s) of the
-Copyright Holder(s) and the Author(s) or with their explicit written
-permission.
-
-5) The Font Software, modified or unmodified, in part or in whole,
-must be distributed entirely under this license, and must not be
-distributed under any other license. The requirement for fonts to
-remain under this license does not apply to any document created
-using the Font Software.
-
-TERMINATION
-This license becomes null and void if any of the above conditions are
-not met.
-
-DISCLAIMER
-THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
-EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
-MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
-OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
-COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
-INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
-DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
-FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
-OTHER DEALINGS IN THE FONT SOFTWARE.
diff --git a/client/assets/fonts/Poppins/Poppins-Regular.ttf b/client/assets/fonts/Poppins/Poppins-Regular.ttf
deleted file mode 100644 (file)
index 9f0c71b..0000000
Binary files a/client/assets/fonts/Poppins/Poppins-Regular.ttf and /dev/null differ
diff --git a/client/autolinkParsers.js b/client/autolinkParsers.js
deleted file mode 100644 (file)
index 961926e..0000000
+++ /dev/null
@@ -1,98 +0,0 @@
-import { basePaths } from "./constants.js";
-
-function parseWikiLink(source) {
-  const matched = source.matchAll(/\[\[(.*)\]\]/g);
-  if (matched) {
-    return Array.from(matched).map((match) => {
-      const text = match[1];
-      return {
-        text,
-        url: encodeURI(`${basePaths.note}/${text.trim()}`),
-        range: [match.index, match.index + match[0].length - 1],
-      };
-    });
-  }
-
-  return null;
-}
-
-export function extendedAutolinks(source) {
-  return [
-    ...parseUrlLink(source),
-    ...parseEmailLink(source),
-    ...parseWikiLink(source),
-  ].sort((a, b) => a.range[0] - b.range[0]);
-}
-
-/*
- * Sourced from toast-ui. There autolink options are
- * either override their built in functionality or
- * use their built in functionality. We'd like to have
- * both so this is the source of their parsers.
- */
-const DOMAIN = '(?:[w-]+.)*[A-Za-z0-9-]+.[A-Za-z0-9-]+';
-const PATH = '[^<\\s]*[^<?!.,:*_?~\\s]';
-const EMAIL = '[\\w.+-]+@(?:[\\w-]+\\.)+[\\w-]+';
-function trimUnmatchedTrailingParens(source) {
-  const trailingParen = /\)+$/.exec(source);
-  if (trailingParen) {
-    let count = 0;
-    for (const ch of source) {
-      if (ch === '(') {
-        if (count < 0) {
-          count = 1;
-        } else {
-          count += 1;
-        }
-      } else if (ch === ')') {
-        count -= 1;
-      }
-    }
-
-    if (count < 0) {
-      const trimCount = Math.min(-count, trailingParen[0].length);
-      return source.substring(0, source.length - trimCount);
-    }
-  }
-  return source;
-}
-
-function trimTrailingEntity(source) {
-  return source.replace(/&[A-Za-z0-9]+;$/, '');
-}
-export function parseEmailLink(source) {
-  const reEmailLink = new RegExp(EMAIL, 'g');
-  const result = [];
-  let m;
-  while ((m = reEmailLink.exec(source))) {
-    const text = m[0];
-    if (!/[_-]+$/.test(text)) {
-      result.push({
-        text,
-        range: [m.index, m.index + text.length - 1],
-        url: `mailto:${text}`,
-      });
-    }
-  }
-
-  return result;
-}
-
-export function parseUrlLink(source) {
-  const reWwwAutolink = new RegExp(`(www|https?://)\.${DOMAIN}${PATH}`, 'g');
-  const result = [];
-  let m;
-
-  while ((m = reWwwAutolink.exec(source))) {
-    const text = trimTrailingEntity(trimUnmatchedTrailingParens(m[0]));
-    const scheme = m[1] === 'www' ? 'http://' : '';
-    result.push({
-      text,
-      range: [m.index, m.index + text.length - 1],
-      url: `${scheme}${text}`,
-    });
-  }
-
-  return result;
-}
-// end of raw toast-ui source
diff --git a/client/classes.js b/client/classes.js
deleted file mode 100644 (file)
index 8366007..0000000
+++ /dev/null
@@ -1,49 +0,0 @@
-import * as constants from "./constants.js";
-
-class Note {
-  constructor(title, lastModified, content) {
-    this.title = title;
-    this.lastModified = lastModified;
-    this.content = content;
-  }
-
-  get href() {
-    return `${constants.basePaths.note}/${encodeURIComponent(this.title)}`;
-  }
-
-  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 };
diff --git a/client/colours.scss b/client/colours.scss
deleted file mode 100644 (file)
index 7df0e73..0000000
+++ /dev/null
@@ -1,47 +0,0 @@
-$black: #000000;
-$white: #ffffff;
-
-$neutral-15: #22262C;
-$neutral-20: #2C3139;
-$neutral-45: #5E6B80;
-$neutral-60: #8891A1;
-$neutral-80: #C1C7D0;
-$neutral-94: #ECEEF0;
-$neutral-96: #F3F4F5;
-$neutral-99: #FCFCFD;
-
-$blue-96: #EFF3FF;
-
-$orange-75: #F8A66B;
-
-body {
-  --colour-brand: #{$orange-75};
-
-  --colour-background: #{$white};
-  --colour-background-elevated: #{$white};
-  --colour-background-tint: #{$neutral-96};
-  --colour-background-highlight: #{$blue-96};
-
-  --colour-text: #{$neutral-20};
-  --colour-text-muted: #{$neutral-60};
-  --colour-text-very-muted: #{$neutral-80};
-
-  --colour-shadow: #{$neutral-94};
-  --colour-border: #{$neutral-94};
-}
-
-body.dark-theme {
-  // --colour-brand: #{$orange-75};
-
-  --colour-background: #{$neutral-15};
-  --colour-background-elevated: #{$neutral-20};
-  --colour-background-tint: #{$neutral-15};
-  --colour-background-highlight: #{$neutral-60};
-
-  --colour-text: #{$neutral-80};
-  --colour-text-muted: #{$neutral-60};
-  --colour-text-very-muted: #{$neutral-45};
-
-  --colour-shadow: none;
-  --colour-border: #{$neutral-45};
-}
diff --git a/client/components/App.js b/client/components/App.js
deleted file mode 100644 (file)
index e42e496..0000000
+++ /dev/null
@@ -1,225 +0,0 @@
-import * as constants from "../constants.js";
-import * as helpers from "../helpers.js";
-
-import { clearToken, loadToken } from "../tokenStorage.js";
-
-import EventBus from "../eventBus.js";
-import LoadingIndicator from "./LoadingIndicator.vue";
-import Login from "./Login.vue";
-import Logo from "./Logo.vue";
-import Mousetrap from "mousetrap";
-import NavBar from "./NavBar.vue";
-import NoteViewerEditor from "./NoteViewerEditor.vue";
-import RecentlyModified from "./RecentlyModified.vue";
-import SearchInput from "./SearchInput.vue";
-import SearchResults from "./SearchResults.vue";
-import api from "../api.js";
-
-export default {
-  name: "App",
-
-  components: {
-    LoadingIndicator,
-    Login,
-    NavBar,
-    SearchInput,
-    Logo,
-    NoteViewerEditor,
-    SearchResults,
-    RecentlyModified,
-  },
-
-  data: function () {
-    return {
-      authType: null,
-
-      views: {
-        login: 0,
-        home: 1,
-        note: 2,
-        search: 3,
-      },
-      currentView: 1,
-
-      noteTitle: null,
-      searchTerm: null,
-      darkTheme: false,
-    };
-  },
-
-  watch: {
-    darkTheme: function () {
-      if (this.darkTheme) {
-        document.body.classList.add("dark-theme");
-      } else {
-        document.body.classList.remove("dark-theme");
-      }
-    },
-  },
-
-  methods: {
-    loadConfig: function () {
-      let parent = this;
-      api
-        .get("/api/config")
-        .then(function (response) {
-          parent.authType = response.data.authType;
-        })
-        .catch(function (error) {
-          if (!error.handled) {
-            parent.unhandledServerErrorToast();
-          }
-        });
-    },
-
-    route: function () {
-      let path = window.location.pathname.split("/");
-      let basePath = `/${path[1]}`;
-
-      this.$bvModal.hide("search-modal");
-
-      // Home Page
-      if (basePath == constants.basePaths.home) {
-        this.updateDocumentTitle();
-        this.currentView = this.views.home;
-        this.$nextTick(function () {
-          this.focusSearchInput();
-        });
-      }
-
-      // Search
-      else if (basePath == constants.basePaths.search) {
-        this.updateDocumentTitle("Search");
-        this.searchTerm = helpers.getSearchParam(constants.params.searchTerm);
-        this.currentView = this.views.search;
-      }
-
-      // New Note
-      else if (basePath == constants.basePaths.new) {
-        this.updateDocumentTitle("New Note");
-        this.currentView = this.views.note;
-      }
-
-      // Note
-      else if (basePath == constants.basePaths.note) {
-        this.noteTitle = decodeURIComponent(path[2]);
-        this.updateDocumentTitle(this.noteTitle);
-        this.currentView = this.views.note;
-      }
-
-      // Login
-      else if (basePath == constants.basePaths.login) {
-        this.updateDocumentTitle("Log In");
-        this.currentView = this.views.login;
-      }
-    },
-
-    navigate: function (href, e) {
-      if (e != undefined && e.ctrlKey == true) {
-        window.open(href);
-      } else {
-        history.pushState(null, "", href);
-        this.noteTitle = null;
-        this.searchTerm = null;
-        this.route();
-      }
-    },
-
-    updateDocumentTitle: function (suffix) {
-      window.document.title = (suffix ? `${suffix} - ` : "") + "flatnotes";
-    },
-
-    logout: function () {
-      clearToken();
-      this.navigate(constants.basePaths.login);
-    },
-
-    showToast: function (variant, message, title = null) {
-      const options = {
-        variant: variant,
-        noCloseButton: true,
-        toaster: "b-toaster-bottom-right",
-      };
-      if (title != null) {
-        options.title = title;
-      }
-      this.$bvToast.toast(message, options);
-    },
-
-    focusSearchInput: function () {
-      let input = document.getElementById("search-input");
-      input.focus();
-      input.select();
-    },
-
-    openSearch: function () {
-      if ([this.views.home, this.views.search].includes(this.currentView)) {
-        this.focusSearchInput();
-        EventBus.$emit("highlight-search-input");
-      } else if (this.currentView != this.views.login) {
-        this.$bvModal.show("search-modal");
-      }
-    },
-
-    unhandledServerErrorToast: function () {
-      this.showToast(
-        "danger",
-        "Unknown error communicating with the server. Please try again.",
-        "Unknown Error"
-      );
-    },
-
-    toggleTheme: function () {
-      this.darkTheme = !this.darkTheme;
-      localStorage.setItem("darkTheme", this.darkTheme);
-    },
-
-    updateNoteTitle: function (title) {
-      this.noteTitle = title;
-      this.updateDocumentTitle(title);
-    },
-  },
-
-  created: function () {
-    let parent = this;
-
-    this.constants = constants;
-
-    EventBus.$on("navigate", this.navigate);
-    EventBus.$on("showToast", this.showToast);
-    EventBus.$on("unhandledServerErrorToast", this.unhandledServerErrorToast);
-    EventBus.$on("updateNoteTitle", this.updateNoteTitle);
-
-    Mousetrap.bind("/", function () {
-      parent.openSearch();
-      return false;
-    });
-
-    this.loadConfig();
-    loadToken();
-
-    let darkTheme = localStorage.getItem("darkTheme");
-    if (darkTheme != null) {
-      this.darkTheme = darkTheme == "true";
-    } else if (
-      window.matchMedia &&
-      window.matchMedia("(prefers-color-scheme: dark)").matches
-    ) {
-      this.darkTheme = true;
-    }
-
-    this.route();
-  },
-
-  mounted: function () {
-    let parent = this;
-
-    window.addEventListener("popstate", this.route);
-
-    this.$root.$on("bv::modal::shown", function (_, modalId) {
-      if (modalId == "search-modal") {
-        parent.focusSearchInput();
-      }
-    });
-  },
-};
diff --git a/client/components/App.vue b/client/components/App.vue
deleted file mode 100644 (file)
index 3e46c52..0000000
+++ /dev/null
@@ -1,92 +0,0 @@
-<template>
-  <div class="container d-flex flex-column h-100">
-    <!-- Search Modal -->
-    <b-modal id="search-modal" centered hide-footer hide-header>
-      <div>
-        <SearchInput></SearchInput>
-      </div>
-    </b-modal>
-
-    <!-- Nav Bar -->
-    <NavBar
-      v-if="currentView != views.login"
-      class="w-100 mb-5"
-      :show-logo="currentView != views.home"
-      :auth-type="authType"
-      :dark-theme="darkTheme"
-      @logout="logout()"
-      @toggleTheme="toggleTheme()"
-      @search="openSearch()"
-    ></NavBar>
-
-    <!-- Login -->
-    <Login
-      v-if="currentView == views.login"
-      class="flex-grow-1"
-      :auth-type="authType"
-    ></Login>
-
-    <!-- Home -->
-    <div
-      v-if="currentView == views.home"
-      class="home-view align-self-center d-flex flex-column justify-content-center align-items-center flex-grow-1 w-100"
-    >
-      <Logo class="mb-3"></Logo>
-      <SearchInput
-        :initial-value="searchTerm"
-        class="search-input mb-4"
-      ></SearchInput>
-      <div v-if="authType != null && authType != constants.authTypes.readOnly">
-        <RecentlyModified
-          class="recently-modified"
-          :max-notes="5"
-        ></RecentlyModified>
-      </div>
-    </div>
-
-    <!-- Search Results -->
-    <div
-      v-if="currentView == views.search"
-      class="flex-grow-1 search-results-view d-flex flex-column"
-    >
-      <SearchResults
-        :search-term="searchTerm"
-        class="flex-grow-1"
-      ></SearchResults>
-    </div>
-
-    <!-- Note -->
-    <NoteViewerEditor
-      v-if="currentView == this.views.note"
-      class="flex-grow-1"
-      :titleToLoad="noteTitle"
-      :auth-type="authType"
-      @note-deleted="showToast('success', 'Note deleted âœ“')"
-    ></NoteViewerEditor>
-  </div>
-</template>
-
-<style lang="scss" scoped>
-@import "../colours";
-
-.home-view {
-  max-width: 500px;
-}
-
-.search-results-view {
-  max-width: 700px;
-}
-
-.search-input {
-  box-shadow: 0 0 20px var(--colour-shadow);
-}
-
-.recently-modified {
-  // Prevent UI from moving during load
-  min-height: 190px;
-}
-</style>
-
-<script>
-export { default } from "./App.js";
-</script>
diff --git a/client/components/LoadingIndicator.vue b/client/components/LoadingIndicator.vue
deleted file mode 100644 (file)
index 6e2d043..0000000
+++ /dev/null
@@ -1,104 +0,0 @@
-<template>
-  <div class="d-flex justify-content-center">
-    <div v-if="showLoader && !failed" class="loader"></div>
-    <div
-      v-else-if="failed"
-      class="d-flex flex-column align-items-center failure-message"
-    >
-      <b-icon
-        class="failed-icon mb-3"
-        :icon="failedBootstrapIcon || 'cone-striped'"
-      ></b-icon>
-      <p>{{ failedMessage }}</p>
-    </div>
-  </div>
-</template>
-
-<style lang="scss" scoped>
-@import "../colours";
-
-p {
-  color: var(--colour-text-muted);
-}
-
-.failed-icon {
-  color: var(--colour-brand);
-  font-size: 60px;
-}
-
-.failure-message {
-  max-width: 300px;
-  text-align: center;
-}
-
-.loader,
-.loader:before,
-.loader:after {
-  background: var(--colour-brand);
-  -webkit-animation: load1 1s infinite ease-in-out;
-  animation: load1 1s infinite ease-in-out;
-  width: 1em;
-  height: 4em;
-}
-.loader {
-  color: var(--colour-brand);
-  text-indent: -9999em;
-  margin: 33% auto;
-  position: relative;
-  font-size: 11px;
-  -webkit-transform: translateZ(0);
-  -ms-transform: translateZ(0);
-  transform: translateZ(0);
-  -webkit-animation-delay: -0.16s;
-  animation-delay: -0.16s;
-}
-.loader:before,
-.loader:after {
-  position: absolute;
-  top: 0;
-  content: "";
-}
-.loader:before {
-  left: -1.5em;
-  -webkit-animation-delay: -0.32s;
-  animation-delay: -0.32s;
-}
-.loader:after {
-  left: 1.5em;
-}
-@-webkit-keyframes load1 {
-  0%,
-  80%,
-  100% {
-    box-shadow: 0 0;
-    height: 4em;
-  }
-  40% {
-    box-shadow: 0 -2em;
-    height: 5em;
-  }
-}
-@keyframes load1 {
-  0%,
-  80%,
-  100% {
-    box-shadow: 0 0;
-    height: 4em;
-  }
-  40% {
-    box-shadow: 0 -2em;
-    height: 5em;
-  }
-}
-</style>
-
-<script>
-export default {
-  props: {
-    showLoader: { type: Boolean, default: true },
-    failed: { type: Boolean },
-    failedBootstrapIcon: { type: String },
-    failedMessage: { type: String, default: "Loading Failed" },
-  },
-};
-</script>
diff --git a/client/components/Login.vue b/client/components/Login.vue
deleted file mode 100644 (file)
index d94f674..0000000
+++ /dev/null
@@ -1,170 +0,0 @@
-<template>
-  <div class="d-flex flex-column justify-content-center align-items-center">
-    <!-- Logo -->
-    <Logo class="mb-5"></Logo>
-    <div
-      v-if="authType != null && authType != constants.authTypes.none"
-      class="d-flex flex-column justify-content-center align-items-center"
-    >
-      <form
-        v-show="authType != null"
-        class="login-form d-flex flex-column align-items-center"
-        v-on:submit.prevent="login"
-      >
-        <div class="mb-1">
-          <!-- Username -->
-          <div class="mb-1">
-            <input
-              type="text"
-              placeholder="Username"
-              class="form-control"
-              id="username"
-              autocomplete="username"
-              v-model="usernameInput"
-              autofocus
-              required
-            />
-          </div>
-
-          <!-- Password -->
-          <div class="mb-1">
-            <input
-              type="password"
-              placeholder="Password"
-              class="form-control"
-              id="password"
-              autocomplete="current-password"
-              v-model="passwordInput"
-              required
-            />
-          </div>
-
-          <!-- 2FA -->
-          <div v-if="authType == constants.authTypes.totp" class="mb-1">
-            <input
-              type="text"
-              inputmode="numeric"
-              pattern="[0-9]*"
-              placeholder="2FA Code"
-              class="form-control"
-              id="totp"
-              autocomplete="one-time-code"
-              v-model="totpInput"
-              required
-            />
-          </div>
-        </div>
-
-        <!-- Remember Me -->
-        <div class="mb-3 form-check">
-          <input
-            type="checkbox"
-            class="form-check-input"
-            id="rememberMe"
-            v-model="rememberMeInput"
-          />
-          <label class="form-check-label" for="rememberMe">Remember Me</label>
-        </div>
-
-        <!-- Button -->
-        <button type="submit" class="bttn">
-          <b-icon icon="box-arrow-in-right"></b-icon><span>Log In</span>
-        </button>
-      </form>
-    </div>
-  </div>
-</template>
-
-<style lang="scss" scoped>
-.login-form {
-  input {
-    color: var(--colour-text);
-    background-color: var(--colour-background-elevated);
-    border-color: var(--colour-border);
-  }
-}
-</style>
-
-<script>
-import * as constants from "../constants.js";
-import * as helpers from "../helpers.js";
-import { setToken } from "../tokenStorage.js";
-
-import EventBus from "../eventBus.js";
-import Logo from "./Logo.vue";
-import api from "../api.js";
-
-export default {
-  components: {
-    Logo,
-  },
-
-  props: {
-    authType: { type: String, default: null },
-  },
-
-  data: function () {
-    return {
-      usernameInput: null,
-      passwordInput: null,
-      totpInput: null,
-      rememberMeInput: false,
-    };
-  },
-
-  watch: {
-    authType: function () {
-      this.skipIfNoneAuthType();
-    },
-  },
-
-  methods: {
-    skipIfNoneAuthType: function () {
-      // Skip past the login page if authentication is disabled
-      if (this.authType == constants.authTypes.none) {
-        EventBus.$emit("navigate", constants.basePaths.home);
-      }
-    },
-
-    login: function () {
-      let parent = this;
-      api
-        .post("/api/token", {
-          username: this.usernameInput,
-          password:
-            this.authType == constants.authTypes.totp
-              ? this.passwordInput + this.totpInput
-              : this.passwordInput,
-        })
-        .then(function (response) {
-          setToken(response.data.access_token, parent.rememberMeInput);
-          let redirectPath = helpers.getSearchParam(constants.params.redirect);
-          EventBus.$emit("navigate", redirectPath || constants.basePaths.home);
-        })
-        .catch(function (error) {
-          if (error.handled) {
-            return;
-          } else if (
-            typeof error.response !== "undefined" &&
-            error.response.status == 401
-          ) {
-            EventBus.$emit("showToast", "danger", "Incorrect login credentials âœ˜")
-          } else {
-            EventBus.$emit("unhandledServerErrorToast");
-          }
-        })
-        .finally(function () {
-          parent.usernameInput = null;
-          parent.passwordInput = null;
-          parent.totpInput = null;
-          parent.rememberMeInput = false;
-        });
-    },
-  },
-
-  created: function () {
-    this.constants = constants;
-    this.skipIfNoneAuthType();
-  },
-};
-</script>
diff --git a/client/components/Logo.vue b/client/components/Logo.vue
deleted file mode 100644 (file)
index 1d0ebf3..0000000
+++ /dev/null
@@ -1,58 +0,0 @@
-<template>
-  <div>
-    <svg
-      width="36"
-      height="36"
-      viewBox="0 0 36 36"
-      fill="none"
-      xmlns="http://www.w3.org/2000/svg"
-      class="part-icon"
-    >
-      <rect width="36" height="36" rx="4" fill="currentColor" />
-      <path
-        d="M9.904 28.88C9.75467 28.88 9.59467 28.7733 9.424 28.56C9.27467 28.3467 9.14667 28.1013 9.04 27.824C8.95467 27.5467 8.912 27.3227 8.912 27.152C8.95467 26.32 9.14667 25.3813 9.488 24.336C9.59467 24.0373 9.75467 23.9093 9.968 23.952C10.0107 23.5893 10.0533 23.1307 10.096 22.576C10.16 22.0213 10.2347 21.3813 10.32 20.656C10.2133 20.3573 9.98933 20.208 9.648 20.208C9.56267 20.208 9.24267 20.2613 8.688 20.368C8.15467 20.4747 7.36533 20.6347 6.32 20.848C6.29867 20.848 6.27733 20.8587 6.256 20.88C6.23467 20.88 6.20267 20.8693 6.16 20.848C5.94667 20.848 5.84 20.72 5.84 20.464C5.84 20.1653 5.97867 19.952 6.256 19.824L6.224 19.856C6.544 19.7067 7.32267 19.4827 8.56 19.184C9.47733 18.9493 10.2133 18.6507 10.768 18.288L10.96 16.656C11.0453 15.8667 11.184 15.056 11.376 14.224C11.568 13.3707 11.8347 12.5067 12.176 11.632C12.88 9.73333 13.712 8.208 14.672 7.056C15.4827 6.11733 16.2933 5.648 17.104 5.648C17.808 5.648 18.4267 5.97867 18.96 6.64C19.0453 6.72533 19.088 6.82133 19.088 6.928C19.088 7.16267 18.96 7.28 18.704 7.28C18.5547 7.28 18.4267 7.216 18.32 7.088C17.9787 6.68267 17.6053 6.48 17.2 6.48C16.24 6.48 15.2267 7.536 14.16 9.648C13.7333 10.5013 13.36 11.5147 13.04 12.688C12.72 13.84 12.4747 15.1413 12.304 16.592L12.272 17.136L12.208 18C12.8267 18.192 13.5093 18.288 14.256 18.288L16.304 18.224C16.944 18.2453 17.264 18.4587 17.264 18.864C17.264 19.2267 17.0933 19.408 16.752 19.408H14.256C13.36 19.472 12.5707 19.7173 11.888 20.144C11.248 25.0933 10.704 27.92 10.256 28.624C10.1493 28.7947 10.032 28.88 9.904 28.88ZM17.839 27.856C17.3483 27.856 16.9643 27.792 16.687 27.664C16.431 27.536 16.303 27.3973 16.303 27.248C16.303 27.12 16.367 27.0027 16.495 26.896C16.6443 26.7893 16.8897 26.7253 17.231 26.704C17.6577 26.6827 18.255 26.6613 19.023 26.64C19.791 26.6187 20.6337 26.5973 21.551 26.576C22.4897 26.5547 23.407 26.544 24.303 26.544C24.7723 26.544 25.231 26.544 25.679 26.544C26.127 26.544 26.5217 26.5547 26.863 26.576C27.2257 26.5973 27.4923 26.6293 27.663 26.672C27.8337 26.7147 27.919 26.832 27.919 27.024C27.919 27.2587 27.823 27.408 27.631 27.472C27.439 27.536 27.2043 27.568 26.927 27.568C26.4363 27.568 25.7963 27.5787 25.007 27.6C24.2177 27.6427 23.375 27.6747 22.479 27.696C21.583 27.7387 20.719 27.7707 19.887 27.792C19.0763 27.8347 18.3937 27.856 17.839 27.856Z"
-        fill="white"
-      />
-    </svg>
-    <svg
-      width="149"
-      height="29"
-      viewBox="0 0 149 29"
-      fill="none"
-      xmlns="http://www.w3.org/2000/svg"
-      class="part-name ml-2"
-      :class="{ 'responsive-hide': responsive }"
-    >
-      <path
-        d="M11.5057 8.36364V10.9205H0.920455V8.36364H11.5057ZM4.09091 28V5.65341C4.09091 4.52841 4.35511 3.59091 4.88352 2.84091C5.41193 2.09091 6.09801 1.52841 6.94176 1.15341C7.78551 0.778407 8.67614 0.590907 9.61364 0.590907C10.3551 0.590907 10.9602 0.650567 11.429 0.769886C11.8977 0.889203 12.2472 0.999999 12.4773 1.10227L11.608 3.71023C11.4545 3.65909 11.2415 3.59517 10.9688 3.51847C10.7045 3.44176 10.3551 3.40341 9.92045 3.40341C8.9233 3.40341 8.20313 3.65483 7.75994 4.15767C7.32528 4.66051 7.10795 5.39773 7.10795 6.36932V28H4.09091ZM18.7511 1.81818V28H15.734V1.81818H18.7511ZM30.0554 28.4602C28.8111 28.4602 27.6818 28.2259 26.6676 27.7571C25.6534 27.2798 24.848 26.5938 24.2514 25.6989C23.6548 24.7955 23.3565 23.7045 23.3565 22.4261C23.3565 21.3011 23.5781 20.3892 24.0213 19.6903C24.4645 18.983 25.0568 18.429 25.7983 18.0284C26.5398 17.6278 27.358 17.3295 28.2528 17.1335C29.1563 16.929 30.0639 16.767 30.9759 16.6477C32.169 16.4943 33.1364 16.3793 33.8778 16.3026C34.6278 16.2173 35.1733 16.0767 35.5142 15.8807C35.8636 15.6847 36.0384 15.3437 36.0384 14.858V14.7557C36.0384 13.4943 35.6932 12.5142 35.0028 11.8153C34.321 11.1165 33.2855 10.767 31.8963 10.767C30.456 10.767 29.3267 11.0824 28.5085 11.7131C27.6903 12.3437 27.1151 13.017 26.7827 13.733L23.919 12.7102C24.4304 11.517 25.1122 10.5881 25.9645 9.9233C26.8253 9.25 27.7628 8.78125 28.777 8.51705C29.7997 8.24432 30.8054 8.10795 31.794 8.10795C32.4247 8.10795 33.1491 8.18466 33.9673 8.33807C34.794 8.48295 35.5909 8.78551 36.358 9.24574C37.1335 9.70597 37.777 10.4006 38.2884 11.3295C38.7997 12.2585 39.0554 13.5028 39.0554 15.0625V28H36.0384V25.3409H35.8849C35.6804 25.767 35.3395 26.223 34.8622 26.7088C34.3849 27.1946 33.75 27.608 32.9574 27.9489C32.1648 28.2898 31.1974 28.4602 30.0554 28.4602ZM30.5156 25.75C31.7088 25.75 32.7145 25.5156 33.5327 25.0469C34.3594 24.5781 34.9815 23.973 35.3991 23.2315C35.8253 22.4901 36.0384 21.7102 36.0384 20.892V18.1307C35.9105 18.2841 35.6293 18.4247 35.1946 18.5526C34.7685 18.6719 34.2741 18.7784 33.7116 18.8722C33.1577 18.9574 32.6165 19.0341 32.0881 19.1023C31.5682 19.1619 31.1463 19.2131 30.8224 19.2557C30.0384 19.358 29.3054 19.5241 28.6236 19.7543C27.9503 19.9759 27.4048 20.3125 26.9872 20.7642C26.5781 21.2074 26.3736 21.8125 26.3736 22.5795C26.3736 23.6278 26.7614 24.4205 27.5369 24.9574C28.321 25.4858 29.3139 25.75 30.5156 25.75ZM53.1019 8.36364V10.9205H42.9258V8.36364H53.1019ZM45.8917 3.65909H48.9087V22.375C48.9087 23.2273 49.0323 23.8665 49.2795 24.2926C49.5352 24.7102 49.859 24.9915 50.2511 25.1364C50.6516 25.2727 51.0735 25.3409 51.5167 25.3409C51.8491 25.3409 52.1218 25.3239 52.3349 25.2898C52.5479 25.2472 52.7184 25.2131 52.8462 25.1875L53.4599 27.8977C53.2553 27.9744 52.9698 28.0511 52.6033 28.1278C52.2369 28.2131 51.7724 28.2557 51.2099 28.2557C50.3576 28.2557 49.5224 28.0724 48.7042 27.706C47.8945 27.3395 47.2212 26.7812 46.6843 26.0312C46.1559 25.2812 45.8917 24.3352 45.8917 23.1932V3.65909ZM60.6573 16.1875V28H57.6403V8.36364H60.555V11.4318H60.8107C61.271 10.4347 61.9698 9.63352 62.9073 9.02841C63.8448 8.41477 65.055 8.10795 66.538 8.10795C67.8675 8.10795 69.0309 8.38068 70.0281 8.92614C71.0252 9.46307 71.8008 10.2812 72.3548 11.3807C72.9087 12.4716 73.1857 13.8523 73.1857 15.5227V28H70.1687V15.7273C70.1687 14.1847 69.7681 12.983 68.967 12.1222C68.1658 11.2528 67.0664 10.8182 65.6687 10.8182C64.7056 10.8182 63.8448 11.027 63.0863 11.4446C62.3363 11.8622 61.744 12.4716 61.3093 13.2727C60.8746 14.0739 60.6573 15.0455 60.6573 16.1875ZM86.6761 28.4091C84.9034 28.4091 83.348 27.9872 82.0099 27.1435C80.6804 26.2997 79.6406 25.1193 78.8906 23.6023C78.1491 22.0852 77.7784 20.3125 77.7784 18.2841C77.7784 16.2386 78.1491 14.4531 78.8906 12.9276C79.6406 11.402 80.6804 10.2173 82.0099 9.37358C83.348 8.52983 84.9034 8.10795 86.6761 8.10795C88.4489 8.10795 90 8.52983 91.3295 9.37358C92.6676 10.2173 93.7074 11.402 94.4489 12.9276C95.1989 14.4531 95.5739 16.2386 95.5739 18.2841C95.5739 20.3125 95.1989 22.0852 94.4489 23.6023C93.7074 25.1193 92.6676 26.2997 91.3295 27.1435C90 27.9872 88.4489 28.4091 86.6761 28.4091ZM86.6761 25.6989C88.0227 25.6989 89.1307 25.3537 90 24.6634C90.8693 23.973 91.5128 23.0653 91.9304 21.9403C92.348 20.8153 92.5568 19.5966 92.5568 18.2841C92.5568 16.9716 92.348 15.7486 91.9304 14.6151C91.5128 13.4815 90.8693 12.5653 90 11.8665C89.1307 11.1676 88.0227 10.8182 86.6761 10.8182C85.3295 10.8182 84.2216 11.1676 83.3523 11.8665C82.483 12.5653 81.8395 13.4815 81.4219 14.6151C81.0043 15.7486 80.7955 16.9716 80.7955 18.2841C80.7955 19.5966 81.0043 20.8153 81.4219 21.9403C81.8395 23.0653 82.483 23.973 83.3523 24.6634C84.2216 25.3537 85.3295 25.6989 86.6761 25.6989ZM108.719 8.36364V10.9205H98.543V8.36364H108.719ZM101.509 3.65909H104.526V22.375C104.526 23.2273 104.65 23.8665 104.897 24.2926C105.152 24.7102 105.476 24.9915 105.868 25.1364C106.269 25.2727 106.691 25.3409 107.134 25.3409C107.466 25.3409 107.739 25.3239 107.952 25.2898C108.165 25.2472 108.336 25.2131 108.463 25.1875L109.077 27.8977C108.873 27.9744 108.587 28.0511 108.221 28.1278C107.854 28.2131 107.39 28.2557 106.827 28.2557C105.975 28.2557 105.14 28.0724 104.321 27.706C103.512 27.3395 102.838 26.7812 102.301 26.0312C101.773 25.2812 101.509 24.3352 101.509 23.1932V3.65909ZM121.279 28.4091C119.387 28.4091 117.755 27.9915 116.383 27.1562C115.02 26.3125 113.967 25.1364 113.225 23.6278C112.493 22.1108 112.126 20.3466 112.126 18.3352C112.126 16.3239 112.493 14.5511 113.225 13.017C113.967 11.4744 114.998 10.2727 116.319 9.41193C117.649 8.54261 119.2 8.10795 120.973 8.10795C121.995 8.10795 123.005 8.27841 124.002 8.61932C125 8.96023 125.907 9.5142 126.725 10.2812C127.544 11.0398 128.196 12.0455 128.681 13.2983C129.167 14.5511 129.41 16.0937 129.41 17.9261V19.2045H114.274V16.5966H126.342C126.342 15.4886 126.12 14.5 125.677 13.6307C125.243 12.7614 124.62 12.0753 123.811 11.5724C123.01 11.0696 122.064 10.8182 120.973 10.8182C119.771 10.8182 118.731 11.1165 117.853 11.7131C116.984 12.3011 116.315 13.0682 115.846 14.0142C115.377 14.9602 115.143 15.9744 115.143 17.0568V18.7955C115.143 20.2784 115.399 21.5355 115.91 22.5668C116.43 23.5895 117.15 24.3693 118.071 24.9062C118.991 25.4347 120.061 25.6989 121.279 25.6989C122.072 25.6989 122.788 25.5881 123.427 25.3665C124.075 25.1364 124.633 24.7955 125.102 24.3438C125.571 23.8835 125.933 23.3125 126.189 22.6307L129.103 23.4489C128.797 24.4375 128.281 25.3068 127.556 26.0568C126.832 26.7983 125.937 27.3778 124.872 27.7955C123.806 28.2045 122.609 28.4091 121.279 28.4091ZM147.909 12.7614L145.199 13.5284C145.028 13.0767 144.777 12.6378 144.444 12.2116C144.12 11.777 143.677 11.419 143.115 11.1378C142.552 10.8565 141.832 10.7159 140.954 10.7159C139.752 10.7159 138.751 10.9929 137.95 11.5469C137.157 12.0923 136.761 12.7869 136.761 13.6307C136.761 14.3807 137.034 14.973 137.579 15.4077C138.125 15.8423 138.977 16.2045 140.136 16.4943L143.051 17.2102C144.806 17.6364 146.115 18.2884 146.975 19.1662C147.836 20.0355 148.267 21.1562 148.267 22.5284C148.267 23.6534 147.943 24.6591 147.295 25.5455C146.656 26.4318 145.761 27.1307 144.61 27.642C143.46 28.1534 142.122 28.4091 140.596 28.4091C138.593 28.4091 136.936 27.9744 135.623 27.1051C134.311 26.2358 133.48 24.9659 133.13 23.2955L135.994 22.5795C136.267 23.6364 136.782 24.429 137.541 24.9574C138.308 25.4858 139.309 25.75 140.545 25.75C141.951 25.75 143.068 25.4517 143.895 24.8551C144.73 24.25 145.147 23.5256 145.147 22.6818C145.147 22 144.909 21.429 144.431 20.9688C143.954 20.5 143.221 20.1506 142.233 19.9205L138.96 19.1534C137.162 18.7273 135.841 18.0668 134.997 17.1719C134.162 16.2685 133.744 15.1392 133.744 13.7841C133.744 12.6761 134.055 11.696 134.677 10.8438C135.308 9.99148 136.164 9.32244 137.247 8.83665C138.338 8.35085 139.574 8.10795 140.954 8.10795C142.897 8.10795 144.423 8.53409 145.531 9.38636C146.647 10.2386 147.44 11.3636 147.909 12.7614Z"
-        fill="currentColor"
-      />
-    </svg>
-  </div>
-</template>
-
-<style lang="scss" scoped>
-@import "../colours";
-
-@media screen and (max-width: 500px) {
-  .responsive-hide {
-    display: none;
-  }
-}
-
-.part-icon {
-  color: var(--colour-brand);
-}
-
-.part-name {
-  color: var(--colour-text-muted);
-}
-</style>
-
-<script>
-export default {
-  props: {
-    responsive: { type: Boolean, default: false },
-  },
-};
-</script>
diff --git a/client/components/NavBar.vue b/client/components/NavBar.vue
deleted file mode 100644 (file)
index b46c24d..0000000
+++ /dev/null
@@ -1,166 +0,0 @@
-<template>
-  <div class="d-flex justify-content-between align-items-center">
-    <!-- Logo -->
-    <a
-      :href="constants.basePaths.home"
-      @click.prevent="navigate(constants.basePaths.home, $event)"
-    >
-      <Logo :class="{ invisible: !showLogo }" responsive></Logo>
-    </a>
-
-    <!-- Buttons -->
-    <div class="d-flex">
-      <!-- New Note -->
-      <a
-        v-if="showNewButton"
-        :href="constants.basePaths.new"
-        class="bttn"
-        @click.prevent="navigate(constants.basePaths.new, $event)"
-      >
-        <b-icon icon="plus-circle"></b-icon><span>New Note</span>
-      </a>
-
-      <!-- Menu -->
-      <b-dropdown
-        menu-class="menu"
-        toggle-class="bttn text-decoration-none"
-        size="md"
-        variant="link"
-        no-caret
-        right
-      >
-        <template #button-content>
-          <b-icon icon="list" class="align-middle"></b-icon><span>Menu</span>
-        </template>
-
-        <!-- Search -->
-        <!-- Note: We use .capture.native.stop here to prevent button from gaining focus after the menu is closed. -->
-        <b-dropdown-item-button
-          button-class="bttn d-flex align-items-center"
-          @click.capture.native.stop="$emit('search')"
-        >
-          <b-icon icon="search" font-scale="0.8" class="mr-2"></b-icon>
-          <span class="mr-3">Search</span>
-          <span class="keyboard-shortcut" title="Keyboard Shortcut">/</span>
-        </b-dropdown-item-button>
-
-        <!-- All Notes -->
-        <b-dropdown-item
-          link-class="bttn d-flex align-items-center"
-          :href="azHref"
-          @click.prevent="navigate(azHref, $event)"
-        >
-          <b-icon icon="files" font-scale="0.8" class="mr-2"></b-icon>
-          <span>All Notes</span>
-        </b-dropdown-item>
-
-        <!-- Toggle Theme -->
-        <b-dropdown-item-button
-          button-class="bttn d-flex align-items-center"
-          @click="$emit('toggleTheme')"
-        >
-          <b-icon
-            :icon="darkTheme ? 'sun' : 'moon'"
-            font-scale="0.8"
-            class="mr-2"
-          >
-          </b-icon>
-          <span>Toggle Theme</span>
-        </b-dropdown-item-button>
-
-        <!-- Log Out -->
-        <b-dropdown-divider v-if="showLogOutButton"></b-dropdown-divider>
-        <b-dropdown-item-button
-          v-if="showLogOutButton"
-          button-class="bttn d-flex align-items-center"
-          @click="$emit('logout')"
-        >
-          <b-icon icon="box-arrow-right" font-scale="0.8" class="mr-2"></b-icon>
-          <span>Log Out</span>
-        </b-dropdown-item-button>
-      </b-dropdown>
-    </div>
-  </div>
-</template>
-
-<style lang="scss">
-// Use visibility hidden instead of v-show to maintain consistent height
-.invisible {
-  visibility: hidden;
-}
-
-.menu {
-  background-color: var(--colour-background);
-  color: var(--colour-text);
-  border: 1px solid var(--colour-border);
-
-  hr {
-    border-top: 1px solid var(--colour-border);
-  }
-}
-
-.keyboard-shortcut {
-  background-color: var(--colour-background-elevated);
-  padding: 0.1em 0.75em;
-  border: 1px solid var(--colour-border);
-  border-radius: 4px;
-  font-size: 0.75em;
-}
-</style>
-
-<script>
-import * as constants from "../constants.js";
-
-import EventBus from "../eventBus.js";
-import Logo from "./Logo.vue";
-
-export default {
-  components: {
-    Logo,
-  },
-
-  props: {
-    showLogo: {
-      type: Boolean,
-      default: true,
-    },
-    authType: { type: String, default: null },
-    darkTheme: { type: Boolean, default: false },
-  },
-
-  computed: {
-    azHref: function () {
-      let params = new URLSearchParams();
-      params.set(constants.params.searchTerm, "*");
-      params.set(constants.params.sortBy, constants.searchSortOptions.title);
-      params.set(constants.params.showHighlights, false);
-      return `${constants.basePaths.search}?${params.toString()}`;
-    },
-
-    showLogOutButton: function () {
-      return (
-        this.authType != null &&
-        ![constants.authTypes.none, constants.authTypes.readOnly].includes(
-          this.authType
-        )
-      );
-    },
-
-    showNewButton: function () {
-      return (
-        this.authType != null && this.authType != constants.authTypes.readOnly
-      );
-    },
-  },
-
-  methods: {
-    navigate: function (href, event) {
-      EventBus.$emit("navigate", href, event);
-    },
-  },
-
-  created: function () {
-    this.constants = constants;
-  },
-};
-</script>
diff --git a/client/components/NoteViewerEditor.vue b/client/components/NoteViewerEditor.vue
deleted file mode 100644 (file)
index 3683951..0000000
+++ /dev/null
@@ -1,662 +0,0 @@
-<template>
-  <!-- Note -->
-  <div class="pb-4">
-    <!-- Loading -->
-    <div
-      v-if="currentNote == null"
-      class="h-100 d-flex flex-column justify-content-center"
-    >
-      <LoadingIndicator
-        :failed="noteLoadFailed"
-        :failedBootstrapIcon="noteLoadFailedIcon"
-        :failedMessage="noteLoadFailedMessage"
-      />
-    </div>
-
-    <!-- Loaded -->
-    <div v-else class="d-flex flex-column h-100">
-      <div
-        class="d-flex justify-content-between flex-wrap-reverse align-items-start mb-2"
-      >
-        <!-- Title -->
-        <h1 v-if="editMode == false" class="title" :title="currentNote.title">
-          {{ currentNote.title }}
-        </h1>
-        <input
-          v-else
-          type="text"
-          class="title-input flex-grow-1"
-          v-model="titleInput"
-          placeholder="Title"
-        />
-
-        <!-- Buttons -->
-        <div class="d-flex flex-grow-1 justify-content-end">
-          <!-- Edit -->
-          <button
-            v-if="canModify && editMode == false && noteLoadFailed == false"
-            type="button"
-            class="bttn"
-            @click="setEditMode(true)"
-            v-b-tooltip.hover
-            title="Keyboard Shortcut: e"
-          >
-            <b-icon icon="pencil-square"></b-icon><span>Edit</span>
-          </button>
-
-          <!-- Delete -->
-          <button
-            v-if="canModify && editMode == false && noteLoadFailed == false"
-            type="button"
-            class="bttn"
-            @click="deleteNote"
-          >
-            <b-icon icon="trash"></b-icon><span>Delete</span>
-          </button>
-
-          <!-- Cancel -->
-          <button
-            v-if="editMode == true"
-            type="button"
-            class="bttn"
-            @click="confirmCancelNote"
-          >
-            <b-icon icon="arrow-return-left"></b-icon><span>Cancel</span>
-          </button>
-
-          <!-- Save -->
-          <button
-            v-if="editMode == true"
-            type="button"
-            class="bttn"
-            @click="saveNote"
-          >
-            <b-icon icon="check-square"></b-icon><span>Save</span>
-          </button>
-        </div>
-      </div>
-
-      <!-- Horizontal Rule -->
-      <hr v-show="editMode == false" class="hr" />
-
-      <!-- Viewer -->
-      <div v-if="editMode == false" class="note note-viewer">
-        <viewer :initialValue="currentNote.content" :options="viewerOptions" />
-      </div>
-
-      <!-- Editor -->
-      <div v-else class="note flex-grow-1">
-        <editor
-          :initialValue="initialContent"
-          :initialEditType="loadDefaultEditorMode()"
-          previewStyle="tab"
-          ref="toastUiEditor"
-          :options="editorOptions"
-          height="100%"
-          @change="startDraftSaveTimeout"
-        />
-      </div>
-    </div>
-  </div>
-</template>
-
-<style lang="scss">
-@import "@toast-ui/editor/dist/toastui-editor.css";
-@import "prismjs/themes/prism.css";
-@import "@toast-ui/editor-plugin-code-syntax-highlight/dist/toastui-editor-plugin-code-syntax-highlight.css";
-
-@import "../toastui-editor-overrides.scss";
-@import "../colours";
-
-.title,
-.title-input {
-  font-size: 2rem;
-  font-weight: bold;
-  line-height: 1.6;
-}
-
-.title {
-  min-width: 300px;
-  white-space: nowrap;
-  text-overflow: ellipsis;
-  overflow: hidden;
-  color: var(--colour-text);
-  margin: 0;
-}
-
-.title-input {
-  border: none;
-
-  // Override user agent styling
-  background-color: transparent;
-  color: var(--colour-text);
-  padding: 0;
-  min-width: 0;
-
-  &:focus {
-    outline: none;
-  }
-}
-
-// Prism overrides
-code[class*="language-"],
-pre[class*="language-"] {
-  // See #138
-  text-shadow: none;
-}
-
-.hr {
-  width: 100%;
-  border-color: var(--colour-border);
-  margin: 0 0 1.25rem 0;
-}
-</style>
-
-<script>
-import * as constants from "../constants.js";
-
-import { Editor } from "@toast-ui/vue-editor";
-import EventBus from "../eventBus.js";
-import LoadingIndicator from "./LoadingIndicator.vue";
-import Mousetrap from "mousetrap";
-import { Note } from "../classes.js";
-import { Viewer } from "@toast-ui/vue-editor";
-import api from "../api.js";
-import codeSyntaxHighlight from "@toast-ui/editor-plugin-code-syntax-highlight/dist/toastui-editor-plugin-code-syntax-highlight-all.js";
-import { extendedAutolinks } from "../autolinkParsers.js";
-
-const reservedFilenameCharacters = /[<>:"/\\|?*]/;
-
-const customHTMLRenderer = {
-  heading(node, { entering, getChildrenText }) {
-    const tagName = `h${node.level}`;
-
-    if (entering) {
-      return {
-        type: "openTag",
-        tagName,
-        attributes: {
-          id: getChildrenText(node)
-            .toLowerCase()
-            .replace(/[^a-z0-9-\s]*/g, "")
-            .trim()
-            .replace(/\s/g, "-"),
-        },
-      };
-    }
-    return { type: "closeTag", tagName };
-  },
-};
-
-export default {
-  components: {
-    Viewer,
-    Editor,
-    LoadingIndicator,
-  },
-
-  props: {
-    titleToLoad: { type: String, default: null },
-    authType: { type: String, default: null },
-  },
-
-  data: function () {
-    return {
-      editMode: false,
-      draftSaveTimeout: null,
-      currentNote: null,
-      titleInput: null,
-      initialContent: null,
-      noteLoadFailed: false,
-      noteLoadFailedIcon: null,
-      noteLoadFailedMessage: "Failed to load Note",
-      viewerOptions: {
-        customHTMLRenderer: customHTMLRenderer,
-        plugins: [codeSyntaxHighlight],
-        extendedAutolinks,
-      },
-      editorOptions: {
-        customHTMLRenderer: customHTMLRenderer,
-        plugins: [codeSyntaxHighlight],
-        hooks: {
-          addImageBlobHook: this.uploadImageHook,
-        },
-      },
-    };
-  },
-
-  computed: {
-    canModify: function () {
-      return (
-        this.authType != null && this.authType != constants.authTypes.readOnly
-      );
-    },
-  },
-
-  watch: {
-    titleToLoad: function () {
-      if (this.titleToLoad !== this.currentNote?.title) {
-        this.init();
-      }
-    },
-  },
-
-  methods: {
-    badFilenameToast: function (invalidItem) {
-      EventBus.$emit(
-        "showToast",
-        "danger",
-        `Invalid ${invalidItem}. Due to filename restrictions, the following characters are not allowed: <>:"/\\|?*`
-      );
-    },
-
-    loadNote: function (title) {
-      let parent = this;
-      this.noteLoadFailed = false;
-      api
-        .get(`/api/notes/${encodeURIComponent(title)}`)
-        .then(function (response) {
-          parent.currentNote = new Note(
-            response.data.title,
-            response.data.lastModified,
-            response.data.content
-          );
-        })
-        .catch(function (error) {
-          if (error.handled) {
-            return;
-          } else if (
-            typeof error.response !== "undefined" &&
-            error.response.status == 404
-          ) {
-            parent.noteLoadFailedIcon = "file-earmark-x";
-            parent.noteLoadFailedMessage = "Note not found";
-            parent.noteLoadFailed = true;
-          } else {
-            EventBus.$emit("unhandledServerErrorToast");
-            parent.noteLoadFailed = true;
-          }
-        });
-    },
-
-    getContentForEditor: function () {
-      let draftContent = localStorage.getItem(this.currentNote.title);
-      if (draftContent) {
-        if (confirm("Do you want to resume the saved draft?")) {
-          return draftContent;
-        } else {
-          localStorage.removeItem(this.currentNote.title);
-        }
-      }
-      return this.currentNote.content;
-    },
-
-    setBeforeUnloadConfirmation: function (enable = true) {
-      if (enable) {
-        window.onbeforeunload = function () {
-          return true;
-        };
-      } else {
-        window.onbeforeunload = null;
-      }
-    },
-
-    setEditMode: function (editMode = true) {
-      let parent = this;
-
-      // To Edit Mode
-      if (editMode === true) {
-        this.setBeforeUnloadConfirmation(true);
-        this.titleInput = this.currentNote.title;
-        let draftContent = localStorage.getItem(this.currentNote.title);
-
-        if (draftContent) {
-          this.$bvModal
-            .msgBoxConfirm(
-              "There is an unsaved draft of this note stored in this browser. Do you want to resume the draft version or delete it?",
-              {
-                centered: true,
-                title: "Resume Draft?",
-                okTitle: "Resume Draft",
-                cancelTitle: "Delete Draft",
-                cancelVariant: "danger",
-              }
-            )
-            .then(function (response) {
-              if (response == true) {
-                parent.initialContent = draftContent;
-              } else {
-                parent.initialContent = parent.currentNote.content;
-                localStorage.removeItem(parent.currentNote.title);
-              }
-              parent.editMode = true;
-            });
-        } else {
-          this.initialContent = this.currentNote.content;
-          this.editMode = true;
-        }
-      }
-      // To View Mode
-      else {
-        this.titleInput = null;
-        this.initialContent = null;
-        this.setBeforeUnloadConfirmation(false);
-        this.editMode = false;
-      }
-    },
-
-    getEditorContent: function () {
-      if (typeof this.$refs.toastUiEditor != "undefined") {
-        return this.$refs.toastUiEditor.invoke("getMarkdown");
-      } else {
-        return null;
-      }
-    },
-
-    saveDefaultEditorMode: function () {
-      let isWysiwygMode = this.$refs.toastUiEditor.invoke("isWysiwygMode");
-      localStorage.setItem(
-        "defaultEditorMode",
-        isWysiwygMode ? "wysiwyg" : "markdown"
-      );
-    },
-
-    loadDefaultEditorMode: function () {
-      let defaultWysiwygMode = localStorage.getItem("defaultEditorMode");
-      if (defaultWysiwygMode) {
-        return defaultWysiwygMode;
-      } else {
-        return "markdown";
-      }
-    },
-
-    clearDraftSaveTimeout: function () {
-      if (this.draftSaveTimeout != null) {
-        clearTimeout(this.draftSaveTimeout);
-      }
-    },
-
-    startDraftSaveTimeout: function () {
-      this.clearDraftSaveTimeout();
-      this.draftSaveTimeout = setTimeout(this.saveDraft, 1000);
-    },
-
-    saveDraft: function () {
-      let content = this.getEditorContent();
-      if (content) {
-        localStorage.setItem(this.currentNote.title, content);
-      }
-    },
-
-    existingTitleToast: function () {
-      EventBus.$emit(
-        "showToast",
-        "danger",
-        "A note with this title already exists. Please try again with a new title.",
-        "Duplicate âœ˜"
-      );
-    },
-
-    entityTooLargeToast: function (entityName) {
-      EventBus.$emit(
-        "showToast",
-        "danger",
-        `This ${entityName.toLowerCase()} is too large. Please try again with a smaller ${entityName.toLowerCase()} or adjust your server configuration.`,
-        `${entityName} Too Large âœ˜`
-      );
-    },
-
-    saveNote: function () {
-      let parent = this;
-      let newContent = this.getEditorContent();
-
-      this.saveDefaultEditorMode();
-
-      // Title Validation
-      if (typeof this.titleInput == "string") {
-        this.titleInput = this.titleInput.trim();
-      }
-      if (!this.titleInput) {
-        EventBus.$emit(
-          "showToast",
-          "danger",
-          "Cannot save note without a title âœ˜"
-        );
-        return;
-      }
-
-      if (reservedFilenameCharacters.test(this.titleInput)) {
-        this.badFilenameToast("title");
-        return;
-      }
-
-      // New Note
-      if (this.currentNote.lastModified == null) {
-        api
-          .post(`/api/notes`, {
-            title: this.titleInput,
-            content: newContent,
-          })
-          .then(this.saveNoteResponseHandler)
-          .catch(function (error) {
-            if (error.handled) {
-              return;
-            } else if (error.response?.status == 409) {
-              parent.existingTitleToast();
-            } else if (error.response?.status == 413) {
-              parent.entityTooLargeToast("Note");
-            } else {
-              EventBus.$emit("unhandledServerErrorToast");
-            }
-          });
-      }
-
-      // Modified Note
-      else if (
-        newContent != this.currentNote.content ||
-        this.titleInput != this.currentNote.title
-      ) {
-        api
-          .patch(`/api/notes/${encodeURIComponent(this.currentNote.title)}`, {
-            newTitle: this.titleInput,
-            newContent: newContent,
-          })
-          .then(this.saveNoteResponseHandler)
-          .catch(function (error) {
-            if (error.handled) {
-              return;
-            } else if (
-              typeof error.response !== "undefined" &&
-              error.response.status == 409
-            ) {
-              parent.existingTitleToast();
-            } else {
-              EventBus.$emit("unhandledServerErrorToast");
-            }
-          });
-      }
-
-      // No Change
-      else {
-        localStorage.removeItem(this.currentNote.title);
-        this.setEditMode(false);
-        this.noteSavedToast();
-      }
-    },
-
-    saveNoteResponseHandler: function (response) {
-      localStorage.removeItem(this.currentNote.title);
-      this.currentNote = new Note(
-        response.data.title,
-        response.data.lastModified,
-        response.data.content
-      );
-      EventBus.$emit("updateNoteTitle", this.currentNote.title);
-      history.replaceState(null, "", this.currentNote.href);
-      this.setEditMode(false);
-      this.noteSavedToast();
-    },
-
-    noteSavedToast: function () {
-      EventBus.$emit("showToast", "success", "Note saved âœ“");
-    },
-
-    cancelNote: function () {
-      localStorage.removeItem(this.currentNote.title);
-      if (this.currentNote.lastModified == null) {
-        // Cancelling a new note
-        EventBus.$emit("navigate", constants.basePaths.home);
-      } else {
-        this.setEditMode(false);
-      }
-    },
-
-    confirmCancelNote: function () {
-      let parent = this;
-      let newContent = this.getEditorContent();
-      if (
-        newContent != this.currentNote.content ||
-        this.titleInput != this.currentNote.title
-      ) {
-        this.$bvModal
-          .msgBoxConfirm(
-            `Are you sure you want to close the note '${this.currentNote.title}' without saving?`,
-            {
-              centered: true,
-              title: "Confirm Closure",
-              okTitle: "Yes, Close",
-              okVariant: "warning",
-            }
-          )
-          .then(function (response) {
-            if (response == true) {
-              parent.cancelNote();
-            }
-          });
-      } else {
-        this.cancelNote();
-      }
-    },
-
-    deleteNote: function () {
-      let parent = this;
-      this.$bvModal
-        .msgBoxConfirm(
-          `Are you sure you want to delete the note '${this.currentNote.title}'?`,
-          {
-            centered: true,
-            title: "Confirm Deletion",
-            okTitle: "Delete",
-            okVariant: "danger",
-          }
-        )
-        .then(function (response) {
-          if (response == true) {
-            api
-              .delete(
-                `/api/notes/${encodeURIComponent(parent.currentNote.title)}`
-              )
-              .then(function () {
-                parent.$emit("note-deleted");
-                EventBus.$emit("navigate", constants.basePaths.home);
-              })
-              .catch(function (error) {
-                if (!error.handled) {
-                  EventBus.$emit("unhandledServerErrorToast");
-                }
-              });
-          }
-        });
-    },
-
-    uploadImageHook(file, callback) {
-      const altTextInputValue = document.getElementById(
-        "toastuiAltTextInput"
-      )?.value;
-
-      // Upload the image then use the callback to insert the URL into the editor
-      this.postAttachment(file).then(function (data) {
-        if (data) {
-          // If the user has entered an alt text, use it. Otherwise, use the filename returned by the API.
-          const altText = altTextInputValue ? altTextInputValue : data.filename;
-          callback(data.url, altText);
-        }
-      });
-    },
-
-    postAttachment(file) {
-      let parent = this;
-
-      if (reservedFilenameCharacters.test(file.name)) {
-        this.badFilenameToast("filename");
-        return;
-      }
-
-      EventBus.$emit("showToast", "success", "Uploading attachment...");
-
-      const formData = new FormData();
-      formData.append("file", file);
-      return api
-        .post("/api/attachments", formData, {
-          headers: {
-            "Content-Type": "multipart/form-data",
-          },
-        })
-        .then(function (response) {
-          EventBus.$emit("showToast", "success", "Attachment uploaded âœ“");
-          return response.data;
-        })
-        .catch(function (error) {
-          if (error.response?.status == 409) {
-            EventBus.$emit(
-              "showToast",
-              "danger",
-              "An attachment with this filename already exists âœ˜"
-            );
-          } else if (error.response?.status == 413) {
-            parent.entityTooLargeToast("Attachment");
-          } else {
-            EventBus.$emit(
-              "showToast",
-              "danger",
-              "Failed to upload attachment âœ˜"
-            );
-          }
-          return;
-        });
-    },
-
-    init: function () {
-      this.currentNote = null;
-      if (this.titleToLoad) {
-        this.loadNote(this.titleToLoad);
-        this.setEditMode(false);
-      } else {
-        this.currentNote = new Note();
-        this.setEditMode(true);
-      }
-    },
-  },
-
-  created: function () {
-    let parent = this;
-
-    // 'e' to edit
-    Mousetrap.bind("e", function () {
-      if (parent.editMode == false && parent.canModify) {
-        parent.setEditMode(true);
-      }
-    });
-
-    // 'ctrl+s' to save
-    // Mousetrap.bind("ctrl+s", function () {
-    //   if (parent.editMode == true) {
-    //     parent.saveNote();
-    //     return false;
-    //   }
-    // });
-
-    this.init();
-  },
-};
-</script>
diff --git a/client/components/RecentlyModified.vue b/client/components/RecentlyModified.vue
deleted file mode 100644 (file)
index 7f390fb..0000000
+++ /dev/null
@@ -1,109 +0,0 @@
-<template>
-  <div>
-    <!-- Loading -->
-    <div
-      v-if="notes == null || notes.length == 0"
-      class="h-100 d-flex flex-column justify-content-center"
-    >
-      <LoadingIndicator
-        :failed="loadingFailed"
-        :failedMessage="loadingFailedMessage"
-        :failedBootstrapIcon="loadingFailedIcon"
-        :show-loader="false"
-      />
-    </div>
-
-    <!-- Notes Loaded -->
-    <div v-else class="d-flex flex-column align-items-center">
-      <p class="mini-header mb-1">RECENTLY MODIFIED</p>
-      <a
-        v-for="note in notes"
-        :key="note.title"
-        class="bttn"
-        :href="note.href"
-        @click.prevent="openNote(note.href, $event)"
-      >
-        {{ note.title }}
-      </a>
-    </div>
-  </div>
-</template>
-
-<style lang="scss" scoped>
-@import "../colours";
-
-.mini-header {
-  font-size: 0.75rem;
-  font-weight: bold;
-  color: var(--colour-text-very-muted);
-}
-</style>
-
-<script>
-import EventBus from "../eventBus.js";
-import LoadingIndicator from "./LoadingIndicator.vue";
-import { SearchResult } from "../classes.js";
-import api from "../api.js";
-
-export default {
-  components: {
-    LoadingIndicator,
-  },
-
-  props: {
-    maxNotes: { type: Number },
-  },
-
-  data: function () {
-    return {
-      notes: null,
-      loadingFailed: false,
-      loadingFailedMessage: "Failed to load notes",
-      loadingFailedIcon: null,
-    };
-  },
-
-  methods: {
-    getNotes: function () {
-      let parent = this;
-      this.loadingFailed = false;
-      api
-        .get("/api/search", {
-          params: {
-            term: "*",
-            sort: "lastModified",
-            order: "desc",
-            limit: this.maxNotes,
-          },
-        })
-        .then(function (response) {
-          parent.notes = [];
-          if (response.data.length) {
-            response.data.forEach(function (searchResult) {
-              parent.notes.push(new SearchResult(searchResult));
-            });
-          } else {
-            parent.loadingFailedMessage =
-              "Click the 'New' button at the top of the page to add your first note";
-            parent.loadingFailedIcon = "pencil";
-            parent.loadingFailed = true;
-          }
-        })
-        .catch(function (error) {
-          parent.loadingFailed = true;
-          if (!error.handled) {
-            EventBus.$emit("unhandledServerErrorToast");
-          }
-        });
-    },
-
-    openNote: function (href, event) {
-      EventBus.$emit("navigate", href, event);
-    },
-  },
-
-  created: function () {
-    this.getNotes();
-  },
-};
-</script>
diff --git a/client/components/SearchInput.vue b/client/components/SearchInput.vue
deleted file mode 100644 (file)
index 2cda8f2..0000000
+++ /dev/null
@@ -1,118 +0,0 @@
-<template>
-  <form v-on:submit.prevent="search" class="w-100">
-    <div class="input-group w-100">
-      <input
-        id="search-input"
-        type="text"
-        inputmode="search"
-        class="form-control"
-        :class="{ highlight: includeHighlightClass }"
-        placeholder="Search"
-        v-model="searchTermInput"
-      />
-      <div class="input-group-append">
-        <button class="btn" type="submit">
-          <b-icon icon="search"></b-icon>
-        </button>
-      </div>
-    </div>
-  </form>
-</template>
-
-<style lang="scss" scoped>
-@import "../colours";
-
-@keyframes highlight {
-  from {
-    background-color: var(--colour-background-highlight);
-  }
-  to {
-    background-color: var(--colour-background-elevated);
-  }
-}
-
-.highlight {
-  animation-name: highlight;
-  animation-duration: 1.5s;
-}
-
-.btn {
-  border: 1px solid var(--colour-border);
-  svg {
-    color: var(--colour-text-muted);
-  }
-}
-
-#search-input {
-  background-color: var(--colour-background-elevated);
-  border-color: var(--colour-border);
-  color: var(--colour-text);
-
-  &:focus {
-    background-color: var(--colour-background-elevated);
-    color: var(--colour-text);
-  }
-
-  &::placeholder {
-    color: var(--colour-text-muted);
-  }
-}
-</style>
-
-<script>
-import * as constants from "../constants.js";
-
-import EventBus from "../eventBus.js";
-
-export default {
-  props: { initialValue: { type: String } },
-
-  data: function () {
-    return {
-      searchTermInput: null,
-      includeHighlightClass: false,
-    };
-  },
-
-  watch: {
-    initialValue: function () {
-      this.init();
-    },
-  },
-
-  methods: {
-    search: function () {
-      if (this.searchTermInput) {
-        this.searchTermInput = this.searchTermInput.trim();
-      }
-      if (this.searchTermInput) {
-        EventBus.$emit(
-          "navigate",
-          `${constants.basePaths.search}?${
-            constants.params.searchTerm
-          }=${encodeURIComponent(this.searchTermInput)}`
-        );
-      } else {
-        EventBus.$emit("showToast", "danger", "Please enter a search term âœ˜");
-      }
-    },
-
-    highlightSearchInput: function () {
-      let parent = this;
-      this.includeHighlightClass = true;
-      setTimeout(function () {
-        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/client/components/SearchResults.vue b/client/components/SearchResults.vue
deleted file mode 100644 (file)
index b9cc3a6..0000000
+++ /dev/null
@@ -1,341 +0,0 @@
-<template>
-  <div class="mb-4">
-    <!-- Input -->
-    <SearchInput :initial-value="searchTerm" class="mb-1"></SearchInput>
-
-    <!-- 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>
-      <!-- Controls -->
-      <div class="mb-3">
-        <select v-model="sortBy" class="bttn sort-select">
-          <option
-            v-for="option in sortOptions"
-            :key="option"
-            :value="option"
-            class="p-0"
-          >
-            Order: {{ sortOptionToString(option) }}
-          </option>
-        </select>
-
-        <button
-          v-if="searchResultsIncludeHighlights"
-          type="button"
-          class="bttn"
-          @click="showHighlights = !showHighlights"
-        >
-          <b-icon :icon="showHighlights ? 'eye-slash' : 'eye'"></b-icon>
-          {{ showHighlights ? "Hide" : "Show" }} Highlights
-        </button>
-      </div>
-
-      <!-- Results -->
-      <div
-        v-for="group in resultsGrouped"
-        :key="group.name"
-        :class="{ 'mb-5': sortByIsGrouped }"
-      >
-        <p v-if="sortByIsGrouped" class="group-name">{{ group.name }}</p>
-        <div
-          v-for="result in group.searchResults"
-          :key="result.title"
-          class="bttn result"
-          :class="{ 'mb-3': searchResultsIncludeHighlights && showHighlights }"
-        >
-          <a :href="result.href" @click.prevent="openNote(result.href, $event)">
-            <div class="d-flex justify-content-between">
-              <p
-                class="result-title"
-                v-html="
-                  showHighlights ? result.titleHighlightsOrTitle : result.title
-                "
-              ></p>
-              <span
-                class="last-modified d-none d-md-block"
-                v-b-tooltip.hover
-                title="Last Modified"
-              >
-                {{ result.lastModifiedAsString }}
-              </span>
-            </div>
-            <p
-              v-show="showHighlights"
-              class="result-contents"
-              v-html="result.contentHighlights"
-            ></p>
-            <div v-show="showHighlights">
-              <span v-for="tag in result.tagMatches" :key="tag" class="tag mr-2"
-                >#{{ tag }}</span
-              >
-            </div>
-          </a>
-        </div>
-      </div>
-    </div>
-  </div>
-</template>
-
-<style lang="scss" scoped>
-@import "../colours";
-
-.sort-select {
-  padding-inline: 6px;
-}
-
-.group-name {
-  padding-left: 8px;
-  font-weight: bold;
-  font-size: 2rem;
-  color: var(--colour-text-very-muted);
-  margin-bottom: 8px;
-}
-
-.result p {
-  margin: 0;
-}
-
-.result-title {
-  color: var(--colour-text);
-}
-
-.last-modified {
-  color: var(--colour-text-muted);
-  font-size: 0.75rem;
-}
-
-.result-contents {
-  color: var(--colour-text-muted);
-}
-</style>
-
-<style lang="scss">
-@import "../colours";
-
-.match {
-  font-weight: bold;
-  color: var(--colour-brand);
-}
-
-.tag {
-  color: white;
-  font-size: 0.75rem;
-  background-color: var(--colour-brand);
-  padding: 2px 6px;
-  border-radius: 4px;
-}
-</style>
-
-<script>
-import * as constants from "../constants.js";
-import * as helpers from "../helpers.js";
-
-import EventBus from "../eventBus.js";
-import LoadingIndicator from "./LoadingIndicator.vue";
-import SearchInput from "./SearchInput.vue";
-import { SearchResult } from "../classes.js";
-import api from "../api.js";
-
-export default {
-  components: {
-    LoadingIndicator,
-    SearchInput,
-  },
-
-  props: {
-    searchTerm: { type: String, required: true },
-  },
-
-  data: function () {
-    return {
-      searchFailed: false,
-      searchFailedMessage: "Failed to load Search Results",
-      searchFailedIcon: null,
-      searchResults: null,
-      searchResultsIncludeHighlights: null,
-      sortBy: 0,
-      showHighlights: true,
-    };
-  },
-
-  computed: {
-    sortByIsGrouped: function () {
-      return this.sortBy == this.sortOptions.title;
-    },
-
-    resultsGrouped: function () {
-      if (this.sortBy == this.sortOptions.title) {
-        return this.resultsByTitle();
-      } else if (this.sortBy == this.sortOptions.lastModified) {
-        return this.resultsByLastModified();
-      } else {
-        // Default
-        return this.resultsByScore();
-      }
-    },
-  },
-
-  watch: {
-    searchTerm: function () {
-      this.init();
-    },
-
-    sortBy: function () {
-      helpers.setSearchParam(constants.params.sortBy, this.sortBy);
-    },
-
-    showHighlights: function () {
-      helpers.setSearchParam(
-        constants.params.showHighlights,
-        this.showHighlights
-      );
-    },
-  },
-
-  methods: {
-    getSearchResults: function () {
-      let parent = this;
-      this.searchFailed = false;
-      this.searchResultsIncludeHighlights = 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 (responseItem) {
-              let searchResult = new SearchResult(responseItem);
-              parent.searchResults.push(searchResult);
-              if (
-                parent.searchResultsIncludeHighlights == false &&
-                searchResult.includesHighlights
-              ) {
-                parent.searchResultsIncludeHighlights = true;
-              }
-            });
-          }
-        })
-        .catch(function (error) {
-          if (!error.handled) {
-            parent.searchFailed = true;
-            EventBus.$emit("unhandledServerErrorToast");
-          }
-        });
-    },
-
-    resultsByScore: function () {
-      return [
-        {
-          name: "_",
-          searchResults: [...this.searchResults].sort(function (
-            searchResultA,
-            searchResultB
-          ) {
-            return searchResultB.score - searchResultA.score;
-          }),
-        },
-      ];
-    },
-
-    resultsByLastModified: function () {
-      return [
-        {
-          name: "_",
-          searchResults: this.searchResults.sort(function (
-            searchResultA,
-            searchResultB
-          ) {
-            return searchResultB.lastModified - searchResultA.lastModified;
-          }),
-        },
-      ];
-    },
-
-    resultsByTitle: function () {
-      // Set up an empty dictionary of groups
-      let notesGroupedDict = {};
-      let specialCharGroupTitle = "#";
-      [specialCharGroupTitle, ...constants.alphabet].forEach(function (group) {
-        notesGroupedDict[group] = [];
-      });
-
-      // Add results to the group dictionary
-      this.searchResults.forEach(function (searchResult) {
-        let firstCharUpper = searchResult.title[0].toUpperCase();
-        if (constants.alphabet.includes(firstCharUpper)) {
-          notesGroupedDict[firstCharUpper].push(searchResult);
-        } else {
-          notesGroupedDict[specialCharGroupTitle].push(searchResult);
-        }
-      });
-
-      // Convert dict to an array skipping empty groups
-      let notesGroupedArray = [];
-      Object.entries(notesGroupedDict).forEach(function (item) {
-        if (item[1].length) {
-          notesGroupedArray.push({
-            name: item[0],
-            searchResults: item[1].sort(function (
-              SearchResultA,
-              SearchResultB
-            ) {
-              // Sort by title within each group
-              return SearchResultA.title.localeCompare(SearchResultB.title);
-            }),
-          });
-        }
-      });
-
-      // Ensure the array is ordered correctly
-      notesGroupedArray.sort(function (groupA, groupB) {
-        return groupA.name.localeCompare(groupB.name);
-      });
-
-      return notesGroupedArray;
-    },
-
-    openNote: function (href, event) {
-      EventBus.$emit("navigate", href, event);
-    },
-
-    sortOptionToString: function (sortOption) {
-      let sortOptionStrings = {
-        0: "Score",
-        1: "Title",
-        2: "Last Modified",
-      };
-      return sortOptionStrings[sortOption];
-    },
-
-    init: function () {
-      this.sortBy = helpers.getSearchParamInt(constants.params.sortBy, 0);
-
-      this.showHighlights = helpers.getSearchParamBool(
-        constants.params.showHighlights,
-        true
-      );
-
-      this.getSearchResults();
-    },
-  },
-
-  created: function () {
-    this.sortOptions = constants.searchSortOptions;
-    this.init();
-  },
-};
-</script>
diff --git a/client/constants.js b/client/constants.js
deleted file mode 100644 (file)
index ac41d31..0000000
+++ /dev/null
@@ -1,55 +0,0 @@
-// Base Paths
-export const basePaths = {
-  home: "/",
-  login: "/login",
-  note: "/note",
-  search: "/search",
-  new: "/new",
-};
-
-// Params
-export const params = {
-  searchTerm: "term",
-  redirect: "redirect",
-  showHighlights: "showHighlights",
-  sortBy: "sortBy",
-};
-
-// Other
-export const alphabet = [
-  "A",
-  "B",
-  "C",
-  "D",
-  "E",
-  "F",
-  "G",
-  "H",
-  "I",
-  "J",
-  "K",
-  "L",
-  "M",
-  "N",
-  "O",
-  "P",
-  "Q",
-  "R",
-  "S",
-  "T",
-  "U",
-  "V",
-  "W",
-  "X",
-  "Y",
-  "Z",
-];
-
-export const searchSortOptions = { score: 0, title: 1, lastModified: 2 };
-
-export const authTypes = {
-  none: "none",
-  readOnly: "read_only",
-  password: "password",
-  totp: "totp",
-};
diff --git a/client/eventBus.js b/client/eventBus.js
deleted file mode 100644 (file)
index dc7e25b..0000000
+++ /dev/null
@@ -1,3 +0,0 @@
-import Vue from "vue";
-
-export default new Vue();
diff --git a/client/global.scss b/client/global.scss
deleted file mode 100644 (file)
index 2cc0569..0000000
+++ /dev/null
@@ -1,73 +0,0 @@
-// Bootstrap
-@import "bootstrap/dist/css/bootstrap.css";
-@import "bootstrap-vue/dist/bootstrap-vue.css";
-@import "node_modules/bootstrap/scss/functions";
-@import "node_modules/bootstrap/scss/variables";
-@import "node_modules/bootstrap/scss/mixins";
-
-// Colours
-@import "./colours.scss";
-
-// Fonts
-@font-face {
-  font-family: "Poppins";
-  font-style: normal;
-  font-weight: 400;
-  font-display: swap;
-  src: url("./assets/fonts/Poppins/Poppins-Regular.ttf");
-}
-
-// Elements & Classes
-body {
-  background-color: var(--colour-background);
-  color: var(--colour-text);
-  font-family: "Poppins", sans-serif;
-}
-
-a {
-  color: inherit;
-  &:hover {
-    text-decoration: none;
-    color: inherit;
-    cursor: pointer;
-  }
-}
-
-.form-control:focus {
-  box-shadow: none;
-  border-color: var(--colour-border);
-}
-
-.bttn {
-  border: 0;
-  background-color: var(--colour-background);
-  border-radius: 4px;
-  padding: 4px 10px;
-  color: var(--colour-text-muted);
-
-  svg {
-    margin-right: 6px;
-  }
-
-  &:hover {
-    background-color: var(--colour-background-tint);
-    cursor: pointer;
-    color: inherit;
-  }
-}
-.dark-theme .bttn:hover {
-  background-color: var(--colour-background-elevated);
-}
-
-.modal-content {
-  background-color: var(--colour-background);
-  color: var(--colour-text);
-}
-
-.modal-content {
-  .modal-header,
-  .modal-body,
-  .modal-footer {
-    border: 0;
-  }
-}
diff --git a/client/helpers.js b/client/helpers.js
deleted file mode 100644 (file)
index 5e45d98..0000000
+++ /dev/null
@@ -1,45 +0,0 @@
-export function getSearchParam(paramName, defaultValue = null) {
-  let urlSearchParams = new URLSearchParams(window.location.search);
-  let paramValue = urlSearchParams.get(paramName);
-  if (paramValue != null) {
-    return paramValue;
-  } else {
-    return defaultValue;
-  }
-}
-
-export function getSearchParamBool(paramName, defaultValue = null) {
-  let paramValue = getSearchParam(paramName)
-  if (paramValue == null) {
-    return defaultValue
-  }
-  let paramValueLowerCase = paramValue.toLowerCase();
-  if (paramValueLowerCase == "true") {
-    return true;
-  } else if (paramValueLowerCase == "false") {
-    return false;
-  } else {
-    return defaultValue;
-  }
-}
-
-export function getSearchParamInt(paramName, defaultValue = null) {
-  let paramValue = getSearchParam(paramName)
-  if (paramValue == null) {
-    return defaultValue
-  }
-  let paramValueInt = parseInt(paramValue);
-  if (!isNaN(paramValueInt)) {
-    return paramValueInt;
-  } else {
-    return defaultValue;
-  }
-}
-
-export function setSearchParam(paramName, value) {
-  let url = new URL(window.location.href);
-  let urlSearchParams = new URLSearchParams(url.search);
-  urlSearchParams.set(paramName, value);
-  url.search = urlSearchParams.toString();
-  window.history.replaceState({}, "", url.toString());
-}
index 853dec5d96d272957e8ae4902b33c48af64e24d7..71cc63a7610a01a8ced4a87f16685e7b547087f8 100644 (file)
@@ -1,40 +1,13 @@
 <!DOCTYPE html>
-<html lang="en" class="h-100">
+<html lang="en">
   <head>
     <meta charset="UTF-8" />
-    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
-    <meta
-      name="viewport"
-      content="width=device-width, initial-scale=1, shrink-to-fit=no"
-    />
-
-    <link
-      rel="apple-touch-icon"
-      sizes="180x180"
-      href="assets/apple-touch-icon.png"
-    />
-    <link
-      rel="icon"
-      type="image/png"
-      sizes="32x32"
-      href="assets/favicon-32x32.png"
-    />
-    <link
-      rel="icon"
-      type="image/png"
-      sizes="16x16"
-      href="assets/favicon-16x16.png"
-    />
-    <link rel="manifest" href="site.webmanifest" />
-    <link rel="mask-icon" href="/safari-pinned-tab.svg" color="#F8A66B" />
-    <link rel="shortcut icon" href="assets/favicon.ico" />
-    <meta name="theme-color" content="#F8A66B" />
-
+    <link rel="icon" href="/favicon.ico" />
+    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
     <title>flatnotes</title>
   </head>
-
-  <body class="h-100 pt-3">
+  <body>
     <div id="app"></div>
-    <script src="index.js" type="module"></script>
+    <script type="module" src="/index.js"></script>
   </body>
 </html>
index 1c2e1ed717d30ebbd893c912b10b0a3c454d1daf..fdfcbaed7a0743bd926c9b65f766847e2678d70a 100644 (file)
@@ -1,14 +1,7 @@
-import "./global.scss"
+import App from "/App.vue";
+import { createApp } from "vue";
+import router from "/router.js";
 
-import { BootstrapVue, IconsPlugin } from "bootstrap-vue";
-
-import App from "./components/App.vue";
-import Vue from "vue";
-
-Vue.use(BootstrapVue);
-Vue.use(IconsPlugin);
-
-new Vue({
-  el: "#app",
-  render: (h) => h(App),
-});
+const app = createApp(App);
+app.use(router);
+app.mount("#app");
diff --git a/client/public/android-chrome-192x192.png b/client/public/android-chrome-192x192.png
deleted file mode 100644 (file)
index 892d6fe..0000000
Binary files a/client/public/android-chrome-192x192.png and /dev/null differ
diff --git a/client/public/android-chrome-512x512.png b/client/public/android-chrome-512x512.png
deleted file mode 100644 (file)
index f5f3774..0000000
Binary files a/client/public/android-chrome-512x512.png and /dev/null differ
diff --git a/client/public/safari-pinned-tab.svg b/client/public/safari-pinned-tab.svg
deleted file mode 100644 (file)
index ddfa208..0000000
+++ /dev/null
@@ -1,30 +0,0 @@
-<?xml version="1.0" standalone="no"?>
-<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 20010904//EN"
- "http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd">
-<svg version="1.0" xmlns="http://www.w3.org/2000/svg"
- width="700.000000pt" height="700.000000pt" viewBox="0 0 700.000000 700.000000"
- preserveAspectRatio="xMidYMid meet">
-<metadata>
-Created by potrace 1.14, written by Peter Selinger 2001-2017
-</metadata>
-<g transform="translate(0.000000,700.000000) scale(0.100000,-0.100000)"
-fill="#000000" stroke="none">
-<path d="M630 6985 c-157 -31 -289 -103 -405 -220 -112 -112 -180 -242 -211
--403 -21 -110 -21 -5614 0 -5724 31 -161 99 -291 211 -403 119 -120 249 -189
-412 -221 111 -21 5614 -21 5725 0 161 31 291 99 403 211 120 119 189 249 221
-412 21 111 21 5614 0 5725 -32 164 -99 292 -216 408 -116 117 -244 184 -408
-216 -105 20 -5631 19 -5732 -1z m2817 -1103 c76 -24 130 -59 198 -128 58 -59
-71 -88 61 -128 -7 -28 -52 -48 -88 -39 -13 3 -48 29 -78 58 -79 75 -112 89
--195 90 -102 0 -166 -33 -280 -149 -267 -270 -491 -784 -609 -1401 -37 -196
--57 -343 -70 -523 l-12 -162 36 -10 c121 -35 207 -42 455 -39 406 5 433 2 472
--57 26 -40 23 -108 -7 -139 l-23 -25 -261 0 c-279 0 -394 -11 -521 -51 -70
--21 -199 -81 -208 -97 -3 -4 -19 -109 -37 -232 -138 -999 -235 -1423 -334
--1459 -69 -26 -164 95 -201 253 -18 78 -8 196 30 369 53 238 91 327 141 327
-13 0 24 6 24 13 0 43 57 575 65 614 4 16 -4 34 -28 59 -51 56 -75 55 -432 -17
--313 -64 -365 -70 -391 -48 -19 15 -18 92 1 129 25 48 80 69 446 165 292 77
-483 157 494 208 2 12 16 121 30 242 52 434 109 674 245 1040 150 400 348 746
-542 944 176 180 362 247 535 193z m1898 -4061 c80 -18 109 -77 65 -134 -30
--37 -85 -45 -375 -51 -143 -4 -372 -11 -510 -16 -1142 -45 -1183 -44 -1285 8
--65 33 -83 67 -60 110 29 52 72 63 288 73 580 28 1768 34 1877 10z"/>
-</g>
-</svg>
diff --git a/client/router.js b/client/router.js
new file mode 100644 (file)
index 0000000..4764c50
--- /dev/null
@@ -0,0 +1,16 @@
+import { createRouter, createWebHistory } from 'vue-router'
+
+import HomeView from '/views/HomeView.vue'
+
+const router = createRouter({
+  history: createWebHistory(import.meta.env.BASE_URL),
+  routes: [
+    {
+      path: '/',
+      name: 'home',
+      component: HomeView
+    }
+  ]
+})
+
+export default router
diff --git a/client/site.webmanifest b/client/site.webmanifest
deleted file mode 100644 (file)
index 692075a..0000000
+++ /dev/null
@@ -1,22 +0,0 @@
-{
-  "name": "flatnotes",
-  "short_name": "flatnotes",
-  "start_url": "/",
-  "icons": [
-    {
-      "src": "/android-chrome-192x192.png",
-      "sizes": "192x192",
-      "type": "image/png",
-      "purpose": "any maskable"
-    },
-    {
-      "src": "/android-chrome-512x512.png",
-      "sizes": "512x512",
-      "type": "image/png",
-      "purpose": "any maskable"
-    }
-  ],
-  "theme_color": "#F8A66B",
-  "background_color": "#ffffff",
-  "display": "standalone"
-}
diff --git a/client/toastui-editor-overrides.scss b/client/toastui-editor-overrides.scss
deleted file mode 100644 (file)
index d2a56b9..0000000
+++ /dev/null
@@ -1,294 +0,0 @@
-@import "./colours.scss";
-
-// Disable checkboxes in view mode. See https://github.com/nhn/tui.editor/issues/1087.
-.note-viewer li.task-list-item {
-  pointer-events: none;
-  a {
-    pointer-events: auto;
-  }
-}
-
-.ProseMirror,
-.toastui-editor-defaultUI .ProseMirror,
-.toastui-editor-md-container .toastui-editor-md-preview {
-  padding: 1rem 0 0 0;
-}
-
-.ProseMirror {
-  height: 100%;
-}
-
-.toastui-editor-contents ul > li::before {
-  // Vertically center the bullet point
-       margin-top: 0.7rem;
-}
-
-// Typography
-.ProseMirror,
-.toastui-editor-contents {
-  font-family: "Poppins", sans-serif;
-  font-size: 1rem;
-}
-
-.toastui-editor-contents,
-.ProseMirror {
-  h1,
-  .toastui-editor-md-heading1,
-  h2,
-  .toastui-editor-md-heading2,
-  h3,
-  .toastui-editor-md-heading3,
-  h4,
-  .toastui-editor-md-heading4,
-  h5,
-  .toastui-editor-md-heading5,
-  h6,
-  .toastui-editor-md-heading6 {
-    font-weight: bold;
-    line-height: 1.4;
-    margin: 1em 0 0.5em 0;
-    padding: 0;
-    border-bottom: none;
-
-    &:first-of-type {
-      margin-top: 0;
-    }
-  }
-
-  h1,
-  .toastui-editor-md-heading1 {
-    font-size: 1.75rem;
-  }
-
-  h2,
-  .toastui-editor-md-heading2 {
-    font-size: 1.6rem;
-  }
-
-  h3,
-  .toastui-editor-md-heading3 {
-    font-size: 1.45rem;
-  }
-
-  h4,
-  .toastui-editor-md-heading4 {
-    font-size: 1.3rem;
-  }
-
-  h5,
-  .toastui-editor-md-heading5 {
-    font-size: 1.15rem;
-  }
-
-  h6,
-  .toastui-editor-md-heading6 {
-    font-size: 1rem;
-  }
-
-  p {
-    line-height: 1.6rem;
-    margin: 0 0 1rem 0;
-  }
-}
-
-// Override the default font-family for code blocks as some of the fallbacks are not monospace
-.toastui-editor-contents code,
-.toastui-editor-contents pre,
-.toastui-editor-md-code,
-.toastui-editor-md-code-block {
-  font-family: Consolas, "Lucida Console", Monaco, "Andale Mono", monospace;
-}
-
-// Colours
-.toastui-editor-defaultUI {
-  border: none;
-}
-
-.toastui-editor-contents {
-  h1,
-  h2,
-  h3,
-  h4,
-  h5,
-  h6 {
-    color: var(--colour-text);
-  }
-  p {
-    color: var(--colour-text);
-  }
-  pre code {
-    color: var(--colour-text);
-  }
-}
-
-.toastui-editor-main {
-  background-color: var(--colour-background);
-}
-
-.toastui-editor-ww-container {
-  background-color: var(--colour-background);
-}
-
-.toastui-editor-contents ul,
-.toastui-editor-contents menu,
-.toastui-editor-contents ol,
-.toastui-editor-contents dir {
-  color: var(--colour-text);
-}
-
-// Code Block
-.toastui-editor-contents pre,
-.toastui-editor-md-code-block-line-background {
-  background-color: var(--colour-background-tint);
-}
-
-.dark-theme .toastui-editor-contents pre,
-.dark-theme .toastui-editor-md-code-block-line-background {
-  background-color: var(--colour-background-elevated);
-}
-
-.token.operator,
-.token.entity,
-.token.url,
-.language-css .token.string,
-.style .token.string {
-  background: none;
-}
-
-// Tables
-.toastui-editor-contents table th {
-  color: var(--colour-text);
-  background-color: var(--colour-background-tint);
-}
-
-.toastui-editor-contents table {
-  color: var(--colour-text);
-}
-
-.toastui-editor-md-table .toastui-editor-md-table-cell {
-  color: var(--colour-text);
-}
-
-// Editor
-.ProseMirror {
-  color: var(--colour-text);
-}
-
-// Toolbar
-.toastui-editor-defaultUI-toolbar {
-  background-color: var(--colour-background);
-  border-bottom-color: var(--colour-border);
-}
-
-.toastui-editor-defaultUI .toastui-editor-md-tab-container {
-  background-color: var(--colour-background);
-  border-bottom-color: var(--colour-border);
-}
-
-.toastui-editor-mode-switch {
-  background-color: var(--colour-background);
-  border-color: var(--colour-border);
-}
-
-.toastui-editor-defaultUI-toolbar button {
-  border: 1px solid var(--colour-background);
-}
-
-.toastui-editor-defaultUI .tab-item.active {
-  background-color: var(--colour-background);
-  color: var(--colour-text);
-  border-color: var(--colour-border);
-}
-
-.toastui-editor-defaultUI .tab-item {
-  background-color: var(--colour-background-tint);
-  color: var(--colour-text-muted);
-  border-color: var(--colour-border);
-}
-
-.toastui-editor-md-tab-container .tab-item.active {
-  border-bottom: none;
-}
-
-.toastui-editor-toolbar-divider {
-  background-color: var(--colour-text);
-}
-
-.dark-theme .toastui-editor-toolbar-icons {
-  // Standard dark theme buttons are dark grey, this position change makes them white
-  background-position-y: -49px;
-}
-
-.toastui-editor-defaultUI-toolbar button:not(:disabled):hover {
-  background-color: var(--colour-background-tint);
-  border: 1px solid var(--colour-background);
-}
-
-.toastui-editor-md-block-quote .toastui-editor-md-marked-text {
-  color: var(--colour-text-muted);
-}
-
-.toastui-editor-md-code,
-.toastui-editor-contents code {
-  background-color: var(--colour-background-tint);
-}
-
-.dark-theme .toastui-editor-md-code,
-.dark-theme .toastui-editor-contents code {
-  background-color: var(--colour-background-elevated);
-}
-
-.toastui-editor-popup {
-  background-color: var(--colour-background);
-  border: 1px solid var(--colour-border);
-}
-
-.toastui-editor-popup-body {
-  label {
-    color: var(--colour-text);
-  }
-  input {
-    background-color: var(--colour-background);
-    border: 1px solid var(--colour-border);
-  }
-}
-
-.toastui-editor-popup-add-table .toastui-editor-table-cell,
-.toastui-editor-popup-add-table .toastui-editor-table-cell.header {
-  background-color: var(--colour-background);
-  border-color: var(--colour-border);
-}
-
-.toastui-editor-popup-add-heading ul li:hover {
-  background-color: var(--colour-background-highlight);
-}
-
-.toastui-editor-popup-add-image .toastui-editor-tabs .tab-item {
-  color: var(--colour-text);
-  background-color: var(--colour-background);
-  border-color: var(--colour-border);
-}
-
-.toastui-editor-popup-add-image .toastui-editor-file-name.has-file {
-  color: var(--colour-text);
-}
-
-.toastui-editor-popup-body input[type="text"] {
-  color: var(--colour-text);
-  background-color: var(--colour-background);
-}
-
-.toastui-editor-dropdown-toolbar {
-  background-color: var(--colour-background);
-  border-color: var(--colour-border);
-}
-
-// Tables
-.toastui-editor-contents table th,
-.toastui-editor-contents table td {
-  border-color: var(--colour-border);
-}
-
-.toastui-editor-contents th p {
-  color: var(--colour-text);
-}
diff --git a/client/tokenStorage.js b/client/tokenStorage.js
deleted file mode 100644 (file)
index dc0fc08..0000000
+++ /dev/null
@@ -1,31 +0,0 @@
-const tokenStorageKey = "token";
-
-function getCookieString(token) {
-  return `${tokenStorageKey}=${token}; path=/attachments; SameSite=Strict`;
-}
-
-export function setToken(token, persist = false) {
-  document.cookie = getCookieString(token);
-  sessionStorage.setItem(tokenStorageKey, token);
-  if (persist === true) {
-    localStorage.setItem(tokenStorageKey, token);
-  }
-}
-
-export function getToken() {
-  return sessionStorage.getItem(tokenStorageKey);
-}
-
-export function loadToken() {
-  const token = localStorage.getItem(tokenStorageKey);
-  if (token != null) {
-    setToken(token, false);
-  }
-}
-
-export function clearToken() {
-  sessionStorage.removeItem(tokenStorageKey);
-  localStorage.removeItem(tokenStorageKey);
-  document.cookie =
-    getCookieString() + "; expires=Thu, 01 Jan 1970 00:00:00 GMT";
-}
diff --git a/client/views/HomeView.vue b/client/views/HomeView.vue
new file mode 100644 (file)
index 0000000..02022f6
--- /dev/null
@@ -0,0 +1 @@
+<template><h1>flatnotes</h1></template>
index 33f3fe1c13510a47228b4ff82780f25591aee0ba..dd50e1f88acc1c1cf6934c6c3f6f141db76b7468 100644 (file)
@@ -8,22 +8,21 @@
       "name": "flatnotes",
       "version": "4.0.3",
       "license": "MIT",
-      "devDependencies": {
-        "@toast-ui/editor-plugin-code-syntax-highlight": "3.1.0",
-        "@toast-ui/vue-editor": "3.2.3",
-        "@vitejs/plugin-vue2": "^2.3.1",
+      "dependencies": {
         "axios": "1.6.8",
-        "bootstrap-vue": "2.23.1",
         "mousetrap": "1.6.5",
+        "vue": "^3.4.24",
+        "vue-router": "^4.3.2"
+      },
+      "devDependencies": {
+        "@vitejs/plugin-vue": "^5.0.4",
         "vite": "^5.2.9"
       }
     },
     "node_modules/@babel/parser": {
-      "version": "7.20.15",
-      "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.20.15.tgz",
-      "integrity": "sha512-DI4a1oZuf8wC+oAJA9RW6ga3Zbe8RZFt7kD9i4qAspz3I/yHet1VvC3DiSy/fsUvv5pvJuNPh0LPOdCcqinDPg==",
-      "dev": true,
-      "peer": true,
+      "version": "7.24.4",
+      "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.24.4.tgz",
+      "integrity": "sha512-zTvEBcghmeBma9QIGunWevvBAp4/Qu9Bdq+2k0Ot4fVMD6v3dsC9WOcRSKk7tRRyBM/53yKMJko9xOatGQAwSg==",
       "bin": {
         "parser": "bin/babel-parser.js"
       },
       }
     },
     "node_modules/@jridgewell/sourcemap-codec": {
-      "version": "1.4.14",
-      "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz",
-      "integrity": "sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==",
-      "dev": true,
-      "optional": true,
-      "peer": true
+      "version": "1.4.15",
+      "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz",
+      "integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg=="
     },
     "node_modules/@jridgewell/trace-mapping": {
       "version": "0.3.25",
         "@jridgewell/sourcemap-codec": "^1.4.14"
       }
     },
-    "node_modules/@nuxt/opencollective": {
-      "version": "0.3.3",
-      "resolved": "https://registry.npmjs.org/@nuxt/opencollective/-/opencollective-0.3.3.tgz",
-      "integrity": "sha512-6IKCd+gP0HliixqZT/p8nW3tucD6Sv/u/eR2A9X4rxT/6hXlMzA4GZQzq4d2qnBAwSwGpmKyzkyTjNjrhaA25A==",
-      "dev": true,
-      "dependencies": {
-        "chalk": "^4.1.0",
-        "consola": "^2.15.0",
-        "node-fetch": "^2.6.7"
-      },
-      "bin": {
-        "opencollective": "bin/opencollective.js"
-      },
-      "engines": {
-        "node": ">=8.0.0",
-        "npm": ">=5.0.0"
-      }
-    },
     "node_modules/@rollup/rollup-android-arm-eabi": {
       "version": "4.14.3",
       "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.14.3.tgz",
         "win32"
       ]
     },
-    "node_modules/@toast-ui/editor": {
-      "version": "3.2.2",
-      "resolved": "https://registry.npmjs.org/@toast-ui/editor/-/editor-3.2.2.tgz",
-      "integrity": "sha512-ASX7LFjN2ZYQJrwmkUajPs7DRr9FsM1+RQ82CfTO0Y5ZXorBk1VZS4C2Dpxinx9kl55V4F8/A2h2QF4QMDtRbA==",
-      "dev": true,
-      "dependencies": {
-        "dompurify": "^2.3.3",
-        "prosemirror-commands": "^1.1.9",
-        "prosemirror-history": "^1.1.3",
-        "prosemirror-inputrules": "^1.1.3",
-        "prosemirror-keymap": "^1.1.4",
-        "prosemirror-model": "^1.14.1",
-        "prosemirror-state": "^1.3.4",
-        "prosemirror-view": "^1.18.7"
-      }
-    },
-    "node_modules/@toast-ui/editor-plugin-code-syntax-highlight": {
-      "version": "3.1.0",
-      "resolved": "https://registry.npmjs.org/@toast-ui/editor-plugin-code-syntax-highlight/-/editor-plugin-code-syntax-highlight-3.1.0.tgz",
-      "integrity": "sha512-OgX5pZiTnHREoTTXDAFu1k6RzEspGOxeJNRlt/Lnoi1GvLbIpUTTbBcls9becpXT/Qdls++8G3r5C60cVdellA==",
-      "dev": true,
-      "dependencies": {
-        "prismjs": "^1.23.0"
-      }
-    },
-    "node_modules/@toast-ui/vue-editor": {
-      "version": "3.2.3",
-      "resolved": "https://registry.npmjs.org/@toast-ui/vue-editor/-/vue-editor-3.2.3.tgz",
-      "integrity": "sha512-IjoV5tBh/yesIuqRqmOQx1+F0oeeAbIeBA7edMTawIXHQXBeJ1qzGHLTY5NWrUQ6BBtV8CDBeedjnVsJ+mHjKQ==",
-      "dev": true,
-      "dependencies": {
-        "@toast-ui/editor": "^3.2.2"
-      },
-      "peerDependencies": {
-        "vue": "^2.5.0"
-      }
-    },
     "node_modules/@types/estree": {
       "version": "1.0.5",
       "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.5.tgz",
       "integrity": "sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==",
       "dev": true
     },
-    "node_modules/@vitejs/plugin-vue2": {
-      "version": "2.3.1",
-      "resolved": "https://registry.npmjs.org/@vitejs/plugin-vue2/-/plugin-vue2-2.3.1.tgz",
-      "integrity": "sha512-/ksaaz2SRLN11JQhLdEUhDzOn909WEk99q9t9w+N12GjQCljzv7GyvAbD/p20aBUjHkvpGOoQ+FCOkG+mjDF4A==",
+    "node_modules/@vitejs/plugin-vue": {
+      "version": "5.0.4",
+      "resolved": "https://registry.npmjs.org/@vitejs/plugin-vue/-/plugin-vue-5.0.4.tgz",
+      "integrity": "sha512-WS3hevEszI6CEVEx28F8RjTX97k3KsrcY6kvTg7+Whm5y3oYvcqzVeGCU3hxSAn4uY2CLCkeokkGKpoctccilQ==",
       "dev": true,
       "engines": {
-        "node": "^14.18.0 || >= 16.0.0"
+        "node": "^18.0.0 || >=20.0.0"
       },
       "peerDependencies": {
-        "vite": "^3.0.0 || ^4.0.0 || ^5.0.0",
-        "vue": "^2.7.0-0"
+        "vite": "^5.0.0",
+        "vue": "^3.2.25"
+      }
+    },
+    "node_modules/@vue/compiler-core": {
+      "version": "3.4.24",
+      "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.4.24.tgz",
+      "integrity": "sha512-vbW/tgbwJYj62N/Ww99x0zhFTkZDTcGh3uwJEuadZ/nF9/xuFMC4693P9r+3sxGXISABpDKvffY5ApH9pmdd1A==",
+      "dependencies": {
+        "@babel/parser": "^7.24.4",
+        "@vue/shared": "3.4.24",
+        "entities": "^4.5.0",
+        "estree-walker": "^2.0.2",
+        "source-map-js": "^1.2.0"
+      }
+    },
+    "node_modules/@vue/compiler-dom": {
+      "version": "3.4.24",
+      "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.4.24.tgz",
+      "integrity": "sha512-4XgABML/4cNndVsQndG6BbGN7+EoisDwi3oXNovqL/4jdNhwvP8/rfRMTb6FxkxIxUUtg6AI1/qZvwfSjxJiWA==",
+      "dependencies": {
+        "@vue/compiler-core": "3.4.24",
+        "@vue/shared": "3.4.24"
       }
     },
     "node_modules/@vue/compiler-sfc": {
-      "version": "2.7.14",
-      "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-2.7.14.tgz",
-      "integrity": "sha512-aNmNHyLPsw+sVvlQFQ2/8sjNuLtK54TC6cuKnVzAY93ks4ZBrvwQSnkkIh7bsbNhum5hJBS00wSDipQ937f5DA==",
-      "dev": true,
-      "peer": true,
+      "version": "3.4.24",
+      "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.4.24.tgz",
+      "integrity": "sha512-nRAlJUK02FTWfA2nuvNBAqsDZuERGFgxZ8sGH62XgFSvMxO2URblzulExsmj4gFZ8e+VAyDooU9oAoXfEDNxTA==",
       "dependencies": {
-        "@babel/parser": "^7.18.4",
-        "postcss": "^8.4.14",
-        "source-map": "^0.6.1"
+        "@babel/parser": "^7.24.4",
+        "@vue/compiler-core": "3.4.24",
+        "@vue/compiler-dom": "3.4.24",
+        "@vue/compiler-ssr": "3.4.24",
+        "@vue/shared": "3.4.24",
+        "estree-walker": "^2.0.2",
+        "magic-string": "^0.30.10",
+        "postcss": "^8.4.38",
+        "source-map-js": "^1.2.0"
       }
     },
-    "node_modules/@vue/compiler-sfc/node_modules/picocolors": {
-      "version": "1.0.0",
-      "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz",
-      "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==",
-      "dev": true,
-      "peer": true
+    "node_modules/@vue/compiler-ssr": {
+      "version": "3.4.24",
+      "resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.4.24.tgz",
+      "integrity": "sha512-ZsAtr4fhaUFnVcDqwW3bYCSDwq+9Gk69q2r/7dAHDrOMw41kylaMgOP4zRnn6GIEJkQznKgrMOGPMFnLB52RbQ==",
+      "dependencies": {
+        "@vue/compiler-dom": "3.4.24",
+        "@vue/shared": "3.4.24"
+      }
     },
-    "node_modules/@vue/compiler-sfc/node_modules/postcss": {
-      "version": "8.4.21",
-      "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.21.tgz",
-      "integrity": "sha512-tP7u/Sn/dVxK2NnruI4H9BG+x+Wxz6oeZ1cJ8P6G/PZY0IKk4k/63TDsQf2kQq3+qoJeLm2kIBUNlZe3zgb4Zg==",
-      "dev": true,
-      "funding": [
-        {
-          "type": "opencollective",
-          "url": "https://opencollective.com/postcss/"
-        },
-        {
-          "type": "tidelift",
-          "url": "https://tidelift.com/funding/github/npm/postcss"
-        }
-      ],
-      "peer": true,
+    "node_modules/@vue/devtools-api": {
+      "version": "6.6.1",
+      "resolved": "https://registry.npmjs.org/@vue/devtools-api/-/devtools-api-6.6.1.tgz",
+      "integrity": "sha512-LgPscpE3Vs0x96PzSSB4IGVSZXZBZHpfxs+ZA1d+VEPwHdOXowy/Y2CsvCAIFrf+ssVU1pD1jidj505EpUnfbA=="
+    },
+    "node_modules/@vue/reactivity": {
+      "version": "3.4.24",
+      "resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.4.24.tgz",
+      "integrity": "sha512-nup3fSYg4i4LtNvu9slF/HF/0dkMQYfepUdORBcMSsankzRPzE7ypAFurpwyRBfU1i7Dn1kcwpYsE1wETSh91g==",
       "dependencies": {
-        "nanoid": "^3.3.4",
-        "picocolors": "^1.0.0",
-        "source-map-js": "^1.0.2"
-      },
-      "engines": {
-        "node": "^10 || ^12 || >=14"
+        "@vue/shared": "3.4.24"
       }
     },
-    "node_modules/ansi-styles": {
-      "version": "4.3.0",
-      "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
-      "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
-      "dev": true,
+    "node_modules/@vue/runtime-core": {
+      "version": "3.4.24",
+      "resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.4.24.tgz",
+      "integrity": "sha512-c7iMfj6cJMeAG3s5yOn9Rc5D9e2/wIuaozmGf/ICGCY3KV5H7mbTVdvEkd4ZshTq7RUZqj2k7LMJWVx+EBiY1g==",
       "dependencies": {
-        "color-convert": "^2.0.1"
-      },
-      "engines": {
-        "node": ">=8"
+        "@vue/reactivity": "3.4.24",
+        "@vue/shared": "3.4.24"
+      }
+    },
+    "node_modules/@vue/runtime-dom": {
+      "version": "3.4.24",
+      "resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.4.24.tgz",
+      "integrity": "sha512-uXKzuh/Emfad2Y7Qm0ABsLZZV6H3mAJ5ZVqmAOlrNQRf+T5mxpPGZBfec1hkP41t6h6FwF6RSGCs/gd8WbuySQ==",
+      "dependencies": {
+        "@vue/runtime-core": "3.4.24",
+        "@vue/shared": "3.4.24",
+        "csstype": "^3.1.3"
+      }
+    },
+    "node_modules/@vue/server-renderer": {
+      "version": "3.4.24",
+      "resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.4.24.tgz",
+      "integrity": "sha512-H+DLK4sQF6sRgzKyofmlEVBIV/9KrQU6HIV7nt6yIwSGGKvSwlV8pqJlebUKLpbXaNHugdSfAbP6YmXF69lxow==",
+      "dependencies": {
+        "@vue/compiler-ssr": "3.4.24",
+        "@vue/shared": "3.4.24"
       },
-      "funding": {
-        "url": "https://github.com/chalk/ansi-styles?sponsor=1"
+      "peerDependencies": {
+        "vue": "3.4.24"
       }
     },
+    "node_modules/@vue/shared": {
+      "version": "3.4.24",
+      "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.4.24.tgz",
+      "integrity": "sha512-BW4tajrJBM9AGAknnyEw5tO2xTmnqgup0VTnDAMcxYmqOX0RG0b9aSUGAbEKolD91tdwpA6oCwbltoJoNzpItw=="
+    },
     "node_modules/asynckit": {
       "version": "0.4.0",
       "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
-      "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==",
-      "dev": true
+      "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q=="
     },
     "node_modules/axios": {
       "version": "1.6.8",
       "resolved": "https://registry.npmjs.org/axios/-/axios-1.6.8.tgz",
       "integrity": "sha512-v/ZHtJDU39mDpyBoFVkETcd/uNdxrWRrg3bKpOKzXFA6Bvqopts6ALSMU3y6ijYxbw2B+wPrIv46egTzJXCLGQ==",
-      "dev": true,
       "dependencies": {
         "follow-redirects": "^1.15.6",
         "form-data": "^4.0.0",
       "version": "4.0.0",
       "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz",
       "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==",
-      "dev": true,
       "dependencies": {
         "asynckit": "^0.4.0",
         "combined-stream": "^1.0.8",
         "node": ">= 6"
       }
     },
-    "node_modules/bootstrap": {
-      "version": "4.6.2",
-      "resolved": "https://registry.npmjs.org/bootstrap/-/bootstrap-4.6.2.tgz",
-      "integrity": "sha512-51Bbp/Uxr9aTuy6ca/8FbFloBUJZLHwnhTcnjIeRn2suQWsWzcuJhGjKDB5eppVte/8oCdOL3VuwxvZDUggwGQ==",
-      "dev": true,
-      "funding": [
-        {
-          "type": "github",
-          "url": "https://github.com/sponsors/twbs"
-        },
-        {
-          "type": "opencollective",
-          "url": "https://opencollective.com/bootstrap"
-        }
-      ],
-      "peerDependencies": {
-        "jquery": "1.9.1 - 3",
-        "popper.js": "^1.16.1"
-      }
-    },
-    "node_modules/bootstrap-vue": {
-      "version": "2.23.1",
-      "resolved": "https://registry.npmjs.org/bootstrap-vue/-/bootstrap-vue-2.23.1.tgz",
-      "integrity": "sha512-SEWkG4LzmMuWjQdSYmAQk1G/oOKm37dtNfjB5kxq0YafnL2W6qUAmeDTcIZVbPiQd2OQlIkWOMPBRGySk/zGsg==",
-      "dev": true,
-      "hasInstallScript": true,
-      "dependencies": {
-        "@nuxt/opencollective": "^0.3.2",
-        "bootstrap": "^4.6.1",
-        "popper.js": "^1.16.1",
-        "portal-vue": "^2.1.7",
-        "vue-functional-data-merge": "^3.1.0"
-      }
-    },
     "node_modules/buffer-from": {
       "version": "1.1.2",
       "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz",
       "optional": true,
       "peer": true
     },
-    "node_modules/chalk": {
-      "version": "4.1.2",
-      "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
-      "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==",
-      "dev": true,
-      "dependencies": {
-        "ansi-styles": "^4.1.0",
-        "supports-color": "^7.1.0"
-      },
-      "engines": {
-        "node": ">=10"
-      },
-      "funding": {
-        "url": "https://github.com/chalk/chalk?sponsor=1"
-      }
-    },
-    "node_modules/color-convert": {
-      "version": "2.0.1",
-      "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
-      "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
-      "dev": true,
-      "dependencies": {
-        "color-name": "~1.1.4"
-      },
-      "engines": {
-        "node": ">=7.0.0"
-      }
-    },
-    "node_modules/color-name": {
-      "version": "1.1.4",
-      "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
-      "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
-      "dev": true
-    },
     "node_modules/combined-stream": {
       "version": "1.0.8",
       "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
       "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==",
-      "dev": true,
       "dependencies": {
         "delayed-stream": "~1.0.0"
       },
       "optional": true,
       "peer": true
     },
-    "node_modules/consola": {
-      "version": "2.15.3",
-      "resolved": "https://registry.npmjs.org/consola/-/consola-2.15.3.tgz",
-      "integrity": "sha512-9vAdYbHj6x2fLKC4+oPH0kFzY/orMZyG2Aj+kNylHxKGJ/Ed4dpNyAQYwJOdqO4zdM7XpVHmyejQDcQHrnuXbw==",
-      "dev": true
-    },
     "node_modules/csstype": {
-      "version": "3.1.1",
-      "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.1.tgz",
-      "integrity": "sha512-DJR/VvkAvSZW9bTouZue2sSxDwdTN92uHjqeKVm+0dAqdfNykRzQ95tay8aXMBAAPpUiq4Qcug2L7neoRh2Egw==",
-      "dev": true,
-      "peer": true
+      "version": "3.1.3",
+      "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz",
+      "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw=="
     },
     "node_modules/delayed-stream": {
       "version": "1.0.0",
       "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
       "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==",
-      "dev": true,
       "engines": {
         "node": ">=0.4.0"
       }
     },
-    "node_modules/dompurify": {
-      "version": "2.5.0",
-      "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-2.5.0.tgz",
-      "integrity": "sha512-5RXhAXSCrKTqt9pSbobT9PVRX+oPpENplTZqCiK1l0ya+ZOzwo9kqsGLbYRsAhzIiLCwKEy99XKSSrqnRTLVcw==",
-      "dev": true
+    "node_modules/entities": {
+      "version": "4.5.0",
+      "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz",
+      "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==",
+      "engines": {
+        "node": ">=0.12"
+      },
+      "funding": {
+        "url": "https://github.com/fb55/entities?sponsor=1"
+      }
     },
     "node_modules/esbuild": {
       "version": "0.20.2",
         "@esbuild/win32-x64": "0.20.2"
       }
     },
+    "node_modules/estree-walker": {
+      "version": "2.0.2",
+      "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz",
+      "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w=="
+    },
     "node_modules/follow-redirects": {
       "version": "1.15.6",
       "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.6.tgz",
       "integrity": "sha512-wWN62YITEaOpSK584EZXJafH1AGpO8RVgElfkuXbTOrPX4fIfOyEpW/CsiNd8JdYrAoOvafRTOEnvsO++qCqFA==",
-      "dev": true,
       "funding": [
         {
           "type": "individual",
         "node": "^8.16.0 || ^10.6.0 || >=11.0.0"
       }
     },
-    "node_modules/has-flag": {
-      "version": "4.0.0",
-      "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
-      "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
-      "dev": true,
-      "engines": {
-        "node": ">=8"
-      }
-    },
     "node_modules/immutable": {
       "version": "4.2.4",
       "resolved": "https://registry.npmjs.org/immutable/-/immutable-4.2.4.tgz",
         "node": ">=0.10.0"
       }
     },
-    "node_modules/jquery": {
-      "version": "3.6.3",
-      "resolved": "https://registry.npmjs.org/jquery/-/jquery-3.6.3.tgz",
-      "integrity": "sha512-bZ5Sy3YzKo9Fyc8wH2iIQK4JImJ6R0GWI9kL1/k7Z91ZBNgkRXE6U0JfHIizZbort8ZunhSI3jw9I6253ahKfg==",
-      "dev": true,
-      "peer": true
+    "node_modules/magic-string": {
+      "version": "0.30.10",
+      "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.10.tgz",
+      "integrity": "sha512-iIRwTIf0QKV3UAnYK4PU8uiEc4SRh5jX0mwpIwETPpHdhVM4f53RSwS/vXvN1JhGX+Cs7B8qIq3d6AH49O5fAQ==",
+      "dependencies": {
+        "@jridgewell/sourcemap-codec": "^1.4.15"
+      }
     },
     "node_modules/mime-db": {
       "version": "1.52.0",
       "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
       "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==",
-      "dev": true,
       "engines": {
         "node": ">= 0.6"
       }
       "version": "2.1.35",
       "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz",
       "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
-      "dev": true,
       "dependencies": {
         "mime-db": "1.52.0"
       },
     "node_modules/mousetrap": {
       "version": "1.6.5",
       "resolved": "https://registry.npmjs.org/mousetrap/-/mousetrap-1.6.5.tgz",
-      "integrity": "sha512-QNo4kEepaIBwiT8CDhP98umTetp+JNfQYBWvC1pc6/OAibuXtRcxZ58Qz8skvEHYvURne/7R8T5VoOI7rDsEUA==",
-      "dev": true
+      "integrity": "sha512-QNo4kEepaIBwiT8CDhP98umTetp+JNfQYBWvC1pc6/OAibuXtRcxZ58Qz8skvEHYvURne/7R8T5VoOI7rDsEUA=="
     },
     "node_modules/nanoid": {
       "version": "3.3.7",
       "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz",
       "integrity": "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==",
-      "dev": true,
       "funding": [
         {
           "type": "github",
         "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
       }
     },
-    "node_modules/node-fetch": {
-      "version": "2.6.9",
-      "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.9.tgz",
-      "integrity": "sha512-DJm/CJkZkRjKKj4Zi4BsKVZh3ValV5IR5s7LVZnW+6YMh0W1BfNA8XSs6DLMGYlId5F3KnA70uu2qepcR08Qqg==",
-      "dev": true,
-      "dependencies": {
-        "whatwg-url": "^5.0.0"
-      },
-      "engines": {
-        "node": "4.x || >=6.0.0"
-      },
-      "peerDependencies": {
-        "encoding": "^0.1.0"
-      },
-      "peerDependenciesMeta": {
-        "encoding": {
-          "optional": true
-        }
-      }
-    },
     "node_modules/normalize-path": {
       "version": "3.0.0",
       "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz",
         "node": ">=0.10.0"
       }
     },
-    "node_modules/orderedmap": {
-      "version": "2.1.1",
-      "resolved": "https://registry.npmjs.org/orderedmap/-/orderedmap-2.1.1.tgz",
-      "integrity": "sha512-TvAWxi0nDe1j/rtMcWcIj94+Ffe6n7zhow33h40SKxmsmozs6dz/e+EajymfoFcHd7sxNn8yHM8839uixMOV6g==",
-      "dev": true
+    "node_modules/picocolors": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz",
+      "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ=="
     },
     "node_modules/picomatch": {
       "version": "2.3.1",
         "url": "https://github.com/sponsors/jonschlinkert"
       }
     },
-    "node_modules/popper.js": {
-      "version": "1.16.1",
-      "resolved": "https://registry.npmjs.org/popper.js/-/popper.js-1.16.1.tgz",
-      "integrity": "sha512-Wb4p1J4zyFTbM+u6WuO4XstYx4Ky9Cewe4DWrel7B0w6VVICvPwdOpotjzcf6eD8TsckVnIMNONQyPIUFOUbCQ==",
-      "deprecated": "You can find the new Popper v2 at @popperjs/core, this package is dedicated to the legacy v1",
-      "dev": true,
-      "funding": {
-        "type": "opencollective",
-        "url": "https://opencollective.com/popperjs"
-      }
-    },
-    "node_modules/portal-vue": {
-      "version": "2.1.7",
-      "resolved": "https://registry.npmjs.org/portal-vue/-/portal-vue-2.1.7.tgz",
-      "integrity": "sha512-+yCno2oB3xA7irTt0EU5Ezw22L2J51uKAacE/6hMPMoO/mx3h4rXFkkBkT4GFsMDv/vEe8TNKC3ujJJ0PTwb6g==",
-      "dev": true,
-      "peerDependencies": {
-        "vue": "^2.5.18"
-      }
-    },
-    "node_modules/prismjs": {
-      "version": "1.29.0",
-      "resolved": "https://registry.npmjs.org/prismjs/-/prismjs-1.29.0.tgz",
-      "integrity": "sha512-Kx/1w86q/epKcmte75LNrEoT+lX8pBpavuAbvJWRXar7Hz8jrtF+e3vY751p0R8H9HdArwaCTNDDzHg/ScJK1Q==",
-      "dev": true,
-      "engines": {
-        "node": ">=6"
-      }
-    },
-    "node_modules/prosemirror-commands": {
-      "version": "1.5.2",
-      "resolved": "https://registry.npmjs.org/prosemirror-commands/-/prosemirror-commands-1.5.2.tgz",
-      "integrity": "sha512-hgLcPaakxH8tu6YvVAaILV2tXYsW3rAdDR8WNkeKGcgeMVQg3/TMhPdVoh7iAmfgVjZGtcOSjKiQaoeKjzd2mQ==",
-      "dev": true,
-      "dependencies": {
-        "prosemirror-model": "^1.0.0",
-        "prosemirror-state": "^1.0.0",
-        "prosemirror-transform": "^1.0.0"
-      }
-    },
-    "node_modules/prosemirror-history": {
-      "version": "1.4.0",
-      "resolved": "https://registry.npmjs.org/prosemirror-history/-/prosemirror-history-1.4.0.tgz",
-      "integrity": "sha512-UUiGzDVcqo1lovOPdi9YxxUps3oBFWAIYkXLu3Ot+JPv1qzVogRbcizxK3LhHmtaUxclohgiOVesRw5QSlMnbQ==",
-      "dev": true,
-      "dependencies": {
-        "prosemirror-state": "^1.2.2",
-        "prosemirror-transform": "^1.0.0",
-        "prosemirror-view": "^1.31.0",
-        "rope-sequence": "^1.3.0"
-      }
-    },
-    "node_modules/prosemirror-inputrules": {
-      "version": "1.4.0",
-      "resolved": "https://registry.npmjs.org/prosemirror-inputrules/-/prosemirror-inputrules-1.4.0.tgz",
-      "integrity": "sha512-6ygpPRuTJ2lcOXs9JkefieMst63wVJBgHZGl5QOytN7oSZs3Co/BYbc3Yx9zm9H37Bxw8kVzCnDsihsVsL4yEg==",
-      "dev": true,
-      "dependencies": {
-        "prosemirror-state": "^1.0.0",
-        "prosemirror-transform": "^1.0.0"
-      }
-    },
-    "node_modules/prosemirror-keymap": {
-      "version": "1.2.2",
-      "resolved": "https://registry.npmjs.org/prosemirror-keymap/-/prosemirror-keymap-1.2.2.tgz",
-      "integrity": "sha512-EAlXoksqC6Vbocqc0GtzCruZEzYgrn+iiGnNjsJsH4mrnIGex4qbLdWWNza3AW5W36ZRrlBID0eM6bdKH4OStQ==",
-      "dev": true,
-      "dependencies": {
-        "prosemirror-state": "^1.0.0",
-        "w3c-keyname": "^2.2.0"
-      }
-    },
-    "node_modules/prosemirror-model": {
-      "version": "1.20.0",
-      "resolved": "https://registry.npmjs.org/prosemirror-model/-/prosemirror-model-1.20.0.tgz",
-      "integrity": "sha512-q7AY7vMjKYqDCeoedgUiAgrLabliXxndJuuFmcmc2+YU1SblvnOiG2WEACF2lwAZsMlfLpiAilA3L+TWlDqIsQ==",
-      "dev": true,
-      "dependencies": {
-        "orderedmap": "^2.0.0"
-      }
-    },
-    "node_modules/prosemirror-state": {
-      "version": "1.4.3",
-      "resolved": "https://registry.npmjs.org/prosemirror-state/-/prosemirror-state-1.4.3.tgz",
-      "integrity": "sha512-goFKORVbvPuAQaXhpbemJFRKJ2aixr+AZMGiquiqKxaucC6hlpHNZHWgz5R7dS4roHiwq9vDctE//CZ++o0W1Q==",
-      "dev": true,
-      "dependencies": {
-        "prosemirror-model": "^1.0.0",
-        "prosemirror-transform": "^1.0.0",
-        "prosemirror-view": "^1.27.0"
-      }
-    },
-    "node_modules/prosemirror-transform": {
-      "version": "1.8.0",
-      "resolved": "https://registry.npmjs.org/prosemirror-transform/-/prosemirror-transform-1.8.0.tgz",
-      "integrity": "sha512-BaSBsIMv52F1BVVMvOmp1yzD3u65uC3HTzCBQV1WDPqJRQ2LuHKcyfn0jwqodo8sR9vVzMzZyI+Dal5W9E6a9A==",
-      "dev": true,
-      "dependencies": {
-        "prosemirror-model": "^1.0.0"
-      }
-    },
-    "node_modules/prosemirror-view": {
-      "version": "1.33.4",
-      "resolved": "https://registry.npmjs.org/prosemirror-view/-/prosemirror-view-1.33.4.tgz",
-      "integrity": "sha512-xQqAhH8/HGleVpKDhQsrd+oqdyeKMxFtdCWDxWMmP+n0k27fBpyUqa8pA+RB5cFY8rqDDc1hll69aRZQa7UaAw==",
-      "dev": true,
+    "node_modules/postcss": {
+      "version": "8.4.38",
+      "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.38.tgz",
+      "integrity": "sha512-Wglpdk03BSfXkHoQa3b/oulrotAkwrlLDRSOb9D0bN86FdRyE9lppSp33aHNPgBa0JKCoB+drFLZkQoRRYae5A==",
+      "funding": [
+        {
+          "type": "opencollective",
+          "url": "https://opencollective.com/postcss/"
+        },
+        {
+          "type": "tidelift",
+          "url": "https://tidelift.com/funding/github/npm/postcss"
+        },
+        {
+          "type": "github",
+          "url": "https://github.com/sponsors/ai"
+        }
+      ],
       "dependencies": {
-        "prosemirror-model": "^1.20.0",
-        "prosemirror-state": "^1.0.0",
-        "prosemirror-transform": "^1.1.0"
+        "nanoid": "^3.3.7",
+        "picocolors": "^1.0.0",
+        "source-map-js": "^1.2.0"
+      },
+      "engines": {
+        "node": "^10 || ^12 || >=14"
       }
     },
     "node_modules/proxy-from-env": {
       "version": "1.1.0",
       "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz",
-      "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==",
-      "dev": true
+      "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg=="
     },
     "node_modules/rollup": {
       "version": "4.14.3",
         "fsevents": "~2.3.2"
       }
     },
-    "node_modules/rope-sequence": {
-      "version": "1.3.4",
-      "resolved": "https://registry.npmjs.org/rope-sequence/-/rope-sequence-1.3.4.tgz",
-      "integrity": "sha512-UT5EDe2cu2E/6O4igUr5PSFs23nvvukicWHx6GnOPlHAiiYbzNuCRQCuiUdHJQcqKalLKlrYJnjY0ySGsXNQXQ==",
-      "dev": true
-    },
     "node_modules/sass": {
       "version": "1.74.1",
       "resolved": "https://registry.npmjs.org/sass/-/sass-1.74.1.tgz",
       "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
       "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
       "dev": true,
+      "optional": true,
       "peer": true,
       "engines": {
         "node": ">=0.10.0"
       "version": "1.2.0",
       "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.0.tgz",
       "integrity": "sha512-itJW8lvSA0TXEphiRoawsCksnlf8SyvmFzIhltqAHluXd88pkCd+cXJVHTDwdCr0IzwptSm035IHQktUu1QUMg==",
-      "dev": true,
       "engines": {
         "node": ">=0.10.0"
       }
         "source-map": "^0.6.0"
       }
     },
-    "node_modules/supports-color": {
-      "version": "7.2.0",
-      "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
-      "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
-      "dev": true,
-      "dependencies": {
-        "has-flag": "^4.0.0"
-      },
-      "engines": {
-        "node": ">=8"
-      }
-    },
     "node_modules/terser": {
       "version": "5.30.3",
       "resolved": "https://registry.npmjs.org/terser/-/terser-5.30.3.tgz",
         "node": ">=0.4.0"
       }
     },
-    "node_modules/tr46": {
-      "version": "0.0.3",
-      "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz",
-      "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==",
-      "dev": true
-    },
     "node_modules/vite": {
       "version": "5.2.9",
       "resolved": "https://registry.npmjs.org/vite/-/vite-5.2.9.tgz",
         }
       }
     },
-    "node_modules/vite/node_modules/picocolors": {
-      "version": "1.0.0",
-      "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz",
-      "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==",
-      "dev": true
-    },
-    "node_modules/vite/node_modules/postcss": {
-      "version": "8.4.38",
-      "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.38.tgz",
-      "integrity": "sha512-Wglpdk03BSfXkHoQa3b/oulrotAkwrlLDRSOb9D0bN86FdRyE9lppSp33aHNPgBa0JKCoB+drFLZkQoRRYae5A==",
-      "dev": true,
-      "funding": [
-        {
-          "type": "opencollective",
-          "url": "https://opencollective.com/postcss/"
-        },
-        {
-          "type": "tidelift",
-          "url": "https://tidelift.com/funding/github/npm/postcss"
-        },
-        {
-          "type": "github",
-          "url": "https://github.com/sponsors/ai"
-        }
-      ],
-      "dependencies": {
-        "nanoid": "^3.3.7",
-        "picocolors": "^1.0.0",
-        "source-map-js": "^1.2.0"
-      },
-      "engines": {
-        "node": "^10 || ^12 || >=14"
-      }
-    },
     "node_modules/vue": {
-      "version": "2.7.14",
-      "resolved": "https://registry.npmjs.org/vue/-/vue-2.7.14.tgz",
-      "integrity": "sha512-b2qkFyOM0kwqWFuQmgd4o+uHGU7T+2z3T+WQp8UBjADfEv2n4FEMffzBmCKNP0IGzOEEfYjvtcC62xaSKeQDrQ==",
-      "dev": true,
-      "peer": true,
+      "version": "3.4.24",
+      "resolved": "https://registry.npmjs.org/vue/-/vue-3.4.24.tgz",
+      "integrity": "sha512-NPdx7dLGyHmKHGRRU5bMRYVE+rechR+KDU5R2tSTNG36PuMwbfAJ+amEvOAw7BPfZp5sQulNELSLm5YUkau+Sg==",
       "dependencies": {
-        "@vue/compiler-sfc": "2.7.14",
-        "csstype": "^3.1.0"
+        "@vue/compiler-dom": "3.4.24",
+        "@vue/compiler-sfc": "3.4.24",
+        "@vue/runtime-dom": "3.4.24",
+        "@vue/server-renderer": "3.4.24",
+        "@vue/shared": "3.4.24"
+      },
+      "peerDependencies": {
+        "typescript": "*"
+      },
+      "peerDependenciesMeta": {
+        "typescript": {
+          "optional": true
+        }
       }
     },
-    "node_modules/vue-functional-data-merge": {
-      "version": "3.1.0",
-      "resolved": "https://registry.npmjs.org/vue-functional-data-merge/-/vue-functional-data-merge-3.1.0.tgz",
-      "integrity": "sha512-leT4kdJVQyeZNY1kmnS1xiUlQ9z1B/kdBFCILIjYYQDqZgLqCLa0UhjSSeRX6c3mUe6U5qYeM8LrEqkHJ1B4LA==",
-      "dev": true
-    },
-    "node_modules/w3c-keyname": {
-      "version": "2.2.8",
-      "resolved": "https://registry.npmjs.org/w3c-keyname/-/w3c-keyname-2.2.8.tgz",
-      "integrity": "sha512-dpojBhNsCNN7T82Tm7k26A6G9ML3NkhDsnw9n/eoxSRlVBB4CEtIQ/KTCLI2Fwf3ataSXRhYFkQi3SlnFwPvPQ==",
-      "dev": true
-    },
-    "node_modules/whatwg-url": {
-      "version": "5.0.0",
-      "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz",
-      "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==",
-      "dev": true,
+    "node_modules/vue-router": {
+      "version": "4.3.2",
+      "resolved": "https://registry.npmjs.org/vue-router/-/vue-router-4.3.2.tgz",
+      "integrity": "sha512-hKQJ1vDAZ5LVkKEnHhmm1f9pMiWIBNGF5AwU67PdH7TyXCj/a4hTccuUuYCAMgJK6rO/NVYtQIEN3yL8CECa7Q==",
       "dependencies": {
-        "tr46": "~0.0.3",
-        "webidl-conversions": "^3.0.0"
+        "@vue/devtools-api": "^6.5.1"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/posva"
+      },
+      "peerDependencies": {
+        "vue": "^3.2.0"
       }
-    },
-    "node_modules/whatwg-url/node_modules/webidl-conversions": {
-      "version": "3.0.1",
-      "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz",
-      "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==",
-      "dev": true
     }
   }
 }
index 2db850c71631610d2aa9ae2e15fc1db9b99e79d7..facb9727848657dbe9c4f08deec05561573000d0 100644 (file)
   },
   "author": "Adam Dullage",
   "license": "MIT",
-  "devDependencies": {
-    "@toast-ui/editor-plugin-code-syntax-highlight": "3.1.0",
-    "@toast-ui/vue-editor": "3.2.3",
-    "@vitejs/plugin-vue2": "^2.3.1",
+  "dependencies": {
     "axios": "1.6.8",
-    "bootstrap-vue": "2.23.1",
     "mousetrap": "1.6.5",
+    "vue": "^3.4.24",
+    "vue-router": "^4.3.2"
+  },
+  "devDependencies": {
+    "@vitejs/plugin-vue": "^5.0.4",
     "vite": "^5.2.9"
   }
 }
index ce4d1d0f8655b66498faa5a1b22462f7733e35b2..150f13edf84ab01212a379d3cfd2fb77a84a86a9 100644 (file)
@@ -1,7 +1,10 @@
 import { defineConfig } from "vite";
-import vue from "@vitejs/plugin-vue2";
+import vue from "@vitejs/plugin-vue";
 
 export default defineConfig({
   plugins: [vue()],
   root: "client",
+  server: {
+    port: 8080,
+  },
 });
git clone https://git.99rst.org/PROJECT