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

@ -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>

@ -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>

@ -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>

@ -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>

@ -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>

@ -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>

@ -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

@ -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">

@ -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">

@ -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>

@ -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()

@ -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>

@ -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`

@ -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()

@ -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 }
})

@ -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 }
})

@ -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
}

@ -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 })

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