diff --git a/assets/bookmark.png b/assets/bookmark.png new file mode 100644 index 0000000..85102c9 Binary files /dev/null and b/assets/bookmark.png differ diff --git a/assets/delete.png b/assets/delete.png new file mode 100644 index 0000000..2bb9bd1 Binary files /dev/null and b/assets/delete.png differ diff --git a/assets/ext-icon.png b/assets/ext-icon.png new file mode 100644 index 0000000..78b0c4c Binary files /dev/null and b/assets/ext-icon.png differ diff --git a/assets/play.png b/assets/play.png new file mode 100644 index 0000000..527bb67 Binary files /dev/null and b/assets/play.png differ diff --git a/assets/save.png b/assets/save.png new file mode 100644 index 0000000..beed6fd Binary files /dev/null and b/assets/save.png differ diff --git a/background.js b/background.js new file mode 100644 index 0000000..a57a2cf --- /dev/null +++ b/background.js @@ -0,0 +1,11 @@ +chrome.tabs.onUpdated.addListener((tabId, tab) => { + if (tab.url && tab.url.includes("youtube.com/watch")) { + const queryParameters = tab.url.split("?")[1]; + const urlParameters = new URLSearchParams(queryParameters); + + chrome.tabs.sendMessage(tabId, { + type: "NEW", + videoId: urlParameters.get("v"), + }); + } +}); diff --git a/contentScript.js b/contentScript.js new file mode 100644 index 0000000..2ea049d --- /dev/null +++ b/contentScript.js @@ -0,0 +1,72 @@ +(() => { + let youtubeLeftControls, youtubePlayer; + let currentVideo = ""; + let currentVideoBookmarks = []; + + const fetchBookmarks = () => { + return new Promise((resolve) => { + chrome.storage.sync.get([currentVideo], (obj) => { + resolve(obj[currentVideo] ? JSON.parse(obj[currentVideo]) : []); + }); + }); + }; + + const addNewBookmarkEventHandler = async () => { + const currentTime = youtubePlayer.currentTime; + const newBookmark = { + time: currentTime, + desc: "Bookmark at " + getTime(currentTime), + }; + + currentVideoBookmarks = await fetchBookmarks(); + + chrome.storage.sync.set({ + [currentVideo]: JSON.stringify([...currentVideoBookmarks, newBookmark].sort((a, b) => a.time - b.time)) + }); + }; + + const newVideoLoaded = async () => { + const bookmarkBtnExists = document.getElementsByClassName("bookmark-btn")[0]; + + currentVideoBookmarks = await fetchBookmarks(); + + if (!bookmarkBtnExists) { + const bookmarkBtn = document.createElement("img"); + + bookmarkBtn.src = chrome.runtime.getURL("assets/bookmark.png"); + bookmarkBtn.className = "ytp-button " + "bookmark-btn"; + bookmarkBtn.title = "Click to bookmark current timestamp"; + + youtubeLeftControls = document.getElementsByClassName("ytp-left-controls")[0]; + youtubePlayer = document.getElementsByClassName('video-stream')[0]; + + youtubeLeftControls.appendChild(bookmarkBtn); + bookmarkBtn.addEventListener("click", addNewBookmarkEventHandler); + } + }; + + chrome.runtime.onMessage.addListener((obj, sender, response) => { + const { type, value, videoId } = obj; + + if (type === "NEW") { + currentVideo = videoId; + newVideoLoaded(); + } else if (type === "PLAY") { + youtubePlayer.currentTime = value; + } else if ( type === "DELETE") { + currentVideoBookmarks = currentVideoBookmarks.filter((b) => b.time != value); + chrome.storage.sync.set({ [currentVideo]: JSON.stringify(currentVideoBookmarks) }); + + response(currentVideoBookmarks); + } + }); + + newVideoLoaded(); +})(); + +const getTime = t => { + var date = new Date(0); + date.setSeconds(t); + + return date.toISOString().substr(11, 8); +}; diff --git a/manifest.json b/manifest.json new file mode 100644 index 0000000..d9b899c --- /dev/null +++ b/manifest.json @@ -0,0 +1,37 @@ +{ + "name": "youtube Bookmarks", + "version": "1.0.0", + "description": "Saving timestamps in YouTube videos", + "permissions": ["storage", "tabs"], + "host_permissions": ["https://*.youtube.com/*"], + "background": { + "service_worker": "background.js" + }, + "content_scripts": [ + { + "matches": ["https://*.youtube.com/*"], + "js": ["contentScript.js"] + } + ], + "web_accessible_resources": [ + { + "resources": [ + "assets/bookmark.png", + "assets/play.png", + "assets/delete.png", + "assets/save.png" + ], + "matches": ["https://*.youtube.com/*"] + } + ], + "action": { + "default_icon": { + "16": "assets/ext-icon.png", + "24": "assets/ext-icon.png", + "32": "assets/ext-icon.png" + }, + "default_title": "My YT Bookmarks", + "default_popup": "popup.html" + }, + "manifest_version": 3 +} diff --git a/popup.css b/popup.css new file mode 100644 index 0000000..2dc0ae4 --- /dev/null +++ b/popup.css @@ -0,0 +1,53 @@ +.container { + width: 280px; + color: #314d3e; +} + +.title { + font-size: 14px; + font-weight: bold; + padding: 8px; +} + +.textbox { + width: 100%; + font-size: 12px; + margin: 0; + padding: 0px 2px; +} + +.textbox:focus { + outline: 0; + border-color: #66afe9; +} + +.bookmarks { + margin: 5px 5px; + padding: 3px; +} + +.bookmark { + display: flex; + border-bottom-color: #00254d; + border-bottom-width: 1px; + border-bottom-style: solid; + border-radius: 0.8rem; + padding: 3px; + padding-bottom: 7px; + margin-bottom: 7px; +} + +.bookmark-title { + padding-left: 2px; +} + +.bookmark-controls img { + margin: 0 4px; + width: 18px; + height: 18px; + cursor: pointer; +} +.bookmark-controls { + flex: auto; + text-align: right; +} diff --git a/popup.html b/popup.html new file mode 100644 index 0000000..7591a8c --- /dev/null +++ b/popup.html @@ -0,0 +1,10 @@ + + + +