mirror of
https://github.com/nagisa77/OpenIsle.git
synced 2026-02-19 13:30:55 +08:00
Merge pull request #460 from nagisa77/feature/nuxt_opt_v1
Feature/nuxt opt
This commit is contained in:
@@ -47,6 +47,10 @@ export default {
|
||||
showLoginOverlay: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
parentUserName: {
|
||||
type: String,
|
||||
default: ''
|
||||
}
|
||||
},
|
||||
components: { LoginOverlay },
|
||||
@@ -71,7 +75,7 @@ export default {
|
||||
if (!vditorInstance.value || isDisabled.value) return
|
||||
const value = vditorInstance.value.getValue()
|
||||
console.debug('CommentEditor submit', value)
|
||||
emit('submit', value, () => {
|
||||
emit('submit', props.parentUserName, value, () => {
|
||||
if (!vditorInstance.value) return
|
||||
vditorInstance.value.setValue('')
|
||||
text.value = ''
|
||||
|
||||
@@ -42,9 +42,9 @@
|
||||
</div>
|
||||
</ReactionsGroup>
|
||||
</div>
|
||||
<div class="comment-editor-wrapper">
|
||||
<div class="comment-editor-wrapper" ref="editorWrapper">
|
||||
<CommentEditor v-if="showEditor" @submit="submitReply" :loading="isWaitingForReply" :disabled="!loggedIn"
|
||||
:show-login-overlay="!loggedIn" />
|
||||
:show-login-overlay="!loggedIn" :parent-user-name="comment.userName" />
|
||||
</div>
|
||||
<div v-if="replyCount && level < 2" class="reply-toggle" @click="toggleReplies">
|
||||
<i v-if="showReplies" class="fas fa-chevron-up reply-toggle-icon"></i>
|
||||
@@ -65,7 +65,7 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { ref, watch, computed } from 'vue'
|
||||
import { ref, watch, computed, nextTick } from 'vue'
|
||||
import VueEasyLightbox from 'vue-easy-lightbox'
|
||||
import { useRouter } from 'vue-router'
|
||||
import CommentEditor from './CommentEditor.vue'
|
||||
@@ -106,6 +106,7 @@ const CommentItem = {
|
||||
}
|
||||
)
|
||||
const showEditor = ref(false)
|
||||
const editorWrapper = ref(null)
|
||||
const isWaitingForReply = ref(false)
|
||||
const lightboxVisible = ref(false)
|
||||
const lightboxIndex = ref(0)
|
||||
@@ -118,6 +119,11 @@ const CommentItem = {
|
||||
}
|
||||
const toggleEditor = () => {
|
||||
showEditor.value = !showEditor.value
|
||||
if (showEditor.value) {
|
||||
setTimeout(() => {
|
||||
editorWrapper.value?.scrollIntoView({ behavior: 'smooth', block: 'nearest' })
|
||||
}, 100)
|
||||
}
|
||||
}
|
||||
|
||||
// 合并所有子回复为一个扁平数组
|
||||
@@ -164,7 +170,7 @@ const CommentItem = {
|
||||
toast.error('操作失败')
|
||||
}
|
||||
}
|
||||
const submitReply = async (text, clear) => {
|
||||
const submitReply = async (parentUserName, text, clear) => {
|
||||
if (!text.trim()) return
|
||||
isWaitingForReply.value = true
|
||||
const token = getToken()
|
||||
@@ -190,7 +196,9 @@ const CommentItem = {
|
||||
userName: data.author.username,
|
||||
time: TimeManager.format(data.createdAt),
|
||||
avatar: data.author.avatar,
|
||||
medal: data.author.displayMedal,
|
||||
text: data.content,
|
||||
parentUserName: parentUserName,
|
||||
reactions: [],
|
||||
reply: (data.replies || []).map(r => ({
|
||||
id: r.id,
|
||||
@@ -239,7 +247,7 @@ const CommentItem = {
|
||||
lightboxVisible.value = true
|
||||
}
|
||||
}
|
||||
return { showReplies, toggleReplies, showEditor, toggleEditor, submitReply, copyCommentLink, renderMarkdown, isWaitingForReply, commentMenuItems, deleteComment, lightboxVisible, lightboxIndex, lightboxImgs, handleContentClick, loggedIn, replyCount, replyList, getMedalTitle }
|
||||
return { showReplies, toggleReplies, showEditor, toggleEditor, submitReply, copyCommentLink, renderMarkdown, isWaitingForReply, commentMenuItems, deleteComment, lightboxVisible, lightboxIndex, lightboxImgs, handleContentClick, loggedIn, replyCount, replyList, getMedalTitle, editorWrapper }
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -153,11 +153,6 @@ export default {
|
||||
isLoadingTag.value = false
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
// fetchCategoryData()
|
||||
// fetchTagData()
|
||||
})
|
||||
|
||||
const iconClass = computed(() => {
|
||||
switch (themeState.mode) {
|
||||
case ThemeMode.DARK:
|
||||
|
||||
@@ -82,7 +82,6 @@ export default {
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ username: this.username, password: this.password })
|
||||
})
|
||||
this.isWaitingForLogin = false
|
||||
const data = await res.json()
|
||||
if (res.ok && data.token) {
|
||||
setToken(data.token)
|
||||
@@ -103,6 +102,8 @@ export default {
|
||||
}
|
||||
} catch (e) {
|
||||
toast.error('登录失败')
|
||||
} finally {
|
||||
this.isWaitingForLogin = false
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
@@ -21,11 +21,15 @@
|
||||
</div>
|
||||
<div v-if="loggedIn && !isAuthor && !subscribed" class="article-subscribe-button" @click="subscribePost">
|
||||
<i class="fas fa-user-plus"></i>
|
||||
<div class="article-subscribe-button-text">订阅文章</div>
|
||||
<div class="article-subscribe-button-text">
|
||||
{{ isMobile ? '订阅' : '订阅文章' }}
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="loggedIn && !isAuthor && subscribed" class="article-unsubscribe-button" @click="unsubscribePost">
|
||||
<i class="fas fa-user-minus"></i>
|
||||
<div class="article-unsubscribe-button-text">取消订阅</div>
|
||||
<div class="article-unsubscribe-button-text">
|
||||
{{ isMobile ? '退订' : '取消订阅' }}
|
||||
</div>
|
||||
</div>
|
||||
<DropdownMenu v-if="articleMenuItems.length > 0" :items="articleMenuItems">
|
||||
<template #trigger>
|
||||
@@ -44,11 +48,8 @@
|
||||
<div class="user-name">
|
||||
{{ author.username }}
|
||||
<i class="fas fa-medal medal-icon"></i>
|
||||
<router-link
|
||||
v-if="author.displayMedal"
|
||||
class="user-medal"
|
||||
:to="`/users/${author.id}?tab=achievements`"
|
||||
>{{ getMedalTitle(author.displayMedal) }}</router-link>
|
||||
<router-link v-if="author.displayMedal" class="user-medal" :to="`/users/${author.id}?tab=achievements`">{{
|
||||
getMedalTitle(author.displayMedal) }}</router-link>
|
||||
</div>
|
||||
<div class="post-time">{{ postTime }}</div>
|
||||
</div>
|
||||
@@ -59,11 +60,8 @@
|
||||
<div class="user-name">
|
||||
{{ author.username }}
|
||||
<i class="fas fa-medal medal-icon"></i>
|
||||
<router-link
|
||||
v-if="author.displayMedal"
|
||||
class="user-medal"
|
||||
:to="`/users/${author.id}?tab=achievements`"
|
||||
>{{ getMedalTitle(author.displayMedal) }}</router-link>
|
||||
<router-link v-if="author.displayMedal" class="user-medal" :to="`/users/${author.id}?tab=achievements`">{{
|
||||
getMedalTitle(author.displayMedal) }}</router-link>
|
||||
</div>
|
||||
<div class="post-time">{{ postTime }}</div>
|
||||
</div>
|
||||
@@ -93,7 +91,7 @@
|
||||
<l-hatch size="28" stroke="4" speed="3.5" color="var(--primary-color)"></l-hatch>
|
||||
</div>
|
||||
<div v-else class="comments-container">
|
||||
<BaseTimeline :items="comments">
|
||||
<BaseTimeline :items="comments">
|
||||
<template #item="{ item }">
|
||||
<CommentItem :key="item.id" :comment="item" :level="0" :default-show-replies="item.openReplies"
|
||||
@deleted="onCommentDeleted" />
|
||||
@@ -197,7 +195,7 @@ export default {
|
||||
window.removeEventListener('scroll', updateCurrentIndex)
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
const lightboxVisible = ref(false)
|
||||
const lightboxIndex = ref(0)
|
||||
const lightboxImgs = ref([])
|
||||
@@ -334,7 +332,6 @@ export default {
|
||||
category.value = data.category
|
||||
tags.value = data.tags || []
|
||||
postReactions.value = data.reactions || []
|
||||
await fetchComments()
|
||||
subscribed.value = !!data.subscribed
|
||||
status.value = data.status
|
||||
pinnedAt.value = data.pinnedAt
|
||||
@@ -498,7 +495,7 @@ export default {
|
||||
toast.error('操作失败')
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
const editPost = () => {
|
||||
router.push(`/posts/${postId}/edit`)
|
||||
}
|
||||
@@ -593,7 +590,6 @@ export default {
|
||||
const hash = location.hash
|
||||
if (hash.startsWith('#comment-')) {
|
||||
const id = hash.substring('#comment-'.length)
|
||||
// 不清楚啥原因,先wait一下子不然会定不准 😅
|
||||
await new Promise(resolve => setTimeout(resolve, 500))
|
||||
const el = document.getElementById('comment-' + id)
|
||||
if (el) {
|
||||
@@ -609,17 +605,18 @@ export default {
|
||||
router.push(`/users/${author.value.id}`)
|
||||
}
|
||||
|
||||
await fetchPost()
|
||||
|
||||
onMounted(async () => {
|
||||
await fetchComments()
|
||||
const hash = location.hash
|
||||
const id = hash.startsWith('#comment-') ? hash.substring('#comment-'.length) : null
|
||||
if (id) expandCommentPath(id)
|
||||
updateCurrentIndex()
|
||||
window.addEventListener('scroll', updateCurrentIndex)
|
||||
await jumpToHashComment()
|
||||
jumpToHashComment()
|
||||
})
|
||||
|
||||
await fetchPost()
|
||||
|
||||
return {
|
||||
postContent,
|
||||
author,
|
||||
|
||||
@@ -187,7 +187,6 @@ export default {
|
||||
username: this.username
|
||||
})
|
||||
})
|
||||
this.isWaitingForEmailVerified = false
|
||||
const data = await res.json()
|
||||
if (res.ok) {
|
||||
if (this.registerMode === 'WHITELIST') {
|
||||
@@ -201,6 +200,8 @@ export default {
|
||||
}
|
||||
} catch (e) {
|
||||
toast.error('注册失败')
|
||||
} finally {
|
||||
this.isWaitingForEmailVerified = false
|
||||
}
|
||||
},
|
||||
signupWithGithub() {
|
||||
|
||||
21
frontend_nuxt/plugins/auth-fetch.client.ts
Normal file
21
frontend_nuxt/plugins/auth-fetch.client.ts
Normal file
@@ -0,0 +1,21 @@
|
||||
import { clearToken } from '~/utils/auth'
|
||||
|
||||
export default defineNuxtPlugin(() => {
|
||||
if (process.client) {
|
||||
const originalFetch = window.fetch
|
||||
window.fetch = async (input, init) => {
|
||||
const response = await originalFetch(input, init)
|
||||
if (response.status === 401) {
|
||||
try {
|
||||
const data = await response.clone().json()
|
||||
if (data && data.error === 'Invalid or expired token') {
|
||||
clearToken()
|
||||
}
|
||||
} catch (e) {
|
||||
// ignore JSON parsing errors
|
||||
}
|
||||
}
|
||||
return response
|
||||
}
|
||||
}
|
||||
})
|
||||
Reference in New Issue
Block a user