├── 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 | - 
24 | - 
25 | - 
26 | - 
27 | - 
28 | - 
29 | - 
30 | - 
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('