diff --git a/packages/comment-widget/src/avatar/avatar-policy.ts b/packages/comment-widget/src/avatar/avatar-policy.ts new file mode 100644 index 0000000..1cc4b56 --- /dev/null +++ b/packages/comment-widget/src/avatar/avatar-policy.ts @@ -0,0 +1,85 @@ +import { CommentVo, ReplyVo } from '@halo-dev/api-client'; +import { getAvatarProvider } from './providers'; + +abstract class AvatarPolicy { + abstract applyCommentPolicy(comment: CommentVo | undefined): string | undefined; + abstract applyReplyPolicy(reply: ReplyVo | undefined): string | undefined; +} + +let policyInstance: AvatarPolicy | undefined; +const emailKind = 'Email'; +const emailHash = 'email-hash'; + +class AnonymousUserPolicy extends AvatarPolicy { + applyCommentPolicy(comment: CommentVo | undefined): string | undefined { + const avatarProvider = getAvatarProvider(); + const isAnonymous = comment?.owner.kind === emailKind; + if (isAnonymous) { + return avatarProvider?.getAvatarSrc(comment?.spec.owner.annotations?.[emailHash]); + } + return comment?.owner.avatar; + } + applyReplyPolicy(reply: ReplyVo | undefined): string | undefined { + const avatarProvider = getAvatarProvider(); + const isAnonymous = reply?.owner.kind === emailKind; + if (isAnonymous) { + return avatarProvider?.getAvatarSrc(reply?.spec.owner.annotations?.[emailHash]); + } + return reply?.owner.avatar; + } +} + +class AllUserPolicy extends AvatarPolicy { + applyCommentPolicy(comment: CommentVo | undefined): string | undefined { + const avatarProvider = getAvatarProvider(); + return avatarProvider?.getAvatarSrc(comment?.spec.owner.annotations?.[emailHash]); + } + applyReplyPolicy(reply: ReplyVo | undefined): string | undefined { + const avatarProvider = getAvatarProvider(); + return avatarProvider?.getAvatarSrc(reply?.spec.owner.annotations?.[emailHash]); + } +} + +class NoAvatarUserPolicy extends AvatarPolicy { + applyCommentPolicy(comment: CommentVo | undefined): string | undefined { + const avatarProvider = getAvatarProvider(); + const isAnonymous = comment?.owner.kind === emailKind; + const avatar = comment?.owner.avatar; + if (isAnonymous || !avatar) { + return avatarProvider?.getAvatarSrc(comment?.spec.owner.annotations?.[emailHash]); + } + return avatar; + } + applyReplyPolicy(reply: ReplyVo | undefined): string | undefined { + const avatarProvider = getAvatarProvider(); + const isAnonymous = reply?.owner.kind === emailKind; + const avatar = reply?.owner.avatar; + if (isAnonymous || !avatar) { + return avatarProvider?.getAvatarSrc(reply?.spec.owner.annotations?.[emailHash]); + } + return avatar; + } +} + +enum AvatarPolicyEnum { + ANONYMOUS_USER_POLICY = 'anonymousUser', + ALL_USER_POLICY = 'allUser', + NO_AVATAR_USER_POLICY = 'noAvatarUser', +} + +function setPolicyInstance(nPolicyInstance: AvatarPolicy | undefined) { + policyInstance = nPolicyInstance; +} + +function getPolicyInstance(): AvatarPolicy | undefined { + return policyInstance; +} + +export { + AnonymousUserPolicy, + AllUserPolicy, + NoAvatarUserPolicy, + AvatarPolicyEnum, + setPolicyInstance, + getPolicyInstance, +}; diff --git a/packages/comment-widget/src/avatar/providers/avatar-provider.ts b/packages/comment-widget/src/avatar/providers/avatar-provider.ts new file mode 100644 index 0000000..6b38042 --- /dev/null +++ b/packages/comment-widget/src/avatar/providers/avatar-provider.ts @@ -0,0 +1,23 @@ +export default abstract class AvatarProvider { + private readonly _name: string; + private _url: string; + + constructor(name: string, url: string) { + this._name = name; + this._url = url; + } + + get url(): string { + return this._url; + } + + set url(value: string) { + this._url = value; + } + + get name(): string { + return this._name; + } + + abstract getAvatarSrc(emailHash: string | undefined): string; +} diff --git a/packages/comment-widget/src/avatar/providers/gravatar.ts b/packages/comment-widget/src/avatar/providers/gravatar.ts new file mode 100644 index 0000000..08da722 --- /dev/null +++ b/packages/comment-widget/src/avatar/providers/gravatar.ts @@ -0,0 +1,9 @@ +import AvatarProvider from './avatar-provider'; + +class Gravatar extends AvatarProvider { + override getAvatarSrc(emailHash: string | undefined): string { + return `${this.url}/avatar/${emailHash}`; + } +} + +export default new Gravatar('Gravatar', 'https://gravatar.com'); diff --git a/packages/comment-widget/src/avatar/providers/index.ts b/packages/comment-widget/src/avatar/providers/index.ts new file mode 100644 index 0000000..7723a1c --- /dev/null +++ b/packages/comment-widget/src/avatar/providers/index.ts @@ -0,0 +1,24 @@ +import Gravatar from './gravatar'; +import AvatarProvider from './avatar-provider'; + +let avatarProvider: AvatarProvider | undefined; + +enum AvatarProviderEnum { + GRAVATAR = 'gravatar', +} + +export function setAvatarProvider(provider: string, mirrorUrl?: string) { + switch (provider) { + case AvatarProviderEnum.GRAVATAR: + if (mirrorUrl) { + Gravatar.url = mirrorUrl; + } + avatarProvider = Gravatar; + break; + default: + } +} + +export function getAvatarProvider() { + return avatarProvider; +} diff --git a/packages/comment-widget/src/comment-item.ts b/packages/comment-widget/src/comment-item.ts index 5404505..25edca7 100644 --- a/packages/comment-widget/src/comment-item.ts +++ b/packages/comment-widget/src/comment-item.ts @@ -12,6 +12,7 @@ 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'; +import { getPolicyInstance } from './avatar/avatar-policy'; export class CommentItem extends LitElement { @consume({ context: baseUrlContext }) @@ -103,7 +104,7 @@ export class CommentItem extends LitElement { override render() { return html`(Symbol('name')); export const versionContext = createContext(Symbol('version')); export const replySizeContext = createContext(Symbol('replySize')); export const withRepliesContext = createContext(Symbol('withReplies')); +export const useAvatarProviderContext = createContext(Symbol('useAvatarProvider')); +export const avatarProviderContext = createContext(Symbol('avatarProvider')); +export const avatarProviderMirrorContext = createContext(Symbol('avatarProviderMirror')); +export const avatarPolicyContext = createContext(Symbol('avatarPolicy')); 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 ef93c56..dd1b639 100644 --- a/packages/comment-widget/src/reply-item.ts +++ b/packages/comment-widget/src/reply-item.ts @@ -10,6 +10,7 @@ import { LS_UPVOTED_REPLIES_KEY } from './constant'; import { consume } from '@lit/context'; import { baseUrlContext } from './context'; import varStyles from './styles/var'; +import { getPolicyInstance } from './avatar/avatar-policy'; export class ReplyItem extends LitElement { @consume({ context: baseUrlContext }) @@ -106,7 +107,7 @@ export class ReplyItem extends LitElement { override render() { return html` { entries.forEach((entry) => { diff --git a/src/main/java/run/halo/comment/widget/CommentWidgetPlugin.java b/src/main/java/run/halo/comment/widget/CommentWidgetPlugin.java index de7b759..b83d35e 100644 --- a/src/main/java/run/halo/comment/widget/CommentWidgetPlugin.java +++ b/src/main/java/run/halo/comment/widget/CommentWidgetPlugin.java @@ -2,7 +2,6 @@ import org.pf4j.PluginWrapper; import org.springframework.stereotype.Component; -import run.halo.app.extension.SchemeManager; import run.halo.app.plugin.BasePlugin; /** @@ -11,10 +10,10 @@ */ @Component public class CommentWidgetPlugin extends BasePlugin { - public CommentWidgetPlugin(PluginWrapper wrapper) { super(wrapper); } + @Override public void start() { } diff --git a/src/main/java/run/halo/comment/widget/DefaultCommentWidget.java b/src/main/java/run/halo/comment/widget/DefaultCommentWidget.java index 04501e7..9dd7948 100644 --- a/src/main/java/run/halo/comment/widget/DefaultCommentWidget.java +++ b/src/main/java/run/halo/comment/widget/DefaultCommentWidget.java @@ -66,12 +66,19 @@ private String commentHtml(IAttribute groupAttribute, IAttribute kindAttribute, var basicConfig = settingFetcher.fetch(BasicConfig.GROUP, BasicConfig.class) .orElse(new BasicConfig()); - // placeholderHelper only support string, so we need to convert boolean to string properties.setProperty("size", String.valueOf(basicConfig.getSize())); properties.setProperty("replySize", String.valueOf(basicConfig.getReplySize())); properties.setProperty("withReplies", String.valueOf(basicConfig.isWithReplies())); properties.setProperty("withReplySize", String.valueOf(basicConfig.getWithReplySize())); + var avatarConfig = settingFetcher.fetch(AvatarConfig.GROUP, AvatarConfig.class) + .orElse(new AvatarConfig()); + properties.setProperty("useAvatarProvider", String.valueOf(avatarConfig.isEnable())); + properties.setProperty("avatarProvider", String.valueOf(avatarConfig.getProvider())); + properties.setProperty("avatarProviderMirror", String.valueOf(avatarConfig.getProviderMirror())); + properties.setProperty("avatarPolicy", String.valueOf(avatarConfig.getPolicy())); + + // placeholderHelper only support string, so we need to convert boolean to string return PROPERTY_PLACEHOLDER_HELPER.replacePlaceholders("""
@@ -92,7 +103,7 @@ private String commentHtml(IAttribute groupAttribute, IAttribute kindAttribute, } @Data - static class BasicConfig { + private static class BasicConfig { public static final String GROUP = "basic"; private int size; private int replySize; @@ -100,6 +111,15 @@ static class BasicConfig { private int withReplySize; } + @Data + private static class AvatarConfig { + public static final String GROUP = "avatar"; + private boolean enable; + private String provider; + private String providerMirror; + private String policy; + } + private String domIdFrom(String group, String kind, String name) { Assert.notNull(name, "The name must not be null."); Assert.notNull(kind, "The kind must not be null."); diff --git a/src/main/resources/extensions/settings.yaml b/src/main/resources/extensions/settings.yaml index aa4be7a..dd02b5e 100644 --- a/src/main/resources/extensions/settings.yaml +++ b/src/main/resources/extensions/settings.yaml @@ -31,3 +31,38 @@ spec: key: withReplySize validation: required value: 5 + - group: avatar + label: 头像设置 + formSchema: + - $formkit: checkbox + label: 是否启用第三方头像 + name: enable + id: enable + key: enable + value: false + - $formkit: select + label: 头像服务提供者 + if: "$get(enable).value === true" + name: provider + options: + - label: 'Gravatar' + value: 'gravatar' + value: "gravatar" + - $formkit: text + label: 头像服务镜像地址 + if: "$get(enable).value === true" + name: providerMirror + value: "" + help: 使用官方源则留空, 示例:https://gravatar.com + - $formkit: select + label: 头像策略 + if: "$get(enable).value === true" + name: policy + options: + - label: '仅匿名用户' + value: 'anonymousUser' + - label: '所有用户' + value: 'allUser' + - label: '匿名&无头像用户' + value: 'noAvatarUser' + value: "anonymousUser"