├── .gitignore ├── README.md ├── icon.png ├── icon128.png ├── icon16.png ├── icon48.png ├── manifest.json ├── popup.html ├── topics.css └── topics.js /.gitignore: -------------------------------------------------------------------------------- 1 | # Created by .ignore support plugin (hsz.mobi) 2 | ### Node template 3 | # Logs 4 | logs 5 | *.log 6 | npm-debug.log* 7 | yarn-debug.log* 8 | yarn-error.log* 9 | 10 | # Runtime data 11 | pids 12 | *.pid 13 | *.seed 14 | *.pid.lock 15 | 16 | # Directory for instrumented libs generated by jscoverage/JSCover 17 | lib-cov 18 | 19 | # Coverage directory used by tools like istanbul 20 | coverage 21 | 22 | # nyc test coverage 23 | .nyc_output 24 | 25 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 26 | .grunt 27 | 28 | # Bower dependency directory (https://bower.io/) 29 | bower_components 30 | 31 | # node-waf configuration 32 | .lock-wscript 33 | 34 | # Compiled binary addons (http://nodejs.org/api/addons.html) 35 | build/Release 36 | 37 | # Dependency directories 38 | node_modules/ 39 | jspm_packages/ 40 | 41 | # Typescript v1 declaration files 42 | typings/ 43 | 44 | # Optional npm cache directory 45 | .npm 46 | 47 | # Optional eslint cache 48 | .eslintcache 49 | 50 | # Optional REPL history 51 | .node_repl_history 52 | 53 | # Output of 'npm pack' 54 | *.tgz 55 | 56 | # Yarn Integrity file 57 | .yarn-integrity 58 | 59 | # dotenv environment variables file 60 | .env 61 | 62 | .idea 63 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Auto-github-topics 2 | 3 | Chrome extension to fetch **keywords** from `package.json` and set it as topics. 4 | 5 | ![Button](http://i1152.photobucket.com/albums/p486/Artem_Yavorsky/Screen%20Shot%202017-02-07%20at%2016.17.39_zpslamyf1ig.png) 6 | 7 | Available for free in [Chrome web store](https://chrome.google.com/webstore/detail/auto-topics/gajhcgghaanikfoalinhplemloclljek). 8 | 9 | ### Demo: 10 | ![Demo](https://media.giphy.com/media/3o6Yg0a9r0eGce4yju/source.gif). 11 | -------------------------------------------------------------------------------- /icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yavorsky/auto-github-topics/550b9cc4eb259b91ec571c08fd821f16c47e34f9/icon.png -------------------------------------------------------------------------------- /icon128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yavorsky/auto-github-topics/550b9cc4eb259b91ec571c08fd821f16c47e34f9/icon128.png -------------------------------------------------------------------------------- /icon16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yavorsky/auto-github-topics/550b9cc4eb259b91ec571c08fd821f16c47e34f9/icon16.png -------------------------------------------------------------------------------- /icon48.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yavorsky/auto-github-topics/550b9cc4eb259b91ec571c08fd821f16c47e34f9/icon48.png -------------------------------------------------------------------------------- /manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "manifest_version": 2, 3 | 4 | "name": "auto-topics", 5 | "description": "Create github topics based on the package.json", 6 | "version": "0.2", 7 | "page_action": { 8 | "default_icon": "icon.png" 9 | }, 10 | "permissions": [ 11 | "activeTab", 12 | "tabs", 13 | "http://www.github.com/*", "http://github.com/*", 14 | "https://www.github.com/*", "https://github.com/*" 15 | ], 16 | "icons": { 17 | "16": "icon16.png", 18 | "48": "icon48.png", 19 | "128": "icon128.png" 20 | }, 21 | "content_scripts": [ 22 | { 23 | "matches": ["https://github.com/*/*"], 24 | "css": ["topics.css"], 25 | "js": ["topics.js"] 26 | } 27 | ] 28 | } 29 | -------------------------------------------------------------------------------- /popup.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Getting Started Extension's Popup 5 | 38 | 39 | 40 | 41 | 45 | Created with ❤️ by yavorsky 46 | 47 | 48 | -------------------------------------------------------------------------------- /topics.css: -------------------------------------------------------------------------------- 1 | .container .repository-content .btn-link.bth-sync { 2 | margin-left: 4px; 3 | font-size: 12px; 4 | color: #6cc644; 5 | } 6 | -------------------------------------------------------------------------------- /topics.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | const createTopic = keyword => { 3 | const $li = document.createElement('li'); 4 | $li.className = 'topic-tag-action f6 float-left js-tag-input-tag'; 5 | const $button = document.createElement('button'); 6 | $button.className = 'delete-topic-button f5 no-underline ml-2 js-remove'; 7 | $button.tabIndex = -1; 8 | $button.textContent = '×'; 9 | const $input = document.createElement('input'); 10 | $input.name = 'repo_topics[]'; 11 | $input.value = keyword; 12 | $input.hidden = true; 13 | $li.append(keyword, $button, $input); 14 | return $li; 15 | }; 16 | const handleSyncClicked = (jsonPath, evt) => { 17 | evt.preventDefault(); 18 | jsonPath = jsonPath 19 | .replace('github', 'raw.githubusercontent') 20 | .replace('/blob', ''); 21 | fetch(jsonPath).then(r => r.json()).then(({ keywords }) => { 22 | document 23 | .querySelector('.btn-link.js-repo-topics-form-toggle.js-details-target') 24 | .click(); 25 | if (!keywords || !keywords.length) return; 26 | 27 | const check = () => { 28 | const $formUl = document.querySelector('.js-tag-input-selected-tags'); 29 | if (!$formUl) return; 30 | 31 | const formValues = Array.from( 32 | $formUl.querySelectorAll('input'), 33 | input => input.value 34 | ); 35 | const topics = keywords 36 | .filter(kw => !formValues.includes(kw)) 37 | .map(createTopic); 38 | $formUl.append(...topics); 39 | 40 | clearInterval(interval); 41 | // $form.submit(); 42 | }; 43 | 44 | const interval = setInterval(check, 1000); 45 | }); 46 | }; 47 | 48 | const onStateChange = ($topics, $addTopics, $jsonFile) => { 49 | if (!$topics) { 50 | $topics = document.querySelector('#topics-list-container'); 51 | } 52 | const $topicsSync = document.getElementById('topics-sync'); 53 | if (!$topics || $topicsSync) return; 54 | 55 | if (!$addTopics) { 56 | $addTopics = $topics.querySelector('button.js-repo-topics-form-toggle'); 57 | } 58 | if (!$jsonFile) { 59 | $jsonFile = document.querySelector( 60 | ".file-wrap .files td span a[title='package.json']" 61 | ); 62 | } 63 | if ($addTopics && $jsonFile) { 64 | const $sync = document.createElement('button'); 65 | $sync.id = 'topics-sync'; 66 | $sync.className = 'btn-link bth-sync'; 67 | $sync.textContent = 'Fetch from package.json'; 68 | $topics.append($sync); 69 | $sync.addEventListener( 70 | 'click', 71 | handleSyncClicked.bind(null, $jsonFile.href) 72 | ); 73 | } 74 | }; 75 | 76 | const $topics = document.querySelector('#topics-list-container'); 77 | const $addTopics = $topics.querySelector('button.js-repo-topics-form-toggle'); 78 | const $jsonFile = document.querySelector( 79 | ".file-wrap .files td span a[title='package.json']" 80 | ); 81 | 82 | if ($addTopics && $jsonFile) { 83 | const observer = new MutationObserver(function(mutations) { 84 | const classChanged = mutations.every(mutation => { 85 | return (mutation.attributeName = 'class'); 86 | }); 87 | const $topicsSync = document.getElementById('topics-sync'); 88 | if (classChanged && !$topicsSync) { 89 | onStateChange(); 90 | } 91 | }); 92 | observer.observe($topics, { 93 | attributes: true, 94 | childList: true, 95 | characterData: true 96 | }); 97 | 98 | onStateChange($topics, $addTopics, $jsonFile); 99 | } 100 | })(); 101 | --------------------------------------------------------------------------------