From b4e1073347ba9a662381ee3774a48eb021b3edb0 Mon Sep 17 00:00:00 2001 From: martrend33 <145278472+martrend33@users.noreply.github.com> Date: Tue, 9 Jun 2026 16:52:13 -0600 Subject: [PATCH 1/7] Add GitHub Chat extension manifest --- manifest.json | 60 ++++++++++++++++++++++++--------------------------- 1 file changed, 28 insertions(+), 32 deletions(-) diff --git a/manifest.json b/manifest.json index db0a183..1fc952e 100644 --- a/manifest.json +++ b/manifest.json @@ -1,36 +1,32 @@ + { - "name": "GitHub Stories", - "version": "0.1", - "description": "Stories on GitHub", - "permissions": [ - "activeTab", - "declarativeContent", - "storage" - ], - "background": { - "scripts": [ - "background.js" + + "name": "GitHub Chat", + "version": "0.2.0", + "description": "Open GitHub Chat from any GitHub page.", + "manifest_version": 3, + "permissions": [ + "storage" ], - "persistent": false - }, - "page_action": {}, - "content_scripts": [ - { - "matches": [ - "https://github.com/", - "https://github.com/orgs/*" - ], - "js": [ - "content-script.js" - ], - "css": [ - "story-list.css", - "story-view.css" - ] + "host_permissions": [ + "https://github.com/*" + ], + "content_scripts": [ + { + "matches": [ + "https://github.com/*" + ], + "js": [ + "content-script.js" + ], + "css": [ + "github-chat.css" + ], + "run_at": "document_idle" + } + ], + "options_ui": { + "page": "options.html", + "open_in_tab": false } - ], - "icons": { - "128": "github-stories.png" - }, - "manifest_version": 2 } From 70547e008581b1ea1574ae61d5a86ba0a0b268e9 Mon Sep 17 00:00:00 2001 From: martrend33 <145278472+martrend33@users.noreply.github.com> Date: Tue, 9 Jun 2026 16:53:32 -0600 Subject: [PATCH 2/7] Update README.mdDocument GitHub Chat extension --- README.md | 45 ++++++++++++++++----------------------------- 1 file changed, 16 insertions(+), 29 deletions(-) diff --git a/README.md b/README.md index 0af0dd3..384f00b 100644 --- a/README.md +++ b/README.md @@ -1,29 +1,16 @@ -# GitHub Stories - -View stories on your GitHub dashboard. - -No uploads required. Just [install](#how-to-install) and visit [github.com](https://github.com). - -![GitHub Stories Demo](./github-stories.gif) - -## How to Install - -1. [Download ZIP](https://github.com/inquid/github-stories/archive/master.zip) and unzip on your computer. - -2. Visit `chrome://extensions` (or click Menu -> Tools -> Extensions). - -3. Enable Developer mode by ticking the checkbox in the upper-right corner. - -4. Click on the "Load unpacked" button in the top-left corner. - -5. Select the downloaded folder. - -## Frequently Asked Questions - -**Q: Why?** - -Why not? - -## Disclaimer - -Free, fun side project. Not affiliated with GitHub. + GitHub Chat Extension + + This Chrome extension adds a small chat toggle to the lower-right corner of every `github.com/*` page. Opening the toggle displays GitHub Chat in an iframe when the configured chat endpoint allows embedding, and passes the current GitHub URL as `github_url`. + + ## Install locally + + 1. Open `chrome://extensions`. + 2. 2. Enable Developer mode. + 3. 3. Click "Load unpacked". + 4. 4. Select this folder. + 5. 5. Visit any `https://github.com/*` page and click the "Chat" button. + + 6. ## Configure the chat endpoint + + 7. The default chat endpoint is `https://github-chat.com/`. That host currently blocks iframe embedding, so the extension shows an "Open Chat" fallback for the default URL. To use an embeddable chat endpoint, open the extension details page, select "Extension options", and save the preferred chat URL. + 8. From a8913cc2b72470841910fe043d23e1572f9dc03c Mon Sep 17 00:00:00 2001 From: martrend33 <145278472+martrend33@users.noreply.github.com> Date: Tue, 9 Jun 2026 16:56:24 -0600 Subject: [PATCH 3/7] Update content-script.jsAdd GitHub Chat toggle content script --- content-script.js | 475 ++++++++++------------------------------------ 1 file changed, 95 insertions(+), 380 deletions(-) diff --git a/content-script.js b/content-script.js index 78cdb14..67d496d 100644 --- a/content-script.js +++ b/content-script.js @@ -1,397 +1,112 @@ -let tries = 0; - -let stories; -let storyList = []; -let storyViewIntervalId = null; -let progressBarIntervalId = null; -let automaticSliderIntervalId = null; - -const emojis = { - added: '👋', - made: '➕', - starred: '⭐', - 'pushed to': '🚀', - 'started following': '🚶', - forked: '🤓', - deleted: '❌', - 'commented on': '💬', - left: '💬', - created: '🏁', - closed: '🚫', - opened: '✨', - merged: '📦', - released: '🏷', - bot: '🤖' -}; - -let time = 200; -const AUTOMATIC_SCROLL_DELAY = 4200; -const PROGRESS_BAR_UPDATE_DELAY = 100; -const UPDATE_PROGRESS_BAR_VALUE = - time / (AUTOMATIC_SCROLL_DELAY / PROGRESS_BAR_UPDATE_DELAY - 5); -let storyViewOpen = false; - -function getActionFullText(element){ - let fullText = ''; - - if(element.getElementsByClassName('repo-description')[0] != null){ - fullText += ' ' + element.getElementsByClassName('repo-description')[0].innerText; - } - - if(element.getElementsByClassName('rpt-1')[0] != null){ - fullText += ' ' + element.getElementsByClassName('pt-1')[0].innerText; - } - - console.log(element); - - return fullText; +onst DEFAULT_CHAT_URL = 'https://github-chat.com/'; +const STORAGE_KEY = 'githubChatUrl'; +const ROOT_ID = 'github-chat-extension-root'; + +function buildChatUrl(baseUrl) { + const url = new URL(baseUrl); + url.searchParams.set('source', 'extension'); + url.searchParams.set('github_url', window.location.href); + return url.toString(); +} + +function shouldUseFallback(baseUrl) { + try { + return new URL(baseUrl).hostname === 'github-chat.com'; + } catch (error) { + return true; + } } -const handle = setInterval(() => { - let dashboardCards; - try { - dashboardCards = document - .getElementById('dashboard') - .querySelector( - 'div[data-repository-hovercards-enabled]:not(.js-recent-activity-container)', - ).children; - } catch (error) { - if (tries++ === 20) { - clearInterval(handle); - return; - } - return; +function createChatFrame(chatUrl) { + const useFallback = shouldUseFallback(chatUrl); + const root = document.createElement('div'); + root.id = ROOT_ID; + root.className = 'github-chat-extension is-collapsed'; + + const toggle = document.createElement('button'); + toggle.type = 'button'; + toggle.className = 'github-chat-extension__toggle'; + toggle.setAttribute('aria-expanded', 'false'); + toggle.setAttribute('aria-controls', 'github-chat-extension-panel'); + toggle.textContent = 'Chat'; + + const panel = document.createElement('section'); + panel.id = 'github-chat-extension-panel'; + panel.className = 'github-chat-extension__panel'; + panel.setAttribute('aria-label', 'GitHub Chat'); + + const header = document.createElement('div'); + header.className = 'github-chat-extension__header'; + header.textContent = 'GitHub Chat'; + + const close = document.createElement('button'); + close.type = 'button'; + close.className = 'github-chat-extension__close'; + close.setAttribute('aria-label', 'Close GitHub Chat'); + close.textContent = 'x'; + + const iframe = document.createElement('iframe'); + iframe.className = 'github-chat-extension__frame'; + iframe.src = buildChatUrl(chatUrl); + iframe.title = 'GitHub Chat'; + iframe.loading = 'lazy'; + iframe.referrerPolicy = 'strict-origin-when-cross-origin'; + iframe.setAttribute('allow', 'clipboard-read; clipboard-write'); + + const fallback = document.createElement('div'); + fallback.className = 'github-chat-extension__fallback'; + + const fallbackMessage = document.createElement('p'); + fallbackMessage.textContent = 'This chat endpoint cannot be embedded on GitHub.'; + + const fallbackLink = document.createElement('a'); + fallbackLink.className = 'github-chat-extension__fallback-link'; + fallbackLink.href = buildChatUrl(chatUrl); + fallbackLink.target = '_blank'; + fallbackLink.rel = 'noopener noreferrer'; + fallbackLink.textContent = 'Open Chat'; + + fallback.append(fallbackMessage, fallbackLink); + + if (useFallback) { + fallback.classList.add('is-visible'); } - clearInterval(handle); - stories = Array.prototype.slice - .call(dashboardCards) - .filter((element) => element.nodeName === 'DIV') - .map((element) => { - const [userName, action, repoOrUserName] = element.textContent - .split('\n') - .map((str) => str.trim()) - .filter((str) => str !== ''); - - const themeID = Math.floor(Math.random() * 18); // 0 - 17 - - let userImageUrl = 'https://image.flaticon.com/icons/png/512/25/25231.png'; - if(element.querySelector('.avatar.avatar-user')!=null){ - userImageUrl = element.querySelector('.avatar.avatar-user').src; - } + const fallbackTimer = window.setTimeout(() => { + fallback.classList.add('is-visible'); + }, 2500); - let actionFull = getActionFullText(element); - - return { - userImageURL: userImageUrl, - userName, - action, - actionFull, - repoOrUserName, - repoOrUserURL: getGithubURL(repoOrUserName), - themeID, - }; - }) - .filter( - (story) => - story.action !== 'created a' && - !story.action.includes('and') && - !story.repoOrUserName.includes('repositories'), - ); - - const batchStories = []; - stories.forEach((story) => { - let index = 0; - const belongsToBatch = batchStories.some((batchStory, idx) => { - if (batchStory[0].userName === story.userName) { - index = idx; - return true; - } - }); - if (belongsToBatch) { - batchStories[index].push(story); - } else { - batchStories.push([story]); - } + iframe.addEventListener('load', () => { + if (!useFallback) { + window.clearTimeout(fallbackTimer); + } }); - storyList = [...batchStories]; - - const storyListView = getStoryListView({ stories: storyList }); - document.querySelector('.news').prepend(getStoryViewer()); - document.querySelector('.news').prepend(storyListView); -}, 1000); - -function onClickStoryBtn(event) { - const path = event.path; - const buttonElem = path.find((element) => element.className === 'user-story'); - const storyID = buttonElem.getAttribute('story-id'); - - const batchStory = storyList[storyID]; - updateSingleStoryView(batchStory[0], storyID, 0); -} - -function getStoryListView({ stories }) { - const storyListWrapperElem = document.createElement('div'); - storyListWrapperElem.classList.add('stories-list-wrapper'); - - const storyListElement = document.createElement('div'); - storyListElement.classList.add('stories-list'); - - stories.forEach((singleStoryBatch, index) => { - let story = singleStoryBatch[0]; - - const userStoryElem = document.createElement('div'); - userStoryElem.classList.add('user-story'); - userStoryElem.setAttribute('story-id', index); - - { - const btnElem = document.createElement('button'); - btnElem.innerHTML = `
- ${story.userName} -
-
${story.userName}
`; - btnElem.addEventListener('click', onClickStoryBtn); - userStoryElem.appendChild(btnElem); - } + function setOpen(isOpen) { + root.classList.toggle('is-collapsed', !isOpen); + toggle.setAttribute('aria-expanded', String(isOpen)); + } - storyListElement.appendChild(userStoryElem); + toggle.addEventListener('click', () => { + setOpen(root.classList.contains('is-collapsed')); }); - storyListWrapperElem.appendChild(storyListElement); - - return storyListWrapperElem; -} - -function getGithubURL(resource) { - return `https://github.com/${resource}`; -} - -function pause() { - //alert(); -} - -function getStoryViewer() { - const storyViewWrapperElem = document.createElement('div'); - storyViewWrapperElem.classList.add('story-view-wrapper', 'hidden'); - storyViewWrapperElem.addEventListener("click", function(event) { - pause(); + close.addEventListener('click', () => { + setOpen(false); }); - storyViewWrapperElem.innerHTML = `
-
-
- - - - -
-
- -
-
-
-
@ starred
-
- - vuejs/docs-next! -
-
-
-
- - - - -
-
-`; - - const storyViewerCloseBtn = storyViewWrapperElem.querySelector( - '.story-view-close-btn', - ); - const storyViewPrevBtn = storyViewWrapperElem.querySelector( - '.story-view-prev', - ); - const storyViewNextBtn = storyViewWrapperElem.querySelector( - '.story-view-next', - ); - - storyViewerCloseBtn.addEventListener('click', handleCloseStoryViewerBtnClick); - storyViewPrevBtn.addEventListener('click', handleStoryViewPrevBtnClick); - storyViewNextBtn.addEventListener('click', handleStoryViewNextBtnClick); - - document.addEventListener('keyup', onPressKey); - window.addEventListener('resize', updateStoryViewWidth); - - return storyViewWrapperElem; -} - -function updateStoryViewWidth() { - const storyViewElem = document.querySelector('.story-view'); - const { height } = storyViewElem.getBoundingClientRect(); - storyViewElem.style.width = `${height / 1.77}px`; + header.appendChild(close); + panel.append(header, fallback, iframe); + root.append(toggle, panel); + document.body.appendChild(root); } -function onPressKey(event) { - if (storyViewOpen) { - switch (event.key) { - case 'Escape': - closeStoryView(); - break; - case 'ArrowLeft': - handleStoryViewPrevBtnClick(); - break; - case 'ArrowRight': - handleStoryViewNextBtnClick(); - break; - } - } -} - -function moveSlide(story, storyID, storyIndex) { - updateSingleStoryView(story, storyID, storyIndex); -} - -function moveToNextSlide(storyID, storyIndex) { - if (storyIndex + 1 >= storyList[storyID].length) { - storyID++; - storyIndex = 0; - } else storyIndex++; - - if (storyID >= storyList.length) return; - - moveSlide(storyList[storyID][storyIndex], storyID, storyIndex); -} -function moveToPrevSlide(storyID, storyIndex) { - if (storyIndex - 1 < 0) { - storyID--; - storyIndex = 0; - } else storyIndex--; - if (storyID < 0) return; - - moveSlide(storyList[storyID][storyIndex], storyID, storyIndex); -} +function init() { + if (document.getElementById(ROOT_ID)) return; -function handleStoryViewNextBtnClick() { - const storyViewer = document.querySelector('.story-view-wrapper'); - let storyID = parseInt(storyViewer.getAttribute('story-id')); - let storyIndex = parseInt(storyViewer.getAttribute('story-index')); - - moveToNextSlide(storyID, storyIndex); -} - -function handleStoryViewPrevBtnClick() { - const storyViewer = document.querySelector('.story-view-wrapper'); - let storyID = storyViewer.getAttribute('story-id'); - let storyIndex = storyViewer.getAttribute('story-index'); - - moveToPrevSlide(storyID, storyIndex); -} - -function handleCloseStoryViewerBtnClick() { - closeStoryView(); -} - -function closeStoryView() { - document.querySelector('.story-view-wrapper').classList.add('hidden'); - clearInterval(storyViewIntervalId); - clearInterval(progressBarIntervalId); - storyViewOpen = false; -} - -function automaticSlideScrolling() { - if (storyViewIntervalId) clearInterval(storyViewIntervalId); - if (progressBarIntervalId) clearInterval(progressBarIntervalId); - handleStoryViewNextBtnClick(); -} - -function updateProgressBarProgress() { - const progressBar = document.querySelector('.ex-progress-bar') - .firstElementChild; - const currValue = parseInt(progressBar.getAttribute('value')); - progressBar.setAttribute( - 'value', - String(currValue + UPDATE_PROGRESS_BAR_VALUE), - ); -} - -function updateProgressBar() { - let initialValue = 0; - let progressBarContainer = document.querySelector('.ex-progress-bar'); - progressBarContainer.innerHTML = ` `; - - if (progressBarIntervalId) clearInterval(progressBarIntervalId); - progressBarIntervalId = setInterval( - updateProgressBarProgress, - PROGRESS_BAR_UPDATE_DELAY, - ); + chrome.storage.sync.get({ [STORAGE_KEY]: DEFAULT_CHAT_URL }, (items) => { + createChatFrame(items[STORAGE_KEY] || DEFAULT_CHAT_URL); + }); } -function updateSingleStoryView(story, storyId, storyIndex) { - const storyViewer = document.querySelector('.story-view-wrapper'); - - const imageLink = storyViewer.querySelector('.story-view-user-img-link'); - imageLink.href = getGithubURL(story.userName); - - const image = storyViewer.querySelector('.story-view-user-img'); - image.src = story.userImageURL; - image.setAttribute('alt', story.userName); - - const name = storyViewer.querySelector('.story-view-user-name'); - name.href = getGithubURL(story.userName); - name.innerText = story.userName; - - const nameOnStory = storyViewer.querySelector('.story-view-user-name-inside'); - nameOnStory.href = getGithubURL(story.userName); - nameOnStory.innerText = story.userName; - - const content = storyViewer.querySelector('.story-view-content'); - content.setAttribute('theme', String(story.themeID)); - - const contentAction = storyViewer.querySelector('.story-view-content-action'); - contentAction.innerHTML = story.action; - - const contentFullAction = storyViewer.querySelector('.story-view-full-text'); - contentFullAction.innerHTML = story.actionFull; - - const contentObject = storyViewer.querySelector('.story-view-content-object') - .firstElementChild; - contentObject.innerText = story.repoOrUserName; - contentObject.href = story.repoOrUserURL; - - const contentEmoji = storyViewer.querySelector('.story-view-content-emoji'); - console.log('story action ->'+story.action+'<-'); - contentEmoji.innerText = emojis[story.action] || ''; - - storyViewer.setAttribute('story-id', storyId); - storyViewer.setAttribute('story-index', storyIndex); - - image.src = story.userImageURL; - name.innerText = story.userName; - name.href = getGithubURL(story.userName); - - if (storyViewIntervalId) clearInterval(storyViewIntervalId); - - updateProgressBar(); - storyViewIntervalId = setInterval( - automaticSlideScrolling, - AUTOMATIC_SCROLL_DELAY, - ); - - document.querySelector('.story-view-wrapper').classList.remove('hidden'); - storyViewOpen = true; - - updateStoryViewWidth(); -} +init(); From ae7d8d3a087c1143b421afe5af8da25df3f4ad07 Mon Sep 17 00:00:00 2001 From: martrend33 <145278472+martrend33@users.noreply.github.com> Date: Tue, 9 Jun 2026 16:59:01 -0600 Subject: [PATCH 4/7] Create github-chat.cssAdd GitHub Chat extension styles --- github-chat.css | 129 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 129 insertions(+) create mode 100644 github-chat.css diff --git a/github-chat.css b/github-chat.css new file mode 100644 index 0000000..1f025aa --- /dev/null +++ b/github-chat.css @@ -0,0 +1,129 @@ +.github-chat-extension { + bottom: 24px; + position: fixed; + right: 24px; + z-index: 2147483647; +} + +.github-chat-extension__toggle, +.github-chat-extension__close { + align-items: center; + background: #24292f; + border: 0; + color: #ffffff; + cursor: pointer; + display: inline-flex; + font: 600 14px/1 -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; + justify-content: center; +} + +.github-chat-extension__toggle { + border-radius: 999px; + box-shadow: 0 8px 24px rgb(140 149 159 / 30%); + height: 44px; + padding: 0 18px; +} + +.github-chat-extension__toggle:hover, +.github-chat-extension__close:hover { + background: #0969da; +} + +.github-chat-extension__panel { + background: #ffffff; + border: 1px solid #d0d7de; + border-radius: 8px; + box-shadow: 0 16px 40px rgb(140 149 159 / 32%); + display: flex; + flex-direction: column; + height: min(680px, calc(100vh - 48px)); + overflow: hidden; + width: min(420px, calc(100vw - 48px)); +} + +.github-chat-extension__header { + align-items: center; + background: #f6f8fa; + border-bottom: 1px solid #d0d7de; + color: #24292f; + display: flex; + font: 600 14px/1 -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; + justify-content: space-between; + min-height: 42px; + padding: 0 8px 0 14px; +} + +.github-chat-extension__close { + border-radius: 6px; + height: 28px; + width: 28px; +} + +.github-chat-extension__frame { + border: 0; + flex: 1; + min-height: 0; + width: 100%; +} + +.github-chat-extension__fallback { + align-items: center; + background: #ffffff; + color: #57606a; + display: none; + flex: 1; + flex-direction: column; + font: 14px/1.4 -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; + gap: 12px; + justify-content: center; + padding: 24px; + text-align: center; +} + +.github-chat-extension__fallback.is-visible { + display: flex; +} + +.github-chat-extension__fallback.is-visible + .github-chat-extension__frame, +.github-chat-extension__fallback.is-visible ~ .github-chat-extension__frame { + display: none; +} + +.github-chat-extension__fallback p { + margin: 0; +} + +.github-chat-extension__fallback-link { + background: #1f883d; + border-radius: 6px; + color: #ffffff; + font-weight: 600; + padding: 9px 14px; + text-decoration: none; +} + +.github-chat-extension__fallback-link:hover { + background: #1a7f37; + color: #ffffff; + text-decoration: none; +} + +.github-chat-extension.is-collapsed .github-chat-extension__panel { + display: none; +} + +.github-chat-extension:not(.is-collapsed) .github-chat-extension__toggle { + display: none; +} + +@media (max-width: 480px) { + .github-chat-extension { + bottom: 12px; + right: 12px; + } + + .github-chat-extension__panel { + height: calc(100vh - 24px); + width: calc(100vw - 24px); + } +} From 6679d5304d38da5f56a6366dbeb722bc7876cd40 Mon Sep 17 00:00:00 2001 From: martrend33 <145278472+martrend33@users.noreply.github.com> Date: Tue, 9 Jun 2026 17:00:25 -0600 Subject: [PATCH 5/7] Create options.htmlAdd GitHub Chat options page --- options.html | 61 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 61 insertions(+) create mode 100644 options.html diff --git a/options.html b/options.html new file mode 100644 index 0000000..b92209e --- /dev/null +++ b/options.html @@ -0,0 +1,61 @@ + + + + + GitHub Chat Options + + + + label> + + button> +

p> + + +html> + + + + From 2d217625ebf3bc86b3c7a98e0903843dad5441d5 Mon Sep 17 00:00:00 2001 From: martrend33 <145278472+martrend33@users.noreply.github.com> Date: Tue, 9 Jun 2026 17:01:24 -0600 Subject: [PATCH 6/7] Create options.jsAdd GitHub Chat options script --- options.js | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) create mode 100644 options.js diff --git a/options.js b/options.js new file mode 100644 index 0000000..e17ad10 --- /dev/null +++ b/options.js @@ -0,0 +1,22 @@ +const DEFAULT_CHAT_URL = 'https://github-chat.com/'; +const STORAGE_KEY = 'githubChatUrl'; +const input = document.getElementById('chat-url'); +const status = document.getElementById('status'); + +chrome.storage.sync.get({ [STORAGE_KEY]: DEFAULT_CHAT_URL }, (items) => { + input.value = items[STORAGE_KEY]; +}); + +document.getElementById('save').addEventListener('click', () => { + if (!input.checkValidity()) { + input.reportValidity(); + return; + } + + chrome.storage.sync.set({ [STORAGE_KEY]: input.value }, () => { + status.textContent = 'Saved.'; + setTimeout(() => { + status.textContent = ''; + }, 1600); + }); +}); From e4acf2f81f4005940bc29b7b1c4be202744edf72 Mon Sep 17 00:00:00 2001 From: martrend33 <145278472+martrend33@users.noreply.github.com> Date: Tue, 9 Jun 2026 17:02:48 -0600 Subject: [PATCH 7/7] Update content-script.jsFix content script first character --- content-script.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/content-script.js b/content-script.js index 67d496d..1048fcd 100644 --- a/content-script.js +++ b/content-script.js @@ -1,4 +1,4 @@ -onst DEFAULT_CHAT_URL = 'https://github-chat.com/'; +const DEFAULT_CHAT_URL = 'https://github-chat.com/'; const STORAGE_KEY = 'githubChatUrl'; const ROOT_ID = 'github-chat-extension-root';