|
|
@@ -3,23 +3,44 @@ document.addEventListener("alpine:init", () => {
|
|
|
// - preserve playlist order (sort)
|
|
|
// - "play/resume playlist" button (pick up where you left off)
|
|
|
// X track timestamp to truly resume where you left off
|
|
|
- // - periodically remove watched videos
|
|
|
- // - or move to an "old" category, and add a screen to see the list
|
|
|
+ // X periodically remove watched videos
|
|
|
+ // X or move to an "old" category, and add a screen to see the list
|
|
|
// - consider separating context menu items (rather than having a sub-menu)
|
|
|
- // - view playback history (?)
|
|
|
+ // X view playback history (?)
|
|
|
// - 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?)
|
|
|
- // - option (probably a button) to add current page (url, title) to playlist
|
|
|
+ // - 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)
|
|
|
// - long-term: replace youtube page? rss feeds? need server?
|
|
|
- // - add personal rating feature ("enjoyed", "this was important", etc)
|
|
|
+ // X add personal rating feature ("enjoyed", "this was important", etc)
|
|
|
Alpine.data("playlistManager", () => ({
|
|
|
playlists: {},
|
|
|
currentIndices: {},
|
|
|
+ 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
|
|
|
+ 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
|
|
|
|
|
|
init() {
|
|
|
this.loadPlaylists();
|
|
|
+ this.loadHistory();
|
|
|
+ this.getCurrentTab();
|
|
|
+ // 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() {
|
|
|
@@ -48,6 +69,196 @@ document.addEventListener("alpine:init", () => {
|
|
|
} catch (error) {
|
|
|
console.error("Error loading playlists:", error);
|
|
|
}
|
|
|
+ this.updatePlaylistsForDisplay();
|
|
|
+ },
|
|
|
+
|
|
|
+ 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,
|
|
|
+ }));
|
|
|
+
|
|
|
+ return {
|
|
|
+ name: playlistName,
|
|
|
+ videos: videos,
|
|
|
+ visibleVideos: visibleVideos,
|
|
|
+ shouldShowTruncation: shouldShowTruncation,
|
|
|
+ truncationText: truncationText,
|
|
|
+ };
|
|
|
+ },
|
|
|
+ );
|
|
|
+ },
|
|
|
+
|
|
|
+ updateCurrentPlaylistVideos() {
|
|
|
+ if (
|
|
|
+ !this.currentPlaylistName ||
|
|
|
+ !this.playlists[this.currentPlaylistName]
|
|
|
+ ) {
|
|
|
+ this.currentPlaylistVideos = [];
|
|
|
+ } else {
|
|
|
+ this.currentPlaylistVideos = this.playlists[this.currentPlaylistName];
|
|
|
+ }
|
|
|
+ },
|
|
|
+
|
|
|
+ async loadHistory() {
|
|
|
+ try {
|
|
|
+ const result = await browser.storage.local.get("history");
|
|
|
+ console.log("LOAD HISTORY RESULT", result.history);
|
|
|
+ this.history = result.history || {};
|
|
|
+ this.sortHistoryByRecentInteraction();
|
|
|
+ } catch (error) {
|
|
|
+ console.error("Error loading history:", error);
|
|
|
+ }
|
|
|
+ },
|
|
|
+
|
|
|
+ async getCurrentTab() {
|
|
|
+ try {
|
|
|
+ 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.updateAddCurrentPageButtonText();
|
|
|
+ }
|
|
|
+ } catch (error) {
|
|
|
+ console.error("Error getting current tab:", error);
|
|
|
+ this.currentTab = null;
|
|
|
+ this.isCurrentTabYoutube = false;
|
|
|
+ this.updateAddCurrentPageButtonText();
|
|
|
+ }
|
|
|
+ },
|
|
|
+
|
|
|
+ updateAddCurrentPageButtonText() {
|
|
|
+ if (!this.currentTab) {
|
|
|
+ this.addCurrentPageButtonText = "Unable to get current page";
|
|
|
+ } else if (!this.isCurrentTabYoutube) {
|
|
|
+ this.addCurrentPageButtonText = "Add Current Page (YouTube only)";
|
|
|
+ } else {
|
|
|
+ this.addCurrentPageButtonText = "Add Current Page to Playlist";
|
|
|
+ }
|
|
|
+ },
|
|
|
+
|
|
|
+ sortHistoryByRecentInteraction() {
|
|
|
+ // 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;
|
|
|
+
|
|
|
+ // Pre-process events with formatted data for CSP compliance
|
|
|
+ const processedEvents = videoData.history
|
|
|
+ .slice()
|
|
|
+ .reverse()
|
|
|
+ .map((event, index) => ({
|
|
|
+ ...event,
|
|
|
+ formattedAction: this.formatActionName(event.action),
|
|
|
+ formattedPosition: `at ${this.formatVideoPosition(event.position)}`,
|
|
|
+ formattedTimestamp: this.formatTimestamp(event.timestamp),
|
|
|
+ uniqueKey: `${videoId}-${event.timestamp}-${index}`,
|
|
|
+ }));
|
|
|
+
|
|
|
+ return {
|
|
|
+ videoId,
|
|
|
+ formattedVideoId: `(${videoId})`,
|
|
|
+ ...videoData,
|
|
|
+ lastInteraction,
|
|
|
+ processedEvents,
|
|
|
+ tags: videoData.tags || [], // Ensure tags array exists
|
|
|
+ };
|
|
|
+ })
|
|
|
+ .sort((a, b) => b.lastInteraction - a.lastInteraction);
|
|
|
+ },
|
|
|
+
|
|
|
+ formatTimestamp(timestamp) {
|
|
|
+ const date = new Date(timestamp);
|
|
|
+ const now = new Date();
|
|
|
+ const diffMs = now - date;
|
|
|
+ const diffMins = Math.floor(diffMs / (1000 * 60));
|
|
|
+ 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`;
|
|
|
+
|
|
|
+ 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")}`;
|
|
|
+ },
|
|
|
+
|
|
|
+ async toggleTag(videoId, tag) {
|
|
|
+ // Create a deep copy of history to avoid proxy issues
|
|
|
+ const history = JSON.parse(JSON.stringify(this.history));
|
|
|
+
|
|
|
+ if (!history[videoId]) {
|
|
|
+ console.error(`Video ${videoId} not found in history`);
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ const videoData = history[videoId];
|
|
|
+ if (!videoData.tags) {
|
|
|
+ videoData.tags = [];
|
|
|
+ }
|
|
|
+
|
|
|
+ const tagIndex = videoData.tags.indexOf(tag);
|
|
|
+ if (tagIndex === -1) {
|
|
|
+ // Add tag
|
|
|
+ videoData.tags.push(tag);
|
|
|
+ } else {
|
|
|
+ // Remove tag
|
|
|
+ videoData.tags.splice(tagIndex, 1);
|
|
|
+ }
|
|
|
+
|
|
|
+ // Save to storage and update local state
|
|
|
+ try {
|
|
|
+ await browser.storage.local.set({ history: history });
|
|
|
+ this.history = history;
|
|
|
+ this.sortHistoryByRecentInteraction(); // Refresh display
|
|
|
+ } catch (error) {
|
|
|
+ console.error("Error saving tag changes:", error);
|
|
|
+ }
|
|
|
+ },
|
|
|
+
|
|
|
+ isTagActive(videoId, tag) {
|
|
|
+ const videoData = this.history[videoId];
|
|
|
+ return videoData && videoData.tags && videoData.tags.includes(tag);
|
|
|
+ },
|
|
|
+
|
|
|
+ formatActionName(action) {
|
|
|
+ const actionMap = {
|
|
|
+ play: "Started",
|
|
|
+ playing: "Playing",
|
|
|
+ pause: "Paused",
|
|
|
+ ended: "Finished",
|
|
|
+ };
|
|
|
+ return actionMap[action] || action;
|
|
|
},
|
|
|
|
|
|
formatPlaylistName(name) {
|
|
|
@@ -81,6 +292,8 @@ document.addEventListener("alpine:init", () => {
|
|
|
try {
|
|
|
await browser.storage.local.set({ playlists: updatedPlaylists });
|
|
|
this.playlists = updatedPlaylists;
|
|
|
+ this.updatePlaylistsForDisplay();
|
|
|
+ this.updateCurrentPlaylistVideos();
|
|
|
} catch (error) {
|
|
|
console.error("Error removing video:", error);
|
|
|
}
|
|
|
@@ -111,6 +324,8 @@ document.addEventListener("alpine:init", () => {
|
|
|
try {
|
|
|
await browser.storage.local.set({ playlists: updatedPlaylists });
|
|
|
this.playlists = updatedPlaylists;
|
|
|
+ this.updatePlaylistsForDisplay();
|
|
|
+ this.updateCurrentPlaylistVideos();
|
|
|
} catch (error) {
|
|
|
console.error("Error moving video up:", error);
|
|
|
}
|
|
|
@@ -139,11 +354,43 @@ document.addEventListener("alpine:init", () => {
|
|
|
try {
|
|
|
await browser.storage.local.set({ playlists: updatedPlaylists });
|
|
|
this.playlists = updatedPlaylists;
|
|
|
+ this.updatePlaylistsForDisplay();
|
|
|
+ this.updateCurrentPlaylistVideos();
|
|
|
} catch (error) {
|
|
|
console.error("Error moving video down:", error);
|
|
|
}
|
|
|
},
|
|
|
|
|
|
+ async addCurrentPageToPlaylist() {
|
|
|
+ if (
|
|
|
+ !this.currentTab ||
|
|
|
+ !this.isCurrentTabYoutube ||
|
|
|
+ !this.currentPlaylistName
|
|
|
+ ) {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ // Create video object using current tab info
|
|
|
+ const video = {
|
|
|
+ url: this.currentTab.url,
|
|
|
+ title: this.currentTab.title,
|
|
|
+ };
|
|
|
+
|
|
|
+ // Use shared utility function to add video (handles duplicate checking)
|
|
|
+ const wasAdded = await PlaylistUtils.addVideoToPlaylist(
|
|
|
+ this.currentPlaylistName,
|
|
|
+ video,
|
|
|
+ );
|
|
|
+
|
|
|
+ if (wasAdded) {
|
|
|
+ // Refresh displays only if video was actually added
|
|
|
+ this.loadPlaylists(); // This will update both display arrays
|
|
|
+ } else {
|
|
|
+ // Could show user feedback that video already exists
|
|
|
+ console.log("Video already exists in a playlist");
|
|
|
+ }
|
|
|
+ },
|
|
|
+
|
|
|
async exportPlaylists() {
|
|
|
try {
|
|
|
// Get current playlists
|
|
|
@@ -227,6 +474,7 @@ document.addEventListener("alpine:init", () => {
|
|
|
this.$el.dataset.playlistName,
|
|
|
this.$el.dataset.playlistIndex,
|
|
|
);
|
|
|
+ this.closeAllMenus();
|
|
|
},
|
|
|
},
|
|
|
|
|
|
@@ -269,6 +517,12 @@ document.addEventListener("alpine:init", () => {
|
|
|
},
|
|
|
},
|
|
|
|
|
|
+ importFileInput: {
|
|
|
+ ["@change"]() {
|
|
|
+ this.importFile(this.$event);
|
|
|
+ },
|
|
|
+ },
|
|
|
+
|
|
|
importFile(event) {
|
|
|
const file = event.target.files[0];
|
|
|
if (!file) return;
|
|
|
@@ -361,11 +615,245 @@ document.addEventListener("alpine:init", () => {
|
|
|
try {
|
|
|
await browser.storage.local.set({ playlists });
|
|
|
this.playlists = playlists;
|
|
|
+ this.updatePlaylistsForDisplay();
|
|
|
+ this.updateCurrentPlaylistVideos();
|
|
|
alert("Playlists imported successfully!");
|
|
|
} catch (error) {
|
|
|
console.error("Error updating playlists:", error);
|
|
|
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),
|
|
|
+ );
|
|
|
+ },
|
|
|
+ },
|
|
|
+
|
|
|
+ // View navigation methods
|
|
|
+ showHistory() {
|
|
|
+ this.currentView = "history";
|
|
|
+ this.loadHistory(); // Refresh history data when switching to history view
|
|
|
+ },
|
|
|
+
|
|
|
+ showPlaylists() {
|
|
|
+ this.currentView = "playlists";
|
|
|
+ },
|
|
|
+
|
|
|
+ showPlaylist(playlistName) {
|
|
|
+ this.currentView = "playlist";
|
|
|
+ this.currentPlaylistName = playlistName;
|
|
|
+ this.updateCurrentPlaylistVideos();
|
|
|
+ },
|
|
|
+
|
|
|
+ // Methods for truncated display
|
|
|
+ shouldShowTruncation(playlistName) {
|
|
|
+ const currentIndex = this.currentIndices[playlistName] || 0;
|
|
|
+ return currentIndex > 0;
|
|
|
+ },
|
|
|
+
|
|
|
+ getTruncationText(playlistName) {
|
|
|
+ const currentIndex = this.currentIndices[playlistName] || 0;
|
|
|
+ const count = currentIndex;
|
|
|
+ return `(${count} previous video${count === 1 ? "" : "s"})`;
|
|
|
+ },
|
|
|
+
|
|
|
+ getVisibleVideos(playlistName, videos) {
|
|
|
+ const currentIndex = this.currentIndices[playlistName] || 0;
|
|
|
+ // Show from current video onwards, but keep original indices
|
|
|
+ return videos.slice(currentIndex).map((video, index) => ({
|
|
|
+ ...video,
|
|
|
+ originalIndex: currentIndex + index,
|
|
|
+ }));
|
|
|
+ },
|
|
|
+
|
|
|
+ getCurrentPlaylistVideos() {
|
|
|
+ if (
|
|
|
+ !this.currentPlaylistName ||
|
|
|
+ !this.playlists[this.currentPlaylistName]
|
|
|
+ ) {
|
|
|
+ return [];
|
|
|
+ }
|
|
|
+ return this.playlists[this.currentPlaylistName];
|
|
|
+ },
|
|
|
+
|
|
|
+ // Button bindings for navigation
|
|
|
+ historyButton: {
|
|
|
+ ["@click"]() {
|
|
|
+ this.showHistory();
|
|
|
+ },
|
|
|
+ },
|
|
|
+
|
|
|
+ backButton: {
|
|
|
+ ["@click"]() {
|
|
|
+ this.showPlaylists();
|
|
|
+ },
|
|
|
+ },
|
|
|
+
|
|
|
+ // Event handlers for playlist navigation
|
|
|
+ playlistNameClick: {
|
|
|
+ ["@click"]() {
|
|
|
+ const playlistName = this.$el.dataset.playlistName;
|
|
|
+ this.showPlaylist(playlistName);
|
|
|
+ },
|
|
|
+ },
|
|
|
+
|
|
|
+ truncatedVideosClick: {
|
|
|
+ ["@click"]() {
|
|
|
+ const playlistName = this.$el.dataset.playlistName;
|
|
|
+ this.showPlaylist(playlistName);
|
|
|
+ },
|
|
|
+ },
|
|
|
+
|
|
|
+ truncatedVideosClick: {
|
|
|
+ ["@click"]() {
|
|
|
+ const playlistName = this.$el.dataset.playlistName;
|
|
|
+ this.showPlaylist(playlistName);
|
|
|
+ },
|
|
|
+ },
|
|
|
+
|
|
|
+ truncatedVideosDisplay: {
|
|
|
+ ["x-show"]() {
|
|
|
+ const playlistName = this.$el.dataset.playlistName;
|
|
|
+ const playlistData = this.playlistsForDisplay.find(
|
|
|
+ (p) => p.name === playlistName,
|
|
|
+ );
|
|
|
+ return playlistData ? playlistData.shouldShowTruncation : false;
|
|
|
+ },
|
|
|
+ ["@click"]() {
|
|
|
+ const playlistName = this.$el.dataset.playlistName;
|
|
|
+ this.showPlaylist(playlistName);
|
|
|
+ },
|
|
|
+ },
|
|
|
+
|
|
|
+ // CSP-compliant view bindings
|
|
|
+ playlistsHeader: {
|
|
|
+ ["x-show"]() {
|
|
|
+ return this.currentView === "playlists";
|
|
|
+ },
|
|
|
+ },
|
|
|
+
|
|
|
+ historyHeader: {
|
|
|
+ ["x-show"]() {
|
|
|
+ return this.currentView === "history";
|
|
|
+ },
|
|
|
+ },
|
|
|
+
|
|
|
+ playlistViewHeader: {
|
|
|
+ ["x-show"]() {
|
|
|
+ return this.currentView === "playlist";
|
|
|
+ },
|
|
|
+ },
|
|
|
+
|
|
|
+ playlistsContainer: {
|
|
|
+ ["x-show"]() {
|
|
|
+ return this.currentView === "playlists";
|
|
|
+ },
|
|
|
+ },
|
|
|
+
|
|
|
+ historyContainer: {
|
|
|
+ ["x-show"]() {
|
|
|
+ return this.currentView === "history";
|
|
|
+ },
|
|
|
+ },
|
|
|
+
|
|
|
+ playlistViewContainer: {
|
|
|
+ ["x-show"]() {
|
|
|
+ return this.currentView === "playlist";
|
|
|
+ },
|
|
|
+ },
|
|
|
+
|
|
|
+ exportContainer: {
|
|
|
+ ["x-show"]() {
|
|
|
+ return this.currentView === "playlists";
|
|
|
+ },
|
|
|
+ },
|
|
|
+
|
|
|
+ // History-specific bindings for CSP compliance
|
|
|
+ historyEmptyState: {
|
|
|
+ ["x-show"]() {
|
|
|
+ return this.sortedHistory.length === 0;
|
|
|
+ },
|
|
|
+ },
|
|
|
+
|
|
|
+ // Playlist view-specific bindings for CSP compliance
|
|
|
+ playlistViewEmptyState: {
|
|
|
+ ["x-show"]() {
|
|
|
+ return this.currentPlaylistVideos.length === 0;
|
|
|
+ },
|
|
|
+ },
|
|
|
+
|
|
|
+ // Tag chip bindings for CSP compliance
|
|
|
+ tagChip: {
|
|
|
+ ["@click"]() {
|
|
|
+ const videoId = this.$el.dataset.videoId;
|
|
|
+ const tag = this.$el.dataset.tag;
|
|
|
+ this.toggleTag(videoId, tag);
|
|
|
+ },
|
|
|
+ [":class"]() {
|
|
|
+ const videoId = this.$el.dataset.videoId;
|
|
|
+ const tag = this.$el.dataset.tag;
|
|
|
+ return {
|
|
|
+ active: this.isTagActive(videoId, tag),
|
|
|
+ };
|
|
|
+ },
|
|
|
+ },
|
|
|
+
|
|
|
+ // Add current page button binding
|
|
|
+ addCurrentPageButton: {
|
|
|
+ ["@click"]() {
|
|
|
+ this.addCurrentPageToPlaylist();
|
|
|
+ },
|
|
|
+ [":disabled"]() {
|
|
|
+ return (
|
|
|
+ !this.currentTab ||
|
|
|
+ !this.isCurrentTabYoutube ||
|
|
|
+ !this.currentPlaylistName
|
|
|
+ );
|
|
|
+ },
|
|
|
+ [":class"]() {
|
|
|
+ return {
|
|
|
+ disabled:
|
|
|
+ !this.currentTab ||
|
|
|
+ !this.isCurrentTabYoutube ||
|
|
|
+ !this.currentPlaylistName,
|
|
|
+ };
|
|
|
+ },
|
|
|
+ },
|
|
|
}));
|
|
|
});
|