diff --git a/extension/README.md b/extension/README.md new file mode 100644 index 0000000..f603008 --- /dev/null +++ b/extension/README.md @@ -0,0 +1,27 @@ +# GitHub Chat - Chrome Extension + +A Chrome extension that adds a chat widget to GitHub pages, allowing users to discuss repositories directly instead of only creating issues. + +## Features + +- Floating chat toggle button on all GitHub pages +- Real-time chat using Firebase +- Send and delete your own messages +- Persistent user identity per browser +- Clean, GitHub-themed UI + +## Installation (Development) + +1. Open Chrome and navigate to `chrome://extensions/` +2. Enable "Developer mode" (toggle in top-right) +3. Click "Load unpacked" and select this `extension/` directory +4. Visit any GitHub repository page - you'll see the chat button in the bottom-right corner + +## Files + +- `manifest.json` - Chrome extension manifest (Manifest V3) +- `content.js` - Content script that injects toggle + iframe on GitHub pages +- `chat.html` - Chat UI loaded inside the iframe +- `chat.css` - Chat styles (GitHub-themed) +- `chat.js` - Firebase chat logic (messages, real-time updates) +- `icons/` - Extension icons (replace placeholders) diff --git a/extension/chat.css b/extension/chat.css new file mode 100644 index 0000000..d4ae6e1 --- /dev/null +++ b/extension/chat.css @@ -0,0 +1,220 @@ + +* { + margin: 0; + padding: 0; + box-sizing: border-box; +} + +body { + font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Open Sans', Helvetica, Arial, sans-serif; + background: #f6f8fa; + height: 100vh; + overflow: hidden; +} + +.chat { + display: flex; + flex-direction: column; + height: 100%; +} + +.chat-title { + background: #2da44e; + color: white; + padding: 12px 16px; + display: flex; + align-items: center; + gap: 10px; + flex-shrink: 0; +} + +.chat-title h1 { + font-size: 16px; + font-weight: 600; + margin: 0; +} + +.chat-title h2 { + font-size: 11px; + font-weight: 400; + opacity: 0.85; + margin: 0; +} + +.chat-title .avatar { + width: 36px; + height: 36px; + border-radius: 50%; + overflow: hidden; + flex-shrink: 0; + margin-left: auto; +} + +.chat-title .avatar img { + width: 100%; + height: 100%; + object-fit: cover; +} + +.chat-title .btn-minimize { + background: rgba(255,255,255,0.2); + color: white; + border: none; + width: 28px; + height: 28px; + border-radius: 6px; + font-size: 18px; + line-height: 1; + cursor: pointer; + display: flex; + align-items: center; + justify-content: center; + margin-left: auto; + margin-right: 8px; + flex-shrink: 0; +} + +.chat-title .btn-minimize:hover { + background: rgba(255,255,255,0.35); +} + +.messages { + flex: 1; + overflow-y: auto; + padding: 12px; + background: #ffffff; +} + +.messages-content { + display: flex; + flex-direction: column; + gap: 8px; +} + +.message { + max-width: 80%; + padding: 8px 12px; + border-radius: 12px; + font-size: 13px; + line-height: 1.4; + word-wrap: break-word; + position: relative; +} + +.message.self { + align-self: flex-end; + background: #2da44e; + color: white; + border-bottom-right-radius: 4px; +} + +.message.other { + align-self: flex-start; + background: #f0f0f0; + color: #24292f; + border-bottom-left-radius: 4px; +} + +.message .sender { + font-size: 11px; + font-weight: 600; + margin-bottom: 2px; + opacity: 0.7; +} + +.message .btn-delete { + background: rgba(0,0,0,0.15); + color: inherit; + border: none; + padding: 2px 8px; + border-radius: 4px; + font-size: 11px; + cursor: pointer; + margin-top: 4px; + display: inline-block; +} + +.message .btn-delete:hover { + background: rgba(0,0,0,0.3); +} + +.message-box { + padding: 10px 12px; + background: #ffffff; + border-top: 1px solid #d0d7de; + display: flex; + gap: 8px; + flex-shrink: 0; +} + +.message-input { + flex: 1; + padding: 8px 12px; + border: 1px solid #d0d7de; + border-radius: 20px; + font-size: 13px; + font-family: inherit; + resize: none; + outline: none; + min-height: 20px; +} + +.message-input:focus { + border-color: #2da44e; + box-shadow: 0 0 0 2px rgba(45,164,78,0.2); +} + +.message-submit { + background: #2da44e; + color: white; + border: none; + padding: 8px 16px; + border-radius: 20px; + font-size: 13px; + font-weight: 600; + cursor: pointer; + white-space: nowrap; +} + +.message-submit:hover { + background: #2c974b; +} + +.message-submit:active { + background: #238636; +} + +/* Scrollbar styling */ +.messages::-webkit-scrollbar { + width: 6px; +} + +.messages::-webkit-scrollbar-track { + background: transparent; +} + +.messages::-webkit-scrollbar-thumb { + background: #d0d7de; + border-radius: 3px; +} + +/* Deleted message style */ +.message.deleted { + font-style: italic; + opacity: 0.5; +} + +/* Loading and error states */ +.loading { + text-align: center; + color: #656d76; + padding: 20px; + font-size: 13px; +} + +.error-msg { + text-align: center; + color: #cf222e; + padding: 20px; + font-size: 13px; +} diff --git a/extension/chat.html b/extension/chat.html new file mode 100644 index 0000000..e274cfd --- /dev/null +++ b/extension/chat.html @@ -0,0 +1,31 @@ + + + + + + + +
+
+
+

