mirror of
https://github.com/nagisa77/OpenIsle.git
synced 2026-08-10 21:31:00 +00:00
Revert "Enhance user timeline grouping and post metadata"
This reverts commit b6c2471bc3.
This commit is contained in:
@@ -1,7 +1,6 @@
|
|||||||
package com.openisle.dto;
|
package com.openisle.dto;
|
||||||
|
|
||||||
import java.time.LocalDateTime;
|
import java.time.LocalDateTime;
|
||||||
import java.util.List;
|
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
|
|
||||||
/** Lightweight post metadata used in user profile lists. */
|
/** Lightweight post metadata used in user profile lists. */
|
||||||
@@ -12,8 +11,6 @@ public class PostMetaDto {
|
|||||||
private String title;
|
private String title;
|
||||||
private String snippet;
|
private String snippet;
|
||||||
private LocalDateTime createdAt;
|
private LocalDateTime createdAt;
|
||||||
private CategoryDto category;
|
private String category;
|
||||||
private List<TagDto> tags;
|
|
||||||
private long views;
|
private long views;
|
||||||
private long commentCount;
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ import com.openisle.model.Comment;
|
|||||||
import com.openisle.model.Post;
|
import com.openisle.model.Post;
|
||||||
import com.openisle.model.User;
|
import com.openisle.model.User;
|
||||||
import com.openisle.service.*;
|
import com.openisle.service.*;
|
||||||
import java.util.stream.Collectors;
|
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
import org.springframework.beans.factory.annotation.Value;
|
import org.springframework.beans.factory.annotation.Value;
|
||||||
import org.springframework.security.core.Authentication;
|
import org.springframework.security.core.Authentication;
|
||||||
@@ -24,8 +23,6 @@ public class UserMapper {
|
|||||||
private final PostReadService postReadService;
|
private final PostReadService postReadService;
|
||||||
private final LevelService levelService;
|
private final LevelService levelService;
|
||||||
private final MedalService medalService;
|
private final MedalService medalService;
|
||||||
private final TagMapper tagMapper;
|
|
||||||
private final CategoryMapper categoryMapper;
|
|
||||||
|
|
||||||
@Value("${app.snippet-length}")
|
@Value("${app.snippet-length}")
|
||||||
private int snippetLength;
|
private int snippetLength;
|
||||||
@@ -91,12 +88,7 @@ public class UserMapper {
|
|||||||
dto.setSnippet(content);
|
dto.setSnippet(content);
|
||||||
}
|
}
|
||||||
dto.setCreatedAt(post.getCreatedAt());
|
dto.setCreatedAt(post.getCreatedAt());
|
||||||
dto.setCategory(categoryMapper.toDto(post.getCategory()));
|
dto.setCategory(post.getCategory().getName());
|
||||||
dto.setTags(post.getTags().stream().map(tagMapper::toDto).collect(Collectors.toList()));
|
|
||||||
if (post.getLastReplyAt() == null) {
|
|
||||||
commentService.updatePostCommentStats(post);
|
|
||||||
}
|
|
||||||
dto.setCommentCount(post.getCommentCount());
|
|
||||||
dto.setViews(post.getViews());
|
dto.setViews(post.getViews());
|
||||||
return dto;
|
return dto;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,168 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div class="timeline-container">
|
|
||||||
<div class="timeline-header">
|
|
||||||
<div class="timeline-title">{{ headerText }}</div>
|
|
||||||
<div class="timeline-date">{{ headerDate }}</div>
|
|
||||||
</div>
|
|
||||||
<div class="comment-content">
|
|
||||||
<div v-for="entry in entries" :key="entry.comment.id" class="comment-content-item">
|
|
||||||
<div class="comment-content-item-main">
|
|
||||||
<comment-one class="comment-content-item-icon" />
|
|
||||||
<template v-if="!entry.comment.parentComment">
|
|
||||||
<span class="comment-prefix">
|
|
||||||
在
|
|
||||||
<NuxtLink :to="entry.postLink" class="timeline-link">
|
|
||||||
{{ entry.comment.post.title }}
|
|
||||||
</NuxtLink>
|
|
||||||
下评论了
|
|
||||||
</span>
|
|
||||||
<NuxtLink :to="entry.commentLink" class="timeline-comment-link">
|
|
||||||
{{ stripContent(entry.comment.content) }}
|
|
||||||
</NuxtLink>
|
|
||||||
</template>
|
|
||||||
<template v-else>
|
|
||||||
<span class="comment-prefix">
|
|
||||||
在
|
|
||||||
<NuxtLink :to="entry.postLink" class="timeline-link">
|
|
||||||
{{ entry.comment.post.title }}
|
|
||||||
</NuxtLink>
|
|
||||||
下对
|
|
||||||
<NuxtLink :to="entry.parentLink" class="timeline-link">
|
|
||||||
{{ stripContent(entry.comment.parentComment.content) }}
|
|
||||||
</NuxtLink>
|
|
||||||
回复了
|
|
||||||
</span>
|
|
||||||
<NuxtLink :to="entry.commentLink" class="timeline-comment-link">
|
|
||||||
{{ stripContent(entry.comment.content) }}
|
|
||||||
</NuxtLink>
|
|
||||||
</template>
|
|
||||||
</div>
|
|
||||||
<div class="timeline-date">{{ formatDate(entry.createdAt) }}</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script setup>
|
|
||||||
import { computed } from 'vue'
|
|
||||||
import { stripMarkdownLength } from '~/utils/markdown'
|
|
||||||
import TimeManager from '~/utils/time'
|
|
||||||
|
|
||||||
const props = defineProps({
|
|
||||||
item: {
|
|
||||||
type: Object,
|
|
||||||
required: true,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
const entries = computed(() =>
|
|
||||||
(props.item.entries || []).map((entry) => ({
|
|
||||||
...entry,
|
|
||||||
postLink: `/posts/${entry.comment.post.id}`,
|
|
||||||
commentLink: `/posts/${entry.comment.post.id}#comment-${entry.comment.id}`,
|
|
||||||
parentLink: entry.comment.parentComment
|
|
||||||
? `/posts/${entry.comment.post.id}#comment-${entry.comment.parentComment.id}`
|
|
||||||
: undefined,
|
|
||||||
})),
|
|
||||||
)
|
|
||||||
|
|
||||||
const commentCount = computed(
|
|
||||||
() => entries.value.filter((entry) => !entry.comment.parentComment).length,
|
|
||||||
)
|
|
||||||
|
|
||||||
const replyCount = computed(
|
|
||||||
() => entries.value.filter((entry) => entry.comment.parentComment).length,
|
|
||||||
)
|
|
||||||
|
|
||||||
const headerText = computed(() => {
|
|
||||||
if (commentCount.value && replyCount.value) {
|
|
||||||
return `发布了${commentCount.value}条评论和${replyCount.value}条回复`
|
|
||||||
}
|
|
||||||
if (commentCount.value) {
|
|
||||||
return `发布了${commentCount.value}条评论`
|
|
||||||
}
|
|
||||||
if (replyCount.value) {
|
|
||||||
return `发布了${replyCount.value}条回复`
|
|
||||||
}
|
|
||||||
return '发布了评论'
|
|
||||||
})
|
|
||||||
|
|
||||||
const headerDate = computed(() => TimeManager.format(props.item.createdAt))
|
|
||||||
|
|
||||||
const formatDate = (date) => TimeManager.format(date)
|
|
||||||
|
|
||||||
const stripContent = (content) => stripMarkdownLength(content || '', 200)
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style scoped>
|
|
||||||
.timeline-container {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
gap: 12px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.timeline-header {
|
|
||||||
display: flex;
|
|
||||||
justify-content: space-between;
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.timeline-title {
|
|
||||||
font-weight: 600;
|
|
||||||
color: var(--primary-color);
|
|
||||||
}
|
|
||||||
|
|
||||||
.timeline-date {
|
|
||||||
color: var(--text-color-secondary);
|
|
||||||
font-size: 14px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.comment-content {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
gap: 12px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.comment-content-item {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
gap: 6px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.comment-content-item-main {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
gap: 4px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.comment-content-item-icon {
|
|
||||||
color: var(--primary-color);
|
|
||||||
font-size: 18px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.comment-prefix {
|
|
||||||
color: var(--text-color-secondary);
|
|
||||||
font-size: 14px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.timeline-comment-link {
|
|
||||||
display: inline-flex;
|
|
||||||
gap: 6px;
|
|
||||||
align-items: center;
|
|
||||||
color: var(--text-color);
|
|
||||||
font-size: 15px;
|
|
||||||
line-height: 1.6;
|
|
||||||
}
|
|
||||||
|
|
||||||
.timeline-comment-link:hover {
|
|
||||||
color: var(--primary-color);
|
|
||||||
}
|
|
||||||
|
|
||||||
.timeline-link {
|
|
||||||
color: var(--primary-color);
|
|
||||||
}
|
|
||||||
|
|
||||||
.timeline-link:hover {
|
|
||||||
color: var(--primary-color-hover);
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@@ -1,79 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div class="timeline-container">
|
|
||||||
<div class="timeline-header">
|
|
||||||
<div class="timeline-title">发布了文章</div>
|
|
||||||
<div class="timeline-date">{{ formattedDate }}</div>
|
|
||||||
</div>
|
|
||||||
<div class="article-container">
|
|
||||||
<NuxtLink :to="postLink" class="timeline-article-link">
|
|
||||||
{{ props.item.post.title }}
|
|
||||||
</NuxtLink>
|
|
||||||
<div class="timeline-snippet">
|
|
||||||
{{ postSnippet }}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script setup>
|
|
||||||
import { computed } from 'vue'
|
|
||||||
import { stripMarkdown } from '~/utils/markdown'
|
|
||||||
import TimeManager from '~/utils/time'
|
|
||||||
|
|
||||||
const props = defineProps({
|
|
||||||
item: {
|
|
||||||
type: Object,
|
|
||||||
required: true,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
const formattedDate = computed(() => TimeManager.format(props.item.createdAt))
|
|
||||||
const postLink = computed(() => `/posts/${props.item.post.id}`)
|
|
||||||
const postSnippet = computed(() => stripMarkdown(props.item.post?.snippet ?? ''))
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style scoped>
|
|
||||||
.timeline-container {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
gap: 12px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.timeline-header {
|
|
||||||
display: flex;
|
|
||||||
justify-content: space-between;
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.timeline-title {
|
|
||||||
font-weight: 600;
|
|
||||||
color: var(--primary-color);
|
|
||||||
}
|
|
||||||
|
|
||||||
.timeline-date {
|
|
||||||
color: var(--text-color-secondary);
|
|
||||||
font-size: 14px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.article-container {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
gap: 8px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.timeline-article-link {
|
|
||||||
font-size: 18px;
|
|
||||||
font-weight: 600;
|
|
||||||
color: var(--text-color);
|
|
||||||
}
|
|
||||||
|
|
||||||
.timeline-article-link:hover {
|
|
||||||
color: var(--primary-color);
|
|
||||||
}
|
|
||||||
|
|
||||||
.timeline-snippet {
|
|
||||||
color: var(--text-color-secondary);
|
|
||||||
font-size: 14px;
|
|
||||||
line-height: 1.6;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@@ -212,9 +212,107 @@
|
|||||||
<div class="timeline-list">
|
<div class="timeline-list">
|
||||||
<BaseTimeline :items="filteredTimelineItems">
|
<BaseTimeline :items="filteredTimelineItems">
|
||||||
<template #item="{ item }">
|
<template #item="{ item }">
|
||||||
<ProfileTimelinePostItem v-if="item.type === 'post'" :item="item" />
|
<!-- <template v-if="item.type === 'post'">
|
||||||
<ProfileTimelineCommentGroup v-else-if="item.type === 'comment'" :item="item" />
|
发布了文章
|
||||||
<ProfileTimelineCommentGroup v-else-if="item.type === 'reply'" :item="item" />
|
<NuxtLink :to="`/posts/${item.post.id}`" class="timeline-link">
|
||||||
|
{{ item.post.title }}
|
||||||
|
</NuxtLink>
|
||||||
|
<div class="timeline-date">{{ formatDate(item.createdAt) }}</div>
|
||||||
|
</template> -->
|
||||||
|
<template v-if="item.type === 'post'">
|
||||||
|
<div class="ttimeline-container">
|
||||||
|
<div class="timeline-header">
|
||||||
|
<div class="timeline-title">发布了文章</div>
|
||||||
|
<div class="timeline-date">{{ formatDate(item.createdAt) }}</div>
|
||||||
|
</div>
|
||||||
|
<div class="article-container">
|
||||||
|
<NuxtLink :to="`/posts/${item.post.id}`" class="timeline-article-link">
|
||||||
|
{{ item.post.title }}
|
||||||
|
</NuxtLink>
|
||||||
|
<div class="timeline-snippet">
|
||||||
|
{{ stripMarkdown(item.post.snippet) }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<!-- <template v-else-if="item.type === 'comment'">
|
||||||
|
在
|
||||||
|
<NuxtLink :to="`/posts/${item.comment.post.id}`" class="timeline-link">
|
||||||
|
{{ item.comment.post.title }}
|
||||||
|
</NuxtLink>
|
||||||
|
下评论了
|
||||||
|
<NuxtLink :to="`/posts/${item.comment.post.id}#comment-${item.comment.id}`" class="timeline-link">
|
||||||
|
{{ stripMarkdownLength(item.comment.content, 200) }}
|
||||||
|
</NuxtLink>
|
||||||
|
<div class="timeline-date">{{ formatDate(item.createdAt) }}</div>
|
||||||
|
</template> -->
|
||||||
|
<template v-else-if="item.type === 'comment'">
|
||||||
|
<div class="ttimeline-container">
|
||||||
|
<div class="timeline-header">
|
||||||
|
<div class="timeline-title">发布了4条评论</div>
|
||||||
|
<div class="timeline-date">{{ formatDate(item.createdAt) }}</div>
|
||||||
|
</div>
|
||||||
|
<div class="comment-content">
|
||||||
|
<div class="comment-content-item">
|
||||||
|
<div class="comment-content-item-main">
|
||||||
|
<comment-one class="comment-content-item-icon" />
|
||||||
|
<NuxtLink
|
||||||
|
:to="`/posts/${item.comment.post.id}#comment-${item.comment.id}`"
|
||||||
|
class="timeline-comment-link"
|
||||||
|
>
|
||||||
|
{{ stripMarkdownLength(item.comment.content, 200) }}
|
||||||
|
</NuxtLink>
|
||||||
|
</div>
|
||||||
|
<div class="timeline-date">{{ formatDate(item.createdAt) }}</div>
|
||||||
|
</div>
|
||||||
|
<div class="comment-content-item">
|
||||||
|
<div class="comment-content-item-main">
|
||||||
|
<comment-one class="comment-content-item-icon" />
|
||||||
|
<NuxtLink
|
||||||
|
:to="`/posts/${item.comment.post.id}#comment-${item.comment.id}`"
|
||||||
|
class="timeline-comment-link"
|
||||||
|
>
|
||||||
|
{{ stripMarkdownLength(item.comment.content, 200) }}
|
||||||
|
</NuxtLink>
|
||||||
|
</div>
|
||||||
|
<div class="timeline-date">{{ formatDate(item.createdAt) }}</div>
|
||||||
|
</div>
|
||||||
|
<div class="comment-content-item">
|
||||||
|
<div class="comment-content-item-main">
|
||||||
|
<comment-one class="comment-content-item-icon" />
|
||||||
|
<NuxtLink
|
||||||
|
:to="`/posts/${item.comment.post.id}#comment-${item.comment.id}`"
|
||||||
|
class="timeline-comment-link"
|
||||||
|
>
|
||||||
|
{{ stripMarkdownLength(item.comment.content, 200) }}
|
||||||
|
</NuxtLink>
|
||||||
|
</div>
|
||||||
|
<div class="timeline-date">{{ formatDate(item.createdAt) }}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<template v-else-if="item.type === 'reply'">
|
||||||
|
在
|
||||||
|
<NuxtLink :to="`/posts/${item.comment.post.id}`" class="timeline-link">
|
||||||
|
{{ item.comment.post.title }}
|
||||||
|
</NuxtLink>
|
||||||
|
下对
|
||||||
|
<NuxtLink
|
||||||
|
:to="`/posts/${item.comment.post.id}#comment-${item.comment.parentComment.id}`"
|
||||||
|
class="timeline-link"
|
||||||
|
>
|
||||||
|
{{ stripMarkdownLength(item.comment.parentComment.content, 200) }}
|
||||||
|
</NuxtLink>
|
||||||
|
回复了
|
||||||
|
<NuxtLink
|
||||||
|
:to="`/posts/${item.comment.post.id}#comment-${item.comment.id}`"
|
||||||
|
class="timeline-link"
|
||||||
|
>
|
||||||
|
{{ stripMarkdownLength(item.comment.content, 200) }}
|
||||||
|
</NuxtLink>
|
||||||
|
<div class="timeline-date">{{ formatDate(item.createdAt) }}</div>
|
||||||
|
</template>
|
||||||
<template v-else-if="item.type === 'tag'">
|
<template v-else-if="item.type === 'tag'">
|
||||||
创建了标签
|
创建了标签
|
||||||
<span class="timeline-link" @click="gotoTag(item.tag)">
|
<span class="timeline-link" @click="gotoTag(item.tag)">
|
||||||
@@ -287,8 +385,6 @@ import BasePlaceholder from '~/components/BasePlaceholder.vue'
|
|||||||
import BaseTimeline from '~/components/BaseTimeline.vue'
|
import BaseTimeline from '~/components/BaseTimeline.vue'
|
||||||
import BaseTabs from '~/components/BaseTabs.vue'
|
import BaseTabs from '~/components/BaseTabs.vue'
|
||||||
import LevelProgress from '~/components/LevelProgress.vue'
|
import LevelProgress from '~/components/LevelProgress.vue'
|
||||||
import ProfileTimelineCommentGroup from '~/components/ProfileTimelineCommentGroup.vue'
|
|
||||||
import ProfileTimelinePostItem from '~/components/ProfileTimelinePostItem.vue'
|
|
||||||
import UserList from '~/components/UserList.vue'
|
import UserList from '~/components/UserList.vue'
|
||||||
import { toast } from '~/main'
|
import { toast } from '~/main'
|
||||||
import { authState, getToken } from '~/utils/auth'
|
import { authState, getToken } from '~/utils/auth'
|
||||||
@@ -394,22 +490,6 @@ const fetchSummary = async () => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const isSameDay = (a, b) => {
|
|
||||||
const dateA = new Date(a)
|
|
||||||
const dateB = new Date(b)
|
|
||||||
return (
|
|
||||||
dateA.getFullYear() === dateB.getFullYear() &&
|
|
||||||
dateA.getMonth() === dateB.getMonth() &&
|
|
||||||
dateA.getDate() === dateB.getDate()
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
const createCommentEntry = (item) => ({
|
|
||||||
type: item.type,
|
|
||||||
comment: item.comment,
|
|
||||||
createdAt: item.createdAt,
|
|
||||||
})
|
|
||||||
|
|
||||||
const fetchTimeline = async () => {
|
const fetchTimeline = async () => {
|
||||||
const [postsRes, repliesRes, tagsRes] = await Promise.all([
|
const [postsRes, repliesRes, tagsRes] = await Promise.all([
|
||||||
fetch(`${API_BASE_URL}/api/users/${username}/posts?limit=50`),
|
fetch(`${API_BASE_URL}/api/users/${username}/posts?limit=50`),
|
||||||
@@ -440,32 +520,7 @@ const fetchTimeline = async () => {
|
|||||||
})),
|
})),
|
||||||
]
|
]
|
||||||
mapped.sort((a, b) => new Date(b.createdAt) - new Date(a.createdAt))
|
mapped.sort((a, b) => new Date(b.createdAt) - new Date(a.createdAt))
|
||||||
|
timelineItems.value = mapped
|
||||||
const grouped = []
|
|
||||||
for (const item of mapped) {
|
|
||||||
if (item.type === 'comment') {
|
|
||||||
const last = grouped[grouped.length - 1]
|
|
||||||
if (last && last.type === 'comment' && isSameDay(last.createdAt, item.createdAt)) {
|
|
||||||
last.entries.push(createCommentEntry(item))
|
|
||||||
if (new Date(item.createdAt) > new Date(last.createdAt)) {
|
|
||||||
last.createdAt = item.createdAt
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
grouped.push({
|
|
||||||
...item,
|
|
||||||
entries: [createCommentEntry(item)],
|
|
||||||
})
|
|
||||||
}
|
|
||||||
} else if (item.type === 'reply') {
|
|
||||||
grouped.push({
|
|
||||||
...item,
|
|
||||||
entries: [createCommentEntry(item)],
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
grouped.push(item)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
timelineItems.value = grouped
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const fetchFollowUsers = async () => {
|
const fetchFollowUsers = async () => {
|
||||||
|
|||||||
Reference in New Issue
Block a user