├── api ├── public │ └── README.md ├── utils │ ├── createFolder.js │ └── createFile.js └── post.js ├── .gitignore ├── .vscode └── settings.json ├── src ├── assets │ └── flUhRq6tzZclQEJ-Vdg-IuiaDsNc.woff2 ├── comps │ ├── Logo.js │ ├── TextEditor.js │ ├── Header.js │ ├── Icon.js │ └── TextEditorMenu.js ├── pages │ ├── _page.js │ ├── Profile.js │ ├── Editor.js │ └── Auth.js ├── index.html ├── utils │ ├── themeHandler.js │ ├── stateHandler.js │ ├── editorChange.js │ ├── editorLock.js │ ├── editorShortcuts.js │ ├── urlHandler.js │ ├── editorState.js │ ├── editorSave.js │ └── pageHandler.js ├── styles │ ├── theme │ │ ├── light.css │ │ └── dark.css │ └── styles.css ├── background.js ├── main.js └── content.js ├── package.json ├── .example ├── www.youtube.com │ └── www.youtube.css └── www.twitch.tv │ └── www.twitch.js ├── SECURITY.md ├── config.json ├── .github ├── ISSUE_TEMPLATE │ ├── security_report.md │ ├── feature_request.md │ └── bug_report.md └── pull_request_template.md ├── manifest.json ├── server.js ├── LICENSE ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md └── README.md /api/public/README.md: -------------------------------------------------------------------------------- 1 | This is your save directory. -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | package-lock* 3 | api/public/**/*.css 4 | api/public/**/*.js 5 | api/public/**/*.text -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "files.exclude": { 3 | "**/node_modules": true, 4 | "**/package-lock*": true 5 | } 6 | } -------------------------------------------------------------------------------- /src/assets/flUhRq6tzZclQEJ-Vdg-IuiaDsNc.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/startrev/SnipX/HEAD/src/assets/flUhRq6tzZclQEJ-Vdg-IuiaDsNc.woff2 -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "module", 3 | "scripts": { 4 | "start": "nodemon server.js" 5 | }, 6 | "dependencies": { 7 | "body-parser": "^1.20.0", 8 | "express": "^4.18.0", 9 | "nodemon": "^2.0.15" 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/comps/Logo.js: -------------------------------------------------------------------------------- 1 | export default class SnipxLogo extends HTMLElement { 2 | constructor() { 3 | super() 4 | this.innerHTML = `${this.getAttribute('text')}` 5 | } 6 | } 7 | 8 | window.customElements.define('snipx-logo', SnipxLogo) -------------------------------------------------------------------------------- /api/utils/createFolder.js: -------------------------------------------------------------------------------- 1 | import fs from 'fs' 2 | import path from 'path' 3 | 4 | export default function createFolder(entry) { 5 | let _path = path.resolve('api', 'public', entry) 6 | 7 | if(!fs.existsSync(_path)) fs.mkdirSync(_path) 8 | 9 | if(fs.existsSync(_path)) return _path 10 | else return undefined 11 | } -------------------------------------------------------------------------------- /api/utils/createFile.js: -------------------------------------------------------------------------------- 1 | import fs from 'fs' 2 | import path from 'path' 3 | 4 | export default function createFile(entry, filename, filedata) { 5 | let _path = path.join(entry, filename) 6 | 7 | fs.writeFile(_path, filedata, (err) => { 8 | if (err) throw err 9 | console.log("The file was succesfully saved!") 10 | }) 11 | } -------------------------------------------------------------------------------- /src/pages/_page.js: -------------------------------------------------------------------------------- 1 | // Import Pages 2 | import '../pages/Auth.js' 3 | import '../pages/Editor.js' 4 | import '../pages/Profile.js' 5 | 6 | class SnipxPage extends HTMLElement { 7 | constructor() { 8 | super() 9 | this.innerHTML = `` 10 | } 11 | } 12 | 13 | window.customElements.define('snipx-page', SnipxPage) -------------------------------------------------------------------------------- /src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /src/pages/Profile.js: -------------------------------------------------------------------------------- 1 | const profile = 2 | `
3 |
4 |

Profile

5 |

available next update...

6 |
7 |
` 8 | 9 | export default class SnipxProfile extends HTMLElement { 10 | constructor() { 11 | super() 12 | this.innerHTML = profile 13 | } 14 | } 15 | 16 | window.customElements.define('snipx-profile', SnipxProfile) -------------------------------------------------------------------------------- /src/pages/Editor.js: -------------------------------------------------------------------------------- 1 | import '../comps/TextEditor.js' 2 | import '../comps/TextEditorMenu.js' 3 | 4 | const editor = 5 | `
6 | 7 | 8 |
` 9 | 10 | export default class SnipxEditor extends HTMLElement { 11 | constructor() { 12 | super() 13 | this.innerHTML = editor 14 | } 15 | } 16 | 17 | window.customElements.define('snipx-editor', SnipxEditor) -------------------------------------------------------------------------------- /api/post.js: -------------------------------------------------------------------------------- 1 | import express from 'express' 2 | const router = express.Router() 3 | 4 | import createFolder from './utils/createFolder.js' 5 | import createFile from './utils/createFile.js' 6 | 7 | router.post('/', function (req, res, next) { 8 | 9 | let folderExists = createFolder(req.body.entryName) 10 | if(folderExists) createFile(folderExists, req.body.fileName, req.body.fileData) 11 | 12 | res.json({ "from": "postRouter" }) 13 | }) 14 | 15 | export default router -------------------------------------------------------------------------------- /src/utils/themeHandler.js: -------------------------------------------------------------------------------- 1 | export default function themeHandler(node) { 2 | switch(node.innerHTML) { 3 | case 'dark_mode': 4 | chrome.storage.local.set({'theme': 'dark'}) 5 | node.innerHTML = 'light_mode' 6 | window.location.reload() 7 | break 8 | case 'light_mode': 9 | chrome.storage.local.set({'theme': 'light'}) 10 | node.innerHTML = 'dark_mode' 11 | window.location.reload() 12 | break 13 | } 14 | } -------------------------------------------------------------------------------- /.example/www.youtube.com/www.youtube.css: -------------------------------------------------------------------------------- 1 | /* 2 | URL: www.youtube.com 3 | Type: CSS 4 | 5 | The following snippet recolors 6 | Youtube's main components. Dive 7 | deeper to truly make it a custom 8 | experience! 9 | */ 10 | 11 | #meta, 12 | #items, 13 | #container, 14 | #contents, 15 | iron-selector, 16 | ytd-watch-flexy, 17 | .ytd-page-manager, 18 | #guide-inner-content { 19 | background: #292d3e !important; 20 | } 21 | 22 | .header, 23 | .ytd-searchbox-spt { 24 | background: #1b1e2b !important; 25 | } -------------------------------------------------------------------------------- /src/utils/stateHandler.js: -------------------------------------------------------------------------------- 1 | export default function stateHandler(node) { 2 | chrome.storage.local.get(function(result) { 3 | switch(node.innerHTML) { 4 | case 'toggle_on': 5 | chrome.storage.local.set({'state': false}) 6 | node.innerHTML = 'toggle_off' 7 | break 8 | case 'toggle_off': 9 | chrome.storage.local.set({'state': true}) 10 | node.innerHTML = 'toggle_on' 11 | break 12 | } 13 | }) 14 | } -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | # Security Policy 2 | 3 | ## Supported Versions 4 | 5 | This project is still in `alpha`, so support isn't available at the moment. 6 | 7 | | Version | Supported | 8 | | ------- | ------------------ | 9 | | 0.3-alpha | :x: | 10 | | 0.1-alpha | :x: | 11 | 12 | ## Reporting a Vulnerability 13 | 14 | Please submit a [Security Report](https://github.com/startrev/SnipX/issues/new?assignees=arakilian0&labels=security&template=security_report.md&title=%5BSecurity%5D%3A+) with as many details as you can provide. 15 | -------------------------------------------------------------------------------- /src/utils/editorChange.js: -------------------------------------------------------------------------------- 1 | export default function editorChange(event) { 2 | switch(event.target.value) { 3 | case 'css': 4 | chrome.storage.local.set({'editor': 'css'}) 5 | window.location.reload() 6 | break 7 | case 'js': 8 | chrome.storage.local.set({'editor': 'js'}) 9 | window.location.reload() 10 | break 11 | case 'text': 12 | chrome.storage.local.set({'editor': 'text'}) 13 | window.location.reload() 14 | break 15 | } 16 | } -------------------------------------------------------------------------------- /config.json: -------------------------------------------------------------------------------- 1 | { 2 | "server": false, 3 | "port": 2323, 4 | "tabURL": "", 5 | "currentFile": "", 6 | "docs": "https://github.com/startrev/SnipX", 7 | "docsInstall": "https://github.com/startrev/SnipX#getting-started", 8 | "state": true, 9 | "theme": "dark", 10 | "page": "auth", 11 | "editor": "css", 12 | "editorTab": 2, 13 | "editorState": true, 14 | "editorLock": false, 15 | "editorText": { 16 | "css": "/* CSS Editor */", 17 | "js": "// JS Editor", 18 | "text": "# Text Editor" 19 | } 20 | } -------------------------------------------------------------------------------- /src/pages/Auth.js: -------------------------------------------------------------------------------- 1 | export default (function() { 2 | chrome.storage.local.get(function(result) { 3 | const auth = 4 | `
5 |

Your server isn't running.

6 | How to run SnipX 7 |
` 8 | 9 | class SnipxAuth extends HTMLElement { 10 | constructor() { 11 | super() 12 | this.innerHTML = auth 13 | } 14 | } 15 | 16 | window.customElements.define('snipx-auth', SnipxAuth) 17 | }) 18 | })() -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/security_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Security Report 3 | about: Please fill in a Security Report to help us stabalize SnipX. 4 | title: '[Security]: ' 5 | labels: 'security' 6 | assignees: 'arakilian0' 7 | 8 | --- 9 | 10 | **Describe the issue** 11 | A clear and concise description of what the issue may be. 12 | 13 | **Proposed Fix** 14 | What do you propose would eliminate the issue: 15 | 1. Go to '...' 16 | 2. Remove '....' 17 | 3. See error 18 | 19 | **Screenshots** 20 | If applicable, add screenshots to help explain the problem. 21 | 22 | **Additional context** 23 | Add any other context about the problem here. 24 | -------------------------------------------------------------------------------- /src/utils/editorLock.js: -------------------------------------------------------------------------------- 1 | export default function editorLock(node) { 2 | switch(node.innerHTML) { 3 | case 'lock_outline': 4 | chrome.storage.local.set({'editorLock': false}) 5 | node.innerHTML = 'lock_open' 6 | window.location.reload() 7 | document.querySelector('textarea').disabled = false 8 | break 9 | case 'lock_open': 10 | chrome.storage.local.set({'editorLock': true}) 11 | node.innerHTML = 'lock_outline' 12 | window.location.reload() 13 | document.querySelector('textarea').disabled = true 14 | break 15 | } 16 | } -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature Request 3 | about: Have an idea for SnipX? Suggest it here! 4 | title: '[Feat]: ' 5 | labels: 'feat' 6 | assignees: 'jaayperez' 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 12 | 13 | **Describe the solution you'd like** 14 | A clear and concise description of what you want to happen. 15 | 16 | **Describe alternatives you've considered** 17 | A clear and concise description of any alternative solutions or features you've considered. 18 | 19 | **Additional context** 20 | Add any other context or screenshots about the feature request here. 21 | -------------------------------------------------------------------------------- /src/comps/TextEditor.js: -------------------------------------------------------------------------------- 1 | export default (function() { 2 | chrome.storage.local.get(function(result) { 3 | const textEditor = 4 | `` 9 | 10 | class SnipxTextEditor extends HTMLElement { 11 | constructor() { 12 | super() 13 | this.innerHTML = textEditor 14 | this.querySelector('textarea').innerHTML = result.editorText[result.editor] 15 | } 16 | } 17 | 18 | window.customElements.define('snipx-text-editor', SnipxTextEditor) 19 | }) 20 | })() 21 | 22 | -------------------------------------------------------------------------------- /src/utils/editorShortcuts.js: -------------------------------------------------------------------------------- 1 | chrome.storage.local.get(function(result) { 2 | document.addEventListener('keydown', e => { 3 | // if(result.editorLock) { e.preventDefault() } 4 | // if(e.key == 'Tab') { e.preventDefault() } 5 | // if(e.key == 'Control') { 6 | // document.addEventListener('keydown', _e => { 7 | // if(_e.key == 's') { 8 | // if(document.querySelector('#editorSave')) { 9 | // _e.preventDefault() 10 | // document.querySelector('#editorSave').click() 11 | // window.location.reload() 12 | // } 13 | // } 14 | // }) 15 | // } 16 | }) 17 | }) -------------------------------------------------------------------------------- /src/utils/urlHandler.js: -------------------------------------------------------------------------------- 1 | let breakAt = 3 2 | let truncateAt = 25 3 | 4 | export default function urlHandler(url) { 5 | let mainArray = url.split(''), 6 | newArray = [], 7 | slashCount = 0 8 | 9 | for(let i = 0; i <= mainArray.length; i++) { 10 | if(mainArray[i] === '/') slashCount++ 11 | if(slashCount === breakAt) break 12 | newArray.push(mainArray[i]) 13 | } 14 | 15 | let newURL = newArray.join('') 16 | 17 | if(newURL.includes('http://')) return truncateString(newURL.replace('http://', ''), truncateAt) 18 | else return truncateString(newURL.replace('https://', ''), truncateAt) 19 | } 20 | 21 | function truncateString(str, num) { 22 | if(str.length <= num) return str 23 | return str.slice(0, num) + '...' 24 | } -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug Report 3 | about: Please fill in a Bug Report to help us improve SnipX. 4 | title: '[Fix]: ' 5 | labels: 'fix' 6 | assignees: 'arakilian0' 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | A clear and concise description of what the bug is. 12 | 13 | **To Reproduce** 14 | Steps to reproduce the behavior: 15 | 1. Go to '...' 16 | 2. Click on '....' 17 | 3. Scroll down to '....' 18 | 4. See error 19 | 20 | **Expected behavior** 21 | A clear and concise description of what you expected to happen. 22 | 23 | **Screenshots** 24 | If applicable, add screenshots to help explain your problem. 25 | 26 | **Desktop (please complete the following information):** 27 | - OS: [e.g. windows, mac] 28 | - Browser [e.g. chrome, safari] 29 | - Version [e.g. 22] 30 | 31 | **Additional context** 32 | Add any other context about the problem here. 33 | -------------------------------------------------------------------------------- /.example/www.twitch.tv/www.twitch.js: -------------------------------------------------------------------------------- 1 | /* 2 | URL: www.twitch.tv 3 | Type: JavaScript 4 | 5 | The following snippet highlights all 6 | messages written by users listed in 7 | 'target', every half of a second. 8 | */ 9 | 10 | let target = ['StreamElements'] 11 | 12 | let msgs = document.querySelector( 13 | '.chat-scrollable-area__message-container' 14 | ) 15 | 16 | setInterval(function () { 17 | let users = document.getElementsByClassName 18 | ('chat-author__display-name') 19 | 20 | for(let i=0; i < users.length; i++) { 21 | for(let x=0; x < target.length; x++) { 22 | if(users[i].innerHTML == target[x]) { 23 | let el = users[i].parentNode 24 | .parentNode.parentNode.parentNode 25 | .parentNode.parentNode.parentNode 26 | .parentNode 27 | 28 | el.style.background = 'yellow' 29 | el.style.color = 'black' 30 | } 31 | }}}, 500) -------------------------------------------------------------------------------- /manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "SnipX", 3 | "version": "0.4.0", 4 | "manifest_version": 3, 5 | "description": "Injects CSS and JS snippets into your current browser tab.", 6 | "action": {"default_popup": "src/index.html"}, 7 | "background": {"service_worker": "src/background.js"}, 8 | "host_permissions": ["http://localhost:*/"], 9 | "permissions": [ 10 | "unlimitedStorage", 11 | "activeTab", 12 | "scripting", 13 | "storage", 14 | "tabs" 15 | ], 16 | "web_accessible_resources": [{ 17 | "resources": [ 18 | "config.json", 19 | "api/public/*/*.css", 20 | "api/public/*/*.js", 21 | "api/public/*/*.text" 22 | ], 23 | "matches": [""] 24 | }], 25 | "content_scripts": [ 26 | { 27 | "matches": [""], 28 | "js": ["src/content.js"] 29 | } 30 | ] 31 | } -------------------------------------------------------------------------------- /src/styles/theme/light.css: -------------------------------------------------------------------------------- 1 | :root { 2 | /* Color */ 3 | --color-primary: #eee; 4 | --color-secondary: #fff; 5 | --color-text: #292d3e; 6 | --color-hover: #ccc; 7 | 8 | /* Font */ 9 | --fontfamily-primary: Arial; 10 | --fontfamily-seconadry: serif; 11 | --fontfamily-editor: monospace; 12 | 13 | --fontsize-icon: 21px; 14 | --fontsize-logo: 20px; 15 | --fontsize-text: 16px; 16 | --fontsize-link: 14px; 17 | --fontsize-menu-button: 11px; 18 | 19 | --fontweight-bold: 600; 20 | 21 | /* Border */ 22 | --border-radius: 4px; 23 | --border: 1px solid grey; 24 | --border-alt: 1px solid grey; 25 | 26 | /* Spacing */ 27 | --padding-select: 2px; 28 | --padding-icon: 6px; 29 | --padding-button: 6px 12px; 30 | --padding-button-alt: 4px 9px; 31 | --padding-container: 10px; 32 | --padding-editor: 16px; 33 | --padding-editor-menu: 6px 16px; 34 | } -------------------------------------------------------------------------------- /server.js: -------------------------------------------------------------------------------- 1 | // Top Level Imports 2 | import fs from 'fs' 3 | import path from 'path' 4 | import express from 'express' 5 | import bodyParser from 'body-parser' 6 | 7 | // Routes 8 | import postRouter from './api/post.js' 9 | 10 | // Config 11 | const basePath = path.resolve() 12 | const snipxConfigPath = [basePath,'config.json'] 13 | const snipxPublicPath = [basePath, 'api', 'public'] 14 | let snipxConfig = JSON.parse(fs.readFileSync(path.join(...snipxConfigPath))) 15 | 16 | // Express 17 | const app = express() 18 | const port = snipxConfig.port 19 | 20 | // Express Middleware 21 | app.set('views', basePath) 22 | app.use(bodyParser.json()) 23 | app.use(bodyParser.urlencoded({extended: true})) 24 | app.use(express.static(path.join(...snipxPublicPath))) 25 | 26 | // Express Router Routes 27 | app.use('/api/post', postRouter) 28 | 29 | // Express listen on user specified Port 30 | app.listen(port, console.log(`SnipX is live: (port ${port})`)) -------------------------------------------------------------------------------- /src/styles/theme/dark.css: -------------------------------------------------------------------------------- 1 | :root { 2 | /* Color */ 3 | --color-primary: #1b1e2b; 4 | --color-secondary: #292d3e; 5 | --color-text: #757ca1; 6 | --color-hover: #11131b; 7 | 8 | /* Font */ 9 | --fontfamily-primary: Arial; 10 | --fontfamily-seconadry: serif; 11 | --fontfamily-editor: monospace; 12 | 13 | --fontsize-icon: 21px; 14 | --fontsize-logo: 20px; 15 | --fontsize-text: 16px; 16 | --fontsize-link: 14px; 17 | --fontsize-menu-button: 11px; 18 | 19 | --fontweight-bold: 600; 20 | 21 | /* Border */ 22 | --border-radius: 4px; 23 | --border: 1px solid #757ca1; 24 | --border-alt: 1px solid #11131b; 25 | 26 | /* Spacing */ 27 | --padding-select: 2px; 28 | --padding-icon: 6px; 29 | --padding-button: 6px 12px; 30 | --padding-button-alt: 4px 9px; 31 | --padding-container: 10px; 32 | --padding-editor: 16px; 33 | --padding-editor-menu: 6px 16px; 34 | } -------------------------------------------------------------------------------- /src/comps/Header.js: -------------------------------------------------------------------------------- 1 | import './Logo.js' 2 | import './Icon.js' 3 | import manifest from '../../manifest.json' with {type: 'json'} 4 | 5 | chrome.storage.local.get(function(result) { 6 | let theme,page,state,docs = result.docs 7 | 8 | result.state === true ? state = 'toggle_on': state = 'toggle_off' 9 | result.theme === 'dark' ? theme = 'light_mode': theme = 'dark_mode' 10 | result.page === 'profile' ? page = 'arrow_back': page = 'account_circle' 11 | 12 | const header = 13 | `
14 |
15 | 16 |
17 |
18 | 19 | 20 | 21 |
22 |
` 23 | 24 | class SnipxHeader extends HTMLElement { 25 | constructor() { 26 | super() 27 | this.innerHTML = header 28 | } 29 | } 30 | 31 | window.customElements.define('snipx-header', SnipxHeader) 32 | }) 33 | 34 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Start Rev Technology 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. 22 | -------------------------------------------------------------------------------- /src/utils/editorState.js: -------------------------------------------------------------------------------- 1 | export default function editorState(node) { 2 | switch(node.innerHTML) { 3 | case 'visibility': 4 | chrome.storage.local.set({'editorState': false}) 5 | node.innerHTML = 'visibility_off' 6 | window.location.reload() 7 | chrome.tabs.query({active: true, currentWindow: true}, ([currentTab]) => { 8 | chrome.tabs.sendMessage(currentTab.id, {appState: false}, function(response) { 9 | console.log(response.farewell) 10 | }) 11 | }) 12 | // Toggle current editor state 13 | break 14 | case 'visibility_off': 15 | chrome.storage.local.set({'editorState': true}) 16 | node.innerHTML = 'visibility' 17 | window.location.reload() 18 | chrome.tabs.query({active: true, currentWindow: true}, ([currentTab]) => { 19 | chrome.tabs.sendMessage(currentTab.id, {appState: true}, function(response) { 20 | console.log(response.farewell) 21 | }) 22 | }) 23 | // Toggle current editor state 24 | break 25 | } 26 | } -------------------------------------------------------------------------------- /.github/pull_request_template.md: -------------------------------------------------------------------------------- 1 | # Description 2 | 3 | Please include a summary of the change and which issue is fixed. Please also include relevant motivation and context. List any dependencies that are required for this change. 4 | 5 | Fixes # (issue) 6 | 7 | ## Type of change 8 | 9 | Please delete options that are not relevant. 10 | 11 | - [ ] Bug fix (non-breaking change which fixes an issue) 12 | - [ ] New feature (non-breaking change which adds functionality) 13 | - [ ] Breaking change (fix or feature that would cause existing functionality to not work as expected) 14 | - [ ] This change requires a documentation update 15 | 16 | ## How Has This Been Tested? 17 | 18 | Please describe the tests that you ran to verify your changes. Provide instructions so we can reproduce. Please also list any relevant details for your test configuration 19 | 20 | - [ ] Test A 21 | - [ ] Test B 22 | 23 | **Test Configuration**: 24 | * OS: 25 | * Browser: 26 | * Toolchain: 27 | * SDK: 28 | 29 | ## Checklist: 30 | 31 | - [ ] I have performed a self-review of my own code 32 | - [ ] I have commented my code, particularly in hard-to-understand areas 33 | - [ ] I have made corresponding changes to the documentation 34 | - [ ] My changes generate no new warnings 35 | - [ ] I ran tests that prove my fix is effective or that my feature works -------------------------------------------------------------------------------- /src/background.js: -------------------------------------------------------------------------------- 1 | // App Installation 2 | chrome.runtime.onInstalled.addListener(function() { 3 | const snipxConfigURL = chrome.runtime.getURL('config.json') 4 | fetch(snipxConfigURL) 5 | .then(response => response.json()) 6 | .then(json => chrome.storage.local.set(json)) 7 | }) 8 | 9 | // Incognito Access 10 | chrome.extension.isAllowedIncognitoAccess(function(isAllowedAccess) { 11 | if(isAllowedAccess) { 12 | console.log('we have incognito access') 13 | return 14 | } 15 | }) 16 | 17 | // Runtime Events 18 | chrome.runtime.onMessage.addListener( 19 | function(request, sender, sendResponse) { 20 | if(request.injectCSS) { 21 | let 22 | semiPath = request.injectCSS, 23 | cssPath = semiPath + '.css' 24 | 25 | fetch(cssPath) 26 | .then(response => response.text()) 27 | .then(text => sendResponse(text)) 28 | // .catch(error => sendResponse(error)) 29 | 30 | return true 31 | } 32 | if(request.injectJS) { 33 | let 34 | semiPath = request.injectJS, 35 | jsPath = semiPath + '.js' 36 | 37 | fetch(jsPath) 38 | .then(response => response.text()) 39 | .then(text => sendResponse(text)) 40 | // .catch(error => sendResponse(error)) 41 | 42 | return true 43 | } 44 | } 45 | ) -------------------------------------------------------------------------------- /src/utils/editorSave.js: -------------------------------------------------------------------------------- 1 | export default function editorSave(e) { 2 | chrome.storage.local.get(function(result) { 3 | // Check if on valid url(browser specific urls are considered invalid) 4 | if(!result.tabURL.includes(':') && document.querySelector('textarea')) { 5 | let editor = document.querySelector('textarea') 6 | let tabURL = document.querySelector('#tabURL') 7 | // If text editor data doesn't = default value 8 | if(editor.value !== result.editorText[result.editor]) { 9 | let data = JSON.stringify({ 10 | "entryName": tabURL.innerHTML, 11 | "fileName" : `${tabURL.innerHTML}.${result.editor}`, 12 | "fileData": editor.value 13 | }) 14 | 15 | let xhr = new XMLHttpRequest() 16 | xhr.open("POST", `http://localhost:${result.port}/api/post`, true) 17 | xhr.setRequestHeader("Content-type", "application/json") 18 | xhr.send(data) 19 | chrome.storage.local.set({'currentFile': editor.value}) 20 | 21 | if(result.editor === 'css' || result.editor === 'js') { 22 | chrome.tabs.query({active: true, currentWindow: true}, ([currentTab]) => { 23 | chrome.tabs.sendMessage(currentTab.id, {reloadTab: true}, function(response) { 24 | console.log(response.farewell) 25 | }) 26 | }) 27 | } 28 | } 29 | } 30 | }) 31 | } 32 | -------------------------------------------------------------------------------- /src/utils/pageHandler.js: -------------------------------------------------------------------------------- 1 | export default function pageHandler(node) { 2 | chrome.storage.local.get(function(result) { 3 | switch(result.page) { 4 | case 'auth': 5 | if(result.server) { 6 | chrome.storage.local.set({'page': 'editor'}) 7 | document.querySelector('snipx-page').innerHTML = '' 8 | document.getElementById('tabURL').innerText = result.tabURL 9 | document.querySelector('textarea').innerHTML = result.currentFile 10 | } 11 | else { 12 | chrome.storage.local.set({'page': 'auth'}) 13 | document.querySelector('snipx-page').innerHTML = '' 14 | } 15 | node.innerHTML = 'account_circle' 16 | break 17 | case 'editor': 18 | chrome.storage.local.set({'page': 'profile'}) 19 | node.innerHTML = 'arrow_back' 20 | document.querySelector('snipx-page').innerHTML = '' 21 | break 22 | case 'profile': 23 | chrome.storage.local.set({'page': 'editor'}) 24 | node.innerHTML = 'account_circle' 25 | document.querySelector('snipx-page').innerHTML = '' 26 | document.getElementById('tabURL').innerText = result.tabURL 27 | document.querySelector('textarea').innerHTML = result.currentFile 28 | break 29 | } 30 | }) 31 | } 32 | 33 | -------------------------------------------------------------------------------- /src/comps/Icon.js: -------------------------------------------------------------------------------- 1 | import pageHandler from '../utils/pageHandler.js' 2 | import stateHandler from '../utils/stateHandler.js' 3 | import themeHandler from '../utils/themeHandler.js' 4 | import editorState from '../utils/editorState.js' 5 | import editorLock from '../utils/editorLock.js' 6 | 7 | export default class SnipxIcon extends HTMLElement { 8 | constructor() { 9 | super() 10 | this.icon = this.getAttribute('icon') 11 | this.innerHTML = `${this.icon}` 12 | } 13 | 14 | checkNode(n) { if(n.nodeName === 'SNIPX-ICON') return n.childNodes[0]; return n } 15 | 16 | clickEvent(e) { 17 | let node = this.checkNode(e.target) 18 | switch(node.innerText) { 19 | case 'account_circle': case 'arrow_back': pageHandler(node); break 20 | case 'toggle_on': case 'toggle_off': stateHandler(node); break 21 | case 'dark_mode': case 'light_mode': themeHandler(node); break 22 | case 'visibility': case 'visibility_off': editorState(node); break 23 | case 'lock_outline': case 'lock_open': editorLock(node); break 24 | } 25 | } 26 | 27 | hoverEvent(e) { 28 | let node = this.checkNode(e.target) 29 | // Implement hidden details, revealed upon hover 30 | } 31 | 32 | connectedCallback() { 33 | this.addEventListener('click', e => this.clickEvent(e)) 34 | this.addEventListener('mouseover', e => this.hoverEvent(e)) 35 | } 36 | disconnectedCallback() { 37 | this.removeEventListener('click', this.clickEvent, true) 38 | this.removeEventListener('mouseover', this.hoverEvent, true) 39 | } 40 | } 41 | 42 | window.customElements.define('snipx-icon', SnipxIcon) -------------------------------------------------------------------------------- /src/comps/TextEditorMenu.js: -------------------------------------------------------------------------------- 1 | import './Icon.js' 2 | import editorChange from '../utils/editorChange.js' 3 | import editorSave from '../utils/editorSave.js' 4 | 5 | export default (function() { 6 | chrome.storage.local.get(function(result) { 7 | 8 | const menu = 9 | `` 22 | 23 | class SnipxEditorMenu extends HTMLElement { 24 | constructor() { 25 | super() 26 | this.innerHTML = menu 27 | 28 | this.selectElement = this.querySelector('select') 29 | this.saveElement = this.querySelector('#editorSave') 30 | 31 | this.selectElement.value = result.editor 32 | this.querySelector('#tabURL').innerText = result.tabURL 33 | } 34 | 35 | connectedCallback() { 36 | this.selectElement.addEventListener('change', e => editorChange(e)) 37 | this.saveElement.addEventListener('click', e => editorSave(e)) 38 | } 39 | 40 | disconnectedCallback() { 41 | this.selectElement.removeEventListener('change', editorChange, true) 42 | this.saveElement.removeEventListener('click', editorSave, true) 43 | } 44 | } 45 | 46 | window.customElements.define('snipx-editor-menu', SnipxEditorMenu) 47 | }) 48 | })() 49 | 50 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Code of Conduct 2 | 3 | As contributors and maintainers of this project, and in the interest of fostering an open and welcoming community, we pledge to respect all people who contribute through reporting issues, posting feature requests, updating documentation, submitting pull requests or patches, and other activities. 4 | 5 | We are committed to making participation in this project a harassment-free experience for everyone, regardless of level of experience, gender, gender identity and expression, sexual orientation, disability, personal appearance, body size, race, ethnicity, age, religion, or nationality. 6 | 7 | Examples of unacceptable behavior by participants include: 8 | 9 | * The use of sexualized language or imagery 10 | * Personal attacks 11 | * Trolling or insulting/derogatory comments 12 | * Public or private harassment 13 | * Publishing other's private information, such as physical or electronic addresses, without explicit permission 14 | * Other unethical or unprofessional conduct. 15 | 16 | Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct. By adopting this Code of Conduct, project maintainers commit themselves to fairly and consistently applying these principles to every aspect of managing this project. Project maintainers who do not follow or enforce the Code of Conduct may be permanently removed from the project team. 17 | 18 | This code of conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. 19 | 20 | Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by opening an issue or contacting one or more of the project maintainers. 21 | 22 | This Code of Conduct is adapted from the [Contributor Covenant](http://contributor-covenant.org), version 1.2.0, available at [http://contributor-covenant.org/version/1/2/0/](http://contributor-covenant.org/version/1/2/0/) -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing to Cola CSS 2 | 3 | 🎉 First off, thanks for taking the time to contribute! 🎉 4 | 5 | The following is a set of guidelines for contributing to Cola.css. These are mostly guidelines, not rules. Use your best judgment, and feel free to propose changes to this document in a pull request. 6 | Let's build open source software together! 7 | 8 | ## Code of Conduct 9 | 10 | This project and everyone participating in it is governed by a [Code of Conduct](../main/CODE_OF_CONDUCT.md). By participating, you are expected to uphold this code. Please report unacceptable behavior to [contact@startrev.com](mailto:contact@startrev.com). 11 | 12 | ## Create good Issues 13 | 14 | If you have ideas for new features, architecture, bug fixes etc, create an issue so we can discuss further. 15 | - Please make well thought out suggestions 16 | - Be as descriptive as possible 17 | - Apply proper labels 18 | 19 | ## Issues and Labels 20 | 21 | Our tracker utilizes eight labels to help organize and identify. Here's what they represent and how we use them: 22 | 23 | - ![Github Label](https://img.shields.io/static/v1?label=label&message=fix&color=eb5a46&style=for-the-badge) 24 | - ![Github Label](https://img.shields.io/static/v1?label=label&message=docs&color=89609e&style=for-the-badge) 25 | - ![Github Label](https://img.shields.io/static/v1?label=label&message=perf&color=f4fffd&style=for-the-badge) 26 | - ![Github Label](https://img.shields.io/static/v1?label=label&message=build&color=519839&style=for-the-badge) 27 | - ![Github Label](https://img.shields.io/static/v1?label=label&message=feat&color=055a8c&style=for-the-badge) 28 | - ![Github Label](https://img.shields.io/static/v1?label=label&message=style&color=B70D5F&style=for-the-badge) 29 | - ![Github Label](https://img.shields.io/static/v1?label=label&message=refactor&color=ff9f1a&style=for-the-badge) 30 | - ![Github Label](https://img.shields.io/static/v1?label=label&message=security&color=eb5a46&style=for-the-badge) 31 | 32 | ## License 33 | 34 | By contributing your code, you agree to license your contribution under the [MIT License](../main/LICENSE). 35 | 36 | **Happy Coding!** 🌎 37 | -------------------------------------------------------------------------------- /src/main.js: -------------------------------------------------------------------------------- 1 | // Snipx 2 | import urlHandler from './utils/urlHandler.js' 3 | import './utils/editorShortcuts.js' 4 | import './comps/Header.js' 5 | import './pages/_page.js' 6 | 7 | let 8 | page = document.querySelector('snipx-page'), 9 | cssTheme = document.querySelector('#cssTheme') 10 | 11 | chrome.storage.local.get(function(result) { 12 | // Check if server is active and set in chrome.storage 13 | fetch(`http://localhost:${result.port}`) 14 | .then(r => r.text()) 15 | .then(result => chrome.storage.local.set({'server': true})) 16 | .catch(e => chrome.storage.local.set({'server': false})) 17 | 18 | // Set app page 19 | switch(result.server) { 20 | case false: chrome.storage.local.set({'page': 'auth'}); break 21 | case true: chrome.storage.local.set({'page': 'editor'}); break 22 | } 23 | 24 | // Handle content-scripts 25 | if(result.state === true) { 26 | console.log('toggle scripts on.') 27 | } 28 | 29 | // Load theme file 30 | result.theme === 'dark' ? 31 | cssTheme.setAttribute('href', 'styles/theme/dark.css'): 32 | cssTheme.setAttribute('href', 'styles/theme/light.css') 33 | 34 | // Load page component 35 | switch(result.page) { 36 | case 'auth': page.innerHTML = ''; break 37 | case 'editor': page.innerHTML = ''; break 38 | case 'profile': page.innerHTML = ''; break 39 | } 40 | }) 41 | 42 | // Set current tabURL 43 | chrome.tabs.query({ active: true, currentWindow: true }, ([currentTab]) => { 44 | let currentTabURL = urlHandler(currentTab.url) 45 | chrome.storage.local.set({'tabURL': currentTabURL}) 46 | if(document.getElementById('tabURL')) document.getElementById('tabURL').innerText = currentTabURL 47 | 48 | chrome.storage.local.get(function(result) { 49 | // Set file data 50 | let xhr = new XMLHttpRequest() 51 | xhr.onreadystatechange = function() { 52 | if(this.readyState == 4 && this.status == 200) { 53 | if(document.querySelector('textarea')) document.querySelector('textarea').innerHTML = xhr.responseText 54 | chrome.storage.local.set({'currentFile': xhr.responseText}) 55 | } 56 | else { 57 | chrome.storage.local.set({'currentFile': result.editorText[result.editor]}) 58 | } 59 | } 60 | xhr.open("GET", `http://localhost:${result.port}/${result.tabURL}/${result.tabURL}.${result.editor}`, true) 61 | xhr.send() 62 | }) 63 | }) 64 | -------------------------------------------------------------------------------- /src/content.js: -------------------------------------------------------------------------------- 1 | let 2 | breakAt = 3, 3 | truncateAt = 25 4 | _BodyNode = document.getElementsByTagName('body')[0], 5 | _CSSNode = document.createElement('style'), 6 | _JSNode = document.createElement('script') 7 | 8 | function truncateString(str, num) { 9 | if(str.length <= num) return str 10 | return str.slice(0, num) + '...' 11 | } 12 | 13 | function urlHandler(url) { 14 | let mainArray = url.split(''), 15 | newArray = [], 16 | slashCount = 0 17 | 18 | for(let i = 0; i <= mainArray.length; i++) { 19 | if(mainArray[i] === '/') slashCount++ 20 | if(slashCount === breakAt) break 21 | newArray.push(mainArray[i]) 22 | } 23 | 24 | let newURL = newArray.join('') 25 | 26 | if(newURL.includes('http://')) return truncateString(newURL.replace('http://', ''), truncateAt) 27 | else return truncateString(newURL.replace('https://', ''), truncateAt) 28 | } 29 | 30 | function urlTarget(url, port) { 31 | return `http://localhost:${port}/${url}/${url}` 32 | } 33 | 34 | let currentURL = urlHandler(window.location.href) 35 | 36 | chrome.storage.local.get(function(result) { 37 | 38 | chrome.runtime.sendMessage({injectCSS: urlTarget(currentURL, result.port)}, function(response) { 39 | if( 40 | !response.startsWith('') && 41 | !response.endsWith('') && 42 | !response.includes('Error') 43 | ) { 44 | _CSSNode.innerHTML = response 45 | _CSSNode.setAttribute('id', 'snipx_css_node') 46 | _BodyNode.appendChild(_CSSNode) 47 | } 48 | 49 | }) 50 | 51 | chrome.runtime.sendMessage({injectJS: urlTarget(currentURL, result.port)}, function(response) { 52 | if( 53 | !response.startsWith('') && 54 | !response.endsWith('') && 55 | !response.includes('Error') 56 | ) { 57 | let chromeJS = chrome.runtime.getURL( 58 | `/api/public/${currentURL}/${currentURL}.js` 59 | ) 60 | _JSNode.setAttribute('src', chromeJS) 61 | _JSNode.setAttribute('id', 'snipx_js_node') 62 | _BodyNode.appendChild(_JSNode) 63 | } 64 | }) 65 | 66 | }) 67 | 68 | chrome.runtime.onMessage.addListener( 69 | function(request, sender, sendResponse) { 70 | if(request.reloadTab === true) { 71 | // handle 72 | window.location.reload() 73 | sendResponse({farewell: "goodbye"}) 74 | } 75 | if(request.appState === true) { 76 | // handle 77 | // window.location.reload() 78 | console.log('true') 79 | sendResponse({farewell: "goodbye"}) 80 | } 81 | } 82 | ) -------------------------------------------------------------------------------- /src/styles/styles.css: -------------------------------------------------------------------------------- 1 | /* RESET */ 2 | /* -------------------------------------- */ 3 | /* http://meyerweb.com/eric/tools/css/reset/ 4 | v2.0 | 20110126 5 | License: none (public domain) 6 | */ 7 | html, body, div, span, applet, object, iframe, 8 | h1, h2, h3, h4, h5, h6, p, blockquote, pre, 9 | a, abbr, acronym, address, big, cite, code, 10 | del, dfn, em, img, ins, kbd, q, s, samp, 11 | small, strike, strong, sub, sup, tt, var, 12 | b, u, i, center, 13 | dl, dt, dd, ol, ul, li, 14 | fieldset, form, label, legend, 15 | table, caption, tbody, tfoot, thead, tr, th, td, 16 | article, aside, canvas, details, embed, 17 | figure, figcaption, footer, header, hgroup, 18 | menu, nav, output, ruby, section, summary, 19 | time, mark, audio, video { 20 | margin: 0; 21 | padding: 0; 22 | border: 0; 23 | font-size: 100%; 24 | font: inherit; 25 | vertical-align: baseline; 26 | text-decoration: none; 27 | list-style-type: none; 28 | box-sizing: border-box; 29 | font-family: Arial, Helvetica, sans-serif } 30 | 31 | /* HTML5 display-role reset for older browsers */ 32 | article, aside, details, figcaption, figure, 33 | footer, header, hgroup, menu, nav, section { display: block } 34 | 35 | body { line-height: 1 } 36 | 37 | ol, ul { list-style: none } 38 | 39 | blockquote, q { quotes: none } 40 | 41 | blockquote:before, blockquote:after, 42 | q:before, q:after { content: ''; content: none } 43 | 44 | table { border-collapse: collapse; border-spacing: 0 } 45 | 46 | /* My additions */ 47 | * { 48 | -webkit-touch-callout: none; 49 | -webkit-user-select: none; 50 | -khtml-user-select: none; 51 | -moz-user-select: none; 52 | -ms-user-select: none; 53 | user-select: none } 54 | 55 | a,button,select { cursor: pointer } 56 | 57 | button { background: none; border: none } 58 | /* RESET END */ 59 | 60 | /* ICONS */ 61 | /* -------------------------------------- */ 62 | @font-face { 63 | font-family: 'Material Icons'; 64 | font-style: normal; 65 | font-weight: 400; 66 | src: url("/src/assets/flUhRq6tzZclQEJ-Vdg-IuiaDsNc.woff2") format('woff2'); 67 | } 68 | 69 | .material-icons { 70 | font-family: 'Material Icons'; 71 | font-weight: normal; 72 | font-style: normal; 73 | line-height: 1; 74 | letter-spacing: normal; 75 | text-transform: none; 76 | display: inline-block; 77 | white-space: nowrap; 78 | word-wrap: normal; 79 | direction: ltr; 80 | -webkit-font-smoothing: antialiased; 81 | cursor: pointer; 82 | } 83 | /* ICONS END */ 84 | 85 | /* THEME */ 86 | /* -------------------------------------- */ 87 | /* Color */ 88 | main aside { background: var(--color-primary) } 89 | 90 | body, header, 91 | textarea, #auth { background: var(--color-secondary) } 92 | 93 | a, p, button, #profile, 94 | textarea, .material-icons, 95 | #editor-actions select { color:var(--color-text) } 96 | 97 | header a:hover, button:hover, 98 | .material-icons:hover { 99 | background: var(--color-hover) !important; 100 | outline: none !important } 101 | 102 | ::-webkit-scrollbar-track { background: var(--color-secondary) } 103 | ::-webkit-scrollbar-thumb { background: var(--color-primary) } 104 | 105 | /* Font Family */ 106 | a, p, button { font-family:var(--fontfamily-primary) } 107 | 108 | #editor-actions select, 109 | textarea { font-family: var(--fontfamily-editor) } 110 | 111 | /* Font Size */ 112 | header a, #auth p, 113 | #profile h1 { font-size: var(--fontsize-logo) } 114 | 115 | #auth button, textarea { font-size: var(--fontsize-text) } 116 | 117 | #auth a, #editor-actions p, 118 | #editor-actions select, 119 | #profile h2 { font-size: var(--fontsize-link) } 120 | 121 | .material-icons { font-size: var(--fontsize-icon) } 122 | 123 | .menu-button { font-size: var(--fontsize-menu-button) } 124 | 125 | /* Font Bold */ 126 | header a, .menu-button, 127 | #editor-actions select, 128 | #profile h1 { font-weight: var(--fontweight-bold) } 129 | 130 | /* Padding */ 131 | #auth p { padding-bottom: var(--padding-editor) } 132 | 133 | .menu-button { padding: var(--padding-button-alt) } 134 | 135 | textarea, #profile { padding: var(--padding-editor) } 136 | 137 | header a, .material-icons { padding: var(--padding-icon) } 138 | 139 | header { padding: var(--padding-container) } 140 | 141 | button { padding: var(--padding-button) } 142 | 143 | main aside { padding: var(--padding-editor-menu) } 144 | 145 | #editor-actions select, 146 | #editor-actions span { padding: var(--padding-select) } 147 | 148 | /* Border */ 149 | header a, button, .material-icons, 150 | #editor-actions select { border-radius: var(--border-radius) } 151 | 152 | button, #editor-actions select { outline: var(--border) } 153 | 154 | header, main aside { border-bottom: var(--border-alt) } 155 | /* THEME END */ 156 | 157 | /* STYLES */ 158 | /* -------------------------------------- */ 159 | ::-webkit-scrollbar { width: 12px } 160 | 161 | body { 162 | width: 450px; 163 | height: 546px } 164 | 165 | header, #auth { 166 | display: flex; 167 | align-items: center } 168 | 169 | header { justify-content: space-between} 170 | 171 | main { height: 492px } 172 | 173 | #auth { 174 | justify-content: center; 175 | flex-direction: column } 176 | 177 | #auth a { text-decoration: underline } 178 | 179 | #editor { 180 | display: flex; 181 | flex-direction: column } 182 | 183 | main aside { 184 | display: flex; 185 | align-items: center; 186 | justify-content: space-between } 187 | 188 | textarea { 189 | width: 418px; 190 | height: 417px; 191 | resize: none; 192 | border: none; 193 | outline: none; 194 | overflow-y: scroll !important } 195 | 196 | #editor-actions { 197 | display: flex; 198 | align-items: center } 199 | 200 | #editor-actions select { 201 | border: none; 202 | background: none; 203 | margin-right: 8px; 204 | min-width: 60px } 205 | 206 | #editor-actions p { margin-left: 8px } 207 | 208 | #profile h1 { margin-bottom: 8px } 209 | 210 | .menu-button { margin-left: 4px } 211 | /* STYLES END*/ -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |
2 | 9 | 10 | 11 | 12 | 13 | 20 | 21 | [![Manifest][manifest-shield]][manifest-url] 22 | [![Contributors][contributors-shield]][contributors-url] 23 | [![Forks][forks-shield]][forks-url] 24 | [![Stargazers][stars-shield]][stars-url] 25 | [![Issues][issues-shield]][issues-url] 26 | [![License][license-shield]][license-url] 27 | 28 | 29 |
30 |
31 | 32 | 33 |

SnipX

34 | 35 |

36 | Injects CSS and JS snippets into your current browser tab.
37 | Report Bug 38 | · 39 | Request Feature 40 | · 41 | View Demo 42 |

43 |
44 | 45 | 46 | 47 | 48 |
49 | Table of Contents 50 |
    51 |
  1. 52 | Introducing SnipX 53 | 57 |
  2. 58 |
  3. 59 | Getting Started 60 | 64 |
  4. 65 |
  5. Usage
  6. 66 |
  7. Roadmap
  8. 67 |
  9. Contributing
  10. 68 |
  11. License
  12. 69 |
  13. Contact
  14. 70 |
  15. Acknowledgments
  16. 71 |
72 |
73 | 74 | 75 | 76 | 77 | ## Introducing SnipX 78 | 79 | ![Product Name Screen Shot][product-screenshot] 80 | 81 | SnipX is short for Snippet Extension. A web extension that let's you inject CSS and JavaScript into your current browser tab. It will watch over your files and the moment you save a change, it will inform your browser to reload (inject) the new CSS / JS. The best part is, once you've saved your changes, that's your newly modified webpage. Everytime you go back to that page it will load your modifications! Currently, it should work on most browsers built on [Chromium][chromium-url]. 82 | 83 | A few examples of Chromium based browsers would be: 84 | * Google Chrome *( tested ✔️ )* 85 | * Microsoft Edge *( tested ✔️ )* 86 | * Brave *( not tested )* 87 | * Opera *( not tested )* 88 | * Vivaldi *( not tested )* 89 | 90 | There are so many great extensions out there; however, we didn't find one that really suited our needs so we created this open source one. 91 | We want to make it simple, easy too use, so amazing that it'll be the last one you ever need 92 | -- We think this is it. 93 | 94 | Here's why: 95 | 96 | * The ability to modify your browsing experience with code injection(css/js) 97 | * Snippets are saved locally, right into the main repo and always active as long as your server is running 98 | * Easily modify the user interface in `styles/theme/dark.css` and `styles/theme/light.css` 99 | * Configure the local server port in `config.json` 100 | * It's open source, do whatever you want with it :smile: 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | Of course, no one tool will serve all since your needs may be different. So we'll be adding more in the near future. You may also suggest changes by forking this repo and creating a pull request or opening an issue. Thanks to all the people that have contributed to expanding this! 109 | 110 |

(back to top)

111 | 112 | ### Making SnipX 113 | 114 | Have you ever found yourself writing CSS or JavaScript code in the developer tools, then refreshing the page only to lose all of your changes? With SnipX, you can write your code in the extension, and your changes will always be rendered. 115 | 116 | Well, that's the goal :shrug: Just make sure your server is running! 117 | 118 | ### Built With 119 | 120 | Major frameworks/libraries used to bootstrap the project. 121 | 122 | * [HTML5][html-url] 123 | * [CSS3][css-url] 124 | * [JavaScript][js-url] 125 | * [Chrome API][chrome-api-url] 126 | * [Node.js][node-url] 127 | * [Express][express-url] 128 | 129 |

(back to top)

130 | 131 | 132 | 133 | 134 | ## Getting Started 135 | 136 | Instructions on setting up your project locally. 137 | To get a local copy up and running follow these simple steps. 138 | 139 | ### Requirements 140 | 141 | Things you need to use the software and how to install them. You've installed the latest versions of [Node.js][node-url] and [npm][npm-url]. 142 | * npm 143 | ```sh 144 | npm install npm@latest -g 145 | ``` 146 | 147 | ### Installation 148 | 149 | _Instructions on installing and setting up SnipX._ 150 | 151 | 0. Clone the repo 152 | ```sh 153 | git clone https://github.com/startrev/SnipX.git 154 | ``` 155 | 1. Install NPM packages 156 | ```sh 157 | npm install 158 | ``` 159 | 2. Start your server 160 | ```sh 161 | npm start 162 | ``` 163 | 164 | *Next we'll go ahead and install the SnipX Extension. Follow along the next few steps respective to your browser.* 165 | 166 | 3. Head over to either of the following links: 167 | * [chrome://extensions](chrome://extensions) 168 | * [edge://extensions](edge://extensions) 169 | 170 | 4. If **Developer Mode** isn't active, activate it 171 | * ![Product Name Screen Shot][product-toggle] 172 | 5. Finally, load the extension you cloned in step `0.` 173 | * ![Product Name Screen Shot][product-load] 174 | 175 | *Sometimes, you'll get stuck on the 'Server isn't running' page. This is because currently there is no ajax checking for server updates. To check if it is running, you can click the Profile icon in the extension.* 176 | 177 | ![Product Name Screen Shot][product-check] 178 | 179 |

(back to top)

180 | 181 | 182 | 183 | 184 | ## Usage 185 | 186 | Useful examples of how SnipX can be used. 187 | - **CSS Styling** ![Product Name Screen Shot][product-css] 188 | - **JS DOM Manipulation** ![Product Name Screen Shot][product-js] 189 | - **Note Taking** ![Product Name Screen Shot][product-text] 190 | 191 |

(back to top)

192 | 193 | 194 | 195 | 196 | ## Roadmap 197 | 198 | - [x] Chromium support 199 | - [x] Dark/light mode 200 | - [ ] Firefox support 201 | - [ ] Event Handling System 202 | - [ ] HTML Injection 203 | - [ ] Global CSS Config 204 | - [ ] Syntax Highlighting 205 | - [ ] Editor Keybinds 206 | - [ ] Profile Page 207 | - [ ] GitHub OAuth & API 208 | - [ ] Documentation 209 | 210 | 211 | See the [open issues][issues-url] for a full list of proposed features (and known issues). 212 | 213 |

(back to top)

214 | 215 | 216 | 217 | 218 | ## Contributing 219 | 220 | Contributions are what make the open source community such an amazing place to learn, inspire, and create. Any contributions you make are **greatly appreciated**. 221 | 222 | If you have a suggestion that would make this better, please fork the repo and create a pull request. You can also simply open an issue with the tag "feat". 223 | Don't forget to give the project a star! Thanks again! 224 | 225 | 1. Fork the Project 226 | 2. Create your Feature Branch (`git checkout -b feature/feature_name`) 227 | 3. Commit your Changes (`git commit -m 'type(scope): add AmazingFeature'`) 228 | 4. Push to the Branch (`git push origin feature/AmazingFeature`) 229 | 5. Open a Pull Request 230 | 231 |

(back to top)

232 | 233 | 234 | 235 | 236 | ## License 237 | 238 | Distributed under the MIT License. See [`LICENSE`][license-url] for more information. 239 | 240 |

(back to top)

241 | 242 | 243 | 244 | 245 | ## Contact 246 | 247 | Email: contact@startrev.com 248 | 249 | Project Link: [github.com/startrev/SnipX][project-url] 250 | 251 |

(back to top)

252 | 253 | 254 | 255 | 256 | ## Acknowledgments 257 | 258 | * [Reset CSS][reset-url] 259 | * [Material Icons][material-url] 260 | 261 |

(back to top)

262 | 263 | 264 | 265 | 266 | 267 | [contributors-shield]: https://img.shields.io/github/contributors/startrev/SnipX.svg?style=for-the-badge&color=green 268 | [forks-shield]: https://img.shields.io/github/forks/startrev/SnipX.svg?style=for-the-badge&color=blue 269 | [stars-shield]: https://img.shields.io/github/stars/startrev/SnipX.svg?style=for-the-badge&color=yellow 270 | [issues-shield]: https://img.shields.io/github/issues/startrev/SnipX.svg?style=for-the-badge&color=red 271 | [license-shield]: https://img.shields.io/github/license/startrev/SnipX.svg?style=for-the-badge&color=yellowgreen 272 | [manifest-shield]: https://img.shields.io/badge/Manifest-v3-black?style=for-the-badge 273 | 274 | [contributors-url]: https://github.com/startrev/SnipX/graphs/contributors 275 | [project-url]: https://github.com/startrev/SnipX 276 | [forks-url]: https://github.com/startrev/SnipX/network/members 277 | [stars-url]: https://github.com/startrev/SnipX/stargazers 278 | [issues-url]: https://github.com/startrev/SnipX/issues 279 | [license-url]: https://github.com/startrev/SnipX/blob/master/LICENSE 280 | [manifest-url]: https://developer.chrome.com/docs/extensions/mv3/intro 281 | [express-url]: https://expressjs.com 282 | [node-url]: https://nodejs.org 283 | [npm-url]: https://docs.npmjs.com/downloading-and-installing-node-js-and-npm 284 | [html-url]: https://developer.mozilla.org/en-US/docs/Glossary/HTML5 285 | [css-url]: https://developer.mozilla.org/en-US/docs/Web/CSS 286 | [js-url]: https://developer.mozilla.org/en-US/docs/Web/JavaScript 287 | [material-url]: https://fonts.google.com/icons?selected=Material+Icons 288 | [reset-url]: https://meyerweb.com/eric/tools/css/reset 289 | [chrome-api-url]: https://developer.chrome.com/docs/extensions/reference 290 | [chromium-url]: https://www.chromium.org 291 | 292 | [product-screenshot]: https://raw.githubusercontent.com/startrev/images/main/SnipX/Screenshot-SnipX-v2.png 293 | [product-toggle]: https://raw.githubusercontent.com/startrev/images/main/SnipX/Screenshot-Toggle-v3.png 294 | [product-url]: https://raw.githubusercontent.com/startrev/images/main/SnipX/Screenshot-URL-v3.png 295 | [product-load]: https://raw.githubusercontent.com/startrev/images/main/SnipX/Screenshot-Load-v2.png 296 | [product-check]: https://raw.githubusercontent.com/startrev/images/main/SnipX/Screenshot-Check-v2.png 297 | [product-css]: https://raw.githubusercontent.com/startrev/images/main/SnipX/Screenshot-CSS-v1.png 298 | [product-js]: https://raw.githubusercontent.com/startrev/images/main/SnipX/Screenshot-JS-v1.png 299 | [product-text]: https://raw.githubusercontent.com/startrev/images/main/SnipX/Screenshot-TXT-v1.png --------------------------------------------------------------------------------