|
|
@@ -10,6 +10,7 @@ document.addEventListener("alpine:init", () => {
|
|
|
// - include currently open tabs in export
|
|
|
// - support playlist management (add, remove, rename playlists)
|
|
|
// - support input text box for adding to playlist (how to handle title?)
|
|
|
+ // - or raw json editing
|
|
|
// X option (probably a button) to add current page (url, title) to playlist
|
|
|
// X align with addLinkToPlaylist in background.js (no repeated videos)
|
|
|
// - button to add channel to youtube page (copy gql mutation to clipboard) (like the automa version)
|
|
|
@@ -21,22 +22,22 @@ document.addEventListener("alpine:init", () => {
|
|
|
history: {},
|
|
|
sortedHistory: [],
|
|
|
openMenus: new Set(), // Track which menus are open
|
|
|
- currentView: 'playlists', // Track current view: 'playlists', 'history', or 'playlist'
|
|
|
- currentPlaylistName: '', // Track which playlist is being viewed
|
|
|
+ currentView: "playlists", // Track current view: 'playlists', 'history', or 'playlist'
|
|
|
+ currentPlaylistName: "", // Track which playlist is being viewed
|
|
|
playlistsForDisplay: [], // Computed array for display
|
|
|
currentPlaylistVideos: [], // Videos for current playlist view
|
|
|
currentTab: null, // Current active tab info
|
|
|
isCurrentTabYoutube: false, // Whether current tab is YouTube
|
|
|
- addCurrentPageButtonText: 'Add Current Page', // Button text
|
|
|
+ addCurrentPageButtonText: "Add Current Page", // Button text
|
|
|
|
|
|
init() {
|
|
|
this.loadPlaylists();
|
|
|
this.loadHistory();
|
|
|
this.getCurrentTab();
|
|
|
// Add document click handler to close menus
|
|
|
- document.addEventListener('click', (e) => {
|
|
|
+ document.addEventListener("click", (e) => {
|
|
|
// If click is not on a more button or menu, close all menus
|
|
|
- if (!e.target.closest('.more-menu-container')) {
|
|
|
+ if (!e.target.closest(".more-menu-container")) {
|
|
|
this.closeAllMenus();
|
|
|
}
|
|
|
});
|
|
|
@@ -72,31 +73,38 @@ document.addEventListener("alpine:init", () => {
|
|
|
},
|
|
|
|
|
|
updatePlaylistsForDisplay() {
|
|
|
- this.playlistsForDisplay = Object.entries(this.playlists).map(([playlistName, videos]) => {
|
|
|
- const currentIndex = this.currentIndices[playlistName] || 0;
|
|
|
- const shouldShowTruncation = currentIndex > 0;
|
|
|
- const truncationText = shouldShowTruncation
|
|
|
- ? `(${currentIndex} previous video${currentIndex === 1 ? '' : 's'})`
|
|
|
- : '';
|
|
|
-
|
|
|
- // Get visible videos from current index onwards with original indices
|
|
|
- const visibleVideos = videos.slice(currentIndex).map((video, index) => ({
|
|
|
- ...video,
|
|
|
- originalIndex: currentIndex + index
|
|
|
- }));
|
|
|
+ this.playlistsForDisplay = Object.entries(this.playlists).map(
|
|
|
+ ([playlistName, videos]) => {
|
|
|
+ const currentIndex = this.currentIndices[playlistName] || 0;
|
|
|
+ const shouldShowTruncation = currentIndex > 0;
|
|
|
+ const truncationText = shouldShowTruncation
|
|
|
+ ? `(${currentIndex} previous video${currentIndex === 1 ? "" : "s"})`
|
|
|
+ : "";
|
|
|
+
|
|
|
+ // Get visible videos from current index onwards with original indices
|
|
|
+ const visibleVideos = videos
|
|
|
+ .slice(currentIndex)
|
|
|
+ .map((video, index) => ({
|
|
|
+ ...video,
|
|
|
+ originalIndex: currentIndex + index,
|
|
|
+ }));
|
|
|
|
|
|
- return {
|
|
|
- name: playlistName,
|
|
|
- videos: videos,
|
|
|
- visibleVideos: visibleVideos,
|
|
|
- shouldShowTruncation: shouldShowTruncation,
|
|
|
- truncationText: truncationText
|
|
|
- };
|
|
|
- });
|
|
|
+ return {
|
|
|
+ name: playlistName,
|
|
|
+ videos: videos,
|
|
|
+ visibleVideos: visibleVideos,
|
|
|
+ shouldShowTruncation: shouldShowTruncation,
|
|
|
+ truncationText: truncationText,
|
|
|
+ };
|
|
|
+ },
|
|
|
+ );
|
|
|
},
|
|
|
|
|
|
updateCurrentPlaylistVideos() {
|
|
|
- if (!this.currentPlaylistName || !this.playlists[this.currentPlaylistName]) {
|
|
|
+ if (
|
|
|
+ !this.currentPlaylistName ||
|
|
|
+ !this.playlists[this.currentPlaylistName]
|
|
|
+ ) {
|
|
|
this.currentPlaylistVideos = [];
|
|
|
} else {
|
|
|
this.currentPlaylistVideos = this.playlists[this.currentPlaylistName];
|
|
|
@@ -116,11 +124,14 @@ document.addEventListener("alpine:init", () => {
|
|
|
|
|
|
async getCurrentTab() {
|
|
|
try {
|
|
|
- const tabs = await browser.tabs.query({ active: true, currentWindow: true });
|
|
|
+ const tabs = await browser.tabs.query({
|
|
|
+ active: true,
|
|
|
+ currentWindow: true,
|
|
|
+ });
|
|
|
if (tabs.length > 0) {
|
|
|
this.currentTab = tabs[0];
|
|
|
const url = new URL(this.currentTab.url);
|
|
|
- this.isCurrentTabYoutube = url.hostname === 'www.youtube.com';
|
|
|
+ this.isCurrentTabYoutube = url.hostname === "www.youtube.com";
|
|
|
this.updateAddCurrentPageButtonText();
|
|
|
}
|
|
|
} catch (error) {
|
|
|
@@ -133,11 +144,11 @@ document.addEventListener("alpine:init", () => {
|
|
|
|
|
|
updateAddCurrentPageButtonText() {
|
|
|
if (!this.currentTab) {
|
|
|
- this.addCurrentPageButtonText = 'Unable to get current page';
|
|
|
+ this.addCurrentPageButtonText = "Unable to get current page";
|
|
|
} else if (!this.isCurrentTabYoutube) {
|
|
|
- this.addCurrentPageButtonText = 'Add Current Page (YouTube only)';
|
|
|
+ this.addCurrentPageButtonText = "Add Current Page (YouTube only)";
|
|
|
} else {
|
|
|
- this.addCurrentPageButtonText = 'Add Current Page to Playlist';
|
|
|
+ this.addCurrentPageButtonText = "Add Current Page to Playlist";
|
|
|
}
|
|
|
},
|
|
|
|
|
|
@@ -145,9 +156,10 @@ document.addEventListener("alpine:init", () => {
|
|
|
// Convert history object to array with video ID and sort by most recent interaction
|
|
|
this.sortedHistory = Object.entries(this.history)
|
|
|
.map(([videoId, videoData]) => {
|
|
|
- const lastInteraction = videoData.history.length > 0
|
|
|
- ? Math.max(...videoData.history.map(event => event.timestamp))
|
|
|
- : 0;
|
|
|
+ const lastInteraction =
|
|
|
+ videoData.history.length > 0
|
|
|
+ ? Math.max(...videoData.history.map((event) => event.timestamp))
|
|
|
+ : 0;
|
|
|
|
|
|
// Pre-process events with formatted data for CSP compliance
|
|
|
const processedEvents = videoData.history
|
|
|
@@ -158,7 +170,7 @@ document.addEventListener("alpine:init", () => {
|
|
|
formattedAction: this.formatActionName(event.action),
|
|
|
formattedPosition: `at ${this.formatVideoPosition(event.position)}`,
|
|
|
formattedTimestamp: this.formatTimestamp(event.timestamp),
|
|
|
- uniqueKey: `${videoId}-${event.timestamp}-${index}`
|
|
|
+ uniqueKey: `${videoId}-${event.timestamp}-${index}`,
|
|
|
}));
|
|
|
|
|
|
return {
|
|
|
@@ -167,7 +179,7 @@ document.addEventListener("alpine:init", () => {
|
|
|
...videoData,
|
|
|
lastInteraction,
|
|
|
processedEvents,
|
|
|
- tags: videoData.tags || [] // Ensure tags array exists
|
|
|
+ tags: videoData.tags || [], // Ensure tags array exists
|
|
|
};
|
|
|
})
|
|
|
.sort((a, b) => b.lastInteraction - a.lastInteraction);
|
|
|
@@ -181,18 +193,24 @@ document.addEventListener("alpine:init", () => {
|
|
|
const diffHours = Math.floor(diffMs / (1000 * 60 * 60));
|
|
|
const diffDays = Math.floor(diffMs / (1000 * 60 * 60 * 24));
|
|
|
|
|
|
- if (diffMins < 1) return 'Just now';
|
|
|
- if (diffMins < 60) return `${diffMins} minute${diffMins > 1 ? 's' : ''} ago`;
|
|
|
- if (diffHours < 24) return `${diffHours} hour${diffHours > 1 ? 's' : ''} ago`;
|
|
|
- if (diffDays < 7) return `${diffDays} day${diffDays > 1 ? 's' : ''} ago`;
|
|
|
+ if (diffMins < 1) return "Just now";
|
|
|
+ if (diffMins < 60)
|
|
|
+ return `${diffMins} minute${diffMins > 1 ? "s" : ""} ago`;
|
|
|
+ if (diffHours < 24)
|
|
|
+ return `${diffHours} hour${diffHours > 1 ? "s" : ""} ago`;
|
|
|
+ if (diffDays < 7) return `${diffDays} day${diffDays > 1 ? "s" : ""} ago`;
|
|
|
|
|
|
- return date.toLocaleDateString() + ' ' + date.toLocaleTimeString([], {hour: '2-digit', minute:'2-digit'});
|
|
|
+ return (
|
|
|
+ date.toLocaleDateString() +
|
|
|
+ " " +
|
|
|
+ date.toLocaleTimeString([], { hour: "2-digit", minute: "2-digit" })
|
|
|
+ );
|
|
|
},
|
|
|
|
|
|
formatVideoPosition(seconds) {
|
|
|
const minutes = Math.floor(seconds / 60);
|
|
|
const remainingSeconds = Math.floor(seconds % 60);
|
|
|
- return `${minutes}:${remainingSeconds.toString().padStart(2, '0')}`;
|
|
|
+ return `${minutes}:${remainingSeconds.toString().padStart(2, "0")}`;
|
|
|
},
|
|
|
|
|
|
async toggleTag(videoId, tag) {
|
|
|
@@ -235,10 +253,10 @@ document.addEventListener("alpine:init", () => {
|
|
|
|
|
|
formatActionName(action) {
|
|
|
const actionMap = {
|
|
|
- 'play': 'Started',
|
|
|
- 'playing': 'Playing',
|
|
|
- 'pause': 'Paused',
|
|
|
- 'ended': 'Finished'
|
|
|
+ play: "Started",
|
|
|
+ playing: "Playing",
|
|
|
+ pause: "Paused",
|
|
|
+ ended: "Finished",
|
|
|
};
|
|
|
return actionMap[action] || action;
|
|
|
},
|
|
|
@@ -344,7 +362,11 @@ document.addEventListener("alpine:init", () => {
|
|
|
},
|
|
|
|
|
|
async addCurrentPageToPlaylist() {
|
|
|
- if (!this.currentTab || !this.isCurrentTabYoutube || !this.currentPlaylistName) {
|
|
|
+ if (
|
|
|
+ !this.currentTab ||
|
|
|
+ !this.isCurrentTabYoutube ||
|
|
|
+ !this.currentPlaylistName
|
|
|
+ ) {
|
|
|
return;
|
|
|
}
|
|
|
|
|
|
@@ -352,11 +374,13 @@ document.addEventListener("alpine:init", () => {
|
|
|
const video = {
|
|
|
url: this.currentTab.url,
|
|
|
title: this.currentTab.title,
|
|
|
- status: "new"
|
|
|
};
|
|
|
|
|
|
// Use shared utility function to add video (handles duplicate checking)
|
|
|
- const wasAdded = await PlaylistUtils.addVideoToPlaylist(this.currentPlaylistName, video);
|
|
|
+ const wasAdded = await PlaylistUtils.addVideoToPlaylist(
|
|
|
+ this.currentPlaylistName,
|
|
|
+ video,
|
|
|
+ );
|
|
|
|
|
|
if (wasAdded) {
|
|
|
// Refresh displays only if video was actually added
|
|
|
@@ -627,7 +651,7 @@ document.addEventListener("alpine:init", () => {
|
|
|
["@click.stop"]() {
|
|
|
this.toggleMenu(
|
|
|
this.$el.dataset.playlistName,
|
|
|
- parseInt(this.$el.dataset.playlistIndex)
|
|
|
+ parseInt(this.$el.dataset.playlistIndex),
|
|
|
);
|
|
|
},
|
|
|
},
|
|
|
@@ -636,23 +660,23 @@ document.addEventListener("alpine:init", () => {
|
|
|
["x-show"]() {
|
|
|
return this.isMenuOpen(
|
|
|
this.$el.dataset.playlistName,
|
|
|
- parseInt(this.$el.dataset.playlistIndex)
|
|
|
+ parseInt(this.$el.dataset.playlistIndex),
|
|
|
);
|
|
|
},
|
|
|
},
|
|
|
|
|
|
// View navigation methods
|
|
|
showHistory() {
|
|
|
- this.currentView = 'history';
|
|
|
+ this.currentView = "history";
|
|
|
this.loadHistory(); // Refresh history data when switching to history view
|
|
|
},
|
|
|
|
|
|
showPlaylists() {
|
|
|
- this.currentView = 'playlists';
|
|
|
+ this.currentView = "playlists";
|
|
|
},
|
|
|
|
|
|
showPlaylist(playlistName) {
|
|
|
- this.currentView = 'playlist';
|
|
|
+ this.currentView = "playlist";
|
|
|
this.currentPlaylistName = playlistName;
|
|
|
this.updateCurrentPlaylistVideos();
|
|
|
},
|
|
|
@@ -666,7 +690,7 @@ document.addEventListener("alpine:init", () => {
|
|
|
getTruncationText(playlistName) {
|
|
|
const currentIndex = this.currentIndices[playlistName] || 0;
|
|
|
const count = currentIndex;
|
|
|
- return `(${count} previous video${count === 1 ? '' : 's'})`;
|
|
|
+ return `(${count} previous video${count === 1 ? "" : "s"})`;
|
|
|
},
|
|
|
|
|
|
getVisibleVideos(playlistName, videos) {
|
|
|
@@ -674,12 +698,15 @@ document.addEventListener("alpine:init", () => {
|
|
|
// Show from current video onwards, but keep original indices
|
|
|
return videos.slice(currentIndex).map((video, index) => ({
|
|
|
...video,
|
|
|
- originalIndex: currentIndex + index
|
|
|
+ originalIndex: currentIndex + index,
|
|
|
}));
|
|
|
},
|
|
|
|
|
|
getCurrentPlaylistVideos() {
|
|
|
- if (!this.currentPlaylistName || !this.playlists[this.currentPlaylistName]) {
|
|
|
+ if (
|
|
|
+ !this.currentPlaylistName ||
|
|
|
+ !this.playlists[this.currentPlaylistName]
|
|
|
+ ) {
|
|
|
return [];
|
|
|
}
|
|
|
return this.playlists[this.currentPlaylistName];
|
|
|
@@ -723,7 +750,9 @@ document.addEventListener("alpine:init", () => {
|
|
|
truncatedVideosDisplay: {
|
|
|
["x-show"]() {
|
|
|
const playlistName = this.$el.dataset.playlistName;
|
|
|
- const playlistData = this.playlistsForDisplay.find(p => p.name === playlistName);
|
|
|
+ const playlistData = this.playlistsForDisplay.find(
|
|
|
+ (p) => p.name === playlistName,
|
|
|
+ );
|
|
|
return playlistData ? playlistData.shouldShowTruncation : false;
|
|
|
},
|
|
|
["@click"]() {
|
|
|
@@ -735,43 +764,43 @@ document.addEventListener("alpine:init", () => {
|
|
|
// CSP-compliant view bindings
|
|
|
playlistsHeader: {
|
|
|
["x-show"]() {
|
|
|
- return this.currentView === 'playlists';
|
|
|
+ return this.currentView === "playlists";
|
|
|
},
|
|
|
},
|
|
|
|
|
|
historyHeader: {
|
|
|
["x-show"]() {
|
|
|
- return this.currentView === 'history';
|
|
|
+ return this.currentView === "history";
|
|
|
},
|
|
|
},
|
|
|
|
|
|
playlistViewHeader: {
|
|
|
["x-show"]() {
|
|
|
- return this.currentView === 'playlist';
|
|
|
+ return this.currentView === "playlist";
|
|
|
},
|
|
|
},
|
|
|
|
|
|
playlistsContainer: {
|
|
|
["x-show"]() {
|
|
|
- return this.currentView === 'playlists';
|
|
|
+ return this.currentView === "playlists";
|
|
|
},
|
|
|
},
|
|
|
|
|
|
historyContainer: {
|
|
|
["x-show"]() {
|
|
|
- return this.currentView === 'history';
|
|
|
+ return this.currentView === "history";
|
|
|
},
|
|
|
},
|
|
|
|
|
|
playlistViewContainer: {
|
|
|
["x-show"]() {
|
|
|
- return this.currentView === 'playlist';
|
|
|
+ return this.currentView === "playlist";
|
|
|
},
|
|
|
},
|
|
|
|
|
|
exportContainer: {
|
|
|
["x-show"]() {
|
|
|
- return this.currentView === 'playlists';
|
|
|
+ return this.currentView === "playlists";
|
|
|
},
|
|
|
},
|
|
|
|
|
|
@@ -800,7 +829,7 @@ document.addEventListener("alpine:init", () => {
|
|
|
const videoId = this.$el.dataset.videoId;
|
|
|
const tag = this.$el.dataset.tag;
|
|
|
return {
|
|
|
- 'active': this.isTagActive(videoId, tag)
|
|
|
+ active: this.isTagActive(videoId, tag),
|
|
|
};
|
|
|
},
|
|
|
},
|
|
|
@@ -811,11 +840,18 @@ document.addEventListener("alpine:init", () => {
|
|
|
this.addCurrentPageToPlaylist();
|
|
|
},
|
|
|
[":disabled"]() {
|
|
|
- return !this.currentTab || !this.isCurrentTabYoutube || !this.currentPlaylistName;
|
|
|
+ return (
|
|
|
+ !this.currentTab ||
|
|
|
+ !this.isCurrentTabYoutube ||
|
|
|
+ !this.currentPlaylistName
|
|
|
+ );
|
|
|
},
|
|
|
[":class"]() {
|
|
|
return {
|
|
|
- 'disabled': !this.currentTab || !this.isCurrentTabYoutube || !this.currentPlaylistName
|
|
|
+ disabled:
|
|
|
+ !this.currentTab ||
|
|
|
+ !this.isCurrentTabYoutube ||
|
|
|
+ !this.currentPlaylistName,
|
|
|
};
|
|
|
},
|
|
|
},
|