Browse Source

feat: replace remove button with more menu for playlist items

in the popup menu, I'd like to add more options or actions per playlist item (video), but there isn't much space to add more buttons. replace the "remove" button with a "more" button (represented by three dots), which opens a menu. in the menu, put the "remove" button as the (currently) only option.

```git-revs
3349603  (Base revision)
404e3f4  Replace remove button with more button and dropdown menu
d78659d  Add more menu state and functionality to Alpine.js
64ac592  Add menu toggle methods after updatePlaylists
343e61c  Add more menu button and menu bindings
649f808  Add CSS for more menu styling
153ccfb  Remove old remove-btn CSS
f9eebd0  Update remove button to close menu after action
038efb5  Add document click handler to close menus
1da117d  Remove click.outside from menu binding since we handle it globally
HEAD     Save context window contents as markdown
```

codemcp-id: 1-feat-replace-remove-button-with-more-menu-for-play
Brandon Wong 1 year ago
parent
commit
8dd3a17b71
4 changed files with 207 additions and 13 deletions
  1. 80 0
      2025-05-27-codemcp-replace-remove-button-with-more-menu.md
  2. 51 5
      popup/popup.css
  3. 26 8
      popup/popup.html
  4. 50 0
      popup/popup.js

+ 80 - 0
2025-05-27-codemcp-replace-remove-button-with-more-menu.md

@@ -0,0 +1,80 @@
+# Replace Remove Button with More Menu - CodeMCP Session
+
+**Date:** 2025-05-27
+**Feature:** Replace remove button with more menu for playlist items
+**Project:** ~/personal/projects/experiments/playlist/
+
+## Summary
+
+Replaced the "remove" button in the playlist popup with a "more" button (three dots) that opens a dropdown menu containing the remove option. This provides a more space-efficient solution and allows for future expansion of additional actions per playlist item.
+
+## Chat Session Context
+
+### Initial Request
+User requested to replace the "remove" button with a "more" button represented by three dots, which opens a menu with the "remove" button as the currently only option. This was needed because there wasn't much space to add more buttons in the popup menu.
+
+### Project Context
+- Firefox browser extension managing playlists of videos (URLs)
+- Tracks video playback history
+- Uses browser storage API for data persistence
+- Popup implemented in Alpine.js with CSP mode (no JavaScript expressions in HTML attributes)
+- Background.js handles context menus and navigation
+- Content script adds video playback event listeners
+
+## Implementation Details
+
+### Files Modified
+1. `popup/popup.html` - Replaced remove button with more menu structure
+2. `popup/popup.js` - Added menu state management and Alpine.js bindings
+3. `popup/popup.css` - Added menu styling and removed old button styles
+
+### Key Changes
+
+#### HTML Structure
+```html
+<div class="more-menu-container">
+  <button x-bind="moreMenuButton" class="more-btn" title="More actions">
+    ⋯
+  </button>
+  <div class="more-menu" x-bind="moreMenu">
+    <button x-bind="removeVideoButton" class="menu-item remove-item">
+      Remove
+    </button>
+  </div>
+</div>
+```
+
+#### JavaScript Functionality
+- Added `openMenus` Set to track menu states
+- Menu management methods: `getMenuId()`, `isMenuOpen()`, `toggleMenu()`, `closeAllMenus()`
+- Alpine.js bindings for menu button and dropdown
+- Document click handler for closing menus when clicking outside
+- Updated remove handler to close menu after action
+
+#### CSS Styling
+- Positioned dropdown menu with proper z-index
+- Added hover effects and visual feedback
+- Removed old remove button styles
+- Red styling for remove action to indicate destructive action
+
+### Technical Considerations
+- Follows Alpine.js CSP mode requirements
+- Maintains existing code patterns and conventions
+- Ensures only one menu open at a time
+- Proper event handling to prevent conflicts
+- Expandable design for future menu items
+
+## Git Commits Made
+- Initial: `feat: replace remove button with more menu for playlist items`
+- Multiple amendments during development process
+- Final commit hash: `1da117d`
+
+## Outcome
+Successfully created a space-efficient, expandable menu system that:
+- Reduces visual clutter in the popup
+- Provides familiar UX pattern (three dots = more actions)
+- Allows easy addition of future actions
+- Maintains all existing functionality
+- Follows project coding standards
+
+The implementation is ready for use and future expansion with additional menu items.

+ 51 - 5
popup/popup.css

@@ -79,11 +79,6 @@ button {
   font-size: 12px;
 }
 
