diff --git a/frontend-vue/src/components/chat/MessageItem.vue b/frontend-vue/src/components/chat/MessageItem.vue index 9546f05..d3e6292 100644 --- a/frontend-vue/src/components/chat/MessageItem.vue +++ b/frontend-vue/src/components/chat/MessageItem.vue @@ -152,126 +152,77 @@ const handleKeydown = (event: KeyboardEvent) => { \ No newline at end of file diff --git a/frontend-vue/src/components/sidebar/ChannelList.vue b/frontend-vue/src/components/sidebar/ChannelList.vue index 8a3cba7..a75891e 100644 --- a/frontend-vue/src/components/sidebar/ChannelList.vue +++ b/frontend-vue/src/components/sidebar/ChannelList.vue @@ -8,6 +8,7 @@ :is-active="channel.id === currentChannelId" :unread-count="unreadCounts[channel.id]" @select="$emit('select-channel', $event)" + @info="$emit('channel-info', $event)" /> @@ -27,6 +28,7 @@ defineProps() defineEmits<{ 'select-channel': [channelId: number] + 'channel-info': [channel: Channel] }>() diff --git a/frontend-vue/src/components/sidebar/ChannelListItem.vue b/frontend-vue/src/components/sidebar/ChannelListItem.vue index 0e5f07b..9a1ee4c 100644 --- a/frontend-vue/src/components/sidebar/ChannelListItem.vue +++ b/frontend-vue/src/components/sidebar/ChannelListItem.vue @@ -43,6 +43,7 @@ defineProps() defineEmits<{ select: [channelId: number] + info: [channel: Channel] }>() @@ -52,6 +53,12 @@ defineEmits<{ margin: 0; } +.channel-wrapper { + display: flex; + align-items: center; + gap: 0.5rem; +} + .channel-button { display: flex; align-items: center; @@ -117,6 +124,35 @@ defineEmits<{ color: #3b82f6; } +.channel-info-button { + display: flex; + align-items: center; + justify-content: center; + width: 2rem; + height: 2rem; + padding: 0; + background: none; + border: none; + border-radius: 4px; + cursor: pointer; + font-size: 1rem; + opacity: 0.6; + transition: all 0.2s ease; + flex-shrink: 0; +} + +.channel-info-button:hover { + opacity: 1; + background: rgba(0, 0, 0, 0.05); +} + +.channel-info-button:focus { + outline: none; + background: rgba(59, 130, 246, 0.1); + box-shadow: 0 0 0 2px rgba(59, 130, 246, 0.2); + opacity: 1; +} + /* Dark mode */ @media (prefers-color-scheme: dark) { .channel-button { @@ -142,5 +178,14 @@ defineEmits<{ .channel-item--active .channel-button:hover { background: #2563eb; } + + .channel-info-button:hover { + background: rgba(255, 255, 255, 0.1); + } + + .channel-info-button:focus { + background: rgba(96, 165, 250, 0.1); + box-shadow: 0 0 0 2px rgba(96, 165, 250, 0.2); + } } \ No newline at end of file diff --git a/frontend-vue/src/components/sidebar/Sidebar.vue b/frontend-vue/src/components/sidebar/Sidebar.vue index 77daab7..a120e34 100644 --- a/frontend-vue/src/components/sidebar/Sidebar.vue +++ b/frontend-vue/src/components/sidebar/Sidebar.vue @@ -18,6 +18,7 @@ :current-channel-id="currentChannelId" :unread-counts="unreadCounts" @select-channel="$emit('select-channel', $event)" + @channel-info="$emit('channel-info', $event)" /> @@ -50,6 +51,7 @@ defineProps() defineEmits<{ 'create-channel': [] 'select-channel': [channelId: number] + 'channel-info': [channel: Channel] 'settings': [] }>() diff --git a/frontend-vue/src/stores/app.ts b/frontend-vue/src/stores/app.ts index abb33d2..37e6a43 100644 --- a/frontend-vue/src/stores/app.ts +++ b/frontend-vue/src/stores/app.ts @@ -112,7 +112,7 @@ export const useAppStore = defineStore('app', () => { const updateSettings = async (newSettings: Partial) => { settings.value = { ...settings.value, ...newSettings } - await set('app_settings', settings.value) + await set('app_settings', JSON.parse(JSON.stringify(settings.value))) } const loadState = async () => { diff --git a/frontend-vue/src/style.css b/frontend-vue/src/style.css index 72458b6..399a4b2 100644 --- a/frontend-vue/src/style.css +++ b/frontend-vue/src/style.css @@ -1,18 +1,4 @@ -:root { - font-family: Inter, system-ui, Avenir, Helvetica, Arial, sans-serif; - line-height: 1.5; - font-weight: 400; - - color-scheme: light dark; - color: rgba(255, 255, 255, 0.87); - background-color: #242424; - - font-synthesis: none; - text-rendering: optimizeLegibility; - -webkit-font-smoothing: antialiased; - -moz-osx-font-smoothing: grayscale; -} - +/* Minimal reset styles only */ * { box-sizing: border-box; } @@ -23,6 +9,13 @@ body { width: 100vw; height: 100vh; overflow: hidden; + font-family: Inter, system-ui, Avenir, Helvetica, Arial, sans-serif; + line-height: 1.5; + font-weight: 400; + font-synthesis: none; + text-rendering: optimizeLegibility; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; } #app { @@ -33,61 +26,6 @@ body { overflow: hidden; } -/* Button styles */ -button { - border-radius: 8px; - border: 1px solid transparent; - padding: 0.6em 1.2em; - font-size: 1em; - font-weight: 500; - font-family: inherit; - background-color: #1a1a1a; - color: inherit; - cursor: pointer; - transition: border-color 0.25s; - outline: none; -} - -button:hover { - border-color: #646cff; -} - -button:focus, -button:focus-visible { - outline: 2px solid #646cff; - outline-offset: 2px; -} - -button:disabled { - opacity: 0.6; - cursor: not-allowed; -} - -/* Input styles */ -input, textarea { - border-radius: 8px; - border: 1px solid #3a3a3a; - padding: 0.6em 1.2em; - font-size: 1em; - font-family: inherit; - background-color: #2a2a2a; - color: inherit; - outline: none; -} - -input:focus, textarea:focus { - border-color: #646cff; - outline: 2px solid #646cff; - outline-offset: 2px; -} - -/* List styles */ -ul { - list-style: none; - padding: 0; - margin: 0; -} - /* Accessibility helpers */ .sr-only { position: absolute; @@ -99,27 +37,4 @@ ul { clip: rect(0, 0, 0, 0); white-space: nowrap; border: 0; -} - -/* Focus indicators */ -.focus-visible { - outline: 2px solid #646cff; - outline-offset: 2px; -} - -/* Light mode */ -@media (prefers-color-scheme: light) { - :root { - color: #213547; - background-color: #ffffff; - } - - button { - background-color: #f9f9f9; - } - - input, textarea { - background-color: #ffffff; - border-color: #d1d5db; - } } \ No newline at end of file diff --git a/frontend-vue/src/views/MainView.vue b/frontend-vue/src/views/MainView.vue index 17af2fd..e1166bb 100644 --- a/frontend-vue/src/views/MainView.vue +++ b/frontend-vue/src/views/MainView.vue @@ -7,6 +7,7 @@ :unread-counts="unreadCounts" @create-channel="showChannelDialog = true" @select-channel="selectChannel" + @channel-info="handleChannelInfo" @settings="showSettings = true" /> @@ -80,6 +81,14 @@ @sent="handleCameraSent" /> + + + + @@ -108,9 +117,10 @@ import SearchDialog from '@/components/dialogs/SearchDialog.vue' import FileUploadDialog from '@/components/dialogs/FileUploadDialog.vue' import VoiceRecordingDialog from '@/components/dialogs/VoiceRecordingDialog.vue' import CameraCaptureDialog from '@/components/dialogs/CameraCaptureDialog.vue' +import ChannelInfoDialog from '@/components/dialogs/ChannelInfoDialog.vue' // Types -import type { ExtendedMessage } from '@/types' +import type { ExtendedMessage, Channel } from '@/types' const router = useRouter() const appStore = useAppStore() @@ -130,12 +140,16 @@ const messageInput = ref() // Dialog states const showChannelDialog = ref(false) +const showChannelInfoDialog = ref(false) const showSettings = ref(false) const showSearchDialog = ref(false) const showFileDialog = ref(false) const showVoiceDialog = ref(false) const showCameraDialog = ref(false) +// Channel info state +const selectedChannelForInfo = ref(null) + // Mock unread counts (implement real logic later) const unreadCounts = ref>({}) @@ -362,6 +376,11 @@ const handleChannelCreated = async (channelId: number) => { await selectChannel(channelId) } +const handleChannelInfo = (channel: Channel) => { + selectedChannelForInfo.value = channel + showChannelInfoDialog.value = true +} + const isUnsentMessage = (messageId: string | number): boolean => { return typeof messageId === 'string' && messageId.startsWith('unsent_') }