Compare commits

...

2 Commits

Author SHA1 Message Date
73b1e376a3 Channel manage 2024-03-31 01:06:06 +08:00
012a02751c Channel establish 2024-03-31 00:38:13 +08:00
19 changed files with 317 additions and 37 deletions

View File

@ -1,7 +1,7 @@
<template>
<v-menu eager :close-on-content-click="false">
<template #activator="{ props }">
<v-btn v-bind="props" stacked rounded="circle" size="small" variant="text" :loading="loading">
<v-btn v-bind="props" icon rounded="circle" size="small" variant="text" :loading="loading">
<v-badge v-if="pagination.total > 0" color="error" :content="pagination.total">
<v-icon icon="mdi-bell" />
</v-badge>

View File

@ -0,0 +1,36 @@
<template>
<v-menu>
<template #activator="{ props }">
<v-btn v-bind="props" icon="mdi-cog" variant="text" />
</template>
<v-list density="compact" lines="one">
<v-list-item disabled append-icon="mdi-flag" title="Report" />
<v-list-item v-if="isOwned" append-icon="mdi-pencil" title="Edit" @click="editChannel" />
<v-list-item v-if="isOwned" append-icon="mdi-delete" title="Delete" @click="deleteChannel" />
</v-list>
</v-menu>
</template>
<script setup lang="ts">
import { useUserinfo } from "@/stores/userinfo"
import { useChannels } from "@/stores/channels"
import { computed } from "vue"
const id = useUserinfo()
const channels = useChannels()
const props = defineProps<{ item: any }>()
const isOwned = computed(() => props.item?.account_id === id.userinfo.idSet?.messaging)
function editChannel() {
channels.related.edit_to = props.item
channels.show.editor = true
}
function deleteChannel() {
channels.related.delete_to = props.item
channels.show.delete = true
}
</script>

View File

@ -0,0 +1,61 @@
<template>
<v-card title="Delete a realm" class="min-h-[540px]" :loading="loading">
<template #text>
You are deleting a channel
<b>{{ channels.related.delete_to?.name }}</b> <br />
All messaging belonging to this channel will be deleted and never appear again. Are you confirm?
</template>
<template #actions>
<div class="w-full flex justify-end">
<v-btn color="grey-darken-3" @click="channels.show.delete = false">Not really</v-btn>
<v-btn color="error" :disabled="loading" @click="deletePost">Yes</v-btn>
</div>
</template>
</v-card>
<v-snackbar v-model="success" :timeout="3000">The realm has been deleted.</v-snackbar>
<!-- @vue-ignore -->
<v-snackbar v-model="error" :timeout="5000">Something went wrong... {{ error }}</v-snackbar>
</template>
<script setup lang="ts">
import { request } from "@/scripts/request"
import { getAtk } from "@/stores/userinfo"
import { useChannels } from "@/stores/channels"
import { useRoute, useRouter } from "vue-router"
import { ref } from "vue"
const route = useRoute()
const router = useRouter()
const channels = useChannels()
const emits = defineEmits(["relist"])
const error = ref<string | null>(null)
const success = ref(false)
const loading = ref(false)
async function deletePost() {
const target = channels.related.delete_to
const url = `/api/channels/${target.id}`
loading.value = true
const res = await request("messaging", url, {
method: "DELETE",
headers: { Authorization: `Bearer ${await getAtk()}` }
})
if (res.status !== 200) {
error.value = await res.text()
} else {
success.value = true
channels.show.delete = false
channels.related.delete_to = null
emits("relist")
if (route.name?.toString()?.startsWith("realm")) {
router.push({ name: "explore" })
}
}
loading.value = false
}
</script>

View File

