diff --git a/apps/web/public/channels/feishu.png b/apps/web/public/channels/feishu.png deleted file mode 100644 index 73a55287..00000000 Binary files a/apps/web/public/channels/feishu.png and /dev/null differ diff --git a/apps/web/public/channels/matrix.svg b/apps/web/public/channels/matrix.svg deleted file mode 100644 index 979fc713..00000000 --- a/apps/web/public/channels/matrix.svg +++ /dev/null @@ -1,5 +0,0 @@ - - - - - diff --git a/apps/web/public/channels/telegram.webp b/apps/web/public/channels/telegram.webp deleted file mode 100644 index 3afc19e3..00000000 Binary files a/apps/web/public/channels/telegram.webp and /dev/null differ diff --git a/apps/web/src/components/channel-icon/index.vue b/apps/web/src/components/channel-icon/index.vue index bada0faf..00da98a1 100644 --- a/apps/web/src/components/channel-icon/index.vue +++ b/apps/web/src/components/channel-icon/index.vue @@ -7,6 +7,8 @@ /> {{ fallback }} @@ -24,7 +26,9 @@ import { Wechatoa, Wecom, Matrix, + Misskey, } from '@memohai/icon' +import { channelIconFallbackText } from '@/utils/channel-icon-fallback' const channelIcons: Record = { qq: Qq, @@ -37,7 +41,7 @@ const channelIcons: Record = { wechatoa: Wechatoa, wecom: Wecom, matrix: Matrix, - // misskey: Misskey, + misskey: Misskey, dingtalk: Dingtalk, } @@ -50,11 +54,19 @@ const props = withDefaults(defineProps<{ defineOptions({ inheritAttrs: false }) +const normalizedChannel = computed(() => + props.channel.trim().toLowerCase(), +) + const iconComponent = computed(() => - channelIcons[props.channel], + channelIcons[normalizedChannel.value], ) const fallback = computed(() => - props.channel.slice(0, 2).toUpperCase(), + channelIconFallbackText(props.channel), ) + +const fallbackStyle = computed(() => ({ + fontSize: typeof props.size === 'number' ? `${props.size}px` : props.size, +})) diff --git a/apps/web/src/utils/channel-icon-fallback.test.ts b/apps/web/src/utils/channel-icon-fallback.test.ts new file mode 100644 index 00000000..a6dd1db8 --- /dev/null +++ b/apps/web/src/utils/channel-icon-fallback.test.ts @@ -0,0 +1,29 @@ +import { describe, expect, it } from 'vitest' +import { channelIconFallbackText } from './channel-icon-fallback' + +describe('channelIconFallbackText', () => { + it('returns empty text for empty channel keys', () => { + expect(channelIconFallbackText('')).toBe('') + expect(channelIconFallbackText(' ')).toBe('') + }) + + it('uses explicit built-in fallbacks for non-brand channels', () => { + expect(channelIconFallbackText('local')).toBe('LC') + expect(channelIconFallbackText('cli')).toBe('CLI') + expect(channelIconFallbackText('web')).toBe('Web') + }) + + it('normalizes casing and whitespace', () => { + expect(channelIconFallbackText(' Discord ')).toBe('DI') + }) + + it('creates initials for multi-part unknown channels', () => { + expect(channelIconFallbackText('custom-bridge')).toBe('CB') + expect(channelIconFallbackText('foo_bar')).toBe('FB') + }) + + it('uses the first two alphanumeric characters for simple unknown channels', () => { + expect(channelIconFallbackText('misskey')).toBe('MI') + expect(channelIconFallbackText('qq')).toBe('QQ') + }) +}) diff --git a/apps/web/src/utils/channel-icon-fallback.ts b/apps/web/src/utils/channel-icon-fallback.ts new file mode 100644 index 00000000..f5f4458a --- /dev/null +++ b/apps/web/src/utils/channel-icon-fallback.ts @@ -0,0 +1,20 @@ +const explicitChannelFallbacks: Record = { + cli: 'CLI', + local: 'LC', + web: 'Web', +} + +export function channelIconFallbackText(channel: string): string { + const normalized = channel.trim().toLowerCase() + if (!normalized) return '' + + const explicit = explicitChannelFallbacks[normalized] + if (explicit) return explicit + + const parts = normalized.match(/[a-z0-9]+/gi) ?? [] + if (parts.length > 1) { + return parts.slice(0, 2).map((part) => part[0]?.toUpperCase() ?? '').join('') + } + + return normalized.replace(/[^a-z0-9]/gi, '').slice(0, 2).toUpperCase() +} diff --git a/internal/handlers/file_embed.go b/internal/handlers/file_embed.go index 1f86770c..1656a7c4 100644 --- a/internal/handlers/file_embed.go +++ b/internal/handlers/file_embed.go @@ -23,9 +23,7 @@ var embeddedStaticRoutes = map[string]struct { assetPath string contentType string }{ - "/logo.png": {assetPath: "logo.png", contentType: "image/png"}, - "/channels/telegram.webp": {assetPath: "channels/telegram.webp", contentType: "image/webp"}, - "/channels/feishu.png": {assetPath: "channels/feishu.png", contentType: "image/png"}, + "/logo.png": {assetPath: "logo.png", contentType: "image/png"}, } func NewEmbeddedWebHandler(log *slog.Logger) (*EmbeddedWebHandler, error) { diff --git a/packages/icons/src/index.ts b/packages/icons/src/index.ts index bc5b59e3..adc7ba00 100644 --- a/packages/icons/src/index.ts +++ b/packages/icons/src/index.ts @@ -51,6 +51,7 @@ export { default as Microsoft } from './icons/Microsoft.vue' export { default as MicrosoftColor } from './icons/MicrosoftColor.vue' export { default as Minimax } from './icons/Minimax.vue' export { default as MinimaxColor } from './icons/MinimaxColor.vue' +export { default as Misskey } from './icons/Misskey.vue' export { default as Mistral } from './icons/Mistral.vue' export { default as MistralColor } from './icons/MistralColor.vue' export { default as Moonshot } from './icons/Moonshot.vue'