GitHub Chat

+

Ask questions, discuss repos

+
+ +
+ +
+
+
+
+
Loading messages...
+
+
+
+ + +
+
+ + + \ No newline at end of file diff --git a/extension/chat.js b/extension/chat.js new file mode 100644 index 0000000..01db2d4 --- /dev/null +++ b/extension/chat.js @@ -0,0 +1,128 @@ + +// Firebase configuration +const firebaseConfig = { + apiKey: "", + authDomain: "inquid-chat.firebaseapp.com", + databaseURL: "https://inquid-chat-default-rtdb.firebaseio.com", + projectId: "inquid-chat", + storageBucket: "inquid-chat.appspot.com", + messagingSenderId: "771474336667", + appId: "1:771474336667:web:819d6a6fe187018f0a0f3e", + measurementId: "G-2SRNLJ8J2X" +}; + +// Initialize Firebase +firebase.initializeApp(firebaseConfig); +const db = firebase.firestore(); + +// Get or set user name +let myName = localStorage.getItem('github-chat-name'); +if (!myName) { + myName = 'GitHub User ' + Math.floor(Math.random() * 10000); + localStorage.setItem('github-chat-name', myName); +} + +const messagesContent = document.getElementById('messages-content'); +const messageInput = document.getElementById('message'); + +// Handle message deletion from realtime database +firebase.database().ref("messages").on("child_removed", function (snapshot) { + const el = document.getElementById("message-" + snapshot.key); + if (el) { + el.classList.add('deleted'); + el.innerHTML = "This message has been deleted"; + } +}); + +// Delete a message +function deleteMessage(self) { + const messageId = self.getAttribute("data-id"); + db.collection("messages").doc(messageId).delete() + .then(() => console.log("Document deleted")) + .catch((error) => console.error("Error removing document: ", error)); +} + +// Send a message +function sendMessage() { + const message = messageInput.value.trim(); + if (!message) return; + + db.collection("messages").add({ + message: message, + sender: myName, + timestamp: firebase.firestore.FieldValue.serverTimestamp() + }) + .then((docRef) => { + console.log("Message sent: ", docRef.id); + messageInput.value = ""; + messageInput.style.height = 'auto'; + }) + .catch((error) => { + console.error("Error sending message: ", error); + }); + + return false; +} + +// Auto-resize textarea +messageInput.addEventListener('input', function() { + this.style.height = 'auto'; + this.style.height = Math.min(this.scrollHeight, 120) + 'px'; +}); + +// Send on Enter (Shift+Enter for newline) +messageInput.addEventListener('keydown', function(e) { + if (e.key === 'Enter' && !e.shiftKey) { + e.preventDefault(); + sendMessage(); + } +}); + +// Listen for new messages +db.collection("messages").orderBy("timestamp", "asc").limit(100) + .onSnapshot((snapshot) => { + messagesContent.innerHTML = ''; + + if (snapshot.empty) { + messagesContent.innerHTML = '
No messages yet. Start the conversation!
'; + return; + } + + snapshot.forEach((doc) => { + const data = doc.data(); + const isSelf = data.sender === myName; + + const msgEl = document.createElement('div'); + msgEl.className = 'message ' + (isSelf ? 'self' : 'other'); + msgEl.id = 'message-' + doc.id; + + msgEl.innerHTML = ` +
${escapeHtml(data.sender || 'Anonymous')}
+
${escapeHtml(data.message || '')}
+ ${isSelf ? `` : ''} + `; + + messagesContent.appendChild(msgEl); + }); + + // Scroll to bottom + const msgContainer = document.querySelector('.messages'); + msgContainer.scrollTop = msgContainer.scrollHeight; + }, (error) => { + messagesContent.innerHTML = `
Error loading messages: ${escapeHtml(error.message)}
`; + }); + +// Helper +function escapeHtml(text) { + const div = document.createElement('div'); + div.textContent = text; + return div.innerHTML; +} + +// Minimize button +const btnMinimize = document.getElementById('btn-minimize'); +if (btnMinimize) { + btnMinimize.addEventListener('click', function() { + parent.postMessage({ action: 'minimize-chat' }, '*'); + }); +} diff --git a/extension/content.js b/extension/content.js new file mode 100644 index 0000000..2241aaa --- /dev/null +++ b/extension/content.js @@ -0,0 +1,109 @@ + +// GitHub Chat - Content Script +(function() { + if (document.getElementById('github-chat-container')) return; // Already injected + + // Create toggle button + const toggleBtn = document.createElement('div'); + toggleBtn.id = 'github-chat-toggle'; + toggleBtn.innerHTML = '💬'; + toggleBtn.title = 'Open GitHub Chat'; + toggleBtn.style.cssText = ` + position: fixed; + bottom: 20px; + right: 20px; + z-index: 9999; + width: 56px; + height: 56px; + border-radius: 50%; + background: #2da44e; + color: white; + font-size: 24px; + display: flex; + align-items: center; + justify-content: center; + cursor: pointer; + box-shadow: 0 4px 12px rgba(0,0,0,0.3); + transition: transform 0.2s, box-shadow 0.2s; + user-select: none; + border: 2px solid white; + `; + toggleBtn.addEventListener('mouseenter', () => { + toggleBtn.style.transform = 'scale(1.1)'; + toggleBtn.style.boxShadow = '0 6px 16px rgba(0,0,0,0.4)'; + }); + toggleBtn.addEventListener('mouseleave', () => { + toggleBtn.style.transform = 'scale(1)'; + toggleBtn.style.boxShadow = '0 4px 12px rgba(0,0,0,0.3)'; + }); + + // Create chat panel container + const chatContainer = document.createElement('div'); + chatContainer.id = 'github-chat-container'; + chatContainer.style.cssText = ` + position: fixed; + bottom: 90px; + right: 20px; + z-index: 9998; + width: 380px; + height: 520px; + border-radius: 12px; + box-shadow: 0 8px 32px rgba(0,0,0,0.3); + display: none; + overflow: hidden; + background: white; + border: 1px solid #d0d7de; + `; + + // Create chat iframe + const chatIframe = document.createElement('iframe'); + chatIframe.id = 'github-chat-iframe'; + chatIframe.src = chrome.runtime.getURL('chat.html'); + chatIframe.style.cssText = ` + width: 100%; + height: 100%; + border: none; + `; + chatContainer.appendChild(chatIframe); + + // Toggle functionality + let isOpen = false; + toggleBtn.addEventListener('click', () => { + isOpen = !isOpen; + chatContainer.style.display = isOpen ? 'block' : 'none'; + toggleBtn.innerHTML = isOpen ? '✕' : '💬'; + toggleBtn.style.background = isOpen ? '#cf222e' : '#2da44e'; + toggleBtn.title = isOpen ? 'Close GitHub Chat' : 'Open GitHub Chat'; + }); + + // Append to page + document.body.appendChild(toggleBtn); + document.body.appendChild(chatContainer); + + // Restore state + chrome.storage.local.get(['chatOpen'], (result) => { + if (result.chatOpen) { + toggleBtn.click(); + } + }); + + // Save state + const observer = new MutationObserver(() => { + chrome.storage.local.set({ chatOpen: isOpen }); + }); + observer.observe(chatContainer, { attributes: true, attributeFilter: ['style'] }); + + // Listen for minimize from iframe + window.addEventListener('message', (event) => { + if (event.data && event.data.action === 'minimize-chat') { + if (isOpen) { + isOpen = false; + chatContainer.style.display = 'none'; + toggleBtn.innerHTML = '💬'; + toggleBtn.style.background = '#2da44e'; + toggleBtn.title = 'Open GitHub Chat'; + chrome.storage.local.set({ chatOpen: false }); + } + } + }); +})(); diff --git a/extension/icons/README.txt b/extension/icons/README.txt new file mode 100644 index 0000000..c23cc5d --- /dev/null +++ b/extension/icons/README.txt @@ -0,0 +1,2 @@ +Replace with actual icon PNGs. Use the GitHub Chat logo or a chat bubble icon. +Recommended: Use a simple chat bubble emoji converted to PNG at 16x16, 48x48, 128x128. diff --git a/extension/icons/icon128.png b/extension/icons/icon128.png new file mode 100644 index 0000000..2a8c070 Binary files /dev/null and b/extension/icons/icon128.png differ diff --git a/extension/icons/icon16.png b/extension/icons/icon16.png new file mode 100644 index 0000000..aea5c11 Binary files /dev/null and b/extension/icons/icon16.png differ diff --git a/extension/icons/icon48.png b/extension/icons/icon48.png new file mode 100644 index 0000000..88aa511 Binary files /dev/null and b/extension/icons/icon48.png differ diff --git a/extension/manifest.json b/extension/manifest.json new file mode 100644 index 0000000..9216755 --- /dev/null +++ b/extension/manifest.json @@ -0,0 +1,44 @@ +{ + "manifest_version": 3, + "name": "GitHub Chat", + "version": "1.0.0", + "description": "Chat with repository owners directly on GitHub. Ask questions and discuss instead of just creating issues.", + "permissions": [ + "storage" + ], + "host_permissions": [ + "https://github.com/*" + ], + "content_scripts": [ + { + "matches": [ + "https://github.com/*" + ], + "js": [ + "content.js" + ], + "css": [ + "chat.css" + ], + "run_at": "document_end" + } + ], + "web_accessible_resources": [ + { + "resources": [ + "chat.html", + "chat.css", + "chat.js", + "icons/*" + ], + "matches": [ + "https://github.com/*" + ] + } + ], + "icons": { + "16": "icons/icon16.png", + "48": "icons/icon48.png", + "128": "icons/icon128.png" + } +} \ No newline at end of file