Skip to content
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.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion biome.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
},
"files": {
"ignoreUnknown": false,
"includes": ["**", "!**/generated/**"]
"includes": ["**", "!**/generated"]
},
"formatter": {
"enabled": true,
Expand Down
2 changes: 1 addition & 1 deletion packages/comment-widget/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@
"dependencies": {
"@emoji-mart/data": "^1.2.1",
"@floating-ui/dom": "^1.7.3",
"@halo-dev/api-client": "^2.21.1",
"@halo-dev/api-client": "https://pkg.pr.new/@halo-dev/api-client@7679",
"@lit/context": "^1.1.6",
"@lit/localize": "^0.12.2",
"@tiptap/core": "^3.2.0",
Expand Down
15 changes: 13 additions & 2 deletions packages/comment-widget/src/base-comment-item.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,10 @@ export class BaseCommentItem extends LitElement {
content = '';

@property({ type: String })
ua: string = '';
ua: string | undefined;

@property({ type: Boolean })
private: boolean | undefined;

@consume({ context: configMapDataContext })
@state()
Expand All @@ -51,7 +54,7 @@ export class BaseCommentItem extends LitElement {
></user-avatar>
</div>
<div class="item-main flex-[1_1_auto] min-w-0 w-full">
<div class="item-meta flex items-center gap-3 flex-wrap">
<div class="item-meta flex items-center gap-2 flex-wrap">
${when(
this.userWebsite,
() => html`
Expand All @@ -69,6 +72,14 @@ export class BaseCommentItem extends LitElement {
`
)}

${when(
this.private && this.configMapData?.basic.showPrivateCommentBadge,
() => html`<div class="inline-flex items-center gap-1 bg-muted-3 rounded-base px-1.5 py-1">
<i class="i-ri-git-repository-private-line opacity-90 size-3" aria-hidden="true"></i>
<span class="text-xs text-text-2">${msg('Private')}</span>
</div>`
)}

${when(this.ua && this.configMapData?.basic.showCommenterDevice, () => html`<commenter-ua-bar .ua=${this.ua}></commenter-ua-bar>`)}

<time class="item-meta-info text-xs text-text-3" title=${formatDate(this.creationTime)}>
Expand Down
27 changes: 23 additions & 4 deletions packages/comment-widget/src/base-form.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import { when } from 'lit/directives/when.js';
import { ofetch } from 'ofetch';
import type { CommentEditor } from './comment-editor';
import { cleanHtml } from './utils/html';
import './base-tooltip';

export class BaseForm extends LitElement {
@consume({ context: baseUrlContext })
Expand Down Expand Up @@ -65,6 +66,9 @@ export class BaseForm extends LitElement {
@state()
toastManager: ToastManager | undefined;

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

textareaRef: Ref<HTMLTextAreaElement> = createRef<HTMLTextAreaElement>();

editorRef: Ref<CommentEditor> = createRef<CommentEditor>();
Expand Down Expand Up @@ -151,9 +155,11 @@ export class BaseForm extends LitElement {

renderAccountInfo() {
return html`<div class="form-account flex items-center gap-2">
<div class="form-account-avatar avatar">
${when(this.currentUser?.spec.avatar, () => html`<img src=${this.currentUser?.spec.avatar || ''} class="size-full object-cover" />`)}
</div>
${when(
this.currentUser?.spec.avatar,
() => html`<div class="form-account-avatar avatar"><img src=${this.currentUser?.spec.avatar || ''} class="size-full object-cover" /></div>
`
)}
<span class="form-account-name text-base text-text-1 font-semibold">
${this.currentUser?.spec.displayName || this.currentUser?.metadata.name}
</span>
Expand Down Expand Up @@ -238,7 +244,19 @@ export class BaseForm extends LitElement {
</button>
`
)}
<div class="form-actions justify-end flex gap-2 flex-wrap items-center">
<div class="form-actions justify-end flex gap-3 flex-wrap items-center">
${when(
!this.hidePrivateCheckbox &&
this.configMapData?.basic.enablePrivateComment,
() => html`<div class="flex items-center gap-2">
<input id="hidden" name="hidden" type="checkbox" />
<label for="hidden" class="text-xs select-none text-text-3 hover:text-text-1 transition-all">${msg('Private')}</label>
<base-tooltip content=${this.currentUser ? msg('Currently logged in. After selecting the private option, comments will only be visible to yourself and the site administrator.') : msg('You are currently anonymous. After selecting the private option, the comment will only be visible to the site administrator.')}>
<i class="i-mingcute:information-line size-3.5 text-text-3 block"></i>
</base-tooltip>
</div>`
)}

${when(
this.showCaptcha && this.captcha,
() => html`
Expand Down Expand Up @@ -293,6 +311,7 @@ export class BaseForm extends LitElement {
detail: {
...data,
content,
hidden: data.hidden === 'on',
},
});
this.dispatchEvent(event);
Expand Down
161 changes: 161 additions & 0 deletions packages/comment-widget/src/base-tooltip.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,161 @@
import {
autoUpdate,
computePosition,
flip,
offset,
shift,
} from '@floating-ui/dom';
import type { TemplateResult } from 'lit';
import { css, html, LitElement } from 'lit';
import { property } from 'lit/decorators.js';
import baseStyles from './styles/base';

/**
* A simple tooltip component based on Lit and floating-ui.
*
* @example
* ```html
* <base-tooltip content="This is a tooltip">
* <button>Hover me</button>
* </base-tooltip>
* ```
*/
export class BaseTooltip extends LitElement {
/**
* The content to display in the tooltip.
*/
@property({ type: String })
content: string | TemplateResult = '';

private tooltipEl?: HTMLElement;
private cleanupFn?: () => void;

override connectedCallback(): void {
super.connectedCallback();
this.addEventListener('mouseenter', this._showTooltip);
this.addEventListener('mouseleave', this._hideTooltip);
}

override disconnectedCallback(): void {
super.disconnectedCallback();
this.removeEventListener('mouseenter', this._showTooltip);
this.removeEventListener('mouseleave', this._hideTooltip);

// Clean up positioning if needed
if (this.cleanupFn) {
this.cleanupFn();
this.cleanupFn = undefined;
}
}

override firstUpdated(): void {
// Get tooltip element after the component is rendered
this.tooltipEl = this.shadowRoot?.querySelector('.tooltip') as HTMLElement;
}

private _showTooltip = (): void => {
if (!this.tooltipEl) return;

this.tooltipEl.classList.add('show');
this._updatePosition();
};

private _hideTooltip = (): void => {
if (!this.tooltipEl) return;

this.tooltipEl.classList.remove('show');

// Clean up positioning
if (this.cleanupFn) {
this.cleanupFn();
this.cleanupFn = undefined;
}
};

private _updatePosition(): void {
if (!this.tooltipEl) return;

const floating = this.tooltipEl;

// Clean up previous positioning if any
if (this.cleanupFn) {
this.cleanupFn();
}

// Use autoUpdate to reposition on scroll/resize
this.cleanupFn = autoUpdate(this, floating, async () => {
// Calculate the position
const middlewares = [
offset(8), // 8px distance from trigger
flip(), // flip to other side if needed
shift({ padding: 5 }), // shift to keep in viewport
];

// Calculate position
const { x, y } = await computePosition(this, floating, {
placement: 'top', // Default placement is top
middleware: middlewares,
});

// Position the tooltip
Object.assign(floating.style, {
left: `${x}px`,
top: `${y}px`,
});
});
}

override render() {
return html`
<slot></slot>
<div class="tooltip">
${this.content}
</div>
`;
}

static override styles = [
...baseStyles,
css`
:host {
display: inline-block;
}

::slotted(*) {
display: inline-block;
}

.tooltip {
position: absolute;
background: rgba(0, 0, 0, 0.8);
color: white;
padding: 5px 10px;
border-radius: 4px;
font-size: 0.875em;
max-width: 300px;
width: auto;
z-index: 10;
opacity: 0;
pointer-events: none;
transition: opacity 0.2s;
top: 0;
left: 0;
white-space: normal;
word-wrap: break-word;
}

.tooltip.show {
opacity: 1;
}
`,
];
}

customElements.get('base-tooltip') ||
customElements.define('base-tooltip', BaseTooltip);

declare global {
interface HTMLElementTagNameMap {
'base-tooltip': BaseTooltip;
}
}
3 changes: 2 additions & 1 deletion packages/comment-widget/src/comment-form.ts
Original file line number Diff line number Diff line change
Expand Up @@ -82,13 +82,14 @@ export class CommentForm extends LitElement {

const data = e.detail;

const { displayName, email, website, content } = data || {};
const { displayName, email, website, content, hidden } = data || {};

const commentRequest: CommentRequest = {
raw: content,
content: content,
// TODO: support user input
allowNotification: true,
hidden: hidden || false,
subjectRef: {
group: this.group,
kind: this.kind,
Expand Down
1 change: 1 addition & 0 deletions packages/comment-widget/src/comment-item.ts
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,7 @@ export class CommentItem extends LitElement {
.approved=${this.comment?.spec.approved}
.userWebsite=${this.comment?.spec.owner.annotations?.website}
.ua=${this.comment?.spec.userAgent}
.private=${this.comment?.spec.hidden}
>
<button slot="action" class="icon-button group -ml-2" type="button" @click="${this.handleUpvote}" aria-label=${msg('Upvote')}>
<div class="icon-button-icon">
Expand Down
3 changes: 3 additions & 0 deletions packages/comment-widget/src/generated/locales/es.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,13 +22,15 @@
's3643189d1abbb7f4': `Código`,
's3fb33d17bad61aa9': `Comentario enviado con éxito, pendiente de revisión`,
's4c0e15f9073382e6': `Error al obtener el código de verificación`,
's5184a3f3e2f7b603': `Actualmente anónimo. Después de seleccionar la opción privada, el comentario solo será visible para el administrador del sitio.`,
's523eb9043213ff0d': `Itálica`,
's58a3c1ecd4dd06cf': `Tachado`,
's67749057edb2586b': `Cerrar sesión`,
's6cb61eeccda272d5': `Bloque de código`,
's7437373e541a8037': `Apodo`,
's7584ded3d749c75e': `Cargar más`,
's82665b2ffabc9c0a': `Sitio web`,
's838e512973be01d4': `Actualmente conectado. Después de seleccionar la opción privada, los comentarios solo serán visibles para usted y el administrador del sitio.`,
's84b033b2f7360187': `Por favor, inicie sesión o complete la información primero`,
's98a5f7789c49dd3f': `En revisión`,
's9f2ed66340f019c6': `Escribir un comentario`,
Expand All @@ -41,6 +43,7 @@
'sc8da3cc71de63832': `Iniciar sesión`,
'sd1f44f1a8bc20e67': `Correo electrónico`,
'sd5e242ab9574958a': `Error al comentar, por favor intente más tarde`,
'se7bee6e9a9b5394c': `Íntimo`,
'sea7e567ed89dc0d7': `Seleccionar emoticono`,
'sf3ff78cc329d3528': `Anterior`,
'sf77128b082955d42': `(O iniciar sesión)`,
Expand Down
3 changes: 3 additions & 0 deletions packages/comment-widget/src/generated/locales/zh-CN.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,13 +22,15 @@
's3643189d1abbb7f4': `代码`,
's3fb33d17bad61aa9': `评论成功,请等待审核`,
's4c0e15f9073382e6': `获取验证码失败`,
's5184a3f3e2f7b603': `当前是匿名状态,选择私密选项后,评论将仅对网站管理员可见。`,
's523eb9043213ff0d': `斜体`,
's58a3c1ecd4dd06cf': `删除线`,
's67749057edb2586b': `退出登录`,
's6cb61eeccda272d5': `代码块`,
's7437373e541a8037': `昵称`,
's7584ded3d749c75e': `加载更多`,
's82665b2ffabc9c0a': `网站`,
's838e512973be01d4': `当前已登录,选择私密选项后,评论将仅对您和网站管理员可见。`,
's84b033b2f7360187': `请先登录或者完善信息`,
's98a5f7789c49dd3f': `审核中`,
's9f2ed66340f019c6': `编写评论`,
Expand All @@ -41,6 +43,7 @@
'sc8da3cc71de63832': `登录`,
'sd1f44f1a8bc20e67': `电子邮件`,
'sd5e242ab9574958a': `评论失败,请稍后重试`,
'se7bee6e9a9b5394c': `私密`,
'sea7e567ed89dc0d7': `选择表情`,
'sf3ff78cc329d3528': `上一页`,
'sf77128b082955d42': `(或登录账号)`,
Expand Down
3 changes: 3 additions & 0 deletions packages/comment-widget/src/generated/locales/zh-TW.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,13 +22,15 @@
's3643189d1abbb7f4': `程式碼`,
's3fb33d17bad61aa9': `評論成功,請等待審核`,
's4c0e15f9073382e6': `獲取驗證碼失敗`,
's5184a3f3e2f7b603': `目前是匿名狀態,選擇私密選項後,評論將僅對網站管理員可見。`,
's523eb9043213ff0d': `斜體`,
's58a3c1ecd4dd06cf': `刪除線`,
's67749057edb2586b': `登出`,
's6cb61eeccda272d5': `程式碼區塊`,
's7437373e541a8037': `暱稱`,
's7584ded3d749c75e': `載入更多`,
's82665b2ffabc9c0a': `網站`,
's838e512973be01d4': `目前已登入,選擇私密選項後,評論將僅對您和網站管理員可見。`,
's84b033b2f7360187': `請先登入或者完善資訊`,
's98a5f7789c49dd3f': `審核中`,
's9f2ed66340f019c6': `撰寫評論`,
Expand All @@ -41,6 +43,7 @@
'sc8da3cc71de63832': `登入`,
'sd1f44f1a8bc20e67': `電子郵件`,
'sd5e242ab9574958a': `評論失敗,請稍後重試`,
'se7bee6e9a9b5394c': `私密`,
'sea7e567ed89dc0d7': `選擇表情`,
'sf3ff78cc329d3528': `上一頁`,
'sf77128b082955d42': `(或登入帳號)`,
Expand Down
1 change: 1 addition & 0 deletions packages/comment-widget/src/reply-form.ts
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@ export class ReplyForm extends LitElement {
return html` <base-form
.submitting=${this.submitting}
.captcha=${this.captcha}
.hidePrivateCheckbox=${true}
${ref(this.baseFormRef)}
@submit="${this.onSubmit}"
></base-form>`;
Expand Down
1 change: 1 addition & 0 deletions packages/comment-widget/src/reply-item.ts
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,7 @@ export class ReplyItem extends LitElement {
.breath=${this.isQuoteReplyHovered}
.userWebsite=${this.reply?.spec.owner.annotations?.website}
.ua=${this.reply?.spec.userAgent}
.private=${this.comment?.spec.hidden || this.reply?.spec.hidden}
>
<button slot="action" class="icon-button group -ml-2" type="button" @click="${this.handleUpvote}" aria-label=${msg('Upvote')}>
<div class="icon-button-icon ">
Expand Down
2 changes: 2 additions & 0 deletions packages/comment-widget/src/types/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ interface BasicConfig {
withReplySize: number;
replySize: number;
showCommenterDevice?: boolean;
enablePrivateComment?: boolean;
showPrivateCommentBadge?: boolean;
}

interface SecurityConfig {
Expand Down
Loading