Browse Source

feat: add move to top/bottom options in video menu

in the main view of the popup menu, in the three-dot menu next to each video, add an option to "move to top", which will move that video in its playlist to the "top", which (as a reminder) is defined as "after the last video marked as 'done' continuously from the beginning of the playlist". also add an option to "move to bottom", which will move the video to the end of its playlist. obviously, disable these options if the video in question is already at the top or the bottom respectively.

```git-revs
a045a7b  (Base revision)
777bc7c  Add moveVideoToTop and moveVideoToBottom methods
4c7a298  Add moveToTopButton and moveToBottomButton bindings
d10db82  Add move to top and move to bottom buttons in main view menu
1f38c03  Add move to top and move to bottom buttons in playlist view menu
HEAD     Create context log for the move to top/bottom feature
```

codemcp-id: 18-feat-add-move-to-top-bottom-options-in-video-menu
Brandon Wong 7 months ago
parent
commit
a11f2914cd
3 changed files with 179 additions and 0 deletions
  1. 52 0
      2025-11-07-codemcp-add-move-to-top-bottom.md
  2. 32 0
      popup/popup.html
  3. 95 0
      popup/popup.js

+ 52 - 0
2025-11-07-codemcp-add-move-to-top-bottom.md

@@ -0,0 +1,52 @@
+# Add Move to Top/Bottom Options in Video Menu
+
+**Date**: 2025-11-07
+**Feature**: Add "Move to Top" and "Move to Bottom" options to the three-dot menu for videos
+
+## User Request
+
+In the main view of the popup menu, in the three-dot menu next to each video, add an option to "move to top", which will move that video in its playlist to the "top", which (as a reminder) is defined as "after the last video marked as 'done' continuously from the beginning of the playlist". Also add an option to "move to bottom", which will move the video to the end of its playlist. Obviously, disable these options if the video in question is already at the top or the bottom respectively.
+
+## Implementation
+
+### Changes Made
+
+1. **popup/popup.js** - Added three new methods:
+   - `moveVideoToTop(playlistName, index)`: Moves a video to the top position (after the last continuously done videos)
+   - `moveVideoToBottom(playlistName, index)`: Moves a video to the bottom of the playlist
+   - `findTopPosition(playlistName)`: Helper method that finds the "top" position by finding the first non-done video
+
+2. **popup/popup.js** - Added two new button bindings:
+   - `moveToTopButton`: Binding with click handler and disabled state (disabled when video is already at top)
+   - `moveToBottomButton`: Binding with click handler and disabled state (disabled when video is already at bottom)
+
+3. **popup/popup.html** - Added buttons to both views:
+   - Main playlists view: Added "Move to Top" and "Move to Bottom" buttons in the more-menu
+   - Individual playlist view: Added the same buttons in the more-menu
+
+### Key Implementation Details
+
+- The "top" position is determined by finding the first video that doesn't have `status === "done"`
+- If all videos are marked as done, the top position is at the end of the playlist
+- Both operations use `splice` to remove the video from its current position and insert it at the new position
+- The buttons are disabled when:
+  - "Move to Top" is disabled when the video is already at the top position
+  - "Move to Bottom" is disabled when the video is already at the last position
+- Both operations close the menu after execution (via `closeAllMenus()`)
+- Alpine.js CSP compliance is maintained - no JavaScript expressions in HTML attributes
+
+### Testing Notes
+
+To test this feature:
+1. Create a playlist with multiple videos
+2. Mark some videos as "done" from the beginning
+3. Open the three-dot menu for a video that's not at the top
+4. Click "Move to Top" - it should move after the last done video
+5. Open the menu for a video in the middle
+6. Click "Move to Bottom" - it should move to the end
+7. Verify that the buttons are disabled appropriately when videos are already at top/bottom
+
+## Files Modified
+
+- `/Users/yellowdig/personal/projects/playlist/popup/popup.js`
+- `/Users/yellowdig/personal/projects/playlist/popup/popup.html`

+ 32 - 0
popup/popup.html

@@ -110,6 +110,22 @@
                         :data-playlist-index="video.originalIndex"
                         x-bind="moreMenu"
                       >