@ -0,0 +1,77 @@
<template>
<v-card title="Establish a channel" prepend-icon="mdi-pound-box" class="min-h-[540px]" :loading="loading">
<v-form @submit.prevent="submit">
<v-card-text>
<v-text-field label="Alias" variant="outlined" density="comfortable" hint="Must be unique"
v-model="data.alias" />
<v-text-field label="Name" variant="outlined" density="comfortable" v-model="data.name" />
<v-textarea label="Description" variant="outlined" density="comfortable" v-model="data.description" />
</v-card-text>
<v-card-actions>
<v-spacer></v-spacer>
<v-btn type="reset" color="grey-darken-3" @click="channels.show.editor = false">Cancel</v-btn>
<v-btn type="submit" :disabled="loading">Save</v-btn>
</v-card-actions>
</v-form>
</v-card>
<!-- @vue-ignore -->
<v-snackbar v-model="error" :timeout="5000">Something went wrong... {{ error }}</v-snackbar>
</template>
<script setup lang="ts">
import { ref, watch } from "vue"
import { getAtk } from "@/stores/userinfo"
import { request } from "@/scripts/request"
import { useChannels } from "@/stores/channels"
const emits = defineEmits(["relist"])
const channels = useChannels()
const error = ref<null | string>(null)
const loading = ref(false)
const data = ref({
alias: "",
name: "",
description: ""
})
async function submit(evt: SubmitEvent) {
const form = evt.target as HTMLFormElement
const payload = data.value
if (!payload.name) return
const url = channels.related.edit_to ? `/api/channels/${channels.related.edit_to?.id}` : "/api/channels"
const method = channels.related.edit_to ? "PUT" : "POST"
loading.value = true
const res = await request("messaging", url, {
method: method,
headers: { "Content-Type": "application/json", Authorization: `Bearer ${await getAtk()}` },
body: JSON.stringify(payload)
})
if (res.status !== 200) {
error.value = await res.text()
} else {
emits("relist")
form.reset()
channels.done = true
channels.show.editor = false
channels.related.edit_to = null
}
loading.value = false
}
watch(
channels.related,
(val) => {
if (val.edit_to) {
data.value = JSON.parse(JSON.stringify(val.edit_to))
}
},
{ immediate: true }
)
</script>

View File

@ -0,0 +1,42 @@
<template>
<v-list-group value="channels">
<template #activator="{ props }">
<v-list-item
v-bind="props"
prepend-icon="mdi-chat"
title="Channels"
/>
</template>
<v-list-item
v-for="item in channels.available"
exact
append-icon="mdi-pound-box"
:to="{ name: 'chat.channel', params: { channel: item.alias } }"
:title="item.name"
/>
<v-list-item
append-icon="mdi-plus"
title="Create a channel"
variant="plain"
:disabled="!id.userinfo.isLoggedIn"
@click="createChannel"
/>
</v-list-group>
</template>
<script setup lang="ts">
import { useUserinfo } from "@/stores/userinfo"
import { useRealms } from "@/stores/realms"
import { useChannels } from "@/stores/channels"
const id = useUserinfo()
const channels = useChannels()
function createChannel() {
channels.related.edit_to = null
channels.related.delete_to = null
channels.show.editor = true
}
</script>

View File

@ -0,0 +1,16 @@
<template>
<v-bottom-sheet class="max-w-[480px]" v-model="channels.show.editor">
<channel-editor @relist="channels.list" />
</v-bottom-sheet>
<v-bottom-sheet class="max-w-[480px]" v-model="channels.show.delete">
<channel-deletion @relist="channels.list" />
</v-bottom-sheet>
</template>
<script setup lang="ts">
import { useChannels } from "@/stores/channels"
import ChannelEditor from "@/components/chat/channels/ChannelEditor.vue"
import ChannelDeletion from "@/components/chat/channels/ChannelDeletion.vue"
const channels = useChannels()
</script>

View File

