Invite accounts

This commit is contained in:
LittleSheep 2024-03-23 16:23:21 +08:00
parent 311700db04
commit 327941455e
26 changed files with 326 additions and 143 deletions

View File

@ -65,9 +65,10 @@ func NewRealm(user models.Account, name, description string, realmType int) (mod
func ListRealmMember(realmId uint) ([]models.RealmMember, error) { func ListRealmMember(realmId uint) ([]models.RealmMember, error) {
var members []models.RealmMember var members []models.RealmMember
if err := database.C.Where(&models.RealmMember{ if err := database.C.
RealmID: realmId, Where(&models.RealmMember{RealmID: realmId}).
}).Find(&members).Error; err != nil { Preload("Account").
Find(&members).Error; err != nil {
return members, err return members, err
} }

View File

@ -1,19 +1,20 @@
/* eslint-env node */ /* eslint-env node */
require('@rushstack/eslint-patch/modern-module-resolution') require("@rushstack/eslint-patch/modern-module-resolution")
module.exports = { module.exports = {
root: true, root: true,
'extends': [ extends: [
'plugin:vue/vue3-essential', "plugin:vue/vue3-essential",
'eslint:recommended', "eslint:recommended",
'@vue/eslint-config-typescript', "@vue/eslint-config-typescript",
'@vue/eslint-config-prettier/skip-formatting' "@vue/eslint-config-prettier/skip-formatting"
], ],
parserOptions: { parserOptions: {
ecmaVersion: 'latest' ecmaVersion: "latest"
}, },
rules: { rules: {
'vue/multi-word-component-names': 'off', "vue/multi-word-component-names": "off",
'vue/valid-v-for': 'off' "vue/valid-v-for": "off",
"vue/require-v-for-key": "off"
} }
} }

View File

@ -13,8 +13,8 @@ TypeScript cannot handle type information for `.vue` imports by default, so we r
If the standalone TypeScript plugin doesn't feel fast enough to you, Volar has also implemented a [Take Over Mode](https://github.com/johnsoncodehk/volar/discussions/471#discussioncomment-1361669) that is more performant. You can enable it by the following steps: If the standalone TypeScript plugin doesn't feel fast enough to you, Volar has also implemented a [Take Over Mode](https://github.com/johnsoncodehk/volar/discussions/471#discussioncomment-1361669) that is more performant. You can enable it by the following steps:
1. Disable the built-in TypeScript Extension 1. Disable the built-in TypeScript Extension
1) Run `Extensions: Show Built-in Extensions` from VSCode's command palette 1. Run `Extensions: Show Built-in Extensions` from VSCode's command palette
2) Find `TypeScript and JavaScript Language Features`, right click and select `Disable (Workspace)` 2. Find `TypeScript and JavaScript Language Features`, right click and select `Disable (Workspace)`
2. Reload the VSCode window by running `Developer: Reload Window` from the command palette. 2. Reload the VSCode window by running `Developer: Reload Window` from the command palette.
## Customize configuration ## Customize configuration

View File

@ -1,9 +1,9 @@
<!DOCTYPE html> <!doctype html>
<html lang="en"> <html lang="en">
<head> <head>
<meta charset="UTF-8"> <meta charset="UTF-8" />
<link rel="icon" type="image/xml+svg" href="/favicon.png"> <link rel="icon" type="image/xml+svg" href="/favicon.png" />
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Solarplaza</title> <title>Solarplaza</title>
</head> </head>
<body> <body>

View File

@ -1,4 +1,7 @@
html, body, #app, .v-application { html,
body,
#app,
.v-application {
overflow: auto !important; overflow: auto !important;
font-family: "Roboto Sans", ui-sans-serif, system-ui, sans-serif; font-family: "Roboto Sans", ui-sans-serif, system-ui, sans-serif;
} }

View File

@ -3,13 +3,13 @@
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import dompurify from "dompurify"; import dompurify from "dompurify"
import { parse } from "marked"; import { parse } from "marked"
const props = defineProps<{ item: any }>(); const props = defineProps<{ item: any }>()
function parseContent(src: string): string { function parseContent(src: string): string {
return dompurify().sanitize(parse(src) as string); return dompurify().sanitize(parse(src) as string)
} }
</script> </script>

View File

@ -14,7 +14,7 @@
<script setup lang="ts"> <script setup lang="ts">
import { useEditor } from "@/stores/editor" import { useEditor } from "@/stores/editor"
import { useUserinfo } from "@/stores/userinfo"; import { useUserinfo } from "@/stores/userinfo"
import { computed } from "vue" import { computed } from "vue"
const id = useUserinfo() const id = useUserinfo()

View File

@ -5,7 +5,7 @@
<div class="mb-3 px-1"> <div class="mb-3 px-1">
<v-card> <v-card>
<template #text> <template #text>
<post-item brief :item="item" @update:item="val => updateItem(idx, val)" /> <post-item brief :item="item" @update:item="(val) => updateItem(idx, val)" />
</template> </template>
</v-card> </v-card>
</div> </div>
@ -15,14 +15,14 @@
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import PostItem from "@/components/posts/PostItem.vue"; import PostItem from "@/components/posts/PostItem.vue"
const props = defineProps<{ posts: any[], loader: (opts: any) => Promise<any> }>(); const props = defineProps<{ posts: any[]; loader: (opts: any) => Promise<any> }>()
const emits = defineEmits(["update:posts"]); const emits = defineEmits(["update:posts"])
function updateItem(idx: number, data: any) { function updateItem(idx: number, data: any) {
const posts = JSON.parse(JSON.stringify(props.posts)); const posts = JSON.parse(JSON.stringify(props.posts))
posts[idx] = data; posts[idx] = data
emits("update:posts", posts); emits("update:posts", posts)
} }
</script> </script>

View File

@ -25,18 +25,37 @@
You are editing a post with alias <b class="font-mono">{{ editor.related.edit_to?.alias }}</b> You are editing a post with alias <b class="font-mono">{{ editor.related.edit_to?.alias }}</b>
</v-alert> </v-alert>
<v-textarea required class="mb-3" variant="outlined" label="Content" <v-textarea
hint="The content supports markdown syntax" v-model="data.content" @paste="pasteMedia" /> required
class="mb-3"
variant="outlined"
label="Content"
hint="The content supports markdown syntax"
v-model="data.content"
@paste="pasteMedia"
/>
<v-expansion-panels> <v-expansion-panels>
<v-expansion-panel title="Brief describe"> <v-expansion-panel title="Brief describe">
<template #text> <template #text>
<div class="mt-1"> <div class="mt-1">
<v-text-field required variant="solo-filled" density="comfortable" label="Title" :loading="reverting" <v-text-field
v-model="data.title" /> required
variant="solo-filled"
density="comfortable"
label="Title"
:loading="reverting"
v-model="data.title"
/>
<v-textarea required auto-grow variant="solo-filled" density="comfortable" label="Description" <v-textarea
v-model="data.description" /> required
auto-grow
variant="solo-filled"
density="comfortable"
label="Description"
v-model="data.description"
/>
</div> </div>
</template> </template>
</v-expansion-panel> </v-expansion-panel>
@ -47,7 +66,8 @@
<div> <div>
<p class="text-xs">Your content will visible for public at</p> <p class="text-xs">Your content will visible for public at</p>
<p class="text-lg font-medium"> <p class="text-lg font-medium">
{{ data.published_at ? new Date(data.published_at).toLocaleString() : new Date().toLocaleString() {{
data.published_at ? new Date(data.published_at).toLocaleString() : new Date().toLocaleString()
}} }}
</p> </p>
</div> </div>
@ -103,12 +123,12 @@
import { request } from "@/scripts/request" import { request } from "@/scripts/request"
import { useEditor } from "@/stores/editor" import { useEditor } from "@/stores/editor"
import { getAtk } from "@/stores/userinfo" import { getAtk } from "@/stores/userinfo"
import { useRealms } from "@/stores/realms"; import { useRealms } from "@/stores/realms"
import { computed, reactive, ref, watch } from "vue"; import { computed, reactive, ref, watch } from "vue"
import { useRoute, useRouter } from "vue-router" import { useRoute, useRouter } from "vue-router"
import PlannedPublish from "@/components/publish/parts/PlannedPublish.vue" import PlannedPublish from "@/components/publish/parts/PlannedPublish.vue"
import Media from "@/components/publish/parts/Media.vue" import Media from "@/components/publish/parts/Media.vue"
import PublishArea from "@/components/publish/parts/PublishArea.vue"; import PublishArea from "@/components/publish/parts/PublishArea.vue"
const route = useRoute() const route = useRoute()
const realms = useRealms() const realms = useRealms()
@ -118,7 +138,7 @@ const dialogs = reactive({
plan: false, plan: false,
categories: false, categories: false,
media: false, media: false,
area: false, area: false
}) })
const data = ref<any>({ const data = ref<any>({

View File

@ -6,20 +6,40 @@
You are editing a post with alias <b class="font-mono">{{ editor.related.edit_to?.alias }}</b> You are editing a post with alias <b class="font-mono">{{ editor.related.edit_to?.alias }}</b>
</v-alert> </v-alert>
<v-textarea required persistent-counter variant="outlined" label="What's happened?!" counter="1024" <v-textarea
v-model="data.content" @paste="pasteMedia" /> required
persistent-counter
variant="outlined"
label="What's happened?!"
counter="1024"
v-model="data.content"
@paste="pasteMedia"
/>
<div class="flex mt-[-18px]"> <div class="flex mt-[-18px]">
<v-tooltip text="Planned publish" location="start"> <v-tooltip text="Planned publish" location="start">
<template #activator="{ props }"> <template #activator="{ props }">
<v-btn v-bind="props" type="button" variant="text" icon="mdi-calendar" size="small" <v-btn
@click="dialogs.plan = true" /> v-bind="props"
type="button"
variant="text"
icon="mdi-calendar"
size="small"
@click="dialogs.plan = true"
/>
</template> </template>
</v-tooltip> </v-tooltip>
<v-tooltip text="Media" location="start"> <v-tooltip text="Media" location="start">
<template #activator="{ props }"> <template #activator="{ props }">
<v-btn v-bind="props" icon class="text-none" type="button" variant="text" size="small" <v-btn
@click="dialogs.media = true"> v-bind="props"
icon
class="text-none"
type="button"
variant="text"
size="small"
@click="dialogs.media = true"
>
<v-badge v-if="data.attachments.length > 0" :content="data.attachments.length"> <v-badge v-if="data.attachments.length > 0" :content="data.attachments.length">
<v-icon icon="mdi-camera" /> <v-icon icon="mdi-camera" />
</v-badge> </v-badge>

View File

@ -20,6 +20,7 @@
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { request } from "@/scripts/request"
import { useEditor } from "@/stores/editor" import { useEditor } from "@/stores/editor"
import { getAtk } from "@/stores/userinfo" import { getAtk } from "@/stores/userinfo"
import { ref } from "vue" import { ref } from "vue"
@ -35,7 +36,7 @@ async function deletePost() {
const url = `/api/p/${target.model_type}/${target.id}` const url = `/api/p/${target.model_type}/${target.id}`
loading.value = true loading.value = true
const res = await fetch(url, { const res = await request(url, {
method: "DELETE", method: "DELETE",
headers: { Authorization: `Bearer ${getAtk()}` } headers: { Authorization: `Bearer ${getAtk()}` }
}) })

View File

@ -28,10 +28,10 @@
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { useRealms } from "@/stores/realms"; import { useRealms } from "@/stores/realms"
const realms = useRealms(); const realms = useRealms()
const props = defineProps<{ show: boolean; value: string | null }>(); const props = defineProps<{ show: boolean; value: string | null }>()
const emits = defineEmits(["update:show", "update:value"]); const emits = defineEmits(["update:show", "update:value"])
</script> </script>

View File

@ -13,8 +13,8 @@
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { useRealms } from "@/stores/realms"; import { useRealms } from "@/stores/realms"
import { useUserinfo } from "@/stores/userinfo"; import { useUserinfo } from "@/stores/userinfo"
import { computed } from "vue" import { computed } from "vue"
const id = useUserinfo() const id = useUserinfo()

View File

@ -20,6 +20,7 @@
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { request } from "@/scripts/request"
import { useRealms } from "@/stores/realms" import { useRealms } from "@/stores/realms"
import { getAtk } from "@/stores/userinfo" import { getAtk } from "@/stores/userinfo"
import { useRoute, useRouter } from "vue-router" import { useRoute, useRouter } from "vue-router"
@ -40,7 +41,7 @@ async function deletePost() {
const url = `/api/realms/${target.id}` const url = `/api/realms/${target.id}`
loading.value = true loading.value = true
const res = await fetch(url, { const res = await request(url, {
method: "DELETE", method: "DELETE",
headers: { Authorization: `Bearer ${getAtk()}` } headers: { Authorization: `Bearer ${getAtk()}` }
}) })

View File

@ -4,8 +4,15 @@
<v-card-text> <v-card-text>
<v-text-field label="Name" variant="outlined" density="comfortable" v-model="data.name" /> <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-textarea label="Description" variant="outlined" density="comfortable" v-model="data.description" />
<v-select label="Realm type" item-title="label" item-value="value" variant="outlined" density="comfortable" <v-select
:items="realmTypeOptions" v-model="data.realm_type" /> label="Realm type"
item-title="label"
item-value="value"
variant="outlined"
density="comfortable"
:items="realmTypeOptions"
v-model="data.realm_type"
/>
</v-card-text> </v-card-text>
<v-card-actions> <v-card-actions>
<v-spacer></v-spacer> <v-spacer></v-spacer>
@ -24,6 +31,7 @@
import { ref, watch } from "vue" import { ref, watch } from "vue"
import { getAtk } from "@/stores/userinfo" import { getAtk } from "@/stores/userinfo"
import { useRealms } from "@/stores/realms" import { useRealms } from "@/stores/realms"
import { request } from "@/scripts/request"
const emits = defineEmits(["relist"]) const emits = defineEmits(["relist"])
@ -53,7 +61,7 @@ async function submit(evt: SubmitEvent) {
const method = realms.related.edit_to ? "PUT" : "POST" const method = realms.related.edit_to ? "PUT" : "POST"
loading.value = true loading.value = true
const res = await fetch(url, { const res = await request(url, {
method: method, method: method,
headers: { "Content-Type": "application/json", Authorization: `Bearer ${getAtk()}` }, headers: { "Content-Type": "application/json", Authorization: `Bearer ${getAtk()}` },
body: JSON.stringify(payload) body: JSON.stringify(payload)

View File

@ -0,0 +1,126 @@
<template>
<div>
<v-list density="comfortable" lines="one">
<v-list-item v-for="item in members" :title="item.account.nick">
<template #prepend>
<v-avatar
color="grey-lighten-2"
icon="mdi-account-circle"
class="rounded-card me-2"
size="small"
:image="item?.account.avatar"
/>
</template>
<template #subtitle>@{{ item.account.name }}</template>
</v-list-item>
</v-list>
<div v-if="isOwned">
<v-divider class="mt-2 mb-3 border-opacity-50 mx-[-1rem]" />
<div class="px-3">
<v-dialog class="max-w-[540px]">
<template #activator="{ props }">
<v-btn v-bind="props" block prepend-icon="mdi-account-plus" variant="plain"> Invite someone </v-btn>
</template>
<template #default="{ isActive }">
<v-card prepend-icon="mdi-account-plus" title="Invite someone">
<v-form @submit.prevent="inviteMember">
<v-card-text>
<v-text-field
label="Username"
variant="outlined"
density="comfortable"
hint="Require username not the nickname"
v-model="data.account_name"
/>
</v-card-text>
<v-card-actions>
<v-spacer></v-spacer>
<v-btn type="reset" color="grey-darken-3" @click="isActive.value = false">Cancel</v-btn>
<v-btn type="submit" :disabled="loading">Invite</v-btn>
</v-card-actions>
</v-form>
</v-card>
</template>
</v-dialog>
</div>
</div>
<!-- @vue-ignore -->
<v-snackbar v-model="error" :timeout="5000">Something went wrong... {{ error }}</v-snackbar>
</div>
</template>
<script setup lang="ts">
import { ref, watch } from "vue"
import { request } from "@/scripts/request"
import { getAtk, useUserinfo } from "@/stores/userinfo"
import { computed } from "vue"
const id = useUserinfo()
const props = defineProps<{ item: any }>()
const data = ref<any>({
account_name: ""
})
const members = ref<any[]>([])
const isOwned = computed(() => {
return id.userinfo.data?.id === props.item?.account_id
})
const loading = ref(false)
const error = ref<string | null>(null)
watch(
() => props.item,
(val) => {
if (val?.id) {
listMembers(val.id)
}
},
{ deep: true, immediate: true }
)
async function listMembers(id: number) {
loading.value = true
const res = await request(`/api/realms/${id}/members`)
if (res.status !== 200) {
error.value = await res.text()
} else {
error.value = null
members.value = await res.json()
}
loading.value = false
}
async function inviteMember(evt: SubmitEvent) {
const form = evt.target as HTMLFormElement
const payload = data.value
loading.value = true
const res = await request(`/api/realms/${props.item?.id}/invite`, {
method: "POST",
headers: { "Content-Type": "application/json", Authorization: `Bearer ${getAtk()}` },
body: JSON.stringify(payload)
})
if (res.status !== 200) {
error.value = await res.text()
} else {
form.reset()
await listMembers(props.item?.id)
}
loading.value = false
}
</script>
<style>
.rounded-card {
border-radius: 8px;
}
</style>

View File

@ -99,7 +99,7 @@ import { useUserinfo } from "@/stores/userinfo"
import { useWellKnown } from "@/stores/wellKnown" import { useWellKnown } from "@/stores/wellKnown"
import PostTools from "@/components/publish/PostTools.vue" import PostTools from "@/components/publish/PostTools.vue"
import RealmTools from "@/components/realms/RealmTools.vue" import RealmTools from "@/components/realms/RealmTools.vue"
import RealmList from "@/components/realms/RealmList.vue"; import RealmList from "@/components/realms/RealmList.vue"
const id = useUserinfo() const id = useUserinfo()
const editor = useEditor() const editor = useEditor()

View File

@ -1,32 +1,32 @@
import "virtual:uno.css"; import "virtual:uno.css"
import "./assets/utils.css"; import "./assets/utils.css"
import { createApp } from "vue"; import { createApp } from "vue"
import { createPinia } from "pinia"; import { createPinia } from "pinia"
import "vuetify/styles"; import "vuetify/styles"
import { createVuetify } from "vuetify"; import { createVuetify } from "vuetify"
import { md3 } from "vuetify/blueprints"; import { md3 } from "vuetify/blueprints"
import * as components from "vuetify/components"; import * as components from "vuetify/components"
import * as labsComponents from 'vuetify/labs/components' import * as labsComponents from "vuetify/labs/components"
import * as directives from "vuetify/directives"; import * as directives from "vuetify/directives"
import "@mdi/font/css/materialdesignicons.min.css"; import "@mdi/font/css/materialdesignicons.min.css"
import "@fontsource/roboto/latin.css"; import "@fontsource/roboto/latin.css"
import "@unocss/reset/tailwind.css"; import "@unocss/reset/tailwind.css"
import index from "./index.vue"; import index from "./index.vue"
import router from "./router"; import router from "./router"
const app = createApp(index); const app = createApp(index)
app.use( app.use(
createVuetify({ createVuetify({
directives, directives,
components: { components: {
...components, ...components,
...labsComponents, ...labsComponents
}, },
blueprint: md3, blueprint: md3,
theme: { theme: {
@ -46,9 +46,9 @@ app.use(
} }
} }
}) })
); )
app.use(createPinia()); app.use(createPinia())
app.use(router); app.use(router)
app.mount("#app"); app.mount("#app")

View File

@ -1,5 +1,5 @@
import { createRouter, createWebHistory } from "vue-router"; import { createRouter, createWebHistory } from "vue-router"
import MasterLayout from "@/layouts/master.vue"; import MasterLayout from "@/layouts/master.vue"
const router = createRouter({ const router = createRouter({
history: createWebHistory(import.meta.env.BASE_URL), history: createWebHistory(import.meta.env.BASE_URL),
@ -14,7 +14,6 @@ const router = createRouter({
component: () => import("@/views/explore.vue") component: () => import("@/views/explore.vue")
}, },
{ {
path: "/p/moments/:alias", path: "/p/moments/:alias",
name: "posts.details.moments", name: "posts.details.moments",
@ -34,6 +33,6 @@ const router = createRouter({
] ]
} }
] ]
}); })
export default router; export default router

View File

@ -1,6 +1,7 @@
import { reactive, ref } from "vue" import { reactive, ref } from "vue"
import { defineStore } from "pinia" import { defineStore } from "pinia"
import { checkLoggedIn, getAtk } from "@/stores/userinfo" import { checkLoggedIn, getAtk } from "@/stores/userinfo"
import { request } from "@/scripts/request"
export const useRealms = defineStore("realms", () => { export const useRealms = defineStore("realms", () => {
const done = ref(false) const done = ref(false)
@ -20,7 +21,7 @@ export const useRealms = defineStore("realms", () => {
async function list() { async function list() {
if (!checkLoggedIn()) return if (!checkLoggedIn()) return
const res = await fetch("/api/realms/me/available", { const res = await request("/api/realms/me/available", {
headers: { Authorization: `Bearer ${getAtk()}` } headers: { Authorization: `Bearer ${getAtk()}` }
}) })
if (res.status !== 200) { if (res.status !== 200) {

View File

@ -31,25 +31,25 @@ export const useUserinfo = defineStore("userinfo", () => {
async function readProfiles() { async function readProfiles() {
if (!checkLoggedIn()) { if (!checkLoggedIn()) {
isReady.value = true; isReady.value = true
} }
const res = await request("/api/users/me", { const res = await request("/api/users/me", {
headers: { "Authorization": `Bearer ${getAtk()}` } headers: { Authorization: `Bearer ${getAtk()}` }
}); })
if (res.status !== 200) { if (res.status !== 200) {
return; return
} }
const data = await res.json(); const data = await res.json()
userinfo.value = { userinfo.value = {
isReady: true, isReady: true,
isLoggedIn: true, isLoggedIn: true,
displayName: data["nick"], displayName: data["nick"],
data: data data: data
}; }
} }
return { userinfo, isReady, readProfiles } return { userinfo, isReady, readProfiles }

View File

@ -15,46 +15,49 @@
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import PostList from "@/components/posts/PostList.vue"; import PostList from "@/components/posts/PostList.vue"
import { reactive, ref } from "vue"; import { reactive, ref } from "vue"
import { request } from "@/scripts/request"; import { request } from "@/scripts/request"
const error = ref<string | null>(null); const error = ref<string | null>(null)
const pagination = reactive({ page: 1, pageSize: 10, total: 0 }); const pagination = reactive({ page: 1, pageSize: 10, total: 0 })
const posts = ref<any[]>([]); const posts = ref<any[]>([])
async function readPosts() { async function readPosts() {
const res = await request(`/api/feed?` + new URLSearchParams({ const res = await request(
`/api/feed?` +
new URLSearchParams({
take: pagination.pageSize.toString(), take: pagination.pageSize.toString(),
offset: ((pagination.page - 1) * pagination.pageSize).toString() offset: ((pagination.page - 1) * pagination.pageSize).toString()
})); })
)
if (res.status !== 200) { if (res.status !== 200) {
error.value = await res.text(); error.value = await res.text()
} else { } else {
error.value = null; error.value = null
const data = await res.json(); const data = await res.json()
pagination.total = data["count"]; pagination.total = data["count"]
posts.value.push(...(data["data"] ?? [])); posts.value.push(...(data["data"] ?? []))
} }
} }
async function readMore({ done }: any) { async function readMore({ done }: any) {
// Reach the end of data // Reach the end of data
if (pagination.total <= pagination.page * pagination.pageSize) { if (pagination.total <= pagination.page * pagination.pageSize) {
done("empty"); done("empty")
return; return
} }
pagination.page++; pagination.page++
await readPosts(); await readPosts()
if (error.value != null) done("error"); if (error.value != null) done("error")
else { else {
if (pagination.total > 0) done("ok"); if (pagination.total > 0) done("ok")
else done("empty"); else done("empty")
} }
} }
readPosts(); readPosts()
</script> </script>

View File

@ -5,7 +5,7 @@
</div> </div>
<div class="aside md:sticky top-0 w-full h-fit md:min-w-[280px] md:max-w-[320px] max-md:order-first"> <div class="aside md:sticky top-0 w-full h-fit md:min-w-[280px] md:max-w-[320px] max-md:order-first">
<v-card title="Realm Info" :loading="loading"> <v-card :loading="loading">
<template #title> <template #title>
<div class="flex justify-between"> <div class="flex justify-between">
<span>Realm Info</span> <span>Realm Info</span>
@ -23,6 +23,10 @@
</div> </div>
</template> </template>
</v-card> </v-card>
<v-card class="mt-3 pb-3" title="Realm Members">
<realm-members :item="metadata" />
</v-card>
</div> </div>
</v-container> </v-container>
</template> </template>
@ -36,6 +40,7 @@ import { parse } from "marked"
import dompurify from "dompurify" import dompurify from "dompurify"
import PostList from "@/components/posts/PostList.vue" import PostList from "@/components/posts/PostList.vue"
import RealmAction from "@/components/realms/RealmAction.vue" import RealmAction from "@/components/realms/RealmAction.vue"
import RealmMembers from "@/components/realms/RealmMembers.vue"
const route = useRoute() const route = useRoute()
const realms = useRealms() const realms = useRealms()

View File

@ -1,12 +1,6 @@
{ {
"extends": "@tsconfig/node20/tsconfig.json", "extends": "@tsconfig/node20/tsconfig.json",
"include": [ "include": ["vite.config.*", "vitest.config.*", "cypress.config.*", "nightwatch.conf.*", "playwright.config.*"],
"vite.config.*",
"vitest.config.*",
"cypress.config.*",
"nightwatch.conf.*",
"playwright.config.*"
],
"compilerOptions": { "compilerOptions": {
"composite": true, "composite": true,
"noEmit": true, "noEmit": true,

View File

@ -1,4 +1,4 @@
import { defineConfig, presetAttributify, presetTypography, presetUno } from "unocss"; import { defineConfig, presetAttributify, presetTypography, presetUno } from "unocss"
export default defineConfig({ export default defineConfig({
presets: [presetAttributify(), presetTypography(), presetUno({ preflight: false })] presets: [presetAttributify(), presetTypography(), presetUno({ preflight: false })]