+                        <button
+                          :data-playlist-name="playlistData.name"
+                          :data-playlist-index="video.originalIndex"
+                          x-bind="moveToTopButton"
+                          class="menu-item move-to-top-item"
+                        >
+                          Move to Top
+                        </button>
+                        <button
+                          :data-playlist-name="playlistData.name"
+                          :data-playlist-index="video.originalIndex"
+                          x-bind="moveToBottomButton"
+                          class="menu-item move-to-bottom-item"
+                        >
+                          Move to Bottom
+                        </button>
                         <button
                           :data-playlist-name="playlistData.name"
                           :data-playlist-index="video.originalIndex"
@@ -368,6 +384,22 @@
                     :data-playlist-index="index"
                     x-bind="moreMenu"
                   >
+                    <button
+                      :data-playlist-name="currentPlaylistName"
+                      :data-playlist-index="index"
+                      x-bind="moveToTopButton"
+                      class="menu-item move-to-top-item"
+                    >
+                      Move to Top
+                    </button>
+                    <button
+                      :data-playlist-name="currentPlaylistName"
+                      :data-playlist-index="index"
+                      x-bind="moveToBottomButton"
+                      class="menu-item move-to-bottom-item"
+                    >
+                      Move to Bottom
+                    </button>
                     <button
                       :data-playlist-name="currentPlaylistName"
                       :data-playlist-index="index"

+ 95 - 0
popup/popup.js

@@ -607,6 +607,69 @@ document.addEventListener("alpine:init", () => {
       }
     },
 
+    async moveVideoToTop(playlistName, index) {
+      const playlists = JSON.parse(JSON.stringify(this.playlists));
+      const playlist = [...playlists[playlistName]];
+
+      const topPosition = this.findTopPosition(playlistName);
+
+      if (index === topPosition) return;
+
+      const video = playlist.splice(index, 1)[0];
+      playlist.splice(topPosition, 0, video);
+
+      const updatedPlaylists = {
+        ...playlists,
+        [playlistName]: playlist,
+      };
+
+      try {
+        await browser.storage.local.set({ playlists: updatedPlaylists });
+        this.playlists = updatedPlaylists;
+        this.updatePlaylistsForDisplay();
+        this.updateCurrentPlaylistVideos();
+      } catch (error) {
+        console.error("Error moving video to top:", error);
+      }
+    },
+
+    async moveVideoToBottom(playlistName, index) {
+      const playlists = JSON.parse(JSON.stringify(this.playlists));
+      const playlist = [...playlists[playlistName]];
+
+      if (index === playlist.length - 1) return;
+
+      const video = playlist.splice(index, 1)[0];
+      playlist.push(video);
+
+      const updatedPlaylists = {
+        ...playlists,
+        [playlistName]: playlist,
+      };
+
+      try {
+        await browser.storage.local.set({ playlists: updatedPlaylists });
+        this.playlists = updatedPlaylists;
+        this.updatePlaylistsForDisplay();
+        this.updateCurrentPlaylistVideos();
+      } catch (error) {
+        console.error("Error moving video to bottom:", error);
+      }
+    },
+
+    findTopPosition(playlistName) {
+      const playlist = this.playlists[playlistName];
+      if (!playlist) return 0;
+
+      for (let i = 0; i < playlist.length; i++) {
+        if (playlist[i].status !== "done") {
+          return i;
+        }
+      }
+
+      return playlist.length;
+    },
+
     async toggleVideoDoneStatus(playlistName, index) {
       const playlists = JSON.parse(JSON.stringify(this.playlists));
       const playlist = [...playlists[playlistName]];
@@ -847,6 +910,38 @@ document.addEventListener("alpine:init", () => {
       },
     },
 
+    moveToTopButton: {
+      ["@click"]() {
+        this.moveVideoToTop(
+          this.$el.dataset.playlistName,
+          parseInt(this.$el.dataset.playlistIndex),
+        );
+        this.closeAllMenus();
+      },
+      [":disabled"]() {
+        const index = parseInt(this.$el.dataset.playlistIndex);
+        const playlistName = this.$el.dataset.playlistName;
+        const topPosition = this.findTopPosition(playlistName);
+        return index === topPosition;
+      },
+    },
+
+    moveToBottomButton: {
+      ["@click"]() {
+        this.moveVideoToBottom(
+          this.$el.dataset.playlistName,
+          parseInt(this.$el.dataset.playlistIndex),
+        );
+        this.closeAllMenus();
+      },
+      [":disabled"]() {
+        return (
+          parseInt(this.$el.dataset.playlistIndex) ===
+          this.playlists[this.$el.dataset.playlistName].length - 1
+        );
+      },
+    },
+
     exportButton: {
       ["@click"]() {
         this.exportPlaylists();