diff --git a/build.gradle b/build.gradle index 31021d7..6b2a1b2 100644 --- a/build.gradle +++ b/build.gradle @@ -41,5 +41,5 @@ build { } halo { - version = "2.13.0" + version = "comment" } \ No newline at end of file diff --git a/packages/comment-widget/src/base-comment-item-action.ts b/packages/comment-widget/src/base-comment-item-action.ts index ad58046..f84584a 100644 --- a/packages/comment-widget/src/base-comment-item-action.ts +++ b/packages/comment-widget/src/base-comment-item-action.ts @@ -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 { @@ -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 { diff --git a/packages/comment-widget/src/base-comment-item.ts b/packages/comment-widget/src/base-comment-item.ts index 5ef358d..0a01e09 100644 --- a/packages/comment-widget/src/base-comment-item.ts +++ b/packages/comment-widget/src/base-comment-item.ts @@ -108,6 +108,7 @@ export class BaseCommentItem extends LitElement { margin-top: 0.5em; display: flex; align-items: center; + gap: 0.7em; } .item--animate-breath { diff --git a/packages/comment-widget/src/comment-item.ts b/packages/comment-widget/src/comment-item.ts index 8ac6c5c..5404505 100644 --- a/packages/comment-widget/src/comment-item.ts +++ b/packages/comment-widget/src/comment-item.ts @@ -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 }) @@ -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 = createRef(); + override connectedCallback(): void { super.connectedCallback(); this.checkUpvotedStatus(); + if (this.withReplies) { + this.showReplies = true; + this.showReplyForm = false; + } + this.upvoteCount = this.comment?.stats.upvote || 0; } @@ -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``} - - - - - + ${this.withReplies && this.comment?.status?.visibleReplyCount === 0 + ? '' + : html` + + + + `} + ${this.withReplies + ? html` + + + + ` + : ''}
+ ${this.showReplyForm + ? html`
+ +
` + : ''} ${this.showReplies - ? html`` + ? html`` : ``}
`; @@ -159,6 +223,10 @@ export class CommentItem extends LitElement { .item__action--upvote { margin-left: -0.5em; } + + .item__reply-form { + margin-top: 0.5em; + } `, ]; } diff --git a/packages/comment-widget/src/comment-replies.ts b/packages/comment-widget/src/comment-replies.ts index 28d223c..b976cb4 100644 --- a/packages/comment-widget/src/comment-replies.ts +++ b/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, toastContext, withRepliesContext } from './context'; import './reply-item'; import './loading-block'; import './reply-form'; @@ -16,12 +16,25 @@ export class CommentReplies extends LitElement { @property({ attribute: false }) baseUrl = ''; + @consume({ context: withRepliesContext, subscribe: true }) + @state() + withReplies = false; + @property({ type: Object }) comment: CommentVo | undefined; + @property({ type: Boolean }) + showReplyForm = false; + @state() replies: ReplyVo[] = []; + @state() + page = 1; + + @state() + hasNext = false; + @state() loading = false; @@ -34,10 +47,8 @@ export class CommentReplies extends LitElement { override render() { return html`
- - ${this.loading - ? html`` - : html` + ${this.replies.length + ? html`
${repeat( this.replies, @@ -53,7 +64,14 @@ export class CommentReplies extends LitElement { >` )}
- `} + ` + : ''} + ${this.loading ? html`` : ''} + ${this.hasNext && !this.loading + ? html`
+ +
` + : ''}
`; } @@ -61,47 +79,100 @@ export class CommentReplies extends LitElement { 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=10`]; + 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/api-client@2.14.0 + // 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); + } `, ]; } diff --git a/packages/comment-widget/src/comment-widget.ts b/packages/comment-widget/src/comment-widget.ts index 90a9be3..ee90851 100644 --- a/packages/comment-widget/src/comment-widget.ts +++ b/packages/comment-widget/src/comment-widget.ts @@ -14,6 +14,7 @@ import { nameContext, toastContext, versionContext, + withRepliesContext, } from './context'; import './comment-form'; import './comment-item'; @@ -42,6 +43,10 @@ export class CommentWidget extends LitElement { @property({ type: String }) name = ''; + @provide({ context: withRepliesContext }) + @property({ type: Boolean }) + withReplies = true; + @provide({ context: emojiDataUrlContext }) @property({ type: String }) emojiDataUrl = 'https://unpkg.com/@emoji-mart/data'; @@ -152,6 +157,7 @@ export class CommentWidget extends LitElement { `page=${this.comments.page}`, `size=${this.comments.size}`, `version=${this.version}`, + `withReplies=${this.withReplies}`, ]; const response = await fetch( diff --git a/packages/comment-widget/src/context/index.ts b/packages/comment-widget/src/context/index.ts index c5b6b7f..6fc067d 100644 --- a/packages/comment-widget/src/context/index.ts +++ b/packages/comment-widget/src/context/index.ts @@ -7,6 +7,7 @@ export const kindContext = createContext(Symbol('kind')); export const groupContext = createContext(Symbol('group')); export const nameContext = createContext(Symbol('name')); export const versionContext = createContext(Symbol('version')); +export const withRepliesContext = createContext(Symbol('withReplies')); export const allowAnonymousCommentsContext = createContext( Symbol('allowAnonymousComments') diff --git a/packages/comment-widget/src/reply-item.ts b/packages/comment-widget/src/reply-item.ts index c7a4f49..ef93c56 100644 --- a/packages/comment-widget/src/reply-item.ts +++ b/packages/comment-widget/src/reply-item.ts @@ -157,6 +157,7 @@ export class ReplyItem extends LitElement { diff --git a/packages/example/index.html b/packages/example/index.html index 8f69b53..c5ae84b 100644 --- a/packages/example/index.html +++ b/packages/example/index.html @@ -67,11 +67,12 @@