Merge branch 'main' of github.com:nagisa77/OpenIsle

This commit is contained in:
tim
2025-08-05 23:26:45 +08:00
22 changed files with 101 additions and 101 deletions

View File

@@ -77,8 +77,8 @@ public class SecurityConfig {
"http://127.0.0.1", "http://127.0.0.1",
"http://localhost:8080", "http://localhost:8080",
"http://localhost", "http://localhost",
"http://30.211.97.254:8080", "http://30.211.98.193:8080",
"http://30.211.97.254", "http://30.211.98.193",
"http://192.168.7.70", "http://192.168.7.70",
"http://192.168.7.70:8080", "http://192.168.7.70:8080",
websiteUrl, websiteUrl,

View File

@@ -2,7 +2,7 @@
"name": "OpenIsle", "name": "OpenIsle",
"short_name": "OpenIsle", "short_name": "OpenIsle",
"start_url": "/", "start_url": "/",
"display": "standalone", "display": "fullscreen",
"icons": [ "icons": [
{ {
"src": "/icon-192.png", "src": "/icon-192.png",

View File

@@ -68,9 +68,13 @@ export default {
.menu-container {} .menu-container {}
.content { .content {
/* height: calc(100vh - var(--header-height)); */
padding-top: var(--header-height);
flex: 1; flex: 1;
max-width: 100%; max-width: 100%;
transition: max-width 0.3s ease; transition: max-width 0.3s ease;
background-color: var(--background-color);
min-height: calc(100vh - var(--header-height));
} }
.content.menu-open { .content.menu-open {

View File

@@ -8,7 +8,8 @@
--header-text-color: black; --header-text-color: black;
--menu-background-color: white; --menu-background-color: white;
--background-color: white; --background-color: white;
--background-color-blur: rgba(255, 255, 255, 0.57); /* --background-color-blur: rgba(255, 255, 255, 0.57); */
--background-color-blur: var(--background-color);
--menu-border-color: lightgray; --menu-border-color: lightgray;
--normal-border-color: lightgray; --normal-border-color: lightgray;
--menu-selected-background-color: rgba(208, 250, 255, 0.659); --menu-selected-background-color: rgba(208, 250, 255, 0.659);
@@ -34,7 +35,8 @@
--header-text-color: white; --header-text-color: white;
--menu-background-color: #333; --menu-background-color: #333;
--background-color: #333; --background-color: #333;
--background-color-blur: #333333a4; /* --background-color-blur: #333333a4; */
--background-color-blur: var(--background-color);
--menu-border-color: #555; --menu-border-color: #555;
--normal-border-color: #555; --normal-border-color: #555;
--menu-selected-background-color: rgba(255, 255, 255, 0.1); --menu-selected-background-color: rgba(255, 255, 255, 0.1);
@@ -54,10 +56,10 @@ body {
font-family: 'Roboto', sans-serif; font-family: 'Roboto', sans-serif;
background-color: var(--normal-background-color); background-color: var(--normal-background-color);
color: var(--text-color); color: var(--text-color);
overflow: hidden; /* 禁止滚动 */ /* 禁止滚动 */
/* overflow: hidden; */
} }
/************************* /*************************
* Vditor 自定义皮肤覆写 * Vditor 自定义皮肤覆写
*************************/ *************************/

View File

@@ -18,8 +18,6 @@ export default {
<style scoped> <style scoped>
.callback-page { .callback-page {
background-color: var(--background-color); background-color: var(--background-color);
height: calc(100vh - var(--header-height));
padding-top: var(--header-height);
display: flex; display: flex;
flex-direction: column; flex-direction: column;
align-items: center; align-items: center;

View File

@@ -268,6 +268,8 @@ export default {
<style scoped> <style scoped>
.menu { .menu {
position: sticky;
top: var(--header-height);
width: 200px; width: 200px;
background-color: var(--menu-background-color); background-color: var(--menu-background-color);
height: calc(100vh - 20px - var(--header-height)); height: calc(100vh - 20px - var(--header-height));
@@ -277,7 +279,6 @@ export default {
padding: 10px; padding: 10px;
overflow-y: auto; overflow-y: auto;
scrollbar-width: none; scrollbar-width: none;
padding-top: calc(var(--header-height) + 10px);
} }
.menu-item-container { .menu-item-container {
@@ -418,7 +419,6 @@ export default {
top: calc(var(--header-height) + 10px); top: calc(var(--header-height) + 10px);
padding-top: 10px; padding-top: 10px;
background-color: var(--background-color-blur); background-color: var(--background-color-blur);
backdrop-filter: blur(10px);
} }
.slide-enter-active, .slide-enter-active,

View File

@@ -0,0 +1,31 @@
import { ref, onMounted, onUnmounted, onActivated, nextTick } from 'vue'
export function useScrollLoadMore(loadMore, offset = 50) {
const savedScrollTop = ref(0)
const handleScroll = () => {
const scrollTop = window.scrollY || document.documentElement.scrollTop
const scrollHeight = document.documentElement.scrollHeight
const windowHeight = window.innerHeight
savedScrollTop.value = scrollTop
if (scrollHeight - (scrollTop + windowHeight) <= offset) {
loadMore()
}
}
onMounted(() => {
window.addEventListener('scroll', handleScroll, { passive: true })
})
onUnmounted(() => {
window.removeEventListener('scroll', handleScroll)
})
onActivated(() => {
nextTick(() => {
window.scrollTo({ top: savedScrollTop.value })
})
})
return { savedScrollTop }
}

View File

@@ -75,8 +75,7 @@ export default {
max-width: var(--page-max-width); max-width: var(--page-max-width);
background-color: var(--background-color); background-color: var(--background-color);
margin: 0 auto; margin: 0 auto;
height: calc(100vh - var(--header-height)); height: 100%;
padding-top: var(--header-height);
overflow-y: auto; overflow-y: auto;
} }
@@ -85,7 +84,6 @@ export default {
top: 1px; top: 1px;
z-index: 200; z-index: 200;
background-color: var(--background-color-blur); background-color: var(--background-color-blur);
backdrop-filter: blur(10px);
display: flex; display: flex;
flex-direction: row; flex-direction: row;
border-bottom: 1px solid var(--normal-border-color); border-bottom: 1px solid var(--normal-border-color);

View File

@@ -73,9 +73,8 @@ export default {
.activity-list-page { .activity-list-page {
background-color: var(--background-color); background-color: var(--background-color);
padding: 20px; padding: 20px;
height: calc(100vh - var(--header-height) - 40px); height: calc(100% - 40px);
overflow-y: auto; overflow-y: auto;
padding-top: calc(var(--header-height) + 20px);
} }
.activity-list-page-card { .activity-list-page-card {

View File

@@ -211,8 +211,7 @@ export default {
display: flex; display: flex;
justify-content: center; justify-content: center;
background-color: var(--background-color); background-color: var(--background-color);
height: calc(100vh - var(--header-height)); height: 100%;
padding-top: var(--header-height);
padding-right: 20px; padding-right: 20px;
padding-left: 20px; padding-left: 20px;
overflow-y: auto; overflow-y: auto;

View File

@@ -131,8 +131,7 @@ export default {
justify-content: center; justify-content: center;
align-items: center; align-items: center;
background-color: var(--background-color); background-color: var(--background-color);
height: calc(100vh - var(--header-height)); height: 100%;
padding-top: var(--header-height);
} }
.forgot-content { .forgot-content {
display: flex; display: flex;

View File

@@ -1,5 +1,5 @@
<template> <template>
<div ref="homePage" class="home-page" @scroll="handleScroll"> <div class="home-page">
<div v-if="!isMobile" class="search-container"> <div v-if="!isMobile" class="search-container">
<div class="search-title">一切可能从此刻启航</div> <div class="search-title">一切可能从此刻启航</div>
<div class="search-subtitle">愿你在此遇见灵感与共鸣若有疑惑欢迎发问亦可在知识的海洋中搜寻答案</div> <div class="search-subtitle">愿你在此遇见灵感与共鸣若有疑惑欢迎发问亦可在知识的海洋中搜寻答案</div>
@@ -107,8 +107,9 @@
</template> </template>
<script> <script>
import { ref, onMounted, watch, onActivated, nextTick } from 'vue' import { ref, onMounted, watch } from 'vue'
import { useRoute } from 'vue-router' import { useRoute } from 'vue-router'
import { useScrollLoadMore } from '../utils/loadMore'
import { stripMarkdown } from '../utils/markdown' import { stripMarkdown } from '../utils/markdown'
import { API_BASE_URL } from '../main' import { API_BASE_URL } from '../main'
import { getToken } from '../utils/auth' import { getToken } from '../utils/auth'
@@ -134,8 +135,6 @@ export default {
}, },
setup() { setup() {
const route = useRoute() const route = useRoute()
const homePage = ref(null)
const savedScrollTop = ref(0)
const selectedCategory = ref('') const selectedCategory = ref('')
if (route.query.category) { if (route.query.category) {
const c = decodeURIComponent(route.query.category) const c = decodeURIComponent(route.query.category)
@@ -167,14 +166,6 @@ export default {
const pageSize = 10 const pageSize = 10
const allLoaded = ref(false) const allLoaded = ref(false)
onActivated(() => {
nextTick(() => {
if (homePage.value) {
homePage.value.scrollTop = savedScrollTop.value
}
})
})
// Backend now returns comment counts directly // Backend now returns comment counts directly
const loadOptions = async () => { const loadOptions = async () => {
@@ -366,13 +357,7 @@ export default {
} }
} }
const handleScroll = (e) => { useScrollLoadMore(fetchContent)
const el = e.target
savedScrollTop.value = el.scrollTop
if (el.scrollHeight - el.scrollTop <= el.clientHeight + 50) {
fetchContent()
}
}
onMounted(async () => { onMounted(async () => {
fetchContent() fetchContent()
@@ -389,7 +374,7 @@ export default {
const sanitizeDescription = (text) => stripMarkdown(text) const sanitizeDescription = (text) => stripMarkdown(text)
return { topics, selectedTopic, articles, sanitizeDescription, isLoadingPosts, handleScroll, selectedCategory, selectedTags, tagOptions, categoryOptions, isMobile, homePage } return { topics, selectedTopic, articles, sanitizeDescription, isLoadingPosts, selectedCategory, selectedTags, tagOptions, categoryOptions, isMobile }
} }
} }
</script> </script>
@@ -397,13 +382,9 @@ export default {
<style scoped> <style scoped>
.home-page { .home-page {
background-color: var(--background-color); background-color: var(--background-color);
height: calc(100vh - var(--header-height));
padding-top: var(--header-height);
display: flex; display: flex;
flex-direction: column; flex-direction: column;
align-items: center; align-items: center;
overflow-y: auto;
/* enable container queries */
container-type: inline-size; container-type: inline-size;
container-name: home-page; container-name: home-page;
} }
@@ -455,7 +436,6 @@ export default {
top: 1px; top: 1px;
z-index: 10; z-index: 10;
background-color: var(--background-color-blur); background-color: var(--background-color-blur);
backdrop-filter: blur(10px);
display: flex; display: flex;
flex-direction: row; flex-direction: row;
align-items: center; align-items: center;
@@ -736,6 +716,10 @@ export default {
font-size: 10px; font-size: 10px;
opacity: 0.5; opacity: 0.5;
} }
.topic-container {
position: initial;
}
} }
</style> </style>

View File

@@ -125,8 +125,7 @@ export default {
flex-direction: row; flex-direction: row;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
height: calc(100vh - var(--header-height)); height: 100%;
padding-top: var(--header-height);
width: 100%; width: 100%;
background-color: var(--background-color); background-color: var(--background-color);
} }

View File

@@ -626,12 +626,8 @@ export default {
height: 300px; height: 300px;
} }
.message-page { .message-page {
background-color: var(--background-color); background-color: var(--background-color);
height: calc(100vh - var(--header-height));
padding-top: var(--header-height);
overflow-y: auto;
} }
.message-page-header { .message-page-header {
@@ -639,7 +635,6 @@ export default {
top: 1px; top: 1px;
z-index: 200; z-index: 200;
background-color: var(--background-color-blur); background-color: var(--background-color-blur);
backdrop-filter: blur(10px);
display: flex; display: flex;
flex-direction: row; flex-direction: row;
justify-content: space-between; justify-content: space-between;

View File

@@ -261,8 +261,7 @@ export default {
display: flex; display: flex;
justify-content: center; justify-content: center;
background-color: var(--background-color); background-color: var(--background-color);
height: calc(100vh - var(--header-height)); height: 100%;
padding-top: var(--header-height);
padding-right: 20px; padding-right: 20px;
padding-left: 20px; padding-left: 20px;
overflow-y: auto; overflow-y: auto;

View File

@@ -17,8 +17,7 @@ export default {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
align-items: center; align-items: center;
height: calc(100vh - var(--header-height)); height: 100%;
padding-top: var(--header-height);
text-align: center; text-align: center;
background-color: var(--background-color); background-color: var(--background-color);
} }

View File

@@ -3,7 +3,7 @@
<div v-if="isWaitingFetchingPost" class="loading-container"> <div v-if="isWaitingFetchingPost" class="loading-container">
<l-hatch size="28" stroke="4" speed="3.5" color="var(--primary-color)"></l-hatch> <l-hatch size="28" stroke="4" speed="3.5" color="var(--primary-color)"></l-hatch>
</div> </div>
<div v-else class="post-page-main-container" ref="mainContainer" @scroll="onScroll"> <div v-else class="post-page-main-container" ref="mainContainer">
<div class="article-title-container"> <div class="article-title-container">
<div class="article-title-container-left"> <div class="article-title-container-left">
<div class="article-title">{{ title }}</div> <div class="article-title">{{ title }}</div>
@@ -156,6 +156,7 @@ export default {
const defaultTitle = document.title const defaultTitle = document.title
const metaDescriptionEl = document.querySelector('meta[name="description"]') const metaDescriptionEl = document.querySelector('meta[name="description"]')
const defaultDescription = metaDescriptionEl ? metaDescriptionEl.getAttribute('content') : '' const defaultDescription = metaDescriptionEl ? metaDescriptionEl.getAttribute('content') : ''
const headerHeight = parseFloat(getComputedStyle(document.documentElement).getPropertyValue('--header-height')) || 0
watch(title, t => { watch(title, t => {
document.title = `OpenIsle - ${t}` document.title = `OpenIsle - ${t}`
@@ -170,6 +171,7 @@ export default {
onBeforeUnmount(() => { onBeforeUnmount(() => {
document.title = defaultTitle document.title = defaultTitle
if (metaDescriptionEl) metaDescriptionEl.setAttribute('content', defaultDescription) if (metaDescriptionEl) metaDescriptionEl.setAttribute('content', defaultDescription)
window.removeEventListener('scroll', updateCurrentIndex)
}) })
const lightboxVisible = ref(false) const lightboxVisible = ref(false)
@@ -202,12 +204,12 @@ export default {
const items = [] const items = []
if (mainContainer.value) { if (mainContainer.value) {
const main = mainContainer.value.querySelector('.info-content-container') const main = mainContainer.value.querySelector('.info-content-container')
if (main) items.push({ el: main, top: 0 }) if (main) items.push({ el: main, top: getTop(main) })
for (const c of comments.value) { for (const c of comments.value) {
const el = document.getElementById('comment-' + c.id) const el = document.getElementById('comment-' + c.id)
if (el) { if (el) {
items.push({ el, top: getTopRelativeTo(el, mainContainer.value) }) items.push({ el, top: getTop(el) })
} }
} }
// 根据 top 排序,防止评论异步插入后顺序错乱 // 根据 top 排序,防止评论异步插入后顺序错乱
@@ -230,11 +232,8 @@ export default {
parentUserName: parentUserName parentUserName: parentUserName
}) })
const getTopRelativeTo = (el, container) => { const getTop = (el) => {
const elRect = el.getBoundingClientRect() return el.getBoundingClientRect().top + window.scrollY
const parentRect = container.getBoundingClientRect()
// 加上 scrollTop得到相对于 container 内部顶部的距离
return elRect.top - parentRect.top + container.scrollTop
} }
const findCommentPath = (id, list) => { const findCommentPath = (id, list) => {
@@ -336,19 +335,16 @@ export default {
async () => { async () => {
await nextTick() await nextTick()
gatherPostItems() gatherPostItems()
updateCurrentIndex()
} }
) )
const updateCurrentIndex = () => { const updateCurrentIndex = () => {
const container = mainContainer.value const scrollTop = window.scrollY
if (!container) return
const scrollTop = container.scrollTop
for (let i = 0; i < postItems.value.length; i++) { for (let i = 0; i < postItems.value.length; i++) {
const el = postItems.value[i] const el = postItems.value[i]
// 计算元素相对 container 顶部的 top const top = getTop(el)
const top = getTopRelativeTo(el, container)
const bottom = top + el.offsetHeight const bottom = top + el.offsetHeight
if (bottom > scrollTop) { if (bottom > scrollTop) {
@@ -362,9 +358,9 @@ export default {
const index = Number(e.target.value) const index = Number(e.target.value)
currentIndex.value = index currentIndex.value = index
const target = postItems.value[index - 1] const target = postItems.value[index - 1]
if (target && mainContainer.value) { if (target) {
const top = getTopRelativeTo(target, mainContainer.value) const top = getTop(target) - headerHeight - 20 // 20 for beauty
mainContainer.value.scrollTo({ top, behavior: 'instant' }) window.scrollTo({ top, behavior: 'auto' })
} }
} }
@@ -570,12 +566,14 @@ export default {
const hash = location.hash const hash = location.hash
if (hash.startsWith('#comment-')) { if (hash.startsWith('#comment-')) {
const id = hash.substring('#comment-'.length) const id = hash.substring('#comment-'.length)
await nextTick() // 不清楚啥原因先wait一下子不然会定不准 😅
await new Promise(resolve => setTimeout(resolve, 500))
const el = document.getElementById('comment-' + id) const el = document.getElementById('comment-' + id)
if (el && mainContainer.value) { if (el) {
mainContainer.value.scrollTo({ top: getTopRelativeTo(el, mainContainer.value), behavior: 'instant' }) const top = el.getBoundingClientRect().top + window.scrollY - headerHeight - 20 // 20 for beauty
window.scrollTo({ top, behavior: 'smooth' })
el.classList.add('comment-highlight') el.classList.add('comment-highlight')
setTimeout(() => el.classList.remove('comment-highlight'), 2000) setTimeout(() => el.classList.remove('comment-highlight'), 4000)
} }
} }
} }
@@ -590,6 +588,7 @@ export default {
await fetchPost() await fetchPost()
if (id) expandCommentPath(id) if (id) expandCommentPath(id)
updateCurrentIndex() updateCurrentIndex()
window.addEventListener('scroll', updateCurrentIndex)
await jumpToHashComment() await jumpToHashComment()
}) })
@@ -612,7 +611,6 @@ export default {
postId, postId,
postComment, postComment,
onSliderInput, onSliderInput,
onScroll: updateCurrentIndex,
copyPostLink, copyPostLink,
subscribePost, subscribePost,
unsubscribePost, unsubscribePost,
@@ -650,7 +648,7 @@ export default {
background-color: var(--background-color); background-color: var(--background-color);
display: flex; display: flex;
flex-direction: row; flex-direction: row;
height: 100vh; height: 100%;
} }
.loading-container { .loading-container {
@@ -662,19 +660,18 @@ export default {
} }
.post-page-main-container { .post-page-main-container {
overflow-y: auto;
scrollbar-width: none; scrollbar-width: none;
padding: 20px; padding: 20px;
height: calc(100% - 40px - var(--header-height));
width: calc(85% - 40px); width: calc(85% - 40px);
padding-top: calc(var(--header-height) + 20px);
} }
.post-page-scroller-container { .post-page-scroller-container {
padding-top: var(--header-height);
display: flex; display: flex;
flex-direction: column; flex-direction: column;
width: 15%; width: 15%;
position: sticky;
top: var(--header-height);
align-self: flex-start;
} }
.comment-config-container { .comment-config-container {
@@ -974,7 +971,6 @@ export default {
.post-page-main-container { .post-page-main-container {
width: calc(100% - 20px); width: calc(100% - 20px);
padding: 10px; padding: 10px;
padding-top: calc(var(--header-height) + 10px);
} }
.article-title { .article-title {

View File

@@ -282,7 +282,7 @@ export default {
const isLoading = ref(true) const isLoading = ref(true)
const tabLoading = ref(false) const tabLoading = ref(false)
const selectedTab = ref('summary') const selectedTab = ref('summary')
const followTab = ref('followers') const followTab = ref('followers')
const levelInfo = computed(() => { const levelInfo = computed(() => {
const exp = user.value.experience || 0 const exp = user.value.experience || 0
@@ -336,9 +336,11 @@ export default {
} }
const fetchTimeline = async () => { const fetchTimeline = async () => {
const postsRes = await fetch(`${API_BASE_URL}/api/users/${username}/posts?limit=50`) const [postsRes, repliesRes, tagsRes] = await Promise.all([
const repliesRes = await fetch(`${API_BASE_URL}/api/users/${username}/replies?limit=50`) fetch(`${API_BASE_URL}/api/users/${username}/posts?limit=50`),
const tagsRes = await fetch(`${API_BASE_URL}/api/users/${username}/tags?limit=50`) fetch(`${API_BASE_URL}/api/users/${username}/replies?limit=50`),
fetch(`${API_BASE_URL}/api/users/${username}/tags?limit=50`)
])
const posts = postsRes.ok ? await postsRes.json() : [] const posts = postsRes.ok ? await postsRes.json() : []
const replies = repliesRes.ok ? await repliesRes.json() : [] const replies = repliesRes.ok ? await repliesRes.json() : []
const tags = tagsRes.ok ? await tagsRes.json() : [] const tags = tagsRes.ok ? await tagsRes.json() : []
@@ -367,8 +369,10 @@ export default {
} }
const fetchFollowUsers = async () => { const fetchFollowUsers = async () => {
const followerRes = await fetch(`${API_BASE_URL}/api/users/${username}/followers`) const [followerRes, followingRes] = await Promise.all([
const followingRes = await fetch(`${API_BASE_URL}/api/users/${username}/following`) fetch(`${API_BASE_URL}/api/users/${username}/followers`),
fetch(`${API_BASE_URL}/api/users/${username}/following`)
])
followers.value = followerRes.ok ? await followerRes.json() : [] followers.value = followerRes.ok ? await followerRes.json() : []
followings.value = followingRes.ok ? await followingRes.json() : [] followings.value = followingRes.ok ? await followingRes.json() : []
} }
@@ -493,8 +497,7 @@ export default {
.profile-page { .profile-page {
background-color: var(--background-color); background-color: var(--background-color);
height: calc(100vh - var(--header-height)); height: 100%;
padding-top: var(--header-height);
overflow-y: auto; overflow-y: auto;
overflow-x: hidden; overflow-x: hidden;
} }
@@ -639,7 +642,6 @@ export default {
top: 1px; top: 1px;
z-index: 200; z-index: 200;
background-color: var(--background-color-blur); background-color: var(--background-color-blur);
backdrop-filter: blur(10px);
display: flex; display: flex;
flex-direction: row; flex-direction: row;
padding: 0 20px; padding: 0 20px;

View File

@@ -239,8 +239,7 @@ export default {
.settings-page { .settings-page {
background-color: var(--background-color); background-color: var(--background-color);
padding: 40px; padding: 40px;
height: calc(100vh - var(--header-height) - 80px); height: calc(100% - 80px);
padding-top: calc(var(--header-height) + 40px);
overflow-y: auto; overflow-y: auto;
} }

View File

@@ -222,8 +222,7 @@ export default {
flex-direction: row; flex-direction: row;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
height: calc(100vh - var(--header-height)); height: 100%;
padding-top: var(--header-height);
width: 100%; width: 100%;
background-color: var(--background-color); background-color: var(--background-color);
} }

View File

@@ -82,8 +82,7 @@ export default {
justify-content: center; justify-content: center;
align-items: center; align-items: center;
background-color: var(--background-color); background-color: var(--background-color);
height: calc(100vh - var(--header-height)); height: 100%;
padding-top: var(--header-height);
} }
.reason-title { .reason-title {

View File

@@ -48,7 +48,6 @@ onMounted(loadData)
max-width: var(--page-max-width); max-width: var(--page-max-width);
background-color: var(--background-color); background-color: var(--background-color);
margin: 0 auto; margin: 0 auto;
height: calc(100vh - var(--header-height) - 40px); height: 100%;
padding-top: calc(var(--header-height) + 20px);
} }
</style> </style>