-.remove-btn {
-  background-color: #f44336;
-  color: white;
-}
-
 button:hover {
   opacity: 0.9;
 }
@@ -110,3 +105,54 @@ button:hover {
 .done-video .video-title {
   text-decoration: line-through;
 }
+
+/* More menu styles */
+.more-menu-container {
+  position: relative;
+}
+
+.more-btn {
+  background-color: #666;
+  color: white;
+  font-size: 14px;
+  padding: 4px 8px;
+  border-radius: 4px;
+  font-weight: bold;
+}
+
+.more-menu {
+  position: absolute;
+  right: 0;
+  top: 100%;
+  background: white;
+  border: 1px solid #ddd;
+  border-radius: 4px;
+  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
+  z-index: 1000;
+  min-width: 100px;
+  margin-top: 4px;
+}
+
+.menu-item {
+  display: block;
+  width: 100%;
+  padding: 8px 12px;
+  border: none;
+  background: none;
+  text-align: left;
+  cursor: pointer;
+  font-size: 12px;
+  border-radius: 0;
+}
+
+.menu-item:hover {
+  background-color: #f5f5f5;
+}
+
+.remove-item {
+  color: #f44336;
+}
+
+.remove-item:hover {
+  background-color: #ffeaea;
+}

+ 26 - 8
popup/popup.html

@@ -55,14 +55,32 @@
                     >
                     </button>
-                    <button
-                      :data-playlist-name="playlistName"
-                      :data-playlist-index="index"
-                      x-bind="removeVideoButton"
-                      class="remove-btn"
-                    >
-                      X
-                    </button>
+                    <div class="more-menu-container">
+                      <button
+                        :data-playlist-name="playlistName"
+                        :data-playlist-index="index"
+                        x-bind="moreMenuButton"
+                        class="more-btn"
+                        title="More actions"
+                      >
+                        ⋯
+                      </button>
+                      <div
+                        class="more-menu"
+                        :data-playlist-name="playlistName"
+                        :data-playlist-index="index"
+                        x-bind="moreMenu"
+                      >
+                        <button
+                          :data-playlist-name="playlistName"
+                          :data-playlist-index="index"
+                          x-bind="removeVideoButton"
+                          class="menu-item remove-item"
+                        >
+                          Remove
+                        </button>
+                      </div>
+                    </div>
                   </div>
                 </div>
               </template>

+ 50 - 0
popup/popup.js

@@ -17,9 +17,17 @@ document.addEventListener("alpine:init", () => {
   Alpine.data("playlistManager", () => ({
     playlists: {},
     currentIndices: {},
+    openMenus: new Set(), // Track which menus are open
 
     init() {
       this.loadPlaylists();
+      // Add document click handler to close menus
+      document.addEventListener('click', (e) => {
+        // If click is not on a more button or menu, close all menus
+        if (!e.target.closest('.more-menu-container')) {
+          this.closeAllMenus();
+        }
+      });
     },
 
     async loadPlaylists() {
@@ -227,6 +235,7 @@ document.addEventListener("alpine:init", () => {
           this.$el.dataset.playlistName,
           this.$el.dataset.playlistIndex,
         );
+        this.closeAllMenus();
       },
     },
 
@@ -367,5 +376,46 @@ document.addEventListener("alpine:init", () => {
         alert("Error importing playlists: " + error.message);
       }
     },
+
+    getMenuId(playlistName, index) {
+      return `${playlistName}-${index}`;
+    },
+
+    isMenuOpen(playlistName, index) {
+      return this.openMenus.has(this.getMenuId(playlistName, index));
+    },
+
+    toggleMenu(playlistName, index) {
+      const menuId = this.getMenuId(playlistName, index);
+      if (this.openMenus.has(menuId)) {
+        this.openMenus.delete(menuId);
+      } else {
+        // Close all other menus first
+        this.openMenus.clear();
+        this.openMenus.add(menuId);
+      }
+    },
+
+    closeAllMenus() {
+      this.openMenus.clear();
+    },
+
+    moreMenuButton: {
+      ["@click.stop"]() {
+        this.toggleMenu(
+          this.$el.dataset.playlistName,
+          parseInt(this.$el.dataset.playlistIndex)
+        );
+      },
+    },
+
+    moreMenu: {
+      ["x-show"]() {
+        return this.isMenuOpen(
+          this.$el.dataset.playlistName,
+          parseInt(this.$el.dataset.playlistIndex)
+        );
+      },
+    },
   }));
 });