diff options
author | vnugent <public@vaughnnugent.com> | 2024-01-28 19:56:02 -0500 |
---|---|---|
committer | vnugent <public@vaughnnugent.com> | 2024-01-28 19:56:02 -0500 |
commit | 87645bfad3943e1110e4cb2e038124083e8ae793 (patch) | |
tree | c327a4437c98d973f45c313cf8259ad75515c4fe /extension/src/entries | |
parent | c438ee90e3be4e5e01ae3d045d6b841a03bd46eb (diff) |
progress update
Diffstat (limited to 'extension/src/entries')
-rw-r--r-- | extension/src/entries/contentScript/primary/components/PromptPopup.vue | 88 | ||||
-rw-r--r-- | extension/src/entries/contentScript/primary/main.js | 2 | ||||
-rw-r--r-- | extension/src/entries/contentScript/util.ts | 4 | ||||
-rw-r--r-- | extension/src/entries/options/App.vue | 32 | ||||
-rw-r--r-- | extension/src/entries/options/components/Activity.vue | 121 | ||||
-rw-r--r-- | extension/src/entries/options/components/AutoRules.vue | 54 | ||||
-rw-r--r-- | extension/src/entries/options/components/EvHistoryTable.vue | 6 | ||||
-rw-r--r-- | extension/src/entries/options/components/EventHistory.vue | 222 | ||||
-rw-r--r-- | extension/src/entries/options/components/Identities.vue | 27 | ||||
-rw-r--r-- | extension/src/entries/options/components/SiteSettings.vue | 21 | ||||
-rw-r--r-- | extension/src/entries/options/main.js | 2 | ||||
-rw-r--r-- | extension/src/entries/popup/Components/IdentitySelection.vue | 4 | ||||
-rw-r--r-- | extension/src/entries/store/features.ts | 13 | ||||
-rw-r--r-- | extension/src/entries/store/identity.ts | 7 | ||||
-rw-r--r-- | extension/src/entries/store/index.ts | 5 | ||||
-rw-r--r-- | extension/src/entries/store/types.ts | 3 |
16 files changed, 419 insertions, 192 deletions
diff --git a/extension/src/entries/contentScript/primary/components/PromptPopup.vue b/extension/src/entries/contentScript/primary/components/PromptPopup.vue index 1f62877..b2415b9 100644 --- a/extension/src/entries/contentScript/primary/components/PromptPopup.vue +++ b/extension/src/entries/contentScript/primary/components/PromptPopup.vue @@ -1,11 +1,12 @@ <template> - <div v-show="event" id="nvault-ext-prompt" :class="{'dark': darkMode }"> + <div v-show="showPrompt" id="nvault-ext-prompt" :class="{'dark': darkMode }"> <div class="fixed top-0 bottom-0 left-0 right-0 text-white" style="z-index:9147483647 !important" > - + + <!-- Only show backdrop when prompt is on the same origin --> <div class="fixed inset-0 left-0 w-full h-full bg-black/50" @click.self="close" /> - <div v-if="store.permissions.isPopup" class="relative w-full md:max-w-[28rem] mx-auto md:mt-36 mb-auto" ref="prompt"> + <div v-if="showPopup" class="relative w-full md:max-w-[28rem] mx-auto md:mt-36 mb-auto" ref="prompt"> <div class="w-full h-screen p-5 text-gray-800 bg-white border shadow-lg md:h-auto md:rounded dark:bg-dark-900 dark:border-dark-500 dark:text-gray-200"> <div v-if="loggedIn" class=""> @@ -49,16 +50,14 @@ </div> <div class="flex gap-2 mt-4"> + + <ListBox class="max-w-40" v-model="allowRuleType" :groups="lbOptions" :modelToString="modelToString" /> + <div class="ml-auto"> <button class="rounded btn sm" @click="close">Close</button> </div> <div> - <button :disabled="selectedKey?.Id == undefined" class="rounded amber btn sm" @click="allow(true)"> - Always Allow - </button> - </div> - <div> - <button :disabled="selectedKey?.Id == undefined" class="rounded btn sm" @click="allow(false)">Allow</button> + <button :disabled="selectedKey?.Id == undefined" class="rounded btn sm" @click="allow()">Allow</button> </div> </div> </div> @@ -89,13 +88,14 @@ </template> <script setup lang="ts"> -import { ref } from 'vue' +import { ref, shallowRef } from 'vue' import { storeToRefs } from 'pinia'; import { computed } from 'vue'; import { Popover, PopoverButton, PopoverPanel } from '@headlessui/vue' -import { clone, first } from 'lodash'; +import { clone, first, isEqual } from 'lodash'; import { useStore } from '../../../store'; -import { type PermissionRequest } from '../../../../features' +import { CreateRuleType, type PermissionRequest } from '../../../../features' +import ListBox, { Option, OptionGroup } from '../../../../components/ListBox.vue'; interface PropmtMessage extends PermissionRequest{ msg: string; @@ -106,6 +106,7 @@ const { loggedIn, selectedKey, darkMode } = storeToRefs(store) const keyName = computed(() => selectedKey.value?.UserName) const prompt = ref(null) +const allowRuleType = shallowRef<CreateRuleType>(CreateRuleType.AllowOnce) const event = computed<PropmtMessage | undefined>(() => { //Use a the current windowpending if set @@ -120,16 +121,26 @@ const event = computed<PropmtMessage | undefined>(() => { const site = computed(() => new URL(event.value?.origin || "https://example.com").host) const evData = computed(() => JSON.stringify(event.value || {}, null, 2)) +const onSameOrigin = computed(() => isEqual(event.value?.origin, window.location.origin)) +//Only show in-page popup if the event is from the same origin +const showPopup = computed(() => (store.permissions.isPopup || !store.settings.authPopup)) +const showPrompt = computed(() => (onSameOrigin.value || store.permissions.isPopup) && event.value) + const close = () => { if(event.value){ store.plugins.permission.deny(event.value.uuid); } + + //Reset the rule type + allowRuleType.value = CreateRuleType.AllowOnce } -const allow = (addRule: boolean) => { +const allow = () => { if (event.value) { - store.plugins.permission.allow(event.value.uuid, addRule); + store.plugins.permission.allow(event.value.uuid, allowRuleType.value); } + //Reset the rule type + allowRuleType.value = CreateRuleType.AllowOnce } //Listen for events @@ -161,4 +172,51 @@ const getPromptMessage = (perms: PermissionRequest | undefined): PropmtMessage | return ev } -</script> +const getRuleName = (rule: CreateRuleType) => { + switch (rule) { + default: + return 'None' + case CreateRuleType.AllowOnce: + return "Allow Once" + case CreateRuleType.AllowForever: + return "Allow Forever" + case CreateRuleType.FiveMinutes: + return "5 Minutes" + case CreateRuleType.OneHour: + return "1 Hour" + case CreateRuleType.OneDay: + return "1 Day" + case CreateRuleType.OneWeek: + return "1 Week" + case CreateRuleType.OneMonth: + return "1 Month" + } +} + +const createOption = (rule: CreateRuleType): Option<CreateRuleType> => { + return { name: getRuleName(rule), value: rule } +} + +const creatGroup = (name: string, options: Option<CreateRuleType>[]): OptionGroup<CreateRuleType> => { + return { name, options } +} + +const lbOptions = ((): OptionGroup<CreateRuleType>[] => { + return[ + creatGroup('Allow', [ + createOption(CreateRuleType.AllowOnce), + createOption(CreateRuleType.AllowForever) + ]), + creatGroup('Allow for', [ + createOption(CreateRuleType.FiveMinutes), + createOption(CreateRuleType.OneHour), + createOption(CreateRuleType.OneDay), + createOption(CreateRuleType.OneWeek), + createOption(CreateRuleType.OneMonth) + ]) + ] +})() + +const modelToString = (rule: CreateRuleType) => getRuleName(rule) + +</script>
\ No newline at end of file diff --git a/extension/src/entries/contentScript/primary/main.js b/extension/src/entries/contentScript/primary/main.js index 15fc2ec..2e05ca3 100644 --- a/extension/src/entries/contentScript/primary/main.js +++ b/extension/src/entries/contentScript/primary/main.js @@ -19,6 +19,7 @@ import { defer } from "lodash"; import { createPinia } from 'pinia'; import { useBackgroundPiniaPlugin, identityPlugin, originPlugin, permissionsPlugin } from '../../store' import { onLoad } from "../util"; +import ListBox from '../../../components/ListBox.vue' import renderContent from "../renderContent"; import App from "./App.vue"; import Notification from '@kyvg/vue3-notification' @@ -64,6 +65,7 @@ renderContent([], (appRoot, shadowRoot) => { .use(store) .use(Notification) .component('fa-icon', FontAwesomeIcon) + .component('list-box', ListBox) .mount(appRoot); //Load the nostr shim diff --git a/extension/src/entries/contentScript/util.ts b/extension/src/entries/contentScript/util.ts index aecb7b2..192f1c1 100644 --- a/extension/src/entries/contentScript/util.ts +++ b/extension/src/entries/contentScript/util.ts @@ -25,7 +25,7 @@ const registerWindowHandler = (store: Store, extName: string) => { const { selectedKey } = storeToRefs(store) const { nostr, permission } = store.plugins; - const onAsyncCall = async ({ source, data, origin } : MessageEvent<any>) => { + const onAsyncCall = async ({ data, origin } : MessageEvent<any>) => { //clean any junk/methods with json parse/stringify data = JSON.parse(JSON.stringify(data)) @@ -38,7 +38,7 @@ const registerWindowHandler = (store: Store, extName: string) => { } //Confirm the message format is correct - if (!isEqual(source, window) || isEmpty(data) || isNil(data.type)) { + if (isEmpty(data) || isNil(data.type)) { return } //Confirm extension is for us diff --git a/extension/src/entries/options/App.vue b/extension/src/entries/options/App.vue index 3dbe94d..f62795f 100644 --- a/extension/src/entries/options/App.vue +++ b/extension/src/entries/options/App.vue @@ -12,7 +12,7 @@ <div class=""> <h2>NVault</h2> </div> - <TabGroup :selected-index="selectedTab" @change="id => selectedTab = id" > + <TabGroup :selected-index="selectedTab" @change="selectTab" > <TabList class="flex gap-3 pb-2 border-b border-gray-300 dark:border-dark-500"> <Tab v-slot="{ selected }"> <button class="tab-title" :class="{ selected }"> @@ -72,7 +72,7 @@ <TabPanel> <Account/> </TabPanel> - <TabPanel> <EventHistory/> </TabPanel> + <TabPanel> <Activity/> </TabPanel> <TabPanel> <Privacy/> </TabPanel> @@ -116,7 +116,7 @@ </template> <script setup lang="ts"> -import { ref, watchEffect } from "vue"; +import { computed, ref, watchEffect } from "vue"; import { TabGroup, TabList, @@ -126,26 +126,32 @@ import { } from '@headlessui/vue' import { apiCall, configureNotifier } from '@vnuge/vnlib.browser'; import { storeToRefs } from "pinia"; -import { type NostrPubKey } from '../../features/'; +import { useQuery, type NostrPubKey } from '../../features/'; import { notify } from "@kyvg/vue3-notification"; -import SiteSettings from './components/SiteSettings.vue'; -import Identities from './components/Identities.vue'; -import Privacy from "./components/Privacy.vue"; +import { toSafeInteger } from "lodash"; import { useStore } from "../store"; import Account from "./components/Account.vue"; import ConfirmPrompt from "../../components/ConfirmPrompt.vue"; import PasswordPrompt from "../../components/PasswordPrompt.vue"; -import EventHistory from "./components/EventHistory.vue"; +import Activity from "./components/Activity.vue"; +import SiteSettings from './components/SiteSettings.vue'; +import Identities from './components/Identities.vue'; +import Privacy from "./components/Privacy.vue"; //Configure the notifier to use the notification library configureNotifier({ notify, close: notify.close }) const store = useStore() +const { identity } = store.plugins const { allKeys, darkMode, userName } = storeToRefs(store) -const selectedTab = ref(0) -const keyBuffer = ref<NostrPubKey>({} as NostrPubKey) +const keyBuffer = ref<Partial<NostrPubKey>>({} as NostrPubKey) + +const tabIdQuery = useQuery('t') +const selectedTab = computed(() => toSafeInteger(tabIdQuery.asRef.value)); +const selectTab = (id: number) => tabIdQuery.set(id.toString()) + const editKey = (key: NostrPubKey) =>{ //Goto hidden tab @@ -158,14 +164,14 @@ const doneEditing = () =>{ //Goto hidden tab selectedTab.value = 0 //Set selected key - keyBuffer.value = null + keyBuffer.value = {} } const onUpdate = async () =>{ await apiCall(async ({ toaster }) => { //Update identity - await store.updateIdentity(keyBuffer.value) + await identity.updateIdentity(keyBuffer.value) //Show success toaster.general.success({ 'title':'Success', @@ -176,7 +182,7 @@ const onUpdate = async () =>{ //Goto hidden tab selectedTab.value = 0 //Set selected key - keyBuffer.value = null + keyBuffer.value = {} } const toggleDark = () => store.toggleDarkMode() diff --git a/extension/src/entries/options/components/Activity.vue b/extension/src/entries/options/components/Activity.vue new file mode 100644 index 0000000..c62fb83 --- /dev/null +++ b/extension/src/entries/options/components/Activity.vue @@ -0,0 +1,121 @@ +<template> + <div id="ev-history" class="flex flex-col w-full mt-4 sm:px-2"> + <form @submit.prevent=""> + <div class="w-full max-w-xl mx-auto"> + <h3 class="text-center"> + Permissions + </h3> + + <div class="flex flex-row justify-between mt-4"> + <div class="font-bold"> + Pending + </div> + <div class="flex justify-center"> + </div> + </div> + + <div class="my-6 "> + <EvHistoryTable :readonly="false" :requests="pending" @deny="deny" @approve="approve" /> + </div> + + <AutoRules /> + + <div class="flex flex-row justify-between mt-16"> + <div class="font-bold"> + History + </div> + <div class="flex justify-center"> + <pagination :pages="pages" /> + </div> + </div> + + <div class="mt-1"> + <EvHistoryTable :readonly="true" :requests="permHistory" @deny="deny" @approve="approve" /> + </div> + + <div class="mt-4 ml-auto w-fit"> + <button class="rounded btn sm red" @click="clearHistory"> + Delete All + </button> + </div> + + <h3 class="text-center"> + History + </h3> + + <div class="mt-1 mb-20"> + <EventHistory /> + </div> + + </div> + </form> + </div> +</template> + +<script setup lang="ts"> +import { useConfirm } from '@vnuge/vnlib.browser'; +import { computed } from 'vue'; +import { get, useOffsetPagination } from '@vueuse/core'; +import { } from '@headlessui/vue' +import { useStore } from '../../store'; +import { CreateRuleType, PermissionRequest, PrStatus } from '../../../features'; +import EvHistoryTable from './EvHistoryTable.vue'; +import { filter, slice } from 'lodash'; +import AutoRules from './AutoRules.vue'; +import EventHistory from './EventHistory.vue'; + +const store = useStore() +const { reveal } = useConfirm() + +const pending = computed(() => store.permissions.pending) +const notPending = computed(() => filter(store.permissions.all, r => r.status !== PrStatus.Pending)) + +const deny = (request: PermissionRequest) => { + if(request.status !== PrStatus.Pending) return + //push deny to store + store.plugins.permission.deny(request.uuid) +} + +const approve = (request: PermissionRequest) => { + if(request.status !== PrStatus.Pending) return + //push allow to store + store.plugins.permission.allow(request.uuid, CreateRuleType.AllowOnce) +} + +const pages = useOffsetPagination({ + pageSize: 10, + total: computed(() => notPending.value.length) +}) + +const permHistory = computed(() => { + const start = (get(pages.currentPage) - 1) * get(pages.currentPageSize) + const end = start + 10 + return slice(notPending.value, start, end) +}) + +const clearHistory = async () => { + const { isCanceled } = await reveal({ + title: 'Clear History', + text: 'Are you sure you want to clear your event history?', + }) + + if(isCanceled) return + + //Clear all history + store.plugins.permission.clearRequests() +} + +</script> + +<style lang="scss"> +#ev-history{ + button.page-btn{ + @apply inline-flex items-center px-2 py-2 space-x-2 font-medium rounded-full; + @apply bg-white border border-gray-300 rounded-full hover:bg-gray-50 dark:bg-dark-600 dark:hover:bg-dark-500 dark:border-dark-300; + } + + form tr { + @apply sm:text-sm text-xs dark:text-gray-400 text-gray-600; + } +} +</style>
\ No newline at end of file diff --git a/extension/src/entries/options/components/AutoRules.vue b/extension/src/entries/options/components/AutoRules.vue index 16fddd3..9f46475 100644 --- a/extension/src/entries/options/components/AutoRules.vue +++ b/extension/src/entries/options/components/AutoRules.vue @@ -5,31 +5,10 @@ Approval Rules </div> <div class="flex justify-center"> - <nav aria-label="Pagination"> - <ul class="inline-flex items-center space-x-1 text-sm rounded-md"> - <li> - <button @click="prev" class="page-btn"> - <fa-icon icon="chevron-left" class="w-4" /> - </button> - </li> - <li> - <span class="inline-flex items-center px-4 py-2 space-x-1 rounded-md"> - Page - <b class="mx-1">{{ currentPage }}</b> - of - <b class="ml-1">{{ pageCount }}</b> - </span> - </li> - <li> - <button @click="next" class="page-btn"> - <fa-icon icon="chevron-right" class="w-4" /> - </button> - </li> - </ul> - </nav> + <pagination :pages="pages" /> </div> </div> - <div class=""> + <div class="mt-1"> <table class="min-w-full text-sm divide-y-2 divide-gray-200 dark:divide-dark-500"> <thead class="text-left bg-gray-50 dark:bg-dark-700"> <tr> @@ -40,7 +19,7 @@ Origin </th> <th class="p-2 font-medium whitespace-nowrap dark:text-white"> - Time + Expires </th> <th class="p-2"></th> </tr> @@ -52,10 +31,12 @@ {{ rule.type }} </td> <td class="p-2 whitespace-nowrap"> - {{ rule.origin }} + <a :href="rule.origin" target="_blank" class="text-blue-500 hover:underline"> + {{ rule.origin }} + </a> </td> <td class="p-2 whitespace-nowrap"> - {{ createShortDateAndTime(rule) }} + {{ getExpiration(rule) }} </td> <td class="p-2 text-right whitespace-nowrap"> <div class="button-group"> @@ -73,7 +54,7 @@ <script setup lang="ts"> import { computed } from 'vue'; -import { get, useOffsetPagination } from '@vueuse/core'; +import { formatTimeAgo, get, useOffsetPagination } from '@vueuse/core'; import { } from '@headlessui/vue' import { useStore } from '../../store'; import { storeToRefs } from 'pinia'; @@ -85,13 +66,13 @@ const { } = storeToRefs(store) const rules = computed(() => store.permissions.rules) -const { next, prev, currentPage, currentPageSize, pageCount } = useOffsetPagination({ +const pages = useOffsetPagination({ pageSize: 10, total: computed(() => rules.value.length) }) const currentRulePage = computed(() => { - const start = (get(currentPage) - 1) * get(currentPageSize) + const start = (get(pages.currentPage) - 1) * get(pages.currentPageSize) const end = start + 10 return slice(rules.value, start, end) }) @@ -100,18 +81,13 @@ const deleteRule = (rule: AutoAllowRule) => { store.plugins.permission.deleteRule(rule) } -const createShortDateAndTime = (request: { timestamp: number}) => { - const date = new Date(request.timestamp) - const hours = date.getHours() - const minutes = date.getMinutes() - const seconds = date.getSeconds() - const day = date.getDate() - const month = date.getMonth() + 1 - const year = date.getFullYear() - return `${month}/${day}/${year} ${hours}:${minutes}:${seconds}` +const getExpiration = (rule: AutoAllowRule) => { + if (!rule.expires) { + return "Never" + } + return formatTimeAgo(new Date(rule.expires)) } - </script> <style lang="scss"> diff --git a/extension/src/entries/options/components/EvHistoryTable.vue b/extension/src/entries/options/components/EvHistoryTable.vue index 6ea6cac..a0e2f30 100644 --- a/extension/src/entries/options/components/EvHistoryTable.vue +++ b/extension/src/entries/options/components/EvHistoryTable.vue @@ -1,5 +1,5 @@ <template> - <table class="min-w-full divide-y-2 divide-gray-200 dark:divide-dark-500"> + <table class="min-w-full divide-y-2 divide-gray-200 dark:divide-dark-500"> <thead class="text-left bg-gray-50 dark:bg-dark-700"> <tr> <th class="p-2 font-medium whitespace-nowrap dark:text-white"> @@ -21,7 +21,9 @@ {{ req.requestType }} </td> <td class="p-2 whitespace-nowrap"> - {{ req.origin }} + <a :href="req.origin" target="_blank" class="text-blue-500 hover:underline"> + {{ req.origin }} + </a> </td> <td class="p-2 whitespace-nowrap"> {{ createShortDateAndTime(req) }} diff --git a/extension/src/entries/options/components/EventHistory.vue b/extension/src/entries/options/components/EventHistory.vue index 0711ae6..b6cd13e 100644 --- a/extension/src/entries/options/components/EventHistory.vue +++ b/extension/src/entries/options/components/EventHistory.vue @@ -1,126 +1,152 @@ <template> - <div id="ev-history" class="flex flex-col w-full mt-4 sm:px-2"> - <form @submit.prevent=""> - <div class="w-full max-w-xl mx-auto"> - <h3 class="text-center"> - Permissions - </h3> - - <div class="flex flex-row justify-between mt-4"> - <div class="font-bold"> - Pending - </div> - <div class="flex justify-center"> - </div> - </div> - - <div class="my-6 "> - <EvHistoryTable :readonly="false" :requests="pending" @deny="deny" @approve="approve" /> - </div> - - <AutoRules /> - - <div class="flex flex-row justify-between mt-16"> - <div class="font-bold"> - History - </div> - <div class="flex justify-center"> - <nav aria-label="Pagination"> - <ul class="inline-flex items-center space-x-1 text-sm rounded-md"> - <li> - <button @click="prev" class="page-btn"> - <fa-icon icon="chevron-left" class="w-4" /> - </button> - </li> - <li> - <span class="inline-flex items-center px-4 py-2 space-x-1 rounded-md"> - Page - <b class="mx-1">{{ currentPage }}</b> - of - <b class="ml-1">{{ pageCount }}</b> - </span> - </li> - <li> - <button @click="next" class="page-btn"> - <fa-icon icon="chevron-right" class="w-4" /> - </button> - </li> - </ul> - </nav> - </div> - </div> - - <div class="mt-1"> - <EvHistoryTable :readonly="true" :requests="evHistoryCurrentPage" @deny="deny" @approve="approve" /> - </div> - - <div class="mt-4 ml-auto w-fit"> - <button class="rounded btn sm red" @click="clearHistory"> - Delete History - </button> - </div> + <div class=""> + <div class="flex flex-row justify-between mt-16"> + <div class="font-bold"> + Event History </div> - </form> + <div class="flex justify-center"> + <pagination :pages="pagination" /> + </div> + </div> + <div class=""> + <table class="min-w-full text-sm divide-y-2 divide-gray-200 dark:divide-dark-500"> + <thead class="text-left bg-gray-50 dark:bg-dark-700"> + <tr> + <th class="pl-2"></th> + <th class="p-2 font-medium whitespace-nowrap dark:text-white"> + Event + </th> + <th class="p-2 font-medium whitespace-nowrap dark:text-white"> + Time + </th> + <th class="p-2"></th> + </tr> + </thead> + + <tbody class="divide-y divide-gray-200 dark:divide-dark-500"> + <tr v-for="event in evHistory" :key="event.Id" class=""> + <td class="pl-2 whitespace-nowrap"> + <div class="flex flex-col items-end gap-0.5"> + <div class=""> + ID: + </div> + <div class=""> + EventId: + </div> + <div class=""> + PubKey: + </div> + <div class=""> + Content: + </div> + </div> + </td> + <td class="p-2"> + <div class="flex flex-col flex-1 gap-0.5"> + <div class="truncate overflow-ellipsis"> + {{ event.Id }} + </div> + + <div class="truncate overflow-ellipsis"> + {{ event.id }} + </div> + + <div class="truncate overflow-ellipsis"> + <a href="#" @click="goToKeyView(event)" class="text-blue-500 hover:underline"> + {{ event.pubkey }} + </a> + </div> + <div class="truncate overflow-ellipsis"> + {{ event.content }} + </div> + </div> + </td> + <td class="p-2 whitespace-nowrap"> + {{ timeAgo(event, timeStamp) }} + </td> + <td class="p-2 text-right whitespace-nowrap"> + <div class="button-group"> + <button class="rounded btn xs" @click="deleteEvent(event)"> + <fa-icon icon="trash" /> + </button> + </div> + </td> + </tr> + </tbody> + </table> + </div> </div> </template> <script setup lang="ts"> -import { useConfirm } from '@vnuge/vnlib.browser'; +import { apiCall } from '@vnuge/vnlib.browser'; import { computed } from 'vue'; -import { get, useOffsetPagination } from '@vueuse/core'; -import { } from '@headlessui/vue' +import { formatTimeAgo, get, useOffsetPagination, useTimestamp } from '@vueuse/core'; +import { } from '@headlessui/vue' import { useStore } from '../../store'; -import { PermissionRequest, PrStatus } from '../../../features'; -import EvHistoryTable from './EvHistoryTable.vue'; -import { filter, slice } from 'lodash'; -import AutoRules from './AutoRules.vue'; +import { EventEntry, NostrEvent } from '../../../features'; +import { map, slice } from 'lodash'; +import { useQuery } from '../../../features/util'; const store = useStore() -const { reveal } = useConfirm() - -const pending = computed(() => store.permissions.pending) -const notPending = computed(() => filter(store.permissions.all, r => r.status !== PrStatus.Pending)) -const deny = (request: PermissionRequest) => { - if(request.status !== PrStatus.Pending) return - //push deny to store - store.plugins.permission.deny(request.uuid) -} +const tabId = useQuery('t'); +const keyId = useQuery('kid'); -const approve = (request: PermissionRequest) => { - if(request.status !== PrStatus.Pending) return - //push allow to store - store.plugins.permission.allow(request.uuid, false) -} - -const { next, prev, currentPage, currentPageSize, pageCount } = useOffsetPagination({ +const pagination = useOffsetPagination({ pageSize: 10, - total: computed(() => notPending.value.length) + total: computed(() => store.eventHistory.length) }) -const evHistoryCurrentPage = computed(() => { - const start = (get(currentPage) - 1) * get(currentPageSize) +const explodeNote = (event: EventEntry) => JSON.parse(event.EventData) as NostrEvent + +const timeStamp = useTimestamp({interval: 1000}) + +const evHistory = computed<Array<NostrEvent & EventEntry>>(() => { + const start = (get(pagination.currentPage) - 1) * get(pagination.currentPageSize) const end = start + 10 - return slice(notPending.value, start, end) + const page = slice(store.eventHistory, start, end) + return map(page, event => { + const exploded = explodeNote(event) + return { + ...event, + ...exploded + } + }) }) -const clearHistory = async () => { - const { isCanceled } = await reveal({ - title: 'Clear History', - text: 'Are you sure you want to clear your event history?', - }) - if(isCanceled) return +const deleteEvent = (event: EventEntry) => { + //Call delete event function + apiCall(() => store.plugins.history.deleteEvent(event)) +} + +const createShortDateAndTime = (request: EventEntry) => { + const date = new Date(request.Created) + const hours = date.getHours() + const minutes = date.getMinutes() + const seconds = date.getSeconds() + const day = date.getDate() + const month = date.getMonth() + 1 + const year = date.getFullYear() + return `${month}/${day}/${year} ${hours}:${minutes}:${seconds}` +} + +const timeAgo = (entry: EventEntry, timeStamp: number) => { + return formatTimeAgo(new Date(entry.Created), { }, timeStamp) +} - //Clear all history - store.plugins.permission.clearRequests() +const goToKeyView = (key: { KeyId:string }) => { + //Show tab0 and set key + tabId.set('0') + keyId.set(key.KeyId); } </script> <style lang="scss"> -#ev-history{ - button.page-btn{ +#ev-history { + button.page-btn { @apply inline-flex items-center px-2 py-2 space-x-2 font-medium rounded-full; @apply bg-white border border-gray-300 rounded-full hover:bg-gray-50 dark:bg-dark-600 dark:hover:bg-dark-500 dark:border-dark-300; } diff --git a/extension/src/entries/options/components/Identities.vue b/extension/src/entries/options/components/Identities.vue index b7765be..8236b83 100644 --- a/extension/src/entries/options/components/Identities.vue +++ b/extension/src/entries/options/components/Identities.vue @@ -40,7 +40,7 @@ </div> <div class=""> <div class=""> - <button class="rounded btn sm" @click="store.refreshIdentities()"> + <button class="rounded btn sm" @click="identity.refreshKeys()"> <fa-icon icon="refresh" class="" /> </button> </div> @@ -89,8 +89,8 @@ <script setup lang="ts"> -import { isEqual, map } from 'lodash' -import { ref } from "vue"; +import { find, isEqual, map } from 'lodash' +import { computed, ref } from "vue"; import { Popover, PopoverButton, @@ -103,6 +103,7 @@ import { notify } from "@kyvg/vue3-notification"; import { get, useClipboard } from '@vueuse/core'; import { useStore } from '../../store'; import { storeToRefs } from 'pinia'; +import { useQuery } from '../../../features/util'; const emit = defineEmits(['edit-key']) @@ -111,13 +112,23 @@ configureNotifier({ notify, close: notify.close }) const downloadAnchor = ref<HTMLAnchorElement>() const store = useStore() -const { selectedKey, allKeys } = storeToRefs(store) +const { identity } = store.plugins +const { selectedKey } = storeToRefs(store) const { copy } = useClipboard() const { reveal } = useConfirm() +const query = useQuery('kid') const isSelected = (me : NostrPubKey) => isEqual(me, selectedKey.value) const editKey = (key : NostrPubKey) => emit('edit-key', key); -const selectKey = (key: NostrPubKey) => store.selectKey(key) +const selectKey = (key: NostrPubKey) => identity.selectKey(key) +const allKeys = computed(() => { + const q = query.get(); + if(q){ + const val = find(store.allKeys, k => k.Id === q || k.PubKey === q) + return val ? [val] : [] + } + return store.allKeys +}) const onCreate = async (e: Event, onClose : () => void) => { @@ -128,7 +139,7 @@ const onCreate = async (e: Event, onClose : () => void) => { await apiCall(async () => { //Create new identity - await store.createIdentity({ UserName, ExistingKey }) + await identity.createIdentity({ UserName, ExistingKey }) }) onClose() @@ -157,7 +168,7 @@ const onDeleteKey = async (key : NostrPubKey) => { apiCall(async ({ toaster }) => { //Delete identity - await store.deleteIdentity(key) + await identity.deleteIdentity(key) toaster.general.success({ 'title': 'Success', 'text': `${key.UserName} has been deleted` @@ -169,7 +180,7 @@ const onNip05Download = () => { apiCall(async () => { //Get all public keys from the server const keys = get(allKeys) - const nip05 = {} + const nip05 = {} as any; //Map the keys to the NIP-05 format map(keys, k => nip05[k.UserName] = k.PublicKey) //create file blob diff --git a/extension/src/entries/options/components/SiteSettings.vue b/extension/src/entries/options/components/SiteSettings.vue index 17f41c4..a2df205 100644 --- a/extension/src/entries/options/components/SiteSettings.vue +++ b/extension/src/entries/options/components/SiteSettings.vue @@ -47,6 +47,24 @@ </div> </div> </div> + <div class="mt-3"> + <div class="flex flex-row w-fit"> + <Switch + v-model="v$.authPopup.$model" + :class="v$.authPopup.$model ? 'bg-black dark:bg-white' : 'bg-gray-200 dark:bg-dark-600'" + class="relative inline-flex items-center h-5 mx-auto rounded-full w-11" + > + <span class="sr-only">Permissions Popup</span> + <span + :class="v$.authPopup.$model ? 'translate-x-6' : 'translate-x-1'" + class="inline-block w-4 h-4 transition transform rounded-full bg-gray-50 dark:bg-dark-900" + /> + </Switch> + <div class="my-auto ml-2 text-sm dark:text-gray-200"> + Permissions Popup + </div> + </div> + </div> </fieldset> </div> <h3 class="text-center"> @@ -165,6 +183,7 @@ const vRules = { maxLength: maxLength(50), alphaNum: helpers.withMessage('Nostr path is not a valid endpoint path that begins with /', path) }, + authPopup: {}, heartbeat: {}, } @@ -176,6 +195,7 @@ const [ editMode, toggleEdit ] = useToggle(false); const autoInject = computed(() => buffer.autoInject) const heartbeat = computed(() => buffer.heartbeat) +const authPopup = computed(() => buffer.authPopup) const onSave = async () => { @@ -218,6 +238,7 @@ const testConnection = async () =>{ //Watch for changes to autoinject value and publish changes when it does watchDebounced(autoInject, update, { debounce: 500, immediate: false }) watchDebounced(heartbeat, update, { debounce: 500, immediate: false }) +watchDebounced(authPopup, update, { debounce: 500, immediate: false }) </script> diff --git a/extension/src/entries/options/main.js b/extension/src/entries/options/main.js index 3dd01cb..901dfdd 100644 --- a/extension/src/entries/options/main.js +++ b/extension/src/entries/options/main.js @@ -19,6 +19,7 @@ import App from "./App.vue"; import '@fontsource/noto-sans-masaram-gondi' import "~/assets/all.scss"; import Notifications from "@kyvg/vue3-notification"; +import Pagination from '../../components/Pagination.vue'; /* FONT AWESOME CONFIG */ import { library } from '@fortawesome/fontawesome-svg-core' @@ -43,4 +44,5 @@ createApp(App) .use(Notifications) .use(pinia) .component('fa-icon', FontAwesomeIcon) + .component('pagination', Pagination) .mount("#app"); diff --git a/extension/src/entries/popup/Components/IdentitySelection.vue b/extension/src/entries/popup/Components/IdentitySelection.vue index eb08fb1..06d09a5 100644 --- a/extension/src/entries/popup/Components/IdentitySelection.vue +++ b/extension/src/entries/popup/Components/IdentitySelection.vue @@ -12,7 +12,7 @@ </select> </div> <div class="my-auto"> - <button class="btn sm borderless" @click="store.refreshIdentities()"> + <button class="btn sm borderless" @click="store.plugins.identity.refreshKeys()"> <fa-icon icon="refresh" class="" /> </button> </div> @@ -35,7 +35,7 @@ const onSelected = async ({target}) =>{ //Select the key of the given id const selected = find(allKeys.value, {Id: target.value}) if(selected){ - await store.selectKey(selected) + await store.plugins.identity.selectKey(selected) } } diff --git a/extension/src/entries/store/features.ts b/extension/src/entries/store/features.ts index ad83e16..d714ac6 100644 --- a/extension/src/entries/store/features.ts +++ b/extension/src/entries/store/features.ts @@ -68,7 +68,7 @@ const usePlugins = (context: ChannelContext) => { export const useBackgroundPiniaPlugin = (context: ChannelContext) => { //Create port for context const plugins = usePlugins(context) - const { user } = plugins; + const { user, settings, history } = plugins; //Plugin store return ({ store }: PiniaPluginContext) => { @@ -84,10 +84,15 @@ export const useBackgroundPiniaPlugin = (context: ChannelContext) => { }, { immediate: true }) //Wait for settings changes - onWatchableChange(plugins.settings, async () => { + onWatchableChange(settings, async () => { //Update settings and dark mode on change - store.settings = await plugins.settings.getSiteConfig(); - store.darkMode = await plugins.settings.getDarkMode(); + store.settings = await settings.getSiteConfig(); + store.darkMode = await settings.getDarkMode(); + }, { immediate: true }) + + onWatchableChange(history, async () => { + //Load event history + store.eventHistory = await history.getEvents(); }, { immediate: true }) return{ diff --git a/extension/src/entries/store/identity.ts b/extension/src/entries/store/identity.ts index ade7c94..ed01f6b 100644 --- a/extension/src/entries/store/identity.ts +++ b/extension/src/entries/store/identity.ts @@ -22,12 +22,7 @@ import { shallowRef } from 'vue'; declare module 'pinia' { export interface PiniaCustomStateProperties { allKeys: NostrPubKey[]; - selectedKey: NostrPubKey | undefined; - deleteIdentity(key: Partial<NostrPubKey>): Promise<void>; - createIdentity(id: Partial<NostrPubKey>): Promise<NostrPubKey>; - updateIdentity(id: NostrPubKey): Promise<NostrPubKey>; - selectKey(key: NostrPubKey): Promise<void>; - refreshIdentities(): Promise<void>; + selectedKey: NostrPubKey | undefined; } } diff --git a/extension/src/entries/store/index.ts b/extension/src/entries/store/index.ts index 8be57ff..0b4d3cd 100644 --- a/extension/src/entries/store/index.ts +++ b/extension/src/entries/store/index.ts @@ -31,8 +31,9 @@ export const useStore = defineStore({ state: (): NostrStoreState =>({ loggedIn: false, userName: '', - settings: undefined as any, - darkMode: false + settings: {} as any, + darkMode: false, + eventHistory: [], }), actions: { diff --git a/extension/src/entries/store/types.ts b/extension/src/entries/store/types.ts index 7addda4..536cf04 100644 --- a/extension/src/entries/store/types.ts +++ b/extension/src/entries/store/types.ts @@ -1,9 +1,10 @@ import { } from "webextension-polyfill"; -import { PluginConfig } from "../../features"; +import type { PluginConfig, EventEntry } from "../../features"; export interface NostrStoreState { loggedIn: boolean; userName: string | null; settings: PluginConfig; darkMode: boolean; + eventHistory: EventEntry[]; }
\ No newline at end of file |