├── src ├── styles │ ├── hn.css │ ├── reddit.css │ ├── quora.css │ └── content.css ├── content │ ├── utils │ │ └── markdown.js │ ├── index.js │ ├── parsers │ │ ├── base.js │ │ ├── reddit.js │ │ ├── quora.js │ │ └── hn.js │ ├── platforms │ │ ├── HNSummarizer.js │ │ ├── RedditSummarizer.js │ │ └── QuoraSummarizer.js │ └── base │ │ └── BaseSummarizer.js ├── services │ ├── ai │ │ └── openai.js │ └── storage.js ├── popup │ ├── popup.css │ ├── popup.html │ └── popup.js ├── background │ └── worker.js └── options │ ├── options.html │ └── options.js ├── public ├── icons │ ├── icon128.png │ ├── icon16.png │ ├── icon32.png │ ├── icon48.png │ └── icon.svg └── manifest.json ├── chrome_store └── images │ ├── hn.jpg │ ├── hn.png │ ├── other.jpg │ ├── other.png │ ├── reddit.jpg │ ├── reddit.png │ ├── other.html │ ├── hn.svg │ ├── reddit.svg │ └── other.svg ├── .gitignore ├── package.json ├── LICENSE ├── .github └── workflows │ └── build.yml ├── webpack.config.js ├── README.md └── CONTRIBUTING.md /src/styles/hn.css: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/icons/icon128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/i365dev/community-tldr/HEAD/public/icons/icon128.png -------------------------------------------------------------------------------- /public/icons/icon16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/i365dev/community-tldr/HEAD/public/icons/icon16.png -------------------------------------------------------------------------------- /public/icons/icon32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/i365dev/community-tldr/HEAD/public/icons/icon32.png -------------------------------------------------------------------------------- /public/icons/icon48.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/i365dev/community-tldr/HEAD/public/icons/icon48.png -------------------------------------------------------------------------------- /chrome_store/images/hn.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/i365dev/community-tldr/HEAD/chrome_store/images/hn.jpg -------------------------------------------------------------------------------- /chrome_store/images/hn.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/i365dev/community-tldr/HEAD/chrome_store/images/hn.png -------------------------------------------------------------------------------- /chrome_store/images/other.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/i365dev/community-tldr/HEAD/chrome_store/images/other.jpg -------------------------------------------------------------------------------- /chrome_store/images/other.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/i365dev/community-tldr/HEAD/chrome_store/images/other.png -------------------------------------------------------------------------------- /chrome_store/images/reddit.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/i365dev/community-tldr/HEAD/chrome_store/images/reddit.jpg -------------------------------------------------------------------------------- /chrome_store/images/reddit.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/i365dev/community-tldr/HEAD/chrome_store/images/reddit.png -------------------------------------------------------------------------------- /public/icons/icon.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Dependencies 2 | node_modules/ 3 | npm-debug.log 4 | yarn-debug.log 5 | yarn-error.log 6 | 7 | # Build 8 | dist/ 9 | build/ 10 | 11 | # IDE 12 | .idea/ 13 | .vscode/ 14 | *.swp 15 | *.swo 16 | 17 | # OS 18 | .DS_Store 19 | Thumbs.db 20 | 21 | # Environment 22 | .env 23 | .env.local 24 | .env.development.local 25 | .env.test.local 26 | .env.production.local 27 | 28 | # Extension specific 29 | *.crx 30 | *.pem 31 | *.zip -------------------------------------------------------------------------------- /src/content/utils/markdown.js: -------------------------------------------------------------------------------- 1 | // Simple markdown renderer 2 | export const renderMarkdown = (text) => { 3 | return text 4 | // Headers 5 | .replace(/^### (.*$)/gm, '
${line}
` : '') 14 | .join('\n'); 15 | }; -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "community-tldr", 3 | "version": "1.1.4", 4 | "description": "AI-powered community discussion summarizer", 5 | "main": "index.js", 6 | "scripts": { 7 | "dev": "webpack --watch --mode=development", 8 | "build": "webpack --mode=production", 9 | "clean": "rimraf dist" 10 | }, 11 | "keywords": ["chrome-extension", "ai", "summarizer"], 12 | "author": "", 13 | "license": "MIT", 14 | "devDependencies": { 15 | "@babel/core": "^7.22.9", 16 | "@babel/preset-env": "^7.22.9", 17 | "babel-loader": "^9.1.3", 18 | "clean-webpack-plugin": "^4.0.0", 19 | "copy-webpack-plugin": "^11.0.0", 20 | "css-loader": "^6.8.1", 21 | "html-webpack-plugin": "^5.5.3", 22 | "rimraf": "^5.0.1", 23 | "style-loader": "^3.3.3", 24 | "webpack": "^5.88.1", 25 | "webpack-cli": "^5.1.4" 26 | } 27 | } -------------------------------------------------------------------------------- /src/content/index.js: -------------------------------------------------------------------------------- 1 | import { RedditSummarizer } from './platforms/RedditSummarizer'; 2 | import { HNSummarizer } from './platforms/HNSummarizer'; 3 | import { QuoraSummarizer } from './platforms/QuoraSummarizer'; 4 | 5 | const PLATFORM_HANDLERS = { 6 | 'news.ycombinator.com': HNSummarizer, 7 | 'reddit.com': RedditSummarizer, 8 | 'www.reddit.com': RedditSummarizer, 9 | 'quora.com': QuoraSummarizer, 10 | 'www.quora.com': QuoraSummarizer 11 | }; 12 | 13 | function initializeSummarizer() { 14 | const hostname = window.location.hostname; 15 | const SummarizerClass = Object.entries(PLATFORM_HANDLERS).find(([domain]) => 16 | hostname.includes(domain) 17 | )?.[1]; 18 | 19 | if (!SummarizerClass) { 20 | console.log('Site not supported'); 21 | return; 22 | } 23 | 24 | const summarizer = new SummarizerClass(); 25 | summarizer.init(); 26 | } 27 | 28 | initializeSummarizer(); 29 | -------------------------------------------------------------------------------- /src/styles/reddit.css: -------------------------------------------------------------------------------- 1 | .tldr-summarize-btn { 2 | display: inline-block; 3 | margin-left: 10px; 4 | font-size: 12px; 5 | color: #666; 6 | cursor: pointer; 7 | text-decoration: none; 8 | } 9 | 10 | .tldr-summarize-btn:hover { 11 | color: #1a1a1b; 12 | text-decoration: underline; 13 | } 14 | 15 | .tldr-highlight { 16 | animation: tldrHighlight 3s ease-out; 17 | } 18 | 19 | @keyframes tldrHighlight { 20 | 0% { 21 | background-color: rgba(255, 102, 0, 0.2); 22 | } 23 | 70% { 24 | background-color: rgba(255, 102, 0, 0.2); 25 | } 26 | 100% { 27 | background-color: transparent; 28 | } 29 | } 30 | 31 | .tldr-summarize-btn { 32 | display: inline-block; 33 | margin-left: 10px; 34 | color: #666; 35 | font-size: 12px; 36 | cursor: pointer; 37 | text-decoration: none; 38 | user-select: none; 39 | } 40 | 41 | .tldr-summarize-btn:hover { 42 | color: #ff4500; 43 | text-decoration: none; 44 | } 45 | 46 | .tldr-btn-container { 47 | display: inline-block; 48 | vertical-align: middle; 49 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 Community TL;DR Contributors 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /src/services/ai/openai.js: -------------------------------------------------------------------------------- 1 | export class OpenAIService { 2 | constructor(apiKey) { 3 | this.apiKey = apiKey; 4 | } 5 | 6 | async summarize(text) { 7 | try { 8 | const response = await fetch('https://api.openai.com/v1/chat/completions', { 9 | method: 'POST', 10 | headers: { 11 | 'Content-Type': 'application/json', 12 | 'Authorization': `Bearer ${this.apiKey}` 13 | }, 14 | body: JSON.stringify({ 15 | model: 'gpt-3.5-turbo', 16 | messages: [ 17 | { 18 | role: 'system', 19 | content: 'You are a helpful assistant that summarizes HN discussions.' 20 | }, 21 | { 22 | role: 'user', 23 | content: `Please summarize this HN discussion: ${text}` 24 | } 25 | ] 26 | }) 27 | }); 28 | 29 | const data = await response.json(); 30 | return data.choices[0].message.content; 31 | } catch (error) { 32 | console.error('OpenAI API error:', error); 33 | throw error; 34 | } 35 | } 36 | } -------------------------------------------------------------------------------- /src/popup/popup.css: -------------------------------------------------------------------------------- 1 | body { 2 | width: 320px; 3 | min-height: 200px; 4 | margin: 0; 5 | font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif; 6 | } 7 | 8 | .popup-container { 9 | min-height: 200px; 10 | } 11 | 12 | /* Animations */ 13 | @keyframes fadeIn { 14 | from { 15 | opacity: 0; 16 | transform: translateY(-10px); 17 | } 18 | to { 19 | opacity: 1; 20 | transform: translateY(0); 21 | } 22 | } 23 | 24 | .fade-in { 25 | animation: fadeIn 0.3s ease-out; 26 | } 27 | 28 | /* Custom scrollbar */ 29 | ::-webkit-scrollbar { 30 | width: 8px; 31 | } 32 | 33 | ::-webkit-scrollbar-track { 34 | background: #f1f1f1; 35 | border-radius: 4px; 36 | } 37 | 38 | ::-webkit-scrollbar-thumb { 39 | background: #888; 40 | border-radius: 4px; 41 | } 42 | 43 | ::-webkit-scrollbar-thumb:hover { 44 | background: #666; 45 | } 46 | 47 | .line-clamp-2 { 48 | display: -webkit-box; 49 | -webkit-line-clamp: 2; 50 | -webkit-box-orient: vertical; 51 | overflow: hidden; 52 | word-break: break-word; 53 | } 54 | 55 | .break-words { 56 | word-break: break-word; 57 | } -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | # .github/workflows/build.yml 2 | 3 | name: Build and Release 4 | 5 | on: 6 | push: 7 | branches: [ main ] 8 | tags: [ 'v*' ] 9 | pull_request: 10 | branches: [ main ] 11 | 12 | jobs: 13 | build: 14 | runs-on: ubuntu-latest 15 | 16 | steps: 17 | - uses: actions/checkout@v3 18 | 19 | - name: Setup Node.js 20 | uses: actions/setup-node@v3 21 | with: 22 | node-version: '18' 23 | cache: 'npm' 24 | 25 | - name: Install dependencies 26 | run: npm ci 27 | 28 | - name: Build extension 29 | run: npm run build 30 | 31 | - name: Create ZIP archive 32 | run: | 33 | cd dist 34 | zip -r ../community-tldr.zip . 35 | 36 | - name: Upload build artifact 37 | uses: actions/upload-artifact@v4 38 | with: 39 | name: extension-build 40 | path: community-tldr.zip 41 | 42 | - name: Create Release 43 | if: startsWith(github.ref, 'refs/tags/v') 44 | uses: softprops/action-gh-release@v1 45 | with: 46 | files: community-tldr.zip 47 | generate_release_notes: true 48 | env: 49 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 50 | -------------------------------------------------------------------------------- /chrome_store/images/other.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 |25 | Visit a supported community to start summarizing discussions: 26 |
27 |${data.message || 'Loading...'}
220 |Invalid provider configuration
'; 152 | return; 153 | } 154 | 155 | // Create provider-specific fields 156 | const fields = config.fields.map(field => this.createField(field)).join(''); 157 | container.innerHTML = fields; 158 | 159 | // Populate field values 160 | this.populateProviderFields(provider); 161 | } 162 | 163 | createField(field) { 164 | const commonClasses = 'w-full border border-gray-300 rounded-md shadow-sm px-3 py-2 mt-1'; 165 | 166 | let input; 167 | switch (field.type) { 168 | case 'select': 169 | input = ` 170 | 175 | `; 176 | break; 177 | 178 | case 'textarea': 179 | input = ` 180 | 186 | `; 187 | break; 188 | 189 | default: 190 | input = ` 191 | 198 | `; 199 | } 200 | 201 | return ` 202 |