✨ Invite accounts
This commit is contained in:
parent
311700db04
commit
327941455e
2
.github/workflows/nightly.yml
vendored
2
.github/workflows/nightly.yml
vendored
@ -2,7 +2,7 @@ name: release-nightly
|
|||||||
|
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
branches: [ master ]
|
branches: [master]
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build-docker:
|
build-docker:
|
||||||
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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
|
||||||
|
@ -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>
|
||||||
|
@ -1,12 +1,15 @@
|
|||||||
html, body, #app, .v-application {
|
html,
|
||||||
overflow: auto !important;
|
body,
|
||||||
font-family: "Roboto Sans", ui-sans-serif, system-ui, sans-serif;
|
#app,
|
||||||
|
.v-application {
|
||||||
|
overflow: auto !important;
|
||||||
|
font-family: "Roboto Sans", ui-sans-serif, system-ui, sans-serif;
|
||||||
}
|
}
|
||||||
|
|
||||||
.no-scrollbar {
|
.no-scrollbar {
|
||||||
scrollbar-width: none;
|
scrollbar-width: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.no-scrollbar::-webkit-scrollbar {
|
.no-scrollbar::-webkit-scrollbar {
|
||||||
width: 0;
|
width: 0;
|
||||||
}
|
}
|
||||||
|
@ -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>
|
||||||
|
|
||||||
|
@ -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()
|
||||||
|
@ -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>
|
||||||
|
@ -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>({
|
||||||
|
@ -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>
|
||||||
|
@ -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()}` }
|
||||||
})
|
})
|
||||||
|
@ -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>
|
||||||
|
@ -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()
|
||||||
|
@ -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()}` }
|
||||||
})
|
})
|
||||||
|
@ -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)
|
||||||
|
126
pkg/views/src/components/realms/RealmMembers.vue
Normal file
126
pkg/views/src/components/realms/RealmMembers.vue
Normal 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>
|
@ -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()
|
||||||
|
@ -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")
|
||||||
|
@ -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
|
||||||
|
@ -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) {
|
||||||
|
@ -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 }
|
||||||
|
@ -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(
|
||||||
take: pagination.pageSize.toString(),
|
`/api/feed?` +
|
||||||
offset: ((pagination.page - 1) * pagination.pageSize).toString()
|
new URLSearchParams({
|
||||||
}));
|
take: 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>
|
||||||
|
@ -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()
|
||||||
|
@ -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,
|
||||||
|
@ -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 })]
|
||||||
|
Loading…
Reference in New Issue
Block a user