@ -22,7 +22,7 @@ const realms = useRealms()
const props = defineProps<{ item: any }>()
const isOwned = computed(() => props.item?.account_id === id.userinfo.data.id)
const isOwned = computed(() => props.item?.account_id === id.userinfo.idSet?.interactive)
function editRealm() {
realms.related.edit_to = props.item

View File

@ -3,7 +3,7 @@
<template #text>
You are deleting a realm
<b>{{ realms.related.delete_to?.name }}</b> <br />
All posts belonging to this domain will be deleted and never appear again. Are you confirm?
All posts belonging to this realm will be deleted and never appear again. Are you confirm?
</template>
<template #actions>
<div class="w-full flex justify-end">

View File

@ -1,27 +1,29 @@
<template>
<v-list density="comfortable">
<v-list-subheader>
Realms
<v-badge color="warning" content="Alpha" inline />
</v-list-subheader>
<v-list-group value="realms">
<template #activator="{ props }">
<v-list-item
v-bind="props"
prepend-icon="mdi-account-box-multiple"
title="Realms"
/>
</template>
<v-list-item
v-for="item in realms.available"
exact
prepend-icon="mdi-account-multiple"
append-icon="mdi-account-multiple"
:to="{ name: 'realms.page', params: { realmId: item.id } }"
:title="item.name"
/>
<v-divider v-if="realms.available.length > 0" class="border-opacity-75 my-2" />
<v-list-item
prepend-icon="mdi-plus"
append-icon="mdi-plus"
title="Create a realm"
variant="plain"
:disabled="!id.userinfo.isLoggedIn"
@click="createRealm"
/>
</v-list>
</v-list-group>
</template>
<script setup lang="ts">

View File

@ -1,8 +1,8 @@
<template>
<v-bottom-sheet v-model="realms.show.editor">
<v-bottom-sheet class="max-w-[480px]" v-model="realms.show.editor">
<realm-editor @relist="realms.list" />
</v-bottom-sheet>
<v-bottom-sheet v-model="realms.show.delete">
<v-bottom-sheet class="max-w-[480px]" v-model="realms.show.delete">
<realm-deletion @relist="realms.list" />
</v-bottom-sheet>
</template>

View File

@ -3,6 +3,9 @@
<v-system-bar v-show="ui.safeArea.top > 0" color="primary" :order="1" :height="ui.safeArea.top" />
<router-view />
<realm-tools />
<channel-tools />
</v-app>
</template>
@ -10,6 +13,8 @@
import { onMounted, ref } from "vue"
import { Capacitor } from "@capacitor/core"
import { useUI } from "@/stores/ui"
import RealmTools from "@/components/realms/RealmTools.vue"
import ChannelTools from "@/components/chat/channels/ChannelTools.vue"
const ui = useUI()

View File

@ -4,8 +4,13 @@
<v-app-bar-nav-icon icon="mdi-chat" :loading="loading" />
<h2 class="ml-2 text-lg font-500">{{ channels.current?.name }}</h2>
<p class="ml-3 text-xs opacity-80">{{ channels.current?.description }}</p>
<v-spacer />
<div v-if="channels.current">
<channel-action :item="channels.current" />
</div>
</div>
</v-app-bar>
@ -20,6 +25,7 @@ import { request } from "@/scripts/request"
import { useRoute } from "vue-router"
import { ref, watch } from "vue"
import { useChannels } from "@/stores/channels"
import ChannelAction from "@/components/chat/channels/ChannelAction.vue"
const route = useRoute()
const channels = useChannels()
@ -47,4 +53,13 @@ watch(
},
{ immediate: true }
)
watch(() => channels.done, (val) => {
if (val) {
readMetadata().then(() => {
channels.messages = []
channels.done = false
})
}
}, { immediate: true })
</script>

View File

