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'