Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: fetch comments with related replies #106

Merged
merged 9 commits into from Mar 19, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 1 addition & 1 deletion build.gradle
Expand Up @@ -42,4 +42,4 @@ build {

halo {
version = "2.13.0"
}
}
3 changes: 2 additions & 1 deletion packages/comment-widget/src/base-comment-item-action.ts
Expand Up @@ -21,7 +21,7 @@ export class BaseCommentItemAction extends LitElement {
display: inline-flex;
align-items: center;
cursor: pointer;
margin-right: 0.5em;
gap: 0.1em;
}

.item-action__icon {
Expand All @@ -44,6 +44,7 @@ export class BaseCommentItemAction extends LitElement {
.item-action__text {
color: var(--base-info-color);
user-select: none;
font-size: 0.75em;
}

.item-action:hover .item-action__icon {
Expand Down
1 change: 1 addition & 0 deletions packages/comment-widget/src/base-comment-item.ts
Expand Up @@ -108,6 +108,7 @@ export class BaseCommentItem extends LitElement {
margin-top: 0.5em;
display: flex;
align-items: center;
gap: 0.7em;
}

.item--animate-breath {
Expand Down
116 changes: 92 additions & 24 deletions packages/comment-widget/src/comment-item.ts
Expand Up @@ -7,9 +7,11 @@ import './user-avatar';
import './base-comment-item';
import './base-comment-item-action';
import { consume } from '@lit/context';
import { baseUrlContext } from './context';
import { baseUrlContext, withRepliesContext } from './context';
import { LS_UPVOTED_COMMENTS_KEY } from './constant';
import varStyles from './styles/var';
import { Ref, createRef, ref } from 'lit/directives/ref.js';
import { CommentReplies } from './comment-replies';

export class CommentItem extends LitElement {
@consume({ context: baseUrlContext })
Expand All @@ -19,19 +21,33 @@ export class CommentItem extends LitElement {
@property({ type: Object })
comment: CommentVo | undefined;

@consume({ context: withRepliesContext, subscribe: true })
@state()
withReplies = false;

@state()
showReplies = false;

@state()
showReplyForm = false;

@state()
upvoted = false;

@state()
upvoteCount = 0;

commentRepliesRef: Ref<CommentReplies> = createRef<CommentReplies>();

override connectedCallback(): void {
super.connectedCallback();
this.checkUpvotedStatus();

if (this.withReplies) {
this.showReplies = true;
this.showReplyForm = false;
}

this.upvoteCount = this.comment?.stats.upvote || 0;
}

Expand Down Expand Up @@ -72,6 +88,19 @@ export class CommentItem extends LitElement {
this.checkUpvotedStatus();
}

handleShowReplies() {
this.showReplies = !this.showReplies;

if (!this.withReplies) {
this.showReplyForm = !this.showReplyForm;
}
}

onReplyCreated() {
this.commentRepliesRef.value?.fetchReplies();
this.showReplies = true;
}

override render() {
return html`<base-comment-item
.userAvatar="${this.comment?.owner.avatar}"
Expand Down Expand Up @@ -121,32 +150,67 @@ export class CommentItem extends LitElement {
</svg>`}
</base-comment-item-action>

<base-comment-item-action
slot="action"
.text="${(this.comment?.status?.visibleReplyCount || 0) + ''}"
@click="${() => (this.showReplies = !this.showReplies)}"
>
<svg
slot="icon"
xmlns="http://www.w3.org/2000/svg"
width="32"
height="32"
viewBox="0 0 24 24"
>
<path
fill="none"
stroke="currentColor"
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="m3 20l1.3-3.9C1.976 12.663 2.874 8.228 6.4 5.726c3.526-2.501 8.59-2.296 11.845.48c3.255 2.777 3.695 7.266 1.029 10.501C16.608 19.942 11.659 20.922 7.7 19L3 20"
/>
</svg>
</base-comment-item-action>
${this.withReplies && this.comment?.status?.visibleReplyCount === 0
? ''
: html`<base-comment-item-action
slot="action"
.text="${(this.comment?.status?.visibleReplyCount || 0) + ''}"
@click="${this.handleShowReplies}"
>
<svg
slot="icon"
xmlns="http://www.w3.org/2000/svg"
width="32"
height="32"
viewBox="0 0 24 24"
>
<path
fill="none"
stroke="currentColor"
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="m3 20l1.3-3.9C1.976 12.663 2.874 8.228 6.4 5.726c3.526-2.501 8.59-2.296 11.845.48c3.255 2.777 3.695 7.266 1.029 10.501C16.608 19.942 11.659 20.922 7.7 19L3 20"
/>
</svg>
</base-comment-item-action>`}
${this.withReplies
? html` <base-comment-item-action
slot="action"
.text=${this.showReplyForm ? '取消回复' : '加入回复'}
@click="${() => (this.showReplyForm = !this.showReplyForm)}"
>
<svg
slot="icon"
xmlns="http://www.w3.org/2000/svg"
width="32"
height="32"
viewBox="0 0 24 24"
>
<path
fill="none"
stroke="currentColor"
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M12.007 19.98A9.869 9.869 0 0 1 7.7 19L3 20l1.3-3.9C1.976 12.663 2.874 8.228 6.4 5.726c3.526-2.501 8.59-2.296 11.845.48c1.992 1.7 2.93 4.04 2.747 6.34M16 19h6m-3-3v6"
/>
</svg>
</base-comment-item-action>`
: ''}

<div slot="footer">
${this.showReplyForm
? html`<div class="item__reply-form">
<reply-form @reload=${this.onReplyCreated} .comment=${this.comment}></reply-form>
</div>`
: ''}
${this.showReplies
? html`<comment-replies .comment="${this.comment}"></comment-replies>`
? html`<comment-replies
${ref(this.commentRepliesRef)}
.comment="${this.comment}"
.showReplyForm=${this.showReplyForm}
></comment-replies>`
: ``}
</div>
</base-comment-item>`;
Expand All @@ -159,6 +223,10 @@ export class CommentItem extends LitElement {
.item__action--upvote {
margin-left: -0.5em;
}

.item__reply-form {
margin-top: 0.5em;
}
`,
];
}
Expand Down
115 changes: 95 additions & 20 deletions packages/comment-widget/src/comment-replies.ts
@@ -1,9 +1,9 @@
import { CommentVo, ReplyVo } from '@halo-dev/api-client';
import { CommentVo, ReplyVo, ReplyVoList } from '@halo-dev/api-client';
import { LitElement, css, html } from 'lit';
import { property, state } from 'lit/decorators.js';
import { repeat } from 'lit/directives/repeat.js';
import { consume } from '@lit/context';
import { baseUrlContext, toastContext } from './context';
import { baseUrlContext, replySizeContext, toastContext, withRepliesContext } from './context';
import './reply-item';
import './loading-block';
import './reply-form';
Expand All @@ -16,12 +16,29 @@ export class CommentReplies extends LitElement {
@property({ attribute: false })
baseUrl = '';

@consume({ context: withRepliesContext, subscribe: true })
@state()
withReplies = false;

@consume({ context: replySizeContext, subscribe: true })
@state()
replySize = 10;

@property({ type: Object })
comment: CommentVo | undefined;

@property({ type: Boolean })
showReplyForm = false;

@state()
replies: ReplyVo[] = [];

@state()
page = 1;

@state()
hasNext = false;

@state()
loading = false;

Expand All @@ -34,10 +51,8 @@ export class CommentReplies extends LitElement {

override render() {
return html`<div class="replies__wrapper">
<reply-form @reload=${this.fetchReplies} .comment=${this.comment}></reply-form>
${this.loading
? html`<loading-block></loading-block>`
: html`
${this.replies.length
? html`
<div class="replies__list">
${repeat(
this.replies,
Expand All @@ -53,55 +68,115 @@ export class CommentReplies extends LitElement {
></reply-item>`
)}
</div>
`}
`
: ''}
${this.loading ? html`<loading-block></loading-block>` : ''}
${this.hasNext && !this.loading
? html`<div class="replies__next-wrapper">
<button @click=${this.fetchNext}>加载更多</button>
</div>`
: ''}
</div>`;
}

onSetActiveQuoteReply(event: CustomEvent) {
this.activeQuoteReply = event.detail.quoteReply;
}

async fetchReplies() {
if (this.replies.length === 0) {
async fetchReplies(options?: { append: boolean }) {
try {
this.loading = true;
}

try {
// Reload replies list
if (!options?.append) {
this.page = 1;
}

const queryParams = [`page=${this.page || 0}`, `size=${this.replySize}`];

const response = await fetch(
`${this.baseUrl}/apis/api.halo.run/v1alpha1/comments/${this.comment?.metadata.name}/reply`
`${this.baseUrl}/apis/api.halo.run/v1alpha1/comments/${this.comment?.metadata.name}/reply?${queryParams.join('&')}`
);

if (!response.ok) {
throw new Error('加载回复列表失败,请稍后重试');
}

const data = await response.json();
this.replies = data.items;
const data = (await response.json()) as ReplyVoList;

if (options?.append) {
this.replies = this.replies.concat(data.items);
} else {
this.replies = data.items;
}

this.hasNext = data.hasNext;
this.page = data.page;
} catch (error) {
if (error instanceof Error) {
this.toastManager?.error(error.message);
}
} finally {
this.loading = false;
}
}

this.loading = false;
async fetchNext() {
this.page++;
this.fetchReplies({ append: true });
}

override connectedCallback(): void {
super.connectedCallback();
this.fetchReplies();

if (this.withReplies) {
// TODO: Fix ts error
// Needs @halo-dev/[email protected]
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
this.replies = this.comment?.replies.items;
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
this.page = this.comment?.replies.page;
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
this.hasNext = this.comment?.replies.hasNext;
} else {
this.fetchReplies();
}
}

static override styles = [
varStyles,
baseStyles,
css`
.replies__wrapper {
margin-top: 0.5em;
}

.replies__list {
margin-top: 0.875em;
}

.replies__next-wrapper {
display: flex;
justify-content: center;
margin: 0.5em 0;
}

.replies__next-wrapper button {
border-radius: var(--base-border-radius);
color: var(--base-color);
font-size: 0.875em;
display: inline-flex;
align-items: center;
font-weight: 600;
padding: 0.4em 0.875em;
transition-property: all;
transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
transition-duration: 0.15s;
border: 1px solid transparent;
}

.replies__next-wrapper button:hover {
background-color: var(--component-pagination-button-bg-color-hover);
}
`,
];
}
Expand Down