""" keyboards/inline.py — All inline keyboard builders for the bot UI. Design principles: - Folders use a 2-column grid layout. - Every nested menu has Back and Home buttons. - Pagination is included for large lists (> ITEMS_PER_PAGE). - Callback data uses a structured format: "action:param1:param2" """ from aiogram.types import InlineKeyboardMarkup, InlineKeyboardButton from aiogram.utils.keyboard import InlineKeyboardBuilder from config import config def _back_home_row(back_callback: str, home_callback: str = "home") -> list[InlineKeyboardButton]: """Returns a standard [🔙 Back] [🏠 Home] button row.""" return [ InlineKeyboardButton(text="🔙 Back", callback_data=back_callback), InlineKeyboardButton(text="🏠 Home", callback_data=home_callback), ] # ─────────────────────────── MAIN MENUS ─────────────────────────── def main_menu_user() -> InlineKeyboardMarkup: """The main menu for regular users.""" builder = InlineKeyboardBuilder() builder.row(InlineKeyboardButton(text="📚 Browse Knowledge Base", callback_data="browse:root:0")) builder.row(InlineKeyboardButton(text="â„šī¸ About", callback_data="about")) return builder.as_markup() def main_menu_admin(view_as_user: bool = False) -> InlineKeyboardMarkup: """ Admin main menu. Includes a toggle to preview as a regular user. When view_as_user=True, management buttons are hidden. """ builder = InlineKeyboardBuilder() builder.row(InlineKeyboardButton(text="📚 Browse Knowledge Base", callback_data="browse:root:0")) if not view_as_user: # Show full admin controls builder.row(InlineKeyboardButton(text="📁 Manage Folders", callback_data="manage:folders:0")) builder.row(InlineKeyboardButton(text="📊 Admin Dashboard", callback_data="admin:dashboard")) builder.row(InlineKeyboardButton(text="đŸ‘ī¸ View as User", callback_data="toggle:user_view")) else: # Preview mode — only show the return button builder.row(InlineKeyboardButton(text="đŸ‘ī¸ Return to Admin View", callback_data="toggle:admin_view")) return builder.as_markup() def main_menu_owner(view_as_user: bool = False) -> InlineKeyboardMarkup: """Owner main menu — extends admin menu with owner-only controls.""" builder = InlineKeyboardBuilder() builder.row(InlineKeyboardButton(text="📚 Browse Knowledge Base", callback_data="browse:root:0")) if not view_as_user: builder.row(InlineKeyboardButton(text="📁 Manage Folders", callback_data="manage:folders:0")) builder.row( InlineKeyboardButton(text="📊 Admin Dashboard", callback_data="admin:dashboard"), InlineKeyboardButton(text="👑 Owner Dashboard", callback_data="owner:dashboard"), ) builder.row( InlineKeyboardButton(text="đŸ‘Ĩ Manage Admins", callback_data="owner:admins"), InlineKeyboardButton(text="đŸ‘ī¸ View as User", callback_data="toggle:user_view"), ) else: builder.row(InlineKeyboardButton(text="đŸ‘ī¸ Return to Admin View", callback_data="toggle:admin_view")) return builder.as_markup() # ─────────────────────────── FOLDER BROWSING ─────────────────────────── def folder_list_keyboard( folders: list, items: list, parent_id: int | None, page: int, is_admin: bool = False, is_owner: bool = False, ) -> InlineKeyboardMarkup: """ Builds a paginated keyboard for browsing folders and items inside a folder. Folders appear in a 2-column grid, items appear as single rows below. """ builder = InlineKeyboardBuilder() per_page = config.ITEMS_PER_PAGE total_content = folders + items # Combined for pagination total_pages = max(1, (len(total_content) + per_page - 1) // per_page) page_content = total_content[page * per_page: (page + 1) * per_page] # Separate paginated content back into folders and items page_folders = [c for c in page_content if isinstance(c, type(folders[0])) if folders] if folders else [] page_items = [c for c in page_content if hasattr(c, 'content_type')] # Folders in 2-column grid folder_buttons = [ InlineKeyboardButton( text=f"{f.emoji} {f.name}", callback_data=f"browse:folder:{f.id}:0" ) for f in page_folders ] # Add folder buttons in pairs (2 per row) for i in range(0, len(folder_buttons), 2): row = folder_buttons[i:i+2] builder.row(*row) # Items in single rows content_type_emoji = { "photo": "đŸ–ŧī¸", "video": "đŸŽŦ", "document": "📄", "audio": "đŸŽĩ", "link": "🔗", "text": "📝" } for item in page_items: emoji = content_type_emoji.get(item.content_type, "📄") builder.row(InlineKeyboardButton( text=f"{emoji} {item.title}", callback_data=f"view:item:{item.id}" )) # Admin/Owner action buttons (if not in user-view mode) if is_admin or is_owner: folder_param = parent_id if parent_id else "root" builder.row( InlineKeyboardButton(text="➕ Add New Item", callback_data=f"add:start:{folder_param}"), InlineKeyboardButton(text="âœī¸ Edit Folder", callback_data=f"edit:folder:{parent_id}" if parent_id else "home"), ) if parent_id: # Can only delete non-root folders builder.row(InlineKeyboardButton( text="đŸ—‘ī¸ Delete Folder", callback_data=f"delete:folder:{parent_id}" )) # Pagination row nav_buttons = [] if page > 0: nav_buttons.append(InlineKeyboardButton(text="âŦ…ī¸ Prev", callback_data=f"browse:folder:{parent_id or 'root'}:{page-1}")) if page < total_pages - 1: nav_buttons.append(InlineKeyboardButton(text="âžĄī¸ Next", callback_data=f"browse:folder:{parent_id or 'root'}:{page+1}")) if nav_buttons: builder.row(*nav_buttons) # Back and Home navigation if parent_id is not None: builder.row(*_back_home_row(back_callback="browse:root:0")) else: builder.row(InlineKeyboardButton(text="🏠 Home", callback_data="home")) return builder.as_markup() def item_view_keyboard(item_id: int, folder_id: int, is_admin: bool = False) -> InlineKeyboardMarkup: """Keyboard shown when a user views a specific item.""" builder = InlineKeyboardBuilder() if is_admin: builder.row( InlineKeyboardButton(text="âœī¸ Edit", callback_data=f"edit:item:{item_id}"), InlineKeyboardButton(text="đŸ—‘ī¸ Delete", callback_data=f"delete:item:{item_id}"), ) builder.row(*_back_home_row(back_callback=f"browse:folder:{folder_id}:0")) return builder.as_markup() # ─────────────────────────── ADMIN MANAGEMENT ─────────────────────────── def add_item_type_keyboard(folder_id: int | str) -> InlineKeyboardMarkup: """Asks whether to add a Folder or Content item.""" builder = InlineKeyboardBuilder() builder.row( InlineKeyboardButton(text="📁 New Folder", callback_data=f"add:folder:{folder_id}"), InlineKeyboardButton(text="📄 New Content", callback_data=f"add:content:{folder_id}"), ) builder.row(InlineKeyboardButton(text="🔙 Cancel", callback_data="home")) return builder.as_markup() def content_type_keyboard(folder_id: int | str) -> InlineKeyboardMarkup: """Lets admin choose the type of content to add.""" builder = InlineKeyboardBuilder() builder.row( InlineKeyboardButton(text="đŸ–ŧī¸ Photo", callback_data=f"ctype:photo:{folder_id}"), InlineKeyboardButton(text="đŸŽŦ Video", callback_data=f"ctype:video:{folder_id}"), ) builder.row( InlineKeyboardButton(text="📄 Document", callback_data=f"ctype:document:{folder_id}"), InlineKeyboardButton(text="đŸŽĩ Audio", callback_data=f"ctype:audio:{folder_id}"), ) builder.row( InlineKeyboardButton(text="🔗 Link", callback_data=f"ctype:link:{folder_id}"), InlineKeyboardButton(text="📝 Text", callback_data=f"ctype:text:{folder_id}"), ) builder.row(InlineKeyboardButton(text="🔙 Cancel", callback_data="home")) return builder.as_markup() def confirm_delete_keyboard(entity_type: str, entity_id: int) -> InlineKeyboardMarkup: """Confirmation dialog before deleting a folder or item.""" builder = InlineKeyboardBuilder() builder.row( InlineKeyboardButton( text="✅ Yes, Delete", callback_data=f"confirm_delete:{entity_type}:{entity_id}" ), InlineKeyboardButton(text="❌ Cancel", callback_data="home"), ) return builder.as_markup() def admin_list_keyboard(admins: list, page: int = 0) -> InlineKeyboardMarkup: """Lists all admins with a remove button for each.""" builder = InlineKeyboardBuilder() per_page = 5 total_pages = max(1, (len(admins) + per_page - 1) // per_page) page_admins = admins[page * per_page: (page + 1) * per_page] for admin in page_admins: name = admin.full_name or admin.username or f"ID:{admin.user_id}" builder.row(InlineKeyboardButton( text=f"👤 {name}", callback_data=f"owner:admin_info:{admin.user_id}" )) # Pagination nav = [] if page > 0: nav.append(InlineKeyboardButton(text="âŦ…ī¸", callback_data=f"owner:admins_page:{page-1}")) if page < total_pages - 1: nav.append(InlineKeyboardButton(text="âžĄī¸", callback_data=f"owner:admins_page:{page+1}")) if nav: builder.row(*nav) builder.row(InlineKeyboardButton(text="➕ Add Admin", callback_data="owner:add_admin")) builder.row(InlineKeyboardButton(text="🏠 Home", callback_data="home")) return builder.as_markup() def admin_info_keyboard(admin_user_id: int) -> InlineKeyboardMarkup: """Shows options for a specific admin.""" builder = InlineKeyboardBuilder() builder.row(InlineKeyboardButton( text="đŸ—‘ī¸ Remove Admin", callback_data=f"owner:remove_admin:{admin_user_id}" )) builder.row(*_back_home_row(back_callback="owner:admins")) return builder.as_markup() def back_home_keyboard(back_callback: str = "home") -> InlineKeyboardMarkup: """Simple back + home keyboard for generic use.""" builder = InlineKeyboardBuilder() builder.row(*_back_home_row(back_callback=back_callback)) return builder.as_markup()