diff --git a/desktop-client/room_widget.py b/desktop-client/room_widget.py
index 0a1987d..8ec0c0d 100644
--- a/desktop-client/room_widget.py
+++ b/desktop-client/room_widget.py
@@ -186,6 +186,51 @@ class RoomWidget(QWidget):
self.users_lbl = QLabel("0 watching")
self.users_lbl.setObjectName("usersLbl")
+ # Tags Container (Hidden by default)
+ self.tags_container = QFrame()
+ self.tags_container.setObjectName("tagsContainer")
+ self.tags_container.hide() # Only show when there are tags
+ tags_layout = QVBoxLayout(self.tags_container)
+ tags_layout.setContentsMargins(0, 0, 0, 10)
+
+ self.tags_header_btn = QPushButton("▼ Highlights")
+ self.tags_header_btn.setCursor(Qt.CursorShape.PointingHandCursor)
+ self.tags_header_btn.setStyleSheet("""
+ QPushButton {
+ color: #ccc;
+ font-weight: bold;
+ font-size: 12px;
+ text-align: left;
+ border: none;
+ background: transparent;
+ padding: 2px 0px;
+ }
+ QPushButton:hover {
+ color: #fff;
+ }
+ """)
+ self.tags_header_btn.clicked.connect(self.toggle_tags_panel)
+
+ self.tags_list = QTextBrowser()
+ self.tags_list.setObjectName("tagsList")
+ self.tags_list.setFixedHeight(100) # Keep it small and unobtrusive
+ self.tags_list.setOpenExternalLinks(False)
+ self.tags_list.setOpenLinks(False)
+ self.tags_list.anchorClicked.connect(self.on_chat_link_clicked)
+ self.tags_list.setStyleSheet("""
+ QTextBrowser {
+ background-color: #1e1e1e;
+ border: 1px solid #333;
+ border-radius: 4px;
+ padding: 4px;
+ color: #ddd;
+ font-size: 12px;
+ }
+ """)
+
+ tags_layout.addWidget(self.tags_header_btn)
+ tags_layout.addWidget(self.tags_list)
+
self.chat_messages = QTextBrowser()
self.chat_messages.setObjectName("chatMessages")
self.chat_messages.setOpenExternalLinks(False)
@@ -207,6 +252,7 @@ class RoomWidget(QWidget):
chat_layout.addWidget(chat_header)
chat_layout.addWidget(self.users_lbl)
+ chat_layout.addWidget(self.tags_container)
chat_layout.addWidget(self.chat_messages, 1)
chat_layout.addLayout(chat_input_layout)
@@ -215,7 +261,7 @@ class RoomWidget(QWidget):
# Prevent UI components from stealing focus (which breaks spacebar shortcuts)
for w in [self.copy_code_btn, self.leave_btn, self.play_btn, self.fullscreen_btn,
- self.chat_send_btn, self.seekbar, self.volume_slider]:
+ self.chat_send_btn, self.seekbar, self.volume_slider, self.tags_header_btn]:
w.setFocusPolicy(Qt.FocusPolicy.NoFocus)
# specifically for chat_messages, we allow clicking links, but avoid focus stealing
@@ -259,6 +305,14 @@ class RoomWidget(QWidget):
def set_room_code_display(self, text: str):
self.room_code_display.setText(f"Room: {text}")
+ def toggle_tags_panel(self):
+ if self.tags_list.isHidden():
+ self.tags_list.show()
+ self.tags_header_btn.setText("▼ Highlights")
+ else:
+ self.tags_list.hide()
+ self.tags_header_btn.setText("▶ Highlights")
+
def toggle_fullscreen(self):
top_window = self.window()
if top_window.isFullScreen():
@@ -527,6 +581,21 @@ class RoomWidget(QWidget):
new_msg = f"{safe_author}: {linked_text} {time_str}"
self.chat_messages.append(new_msg)
+
+ # Auto-extract tags added by users directly via /tag command
+ # This will catch messages that look like purely a tag command
+ # (Since we just append the output of /tag to chat)
+ match = re.match(r'^[{time_str}] {tag_text} - {author}"
+ self.tags_list.append(highlight_html)
def add_system_message(self, text: str):
new_msg = f"{text}"
@@ -569,16 +638,17 @@ class RoomWidget(QWidget):
time_arg = parts[1].lower()
tag_name = " ".join(parts[2:])
- if time_arg == "now":
- current_ms = self.vlc_player.current_time_ms
- s = max(0, current_ms) // 1000
+ target_ms = self._parse_time_arg(time_arg)
+
+ if target_ms is not None:
+ s = int(max(0, target_ms) // 1000)
if s >= 3600: time_str = f"{s//3600:02d}:{(s%3600)//60:02d}:{s%60:02d}"
else: time_str = f"{(s%3600)//60:02d}:{s%60:02d}"
+
+ msg = f"{time_str} {tag_name}".strip()
+ self.chat_message_ready.emit(msg)
else:
- time_str = time_arg
-
- msg = f"{time_str} {tag_name}".strip()
- self.chat_message_ready.emit(msg)
+ self.add_system_message("Invalid time format. Use: now, 1:23, +30s, -1m")
else:
self.add_system_message("Usage: /tag [now|time] [name]")
return
@@ -598,12 +668,16 @@ class RoomWidget(QWidget):
self.chat_message_ready.emit(text)
self.chat_input.setText("")
- def _handle_seek_command(self, arg: str) -> bool:
+ def _parse_time_arg(self, arg: str) -> float | None:
+ """Parses a time string (absolute time, offset like +30s, or 'now') and returns the target time in MS. Returns None on error."""
current_ms = self.vlc_player.current_time_ms
length_ms = self.vlc_player.get_length()
if length_ms <= 0:
- return False
+ return None
+ if arg == "now":
+ return current_ms
+
try:
target_ms = 0
if arg.startswith('+') or arg.startswith('-'):
@@ -626,9 +700,14 @@ class RoomWidget(QWidget):
elif arg.endswith('h'): target_ms = float(arg[:-1]) * 3600 * 1000
else: target_ms = float(arg) * 1000
- target_ms = max(0, min(target_ms, length_ms))
+ return max(0, min(target_ms, length_ms))
+ except ValueError:
+ return None
+
+ def _handle_seek_command(self, arg: str) -> bool:
+ target_ms = self._parse_time_arg(arg)
+ if target_ms is not None:
req = self._tell_vlc_and_expect("seek", target_ms / 1000.0)
self.sync_action_requested.emit("seek", target_ms / 1000.0, req)
return True
- except ValueError:
- return False
+ return False