|
|
@@ -25,14 +25,17 @@ document.addEventListener("alpine:init", () => {
|
|
|
history: {},
|
|
|
sortedHistory: [],
|
|
|
openMenus: new Set(), // Track which menus are open
|
|
|
+ moveSubmenuOpenFor: null, // Track which video has the move-to submenu open (menuId or null)
|
|
|
currentView: "playlists", // Track current view: 'playlists', 'history', 'playlist', 'saveChannel', or 'wikiInsp'
|
|
|
currentPlaylistName: "", // Track which playlist is being viewed
|
|
|
playlistsForDisplay: [], // Computed array for display
|
|
|
currentPlaylistVideos: [], // Videos for current playlist view
|
|
|
+ otherPlaylists: [], // Playlist names (with display labels) other than currentPlaylistName
|
|
|
currentTab: null, // Current active tab info
|
|
|
isCurrentTabYoutube: false, // Whether current tab is YouTube
|
|
|
isCurrentTabChannelPage: false, // Whether current tab is YouTube videos page
|
|
|
- addCurrentPageButtonText: "Add Current Page", // Button text
|
|
|
+ addCurrentPageButtonText: "Add Cur. Page", // Button text
|
|
|
+ scatterCount: 4,
|
|
|
|
|
|
// Save channel properties
|
|
|
selectedCategory: "FOR BOTH", // Currently selected category
|
|
|
@@ -135,6 +138,9 @@ document.addEventListener("alpine:init", () => {
|
|
|
visibleVideos: visibleVideos,
|
|
|
shouldShowTruncation: shouldShowTruncation,
|
|
|
truncationText: truncationText,
|
|
|
+ otherPlaylists: Object.keys(this.playlists)
|
|
|
+ .filter((n) => n !== playlistName)
|
|
|
+ .map((n) => ({ name: n, display: this.formatPlaylistName(n) })),
|
|
|
};
|
|
|
},
|
|
|
);
|
|
|
@@ -146,6 +152,7 @@ document.addEventListener("alpine:init", () => {
|
|
|
!this.playlists[this.currentPlaylistName]
|
|
|
) {
|
|
|
this.currentPlaylistVideos = [];
|
|
|
+ this.otherPlaylists = [];
|
|
|
} else {
|
|
|
this.currentPlaylistVideos = this.playlists[
|
|
|
this.currentPlaylistName
|
|
|
@@ -158,6 +165,9 @@ document.addEventListener("alpine:init", () => {
|
|
|
index,
|
|
|
),
|
|
|
}));
|
|
|
+ this.otherPlaylists = Object.keys(this.playlists)
|
|
|
+ .filter((name) => name !== this.currentPlaylistName)
|
|
|
+ .map((name) => ({ name, display: this.formatPlaylistName(name) }));
|
|
|
}
|
|
|
},
|
|
|
|
|
|
@@ -262,11 +272,11 @@ document.addEventListener("alpine:init", () => {
|
|
|
|
|
|
updateAddCurrentPageButtonText() {
|
|
|
if (!this.currentTab) {
|
|
|
- this.addCurrentPageButtonText = "Unable to get current page";
|
|
|
+ this.addCurrentPageButtonText = "Unable to get page";
|
|
|
} else if (!this.isCurrentTabYoutube) {
|
|
|
- this.addCurrentPageButtonText = "Add Current Page (YouTube only)";
|
|
|
+ this.addCurrentPageButtonText = "Add Cur. Page (YT only)";
|
|
|
} else {
|
|
|
- this.addCurrentPageButtonText = "Add Current Page to Playlist";
|
|
|
+ this.addCurrentPageButtonText = "Add Cur. Page";
|
|
|
}
|
|
|
},
|
|
|
|
|
|
@@ -613,10 +623,10 @@ document.addEventListener("alpine:init", () => {
|
|
|
|
|
|
const topPosition = this.findTopPosition(playlistName);
|
|
|
|
|
|
- if (index === topPosition) return;
|
|
|
+ if (index === topPosition + 1) return;
|
|
|
|
|
|
const video = playlist.splice(index, 1)[0];
|
|
|
- playlist.splice(topPosition, 0, video);
|
|
|
+ playlist.splice(topPosition + 1, 0, video);
|
|
|
|
|
|
const updatedPlaylists = {
|
|
|
...playlists,
|
|
|
@@ -657,6 +667,20 @@ document.addEventListener("alpine:init", () => {
|
|
|
}
|
|
|
},
|
|
|
|
|
|
+ async moveVideoToPlaylist(fromPlaylistName, videoIndex, toPlaylistName) {
|
|
|
+ const playlists = JSON.parse(JSON.stringify(this.playlists));
|
|
|
+ const video = playlists[fromPlaylistName].splice(videoIndex, 1)[0];
|
|
|
+ playlists[toPlaylistName].push(video);
|
|
|
+ try {
|
|
|
+ await browser.storage.local.set({ playlists });
|
|
|
+ this.playlists = playlists;
|
|
|
+ this.updatePlaylistsForDisplay();
|
|
|
+ this.updateCurrentPlaylistVideos();
|
|
|
+ } catch (error) {
|
|
|
+ console.error("Error moving video to playlist:", error);
|
|
|
+ }
|
|
|
+ },
|
|
|
+
|
|
|
findTopPosition(playlistName) {
|
|
|
const playlist = this.playlists[playlistName];
|
|
|
if (!playlist) return 0;
|
|
|
@@ -670,6 +694,38 @@ document.addEventListener("alpine:init", () => {
|
|
|
return playlist.length;
|
|
|
},
|
|
|
|
|
|
+ async scatterLastN(playlistName, count) {
|
|
|
+ const playlists = JSON.parse(JSON.stringify(this.playlists));
|
|
|
+ const playlist = playlists[playlistName];
|
|
|
+ const n = playlist.length;
|
|
|
+ if (!playlist || count < 2 || count >= n) return;
|
|
|
+
|
|
|
+ const prefix = playlist.slice(0, n - count);
|
|
|
+ const tail = playlist.slice(n - count);
|
|
|
+ const insertions = [];
|
|
|
+ let cursor = prefix.length;
|
|
|
+
|
|
|
+ for (let i = count - 2; i >= 0; i--) {
|
|
|
+ const gap = Math.floor(Math.random() * 3) + 1;
|
|
|
+ cursor = Math.max(1, cursor - gap);
|
|
|
+ insertions.push([cursor, tail[i]]);
|
|
|
+ }
|
|
|
+ for (const [idx, video] of insertions) {
|
|
|
+ prefix.splice(idx, 0, video);
|
|
|
+ }
|
|
|
+ prefix.push(tail[count - 1]);
|
|
|
+
|
|
|
+ playlists[playlistName] = prefix;
|
|
|
+ try {
|
|
|
+ await browser.storage.local.set({ playlists });
|
|
|
+ this.playlists = playlists;
|
|
|
+ this.updatePlaylistsForDisplay();
|
|
|
+ this.updateCurrentPlaylistVideos();
|
|
|
+ } catch (error) {
|
|
|
+ console.error("Error scattering videos:", error);
|
|
|
+ }
|
|
|
+ },
|
|
|
+
|
|
|
async toggleVideoDoneStatus(playlistName, index) {
|
|
|
const playlists = JSON.parse(JSON.stringify(this.playlists));
|
|
|
const playlist = [...playlists[playlistName]];
|
|
|
@@ -922,7 +978,7 @@ document.addEventListener("alpine:init", () => {
|
|
|
const index = parseInt(this.$el.dataset.playlistIndex);
|
|
|
const playlistName = this.$el.dataset.playlistName;
|
|
|
const topPosition = this.findTopPosition(playlistName);
|
|
|
- return index === topPosition;
|
|
|
+ return index === topPosition + 1;
|
|
|
},
|
|
|
},
|
|
|
|
|
|
@@ -942,6 +998,33 @@ document.addEventListener("alpine:init", () => {
|
|
|
},
|
|
|
},
|
|
|
|
|
|
+ moveToSubmenuButton: {
|
|
|
+ ["@click.stop"]() {
|
|
|
+ const playlistName = this.$el.dataset.playlistName;
|
|
|
+ const index = parseInt(this.$el.dataset.playlistIndex);
|
|
|
+ const menuId = this.getMenuId(playlistName, index);
|
|
|
+ this.moveSubmenuOpenFor = this.moveSubmenuOpenFor === menuId ? null : menuId;
|
|
|
+ },
|
|
|
+ },
|
|
|
+
|
|
|
+ moveToSubmenu: {
|
|
|
+ ["x-show"]() {
|
|
|
+ const playlistName = this.$el.dataset.playlistName;
|
|
|
+ const index = parseInt(this.$el.dataset.playlistIndex);
|
|
|
+ return this.moveSubmenuOpenFor === this.getMenuId(playlistName, index);
|
|
|
+ },
|
|
|
+ },
|
|
|
+
|
|
|
+ moveToPlaylistButton: {
|
|
|
+ ["@click"]() {
|
|
|
+ const fromPlaylistName = this.$el.dataset.fromPlaylistName;
|
|
|
+ const videoIndex = parseInt(this.$el.dataset.playlistIndex);
|
|
|
+ const toPlaylistName = this.$el.dataset.toPlaylistName;
|
|
|
+ this.moveVideoToPlaylist(fromPlaylistName, videoIndex, toPlaylistName);
|
|
|
+ this.closeAllMenus();
|
|
|
+ },
|
|
|
+ },
|
|
|
+
|
|
|
exportButton: {
|
|
|
["@click"]() {
|
|
|
this.exportPlaylists();
|
|
|
@@ -1089,14 +1172,15 @@ document.addEventListener("alpine:init", () => {
|
|
|
if (this.openMenus.has(menuId)) {
|
|
|
this.openMenus.delete(menuId);
|
|
|
} else {
|
|
|
- // Close all other menus first
|
|
|
this.openMenus.clear();
|
|
|
this.openMenus.add(menuId);
|
|
|
}
|
|
|
+ this.moveSubmenuOpenFor = null;
|
|
|
},
|
|
|
|
|
|
closeAllMenus() {
|
|
|
this.openMenus.clear();
|
|
|
+ this.moveSubmenuOpenFor = null;
|
|
|
},
|
|
|
|
|
|
moreMenuButton: {
|
|
|
@@ -1274,6 +1358,12 @@ document.addEventListener("alpine:init", () => {
|
|
|
},
|
|
|
},
|
|
|
|
|
|
+ addCurrentPageContainer: {
|
|
|
+ ["x-show"]() {
|
|
|
+ return this.currentView === "playlist";
|
|
|
+ },
|
|
|
+ },
|
|
|
+
|
|
|
playlistViewContainer: {
|
|
|
["x-show"]() {
|
|
|
return this.currentView === "playlist";
|
|
|
@@ -1406,6 +1496,32 @@ document.addEventListener("alpine:init", () => {
|
|
|
},
|
|
|
},
|
|
|
|
|
|
+ scatterCountInput: {
|
|
|
+ [":value"]() {
|
|
|
+ return this.scatterCount;
|
|
|
+ },
|
|
|
+ [":max"]() {
|
|
|
+ const pl = this.playlists[this.currentPlaylistName];
|
|
|
+ return pl ? pl.length - 1 : 2;
|
|
|
+ },
|
|
|
+ ["@input"]() {
|
|
|
+ const pl = this.playlists[this.currentPlaylistName];
|
|
|
+ const max = pl ? pl.length - 1 : 2;
|
|
|
+ const v = Math.min(max, Math.max(2, parseInt(this.$el.value) || 2));
|
|
|
+ this.scatterCount = v;
|
|
|
+ },
|
|
|
+ },
|
|
|
+
|
|
|
+ scatterButton: {
|
|
|
+ ["@click"]() {
|
|
|
+ this.scatterLastN(this.currentPlaylistName, this.scatterCount);
|
|
|
+ },
|
|
|
+ [":disabled"]() {
|
|
|
+ const pl = this.playlists[this.currentPlaylistName];
|
|
|
+ return !pl || this.scatterCount >= pl.length;
|
|
|
+ },
|
|
|
+ },
|
|
|
+
|
|
|
// Add current page button binding
|
|
|
addCurrentPageButton: {
|
|
|
["@click"]() {
|