@ -34,13 +34,18 @@
</div>
</v-toolbar>
<div class="flex-grow-1">
<v-list class="flex-grow-1" :opened="drawerMini ? [] : expanded" @update:opened="(val) => expanded = val">
<channel-list />
<v-divider class="border-opacity-75 my-2" />
<realm-list />
</div>
</v-list>
<!-- User info -->
<v-list class="border-opacity-15 h-[64px]" style="border-top-width: thin"
:style="`margin-bottom: ${safeAreaBottom}`">
<v-list
class="border-opacity-15 h-[64px]"
style="border-top-width: thin"
:style="`margin-bottom: ${safeAreaBottom}`"
>
<v-list-item :subtitle="username" :title="nickname">
<template #prepend>
<v-avatar icon="mdi-account-circle" :image="id.userinfo.data?.picture" />
@ -52,8 +57,12 @@
</template>
<v-list density="compact">
<v-list-item title="Solarpass" prepend-icon="mdi-passport-biometric" target="_blank"
:href="passportUrl" />
<v-list-item
title="Solarpass"
prepend-icon="mdi-passport-biometric"
target="_blank"
:href="passportUrl"
/>
</v-list>
</v-menu>
@ -87,16 +96,15 @@
<script setup lang="ts">
import { computed, ref } from "vue"
import { useEditor } from "@/stores/editor"
import { useUserinfo } from "@/stores/userinfo"
import { useWellKnown } from "@/stores/wellKnown"
import { useUI } from "@/stores/ui"
import PostTools from "@/components/publish/PostTools.vue"
import RealmTools from "@/components/realms/RealmTools.vue"
import RealmList from "@/components/realms/RealmList.vue"
import NotificationList from "@/components/NotificationList.vue"
import ChannelList from "@/components/chat/channels/ChannelList.vue"
const ui = useUI()
const expanded = ref<string[]>(["channels"])
const safeAreaTop = computed(() => {
return `${ui.safeArea.top}px`

View File

@ -37,14 +37,12 @@
</v-fab>
<post-tools />
<realm-tools />
</template>
<script setup lang="ts">
import PostTools from "@/components/publish/PostTools.vue"
import RealmTools from "@/components/realms/RealmTools.vue"
import { useEditor } from "@/stores/editor"
import { useUserinfo } from "@/stores/userinfo"
import PostTools from "@/components/publish/PostTools.vue"
const id = useUserinfo()
const editor = useEditor()

View File

@ -13,7 +13,7 @@ export const useChannels = defineStore("channels", () => {
delete: false
})
const related_to = reactive<{ edit_to: any; delete_to: any }>({
const related = reactive<{ edit_to: any; delete_to: any }>({
edit_to: null,
delete_to: null
})
@ -64,5 +64,5 @@ export const useChannels = defineStore("channels", () => {
socket.close()
}
return { done, show, related_to, available, current, messages, list, connect, disconnect }
return { done, show, related, available, current, messages, list, connect, disconnect }
})

View File

@ -11,7 +11,7 @@ export const useRealms = defineStore("realms", () => {
delete: false
})
const related_to = reactive<{ edit_to: any; delete_to: any }>({
const related = reactive<{ edit_to: any; delete_to: any }>({
edit_to: null,
delete_to: null
})
@ -31,5 +31,5 @@ export const useRealms = defineStore("realms", () => {
}
}
return { done, show, related: related_to, available, list }
return { done, show, related, available, list }
})

View File

@ -9,6 +9,7 @@ export interface Userinfo {
isReady: boolean
isLoggedIn: boolean
displayName: string
idSet: { [id: string]: number }
data: any
}
@ -16,6 +17,7 @@ const defaultUserinfo: Userinfo = {
isReady: false,
isLoggedIn: false,
displayName: "Citizen",
idSet: {},
data: null
}
@ -47,17 +49,29 @@ export const useUserinfo = defineStore("userinfo", () => {
const res = await request("identity", "/api/users/me", {
headers: { Authorization: `Bearer ${await getAtk()}` }
})
if (res.status !== 200) {
return
}
const data = await res.json()
const federationResp = await Promise.all([
request("interactive", "/api/users/me", {
headers: { Authorization: `Bearer ${await getAtk()}` }
}),
request("messaging", "/api/users/me", {
headers: { Authorization: `Bearer ${await getAtk()}` }
})
])
userinfo.value = {
isReady: true,
isLoggedIn: true,
displayName: data["nick"],
idSet: {
interactive: (await federationResp[0].json())["id"],
messaging: (await federationResp[1].json())["id"]
},
data: data
}

View File

@ -73,10 +73,14 @@ async function readMore({ done }: any) {
}
watch(() => channels.current, (val) => {
if (val) {
readHistory()
}
}, { immediate: true })
if (val) {
pagination.page = 1
pagination.total = 0
readHistory()
}
},
{ immediate: true }
)
function scrollTop() {
window.scroll({ top: 0 })

View File

@ -105,6 +105,8 @@ watch(
() => route.params.realmId,
() => {
posts.value = []
pagination.page = 1
pagination.total = 0
readMetadata()
readPosts()
},