diff options
author | vnugent <public@vaughnnugent.com> | 2024-02-02 17:04:47 -0500 |
---|---|---|
committer | vnugent <public@vaughnnugent.com> | 2024-02-02 17:04:47 -0500 |
commit | dd5e5164262c5ff7ecf6db1003d41d81e6364c09 (patch) | |
tree | 9b5a34f0c57eeb55f7625f2eb60b191a494d1758 /front-end/src | |
parent | dab4c63543af688e67661b0091b49edb00e9557d (diff) |
add bookmark export, fix some ui stuff
Diffstat (limited to 'front-end/src')
-rw-r--r-- | front-end/src/App.vue | 2 | ||||
-rw-r--r-- | front-end/src/components/Bookmarks.vue | 27 | ||||
-rw-r--r-- | front-end/src/components/Login/PkiLogin.vue | 2 | ||||
-rw-r--r-- | front-end/src/components/Settings.vue | 9 | ||||
-rw-r--r-- | front-end/src/components/Settings/Bookmarks.vue | 33 | ||||
-rw-r--r-- | front-end/src/components/Settings/PkiSettings.vue | 2 | ||||
-rw-r--r-- | front-end/src/components/Settings/TotpSettings.vue | 2 | ||||
-rw-r--r-- | front-end/src/components/global/PasswordPrompt.vue | 2 | ||||
-rw-r--r-- | front-end/src/index.scss | 2 | ||||
-rw-r--r-- | front-end/src/store/bookmarks.ts | 12 |
10 files changed, 65 insertions, 28 deletions
diff --git a/front-end/src/App.vue b/front-end/src/App.vue index f63f9ca..01e91e7 100644 --- a/front-end/src/App.vue +++ b/front-end/src/App.vue @@ -36,7 +36,7 @@ const showIf = (tabId: TabId, active: TabId) => isEqual(tabId, active) <title>{{ siteTitle }}</title> </head> <body> - <div id="app" class="min-h-screen pb-16 text-gray-700 bg-gray-100 dark:bg-gray-900 dark:text-white sm:pb-0"> + <div id="app" class="min-h-screen pb-16 text-gray-700 bg-gray-50 dark:bg-gray-900 dark:text-white sm:pb-0"> <div class="relative"> <div class="absolute z-50 right-10 top-10"> diff --git a/front-end/src/components/Bookmarks.vue b/front-end/src/components/Bookmarks.vue index 34c31a8..93ddd73 100644 --- a/front-end/src/components/Bookmarks.vue +++ b/front-end/src/components/Bookmarks.vue @@ -1,7 +1,7 @@ <script setup lang="ts"> import { MaybeRef, Ref, computed, defineAsyncComponent, ref, shallowRef, watch } from 'vue'; import { useQuery, useStore } from '../store'; -import { get, set, formatTimeAgo, useToggle, useTimestamp, useFileDialog, asyncComputed, toReactive } from '@vueuse/core'; +import { get, set, formatTimeAgo, useToggle, useTimestamp, useFileDialog, asyncComputed, toReactive, useClipboard } from '@vueuse/core'; import { useVuelidate } from '@vuelidate/core'; import { required, maxLength, minLength, helpers } from '@vuelidate/validators'; import { apiCall, useConfirm, useGeneralToaster, useVuelidateWrapper, useWait } from '@vnuge/vnlib.browser'; @@ -22,6 +22,7 @@ const now = useTimestamp({interval: 1000}); const selectedTags = computed(() => store.bookmarks.tags); const localSearch = shallowRef<string>(store.bookmarks.query); const nextPageAvailable = computed(() => isEqual(bookmarks.value?.length, get(store.bookmarks.pages.currentPageSize))); +const { copy } = useClipboard() //Refresh on page load store.bookmarks.refresh(); @@ -399,25 +400,6 @@ const upload = (() => { </MenuItems> </transition> </Menu> - - <!-- Dropdown menu --> - <div id="dropdown" class="z-10 hidden bg-white divide-y divide-gray-100 rounded-lg shadow w-44 dark:bg-gray-700"> - <ul class="py-2 text-sm text-gray-700 dark:text-gray-200" aria-labelledby="dropdownDefaultButton"> - <li> - <a href="#" class="block px-4 py-2 hover:bg-gray-100 dark:hover:bg-gray-600 dark:hover:text-white">Dashboard</a> - </li> - <li> - <a href="#" class="block px-4 py-2 hover:bg-gray-100 dark:hover:bg-gray-600 dark:hover:text-white">Settings</a> - </li> - <li> - <a href="#" class="block px-4 py-2 hover:bg-gray-100 dark:hover:bg-gray-600 dark:hover:text-white">Earnings</a> - </li> - <li> - <a href="#" class="block px-4 py-2 hover:bg-gray-100 dark:hover:bg-gray-600 dark:hover:text-white">Sign out</a> - </li> - </ul> - </div> - </div> </div> <div class="grid flex-auto grid-cols-4 gap-8 mt-4 max-w-[60rem] mx-auto w-full"> @@ -457,6 +439,9 @@ const upload = (() => { </span> | <span class="inline-flex gap-1.5"> + <button class="text-xs text-gray-700 dark:text-gray-400" @click="copy(bm.Url)"> + Copy + </button> <button class="text-xs text-gray-700 dark:text-gray-400" @click="edit.editBookmark(bm)"> Edit </button> @@ -595,7 +580,7 @@ const upload = (() => { <style scoped lang="scss"> input.search{ @apply ps-10 p-2.5 border block w-full text-sm rounded pe-10; - @apply bg-gray-50 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500 border-gray-300 text-gray-900 focus:ring-blue-500 focus:border-blue-500 ; + @apply bg-white dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500 border-gray-300 text-gray-900 focus:ring-blue-500 focus:border-blue-500 ; } button.search{ diff --git a/front-end/src/components/Login/PkiLogin.vue b/front-end/src/components/Login/PkiLogin.vue index 26528c4..4515062 100644 --- a/front-end/src/components/Login/PkiLogin.vue +++ b/front-end/src/components/Login/PkiLogin.vue @@ -39,7 +39,7 @@ const onSubmit = () => { <textarea id="message" rows="5" v-model="otp" - class="block p-2.5 w-full text-sm text-gray-900 bg-gray-50 rounded border border-gray-300 focus:ring-blue-500 focus:border-blue-500 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500" + class="block p-2.5 w-full text-sm text-gray-900 bg-transparent rounded border border-gray-300 focus:ring-blue-500 focus:border-blue-500 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500" placeholder="Enter your OTP" > </textarea> diff --git a/front-end/src/components/Settings.vue b/front-end/src/components/Settings.vue index e03ae61..720c556 100644 --- a/front-end/src/components/Settings.vue +++ b/front-end/src/components/Settings.vue @@ -5,6 +5,7 @@ import Oauth2Apps from './Settings/Oauth2Apps.vue'; import PasswordReset from './Settings/PasswordReset.vue'; import PkiSettings from './Settings/PkiSettings.vue'; import TotpSettings from './Settings/TotpSettings.vue'; +import Bookmarks from './Settings/Bookmarks.vue'; const store = useStore(); const darkMode = useDark(); @@ -38,6 +39,14 @@ const darkMode = useDark(); </div> </div> + <div class=""> + <h3 class="text-xl font-bold">Boomarks</h3> + + <div class="relative mt-4"> + <Bookmarks /> + </div> + </div> + <PasswordReset /> <div class=""> diff --git a/front-end/src/components/Settings/Bookmarks.vue b/front-end/src/components/Settings/Bookmarks.vue new file mode 100644 index 0000000..68fa86b --- /dev/null +++ b/front-end/src/components/Settings/Bookmarks.vue @@ -0,0 +1,33 @@ +<script setup lang="ts"> +import { apiCall, useWait } from '@vnuge/vnlib.browser'; +import { useStore } from '../../store'; +import { useObjectUrl, set } from '@vueuse/core'; +import { ref } from 'vue'; + +const { bookmarks } = useStore(); + +const blobUrl = ref<Blob>(); +const downloadAnchor = ref(); +const obj = useObjectUrl(blobUrl); +const { waiting } = useWait() + +const downloadBookmarks = () => { + apiCall(async () => { + const htmlData = await bookmarks.api.downloadAll(); + const blob = new Blob([htmlData], { type: 'text/html' }); + set(blobUrl, blob); + downloadAnchor.value?.click(); + }); +} + +</script> +<template> + <div class="flex flex-row justify-start"> + <div class=""> + <button class="flex items-center btn light" :disabled="waiting" @click="downloadBookmarks()"> + Download + </button> + </div> + </div> + <a ref="downloadAnchor" :href="obj" download="bookmarks.html" class="hidden"></a> +</template>
\ No newline at end of file diff --git a/front-end/src/components/Settings/PkiSettings.vue b/front-end/src/components/Settings/PkiSettings.vue index 5fa5c93..2564cf6 100644 --- a/front-end/src/components/Settings/PkiSettings.vue +++ b/front-end/src/components/Settings/PkiSettings.vue @@ -142,7 +142,7 @@ const onAddKey = async () => { </div> </div> - <div class="relative mt-2 overflow-x-auto shadow-md sm:rounded"> + <div class="relative mt-4 overflow-x-auto shadow-md sm:rounded"> <table class="w-full text-sm text-left text-gray-500 rtl:text-right dark:text-gray-400"> <thead class="text-xs text-gray-700 uppercase bg-gray-50 dark:bg-gray-700 dark:text-gray-400"> <tr> diff --git a/front-end/src/components/Settings/TotpSettings.vue b/front-end/src/components/Settings/TotpSettings.vue index 1d79914..0019a9d 100644 --- a/front-end/src/components/Settings/TotpSettings.vue +++ b/front-end/src/components/Settings/TotpSettings.vue @@ -114,7 +114,7 @@ const onVerifyOtp = async (code: string) => { </span> </div> <div v-if="totpEnabled" class="flex"> - <button class="btn light"> + <button class="btn light" @click="addOrUpdate()"> Regenerate </button> <button class="btn red" @click="disableTotp()"> diff --git a/front-end/src/components/global/PasswordPrompt.vue b/front-end/src/components/global/PasswordPrompt.vue index d638c71..50024fb 100644 --- a/front-end/src/components/global/PasswordPrompt.vue +++ b/front-end/src/components/global/PasswordPrompt.vue @@ -79,7 +79,7 @@ onReveal(m => message.value = defaultTo(m, {})); type="password" id="password" v-model.trim="v$.password.$model" - class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500" + class="bg-transparent border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500" placeholder="•••••••••" required > diff --git a/front-end/src/index.scss b/front-end/src/index.scss index fece9ee..5575eb3 100644 --- a/front-end/src/index.scss +++ b/front-end/src/index.scss @@ -16,7 +16,7 @@ } .input { - @apply bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded focus:ring-blue-500 focus:border-blue-500 block w-full p-2 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500; + @apply bg-transparent border border-gray-300 text-gray-900 text-sm rounded focus:ring-blue-500 focus:border-blue-500 block w-full p-2 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500; &.dirty{ @apply bg-green-50 border-green-500 text-green-900 dark:text-green-400 placeholder-green-700 dark:placeholder-green-500 focus:ring-green-500 focus:border-green-500 dark:bg-gray-700 dark:border-green-500; diff --git a/front-end/src/store/bookmarks.ts b/front-end/src/store/bookmarks.ts index b6430f7..b0595e9 100644 --- a/front-end/src/store/bookmarks.ts +++ b/front-end/src/store/bookmarks.ts @@ -51,6 +51,7 @@ export interface BookmarkApi{ getTags: () => Promise<string[]> delete: (bookmark: Bookmark | Bookmark[]) => Promise<void> count: () => Promise<number> + downloadAll: () => Promise<string> } export interface BookmarkSearch{ @@ -140,6 +141,14 @@ const useBookmarkApi = (endpoint: MaybeRef<string>): BookmarkApi => { return data.result; } + const downloadAll = async () => { + //download the bookmarks as a html file + const { data } = await axios.get<string>(`${get(endpoint)}?export=true`, { + headers: { 'Content-Type': 'application/htlm' } + }) + return data; + } + return { list: listBookmarks, add: addBookmark, @@ -147,7 +156,8 @@ const useBookmarkApi = (endpoint: MaybeRef<string>): BookmarkApi => { delete: deleteBookmark, count: getItemsCount, addMany, - getTags + getTags, + downloadAll } } |