✨ Post detail page
This commit is contained in:
		
							
								
								
									
										7
									
								
								DysonNetwork.Drive/Client/src/dy-prefetch.d.ts
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										7
									
								
								DysonNetwork.Drive/Client/src/dy-prefetch.d.ts
									
									
									
									
										vendored
									
									
										Normal file
									
								
							@@ -0,0 +1,7 @@
 | 
			
		||||
export {}
 | 
			
		||||
 | 
			
		||||
declare global {
 | 
			
		||||
  interface Window {
 | 
			
		||||
    DyPrefetch?: any
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
@@ -183,7 +183,7 @@ async function fetchUser() {
 | 
			
		||||
 | 
			
		||||
  console.log('[Fetch] Using the API to load user data.')
 | 
			
		||||
  try {
 | 
			
		||||
    const resp = await fetch(`/api/accounts/${route.params.name}`)
 | 
			
		||||
    const resp = await fetch(`/api/accounts/${route.params.slug}`)
 | 
			
		||||
    user.value = await resp.json()
 | 
			
		||||
  } catch (err) {
 | 
			
		||||
    console.error(err)
 | 
			
		||||
 
 | 
			
		||||
@@ -43,13 +43,11 @@ public class SubscriptionController(SubscriptionService subscriptions, AfdianPay
 | 
			
		||||
    {
 | 
			
		||||
        if (HttpContext.Items["CurrentUser"] is not Account.Account currentUser) return Unauthorized();
 | 
			
		||||
 | 
			
		||||
        var subs = await db.WalletSubscriptions
 | 
			
		||||
        var subscription = await db.WalletSubscriptions
 | 
			
		||||
            .Where(s => s.AccountId == currentUser.Id && s.IsActive)
 | 
			
		||||
            .Where(s => EF.Functions.ILike(s.Identifier, prefix + "%"))
 | 
			
		||||
            .OrderByDescending(s => s.BegunAt)
 | 
			
		||||
            .ToListAsync();
 | 
			
		||||
        if (subs.Count == 0) return NotFound();
 | 
			
		||||
        var subscription = subs.FirstOrDefault(s => s.IsAvailable);
 | 
			
		||||
            .FirstOrDefaultAsync();
 | 
			
		||||
        if (subscription is null) return NotFound();
 | 
			
		||||
 | 
			
		||||
        return Ok(subscription);
 | 
			
		||||
 
 | 
			
		||||
@@ -400,9 +400,9 @@ public class SubscriptionService(
 | 
			
		||||
            .OrderByDescending(s => s.BegunAt)
 | 
			
		||||
            .FirstOrDefaultAsync();
 | 
			
		||||
 | 
			
		||||
        // Cache the result if found (with 30 minutes expiry)
 | 
			
		||||
        // Cache the result if found (with 5 minutes expiry)
 | 
			
		||||
        if (subscription != null)
 | 
			
		||||
            await cache.SetAsync(cacheKey, subscription, TimeSpan.FromMinutes(30));
 | 
			
		||||
            await cache.SetAsync(cacheKey, subscription, TimeSpan.FromMinutes(5));
 | 
			
		||||
 | 
			
		||||
        return subscription;
 | 
			
		||||
    }
 | 
			
		||||
 
 | 
			
		||||
@@ -6,6 +6,7 @@
 | 
			
		||||
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
 | 
			
		||||
    <title>Solar Network</title>
 | 
			
		||||
    <app-data />
 | 
			
		||||
    <og-data />
 | 
			
		||||
  </head>
 | 
			
		||||
  <body>
 | 
			
		||||
    <div id="app"></div>
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										7
									
								
								DysonNetwork.Sphere/Client/src/dy-prefetch.d.ts
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										7
									
								
								DysonNetwork.Sphere/Client/src/dy-prefetch.d.ts
									
									
									
									
										vendored
									
									
										Normal file
									
								
							@@ -0,0 +1,7 @@
 | 
			
		||||
export {}
 | 
			
		||||
 | 
			
		||||
declare global {
 | 
			
		||||
  interface Window {
 | 
			
		||||
    DyPrefetch?: any
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
@@ -10,6 +10,11 @@ const router = createRouter({
 | 
			
		||||
      name: 'index',
 | 
			
		||||
      component: () => import('../views/index.vue'),
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
      path: '/posts/:slug',
 | 
			
		||||
      name: 'postDetail',
 | 
			
		||||
      component: () => import('../views/posts.vue'),
 | 
			
		||||
    },
 | 
			
		||||
  ],
 | 
			
		||||
})
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -4,7 +4,11 @@
 | 
			
		||||
      <n-gi span="3">
 | 
			
		||||
        <n-infinite-scroll style="height: calc(100vh - 57px)" :distance="10" @load="fetchActivites">
 | 
			
		||||
          <div v-for="activity in activites" :key="activity.id" class="mt-4">
 | 
			
		||||
            <post-item v-if="activity.type == 'posts.new'" :item="activity.data" />
 | 
			
		||||
            <post-item
 | 
			
		||||
              v-if="activity.type.startsWith('posts')"
 | 
			
		||||
              :item="activity.data"
 | 
			
		||||
              @click="router.push('/posts/' + activity.id)"
 | 
			
		||||
            />
 | 
			
		||||
          </div>
 | 
			
		||||
        </n-infinite-scroll>
 | 
			
		||||
      </n-gi>
 | 
			
		||||
@@ -40,11 +44,14 @@
 | 
			
		||||
<script setup lang="ts">
 | 
			
		||||
import { NCard, NInfiniteScroll, NGrid, NGi, NAlert, NA, NHr } from 'naive-ui'
 | 
			
		||||
import { computed, onMounted, ref } from 'vue'
 | 
			
		||||
import { useRouter } from 'vue-router'
 | 
			
		||||
import { useUserStore } from '@/stores/user'
 | 
			
		||||
 | 
			
		||||
import PostEditor from '@/components/PostEditor.vue'
 | 
			
		||||
import PostItem from '@/components/PostItem.vue'
 | 
			
		||||
 | 
			
		||||
const router = useRouter()
 | 
			
		||||
 | 
			
		||||
const userStore = useUserStore()
 | 
			
		||||
 | 
			
		||||
const version = ref<any>(null)
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										100
									
								
								DysonNetwork.Sphere/Client/src/views/posts.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										100
									
								
								DysonNetwork.Sphere/Client/src/views/posts.vue
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,100 @@
 | 
			
		||||
<template>
 | 
			
		||||
  <div v-if="post" class="container max-w-5xl mx-auto mt-4">
 | 
			
		||||
    <n-grid cols="1 l:5" responsive="screen" :x-gap="16">
 | 
			
		||||
      <n-gi span="3">
 | 
			
		||||
        <post-item :item="post" />
 | 
			
		||||
      </n-gi>
 | 
			
		||||
      <n-gi span="2">
 | 
			
		||||
        <n-card title="About the author">
 | 
			
		||||
          <div class="relative mb-7">
 | 
			
		||||
            <img
 | 
			
		||||
              class="object-cover rounded-lg"
 | 
			
		||||
              style="aspect-ratio: 16/7"
 | 
			
		||||
              :src="publisherBackground"
 | 
			
		||||
            />
 | 
			
		||||
            <div class="absolute left-3 bottom-[-24px]">
 | 
			
		||||
              <n-avatar :src="publisherAvatar" :size="64" round bordered />
 | 
			
		||||
            </div>
 | 
			
		||||
          </div>
 | 
			
		||||
          <div class="flex flex-col">
 | 
			
		||||
            <p class="flex gap-1 items-baseline">
 | 
			
		||||
              <span class="font-bold">
 | 
			
		||||
                {{ post.publisher.nick }}
 | 
			
		||||
              </span>
 | 
			
		||||
              <span class="text-sm"> @{{ post.publisher.name }} </span>
 | 
			
		||||
            </p>
 | 
			
		||||
            <div class="max-h-96 overflow-y-auto">
 | 
			
		||||
              <div
 | 
			
		||||
                class="prose prose-sm dark:prose-invert prose-slate"
 | 
			
		||||
                v-if="publisherBio"
 | 
			
		||||
                v-html="publisherBio"
 | 
			
		||||
              ></div>
 | 
			
		||||
            </div>
 | 
			
		||||
          </div>
 | 
			
		||||
        </n-card>
 | 
			
		||||
      </n-gi>
 | 
			
		||||
    </n-grid>
 | 
			
		||||
  </div>
 | 
			
		||||
  <div v-else-if="notFound" class="flex justify-center items-center h-full">
 | 
			
		||||
    <n-result
 | 
			
		||||
      status="404"
 | 
			
		||||
      title="Post not found"
 | 
			
		||||
      description="The post you are looking cannot be found, it might be deleted, or you have no permission to view it or it just never been posted."
 | 
			
		||||
    />
 | 
			
		||||
  </div>
 | 
			
		||||
  <div v-else class="flex justify-center items-center h-full">
 | 
			
		||||
    <n-spin />
 | 
			
		||||
  </div>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script setup lang="ts">
 | 
			
		||||
import { NGrid, NGi, NCard, NAvatar } from 'naive-ui'
 | 
			
		||||
import { computed, onMounted, ref, watch } from 'vue'
 | 
			
		||||
import { useRoute } from 'vue-router'
 | 
			
		||||
import { Marked } from 'marked'
 | 
			
		||||
 | 
			
		||||
import PostItem from '@/components/PostItem.vue'
 | 
			
		||||
 | 
			
		||||
const route = useRoute()
 | 
			
		||||
 | 
			
		||||
const post = ref<any>()
 | 
			
		||||
const notFound = ref(false)
 | 
			
		||||
 | 
			
		||||
async function fetchPost() {
 | 
			
		||||
  if (window.DyPrefetch?.Post != null) {
 | 
			
		||||
    console.log('[Fetch] Use the pre-rendered post data.')
 | 
			
		||||
    post.value = window.DyPrefetch.Post
 | 
			
		||||
    return
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  console.log('[Fetch] Using the API to load user data.')
 | 
			
		||||
  try {
 | 
			
		||||
    const resp = await fetch(`/api/posts/${route.params.slug}`)
 | 
			
		||||
    post.value = await resp.json()
 | 
			
		||||
  } catch (err) {
 | 
			
		||||
    console.error(err)
 | 
			
		||||
    notFound.value = true
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
onMounted(() => fetchPost())
 | 
			
		||||
 | 
			
		||||
const publisherAvatar = computed(() =>
 | 
			
		||||
  post.value.publisher.picture ? `/cgi/drive/files/${post.value.publisher.picture.id}` : undefined,
 | 
			
		||||
)
 | 
			
		||||
const publisherBackground = computed(() =>
 | 
			
		||||
  post.value.publisher.background
 | 
			
		||||
    ? `/cgi/drive/files/${post.value.publisher.background.id}`
 | 
			
		||||
    : undefined,
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
const marked = new Marked()
 | 
			
		||||
 | 
			
		||||
const publisherBio = ref('')
 | 
			
		||||
watch(
 | 
			
		||||
  post,
 | 
			
		||||
  async (value) => {
 | 
			
		||||
    if (value?.publisher?.bio) publisherBio.value = await marked.parse(value.publisher.bio)
 | 
			
		||||
  },
 | 
			
		||||
  { immediate: true, deep: true },
 | 
			
		||||
)
 | 
			
		||||
</script>
 | 
			
		||||
		Reference in New Issue
	
	Block a user