├── .editorconfig
├── .gitignore
├── .jshintrc
├── README.md
├── app
├── _locales
│ └── en
│ │ └── messages.json
├── css
│ ├── app.css
│ ├── cm-dope.css
│ └── themes.css
├── images
│ ├── icon128.png
│ ├── icon16.png
│ ├── icon48.png
│ └── settings.svg
├── index.html
├── js
│ ├── app.js
│ └── themes.js
├── manifest.json
├── scripts
│ └── content.js
└── templates
│ └── themes.html
├── gulpfile.js
├── license.md
└── package.json
/.editorconfig:
--------------------------------------------------------------------------------
1 | # EditorConfig helps developers define and maintain consistent
2 | # coding styles between different editors and IDEs
3 | # editorconfig.org
4 |
5 | root = true
6 |
7 |
8 | [*]
9 |
10 | # Change these settings to your own preference
11 | indent_style = space
12 | indent_size = 2
13 |
14 | [*.json]
15 | indent_size = 2
16 |
17 | # We recommend you to keep these unchanged
18 | end_of_line = lf
19 | charset = utf-8
20 | trim_trailing_whitespace = true
21 | insert_final_newline = true
22 |
23 | [*.md]
24 | trim_trailing_whitespace = false
25 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | temp
3 | .tmp
4 | dist
5 | package
6 | Veneer.zip
--------------------------------------------------------------------------------
/.jshintrc:
--------------------------------------------------------------------------------
1 | {
2 | "node": true,
3 | "browser": true,
4 | "esnext": true,
5 | "bitwise": true,
6 | "camelcase": true,
7 | "curly": true,
8 | "eqeqeq": true,
9 | "immed": true,
10 | "indent": 2,
11 | "latedef": true,
12 | "newcap": true,
13 | "noarg": true,
14 | "quotmark": "single",
15 | "regexp": true,
16 | "undef": true,
17 | "unused": true,
18 | "strict": true,
19 | "trailing": true,
20 | "smarttabs": true,
21 | "globals" : {
22 | "chrome": true,
23 | "crypto": true
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Building Veneer
2 | clone the repo
3 | npm install
4 | gulp build-ext
5 |
6 | ### Todo's
7 | - Implement a way to use more than 8,000 bytes
8 | - Create a way for users to download themes
9 | - Streamline the website-themes repo and chrome extension
10 | - Make entire process more user friendly
11 |
12 | Open Sourced under MIT license
13 |
--------------------------------------------------------------------------------
/app/_locales/en/messages.json:
--------------------------------------------------------------------------------
1 | {
2 | "appName": {
3 | "message": "Veneer",
4 | "description": "Install custom themes for your favorite websites."
5 | },
6 | "appDescription": {
7 | "message": "Veneer",
8 | "description": "Install custom themes for your favorite websites."
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/app/css/app.css:
--------------------------------------------------------------------------------
1 | body {
2 | background-color: #eee;
3 | font-family: Arial !important;
4 | min-width: 350px;
5 | }
6 |
7 | .button-container {
8 | width: 100%;
9 | display: block;
10 | }
11 |
12 | .line {
13 | font-size: 13px;
14 | display: block;
15 | clear: both;
16 | margin: 10px 0;
17 | padding: 10px 0;
18 | }
19 |
20 | .line > span {
21 | float: left;
22 | display: inline-block;
23 | }
24 |
25 | .line > .import {
26 | text-transform: uppercase;
27 | background-color: transparent;
28 | border: 0px;
29 | color: #2196F3;
30 | display: inline-block;
31 | cursor: pointer;
32 | }
33 |
34 | .line > button {
35 | text-transform: uppercase;
36 | background-color: transparent;
37 | border: 0px;
38 | color: #2196F3;
39 | float: right;
40 | display: inline-block;
41 | cursor: pointer;
42 | }
43 |
44 | .url-bar {
45 | padding: 5px;
46 | width: 90%;
47 | display: inline-block;
48 | margin-bottom: 6px;
49 | border-radius: 2px;
50 | border: 1px;
51 | font-size: 16px;
52 | box-shadow: 0 1px 0 #DADADA;
53 | outline: none;
54 | }
55 |
56 | .settings {
57 | background-image: url(../images/settings.svg);
58 | background-size: 23px;
59 | padding: 5px;
60 | height: 25px;
61 | width: 25px;
62 | background-color: transparent;
63 | outline: none;
64 | border: 1px solid transparent;
65 | background-repeat: no-repeat;
66 | position: relative;
67 | top: 2px;
68 | left: 3px;
69 | cursor: pointer;
70 | }
71 |
72 | .settings-container {
73 | display: none;
74 | }
75 |
76 | textarea {
77 | cursor: text;
78 | }
79 |
80 | .CodeMirror.cm-s-dope {
81 | box-shadow: inset 0 4px 12px rgba(0,0,0,.15);
82 | border-bottom-right-radius: 4px;
83 | border-bottom-left-radius: 4px;
84 | overflow: hidden;
85 | }
86 |
87 | .cm-s-dope.CodeMirror {
88 | max-width: 350px;
89 | }
90 |
91 | .pull-right {
92 | float: right;
93 | display: inline-block;
94 | }
95 |
--------------------------------------------------------------------------------
/app/css/cm-dope.css:
--------------------------------------------------------------------------------
1 | .cm-s-dope .cm-keyword{color:#cda869}
2 | .cm-s-dope .cm-atom{color:#cf7ea9}
3 | .cm-s-dope .cm-number{color:#78cf8a}
4 | .cm-s-dope .cm-def{color:#aac6e3}
5 | .cm-s-dope .cm-variable{color:#ffb795}
6 | .cm-s-dope .cm-variable-2{color:#eed1b3}
7 | .cm-s-dope .cm-variable-3{color:#faded3}
8 | .cm-s-dope .cm-property{color:#eed1b3}
9 | .cm-s-dope .cm-operator{color:#fa8d6a}
10 | .cm-s-dope .cm-comment{color:#bcbeca;font-style:italic}
11 | .cm-s-dope .cm-string{color:#56bc8a}
12 | .cm-s-dope .cm-string-2{color:#9d937c}
13 | .cm-s-dope .cm-meta{color:#d2a8a1}
14 | .cm-s-dope .cm-qualifier{color:#f5f5b4}
15 | .cm-s-dope .cm-builtin{color:#99c}
16 | .cm-s-dope .cm-bracket{color:#24c2c7}
17 | .cm-s-dope .cm-tag{color:#3b93c8}
18 | .cm-s-dope .cm-attribute{color:#9b859d}
19 | .cm-s-dope .cm-header{color:#6f9ce0}
20 | .cm-s-dope .cm-quote{color:#24c2c7}
21 | .cm-s-dope .cm-hr{color:#ffc0cb}
22 | .cm-s-dope .cm-link{color:#f4c20b}
23 | .cm-s-dope .cm-special{color:#ff9d00}
24 | .cm-s-dope .cm-error{border-bottom:1px solid #FF4136}
25 | .cm-s-dope .CodeMirror-matchingbracket{color:#0f0}
26 | .cm-s-dope .CodeMirror-nonmatchingbracket{color:#f22}
27 | .cm-s-dope .CodeMirror-selected{background:rgba(255,255,255,0.15)}
28 | .cm-s-dope .CodeMirror-focused .CodeMirror-selected{background:rgba(255,255,255,0.1)}
29 | .cm-s-dope.CodeMirror{color:#fff;background-color:rgba(69,76,82,.85);font-family:'Source Code Pro','Monaco',monospace;font-size:13px;line-height:1.3;text-shadow:0 1px rgba(0,0,0,0.3)}
30 | .cm-s-dope.CodeMirror:hover{cursor:text}
31 | .cm-s-dope .CodeMirror-gutters{background:rgba(64, 69, 73, .65);border-right:none}
32 | .cm-s-dope .CodeMirror-linenumber{color:#bbb;padding:0 5px;width:35px !important;font-size:13px;text-align:right}
33 | .cm-s-dope .CodeMirror-lines { padding: 6px 0; }
34 | .cm-s-dope .CodeMirror-lines .CodeMirror-cursor{border-left:2px solid rgba(255,255,255,.85);}
35 | .cm-s-dope .CodeMirror-activeline-background{background:none repeat scroll 0% 0% rgba(255,255,255,0.041)}
36 |
--------------------------------------------------------------------------------
/app/css/themes.css:
--------------------------------------------------------------------------------
1 | html, body {
2 | margin: 0;
3 | padding: 0;
4 | background: #FAFAFA;
5 | font-family: Arial;
6 | color: #333;
7 | height: 100%;
8 | width: 100%;
9 | font-size: 18px;
10 | }
11 |
12 | * {
13 | box-sizing: border-box;
14 | }
15 |
16 | .title {
17 | font-size: 24px;
18 | font-weight: 700;
19 | margin-top: 24px;
20 | }
21 |
22 | .text-center {
23 | text-align: center;
24 | }
25 |
26 | .card-container {
27 | width: 100%;
28 | max-width: 450px;
29 | margin: 16px auto 0;
30 | }
31 |
32 | .card {
33 | background: #FFF;
34 | border-radius: 3px;
35 | box-shadow: 0 3px 4px rgba(0,0,0,0.3);
36 | margin: 20px auto;
37 | width: 300px;
38 | height: 80px;
39 | display: flex;
40 | justify-content: center;
41 | align-items: center;
42 | position: relative;
43 | transition: all 0.35s;
44 | }
45 |
46 | .xbttn {
47 | font-size: 12px;
48 | background: transparent;
49 | border: 0px;
50 | position: absolute;
51 | right: 8px;
52 | top: 8px;
53 | cursor: pointer;
54 | }
55 |
56 | .swoosh-out {
57 | animation-duration: 0.35s;
58 | animation-name: swoosh;
59 | animation-fill-mode: forwards;
60 | }
61 |
62 | @keyframes swoosh {
63 | 0% {
64 | opacity: 1;
65 | transform: translateX(0px);
66 | }
67 |
68 | 100% {
69 | opacity: 0;
70 | transform: translateX(-200px);
71 | }
72 | }
73 |
--------------------------------------------------------------------------------
/app/images/icon128.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hanford/Veneer/4737f03d6ad15882878a8a0841db9368dedabd61/app/images/icon128.png
--------------------------------------------------------------------------------
/app/images/icon16.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hanford/Veneer/4737f03d6ad15882878a8a0841db9368dedabd61/app/images/icon16.png
--------------------------------------------------------------------------------
/app/images/icon48.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hanford/Veneer/4737f03d6ad15882878a8a0841db9368dedabd61/app/images/icon48.png
--------------------------------------------------------------------------------
/app/images/settings.svg:
--------------------------------------------------------------------------------
1 |
12 |
--------------------------------------------------------------------------------
/app/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
Settings
15 |
16 | Remove data
17 |
18 |
19 |
20 | Themes
21 |
22 |
23 |
24 |
Space used
25 |
26 |
27 |
31 |
32 |
33 |
34 |
35 |
36 |
--------------------------------------------------------------------------------
/app/js/app.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 |
3 | require('codemirror/mode/css/css')
4 |
5 | const CodeMirror = require('codemirror')
6 | const url = require('url')
7 |
8 | const textArea = document.querySelector('textarea')
9 | const editor = CodeMirror.fromTextArea(textArea, {
10 | theme: 'dope',
11 | lineNumbers: true,
12 | mode: 'css',
13 | tabSize: 2
14 | })
15 |
16 | class Veneer {
17 | constructor () {
18 | const completeBtn = document.querySelector('.complete')
19 | const removeBtn = document.querySelector('.remove')
20 | const settingsBtn = document.querySelector('.settings')
21 | const urlBar = document.querySelector('.url-bar')
22 | const themeBtn = document.querySelector('.theme-btn')
23 |
24 | this.load = this.load.bind(this)
25 | this.removeStorage = this.removeStorage.bind(this)
26 | this.saveToStorage = this.saveToStorage.bind(this)
27 | this.newCSS = this.newCSS.bind(this)
28 |
29 | chrome.tabs.query({ active: true }, (tabs) => {
30 | this.currentUrl = urlBar.value = url.parse(tabs[0].url).host
31 |
32 | editor.on('change', this.newCSS)
33 | themeBtn.addEventListener('click', this.themes)
34 | settingsBtn.addEventListener('click', this.settings)
35 | removeBtn.addEventListener('click', this.removeStorage)
36 | })
37 | }
38 |
39 | load () {
40 | chrome.storage.sync.get('CustomCSS', (res) => {
41 | if (res['CustomCSS']) {
42 | try {
43 | JSON.parse(res['CustomCSS'])
44 | .map((item) => item.url === this.currentUrl && editor.getDoc().setValue(atob(item.CSS)))
45 | } catch (e) {
46 | this.removeStorage()
47 | }
48 | }
49 | })
50 | }
51 |
52 | themes () {
53 | chrome.tabs.create({url:"../templates/themes.html"}, function (tab) {
54 | console.log(tab)
55 | })
56 | }
57 |
58 |
59 | removeStorage () {
60 | chrome.storage.sync.remove('CustomCSS', () => {
61 | chrome.storage.sync.getBytesInUse('CustomCSS', (res) => {
62 | document.querySelector('.storage').innerText = res + " Bytes"
63 | })
64 | })
65 | }
66 |
67 | saveToStorage (storage) {
68 | chrome.storage.sync.set({'CustomCSS': storage}, (res) => {
69 | chrome.tabs.query({active: true, currentWindow: true}, (tabs) => {
70 | return chrome.tabs.sendMessage(tabs[0].id, 'reload')
71 | })
72 | })
73 | }
74 |
75 | newCSS (c) {
76 | chrome.storage.sync.get('CustomCSS', (res) => {
77 | let changed = false
78 | let storage
79 |
80 | if (!res['CustomCSS']) {
81 | storage = []
82 | } else {
83 | storage = JSON.parse(res['CustomCSS'])
84 |
85 | storage
86 | .filter(item => item.url === this.currentUrl)
87 | .map((item) => {
88 | changed = true
89 | item.CSS = btoa(editor.getValue())
90 | return item
91 | })
92 | }
93 |
94 | if (!changed) {
95 | let b64 = btoa(editor.getValue())
96 |
97 | storage.push({
98 | 'url': this.currentUrl,
99 | 'CSS': b64,
100 | })
101 | }
102 |
103 | return this.saveToStorage(JSON.stringify(storage))
104 | })
105 | }
106 |
107 | settings () {
108 | this.settingsToggle = !this.settingsToggle
109 | let codeContainer = document.querySelector('.code-container')
110 | let settingsContainer = document.querySelector('.settings-container')
111 | let storageMeter = document.querySelector('.storage')
112 |
113 | chrome.storage.sync.getBytesInUse('CustomCSS', usedSpace => storageMeter.innerText = `${usedSpace} / 8,192`)
114 |
115 | if (this.settingsToggle) {
116 | codeContainer.style.display = 'none'
117 | settingsContainer.style.display = 'block'
118 | } else {
119 | codeContainer.style.display = 'block'
120 | settingsContainer.style.display = 'none'
121 | }
122 | }
123 | }
124 |
125 | var veneer = new Veneer()
126 | veneer.load()
127 |
--------------------------------------------------------------------------------
/app/js/themes.js:
--------------------------------------------------------------------------------
1 | class Themes {
2 | constructor () {
3 | chrome.storage.sync.get('CustomCSS', (res) => {
4 | try {
5 | this.themes = JSON.parse(res['CustomCSS'])
6 | this.appendThemesToDom()
7 | }
8 | catch (e) {
9 | console.log('No Themes!')
10 | }
11 | })
12 |
13 | this.appendThemesToDom.bind(this)
14 | this.saveToStorage.bind(this)
15 | this.removeTheme.bind(this)
16 | }
17 |
18 | appendThemesToDom () {
19 | const list = document.querySelector('.card-container')
20 |
21 | this.themes.forEach((item, index) => {
22 | let div = document.createElement('div')
23 | let removeBtn = document.createElement('button')
24 |
25 | removeBtn.innerHTML = '✕';
26 | removeBtn.addEventListener('click', () => this.removeTheme(event, index))
27 | removeBtn.classList.add('xbttn')
28 |
29 | div.innerText = item.url
30 | div.appendChild(removeBtn)
31 | div.classList.add('card')
32 | list.appendChild(div)
33 | })
34 | }
35 |
36 | saveToStorage (strg) {
37 | chrome.storage.sync.set({'CustomCSS': strg}, (res) => location.reload())
38 | }
39 |
40 | removeTheme (event, index) {
41 | const confirm = window.confirm(`Are you sure you want to deconste your theme for ${this.themes[index].url}?`)
42 | if (confirm) {
43 | this.themes[index] = {}
44 | chrome.storage.sync.set({'CustomCSS': JSON.stringify(this.themes)}, () => {
45 | event.target.parentElement.classList.add('swoosh-out')
46 | setTimeout(() => event.target.parentElement.remove(), 500)
47 | })
48 | }
49 | }
50 | }
51 |
52 | new Themes()
53 |
--------------------------------------------------------------------------------
/app/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "Veneer",
3 | "version": "0.0.1",
4 | "manifest_version": 2,
5 | "description": "Use your own CSS across the web!",
6 | "icons": {
7 | "16": "images/icon16.png",
8 | "32": "images/icon48.png",
9 | "128": "images/icon128.png"
10 | },
11 | "content_scripts": [{
12 | "matches": [
13 | "http://*/*",
14 | "https://*/*"
15 | ],
16 | "js": ["scripts/content.js"],
17 | "run_at": "document_end"
18 | }],
19 | "browser_action": {
20 | "default_popup": "index.html"
21 | },
22 | "background": {},
23 | "permissions": [
24 | "storage",
25 | "unlimitedStorage",
26 | "tabs",
27 | "webRequest",
28 | "webRequestBlocking"
29 | ],
30 | "default_locale": "en"
31 | }
32 |
--------------------------------------------------------------------------------
/app/scripts/content.js:
--------------------------------------------------------------------------------
1 | var style = document.createElement('style')
2 | var url = window.location.host
3 |
4 | style.id = 'custom-css-style'
5 |
6 | function loadCustom() {
7 | chrome.storage.sync.get('CustomCSS', function (res) {
8 | if (res['CustomCSS']) {
9 | var saved = JSON.parse(res['CustomCSS'])
10 | console.log(saved)
11 | style.innerText = saved.reduce(function (prev, item) {
12 | if (item.url === url) {
13 | console.log(item.CSS)
14 | var code = atob(item.CSS)
15 | return prev + code
16 | } else {
17 | return prev
18 | }
19 | }, '')
20 | }
21 | })
22 | }
23 |
24 | loadCustom()
25 |
26 | document.head.appendChild(style)
27 |
28 | chrome.runtime.onMessage.addListener(msg => msg === 'reload' && loadCustom())
29 |
--------------------------------------------------------------------------------
/app/templates/themes.html:
--------------------------------------------------------------------------------
1 |
2 |
3 | Veneer Themes
4 |
5 |
6 |
7 |
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/gulpfile.js:
--------------------------------------------------------------------------------
1 | var gulp = require('gulp');
2 | var $ = require('gulp-load-plugins')();
3 |
4 | gulp.task('default', ['build-ext'])
5 |
6 | gulp.task('build-ext', ['img', 'manifest', 'en', 'index', 'css', 'move-themes']);
7 |
8 | gulp.task('index', function() {
9 | return gulp.src('./app/index.html')
10 | .pipe(gulp.dest('./dist/'))
11 | })
12 |
13 | gulp.task('css', function() {
14 | return gulp.src(['./app/css/*.css', 'node_modules/codemirror/lib/codemirror.css'])
15 | .pipe(gulp.dest('./dist/css/'))
16 | })
17 |
18 | gulp.task('move-themes', function() {
19 | gulp.src(['./app/js/themes.js'])
20 | .pipe(gulp.dest('./dist/js/'))
21 |
22 | gulp.src(['./app/css/themes.css'])
23 | .pipe(gulp.dest('./dist/css/'))
24 |
25 | gulp.src(['./app/scripts/content.js'])
26 | .pipe(gulp.dest('./dist/scripts/'))
27 |
28 | gulp.src(['./app/templates/**.html'])
29 | .pipe(gulp.dest('./dist/templates/'))
30 | })
31 |
32 | gulp.task('watch', ['build-ext'], function() {
33 | gulp.watch('./app/**.html', ['index'])
34 | gulp.watch(['./app/templates/**.html', './app/js/themes.js'], ['move-themes'])
35 | gulp.watch('./app/css/**.css', ['css'])
36 | })
37 |
38 | gulp.task('bump', function(){
39 | gulp.src('./app/manifest.json')
40 | .pipe($.bump({type: 'patch'}))
41 | .pipe(gulp.dest('./dist/'))
42 | })
43 |
44 | gulp.task('zip', function() {
45 | return gulp.src('./dist/**/**.*')
46 | .pipe($.zip('Veneer.zip'))
47 | .pipe(gulp.dest('./'));
48 | })
49 |
50 | gulp.task('release', ['build-ext', 'bump', 'zip'])
51 |
52 | gulp.task('img', function() {
53 | return gulp.src('./app/images/**')
54 | .pipe(gulp.dest('./dist/images/'))
55 | })
56 |
57 | gulp.task('templates', function() {
58 | return gulp.src('./app/templates/**.html')
59 | .pipe(gulp.dest('./dist/templates/'))
60 | })
61 |
62 | gulp.task('manifest', function() {
63 | return gulp.src('./app/manifest.json')
64 | .pipe(gulp.dest('./dist/'))
65 | })
66 |
67 | gulp.task('en', function() {
68 | return gulp.src('./app/_locales/**/**.**')
69 | .pipe(gulp.dest('./dist/_locales'))
70 | })
71 |
--------------------------------------------------------------------------------
/license.md:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2015 Jack Hanford
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
13 | all 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
21 | THE SOFTWARE.
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "Veneer",
3 | "version": "1.0.0",
4 | "description": "",
5 | "scripts": {
6 | "test": "echo \"Error: no test specified\" && exit 1",
7 | "build": "browserify ./app/js/app.js > ./dist/js/app.js",
8 | "watch": "watchify ./app/js/app.js -o ./dist/js/app.js"
9 | },
10 | "author": "",
11 | "license": "ISC",
12 | "devDependencies": {
13 | "browserify": "~13.0.1",
14 | "gulp": "~3.8.11",
15 | "gulp-bump": "~0.3.1",
16 | "gulp-concat": "~2.5.2",
17 | "gulp-load-plugins": "~0.9.0",
18 | "gulp-uglify": "~1.1.0",
19 | "gulp-watch": "~4.2.4",
20 | "gulp-zip": "~3.0.2",
21 | "watchify": "~3.7.0"
22 | },
23 | "dependencies": {
24 | "codemirror": "~5.15.2",
25 | "url": "~0.11.0"
26 | }
27 | }
28 |
--------------------------------------------------------------------------------