├── .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 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 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 |
28 | Github Repository 29 | 30 |
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 |
8 |
9 | 10 |
11 |
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 | --------------------------------------------------------------------------------