aboutsummaryrefslogtreecommitdiff
path: root/extension/src/entries
diff options
context:
space:
mode:
authorLibravatar vnugent <public@vaughnnugent.com>2024-01-28 19:56:02 -0500
committerLibravatar vnugent <public@vaughnnugent.com>2024-01-28 19:56:02 -0500
commit87645bfad3943e1110e4cb2e038124083e8ae793 (patch)
treec327a4437c98d973f45c313cf8259ad75515c4fe /extension/src/entries
parentc438ee90e3be4e5e01ae3d045d6b841a03bd46eb (diff)
progress update
Diffstat (limited to 'extension/src/entries')
-rw-r--r--extension/src/entries/contentScript/primary/components/PromptPopup.vue88
-rw-r--r--extension/src/entries/contentScript/primary/main.js2
-rw-r--r--extension/src/entries/contentScript/util.ts4
-rw-r--r--extension/src/entries/options/App.vue32
-rw-r--r--extension/src/entries/options/components/Activity.vue121
-rw-r--r--extension/src/entries/options/components/AutoRules.vue54
-rw-r--r--extension/src/entries/options/components/EvHistoryTable.vue6
-rw-r--r--extension/src/entries/options/components/EventHistory.vue222
-rw-r--r--extension/src/entries/options/components/Identities.vue27
-rw-r--r--extension/src/entries/options/components/SiteSettings.vue21
-rw-r--r--extension/src/entries/options/main.js2
-rw-r--r--extension/src/entries/popup/Components/IdentitySelection.vue4
-rw-r--r--extension/src/entries/store/features.ts13
-rw-r--r--extension/src/entries/store/identity.ts7
-rw-r--r--extension/src/entries/store/index.ts5
-rw-r--r--extension/src/entries/store/types.ts3
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