✨ Editable realm
This commit is contained in:
parent
2f87f9bc32
commit
c14d3f70a3
36
pkg/views/src/components/realms/RealmAction.vue
Normal file
36
pkg/views/src/components/realms/RealmAction.vue
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
<template>
|
||||||
|
<v-menu>
|
||||||
|
<template #activator="{ props }">
|
||||||
|
<v-btn v-bind="props" icon="mdi-dots-vertical" variant="text" size="x-small" />
|
||||||
|
</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="editRealm" />
|
||||||
|
<v-list-item v-if="isOwned" append-icon="mdi-delete" title="Delete" @click="deleteRealm" />
|
||||||
|
</v-list>
|
||||||
|
</v-menu>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { useRealms } from "@/stores/realms";
|
||||||
|
import { useUserinfo } from "@/stores/userinfo";
|
||||||
|
import { computed } from "vue"
|
||||||
|
|
||||||
|
const id = useUserinfo()
|
||||||
|
const realms = useRealms()
|
||||||
|
|
||||||
|
const props = defineProps<{ item: any }>()
|
||||||
|
|
||||||
|
const isOwned = computed(() => props.item?.account_id === id.userinfo.data.id)
|
||||||
|
|
||||||
|
function editRealm() {
|
||||||
|
realms.related.edit_to = props.item
|
||||||
|
realms.show.editor = true
|
||||||
|
}
|
||||||
|
|
||||||
|
function deleteRealm() {
|
||||||
|
realms.related.delete_to = props.item
|
||||||
|
realms.show.delete = true
|
||||||
|
}
|
||||||
|
</script>
|
@ -1,37 +1,40 @@
|
|||||||
<template>
|
<template>
|
||||||
<v-dialog :model-value="props.show" @update:model-value="(val) => emits('update:show', val)" class="max-w-[540px]">
|
<v-card title="Organize a realm" prepend-icon="mdi-account-multiple" :loading="loading">
|
||||||
<v-card title="Organize a realm" prepend-icon="mdi-account-multiple" :loading="loading">
|
<v-form @submit.prevent="submit">
|
||||||
<v-form @submit.prevent="submit">
|
<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
|
||||||
<v-select
|
label="Realm type"
|
||||||
label="Realm type"
|
item-title="label"
|
||||||
item-title="label"
|
item-value="value"
|
||||||
item-value="value"
|
variant="outlined"
|
||||||
variant="outlined"
|
density="comfortable"
|
||||||
density="comfortable"
|
:items="realmTypeOptions"
|
||||||
:items="realmTypeOptions"
|
v-model="data.realm_type"
|
||||||
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>
|
|
||||||
|
|
||||||
<v-btn type="reset" color="grey-darken-3" @click="emits('update:show', false)">Cancel</v-btn>
|
<v-btn type="reset" color="grey-darken-3" @click="realms.show.editor = false">Cancel</v-btn>
|
||||||
<v-btn type="submit" :disabled="loading">Save</v-btn>
|
<v-btn type="submit" :disabled="loading">Save</v-btn>
|
||||||
</v-card-actions>
|
</v-card-actions>
|
||||||
</v-form>
|
</v-form>
|
||||||
</v-card>
|
</v-card>
|
||||||
</v-dialog>
|
|
||||||
|
<!-- @vue-ignore -->
|
||||||
|
<v-snackbar v-model="error" :timeout="5000">Something went wrong... {{ error }}</v-snackbar>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref } from "vue"
|
import { ref, watch } from "vue"
|
||||||
import { getAtk } from "@/stores/userinfo"
|
import { getAtk } from "@/stores/userinfo"
|
||||||
|
import { useRealms } from "@/stores/realms"
|
||||||
|
|
||||||
const props = defineProps<{ show: boolean }>()
|
const emits = defineEmits(["relist"])
|
||||||
const emits = defineEmits(["update:show", "relist"])
|
|
||||||
|
const realms = useRealms()
|
||||||
|
|
||||||
const realmTypeOptions = [
|
const realmTypeOptions = [
|
||||||
{ label: "Public Realm", value: 0 },
|
{ label: "Public Realm", value: 0 },
|
||||||
@ -53,9 +56,12 @@ async function submit(evt: SubmitEvent) {
|
|||||||
const payload = data.value
|
const payload = data.value
|
||||||
if (!payload.name) return
|
if (!payload.name) return
|
||||||
|
|
||||||
|
const url = realms.related.edit_to ? `/api/realms/${realms.related.edit_to?.id}` : "/api/moments";
|
||||||
|
const method = realms.related.edit_to ? "PUT" : "POST";
|
||||||
|
|
||||||
loading.value = true
|
loading.value = true
|
||||||
const res = await fetch("/api/realms", {
|
const res = await fetch(url, {
|
||||||
method: "POST",
|
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)
|
||||||
})
|
})
|
||||||
@ -64,8 +70,19 @@ async function submit(evt: SubmitEvent) {
|
|||||||
} else {
|
} else {
|
||||||
emits("relist")
|
emits("relist")
|
||||||
form.reset()
|
form.reset()
|
||||||
emits("update:show", false)
|
realms.done = true
|
||||||
|
realms.show.editor = false
|
||||||
}
|
}
|
||||||
loading.value = false
|
loading.value = false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
watch(
|
||||||
|
realms.related,
|
||||||
|
(val) => {
|
||||||
|
if (val.edit_to) {
|
||||||
|
data.value = JSON.parse(JSON.stringify(val.edit_to))
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{ immediate: true }
|
||||||
|
)
|
||||||
</script>
|
</script>
|
||||||
|
@ -6,52 +6,34 @@
|
|||||||
</v-list-subheader>
|
</v-list-subheader>
|
||||||
|
|
||||||
<v-list-item
|
<v-list-item
|
||||||
v-for="item in realms"
|
v-for="item in realms.available"
|
||||||
exact
|
exact
|
||||||
prepend-icon="mdi-account-multiple"
|
prepend-icon="mdi-account-multiple"
|
||||||
:to="{ name: 'realms.details', params: { realmId: item.id } }"
|
:to="{ name: 'realms.page', params: { realmId: item.id } }"
|
||||||
:title="item.name"
|
:title="item.name"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<v-divider v-if="realms.length > 0" class="border-opacity-75 my-2" />
|
<v-divider v-if="realms.available.length > 0" class="border-opacity-75 my-2" />
|
||||||
|
|
||||||
<v-list-item
|
<v-list-item
|
||||||
prepend-icon="mdi-plus"
|
prepend-icon="mdi-plus"
|
||||||
title="Create a realm"
|
title="Create a realm"
|
||||||
:disabled="!id.userinfo.isLoggedIn"
|
:disabled="!id.userinfo.isLoggedIn"
|
||||||
@click="creating = true"
|
@click="createRealm"
|
||||||
/>
|
/>
|
||||||
</v-list>
|
</v-list>
|
||||||
|
|
||||||
<realm-editor v-model:show="creating" @relist="list" />
|
|
||||||
|
|
||||||
<!-- @vue-ignore -->
|
|
||||||
<v-snackbar v-model="error" :timeout="5000">Something went wrong... {{ error }}</v-snackbar>
|
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { computed, ref } from "vue"
|
|
||||||
import { useUserinfo } from "@/stores/userinfo"
|
import { useUserinfo } from "@/stores/userinfo"
|
||||||
import { useEditor } from "@/stores/editor"
|
import { useRealms } from "@/stores/realms"
|
||||||
import RealmEditor from "@/components/realms/RealmEditor.vue"
|
|
||||||
|
|
||||||
const id = useUserinfo()
|
const id = useUserinfo()
|
||||||
const editor = useEditor()
|
const realms = useRealms()
|
||||||
|
|
||||||
const realms = computed(() => editor.availableRealms)
|
function createRealm() {
|
||||||
|
realms.related.edit_to = null
|
||||||
const creating = ref(false)
|
realms.related.delete_to = null
|
||||||
|
realms.show.editor = true
|
||||||
const error = ref<string | null>(null)
|
|
||||||
const reverting = ref(false)
|
|
||||||
|
|
||||||
async function list() {
|
|
||||||
reverting.value = true
|
|
||||||
try {
|
|
||||||
await editor.listRealms()
|
|
||||||
} catch (err) {
|
|
||||||
error.value = (err as Error).message
|
|
||||||
}
|
|
||||||
reverting.value = false
|
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
12
pkg/views/src/components/realms/RealmTools.vue
Normal file
12
pkg/views/src/components/realms/RealmTools.vue
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
<template>
|
||||||
|
<v-dialog v-model="realms.show.editor" class="max-w-[540px]">
|
||||||
|
<realm-editor @relist="realms.list" />
|
||||||
|
</v-dialog>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { useRealms } from "@/stores/realms"
|
||||||
|
import RealmEditor from "@/components/realms/RealmEditor.vue"
|
||||||
|
|
||||||
|
const realms = useRealms()
|
||||||
|
</script>
|
@ -88,7 +88,8 @@
|
|||||||
</div>
|
</div>
|
||||||
</v-menu>
|
</v-menu>
|
||||||
|
|
||||||
<post-action />
|
<post-tools />
|
||||||
|
<realm-tools />
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
@ -96,7 +97,8 @@ import { computed, ref } from "vue"
|
|||||||
import { useEditor } from "@/stores/editor"
|
import { useEditor } from "@/stores/editor"
|
||||||
import { useUserinfo } from "@/stores/userinfo"
|
import { useUserinfo } from "@/stores/userinfo"
|
||||||
import { useWellKnown } from "@/stores/wellKnown"
|
import { useWellKnown } from "@/stores/wellKnown"
|
||||||
import PostAction from "@/components/publish/PostAction.vue"
|
import PostTools from "@/components/publish/PostTools.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()
|
||||||
|
@ -28,8 +28,8 @@ const router = createRouter({
|
|||||||
|
|
||||||
{
|
{
|
||||||
path: "/realms/:realmId",
|
path: "/realms/:realmId",
|
||||||
name: "realms.details",
|
name: "realms.page",
|
||||||
component: () => import("@/views/realms/details.vue")
|
component: () => import("@/views/realms/page.vue")
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
import { defineStore } from "pinia"
|
import { defineStore } from "pinia"
|
||||||
import { reactive, ref } from "vue"
|
import { reactive, ref } from "vue"
|
||||||
import { checkLoggedIn, getAtk } from "@/stores/userinfo"
|
|
||||||
|
|
||||||
export const useEditor = defineStore("editor", () => {
|
export const useEditor = defineStore("editor", () => {
|
||||||
const done = ref(false)
|
const done = ref(false)
|
||||||
@ -26,22 +25,5 @@ export const useEditor = defineStore("editor", () => {
|
|||||||
delete_to: null
|
delete_to: null
|
||||||
})
|
})
|
||||||
|
|
||||||
const availableRealms = ref<any[]>([])
|
return { show, related, done }
|
||||||
|
|
||||||
async function listRealms() {
|
|
||||||
if (!checkLoggedIn()) return
|
|
||||||
|
|
||||||
const res = await fetch("/api/realms/me/available", {
|
|
||||||
headers: { Authorization: `Bearer ${getAtk()}` }
|
|
||||||
})
|
|
||||||
if (res.status !== 200) {
|
|
||||||
throw new Error(await res.text())
|
|
||||||
} else {
|
|
||||||
availableRealms.value = await res.json()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
listRealms().then(() => console.log("[STARTUP HOOK] Fetch available realm successes."))
|
|
||||||
|
|
||||||
return { show, related, availableRealms, listRealms, done }
|
|
||||||
})
|
})
|
||||||
|
36
pkg/views/src/stores/realms.ts
Normal file
36
pkg/views/src/stores/realms.ts
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
import { reactive, ref } from "vue"
|
||||||
|
import { defineStore } from "pinia"
|
||||||
|
import { checkLoggedIn, getAtk } from "@/stores/userinfo"
|
||||||
|
|
||||||
|
export const useRealms = defineStore("realms", () => {
|
||||||
|
const done = ref(false)
|
||||||
|
|
||||||
|
const show = reactive({
|
||||||
|
editor: false,
|
||||||
|
delete: false
|
||||||
|
})
|
||||||
|
|
||||||
|
const related_to = reactive<{ edit_to: any; delete_to: any }>({
|
||||||
|
edit_to: null,
|
||||||
|
delete_to: null
|
||||||
|
})
|
||||||
|
|
||||||
|
const available = ref<any[]>([])
|
||||||
|
|
||||||
|
async function list() {
|
||||||
|
if (!checkLoggedIn()) return
|
||||||
|
|
||||||
|
const res = await fetch("/api/realms/me/available", {
|
||||||
|
headers: { Authorization: `Bearer ${getAtk()}` }
|
||||||
|
})
|
||||||
|
if (res.status !== 200) {
|
||||||
|
throw new Error(await res.text())
|
||||||
|
} else {
|
||||||
|
available.value = await res.json()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
list().then(() => console.log("[STARTUP HOOK] Fetch available realm successes."))
|
||||||
|
|
||||||
|
return { done, show, related: related_to, available, list }
|
||||||
|
})
|
@ -1,83 +0,0 @@
|
|||||||
<template>
|
|
||||||
<v-container class="flex max-md:flex-col gap-3 overflow-auto max-h-[calc(100vh-64px)] no-scrollbar">
|
|
||||||
<div class="timeline flex-grow-1 mt-[-16px]">
|
|
||||||
<post-list v-model:posts="posts" :loader="readMore" />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="aside 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">
|
|
||||||
<template #text>
|
|
||||||
<h2 class="font-medium">Name</h2>
|
|
||||||
<p>{{ metadata?.name }}</p>
|
|
||||||
|
|
||||||
<h2 class="font-medium mt-2">Description</h2>
|
|
||||||
<p>{{ metadata?.description }}</p>
|
|
||||||
</template>
|
|
||||||
</v-card>
|
|
||||||
</div>
|
|
||||||
</v-container>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script setup lang="ts">
|
|
||||||
import { reactive, ref } from "vue";
|
|
||||||
import { request } from "@/scripts/request";
|
|
||||||
import { useRoute } from "vue-router";
|
|
||||||
import PostList from "@/components/posts/PostList.vue";
|
|
||||||
|
|
||||||
const route = useRoute();
|
|
||||||
|
|
||||||
const loading = ref(false);
|
|
||||||
const error = ref<string | null>(null);
|
|
||||||
const pagination = reactive({ page: 1, pageSize: 10, total: 0 });
|
|
||||||
|
|
||||||
const metadata = ref<any>(null);
|
|
||||||
const posts = ref<any[]>([]);
|
|
||||||
|
|
||||||
async function readMetadata() {
|
|
||||||
loading.value = true;
|
|
||||||
const res = await request(`/api/realms/${route.params.realmId}`);
|
|
||||||
if (res.status !== 200) {
|
|
||||||
error.value = await res.text();
|
|
||||||
} else {
|
|
||||||
error.value = null;
|
|
||||||
metadata.value = await res.json();
|
|
||||||
}
|
|
||||||
loading.value = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
async function readPosts() {
|
|
||||||
const res = await request(`/api/feed?` + new URLSearchParams({
|
|
||||||
take: pagination.pageSize.toString(),
|
|
||||||
offset: ((pagination.page - 1) * pagination.pageSize).toString(),
|
|
||||||
realmId: route.params.realmId as string
|
|
||||||
}));
|
|
||||||
if (res.status !== 200) {
|
|
||||||
error.value = await res.text();
|
|
||||||
} else {
|
|
||||||
error.value = null;
|
|
||||||
const data = await res.json();
|
|
||||||
pagination.total = data["count"];
|
|
||||||
posts.value.push(...(data["data"] ?? []));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async function readMore({ done }: any) {
|
|
||||||
// Reach the end of data
|
|
||||||
if (pagination.total <= pagination.page * pagination.pageSize) {
|
|
||||||
done("empty");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
pagination.page++;
|
|
||||||
await readPosts();
|
|
||||||
|
|
||||||
if (error.value != null) done("error");
|
|
||||||
else {
|
|
||||||
if (pagination.total > 0) done("ok");
|
|
||||||
else done("empty");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
readMetadata();
|
|
||||||
readPosts();
|
|
||||||
</script>
|
|
110
pkg/views/src/views/realms/page.vue
Normal file
110
pkg/views/src/views/realms/page.vue
Normal file
@ -0,0 +1,110 @@
|
|||||||
|
<template>
|
||||||
|
<v-container class="flex max-md:flex-col gap-3 overflow-auto max-h-[calc(100vh-64px)] no-scrollbar">
|
||||||
|
<div class="timeline flex-grow-1 mt-[-16px]">
|
||||||
|
<post-list v-model:posts="posts" :loader="readMore" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="aside 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">
|
||||||
|
<template #title>
|
||||||
|
<div class="flex justify-between">
|
||||||
|
<span>Realm Info</span>
|
||||||
|
|
||||||
|
<realm-action :item="metadata" />
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<template #text>
|
||||||
|
<div>
|
||||||
|
<h2 class="font-medium">Name</h2>
|
||||||
|
<p>{{ metadata?.name }}</p>
|
||||||
|
|
||||||
|
<h2 class="font-medium mt-2">Description</h2>
|
||||||
|
<div v-html="parseContent(metadata?.description ?? '')"></div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</v-card>
|
||||||
|
</div>
|
||||||
|
</v-container>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { reactive, ref, watch } from "vue"
|
||||||
|
import { request } from "@/scripts/request"
|
||||||
|
import { useRealms } from "@/stores/realms"
|
||||||
|
import { useRoute } from "vue-router"
|
||||||
|
import { parse } from "marked"
|
||||||
|
import dompurify from "dompurify"
|
||||||
|
import PostList from "@/components/posts/PostList.vue"
|
||||||
|
import RealmAction from "@/components/realms/RealmAction.vue"
|
||||||
|
|
||||||
|
const route = useRoute()
|
||||||
|
const realms = useRealms()
|
||||||
|
|
||||||
|
const loading = ref(false)
|
||||||
|
const error = ref<string | null>(null)
|
||||||
|
const pagination = reactive({ page: 1, pageSize: 10, total: 0 })
|
||||||
|
|
||||||
|
const metadata = ref<any>(null)
|
||||||
|
const posts = ref<any[]>([])
|
||||||
|
|
||||||
|
async function readMetadata() {
|
||||||
|
loading.value = true
|
||||||
|
const res = await request(`/api/realms/${route.params.realmId}`)
|
||||||
|
if (res.status !== 200) {
|
||||||
|
error.value = await res.text()
|
||||||
|
} else {
|
||||||
|
error.value = null
|
||||||
|
metadata.value = await res.json()
|
||||||
|
}
|
||||||
|
loading.value = false
|
||||||
|
}
|
||||||
|
|
||||||
|
async function readPosts() {
|
||||||
|
const res = await request(
|
||||||
|
`/api/feed?` +
|
||||||
|
new URLSearchParams({
|
||||||
|
take: pagination.pageSize.toString(),
|
||||||
|
offset: ((pagination.page - 1) * pagination.pageSize).toString(),
|
||||||
|
realmId: route.params.realmId as string
|
||||||
|
})
|
||||||
|
)
|
||||||
|
if (res.status !== 200) {
|
||||||
|
error.value = await res.text()
|
||||||
|
} else {
|
||||||
|
error.value = null
|
||||||
|
const data = await res.json()
|
||||||
|
pagination.total = data["count"]
|
||||||
|
posts.value.push(...(data["data"] ?? []))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function readMore({ done }: any) {
|
||||||
|
// Reach the end of data
|
||||||
|
if (pagination.total <= pagination.page * pagination.pageSize) {
|
||||||
|
done("empty")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
pagination.page++
|
||||||
|
await readPosts()
|
||||||
|
|
||||||
|
if (error.value != null) done("error")
|
||||||
|
else {
|
||||||
|
if (pagination.total > 0) done("ok")
|
||||||
|
else done("empty")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
readMetadata()
|
||||||
|
readPosts()
|
||||||
|
|
||||||
|
watch(realms, (val) => {
|
||||||
|
if (val.done) {
|
||||||
|
readMetadata().then(() => (realms.done = false))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
function parseContent(src: string): string {
|
||||||
|
return dompurify().sanitize(parse(src) as string)
|
||||||
|
}
|
||||||
|
</script>
|
Loading…
Reference in New Issue
Block a user