|
|
@@ -13,17 +13,18 @@ document.addEventListener("alpine:init", () => {
|
|
|
// - 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)
|
|
|
+ // X 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?
|
|
|
// X add personal rating feature ("enjoyed", "this was important", etc)
|
|
|
// - option to move video (including status) to another playlist
|
|
|
+ // X button to copy current video to clipboard in wikitext format (for personal wiki list)
|
|
|
Alpine.data("playlistManager", () => ({
|
|
|
playlists: {},
|
|
|
currentIndices: {},
|
|
|
history: {},
|
|
|
sortedHistory: [],
|
|
|
openMenus: new Set(), // Track which menus are open
|
|
|
- currentView: "playlists", // Track current view: 'playlists', 'history', 'playlist', or 'saveChannel'
|
|
|
+ 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
|
|
|
@@ -52,6 +53,13 @@ document.addEventListener("alpine:init", () => {
|
|
|
curlCommand: "", // Generated curl command
|
|
|
checkInterval: 14, // Check interval in days
|
|
|
|
|
|
+ // Wiki/insp properties
|
|
|
+ wikitextCommand: "", // Generated wikitext
|
|
|
+ isCurrentVideoYoutube: false, // Whether current video is YouTube
|
|
|
+ hasVideoEnded: false, // Whether video has ended
|
|
|
+ currentVideoTimestamp: "", // Current video timestamp
|
|
|
+ includeTimestamp: true, // Whether to include timestamp line
|
|
|
+
|
|
|
init() {
|
|
|
this.loadPlaylists();
|
|
|
this.loadHistory();
|
|
|
@@ -172,16 +180,79 @@ document.addEventListener("alpine:init", () => {
|
|
|
this.isCurrentTabYoutube = url.hostname === "www.youtube.com";
|
|
|
this.isCurrentTabChannelPage =
|
|
|
this.isCurrentTabYoutube && url.pathname.includes("/videos");
|
|
|
+ this.isCurrentVideoYoutube =
|
|
|
+ this.isCurrentTabYoutube && url.pathname.includes("/watch");
|
|
|
+
|
|
|
+ // Get video timestamp if on YouTube video page
|
|
|
+ if (this.isCurrentVideoYoutube) {
|
|
|
+ await this.getVideoTimestamp();
|
|
|
+ }
|
|
|
+
|
|
|
this.updateAddCurrentPageButtonText();
|
|
|
this.updateCurlCommand();
|
|
|
+ this.updateWikitextCommand();
|
|
|
}
|
|
|
} catch (error) {
|
|
|
console.error("Error getting current tab:", error);
|
|
|
this.currentTab = null;
|
|
|
this.isCurrentTabYoutube = false;
|
|
|
this.isCurrentTabChannelPage = false;
|
|
|
+ this.isCurrentVideoYoutube = false;
|
|
|
this.updateAddCurrentPageButtonText();
|
|
|
this.updateCurlCommand();
|
|
|
+ this.updateWikitextCommand();
|
|
|
+ }
|
|
|
+ },
|
|
|
+
|
|
|
+ async getVideoTimestamp() {
|
|
|
+ try {
|
|
|
+ // Execute script in the current tab to get video timestamp
|
|
|
+ const results = await browser.tabs.executeScript(this.currentTab.id, {
|
|
|
+ code: `
|
|
|
+ (function() {
|
|
|
+ const video = document.querySelector('video');
|
|
|
+ if (video) {
|
|
|
+ const currentTime = video.currentTime;
|
|
|
+ const duration = video.duration;
|
|
|
+ const ended = video.ended;
|
|
|
+
|
|
|
+ const formatTime = (seconds) => {
|
|
|
+ const hours = Math.floor(seconds / 3600);
|
|
|
+ const minutes = Math.floor((seconds % 3600) / 60);
|
|
|
+ const remainingSeconds = Math.floor(seconds % 60);
|
|
|
+
|
|
|
+ if (hours > 0) {
|
|
|
+ return hours + ':' + minutes.toString().padStart(2, '0') + ':' + remainingSeconds.toString().padStart(2, '0');
|
|
|
+ } else {
|
|
|
+ return minutes + ':' + remainingSeconds.toString().padStart(2, '0');
|
|
|
+ }
|
|
|
+ };
|
|
|
+
|
|
|
+ return {
|
|
|
+ currentTime: currentTime,
|
|
|
+ formattedTime: formatTime(currentTime),
|
|
|
+ duration: duration,
|
|
|
+ ended: ended,
|
|
|
+ isLongVideo: duration > 3600
|
|
|
+ };
|
|
|
+ }
|
|
|
+ return null;
|
|
|
+ })();
|
|
|
+ `
|
|
|
+ });
|
|
|
+
|
|
|
+ if (results && results[0]) {
|
|
|
+ const videoData = results[0];
|
|
|
+ this.currentVideoTimestamp = videoData.formattedTime;
|
|
|
+ this.hasVideoEnded = videoData.ended;
|
|
|
+ } else {
|
|
|
+ this.currentVideoTimestamp = "0:00";
|
|
|
+ this.hasVideoEnded = false;
|
|
|
+ }
|
|
|
+ } catch (error) {
|
|
|
+ console.error("Error getting video timestamp:", error);
|
|
|
+ this.currentVideoTimestamp = "0:00";
|
|
|
+ this.hasVideoEnded = false;
|
|
|
}
|
|
|
},
|
|
|
|
|
|
@@ -250,6 +321,75 @@ document.addEventListener("alpine:init", () => {
|
|
|
this.updateCurlCommand();
|
|
|
},
|
|
|
|
|
|
+ showWikiInsp() {
|
|
|
+ this.currentView = "wikiInsp";
|
|
|
+ // Refresh timestamp when opening the view
|
|
|
+ if (this.isCurrentVideoYoutube) {
|
|
|
+ this.getVideoTimestamp().then(() => {
|
|
|
+ this.updateWikitextCommand();
|
|
|
+ });
|
|
|
+ } else {
|
|
|
+ this.updateWikitextCommand();
|
|
|
+ }
|
|
|
+ },
|
|
|
+
|
|
|
+ async refreshVideoTimestamp() {
|
|
|
+ if (this.isCurrentVideoYoutube) {
|
|
|
+ await this.getVideoTimestamp();
|
|
|
+ this.updateWikitextCommand();
|
|
|
+ }
|
|
|
+ },
|
|
|
+
|
|
|
+ updateWikitextCommand() {
|
|
|
+ if (!this.currentTab || !this.isCurrentVideoYoutube) {
|
|
|
+ this.wikitextCommand = "This feature is only available on YouTube videos.";
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ const today = new Date();
|
|
|
+ const dateString = today.toISOString().split('T')[0]; // YYYY-MM-DD format
|
|
|
+
|
|
|
+ const title = this.currentTab.title.replace(" - YouTube", "").replace(/\|/g, "-");
|
|
|
+ const url = this.currentTab.url;
|
|
|
+
|
|
|
+ let wikitext = `!!! ${dateString}\n* [[${title}|${url}]] (yt)`;
|
|
|
+
|
|
|
+ // Add timestamp if user has enabled it and video hasn't ended
|
|
|
+ if (this.includeTimestamp && !this.hasVideoEnded) {
|
|
|
+ wikitext += `\n** ${this.currentVideoTimestamp || "0:00"} - `;
|
|
|
+ }
|
|
|
+
|
|
|
+ this.wikitextCommand = wikitext;
|
|
|
+ },
|
|
|
+
|
|
|
+ async copyWikitextCommand() {
|
|
|
+ try {
|
|
|
+ await navigator.clipboard.writeText(this.wikitextCommand);
|
|
|
+ console.log("Wikitext copied to clipboard");
|
|
|
+ // Could add visual feedback here
|
|
|
+ } catch (error) {
|
|
|
+ console.error("Failed to copy to clipboard:", error);
|
|
|
+ // Fallback for older browsers
|
|
|
+ try {
|
|
|
+ const textArea = document.createElement("textarea");
|
|
|
+ textArea.value = this.wikitextCommand;
|
|
|
+ document.body.appendChild(textArea);
|
|
|
+ textArea.focus();
|
|
|
+ textArea.select();
|
|
|
+ document.execCommand("copy");
|
|
|
+ document.body.removeChild(textArea);
|
|
|
+ console.log("Wikitext copied to clipboard (fallback)");
|
|
|
+ } catch (fallbackError) {
|
|
|
+ console.error("Fallback copy failed:", fallbackError);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ },
|
|
|
+
|
|
|
+ toggleTimestamp() {
|
|
|
+ this.includeTimestamp = !this.includeTimestamp;
|
|
|
+ this.updateWikitextCommand();
|
|
|
+ },
|
|
|
+
|
|
|
sortHistoryByRecentInteraction() {
|
|
|
// Convert history object to array with video ID and sort by most recent interaction
|
|
|
this.sortedHistory = Object.entries(this.history)
|
|
|
@@ -919,6 +1059,12 @@ document.addEventListener("alpine:init", () => {
|
|
|
},
|
|
|
},
|
|
|
|
|
|
+ wikiInspButton: {
|
|
|
+ ["@click"]() {
|
|
|
+ this.showWikiInsp();
|
|
|
+ },
|
|
|
+ },
|
|
|
+
|
|
|
backButton: {
|
|
|
["@click"]() {
|
|
|
this.showPlaylists();
|
|
|
@@ -986,6 +1132,12 @@ document.addEventListener("alpine:init", () => {
|
|
|
},
|
|
|
},
|
|
|
|
|
|
+ wikiInspHeader: {
|
|
|
+ ["x-show"]() {
|
|
|
+ return this.currentView === "wikiInsp";
|
|
|
+ },
|
|
|
+ },
|
|
|
+
|
|
|
playlistsContainer: {
|
|
|
["x-show"]() {
|
|
|
return this.currentView === "playlists";
|
|
|
@@ -1010,6 +1162,12 @@ document.addEventListener("alpine:init", () => {
|
|
|
},
|
|
|
},
|
|
|
|
|
|
+ wikiInspContainer: {
|
|
|
+ ["x-show"]() {
|
|
|
+ return this.currentView === "wikiInsp";
|
|
|
+ },
|
|
|
+ },
|
|
|
+
|
|
|
exportContainer: {
|
|
|
["x-show"]() {
|
|
|
return this.currentView === "playlists";
|
|
|
@@ -1056,6 +1214,34 @@ document.addEventListener("alpine:init", () => {
|
|
|
},
|
|
|
},
|
|
|
|
|
|
+ // Wiki/insp specific bindings
|
|
|
+ copyWikitextButton: {
|
|
|
+ ["@click"]() {
|
|
|
+ this.copyWikitextCommand();
|
|
|
+ },
|
|
|
+ [":disabled"]() {
|
|
|
+ return !this.isCurrentVideoYoutube;
|
|
|
+ },
|
|
|
+ },
|
|
|
+
|
|
|
+ timestampCheckbox: {
|
|
|
+ ["@change"]() {
|
|
|
+ this.toggleTimestamp();
|
|
|
+ },
|
|
|
+ [":checked"]() {
|
|
|
+ return this.includeTimestamp;
|
|
|
+ },
|
|
|
+ },
|
|
|
+
|
|
|
+ refreshTimestampButton: {
|
|
|
+ ["@click"]() {
|
|
|
+ this.refreshVideoTimestamp();
|
|
|
+ },
|
|
|
+ [":disabled"]() {
|
|
|
+ return !this.isCurrentVideoYoutube;
|
|
|
+ },
|
|
|
+ },
|
|
|
+
|
|
|
categoryButton: {
|
|
|
["@click"]() {
|
|
|
const category = this.$el.dataset.category;
|
|
|
@@ -1075,6 +1261,12 @@ document.addEventListener("alpine:init", () => {
|
|
|
},
|
|
|
},
|
|
|
|
|
|
+ wikiInspNotice: {
|
|
|
+ ["x-show"]() {
|
|
|
+ return !this.isCurrentVideoYoutube;
|
|
|
+ },
|
|
|
+ },
|
|
|
+
|
|
|
intervalInput: {
|
|
|
[":value"]() {
|
|
|
return this.checkInterval;
|