├── .gitignore ├── .travis.yml ├── ISSUE_TEMPLATE.md ├── LICENSE.md ├── README.md ├── cache.manifest ├── index.html ├── package.json ├── src ├── editor │ └── editor.js ├── filesystem │ ├── File.js │ ├── directory.js │ ├── filesystem.js │ ├── languageExtensions.json │ └── util.js ├── index.js ├── interface │ ├── interface.js │ ├── lang │ │ ├── lang.js │ │ └── translations.json │ ├── modal.js │ ├── tab.js │ ├── tabs.js │ ├── templates.js │ └── treeview.js └── network │ ├── hyperhostwrapper.js │ └── voice.js └── static ├── css ├── Monaco.ttf ├── codemirror-themes │ └── theme.css ├── codemirror.css ├── filetree │ ├── file-icons.css │ ├── toggle-small-expand.png │ ├── toggle-small.png │ └── treestyles.css ├── fonts │ ├── devopicons.woff2 │ ├── file-icons.woff2 │ ├── fontawesome.woff2 │ ├── mfixx.woff2 │ └── octicons.woff2 ├── show-hint.css ├── sourcecodelight.ttf ├── sourcecoderegular.ttf └── style.css ├── img ├── camera.png ├── collapse.png ├── contrast-black.png ├── contrast-white.png ├── diamond.png ├── folder.png ├── gear.png ├── icon144.png ├── icon168.png ├── icon192.png ├── icon48.png ├── icon72.png ├── icon96.png ├── launch.png ├── logo.png ├── mic.png ├── mockup.jpg ├── muted.png ├── network.png ├── newfile.png ├── nocamera.png ├── pointer.png ├── reload.png ├── save.png ├── splash.png ├── trash.png └── upload.png └── js ├── multihack.js └── vendor ├── FileSaver.min.js ├── addons ├── closebrackets.js ├── closetag.js ├── css-hint.js ├── html-hint.js ├── javascript-hint.js ├── matchbrackets.js ├── matchtags.js ├── show-hint.js ├── xml-fold.js └── xml-hint.js ├── codemirror.js ├── hyperhost.js ├── jszip.min.js ├── peer.js ├── socket.io.js └── syntaxmodes ├── css.js ├── htmlmixed.js ├── javascript.js ├── php.js └── xml.js /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | sudo: false 3 | node_js: 4 | - '5' 5 | script: 6 | - npm run test -------------------------------------------------------------------------------- /ISSUE_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | *Please fill out as much information as you can, it will help with debugging greatly!* 2 | 3 | ### Description 4 | 5 | [Description of the bug or feature] 6 | 7 | ### Steps to Reproduce 8 | 9 | 1. [First Step] 10 | 2. [Second Step] 11 | 3. [and so on...] 12 | 13 | **Expected behavior:** [What you expected to happen] 14 | 15 | **Actual behavior:** [What actually happened] 16 | 17 | ### Versions 18 | 19 | Please include OS version, browser version, and any network information (same network, VPN, etc) 20 | 21 | ### Console Log 22 |
23 | 24 | Click to expand 25 | 26 | ``` 27 | [Paste your console log here] 28 | ``` 29 |
30 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | Copyright (c) 2017 Thomas Mullen 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | SOFTWARE. 20 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 |
3 | MultiHack 4 |
5 |
6 |

7 |

Real-Time Editor and Voice Chat.

8 | 9 | [![JavaScript Style Guide](https://img.shields.io/badge/code_style-standard-brightgreen.svg)](https://standardjs.com) 10 | [![Gitter chat](https://img.shields.io/badge/gitter-join%20chat%20%E2%86%92-brightgreen.svg)](https://gitter.im/multihack/multihack) 11 | ![Alpha](https://img.shields.io/badge/status-alpha-green.svg?style=flat) 12 | 13 | Compatible with multihack-brackets! 14 | 15 | Tired of struggling to set up remote collaboration with your team? Try multihack 16 | 17 | **Still in active development. Expect instability and constant updates.** 18 | 19 | ## Usage: 20 | 21 | 1. Upload your project as a ZIP to https://multihack.github.io/multihack-web/, or use multihack-brackets. 22 | 23 | 2. Decide on a secret room ID, and have everyone enter it. 24 | 25 | 3. VOILA! Your project is now being synchronized in real time! 26 | 27 | ### Collaborate in Real Time 28 | 29 | Code is synchronized in real time between everyone on your team. 30 | 31 | ### Integrated Voice Chat 32 | 33 | Join a secure group call in one click with WebRTC. 34 | 35 | ### Syntax Highlighting 36 | 37 | Syntax highlighting for all web languages is supported. Adding new languages is as easy as adding a CSS file. 38 | 39 | Autocompletion for Javscript and CSS! 40 | 41 | ### Export Your Project 42 | 43 | Save your project as a ZIP archive and use it in your favourite native IDE. 44 | 45 | ## Run Your Own Instance 46 | 47 | To run your own instance, see [multihack-server](https://github.com/t-mullen/multihack-server). 48 | 49 | ## Introducting Version 4.0! 50 | 51 | - Multihack now uses a [Conflict-Free Replicated Data Type](https://en.wikipedia.org/wiki/Conflict-free_replicated_data_type) to merge conflicts and ensure everyone is always looking at the same code. Thanks @kifhan for assistance with this! 52 | 53 | - You can now see your team's carets, and the notifications are less instrusive. Thanks to @Worie! 54 | -------------------------------------------------------------------------------- /cache.manifest: -------------------------------------------------------------------------------- 1 | CACHE MANIFEST 2 | # VERSION 1.0.1 3 | CACHE: 4 | ./static/css/sourcecoderegular.ttf 5 | ./static/css/sourcecodelight.ttf 6 | ./static/css/Monaco.ttf 7 | ./static/js/vendor/codemirror.js 8 | ./static/js/vendor/syntaxmodes/javascript.js 9 | ./static/js/vendor/syntaxmodes/css.js 10 | ./static/js/vendor/syntaxmodes/xml.js 11 | ./static/js/vendor/syntaxmodes/htmlmixed.js 12 | ./static/js/vendor/syntaxmodes/php.js 13 | ./static/js/vendor/addons/xml-fold.js 14 | ./static/js/vendor/addons/closebrackets.js 15 | ./static/js/vendor/addons/matchbrackets.js 16 | ./static/js/vendor/addons/closetag.js 17 | ./static/js/vendor/addons/matchtags.js 18 | ./static/js/vendor/addons/show-hint.js 19 | ./static/js/vendor/addons/xml-hint.js 20 | ./static/js/vendor/addons/javascript-hint.js 21 | ./static/js/vendor/addons/css-hint.js 22 | ./static/js/vendor/addons/xml-hint.js 23 | ./static/js/vendor/addons/html-hint.js 24 | ./static/js/vendor/jszip.min.js 25 | ./static/js/vendor/FileSaver.min.js 26 | ./static/js/vendor/socket.io.js 27 | ./static/js/vendor/peer.js 28 | ./static/js/vendor/hyperhost.js 29 | ./static/js/multihack.js 30 | ./static/css/codemirror.css 31 | ./static/css/codemirror-themes/theme.css 32 | ./static/css/show-hint.css 33 | ./static/css/filetree/treestyles.css 34 | ./static/css/filetree/file-icons.css 35 | ./static/css/style.css 36 | ./static/img/icon192.png 37 | ./static/img/logo.png 38 | ./static/img/save.png 39 | ./static/img/launch.png 40 | ./static/img/muted.png 41 | ./static/img/network.png 42 | ./static/img/collapse.png 43 | ./static/img/contrast-white.png 44 | NETWORK: 45 | * -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | MultiHack - Realtime IDE 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 47 | 60 |
61 | 67 |
68 | 71 |
72 | 73 | 74 | 75 |
76 |
77 | 78 |
79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 116 | 117 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "multihack-web", 3 | "version": "4.2.3", 4 | "description": "A web-based editor with realtime collaboration and voice calls.", 5 | "main": "src/index.js", 6 | "directories": { 7 | "test": "test" 8 | }, 9 | "scripts": { 10 | "test": "standard --fix src/** && standard --fix test/** && tape test/*.js", 11 | "build": "browserify src/index.js -i wrtc -s Multihack -o static/js/multihack.js", 12 | "watch": "watchify src/index.js -i wrtc -s Multihack -o static/js/multihack.js" 13 | }, 14 | "author": "Thomas Mullen", 15 | "license": "MIT", 16 | "devDependencies": { 17 | "browserify": "^14.4.0", 18 | "standard": "^8.6.0", 19 | "tape": "^4.6.3", 20 | "watchify": "^3.9.0" 21 | }, 22 | "dependencies": { 23 | "clipboard": "^1.7.1", 24 | "cuid": "^1.3.8", 25 | "events": "^1.1.1", 26 | "file-icons-js": "github:websemantics/file-icons-js", 27 | "getusermedia": "^2.0.1", 28 | "inherits": "^2.0.3", 29 | "multihack-core": "^2.2.0", 30 | "mustache": "^2.3.0", 31 | "p2p-graph": "^1.1.1" 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/editor/editor.js: -------------------------------------------------------------------------------- 1 | /* globals CodeMirror */ 2 | 3 | var EventEmitter = require('events').EventEmitter 4 | var inherits = require('inherits') 5 | var FileSystem = require('./../filesystem/filesystem') 6 | 7 | inherits(Editor, EventEmitter) 8 | 9 | function Editor () { 10 | var self = this 11 | if (!(self instanceof Editor)) return new Editor() 12 | 13 | var textArea = document.getElementById('editor') 14 | 15 | var options = { 16 | mode: {name: 'javascript', globalVars: true}, 17 | extraKeys: {'tab': 'autocomplete'}, 18 | lineNumbers: true, 19 | theme: self._theme || 'atom', 20 | tabSize: 4, 21 | indentWithTabs: true, 22 | indentUnit: 4, 23 | lineWrapping: !!(window.innerWidth < 480), // No wrap on mobile 24 | styleActiveLine: true, 25 | matchBrackets: true, 26 | autoCloseBrackets: true, 27 | matchTags: {bothTags: true}, 28 | autoCloseTags: true, 29 | maxHighlightLength: 2000, 30 | crudeMeasuringFrom: 2000 31 | } 32 | 33 | self._cm = CodeMirror.fromTextArea(textArea, options) 34 | 35 | self._cm.on('keyup', function (editor, event) { 36 | if (!ExcludedIntelliSenseTriggerKeys[(event.keyCode || event.which).toString()]) { 37 | CodeMirror.commands.autocomplete(editor, null, { completeSingle: false }) 38 | } 39 | }) 40 | 41 | self._workingFile = null 42 | self._mutex = false 43 | 44 | self._cm.on('change', self._onchange.bind(self)) 45 | self._cm.on('beforeSelectionChange', self._onSelectionChange.bind(self)) 46 | 47 | self._theme = null 48 | self._remoteCarets = [] 49 | self._lastSelections = [] 50 | } 51 | 52 | Editor.prototype._onchange = function (cm, change) { 53 | var self = this 54 | 55 | if (self._mutex) return 56 | 57 | change.start = self._cm.indexFromPos(change.from) 58 | self.emit('change', { 59 | filePath: self._workingFile.path, 60 | change: change 61 | }) 62 | } 63 | 64 | Editor.prototype._onSelectionChange = function (cm, change) { 65 | var self = this 66 | 67 | var ranges = change.ranges.map(self._putHeadBeforeAnchor) 68 | 69 | self._workingFile.lastCursor = ranges[0].head 70 | 71 | self.emit('selection', { 72 | filePath: self._workingFile.path, 73 | change: { 74 | type: 'selection', 75 | ranges: ranges 76 | } 77 | }) 78 | } 79 | 80 | Editor.prototype.highlight = function (selections) { 81 | var self = this 82 | 83 | self._lastSelections = selections 84 | 85 | // Timeout so selections are always applied after changes 86 | window.setTimeout(function () { 87 | if (!self._workingFile) return 88 | 89 | self._remoteCarets.forEach(self._removeRemoteCaret) 90 | self._remoteCarets = [] 91 | 92 | self._cm.getAllMarks().forEach(function (mark) { 93 | mark.clear() 94 | }) 95 | 96 | selections.forEach(function (sel) { 97 | if (sel.filePath !== self._workingFile.path) return 98 | 99 | sel.change.ranges.forEach(function (range) { 100 | if (self._isNonEmptyRange(range)) { 101 | self._cm.markText(range.head, range.anchor, { 102 | className: 'remoteSelection' 103 | }) 104 | } else { 105 | self._insertRemoteCaret(range) 106 | } 107 | }) 108 | }) 109 | }, 10) 110 | } 111 | 112 | Editor.prototype._insertRemoteCaret = function (range) { 113 | var self = this 114 | 115 | var caretEl = document.createElement('div') 116 | 117 | caretEl.classList.add('remoteCaret') 118 | caretEl.style.height = self._cm.defaultTextHeight() + 'px' 119 | caretEl.style.marginTop = '-' + self._cm.defaultTextHeight() + 'px' 120 | 121 | self._remoteCarets.push(caretEl) 122 | 123 | self._cm.addWidget(range.anchor, caretEl, false) 124 | } 125 | 126 | Editor.prototype._removeRemoteCaret = function (caret) { 127 | caret.parentNode.removeChild(caret) 128 | } 129 | 130 | // Handle an external change 131 | Editor.prototype.change = function (filePath, change) { 132 | var self = this 133 | 134 | if (!self._workingFile || filePath !== self._workingFile.path) { 135 | FileSystem.getFile(filePath).doc.replaceRange(change.text, change.to, change.from) 136 | } else { 137 | self._mutex = true 138 | self._cm.replaceRange(change.text, change.to, change.from) 139 | self._mutex = false 140 | } 141 | } 142 | 143 | Editor.prototype.posFromIndex = function (index) { 144 | var self = this 145 | 146 | return self._cm.posFromIndex(index) 147 | } 148 | 149 | Editor.prototype.open = function (filePath) { 150 | var self = this 151 | if (self._workingFile && filePath === self._workingFile.path) return 152 | self._workingFile = FileSystem.get(filePath) 153 | document.getElementById('working-file').innerHTML = self._workingFile.path 154 | switch (self._workingFile.viewMapping) { 155 | case 'image': 156 | document.querySelector('.editor-wrapper').style.display = 'none' 157 | document.querySelector('.image-wrapper').style.display = '' 158 | self._workingFile.read(function (content) { 159 | document.querySelector('.image-wrapper > img').src = 'data:text/javascript;base64,' + content 160 | }) 161 | break 162 | default: 163 | document.querySelector('.editor-wrapper').style.display = '' 164 | document.querySelector('.image-wrapper').style.display = 'none' 165 | self._cm.swapDoc(self._workingFile.doc) 166 | if (self._workingFile.lastCursor) { 167 | self._cm.focus() 168 | self._cm.scrollIntoView(self._workingFile.lastCursor) 169 | self._cm.setCursor(self._workingFile.lastCursor) 170 | } else { 171 | self.focus() 172 | } 173 | break 174 | } 175 | 176 | self.highlight(self._lastSelections) 177 | } 178 | 179 | Editor.prototype.focus = function () { 180 | var self = this 181 | self._cm.focus() 182 | self._cm.setCursor(0, 0) 183 | } 184 | 185 | Editor.prototype.close = function () { 186 | var self = this 187 | self._workingFile = null 188 | document.getElementById('working-file').innerHTML = '' 189 | document.querySelector('.editor-wrapper').style.display = 'none' 190 | document.querySelector('.editor-wrapper').style.display = 'none' 191 | } 192 | 193 | Editor.prototype.getWorkingFile = function () { 194 | var self = this 195 | return self._workingFile 196 | } 197 | 198 | Editor.prototype._isNonEmptyRange = function (range) { 199 | return range.head.ch !== range.anchor.ch || range.head.line !== range.anchor.line 200 | } 201 | 202 | Editor.prototype._putHeadBeforeAnchor = function (range) { 203 | var nr = JSON.parse(JSON.stringify(range)) 204 | if (nr.head.line > nr.anchor.line || ( 205 | nr.head.line === nr.anchor.line && nr.head.ch > nr.anchor.ch 206 | )) { 207 | var temp = nr.head 208 | nr.head = nr.anchor 209 | nr.anchor = temp 210 | } 211 | return nr 212 | } 213 | 214 | module.exports = new Editor() 215 | 216 | var ExcludedIntelliSenseTriggerKeys = { 217 | '8': 'backspace', 218 | '9': 'tab', 219 | '13': 'enter', 220 | '16': 'shift', 221 | '17': 'ctrl', 222 | '18': 'alt', 223 | '19': 'pause', 224 | '20': 'capslock', 225 | '27': 'escape', 226 | '32': 'space', 227 | '33': 'pageup', 228 | '34': 'pagedown', 229 | '35': 'end', 230 | '36': 'home', 231 | '37': 'left', 232 | '38': 'up', 233 | '39': 'right', 234 | '40': 'down', 235 | '45': 'insert', 236 | '46': 'delete', 237 | '91': 'left window key', 238 | '92': 'right window key', 239 | '93': 'select', 240 | '107': 'add', 241 | '109': 'subtract', 242 | '110': 'decimal point', 243 | '111': 'divide', 244 | '112': 'f1', 245 | '113': 'f2', 246 | '114': 'f3', 247 | '115': 'f4', 248 | '116': 'f5', 249 | '117': 'f6', 250 | '118': 'f7', 251 | '119': 'f8', 252 | '120': 'f9', 253 | '121': 'f10', 254 | '122': 'f11', 255 | '123': 'f12', 256 | '144': 'numlock', 257 | '145': 'scrolllock', 258 | '186': 'semicolon', 259 | '187': 'equalsign', 260 | '188': 'comma', 261 | '189': 'dash', 262 | '191': 'slash', 263 | '192': 'graveaccent', 264 | '219': 'bracket', 265 | '220': 'backslash', 266 | '222': 'quote' 267 | } 268 | -------------------------------------------------------------------------------- /src/filesystem/File.js: -------------------------------------------------------------------------------- 1 | /* globals CodeMirror */ 2 | 3 | var util = require('./util') 4 | 5 | function File (path) { 6 | var self = this 7 | if (!(self instanceof File)) return new File() 8 | 9 | self.name = util.getFilename(path) 10 | self.path = path 11 | self.isDir = false 12 | self.viewMapping = util.getViewMapping(path) 13 | self.alreadyLink = false 14 | self.content = '' 15 | self.size = 0 16 | self._doc = null // holds temporary CodeMirror document reference 17 | self._releaseTimer = null 18 | self.lastCursor = null 19 | 20 | Object.defineProperty(self, 'doc', { 21 | get: function () { 22 | if (!self._doc) { 23 | self._doc = new CodeMirror.Doc(self.content, util.pathToMode(self.path)) 24 | } 25 | if (!self._releaseTimer) { 26 | self._releaseTimer = window.setTimeout(function () { 27 | self.content = self._doc.getValue() // save the value 28 | self._doc = null // allow garbage collection 29 | self._releaseTimer = null 30 | }, 30000) // release document reference after 30 seconds 31 | } else { 32 | window.clearTimeout(self._releaseTimer) 33 | } 34 | return self._doc 35 | } 36 | }) 37 | } 38 | 39 | File.prototype.write = function (content, cb) { 40 | var self = this 41 | 42 | cb = cb || function () {} 43 | 44 | if (self._doc) { 45 | self._doc.setValue(content) 46 | } else { 47 | self.content = content 48 | } 49 | 50 | self.size = content.length 51 | } 52 | 53 | File.prototype.read = function (cb) { 54 | var self = this 55 | 56 | cb = cb || function () {} 57 | 58 | if (self._doc) { 59 | cb(self._doc.getValue()) 60 | } else { 61 | cb(self.content) 62 | } 63 | } 64 | 65 | module.exports = File 66 | -------------------------------------------------------------------------------- /src/filesystem/directory.js: -------------------------------------------------------------------------------- 1 | var util = require('./util') 2 | 3 | function Directory (path) { 4 | var self = this 5 | if (!(self instanceof Directory)) return new Directory() 6 | 7 | self.name = util.getFilename(path) 8 | self.path = path 9 | self.nodes = [] 10 | self.isDir = true 11 | } 12 | 13 | module.exports = Directory 14 | -------------------------------------------------------------------------------- /src/filesystem/filesystem.js: -------------------------------------------------------------------------------- 1 | /* globals JSZip, CodeMirror */ 2 | 3 | var File = require('./file') 4 | var Directory = require('./directory') 5 | var util = require('./util') 6 | 7 | var EventEmitter = require('events').EventEmitter 8 | var inherits = require('inherits') 9 | 10 | inherits(FileSystem, EventEmitter) 11 | 12 | var ignoredFilenames = ['__MACOSX', '.DS_Store', 'node_modules'] 13 | 14 | function FileSystem () { 15 | var self = this 16 | if (!(self instanceof FileSystem)) return new FileSystem() 17 | 18 | self._tree = [ 19 | new Directory('') 20 | ] 21 | } 22 | 23 | // Loads a project 24 | FileSystem.prototype.loadProject = function (file, cb) { 25 | var self = this 26 | 27 | // TODO: More load types 28 | self.unzip(file, function () { 29 | cb(self._tree[0].nodes) 30 | }) 31 | 32 | // TODO: More input options 33 | } 34 | 35 | // Saves the project 36 | FileSystem.prototype.saveProject = function (saveType, cb) { 37 | var self = this 38 | 39 | // TODO: More save types 40 | if (saveType === 'zip') { 41 | try { 42 | var zip = new JSZip() 43 | util.zipTree(zip, self._tree[0].nodes) 44 | 45 | zip.generateAsync({type: 'blob'}).then(function (content) { 46 | window.saveAs(content, 'myProject.zip') 47 | cb(true) 48 | }) 49 | } catch (err) { 50 | console.error(err) 51 | cb(false) 52 | } 53 | } 54 | } 55 | 56 | // Makes a directory, building paths 57 | FileSystem.prototype.mkdir = function (path) { 58 | var self = this 59 | 60 | var parentPath = path.split('/') 61 | parentPath.splice(-1, 1) 62 | parentPath = parentPath.join('/') 63 | 64 | self._buildPath(parentPath) 65 | if (self._getNode(path, self._getNode(parentPath).nodes)) return false 66 | self._getNode(parentPath).nodes.push(new Directory(path)) 67 | 68 | return true 69 | } 70 | 71 | // Makes an empty file (must set doc), building paths 72 | FileSystem.prototype.mkfile = function (path) { 73 | var self = this 74 | var parentPath = path.split('/') 75 | parentPath.splice(-1, 1) 76 | parentPath = parentPath.join('/') 77 | 78 | self._buildPath(parentPath) 79 | if (self._getNode(path, self._getNode(parentPath).nodes)) return false 80 | self._getNode(parentPath).nodes.push(new File(path)) 81 | 82 | return true 83 | } 84 | 85 | FileSystem.prototype.getContained = function (path) { 86 | var self = this 87 | 88 | var dir = self.getFile(path) 89 | if (!dir.isDir) return [dir] 90 | 91 | var contained = [] 92 | 93 | dir.nodes.forEach(function (node) { 94 | self.getContained(node.path).forEach(function (c) { 95 | contained.push(c) 96 | }) 97 | }) 98 | 99 | return contained 100 | } 101 | 102 | // Ensures all directories have been built along a path 103 | FileSystem.prototype._buildPath = function (path) { 104 | var self = this 105 | 106 | var split = path.split('/') 107 | for (var i = 0; i <= split.length; i++) { 108 | var check = split.slice(0, i).join('/') 109 | if (!self._getNode(check)) { 110 | self.mkdir(check) 111 | } 112 | } 113 | } 114 | 115 | // Recursive node search 116 | FileSystem.prototype._getNode = function (path, nodeList) { 117 | var self = this 118 | 119 | nodeList = nodeList || self._tree 120 | for (var i = 0; i < nodeList.length; i++) { 121 | if (nodeList[i].path === path) { 122 | return nodeList[i] 123 | } else if (nodeList[i].isDir) { 124 | var recur = self._getNode(path, nodeList[i].nodes) 125 | if (recur) return recur 126 | } 127 | } 128 | return undefined 129 | } 130 | 131 | // Checks if a file/directory exists at a path 132 | FileSystem.prototype.exists = function (path) { 133 | var self = this 134 | 135 | var parentPath = path.split('/') 136 | parentPath.splice(-1, 1) 137 | parentPath = parentPath.join('/') 138 | 139 | return !!self._getNode(path) 140 | } 141 | 142 | // Gets a node, building any broken paths 143 | FileSystem.prototype.get = function (path) { 144 | var self = this 145 | 146 | var parentPath = path.split('/') 147 | parentPath.splice(-1, 1) 148 | parentPath = parentPath.join('/') 149 | 150 | self._buildPath(parentPath) 151 | return self._getNode(path) 152 | } 153 | 154 | // Gets an existing file, or creates one if none exists 155 | FileSystem.prototype.getFile = function (path) { 156 | var self = this 157 | 158 | var parentPath = path.split('/') 159 | parentPath.splice(-1, 1) 160 | parentPath = parentPath.join('/') 161 | 162 | self._buildPath(parentPath) 163 | return self._getNode(path) || (function () { 164 | self.mkfile(path) 165 | return self._getNode(path) 166 | }()) 167 | } 168 | 169 | // Deletes a file/directory on a path 170 | FileSystem.prototype.delete = function (path) { 171 | var self = this 172 | var parentPath = path.split('/') 173 | parentPath.splice(-1, 1) 174 | parentPath = parentPath.join('/') 175 | self._getNode(parentPath).nodes = self._getNode(parentPath).nodes.filter(function (e) { 176 | if (e.path === path) { 177 | return false 178 | } 179 | return true 180 | }) 181 | } 182 | 183 | // Returns the useable part of the tree 184 | FileSystem.prototype.getTree = function () { 185 | var self = this 186 | 187 | return self._tree[0].nodes 188 | } 189 | 190 | // Return array of all files and folders 191 | FileSystem.prototype.getAllFiles = function () { 192 | var self = this 193 | 194 | var all = [] 195 | 196 | function walk (dir) { 197 | for (var i = 0; i < dir.nodes.length; i++) { 198 | if (dir.nodes[i].isDir) { 199 | walk(dir.nodes[i]) 200 | } 201 | all.push(dir.nodes[i]) 202 | } 203 | } 204 | 205 | walk(self._tree[0]) 206 | 207 | return all 208 | } 209 | 210 | // Loads a project from a zip file 211 | FileSystem.prototype.unzip = function (file, cb) { 212 | var self = this 213 | 214 | JSZip.loadAsync(file).then(function (zip) { 215 | var awaiting = Object.keys(zip.files).length 216 | zip.forEach(function (relativePath, zipEntry) { 217 | if (relativePath[0] !== '/') relativePath = '/' + relativePath 218 | 219 | // Filter out ignored files 220 | for (var i = 0; i < ignoredFilenames.length; i++) { 221 | if (relativePath.indexOf(ignoredFilenames[i]) !== -1) { 222 | if (--awaiting <= 0) cb() 223 | return 224 | } 225 | } 226 | 227 | relativePath = relativePath.split('/') 228 | relativePath.splice(0, 1) 229 | relativePath = relativePath.join('/') 230 | relativePath = '/' + relativePath 231 | 232 | if (zipEntry.dir) { 233 | relativePath = relativePath.slice(0, -1) 234 | } 235 | 236 | var parentPath = relativePath.split('/') 237 | parentPath.splice(-1, 1) 238 | parentPath = parentPath.join('/') 239 | 240 | if (zipEntry.dir) { 241 | self.mkdir(relativePath) 242 | if (--awaiting <= 0) cb() 243 | } else { 244 | self.mkfile(relativePath) 245 | zipEntry.async(util.getLoadMode(relativePath)).then(function (content) { 246 | self.get(relativePath).write(content) 247 | self.emit('unzipFile', self.get(relativePath)) 248 | if (--awaiting <= 0) cb() 249 | }) 250 | } 251 | }) 252 | }) 253 | } 254 | 255 | module.exports = new FileSystem() 256 | -------------------------------------------------------------------------------- /src/filesystem/util.js: -------------------------------------------------------------------------------- 1 | var util = {} 2 | 3 | util.getFilename = function (path) { 4 | var split = path.split('/') 5 | return split[split.length - 1] 6 | } 7 | 8 | util.getExtension = function (path) { 9 | path = util.getFilename(path) 10 | var split = path.split('.') 11 | return split[split.length - 1] 12 | } 13 | 14 | var CM_MAPPINGS = { 15 | 'js': 'javascript', 16 | 'coffee': 'javascript', 17 | 'ts': 'javascript', 18 | 'json': 'javascript', 19 | 'css': 'css', 20 | 'sass': 'css', 21 | 'less': 'css', 22 | 'html': 'htmlmixed', 23 | 'xml': 'xml', 24 | 'php': 'application/x-httpd-php' 25 | } 26 | util.pathToMode = function (path) { 27 | return { 28 | name: CM_MAPPINGS[util.getExtension(path)], 29 | globalVars: true 30 | } 31 | } 32 | 33 | var VIEW_MAPPINGS = { 34 | 'png': 'image', 35 | 'jpg': 'image', 36 | 'jpeg': 'image', 37 | 'jpeg2000': 'image', 38 | 'tif': 'image', 39 | 'tiff': 'image', 40 | 'gif': 'image', 41 | 'bmp': 'image', 42 | 'ico': 'image' 43 | } 44 | util.getViewMapping = function (path) { 45 | return VIEW_MAPPINGS[util.getExtension(path)] || 'text' 46 | } 47 | util.getLoadMode = function (path) { 48 | switch (util.getViewMapping(path)) { 49 | case 'image': 50 | return 'base64' 51 | default: 52 | return 'string' 53 | } 54 | } 55 | 56 | // Creates a zip archive from a file tree 57 | util.zipTree = function (zip, nodeList) { 58 | console.log(nodeList) 59 | for (var i = 0; i < nodeList.length; i++) { 60 | // Iterate children 61 | 62 | if (nodeList[i].isDir) { 63 | util.zipTree(zip, nodeList[i].nodes) 64 | } else { 65 | zip.file(nodeList[i].path.slice(1), nodeList[i].content) 66 | } 67 | } 68 | } 69 | 70 | util.getParameterByName = function (name) { 71 | var url = window.location.href 72 | name = name.replace(/[[\]]/g, '\\$&') 73 | var regex = new RegExp('[?&]' + name + '(=([^&#]*)|&|#|$)') 74 | var results = regex.exec(url) 75 | if (!results) return null 76 | if (!results[2]) return '' 77 | return decodeURIComponent(results[2].replace(/\+/g, ' ')) 78 | } 79 | 80 | module.exports = util 81 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | var FileSystem = require('./filesystem/filesystem') 2 | var Interface = require('./interface/interface') 3 | var Editor = require('./editor/editor') 4 | var Remote = require('multihack-core') 5 | var HyperHostWrapper = require('./network/hyperhostwrapper') 6 | var util = require('./filesystem/util') 7 | var Voice = require('./network/voice') 8 | var lang = require('./interface/lang/lang') 9 | var lg = lang.get.bind(lang) 10 | 11 | function Multihack (config) { 12 | var self = this 13 | if (!(self instanceof Multihack)) return new Multihack(config) 14 | 15 | config = config || {} 16 | 17 | Interface.on('openFile', function (e) { 18 | Editor.open(e.path) 19 | Interface.fileOpened(e.path) 20 | }) 21 | 22 | Interface.on('addFile', function (e) { 23 | var created = FileSystem.mkfile(e.path) 24 | 25 | if (created) { 26 | Interface.treeview.addFile(e.parentElement, FileSystem.get(e.path)) 27 | Editor.open(e.path) 28 | Interface.fileOpened(e.path) 29 | } 30 | self._remote.createFile(e.path) 31 | }) 32 | 33 | Interface.on('closeFile', function(e) { 34 | if (!e.activePath) { 35 | Editor.close() 36 | } else { 37 | Editor.open(e.activePath) 38 | } 39 | }) 40 | 41 | FileSystem.on('unzipFile', function (file) { 42 | file.read(function (content) { 43 | self._remote.createFile(file.path, content) 44 | }) 45 | }) 46 | 47 | Interface.on('addDir', function (e) { 48 | var created = FileSystem.mkdir(e.path) 49 | 50 | if (created) { 51 | Interface.treeview.addDir(e.parentElement, FileSystem.get(e.path)) 52 | } 53 | self._remote.createDir(e.path) 54 | }) 55 | 56 | Interface.on('removeDir', function (e) { 57 | var dir = FileSystem.get(e.path) 58 | var workingFile = Editor.getWorkingFile() 59 | 60 | Interface.confirmDelete(dir.name, function () { 61 | Interface.treeview.remove(e.parentElement, dir) 62 | 63 | FileSystem.getContained(e.path).forEach(function (file) { 64 | if (workingFile && file.path === workingFile.path) { 65 | Editor.close() 66 | } 67 | self._remote.deleteFile(file.path) 68 | Interface.fileDeleted(file.path) 69 | }) 70 | self._remote.deleteFile(e.path) 71 | Interface.fileDeleted(e.path) 72 | }) 73 | }) 74 | 75 | Interface.on('deleteCurrent', function (e) { 76 | var workingFile = Editor.getWorkingFile() 77 | if (!workingFile) return 78 | 79 | Interface.confirmDelete(workingFile.name, function () { 80 | Editor.close() 81 | var workingPath = workingFile.path 82 | var parentElement = Interface.treeview.getParentElement(workingPath) 83 | if (parentElement) { 84 | Interface.treeview.remove(parentElement, FileSystem.get(workingPath)) 85 | } 86 | FileSystem.delete(workingPath) 87 | self._remote.deleteFile(workingPath) 88 | Interface.fileDeleted(workingPath) 89 | }) 90 | }) 91 | 92 | self.embed = util.getParameterByName('embed') || null 93 | self.roomID = util.getParameterByName('room') || null 94 | self.hostname = config.hostname 95 | 96 | Interface.on('saveAs', function (saveType) { 97 | FileSystem.getContained('').forEach(function (file) { 98 | file.write(self._remote.getContent(file.path)) 99 | }) 100 | FileSystem.saveProject(saveType, function (success) { 101 | if (success) { 102 | Interface.alert(lg('save_success_title'), lg('save_success')) 103 | } else { 104 | Interface.alert(lg('save_fail_title'), lg('save_fail')) 105 | } 106 | }) 107 | }) 108 | 109 | Interface.on('deploy', function () { 110 | HyperHostWrapper.on('error', function (err) { 111 | Interface.alert(lg('deploy_fail_title'), err) 112 | }) 113 | 114 | HyperHostWrapper.on('ready', function (url) { 115 | Interface.alertHTML(lg('deploy_title'), lg('deploy_success', {url: url})) 116 | }) 117 | 118 | HyperHostWrapper.deploy(FileSystem.getTree()) 119 | }) 120 | 121 | Interface.hideOverlay() 122 | if (self.embed) { 123 | self._initRemote() 124 | } else { 125 | self._initRemote(function () { 126 | Interface.getProject(function (project) { 127 | if (project) { 128 | Interface.showOverlay() 129 | FileSystem.loadProject(project, function (tree) { 130 | Interface.treeview.rerender(tree) 131 | Interface.hideOverlay() 132 | }) 133 | } 134 | }) 135 | }) 136 | } 137 | } 138 | 139 | Multihack.prototype._initRemote = function (cb) { 140 | var self = this 141 | 142 | function onRoom (data) { 143 | self.roomID = data.room 144 | Interface.setRoom(self.roomID) 145 | window.history.pushState('Multihack', lg('history_item', {room: self.roomID}), '?room=' + self.roomID + (self.embed ? '&embed=true' : '')) 146 | self.nickname = data.nickname 147 | self._remote = new Remote({ 148 | hostname: self.hostname, 149 | room: self.roomID, 150 | nickname: self.nickname, 151 | voice: Voice, 152 | wrtc: null 153 | }) 154 | self._remote.posFromIndex = function (filePath, index, cb) { 155 | cb(FileSystem.getFile(filePath).doc.posFromIndex(index)) 156 | } 157 | 158 | document.getElementById('voice').style.display = '' 159 | document.getElementById('network').style.display = '' 160 | 161 | Interface.on('voiceToggle', function () { 162 | self._remote.voice.toggle() 163 | }) 164 | Interface.on('showNetwork', function () { 165 | Interface.showNetwork(self._remote.peers, self.roomID, self._remote.nop2p, self._remote.mustForward) 166 | }) 167 | 168 | self._remote.on('changeSelection', function (selections) { 169 | Editor.highlight(selections) 170 | }) 171 | self._remote.on('changeFile', function (data) { 172 | Editor.change(data.filePath, data.change) 173 | }) 174 | self._remote.on('deleteFile', function (data) { 175 | var parentElement = Interface.treeview.getParentElement(data.filePath) 176 | var workingFile = Editor.getWorkingFile() 177 | 178 | if (workingFile && data.filePath === workingFile.path) { 179 | Editor.close() 180 | } 181 | 182 | if (parentElement) { 183 | Interface.treeview.remove(parentElement, FileSystem.get(data.filePath)) 184 | } 185 | FileSystem.delete(data.filePath) 186 | Interface.fileDeleted(data.filePath) 187 | }) 188 | self._remote.on('createFile', function (data) { 189 | FileSystem.getFile(data.filePath).write(data.content) 190 | Interface.treeview.rerender(FileSystem.getTree()) 191 | if (!Editor.getWorkingFile()) { 192 | Editor.open(data.filePath) 193 | Interface.fileOpened(data.filePath) 194 | } 195 | }) 196 | self._remote.on('createDir', function (data) { 197 | FileSystem.mkdir(data.path) 198 | Interface.treeview.rerender(FileSystem.getTree()) 199 | }) 200 | self._remote.on('lostPeer', function (peer) { 201 | if (self.embed) return 202 | Interface.flashTooltip('tooltip-lostpeer', lg('lost_connection', {nickname: peer.metadata.nickname})) 203 | }) 204 | 205 | Editor.on('change', function (data) { 206 | self._remote.changeFile(data.filePath, data.change) 207 | }) 208 | Editor.on('selection', function (data) { 209 | self._remote.changeSelection(data) 210 | }) 211 | 212 | cb() 213 | } 214 | 215 | // Random starting room (to be changed) or from query 216 | if (!self.roomID && !self.embed) { 217 | Interface.getRoom(Math.random().toString(36).substr(2), onRoom) 218 | } else if (!self.embed) { 219 | Interface.getNickname(self.roomID, onRoom) 220 | } else { 221 | Interface.embedMode() 222 | onRoom({ 223 | room: self.roomID || Math.random().toString(36).substr(2), 224 | nickname: lg('default_nickname') 225 | }) 226 | } 227 | } 228 | 229 | module.exports = Multihack 230 | -------------------------------------------------------------------------------- /src/interface/interface.js: -------------------------------------------------------------------------------- 1 | var EventEmitter = require('events').EventEmitter 2 | var inherits = require('inherits') 3 | var Modal = require('./modal') 4 | var TreeView = require('./treeview') 5 | var PeerGraph = require('p2p-graph') 6 | var Tabs = require('./tabs') 7 | var cuid = require('cuid') 8 | var lang = require('./lang/lang') 9 | var lg = lang.get.bind(lang) 10 | var Clipboard = require('clipboard') 11 | 12 | inherits(Interface, EventEmitter) 13 | 14 | function Interface() { 15 | var self = this 16 | if (!(self instanceof Interface)) return new Interface() 17 | 18 | self.treeview = new TreeView() 19 | 20 | self.treeview.on('open', function (e) { 21 | self.emit('openFile', e) 22 | }) 23 | 24 | self.treeview.on('remove', function (e) { 25 | self.emit('removeDir', e) 26 | }) 27 | 28 | Tabs.on('change', function (e) { 29 | self.emit('openFile', e) 30 | }) 31 | 32 | Tabs.on('close', function (e) { 33 | self.emit('closeFile', e) 34 | }) 35 | 36 | self.addCounter = 1 37 | self.treeview.on('add', function (e) { 38 | self.newFileDialog(e.path, function (name, type) { 39 | e.path = e.path + '/' + name 40 | if (type === 'dir') { 41 | self.emit('addDir', e) 42 | } else { 43 | self.emit('addFile', e) 44 | } 45 | }) 46 | }) 47 | 48 | // Setup sidebar 49 | var sidebar = document.getElementById('sidebar') 50 | self.collapsed = false 51 | document.getElementById('collapsesidebar').addEventListener('click', function () { 52 | self.collapsed = !self.collapsed 53 | if (self.collapsed) { 54 | sidebar.className = sidebar.className + ' collapsed' 55 | } else { 56 | sidebar.className = sidebar.className.replace('collapsed', '') 57 | } 58 | }) 59 | 60 | // Click room ID to copy share link 61 | new Clipboard('#room', { 62 | text: function (trigger) { 63 | return window.location.href 64 | } 65 | }); 66 | 67 | // Setup contrast toggle 68 | var contrast = false 69 | document.getElementById('image-contrast').addEventListener('click', function () { 70 | contrast = !contrast 71 | document.querySelector('.image-wrapper').style.backgroundColor = contrast ? 'white' : 'black' 72 | document.querySelector('#image-contrast > img').src = contrast ? 'static/img/contrast-black.png' : 'static/img/contrast-white.png' 73 | }) 74 | 75 | // Setup save button 76 | document.getElementById('save').addEventListener('click', function () { 77 | self.emit('saveAs', 'zip') 78 | }) 79 | 80 | // Setup voice button 81 | document.getElementById('voice').addEventListener('click', function () { 82 | self.emit('voiceToggle') 83 | }) 84 | 85 | // Setup deploy button 86 | document.getElementById('deploy').addEventListener('click', function () { 87 | self.emit('deploy') 88 | }) 89 | 90 | // Network button 91 | document.getElementById('network').addEventListener('click', function () { 92 | self.emit('showNetwork') 93 | }) 94 | 95 | // Setup delete button 96 | document.getElementById('delete').addEventListener('click', function () { 97 | self.emit('deleteCurrent') 98 | }) 99 | } 100 | 101 | Interface.prototype.newFileDialog = function (path, cb) { 102 | var modal = new Modal('newFile', { 103 | title: lg('create_title'), 104 | path: path 105 | }) 106 | 107 | modal.on('done', function (e) { 108 | modal.close() 109 | var name = e.inputs[0].value 110 | var type = e.target.dataset['type'] 111 | if (!name) { 112 | name = (type === 'dir' ? lg('new_folder') : lg('new_file')) + '-' + cuid().slice(-7, -1) 113 | } 114 | if (cb) cb(name, type) 115 | }) 116 | modal.on('cancel', function () { 117 | modal.close() 118 | }) 119 | modal.open() 120 | } 121 | 122 | Interface.prototype.confirmDelete = function (fileName, cb) { 123 | var modal = new Modal('confirm-delete', { 124 | fileName: fileName 125 | }) 126 | 127 | modal.on('done', function (e) { 128 | modal.close() 129 | if (cb) cb(true) 130 | }) 131 | modal.on('cancel', function () { 132 | modal.close() 133 | }) 134 | modal.open() 135 | } 136 | 137 | Interface.prototype.getProject = function (cb) { 138 | // var self = this 139 | 140 | var projectModal = new Modal('file', { 141 | title: lg('load_title'), 142 | message: lg('load_prompt') 143 | }) 144 | projectModal.on('cancel', function () { 145 | projectModal.close() 146 | if (cb) cb(null) 147 | }) 148 | projectModal.open() 149 | 150 | var input = projectModal.el.querySelector('input[type="file"]') 151 | projectModal.el.querySelector('#file-button').addEventListener('click', function () { 152 | input.click() 153 | }) 154 | input.addEventListener('change', function () { 155 | projectModal.close() 156 | cb(input.files[0]) 157 | }) 158 | } 159 | 160 | Interface.prototype.getRoom = function (roomID, cb) { 161 | var self = this 162 | 163 | var roomModal = new Modal('input', { 164 | title: lg('choose_room_title'), 165 | message: lg('choose_room_prompt'), 166 | placeholder: lg('room_placeholder'), 167 | default: roomID 168 | }) 169 | roomModal.on('done', function (e) { 170 | roomModal.close() 171 | self.getNickname(e.inputs[0].value, cb) 172 | }) 173 | roomModal.on('cancel', function () { 174 | roomModal.close() 175 | self.alertHTML(lg('offline_title'), lg('offline_alert')) 176 | }) 177 | roomModal.open() 178 | } 179 | 180 | Interface.prototype.getNickname = function (room, cb) { 181 | var modal = new Modal('force-input', { 182 | title: lg('nickname_prompt_title'), 183 | message: lg('nickname_prompt'), 184 | placeholder: lg('nickname_placeholder'), 185 | default: '' 186 | }) 187 | modal.on('done', function (e) { 188 | modal.close() 189 | if (cb) { 190 | cb({ 191 | room: room, 192 | nickname: e.inputs[0].value 193 | }) 194 | } 195 | }) 196 | modal.open() 197 | } 198 | 199 | Interface.prototype.alert = function (title, message, cb) { 200 | var alertModal = new Modal('alert', { 201 | title: title, 202 | message: message 203 | }) 204 | alertModal.on('done', function (e) { 205 | alertModal.close() 206 | if (cb) cb() 207 | }) 208 | alertModal.open() 209 | } 210 | 211 | Interface.prototype.flashTooltip = function (id, message) { 212 | var tooltip = document.getElementById(id) 213 | var span = tooltip.querySelector('span') 214 | 215 | span.innerHTML = message 216 | tooltip.style.opacity = 1 217 | tooltip.style.display = '' 218 | 219 | setTimeout(function () { 220 | tooltip.style.opacity = 0 221 | setTimeout(function () { 222 | tooltip.style.display = '' 223 | }, 300) 224 | }, 3000) 225 | } 226 | 227 | Interface.prototype.alertHTML = function (title, message, cb) { 228 | var alertModal = new Modal('alert-html', { 229 | title: title, 230 | message: message 231 | }) 232 | alertModal.on('done', function (e) { 233 | alertModal.close() 234 | if (cb) cb() 235 | }) 236 | alertModal.open() 237 | } 238 | 239 | Interface.prototype.embedMode = function () { 240 | var self = this 241 | 242 | self.collapsed = true 243 | document.querySelector('body').className += ' embed' 244 | document.querySelector('#sidebar').className = 'sidebar theme-light collapsed' 245 | } 246 | 247 | Interface.prototype.setRoom = function (roomID) { 248 | document.querySelector('#room').innerHTML = roomID 249 | } 250 | 251 | Interface.prototype.showNetwork = function (peers, room, nop2p, mustForward) { 252 | var modal = new Modal('network', { 253 | room: room 254 | }) 255 | 256 | modal.on('cancel', function () { 257 | modal.close() 258 | }) 259 | 260 | modal.open() 261 | 262 | var el = document.querySelector('#network-graph') 263 | el.style.overflow = 'hidden' 264 | var graph = new PeerGraph(el) 265 | 266 | graph.add({ 267 | id: 'Me', 268 | me: true, 269 | name: lg('you') 270 | }) 271 | 272 | var proxyID = nop2p ? 'Server' : 'Me' 273 | 274 | if (mustForward || nop2p) { 275 | graph.add({ 276 | id: 'Server', 277 | me: false, 278 | name: lg('server') 279 | }) 280 | graph.connect('Server', 'Me') 281 | } 282 | 283 | for (var i = 0; i < peers.length; i++) { 284 | graph.add({ 285 | id: peers[i].id, 286 | me: false, 287 | name: peers[i].metadata.nickname 288 | }) 289 | if (peers[i].nop2p) { 290 | graph.connect('Server', peers[i].id) 291 | } else { 292 | graph.connect(proxyID, peers[i].id) 293 | } 294 | } 295 | 296 | modal.on('done', function (e) { 297 | graph.destroy() 298 | }) 299 | } 300 | 301 | Interface.prototype.fileOpened = function (filepath) { 302 | Tabs.fileOpened(filepath) 303 | } 304 | 305 | Interface.prototype.fileDeleted = function (filepath) { 306 | Tabs.fileDeleted(filepath) 307 | } 308 | 309 | Interface.prototype.hideOverlay = function (msg, cb) { 310 | document.getElementById('overlay').style.display = 'none' 311 | document.getElementById('modal').style.display = 'none' 312 | } 313 | 314 | Interface.prototype.showOverlay = function (msg, cb) { 315 | document.getElementById('overlay').style.display = '' 316 | } 317 | 318 | module.exports = new Interface() 319 | -------------------------------------------------------------------------------- /src/interface/lang/lang.js: -------------------------------------------------------------------------------- 1 | var translations = require('./translations') 2 | 3 | var mustache = require('mustache') 4 | var EventEmitter = require('events').EventEmitter 5 | var inherits = require('inherits') 6 | 7 | inherits(Lang, EventEmitter) 8 | 9 | function Lang () { 10 | var self = this 11 | if (!(self instanceof Lang)) return new Lang() 12 | 13 | var langLocale = navigator.languages 14 | ? navigator.languages[0] 15 | : (navigator.language || navigator.userLanguage) 16 | 17 | self.lang = langLocale.split('-')[0] 18 | self.locale = langLocale.split('-')[1] // TODO: locale support 19 | 20 | // translate the DOM 21 | document.querySelector('#save > span').innerHTML = self.get('save') 22 | document.querySelector('#deploy > span').innerHTML = self.get('deploy') 23 | document.querySelector('#voice > span').innerHTML = self.get('talk') 24 | document.querySelector('#save > span').innerHTML = self.get('save') 25 | } 26 | 27 | Lang.prototype.get = function (key, data) { 28 | var self = this 29 | 30 | data = data || {} 31 | 32 | console.log(key) 33 | 34 | var lookup = translations[self.lang] || translations['en'] 35 | return mustache.render(lookup[key] || translations['en'][key], data) 36 | } 37 | 38 | module.exports = new Lang() 39 | -------------------------------------------------------------------------------- /src/interface/lang/translations.json: -------------------------------------------------------------------------------- 1 | { 2 | "en": { 3 | "save_success_title": "Save Completed", 4 | "save_fail_title": "Save Failed", 5 | "save_success": "Your project has been successfully saved.", 6 | "save_fail": "An error occured while trying to save your project.
Please select a different method.", 7 | "deploy_title": "Website Deployed", 8 | "deploy_fail_title": "Deploy Failed", 9 | "deploy_success": "Anyone can visit your site at
{{{url}}}", 10 | "lost_connection": "Your connection to \"{{nickname}}\" has been lost.", 11 | "history_item": "Multihack Room {{room}}", 12 | "confirm_delete": "Are you sure you want to delete \"{{fileName}}\"?", 13 | "server": "Server", 14 | "you": "You", 15 | "optional": "Optional", 16 | "confirm": "Confirm", 17 | "cancel": "Cancel", 18 | "skip": "Skip", 19 | "join": "Join", 20 | "close": "Close", 21 | "deploy": "Deploy", 22 | "continue": "Continue", 23 | "save": "Save", 24 | "name": "Name", 25 | "upload": "Upload", 26 | "room": "Room", 27 | "file": "File", 28 | "folder": "Folder", 29 | "delete": "Delete", 30 | "talk": "Talk", 31 | "new_file": "New File", 32 | "new_folder": "New Folder", 33 | "nickname_prompt_title": "Choose Nickname", 34 | "nickname_prompt": "Enter a nickname so your team knows who you are.", 35 | "nickname_placeholder": "Nickname", 36 | "default_nickname": "Guest", 37 | "choose_room_title": "Join Room", 38 | "choose_room_prompt": "Enter the ID of the room you want to join.", 39 | "room_placeholder": "Room ID", 40 | "create_title": "Create File/Folder", 41 | "load_title": "Load Project", 42 | "load_prompt": "Upload a ZIP file.", 43 | "offline_title": "Offline Mode", 44 | "offline_alert": "You are now in offline mode.
Save and refresh to join a room.", 45 | "leave_call": "Leave Call", 46 | "join_call": "Join Call", 47 | "join_leave_call": "Join/Leave Call", 48 | "leave_room": "Leave Room" 49 | }, 50 | "ko": { 51 | "save_success_title": "저장 완료", 52 | "save_fail_title": "저장 실패", 53 | "save_success": "프로젝트가 성공적으로 저장되었습니다.", 54 | "save_fail": "프로젝트를 저장하는 중 오류가 발생했습니다.
다른 방법을 선택하세요.", 55 | "deploy_title": "웹사이트가 배포되었습니다.", 56 | "deploy_fail_title": "배포가 실패하였습니다.", 57 | "deploy_success": "누구든지 다음 주소로 당신의 웹사이트를 방문할 수 있습니다.
{{{url}}}", 58 | "lost_connection": "\"{{nickname}}\"님이 접속을 해제하였습니다.", 59 | "history_item": "멀티핵 룸 {{room}}", 60 | "server": "서버", 61 | "you": "당신", 62 | "optional": "선택사항", 63 | "cancel": "취소", 64 | "skip": "Skip", 65 | "join": "접속", 66 | "close": "닫기", 67 | "deploy": "배포", 68 | "continue": "계속", 69 | "save": "저장", 70 | "name": "이름", 71 | "upload": "업로드", 72 | "room": "룸", 73 | "file": "파일", 74 | "folder": "폴더", 75 | "delete": "삭제", 76 | "talk": "음성대화", 77 | "new_file": "새 파일", 78 | "new_folder": "새 폴더", 79 | "nickname_prompt_title": "이름 선택", 80 | "nickname_prompt": "룸의 구성원이 당신을 알아볼 수 있게 이름을 입력하세요.", 81 | "nickname_placeholder": "별명", 82 | "default_nickname": "방문객", 83 | "choose_room_title": "룸 접속", 84 | "choose_room_prompt": "접속하려고 하는 룸의 ID를 입력하세요.", 85 | "room_placeholder": "룸 ID", 86 | "create_title": "파일/폴더 만들기", 87 | "load_title": "프로젝트 로드", 88 | "load_prompt": "ZIP 파일을 업로드할 수 있습니다.", 89 | "offline_title": "오프라인 모드", 90 | "offline_alert": "오프라인 모드로 접속하셨습니다.
저장한 후 새로고침하여 룸으로 접속하세요.", 91 | "leave_call": "통화 종료", 92 | "join_call": "통화 시작", 93 | "join_leave_call": "통화 시작/종료", 94 | "leave_room": "룸 나가기" 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /src/interface/modal.js: -------------------------------------------------------------------------------- 1 | var EventEmitter = require('events').EventEmitter 2 | var inherits = require('inherits') 3 | var mustache = require('mustache') 4 | var templates = require('./templates') 5 | 6 | inherits(Modal, EventEmitter) 7 | 8 | function Modal (name, data) { 9 | var self = this 10 | if (!(self instanceof Modal)) return new Modal() 11 | 12 | self._html = mustache.render(templates[name], data) 13 | self.el = document.getElementById('modal') 14 | self.overlay = document.getElementById('overlay') 15 | } 16 | 17 | Modal.prototype.open = function () { 18 | var self = this 19 | 20 | self.el.style.display = '' 21 | self.overlay.style.display = '' 22 | self.el.innerHTML = self._html 23 | 24 | var inputs = self.el.querySelectorAll('input') 25 | if (inputs[0] && inputs[0].type === 'text') inputs[0].select() 26 | 27 | function done (e) { 28 | e.inputs = inputs 29 | self.emit('done', e) 30 | } 31 | 32 | function keyUp (e) { 33 | e.preventDefault() 34 | if (e.keyCode === 13) { 35 | done(e) 36 | } 37 | } 38 | 39 | function cancel () { 40 | self.emit('cancel') 41 | } 42 | 43 | var ip = Array.prototype.slice.call(self.el.querySelectorAll('.modal-input,.filename-input')) 44 | while (ip[0]) { 45 | if (ip[0].tagName === 'INPUT') { 46 | ip[0].addEventListener('keyup', keyUp) 47 | } 48 | ip.shift() 49 | } 50 | 51 | var go = Array.prototype.slice.call(self.el.querySelectorAll('.go-button')) 52 | while (go[0]) { 53 | if (go[0].tagName === 'BUTTON') { 54 | go[0].addEventListener('click', done) 55 | } else { 56 | go[0].addEventListener('change', done) 57 | } 58 | go.shift() 59 | } 60 | 61 | var no = self.el.querySelector('.no-button') 62 | if (no) { 63 | if (no.tagName === 'BUTTON') { 64 | no.addEventListener('click', cancel) 65 | } else { 66 | no.addEventListener('change', cancel) 67 | } 68 | } 69 | } 70 | 71 | Modal.prototype.close = function () { 72 | var self = this 73 | 74 | self.el.style.display = 'none' 75 | self.overlay.style.display = 'none' 76 | self.el.innerHTML = '' 77 | } 78 | 79 | module.exports = Modal 80 | -------------------------------------------------------------------------------- /src/interface/tab.js: -------------------------------------------------------------------------------- 1 | var mustache = require('mustache') 2 | var EventEmitter = require('events').EventEmitter 3 | var inherits = require('inherits') 4 | var util = require('./../filesystem/util') 5 | 6 | var template = '{{filename}}
×
' 7 | 8 | inherits(Tab, EventEmitter) 9 | 10 | function Tab (filepath) { 11 | var self = this 12 | if (!(self instanceof Tab)) return new Tab() 13 | 14 | self.el = document.createElement('div') 15 | self.el.className = 'tab active' 16 | self.el.innerHTML = mustache.render(template, {filename: util.getFilename(filepath)}) 17 | 18 | self.el.addEventListener('click', self._onclick.bind(self)) 19 | self.el.querySelector('.close').addEventListener('click', self.close.bind(self)) 20 | 21 | self.filepath = filepath 22 | } 23 | 24 | Tab.prototype._onclick = function (e) { 25 | var self = this 26 | if (e) e.stopPropagation() 27 | self.emit('click') 28 | } 29 | 30 | Tab.prototype.setActive = function () { 31 | var self = this 32 | self.el.className = 'active tab' 33 | } 34 | 35 | Tab.prototype.close = function (e) { 36 | var self = this 37 | if (e) e.stopPropagation() 38 | self.emit('close') 39 | } 40 | 41 | Tab.prototype.rename = function (newFilepath) { 42 | var self = this 43 | 44 | self.filepath = newFilepath 45 | 46 | self.el.innerHTML = mustache.render(template, {filename: util.getFilename(newFilepath)}) 47 | self.el.querySelector('.close').addEventListener('click', self._onclose.bind(self)) 48 | } 49 | 50 | module.exports = Tab 51 | -------------------------------------------------------------------------------- /src/interface/tabs.js: -------------------------------------------------------------------------------- 1 | var EventEmitter = require('events').EventEmitter 2 | var inherits = require('inherits') 3 | 4 | var Tab = require('./tab') 5 | var MAX_TABS = 8 6 | 7 | inherits(Tabs, EventEmitter) 8 | 9 | var tabs = [] 10 | var workspace = document.querySelector('.workspace') 11 | 12 | function Tabs() { 13 | var self = this 14 | if (!(self instanceof Tabs)) return new Tabs() 15 | 16 | self.el = document.querySelector('#tabs') 17 | self._activePath = '' 18 | } 19 | 20 | Tabs.prototype.fileOpened = function (filepath) { 21 | var self = this 22 | 23 | var lastTab = self.el.querySelector('.active.tab') 24 | if (lastTab) lastTab.className = 'tab' 25 | 26 | for (var i = 0; i < tabs.length; i++) { 27 | if (tabs[i].filepath === filepath) { 28 | tabs[i].setActive() 29 | return 30 | } 31 | } 32 | 33 | self._newTab(filepath) 34 | } 35 | 36 | Tabs.prototype._newTab = function (filepath) { 37 | var self = this 38 | 39 | var tab = new Tab(filepath) 40 | 41 | tab.on('click', function () { 42 | self._activePath = tab.filepath 43 | self.emit('change', { 44 | path: tab.filepath 45 | }) 46 | }) 47 | 48 | tab.on('close', function () { 49 | self._closeTab(tab) 50 | }) 51 | 52 | tabs.push(tab) 53 | self.el.insertBefore(tab.el, self.el.firstChild) 54 | 55 | if (tabs.length > MAX_TABS) { 56 | tabs[0].close() 57 | } 58 | 59 | workspace.className = workspace.className.replace(new RegExp('tabs-hidden', 'g'), '') 60 | } 61 | 62 | Tabs.prototype._closeTab = function (tab) { 63 | var self = this 64 | 65 | self.el.removeChild(tab.el) 66 | 67 | var index = tabs.indexOf(tab) 68 | tabs.splice(index, 1) 69 | if (tabs.length === 0) { 70 | workspace.className = workspace.className + ' tabs-hidden' 71 | } else if (self._activePath === tab.filepath) { 72 | var nextTab = tabs[index] || tabs[index - 1] 73 | console.log(`Setting ${nextTab.filepath} as active tab`) 74 | nextTab.setActive() 75 | self._activePath = nextTab.filepath 76 | self.emit('close', { 77 | activePath: self._activePath, 78 | }) 79 | } 80 | } 81 | 82 | Tabs.prototype.fileRenamed = function (filepath, newFilepath) { 83 | for (var i = 0; i < tabs.length; i++) { 84 | if (tabs[i].filepath === filepath) { 85 | tabs[i].rename(newFilepath) 86 | return 87 | } 88 | } 89 | } 90 | 91 | Tabs.prototype.fileDeleted = function (filepath) { 92 | for (var i = 0; i < tabs.length; i++) { 93 | if (tabs[i].filepath === filepath) { 94 | tabs[i].close() 95 | return 96 | } 97 | } 98 | } 99 | 100 | module.exports = new Tabs() 101 | -------------------------------------------------------------------------------- /src/interface/templates.js: -------------------------------------------------------------------------------- 1 | var dict = {} 2 | var lang = require('./lang/lang') 3 | var lg = lang.get.bind(lang) 4 | 5 | dict['file'] = 6 | '

{{title}}

' + 7 | '

{{message}}

' + 8 | '' + 9 | '' + 10 | '' 11 | 12 | dict['input'] = 13 | '

{{title}}

' + 14 | '

{{message}}

' + 15 | '
' + 16 | '' 17 | 18 | dict['confirm-delete'] = 19 | '

{{title}}

' + 20 | '

'+ lg('confirm_delete') +'

' + 21 | '' + 22 | '' 23 | 24 | dict['force-input'] = 25 | '

{{title}}

' + 26 | '

{{message}}

' + 27 | '
' + 28 | '' 29 | 30 | dict['alert'] = 31 | '

{{title}}

' + 32 | '

{{message}}

' + 33 | '' 34 | 35 | dict['alert-html'] = 36 | '

{{title}}

' + 37 | '

{{{message}}}

' + 38 | '' 39 | 40 | dict['newFile'] = 41 | '

{{title}}

' + 42 | '
' + 43 | '' + 44 | '' + 45 | '' 46 | 47 | dict['network'] = 48 | '

Room {{room}}

' + 49 | '
' + 50 | '' 51 | 52 | module.exports = dict 53 | -------------------------------------------------------------------------------- /src/interface/treeview.js: -------------------------------------------------------------------------------- 1 | var EventEmitter = require('events').EventEmitter 2 | var inherits = require('inherits') 3 | var icons = require('file-icons-js') 4 | 5 | inherits(TreeView, EventEmitter) 6 | 7 | function TreeView () { 8 | var self = this 9 | if (!(self instanceof TreeView)) return new TreeView() 10 | 11 | document.getElementById('root-plus').addEventListener('click', function (e) { 12 | self.emit('add', { 13 | target: null, 14 | path: '', 15 | parentElement: document.querySelector('#tree') 16 | }) 17 | }) 18 | } 19 | 20 | TreeView.prototype.render = function (nodeList, parentElement) { 21 | var self = this 22 | 23 | parentElement = parentElement || document.querySelector('#tree') 24 | 25 | for (var i = 0; i < nodeList.length; i++) { 26 | if (nodeList[i].path === '') continue 27 | self.add(parentElement, nodeList[i]) 28 | } 29 | } 30 | 31 | TreeView.prototype.rerender = function (nodeList) { 32 | var self = this 33 | 34 | var rootElement = document.querySelector('#tree') 35 | while (rootElement.firstChild) { 36 | rootElement.removeChild(rootElement.firstChild) 37 | } 38 | 39 | self.render(nodeList) 40 | } 41 | 42 | TreeView.prototype._handleFileClick = function (e) { 43 | var self = this 44 | self.emit('open', { 45 | target: e.target, 46 | path: e.target.id, 47 | parentElement: e.parentElement 48 | }) 49 | } 50 | 51 | TreeView.prototype._handleFolderClick = function (e) { 52 | // var self = this 53 | // Nothing 54 | } 55 | 56 | // Returns parentElement of node if it already exists 57 | TreeView.prototype.getParentElement = function (path) { 58 | // var self = this 59 | var el = document.getElementById(path) 60 | return el ? el.parentElement.parentElement : null 61 | } 62 | 63 | TreeView.prototype.remove = function (parentElement, file) { 64 | // var self = this 65 | 66 | var element = document.getElementById(file.path).parentElement 67 | parentElement.removeChild(element) 68 | } 69 | 70 | TreeView.prototype.add = function (parentElement, file) { 71 | var self = this 72 | 73 | if (file.isDir) { 74 | self.addDir(parentElement, file) 75 | } else { 76 | self.addFile(parentElement, file) 77 | } 78 | } 79 | 80 | TreeView.prototype.addFile = function (parentElement, file) { 81 | var self = this 82 | 83 | // Render file 84 | var el = document.createElement('li') 85 | el.className = 'file' 86 | 87 | var a = document.createElement('a') 88 | a.className = 'filelink' 89 | a.id = file.path 90 | 91 | var icon = document.createElement('i') 92 | icon.className = icons.getClassWithColor(file.path) 93 | 94 | var span = document.createElement('span') 95 | span.innerHTML = file.name 96 | 97 | a.appendChild(icon) 98 | a.appendChild(span) 99 | 100 | a.addEventListener('click', function (e) { 101 | self.emit('open', { 102 | target: e.target, 103 | path: file.path, 104 | parentElement: parentElement 105 | }) 106 | }) 107 | 108 | el.appendChild(a) 109 | parentElement.appendChild(el) 110 | } 111 | 112 | TreeView.prototype.addDir = function (parentElement, file) { 113 | var self = this 114 | 115 | var el = document.createElement('li') 116 | 117 | var label = document.createElement('label') 118 | label.setAttribute('for', file.path) 119 | label.innerHTML = file.name 120 | label.addEventListener('click', self._handleFolderClick.bind(self)) 121 | 122 | var input = document.createElement('input') 123 | input.id = file.path 124 | input.checked = true 125 | input.type = 'checkbox' 126 | 127 | var ol = document.createElement('ol') 128 | self.render(file.nodes, ol) 129 | 130 | var plus = document.createElement('span') 131 | plus.className = 'beside plus' 132 | plus.innerHTML = '+' 133 | plus.addEventListener('click', function (e) { 134 | self.emit('add', { 135 | target: null, 136 | path: file.path, 137 | parentElement: ol 138 | }) 139 | }) 140 | 141 | var del = document.createElement('span') 142 | del.className = 'beside delete' 143 | del.innerHTML = '⌫' 144 | del.addEventListener('click', function (e) { 145 | self.emit('remove', { 146 | target: e.target, 147 | path: file.path, 148 | parentElement: parentElement 149 | }) 150 | }) 151 | 152 | el.appendChild(label) 153 | el.appendChild(input) 154 | el.appendChild(plus) 155 | el.appendChild(del) 156 | el.appendChild(ol) 157 | parentElement.appendChild(el) 158 | } 159 | 160 | module.exports = TreeView 161 | -------------------------------------------------------------------------------- /src/network/hyperhostwrapper.js: -------------------------------------------------------------------------------- 1 | /* globals HyperHost */ 2 | 3 | // Wraps the HyperHost instance 4 | // TODO: When HyperHost uses simple-signal, make child of remote.js 5 | 6 | var EventEmitter = require('events').EventEmitter 7 | var inherits = require('inherits') 8 | 9 | inherits(HyperHostWrapper, EventEmitter) 10 | 11 | function HyperHostWrapper () { 12 | var self = this 13 | if (!(self instanceof HyperHostWrapper)) return new HyperHostWrapper() 14 | 15 | self._host = new HyperHost() 16 | } 17 | 18 | HyperHostWrapper.prototype.deploy = function (tree) { 19 | var self = this 20 | console.log('HH started') 21 | 22 | self._host.on('ready', function (url) { 23 | console.log('HH ready') 24 | self.emit('ready', url) 25 | }) 26 | 27 | self._host.io.on('digest', function () { 28 | console.log('HH digested') 29 | self._host.launch() 30 | }) 31 | 32 | console.log(tree) 33 | try { 34 | self._host.io.contentTree(tree) // TODO: Don't try/catch when HH supports "error" event 35 | } catch (err) { 36 | console.error(err) 37 | self.emit('error', err) 38 | } 39 | } 40 | 41 | module.exports = new HyperHostWrapper() 42 | -------------------------------------------------------------------------------- /src/network/voice.js: -------------------------------------------------------------------------------- 1 | var getusermedia = require('getusermedia') 2 | 3 | function VoiceCall (socket, client, room) { 4 | var self = this 5 | if (!(self instanceof VoiceCall)) return new VoiceCall() 6 | 7 | self.room = room 8 | self.stream = null 9 | self.peers = [] 10 | self.socket = socket 11 | self.client = client 12 | 13 | socket.on('voice-discover', function (peerIDs) { 14 | console.log('voice peers', peerIDs) 15 | 16 | if (self.stream) { 17 | for (var i = 0; i < peerIDs.length; i++) { 18 | self.client.connect(peerIDs[0], { 19 | stream: self.stream, 20 | answerConstraints: { 21 | offerToReceiveAudio: true, 22 | offerToReceiveVideo: true 23 | }, 24 | offerConstraints: { 25 | offerToReceiveAudio: true, 26 | offerToReceiveVideo: true 27 | } 28 | }, { 29 | voice: true 30 | }) 31 | } 32 | } 33 | }) 34 | 35 | self.client.on('request', function (request) { 36 | if (!request.metadata.voice) return 37 | if (!self.stream) return 38 | 39 | request.accept({ 40 | stream: self.stream, 41 | answerConstraints: { 42 | offerToReceiveAudio: true, 43 | offerToReceiveVideo: true 44 | }, 45 | offerConstraints: { 46 | offerToReceiveAudio: true, 47 | offerToReceiveVideo: true 48 | } 49 | }, { 50 | voice: true 51 | }) 52 | }) 53 | 54 | self.client.on('peer', function (peer) { 55 | if (!peer.metadata.voice) return 56 | self.peers.push(peer) 57 | 58 | var audio = document.createElement('audio') 59 | peer.on('stream', function (stream) { 60 | console.log('stream') 61 | audio.setAttribute('autoplay', true) 62 | audio.setAttribute('src', window.URL.createObjectURL(stream)) 63 | document.body.appendChild(audio) 64 | }) 65 | 66 | peer.on('close', function () { 67 | document.body.removeChild(audio) 68 | self._removePeer(peer) 69 | }) 70 | }) 71 | } 72 | 73 | VoiceCall.prototype._removePeer = function (peer) { 74 | var self = this 75 | 76 | for (var i = 0; i < self.peers.length; i++) { 77 | if (self.peers[i].id === peer.id) { 78 | self.peers.splice(i, 1) 79 | return 80 | } 81 | } 82 | } 83 | 84 | VoiceCall.prototype.leave = function () { 85 | var self = this 86 | if (!self.stream) return 87 | 88 | console.log('voice leave') 89 | 90 | while (self.peers[0]) { 91 | self.peers[0].destroy() 92 | self.peers.shift() 93 | } 94 | var audioEls = document.querySelectorAll('audio') 95 | for (var i = 0; i < audioEls.length; i++) { 96 | document.body.removeChild(audioEls[i]) 97 | } 98 | self.stream = null 99 | self.socket.emit('voice-leave') 100 | } 101 | 102 | VoiceCall.prototype.join = function () { 103 | var self = this 104 | if (self.stream) return 105 | 106 | console.log('voice join') 107 | 108 | getusermedia(function (err, stream) { 109 | if (err) return console.log(err) 110 | self.stream = stream 111 | self.socket.emit('voice-join') 112 | }) 113 | } 114 | 115 | VoiceCall.prototype.toggle = function () { 116 | var self = this 117 | 118 | console.log('voice toggle') 119 | 120 | console.log(self.stream) 121 | if (!self.stream) { 122 | self.join() 123 | } else { 124 | self.leave() 125 | } 126 | } 127 | 128 | module.exports = VoiceCall 129 | -------------------------------------------------------------------------------- /static/css/Monaco.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/multihack/multihack-web/37febf6a31556c2d3fa53b23ee6ee8510d7389ca/static/css/Monaco.ttf -------------------------------------------------------------------------------- /static/css/codemirror-themes/theme.css: -------------------------------------------------------------------------------- 1 | /* 2 | Name: Atom 3 | Author: Thomas Mullen 4 | 5 | Based off the Railcasts CodeMirror template: 6 | Railcasts original theme by Ryan Bates (http://railscasts.com) 7 | CodeMirror template by Jan T. Sott (https://github.com/idleberg/base16-codemirror) 8 | Original Base16 color scheme by Chris Kempson (https://github.com/chriskempson/base16) 9 | 10 | */ 11 | 12 | .cm-s-atom.CodeMirror {background: #1E1E1E; color: #c5c8c6;} 13 | .cm-s-atom div.CodeMirror-selected {background: #444444 !important;} 14 | .cm-s-atom .CodeMirror-gutters {background: #1E1E1E; border-right: none} 15 | .cm-s-atom .CodeMirror-linenumber {color: #c5c8c6;} 16 | .cm-s-atom .CodeMirror-cursor {border-left: 1px solid #d4cfc9 !important;} 17 | 18 | .cm-s-atom span.cm-comment {color: #7C7C7C;} 19 | .cm-s-atom span.cm-atom {color: #b6b3eb;} 20 | .cm-s-atom span.cm-number {color: #b6b3eb;} 21 | 22 | .cm-s-atom span.cm-property, .cm-s-atom span.cm-attribute {color: #C6C5FE;} 23 | .cm-s-atom span.cm-keyword {color: #96CBFE;} 24 | .cm-s-atom span.cm-string {color: #A8FF60;} 25 | 26 | .cm-s-atom span.cm-variable {color: #C6C5FE;} 27 | .cm-s-atom span.cm-def {color: #c5c8c6;} 28 | .cm-s-atom span.cm-error {background: #da4939; color: #d4cfc9;} 29 | .cm-s-atom span.cm-bracket {color: #f4f1ed;} 30 | .cm-s-atom span.cm-tag {color: #96CBFE;} 31 | .cm-s-atom span.cm-link {color: #b6b3eb;} 32 | 33 | .cm-s-atom .CodeMirror-matchingbracket { text-decoration: underline; color: white !important;} 34 | .cm-s-atom .CodeMirror-activeline-background { background: #303040; } 35 | -------------------------------------------------------------------------------- /static/css/codemirror.css: -------------------------------------------------------------------------------- 1 | /* BASICS */ 2 | 3 | .CodeMirror { 4 | /* Set height, width, borders, and global font properties here */ 5 | font-family: monospace; 6 | height: 300px; 7 | color: black; 8 | } 9 | 10 | /* PADDING */ 11 | 12 | .CodeMirror-lines { 13 | padding: 4px 0; /* Vertical padding around content */ 14 | } 15 | .CodeMirror pre { 16 | padding: 0 4px; /* Horizontal padding of content */ 17 | } 18 | 19 | .CodeMirror-scrollbar-filler, .CodeMirror-gutter-filler { 20 | background-color: white; /* The little square between H and V scrollbars */ 21 | } 22 | 23 | /* GUTTER */ 24 | 25 | .CodeMirror-gutters { 26 | border-right: 1px solid #ddd; 27 | background-color: #f7f7f7; 28 | white-space: nowrap; 29 | } 30 | .CodeMirror-linenumbers {} 31 | .CodeMirror-linenumber { 32 | padding: 0 3px 0 5px; 33 | min-width: 20px; 34 | text-align: right; 35 | color: #999; 36 | white-space: nowrap; 37 | } 38 | 39 | .CodeMirror-guttermarker { color: black; } 40 | .CodeMirror-guttermarker-subtle { color: #999; } 41 | 42 | /* CURSOR */ 43 | 44 | .CodeMirror-cursor { 45 | border-left: 1px solid black; 46 | border-right: none; 47 | width: 0; 48 | } 49 | /* Shown when moving in bi-directional text */ 50 | .CodeMirror div.CodeMirror-secondarycursor { 51 | border-left: 1px solid silver; 52 | } 53 | .cm-fat-cursor .CodeMirror-cursor { 54 | width: auto; 55 | border: 0 !important; 56 | background: #7e7; 57 | } 58 | .cm-fat-cursor div.CodeMirror-cursors { 59 | z-index: 1; 60 | } 61 | 62 | .cm-animate-fat-cursor { 63 | width: auto; 64 | border: 0; 65 | -webkit-animation: blink 1.06s steps(1) infinite; 66 | -moz-animation: blink 1.06s steps(1) infinite; 67 | animation: blink 1.06s steps(1) infinite; 68 | background-color: #7e7; 69 | } 70 | @-moz-keyframes blink { 71 | 0% {} 72 | 50% { background-color: transparent; } 73 | 100% {} 74 | } 75 | @-webkit-keyframes blink { 76 | 0% {} 77 | 50% { background-color: transparent; } 78 | 100% {} 79 | } 80 | @keyframes blink { 81 | 0% {} 82 | 50% { background-color: transparent; } 83 | 100% {} 84 | } 85 | 86 | /* Can style cursor different in overwrite (non-insert) mode */ 87 | .CodeMirror-overwrite .CodeMirror-cursor {} 88 | 89 | .cm-tab { display: inline-block; text-decoration: inherit; } 90 | 91 | .CodeMirror-rulers { 92 | position: absolute; 93 | left: 0; right: 0; top: -50px; bottom: -20px; 94 | overflow: hidden; 95 | } 96 | .CodeMirror-ruler { 97 | border-left: 1px solid #ccc; 98 | top: 0; bottom: 0; 99 | position: absolute; 100 | } 101 | 102 | /* DEFAULT THEME */ 103 | 104 | .cm-s-default .cm-header {color: blue;} 105 | .cm-s-default .cm-quote {color: #090;} 106 | .cm-negative {color: #d44;} 107 | .cm-positive {color: #292;} 108 | .cm-header, .cm-strong {font-weight: bold;} 109 | .cm-em {font-style: italic;} 110 | .cm-link {text-decoration: underline;} 111 | .cm-strikethrough {text-decoration: line-through;} 112 | 113 | .cm-s-default .cm-keyword {color: #708;} 114 | .cm-s-default .cm-atom {color: #219;} 115 | .cm-s-default .cm-number {color: #164;} 116 | .cm-s-default .cm-def {color: #00f;} 117 | .cm-s-default .cm-variable, 118 | .cm-s-default .cm-punctuation, 119 | .cm-s-default .cm-property, 120 | .cm-s-default .cm-operator {} 121 | .cm-s-default .cm-variable-2 {color: #05a;} 122 | .cm-s-default .cm-variable-3 {color: #085;} 123 | .cm-s-default .cm-comment {color: #a50;} 124 | .cm-s-default .cm-string {color: #a11;} 125 | .cm-s-default .cm-string-2 {color: #f50;} 126 | .cm-s-default .cm-meta {color: #555;} 127 | .cm-s-default .cm-qualifier {color: #555;} 128 | .cm-s-default .cm-builtin {color: #30a;} 129 | .cm-s-default .cm-bracket {color: #997;} 130 | .cm-s-default .cm-tag {color: #170;} 131 | .cm-s-default .cm-attribute {color: #00c;} 132 | .cm-s-default .cm-hr {color: #999;} 133 | .cm-s-default .cm-link {color: #00c;} 134 | 135 | .cm-s-default .cm-error {color: #f00;} 136 | .cm-invalidchar {color: #f00;} 137 | 138 | .CodeMirror-composing { border-bottom: 2px solid; } 139 | 140 | /* Default styles for common addons */ 141 | 142 | div.CodeMirror span.CodeMirror-matchingbracket {color: #0f0;} 143 | div.CodeMirror span.CodeMirror-nonmatchingbracket {color: #f22;} 144 | .CodeMirror-matchingtag { background: rgba(123, 123, 123, 0.3); } 145 | .CodeMirror-activeline-background {background: #e8f2ff;} 146 | 147 | /* STOP */ 148 | 149 | /* The rest of this file contains styles related to the mechanics of 150 | the editor. You probably shouldn't touch them. */ 151 | 152 | .CodeMirror { 153 | position: relative; 154 | overflow: hidden; 155 | background: white; 156 | } 157 | 158 | .CodeMirror-scroll { 159 | overflow: scroll !important; /* Things will break if this is overridden */ 160 | /* 30px is the magic margin used to hide the element's real scrollbars */ 161 | /* See overflow: hidden in .CodeMirror */ 162 | margin-bottom: -30px; margin-right: -30px; 163 | padding-bottom: 30px; 164 | height: 100%; 165 | outline: none; /* Prevent dragging from highlighting the element */ 166 | position: relative; 167 | } 168 | .CodeMirror-sizer { 169 | position: relative; 170 | border-right: 30px solid transparent; 171 | } 172 | 173 | /* The fake, visible scrollbars. Used to force redraw during scrolling 174 | before actual scrolling happens, thus preventing shaking and 175 | flickering artifacts. */ 176 | .CodeMirror-vscrollbar, .CodeMirror-hscrollbar, .CodeMirror-scrollbar-filler, .CodeMirror-gutter-filler { 177 | position: absolute; 178 | z-index: 6; 179 | display: none; 180 | } 181 | .CodeMirror-vscrollbar { 182 | right: 0; top: 0; 183 | overflow-x: hidden; 184 | overflow-y: scroll; 185 | } 186 | .CodeMirror-hscrollbar { 187 | bottom: 0; left: 0; 188 | overflow-y: hidden; 189 | overflow-x: scroll; 190 | } 191 | .CodeMirror-scrollbar-filler { 192 | right: 0; bottom: 0; 193 | } 194 | .CodeMirror-gutter-filler { 195 | left: 0; bottom: 0; 196 | } 197 | 198 | .CodeMirror-gutters { 199 | position: absolute; left: 0; top: 0; 200 | min-height: 100%; 201 | z-index: 3; 202 | } 203 | .CodeMirror-gutter { 204 | white-space: normal; 205 | height: 100%; 206 | display: inline-block; 207 | vertical-align: top; 208 | margin-bottom: -30px; 209 | /* Hack to make IE7 behave */ 210 | *zoom:1; 211 | *display:inline; 212 | } 213 | .CodeMirror-gutter-wrapper { 214 | position: absolute; 215 | z-index: 4; 216 | background: none !important; 217 | border: none !important; 218 | } 219 | .CodeMirror-gutter-background { 220 | position: absolute; 221 | top: 0; bottom: 0; 222 | z-index: 4; 223 | } 224 | .CodeMirror-gutter-elt { 225 | position: absolute; 226 | cursor: default; 227 | z-index: 4; 228 | } 229 | .CodeMirror-gutter-wrapper { 230 | -webkit-user-select: none; 231 | -moz-user-select: none; 232 | user-select: none; 233 | } 234 | 235 | .CodeMirror-lines { 236 | cursor: text; 237 | min-height: 1px; /* prevents collapsing before first draw */ 238 | } 239 | .CodeMirror pre { 240 | /* Reset some styles that the rest of the page might have set */ 241 | -moz-border-radius: 0; -webkit-border-radius: 0; border-radius: 0; 242 | border-width: 0; 243 | background: transparent; 244 | font-family: inherit; 245 | font-size: inherit; 246 | margin: 0; 247 | white-space: pre; 248 | word-wrap: normal; 249 | line-height: inherit; 250 | color: inherit; 251 | z-index: 2; 252 | position: relative; 253 | overflow: visible; 254 | -webkit-tap-highlight-color: transparent; 255 | -webkit-font-variant-ligatures: none; 256 | font-variant-ligatures: none; 257 | } 258 | .CodeMirror-wrap pre { 259 | word-wrap: break-word; 260 | white-space: pre-wrap; 261 | word-break: normal; 262 | } 263 | 264 | .CodeMirror-linebackground { 265 | position: absolute; 266 | left: 0; right: 0; top: 0; bottom: 0; 267 | z-index: 0; 268 | } 269 | 270 | .CodeMirror-linewidget { 271 | position: relative; 272 | z-index: 2; 273 | overflow: auto; 274 | } 275 | 276 | .CodeMirror-widget {} 277 | 278 | .CodeMirror-code { 279 | outline: none; 280 | } 281 | 282 | /* Force content-box sizing for the elements where we expect it */ 283 | .CodeMirror-scroll, 284 | .CodeMirror-sizer, 285 | .CodeMirror-gutter, 286 | .CodeMirror-gutters, 287 | .CodeMirror-linenumber { 288 | -moz-box-sizing: content-box; 289 | box-sizing: content-box; 290 | } 291 | 292 | .CodeMirror-measure { 293 | position: absolute; 294 | width: 100%; 295 | height: 0; 296 | overflow: hidden; 297 | visibility: hidden; 298 | } 299 | 300 | .CodeMirror-cursor { 301 | position: absolute; 302 | pointer-events: none; 303 | } 304 | .CodeMirror-measure pre { position: static; } 305 | 306 | div.CodeMirror-cursors { 307 | visibility: hidden; 308 | position: relative; 309 | z-index: 3; 310 | } 311 | div.CodeMirror-dragcursors { 312 | visibility: visible; 313 | } 314 | 315 | .CodeMirror-focused div.CodeMirror-cursors { 316 | visibility: visible; 317 | } 318 | 319 | .CodeMirror-selected { background: #d9d9d9; } 320 | .CodeMirror-focused .CodeMirror-selected { background: #d7d4f0; } 321 | .CodeMirror-crosshair { cursor: crosshair; } 322 | .CodeMirror-line::selection, .CodeMirror-line > span::selection, .CodeMirror-line > span > span::selection { background: #d7d4f0; } 323 | .CodeMirror-line::-moz-selection, .CodeMirror-line > span::-moz-selection, .CodeMirror-line > span > span::-moz-selection { background: #d7d4f0; } 324 | 325 | .cm-searching { 326 | background: #ffa; 327 | background: rgba(255, 255, 0, .4); 328 | } 329 | 330 | /* IE7 hack to prevent it from returning funny offsetTops on the spans */ 331 | .CodeMirror span { *vertical-align: text-bottom; } 332 | 333 | /* Used to force a border model for a node */ 334 | .cm-force-border { padding-right: .1px; } 335 | 336 | @media print { 337 | /* Hide the cursor when printing */ 338 | .CodeMirror div.CodeMirror-cursors { 339 | visibility: hidden; 340 | } 341 | } 342 | 343 | /* See issue #2901 */ 344 | .cm-tab-wrap-hack:after { content: ''; } 345 | 346 | /* Help users use markselection to safely style text background */ 347 | span.CodeMirror-selectedtext { background: none; } 348 | -------------------------------------------------------------------------------- /static/css/filetree/toggle-small-expand.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/multihack/multihack-web/37febf6a31556c2d3fa53b23ee6ee8510d7389ca/static/css/filetree/toggle-small-expand.png -------------------------------------------------------------------------------- /static/css/filetree/toggle-small.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/multihack/multihack-web/37febf6a31556c2d3fa53b23ee6ee8510d7389ca/static/css/filetree/toggle-small.png -------------------------------------------------------------------------------- /static/css/filetree/treestyles.css: -------------------------------------------------------------------------------- 1 | /* CSS Tree menu styles */ 2 | 3 | ol.tree { 4 | padding: 0 0 0 30px; 5 | width: 300px; 6 | } 7 | 8 | .beside { 9 | margin-left: 5px; 10 | cursor: pointer !important; 11 | } 12 | 13 | .beside.plus { 14 | font-weight: 700; 15 | } 16 | 17 | .beside.delete { 18 | font-size: 11px; 19 | } 20 | 21 | .tree label { 22 | display: inline-block; 23 | } 24 | 25 | .file i { 26 | font-style: normal; 27 | } 28 | 29 | li { 30 | position: relative; 31 | margin-left: -15px; 32 | list-style: none; 33 | cursor: pointer; 34 | } 35 | 36 | li.file { 37 | margin-left: -6px !important; 38 | } 39 | 40 | li.file a { 41 | color: #9DA5AB; 42 | text-decoration: none; 43 | display: block; 44 | cursor: pointer; 45 | } 46 | 47 | li.file a span { 48 | margin-left: 5px; 49 | } 50 | 51 | li.file a:hover { 52 | color: gray; 53 | } 54 | 55 | li input { 56 | position: absolute; 57 | left: 0; 58 | margin-left: 0; 59 | opacity: 0; 60 | z-index: 2; 61 | cursor: pointer; 62 | height: 1em; 63 | width: 1em; 64 | top: 0; 65 | cursor: pointer !important; 66 | } 67 | 68 | li input:after{ 69 | content:""; 70 | padding: 20px; 71 | position: absolute; 72 | left: -10px; 73 | top: -10px; 74 | } 75 | 76 | li input ~ ol { 77 | background: url(toggle-small-expand.png) 40px 0 no-repeat; 78 | margin: -1em 0 0 -44px; 79 | height: 1em; 80 | cursor: pointer; 81 | } 82 | 83 | li input ~ ol > li { 84 | display: none; 85 | margin-left: -14px !important; 86 | padding-left: 1px; 87 | cursor: pointer; 88 | } 89 | 90 | li label { 91 | cursor: pointer !important; 92 | display: block; 93 | padding-left: 8px; 94 | } 95 | 96 | li label:hover{ 97 | color: gray; 98 | } 99 | 100 | li input:checked ~ ol { 101 | background: url(toggle-small.png) 40px 5px no-repeat; 102 | margin: -1.25em 0 0 -44px; 103 | /* 20px */ 104 | padding: 1.563em 0 0 80px; 105 | height: auto; 106 | } 107 | 108 | li input:checked ~ ol > li { 109 | display: block; 110 | margin: 0 0 0.125em; 111 | /* 2px */ 112 | } 113 | 114 | li input:checked ~ ol > li:last-child { 115 | margin: 0 0 0.063em; 116 | /* 1px */ 117 | } -------------------------------------------------------------------------------- /static/css/fonts/devopicons.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/multihack/multihack-web/37febf6a31556c2d3fa53b23ee6ee8510d7389ca/static/css/fonts/devopicons.woff2 -------------------------------------------------------------------------------- /static/css/fonts/file-icons.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/multihack/multihack-web/37febf6a31556c2d3fa53b23ee6ee8510d7389ca/static/css/fonts/file-icons.woff2 -------------------------------------------------------------------------------- /static/css/fonts/fontawesome.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/multihack/multihack-web/37febf6a31556c2d3fa53b23ee6ee8510d7389ca/static/css/fonts/fontawesome.woff2 -------------------------------------------------------------------------------- /static/css/fonts/mfixx.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/multihack/multihack-web/37febf6a31556c2d3fa53b23ee6ee8510d7389ca/static/css/fonts/mfixx.woff2 -------------------------------------------------------------------------------- /static/css/fonts/octicons.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/multihack/multihack-web/37febf6a31556c2d3fa53b23ee6ee8510d7389ca/static/css/fonts/octicons.woff2 -------------------------------------------------------------------------------- /static/css/show-hint.css: -------------------------------------------------------------------------------- 1 | .CodeMirror-hints { 2 | position: absolute; 3 | z-index: 10; 4 | overflow: hidden; 5 | list-style: none; 6 | 7 | margin: 0; 8 | padding: 2px; 9 | 10 | background: #292C2F; 11 | font-size: 90%; 12 | font-family: monospace; 13 | 14 | max-height: 20em; 15 | overflow-y: auto; 16 | } 17 | 18 | .CodeMirror-hint { 19 | margin: 0; 20 | padding: 0 4px; 21 | white-space: pre; 22 | color: #a3a3a3; 23 | cursor: pointer; 24 | } 25 | 26 | li.CodeMirror-hint-active { 27 | background: #08f; 28 | color: white; 29 | } 30 | -------------------------------------------------------------------------------- /static/css/sourcecodelight.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/multihack/multihack-web/37febf6a31556c2d3fa53b23ee6ee8510d7389ca/static/css/sourcecodelight.ttf -------------------------------------------------------------------------------- /static/css/sourcecoderegular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/multihack/multihack-web/37febf6a31556c2d3fa53b23ee6ee8510d7389ca/static/css/sourcecoderegular.ttf -------------------------------------------------------------------------------- /static/css/style.css: -------------------------------------------------------------------------------- 1 | @font-face { 2 | font-family: SourceCodeRegular; 3 | src: url('sourcecoderegular.ttf') format('truetype'); 4 | /* others */ 5 | } 6 | 7 | @font-face { 8 | font-family: SourceCodeLight; 9 | src: url('sourcecodelight.ttf') format('truetype'); 10 | /* others */ 11 | } 12 | 13 | @font-face { 14 | font-family: Monaco; 15 | src: url('Monaco.ttf') format('truetype'); 16 | /* others */ 17 | } 18 | 19 | html, 20 | body { 21 | padding: 0; 22 | margin: 0; 23 | height: 100%; 24 | width: 100%; 25 | overflow: hidden; 26 | font-size: 14px; 27 | background: #0F0F0F; 28 | color: #9DA5AB; 29 | font-size: 12px; 30 | } 31 | 32 | * { 33 | font-family: Monaco; 34 | } 35 | 36 | .nav { 37 | height: 60px; 38 | position: fixed; 39 | left: 0; 40 | top: 0; 41 | right: 0; 42 | padding-right: 10px; 43 | z-index: 1; 44 | background: #252526; 45 | background: rgb(62,62,64); 46 | background: -moz-radial-gradient(center, ellipse cover, rgb(62,62,64) 0%, rgb(21,21,22) 100%); 47 | background: -webkit-radial-gradient(center, ellipse cover, rgb(62,62,64) 0%,rgb(21,21,22) 100%); 48 | background: radial-gradient(ellipse at center, rgb(62,62,64) 0%,rgb(21,21,22) 100%); 49 | filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#3e3e40', endColorstr='#151516',GradientType=1 ); 50 | background-size: 100% 150%; 51 | background-position: 100% 100%; 52 | } 53 | 54 | .embed .nav { 55 | display: none; 56 | } 57 | 58 | .modal { 59 | background: black; 60 | border: 1px solid rgb(62,62,64); 61 | } 62 | 63 | .nav .logo { 64 | width: 110px; 65 | margin-top: 10px; 66 | margin-left: 20px; 67 | } 68 | 69 | .nav .icon-button { 70 | float: right; 71 | margin-top: 15px; 72 | position: relative; 73 | background: black; 74 | border-radius: 0; 75 | border: none; 76 | } 77 | 78 | .no-button, .go-button { 79 | background: black; 80 | border: none; 81 | border-radius: 0; 82 | cursor: pointer !important; 83 | outline: 1px solid #439bda; 84 | min-width: 80px; 85 | } 86 | 87 | .no-button:hover, .go-button:hover { 88 | outline: 1px solid white; 89 | } 90 | 91 | .nav .icon-button span { 92 | float: right; 93 | margin-top: 1px; 94 | } 95 | 96 | .nav .icon-button img ~ span { 97 | margin-left: 6px; 98 | } 99 | 100 | .nav .icon-button .tooltip { 101 | position: absolute; 102 | left: 0; 103 | top: 0; 104 | right: 0; 105 | bottom: 0; 106 | width: 100%; 107 | height: 100%; 108 | text-align: center; 109 | transition: opacity .3s ease-in-out; 110 | } 111 | 112 | .nav a { 113 | position: absolute; 114 | top: 0; 115 | left: 0px; 116 | bottom: 10px; 117 | width: 150px; 118 | } 119 | 120 | .nav .icon-button .tooltip > span { 121 | position: absolute; 122 | top: calc(100% + 5px); 123 | right: 0; 124 | background: #222222; 125 | padding: 5px; 126 | white-space: nowrap; 127 | } 128 | 129 | .nav .icon-button .tooltip > span:after { 130 | width: 0; 131 | height: 0; 132 | border-left: 5px solid transparent; 133 | border-right: 5px solid transparent; 134 | border-bottom: 5px solid black; 135 | position: absolute; 136 | right: 30px; 137 | top: -5px; 138 | content: ""; 139 | } 140 | 141 | .noselect, 142 | .userlist *, 143 | .tree *, 144 | .topbar, 145 | .topbar *, 146 | .logo, 147 | .panel-topbar{ 148 | -webkit-touch-callout: none; 149 | -webkit-user-select: none; 150 | -khtml-user-select: none; 151 | -moz-user-select: none; 152 | -ms-user-select: none; 153 | user-select: none; 154 | cursor: default; 155 | } 156 | 157 | .nodrag, 158 | img , 159 | .statusbar{ 160 | -moz-user-select: none; 161 | -webkit-user-select: none; 162 | -ms-user-select: none; 163 | user-select: none; 164 | -webkit-user-drag: none; 165 | user-drag: none; 166 | -webkit-touch-callout: none; 167 | pointer-events: none; 168 | } 169 | 170 | .sidebar { 171 | width: 150px; 172 | position: fixed; 173 | left: 0; 174 | top: 60px; 175 | bottom: 0px; 176 | z-index: 1; 177 | overflow: auto; 178 | background: #252526; 179 | border-top: 1px solid black; 180 | } 181 | 182 | .embed .sidebar { 183 | top: 0px; 184 | } 185 | 186 | .sidebar.collapsed { 187 | width: 24px; 188 | overflow-x: hidden; 189 | } 190 | 191 | .sidebar.collapsed ~ .workspace { 192 | left: 24px; 193 | } 194 | 195 | .panel-topbar { 196 | height: 20px; 197 | position: fixed; 198 | top: 62px; 199 | width: 142px; 200 | left: 0; 201 | text-align: center; 202 | border-bottom: 1px solid black; 203 | } 204 | 205 | .embed .panel-topbar { 206 | top: 2px; 207 | } 208 | 209 | #root-plus { 210 | position: absolute; 211 | right: 5px; 212 | top: 2px; 213 | cursor: pointer !important; 214 | z-index: 10; 215 | pointer-events: all; 216 | width: 15px; 217 | } 218 | #root-plus:active { 219 | transform: scale(0.95) 220 | } 221 | 222 | #delete { 223 | position: absolute; 224 | right: 30px; 225 | cursor: pointer !important; 226 | top: 2px; 227 | z-index: 10; 228 | pointer-events: all; 229 | width: 15px; 230 | } 231 | #delete:active { 232 | transform: scale(0.95) 233 | } 234 | 235 | .sidebar.collapsed > .panel-topbar { 236 | display: none; 237 | width: 24px; 238 | } 239 | 240 | #room { 241 | max-width: 70px; 242 | overflow: hidden; 243 | display: inline-block; 244 | cursor: pointer; 245 | margin-top: 2px; 246 | } 247 | 248 | #room:hover { 249 | color: gray; 250 | } 251 | 252 | .sidebar.collapsed > .tree { 253 | display: none; 254 | } 255 | 256 | .panel { 257 | position: absolute; 258 | top: 20px; 259 | bottom: 0; 260 | left: 0; 261 | right: 0; 262 | overflow: auto; 263 | margin: 15px 0 5px 0; 264 | } 265 | 266 | .workspace { 267 | position: fixed; 268 | background: black; 269 | top: 60px; 270 | left: 150px; 271 | right: 0; 272 | bottom: 0px; 273 | } 274 | 275 | .embed .workspace { 276 | top: 0px; 277 | } 278 | 279 | .minus { 280 | margin-left: 10px; 281 | } 282 | 283 | .icon-button { 284 | padding: 5px 9px; 285 | transition: none; 286 | min-width: 30px; 287 | } 288 | 289 | .icon-button img { 290 | width: 13px; 291 | height: 13px; 292 | margin-top: 1px; 293 | } 294 | 295 | .workspace .image-contrast { 296 | position: absolute; 297 | left: 10px; 298 | top: 10px; 299 | z-index: 10; 300 | background: none !important; 301 | } 302 | 303 | .workspace .image-wrapper { 304 | position: absolute; 305 | left: 0; 306 | top: 25px; 307 | right: 0; 308 | bottom: -16px; 309 | background: black; 310 | z-index: 7; 311 | text-align: center; 312 | overflow: auto; 313 | } 314 | 315 | .workspace.tabs-hidden .image-wrapper { 316 | top: 0; 317 | } 318 | 319 | .workspace .image-wrapper > img{ 320 | margin-top: 40px; 321 | width: 70%; 322 | margin-bottom: 60px; 323 | } 324 | 325 | .workspace .tabs { 326 | position: absolute; 327 | top: 0; 328 | left: 0; 329 | right: 0; 330 | height: 25px; 331 | overflow: hidden; 332 | background: #2D2D2D; 333 | } 334 | 335 | .workspace.tabs-hidden .tabs { 336 | height: 0; 337 | } 338 | 339 | .workspace .tabs .tab { 340 | display: inline-block; 341 | height: 25px; 342 | background: #2D2D2D; 343 | width: 110px; 344 | position: relative; 345 | overflow: hidden; 346 | border-right: 1px solid black; 347 | color: #b2b2b2; 348 | -webkit-user-select: none; 349 | -khtml-user-select: none; 350 | -moz-user-select: none; 351 | -ms-user-select: none; 352 | user-select: none; 353 | } 354 | 355 | .workspace .tabs .tab span { 356 | width: 87px; 357 | padding: 4px; 358 | position: absolute; 359 | top: 0; 360 | left: 0; 361 | overflow: hidden; 362 | white-space: nowrap; 363 | cursor: default; 364 | } 365 | 366 | .workspace .tabs .tab .close { 367 | position: absolute; 368 | right: 4px; 369 | top: 3px; 370 | cursor: pointer; 371 | } 372 | 373 | .workspace .tabs .tab.active { 374 | background: #1E1E1E; 375 | } 376 | 377 | .workspace .editor-wrapper { 378 | position: absolute; 379 | top: 25px; 380 | left: 0; 381 | right: 0; 382 | bottom: 20px; 383 | } 384 | 385 | .workspace.tabs-hidden .editor-wrapper { 386 | top: 0; 387 | } 388 | 389 | .workspace .editor { 390 | background: black; 391 | height: 100%; 392 | width: 100%; 393 | border: none; 394 | } 395 | 396 | .workspace .statusbar { 397 | position: absolute; 398 | bottom: 0; 399 | left: 0; 400 | right: 0; 401 | height: 18px; 402 | z-index: 999999; 403 | background: #2D2D2D; 404 | font-size: 12px; 405 | padding: 2px 5px 0 5px; 406 | color: white; 407 | } 408 | 409 | .sidebar #collapsesidebar{ 410 | position: absolute; 411 | left: -4px; 412 | top: -1px; 413 | cursor: pointer; 414 | } 415 | .sidebar #collapsesidebar:active { 416 | transform: scale(0.95) 417 | } 418 | 419 | .sidebar #resync{ 420 | position: absolute; 421 | right: 17px; 422 | top: 2px; 423 | cursor: pointer; 424 | padding: 5px; 425 | width: 19px; 426 | min-width: 19px; 427 | } 428 | .sidebar #resync:active { 429 | transform: scale(0.95) 430 | } 431 | 432 | .modal { 433 | margin: auto; 434 | width: 500px; 435 | max-width: 80%; 436 | z-index: 99999; 437 | position: fixed; 438 | top: 50%; 439 | left: 50%; 440 | transform: translate(-50%, -50%); 441 | padding: 20px; 442 | max-height: 100%; 443 | overflow-y: auto; 444 | } 445 | 446 | .modal input[type="text"] { 447 | margin-bottom: 10px; 448 | } 449 | 450 | #network-graph { 451 | max-height: 300px; 452 | overflow: hidden; 453 | } 454 | 455 | #network-graph .text { 456 | fill: white !important; 457 | transform: translateX(-15px) 458 | } 459 | 460 | .blocking-overlay { 461 | background: black; 462 | background: rgba(0, 0, 0, 0.5); 463 | position: fixed; 464 | top: 0; 465 | left: 0; 466 | bottom: 0; 467 | right: 0; 468 | z-index: 1; 469 | text-align: center; 470 | padding-top: 100px; 471 | font-size: 30px; 472 | } 473 | 474 | .blocking-overlay > span { 475 | position: relative; 476 | animation: swirl 2s infinite alternate; 477 | animation-timing-function: ease-out; 478 | } 479 | 480 | 481 | @keyframes swirl { 482 | 0% { 483 | top: 10px; 484 | } 485 | 486 | 50% { 487 | top: -10px; 488 | } 489 | 490 | 100% { 491 | top: 10px; 492 | } 493 | } 494 | 495 | button { 496 | height: 33px; 497 | padding: 10px 15px 10px 15px; 498 | margin: 5px; 499 | display: inline-block; 500 | border: 0; 501 | cursor: pointer; 502 | outline: none; 503 | color: white; 504 | font-size: 13px; 505 | line-height: 13px; 506 | min-width: 23px; 507 | } 508 | 509 | 510 | .text-center { 511 | text-align: center; 512 | } 513 | 514 | a { 515 | text-decoration: none; 516 | } 517 | 518 | .red { 519 | color: firebrick !important; 520 | } 521 | 522 | .pull-right { 523 | float: right; 524 | } 525 | 526 | input[type="text"] { 527 | color: white; 528 | background: black; 529 | border: 3px transparent solid; 530 | outline: none; 531 | font-size: 16px; 532 | padding: 5px; 533 | border: 1px solid rgb(62,62,64); 534 | transition: all .3s; 535 | max-width: 100% !important; 536 | } 537 | 538 | input[type="text"].long { 539 | width: 90%; 540 | } 541 | 542 | .CodeMirror { 543 | height: 100%; 544 | } 545 | 546 | .cursor { 547 | position: fixed; 548 | height: 12px; 549 | width: 12px; 550 | z-index: 9999999; 551 | transition: top .3s ease-out, left .3s ease-out; 552 | border-radius: 100%; 553 | } 554 | 555 | .remoteSelection { 556 | background-color: #3f5d38; 557 | } 558 | 559 | .remoteCaret { 560 | background-color: #7fb971; 561 | width: 1px; 562 | z-index: 2; 563 | } 564 | 565 | /* Custom scrollbars */ 566 | 567 | ::-webkit-scrollbar { 568 | width: 10px; 569 | } 570 | 571 | ::-webkit-scrollbar-track { 572 | border: solid 1px gray; 573 | border-radius: 0; 574 | } 575 | 576 | ::-webkit-scrollbar-thumb { 577 | border-radius: 0; 578 | } 579 | 580 | ::-webkit-scrollbar-thumb:hover { 581 | border-radius: 0; 582 | } 583 | 584 | /* File upload */ 585 | input[type='file'] { 586 | width: 63px; 587 | padding: 3px 6px 6px 6px; 588 | height: 24px; 589 | position: relative; 590 | top: 32px; 591 | outline: none; 592 | -webkit-touch-callout: none; 593 | -webkit-user-select: none; 594 | -khtml-user-select: none; 595 | -moz-user-select: none; 596 | -ms-user-select: none; 597 | user-select: none; 598 | cursor: pointer; 599 | margin-right: 5px; 600 | } 601 | 602 | input[type='file']::-webkit-file-upload-button { 603 | visibility: hidden; 604 | } 605 | 606 | input[type='file']::before { 607 | content: 'Upload'; 608 | display: inline-block; 609 | outline: none; 610 | white-space: nowrap; 611 | cursor: pointer !important; 612 | padding: 10px 15px; 613 | font-size: 13px; 614 | color: white; 615 | position: relative; 616 | bottom: 4px; 617 | left: -7px; 618 | -webkit-touch-callout: none; 619 | -webkit-user-select: none; 620 | -khtml-user-select: none; 621 | -moz-user-select: none; 622 | -ms-user-select: none; 623 | user-select: none; 624 | } 625 | 626 | .icon-collapse > span{ 627 | display: none; 628 | } 629 | 630 | span.mobile-hide{ 631 | display: none; 632 | } 633 | 634 | 635 | /* Custom, iPhone Retina */ 636 | @media only screen and (max-width: 480px) { 637 | .modal { 638 | position: fixed; 639 | top: 0px; 640 | left: 0px; 641 | right: 0px; 642 | bottom: 0px; 643 | margin: 0; 644 | padding: 0; 645 | padding-top: 40px; 646 | border: 0; 647 | transform: none; 648 | max-width: 100%; 649 | width: 100%; 650 | } 651 | } 652 | 653 | 654 | /* Small Devices, Tablets */ 655 | 656 | @media only screen and (min-width: 768px) { 657 | .workspace { 658 | left: 340px; 659 | } 660 | .sidebar { 661 | width: 340px; 662 | } 663 | .sidebar .panel-topbar { 664 | width: 340px; 665 | } 666 | .icon-button { 667 | min-width: 70px; 668 | } 669 | .icon-collapse > span{ 670 | display: inline-block; 671 | } 672 | span.mobile-hide{ 673 | display: inline-block; 674 | } 675 | #room { 676 | max-width: 260px; 677 | } 678 | } 679 | 680 | /* Medium Devices, Desktops */ 681 | 682 | @media only screen and (min-width: 992px) {} 683 | 684 | 685 | /* Large Devices, Wide Screens */ 686 | 687 | @media only screen and (min-width: 1200px) {} -------------------------------------------------------------------------------- /static/img/camera.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/multihack/multihack-web/37febf6a31556c2d3fa53b23ee6ee8510d7389ca/static/img/camera.png -------------------------------------------------------------------------------- /static/img/collapse.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/multihack/multihack-web/37febf6a31556c2d3fa53b23ee6ee8510d7389ca/static/img/collapse.png -------------------------------------------------------------------------------- /static/img/contrast-black.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/multihack/multihack-web/37febf6a31556c2d3fa53b23ee6ee8510d7389ca/static/img/contrast-black.png -------------------------------------------------------------------------------- /static/img/contrast-white.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/multihack/multihack-web/37febf6a31556c2d3fa53b23ee6ee8510d7389ca/static/img/contrast-white.png -------------------------------------------------------------------------------- /static/img/diamond.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/multihack/multihack-web/37febf6a31556c2d3fa53b23ee6ee8510d7389ca/static/img/diamond.png -------------------------------------------------------------------------------- /static/img/folder.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/multihack/multihack-web/37febf6a31556c2d3fa53b23ee6ee8510d7389ca/static/img/folder.png -------------------------------------------------------------------------------- /static/img/gear.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/multihack/multihack-web/37febf6a31556c2d3fa53b23ee6ee8510d7389ca/static/img/gear.png -------------------------------------------------------------------------------- /static/img/icon144.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/multihack/multihack-web/37febf6a31556c2d3fa53b23ee6ee8510d7389ca/static/img/icon144.png -------------------------------------------------------------------------------- /static/img/icon168.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/multihack/multihack-web/37febf6a31556c2d3fa53b23ee6ee8510d7389ca/static/img/icon168.png -------------------------------------------------------------------------------- /static/img/icon192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/multihack/multihack-web/37febf6a31556c2d3fa53b23ee6ee8510d7389ca/static/img/icon192.png -------------------------------------------------------------------------------- /static/img/icon48.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/multihack/multihack-web/37febf6a31556c2d3fa53b23ee6ee8510d7389ca/static/img/icon48.png -------------------------------------------------------------------------------- /static/img/icon72.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/multihack/multihack-web/37febf6a31556c2d3fa53b23ee6ee8510d7389ca/static/img/icon72.png -------------------------------------------------------------------------------- /static/img/icon96.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/multihack/multihack-web/37febf6a31556c2d3fa53b23ee6ee8510d7389ca/static/img/icon96.png -------------------------------------------------------------------------------- /static/img/launch.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/multihack/multihack-web/37febf6a31556c2d3fa53b23ee6ee8510d7389ca/static/img/launch.png -------------------------------------------------------------------------------- /static/img/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/multihack/multihack-web/37febf6a31556c2d3fa53b23ee6ee8510d7389ca/static/img/logo.png -------------------------------------------------------------------------------- /static/img/mic.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/multihack/multihack-web/37febf6a31556c2d3fa53b23ee6ee8510d7389ca/static/img/mic.png -------------------------------------------------------------------------------- /static/img/mockup.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/multihack/multihack-web/37febf6a31556c2d3fa53b23ee6ee8510d7389ca/static/img/mockup.jpg -------------------------------------------------------------------------------- /static/img/muted.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/multihack/multihack-web/37febf6a31556c2d3fa53b23ee6ee8510d7389ca/static/img/muted.png -------------------------------------------------------------------------------- /static/img/network.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/multihack/multihack-web/37febf6a31556c2d3fa53b23ee6ee8510d7389ca/static/img/network.png -------------------------------------------------------------------------------- /static/img/newfile.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/multihack/multihack-web/37febf6a31556c2d3fa53b23ee6ee8510d7389ca/static/img/newfile.png -------------------------------------------------------------------------------- /static/img/nocamera.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/multihack/multihack-web/37febf6a31556c2d3fa53b23ee6ee8510d7389ca/static/img/nocamera.png -------------------------------------------------------------------------------- /static/img/pointer.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/multihack/multihack-web/37febf6a31556c2d3fa53b23ee6ee8510d7389ca/static/img/pointer.png -------------------------------------------------------------------------------- /static/img/reload.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/multihack/multihack-web/37febf6a31556c2d3fa53b23ee6ee8510d7389ca/static/img/reload.png -------------------------------------------------------------------------------- /static/img/save.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/multihack/multihack-web/37febf6a31556c2d3fa53b23ee6ee8510d7389ca/static/img/save.png -------------------------------------------------------------------------------- /static/img/splash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/multihack/multihack-web/37febf6a31556c2d3fa53b23ee6ee8510d7389ca/static/img/splash.png -------------------------------------------------------------------------------- /static/img/trash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/multihack/multihack-web/37febf6a31556c2d3fa53b23ee6ee8510d7389ca/static/img/trash.png -------------------------------------------------------------------------------- /static/img/upload.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/multihack/multihack-web/37febf6a31556c2d3fa53b23ee6ee8510d7389ca/static/img/upload.png -------------------------------------------------------------------------------- /static/js/vendor/FileSaver.min.js: -------------------------------------------------------------------------------- 1 | /*! @source http://purl.eligrey.com/github/FileSaver.js/blob/master/FileSaver.js */ 2 | var saveAs=saveAs||function(e){"use strict";if(typeof e==="undefined"||typeof navigator!=="undefined"&&/MSIE [1-9]\./.test(navigator.userAgent)){return}var t=e.document,n=function(){return e.URL||e.webkitURL||e},r=t.createElementNS("http://www.w3.org/1999/xhtml","a"),o="download"in r,a=function(e){var t=new MouseEvent("click");e.dispatchEvent(t)},i=/constructor/i.test(e.HTMLElement)||e.safari,f=/CriOS\/[\d]+/.test(navigator.userAgent),u=function(t){(e.setImmediate||e.setTimeout)(function(){throw t},0)},s="application/octet-stream",d=1e3*40,c=function(e){var t=function(){if(typeof e==="string"){n().revokeObjectURL(e)}else{e.remove()}};setTimeout(t,d)},l=function(e,t,n){t=[].concat(t);var r=t.length;while(r--){var o=e["on"+t[r]];if(typeof o==="function"){try{o.call(e,n||e)}catch(a){u(a)}}}},p=function(e){if(/^\s*(?:text\/\S*|application\/xml|\S*\/\S*\+xml)\s*;.*charset\s*=\s*utf-8/i.test(e.type)){return new Blob([String.fromCharCode(65279),e],{type:e.type})}return e},v=function(t,u,d){if(!d){t=p(t)}var v=this,w=t.type,m=w===s,y,h=function(){l(v,"writestart progress write writeend".split(" "))},S=function(){if((f||m&&i)&&e.FileReader){var r=new FileReader;r.onloadend=function(){var t=f?r.result:r.result.replace(/^data:[^;]*;/,"data:attachment/file;");var n=e.open(t,"_blank");if(!n)e.location.href=t;t=undefined;v.readyState=v.DONE;h()};r.readAsDataURL(t);v.readyState=v.INIT;return}if(!y){y=n().createObjectURL(t)}if(m){e.location.href=y}else{var o=e.open(y,"_blank");if(!o){e.location.href=y}}v.readyState=v.DONE;h();c(y)};v.readyState=v.INIT;if(o){y=n().createObjectURL(t);setTimeout(function(){r.href=y;r.download=u;a(r);h();c(y);v.readyState=v.DONE});return}S()},w=v.prototype,m=function(e,t,n){return new v(e,t||e.name||"download",n)};if(typeof navigator!=="undefined"&&navigator.msSaveOrOpenBlob){return function(e,t,n){t=t||e.name||"download";if(!n){e=p(e)}return navigator.msSaveOrOpenBlob(e,t)}}w.abort=function(){};w.readyState=w.INIT=0;w.WRITING=1;w.DONE=2;w.error=w.onwritestart=w.onprogress=w.onwrite=w.onabort=w.onerror=w.onwriteend=null;return m}(typeof self!=="undefined"&&self||typeof window!=="undefined"&&window||this.content);if(typeof module!=="undefined"&&module.exports){module.exports.saveAs=saveAs}else if(typeof define!=="undefined"&&define!==null&&define.amd!==null){define("FileSaver.js",function(){return saveAs})} 3 | -------------------------------------------------------------------------------- /static/js/vendor/addons/closebrackets.js: -------------------------------------------------------------------------------- 1 | // CodeMirror, copyright (c) by Marijn Haverbeke and others 2 | // Distributed under an MIT license: http://codemirror.net/LICENSE 3 | 4 | (function(mod) { 5 | if (typeof exports == "object" && typeof module == "object") // CommonJS 6 | mod(require("../../lib/codemirror")); 7 | else if (typeof define == "function" && define.amd) // AMD 8 | define(["../../lib/codemirror"], mod); 9 | else // Plain browser env 10 | mod(CodeMirror); 11 | })(function(CodeMirror) { 12 | var defaults = { 13 | pairs: "()[]{}''\"\"", 14 | triples: "", 15 | explode: "[]{}" 16 | }; 17 | 18 | var Pos = CodeMirror.Pos; 19 | 20 | CodeMirror.defineOption("autoCloseBrackets", false, function(cm, val, old) { 21 | if (old && old != CodeMirror.Init) { 22 | cm.removeKeyMap(keyMap); 23 | cm.state.closeBrackets = null; 24 | } 25 | if (val) { 26 | cm.state.closeBrackets = val; 27 | cm.addKeyMap(keyMap); 28 | } 29 | }); 30 | 31 | function getOption(conf, name) { 32 | if (name == "pairs" && typeof conf == "string") return conf; 33 | if (typeof conf == "object" && conf[name] != null) return conf[name]; 34 | return defaults[name]; 35 | } 36 | 37 | var bind = defaults.pairs + "`"; 38 | var keyMap = {Backspace: handleBackspace, Enter: handleEnter}; 39 | for (var i = 0; i < bind.length; i++) 40 | keyMap["'" + bind.charAt(i) + "'"] = handler(bind.charAt(i)); 41 | 42 | function handler(ch) { 43 | return function(cm) { return handleChar(cm, ch); }; 44 | } 45 | 46 | function getConfig(cm) { 47 | var deflt = cm.state.closeBrackets; 48 | if (!deflt || deflt.override) return deflt; 49 | var mode = cm.getModeAt(cm.getCursor()); 50 | return mode.closeBrackets || deflt; 51 | } 52 | 53 | function handleBackspace(cm) { 54 | var conf = getConfig(cm); 55 | if (!conf || cm.getOption("disableInput")) return CodeMirror.Pass; 56 | 57 | var pairs = getOption(conf, "pairs"); 58 | var ranges = cm.listSelections(); 59 | for (var i = 0; i < ranges.length; i++) { 60 | if (!ranges[i].empty()) return CodeMirror.Pass; 61 | var around = charsAround(cm, ranges[i].head); 62 | if (!around || pairs.indexOf(around) % 2 != 0) return CodeMirror.Pass; 63 | } 64 | for (var i = ranges.length - 1; i >= 0; i--) { 65 | var cur = ranges[i].head; 66 | cm.replaceRange("", Pos(cur.line, cur.ch - 1), Pos(cur.line, cur.ch + 1), "+delete"); 67 | } 68 | } 69 | 70 | function handleEnter(cm) { 71 | var conf = getConfig(cm); 72 | var explode = conf && getOption(conf, "explode"); 73 | if (!explode || cm.getOption("disableInput")) return CodeMirror.Pass; 74 | 75 | var ranges = cm.listSelections(); 76 | for (var i = 0; i < ranges.length; i++) { 77 | if (!ranges[i].empty()) return CodeMirror.Pass; 78 | var around = charsAround(cm, ranges[i].head); 79 | if (!around || explode.indexOf(around) % 2 != 0) return CodeMirror.Pass; 80 | } 81 | cm.operation(function() { 82 | cm.replaceSelection("\n\n", null); 83 | cm.execCommand("goCharLeft"); 84 | ranges = cm.listSelections(); 85 | for (var i = 0; i < ranges.length; i++) { 86 | var line = ranges[i].head.line; 87 | cm.indentLine(line, null, true); 88 | cm.indentLine(line + 1, null, true); 89 | } 90 | }); 91 | } 92 | 93 | function contractSelection(sel) { 94 | var inverted = CodeMirror.cmpPos(sel.anchor, sel.head) > 0; 95 | return {anchor: new Pos(sel.anchor.line, sel.anchor.ch + (inverted ? -1 : 1)), 96 | head: new Pos(sel.head.line, sel.head.ch + (inverted ? 1 : -1))}; 97 | } 98 | 99 | function handleChar(cm, ch) { 100 | var conf = getConfig(cm); 101 | if (!conf || cm.getOption("disableInput")) return CodeMirror.Pass; 102 | 103 | var pairs = getOption(conf, "pairs"); 104 | var pos = pairs.indexOf(ch); 105 | if (pos == -1) return CodeMirror.Pass; 106 | var triples = getOption(conf, "triples"); 107 | 108 | var identical = pairs.charAt(pos + 1) == ch; 109 | var ranges = cm.listSelections(); 110 | var opening = pos % 2 == 0; 111 | 112 | var type; 113 | for (var i = 0; i < ranges.length; i++) { 114 | var range = ranges[i], cur = range.head, curType; 115 | var next = cm.getRange(cur, Pos(cur.line, cur.ch + 1)); 116 | if (opening && !range.empty()) { 117 | curType = "surround"; 118 | } else if ((identical || !opening) && next == ch) { 119 | if (identical && stringStartsAfter(cm, cur)) 120 | curType = "both"; 121 | else if (triples.indexOf(ch) >= 0 && cm.getRange(cur, Pos(cur.line, cur.ch + 3)) == ch + ch + ch) 122 | curType = "skipThree"; 123 | else 124 | curType = "skip"; 125 | } else if (identical && cur.ch > 1 && triples.indexOf(ch) >= 0 && 126 | cm.getRange(Pos(cur.line, cur.ch - 2), cur) == ch + ch && 127 | (cur.ch <= 2 || cm.getRange(Pos(cur.line, cur.ch - 3), Pos(cur.line, cur.ch - 2)) != ch)) { 128 | curType = "addFour"; 129 | } else if (identical) { 130 | if (!CodeMirror.isWordChar(next) && enteringString(cm, cur, ch)) curType = "both"; 131 | else return CodeMirror.Pass; 132 | } else if (opening && (cm.getLine(cur.line).length == cur.ch || 133 | isClosingBracket(next, pairs) || 134 | /\s/.test(next))) { 135 | curType = "both"; 136 | } else { 137 | return CodeMirror.Pass; 138 | } 139 | if (!type) type = curType; 140 | else if (type != curType) return CodeMirror.Pass; 141 | } 142 | 143 | var left = pos % 2 ? pairs.charAt(pos - 1) : ch; 144 | var right = pos % 2 ? ch : pairs.charAt(pos + 1); 145 | cm.operation(function() { 146 | if (type == "skip") { 147 | cm.execCommand("goCharRight"); 148 | } else if (type == "skipThree") { 149 | for (var i = 0; i < 3; i++) 150 | cm.execCommand("goCharRight"); 151 | } else if (type == "surround") { 152 | var sels = cm.getSelections(); 153 | for (var i = 0; i < sels.length; i++) 154 | sels[i] = left + sels[i] + right; 155 | cm.replaceSelections(sels, "around"); 156 | sels = cm.listSelections().slice(); 157 | for (var i = 0; i < sels.length; i++) 158 | sels[i] = contractSelection(sels[i]); 159 | cm.setSelections(sels); 160 | } else if (type == "both") { 161 | cm.replaceSelection(left + right, null); 162 | cm.triggerElectric(left + right); 163 | cm.execCommand("goCharLeft"); 164 | } else if (type == "addFour") { 165 | cm.replaceSelection(left + left + left + left, "before"); 166 | cm.execCommand("goCharRight"); 167 | } 168 | }); 169 | } 170 | 171 | function isClosingBracket(ch, pairs) { 172 | var pos = pairs.lastIndexOf(ch); 173 | return pos > -1 && pos % 2 == 1; 174 | } 175 | 176 | function charsAround(cm, pos) { 177 | var str = cm.getRange(Pos(pos.line, pos.ch - 1), 178 | Pos(pos.line, pos.ch + 1)); 179 | return str.length == 2 ? str : null; 180 | } 181 | 182 | // Project the token type that will exists after the given char is 183 | // typed, and use it to determine whether it would cause the start 184 | // of a string token. 185 | function enteringString(cm, pos, ch) { 186 | var line = cm.getLine(pos.line); 187 | var token = cm.getTokenAt(pos); 188 | if (/\bstring2?\b/.test(token.type) || stringStartsAfter(cm, pos)) return false; 189 | var stream = new CodeMirror.StringStream(line.slice(0, pos.ch) + ch + line.slice(pos.ch), 4); 190 | stream.pos = stream.start = token.start; 191 | for (;;) { 192 | var type1 = cm.getMode().token(stream, token.state); 193 | if (stream.pos >= pos.ch + 1) return /\bstring2?\b/.test(type1); 194 | stream.start = stream.pos; 195 | } 196 | } 197 | 198 | function stringStartsAfter(cm, pos) { 199 | var token = cm.getTokenAt(Pos(pos.line, pos.ch + 1)) 200 | return /\bstring/.test(token.type) && token.start == pos.ch 201 | } 202 | }); 203 | -------------------------------------------------------------------------------- /static/js/vendor/addons/closetag.js: -------------------------------------------------------------------------------- 1 | // CodeMirror, copyright (c) by Marijn Haverbeke and others 2 | // Distributed under an MIT license: http://codemirror.net/LICENSE 3 | 4 | /** 5 | * Tag-closer extension for CodeMirror. 6 | * 7 | * This extension adds an "autoCloseTags" option that can be set to 8 | * either true to get the default behavior, or an object to further 9 | * configure its behavior. 10 | * 11 | * These are supported options: 12 | * 13 | * `whenClosing` (default true) 14 | * Whether to autoclose when the '/' of a closing tag is typed. 15 | * `whenOpening` (default true) 16 | * Whether to autoclose the tag when the final '>' of an opening 17 | * tag is typed. 18 | * `dontCloseTags` (default is empty tags for HTML, none for XML) 19 | * An array of tag names that should not be autoclosed. 20 | * `indentTags` (default is block tags for HTML, none for XML) 21 | * An array of tag names that should, when opened, cause a 22 | * blank line to be added inside the tag, and the blank line and 23 | * closing line to be indented. 24 | * 25 | * See demos/closetag.html for a usage example. 26 | */ 27 | 28 | (function(mod) { 29 | if (typeof exports == "object" && typeof module == "object") // CommonJS 30 | mod(require("../../lib/codemirror"), require("../fold/xml-fold")); 31 | else if (typeof define == "function" && define.amd) // AMD 32 | define(["../../lib/codemirror", "../fold/xml-fold"], mod); 33 | else // Plain browser env 34 | mod(CodeMirror); 35 | })(function(CodeMirror) { 36 | CodeMirror.defineOption("autoCloseTags", false, function(cm, val, old) { 37 | if (old != CodeMirror.Init && old) 38 | cm.removeKeyMap("autoCloseTags"); 39 | if (!val) return; 40 | var map = {name: "autoCloseTags"}; 41 | if (typeof val != "object" || val.whenClosing) 42 | map["'/'"] = function(cm) { return autoCloseSlash(cm); }; 43 | if (typeof val != "object" || val.whenOpening) 44 | map["'>'"] = function(cm) { return autoCloseGT(cm); }; 45 | cm.addKeyMap(map); 46 | }); 47 | 48 | var htmlDontClose = ["area", "base", "br", "col", "command", "embed", "hr", "img", "input", "keygen", "link", "meta", "param", 49 | "source", "track", "wbr"]; 50 | var htmlIndent = ["applet", "blockquote", "body", "button", "div", "dl", "fieldset", "form", "frameset", "h1", "h2", "h3", "h4", 51 | "h5", "h6", "head", "html", "iframe", "layer", "legend", "object", "ol", "p", "select", "table", "ul"]; 52 | 53 | function autoCloseGT(cm) { 54 | if (cm.getOption("disableInput")) return CodeMirror.Pass; 55 | var ranges = cm.listSelections(), replacements = []; 56 | for (var i = 0; i < ranges.length; i++) { 57 | if (!ranges[i].empty()) return CodeMirror.Pass; 58 | var pos = ranges[i].head, tok = cm.getTokenAt(pos); 59 | var inner = CodeMirror.innerMode(cm.getMode(), tok.state), state = inner.state; 60 | if (inner.mode.name != "xml" || !state.tagName) return CodeMirror.Pass; 61 | 62 | var opt = cm.getOption("autoCloseTags"), html = inner.mode.configuration == "html"; 63 | var dontCloseTags = (typeof opt == "object" && opt.dontCloseTags) || (html && htmlDontClose); 64 | var indentTags = (typeof opt == "object" && opt.indentTags) || (html && htmlIndent); 65 | 66 | var tagName = state.tagName; 67 | if (tok.end > pos.ch) tagName = tagName.slice(0, tagName.length - tok.end + pos.ch); 68 | var lowerTagName = tagName.toLowerCase(); 69 | // Don't process the '>' at the end of an end-tag or self-closing tag 70 | if (!tagName || 71 | tok.type == "string" && (tok.end != pos.ch || !/[\"\']/.test(tok.string.charAt(tok.string.length - 1)) || tok.string.length == 1) || 72 | tok.type == "tag" && state.type == "closeTag" || 73 | tok.string.indexOf("/") == (tok.string.length - 1) || // match something like 74 | dontCloseTags && indexOf(dontCloseTags, lowerTagName) > -1 || 75 | closingTagExists(cm, tagName, pos, state, true)) 76 | return CodeMirror.Pass; 77 | 78 | var indent = indentTags && indexOf(indentTags, lowerTagName) > -1; 79 | replacements[i] = {indent: indent, 80 | text: ">" + (indent ? "\n\n" : "") + "", 81 | newPos: indent ? CodeMirror.Pos(pos.line + 1, 0) : CodeMirror.Pos(pos.line, pos.ch + 1)}; 82 | } 83 | 84 | for (var i = ranges.length - 1; i >= 0; i--) { 85 | var info = replacements[i]; 86 | cm.replaceRange(info.text, ranges[i].head, ranges[i].anchor, "+insert"); 87 | var sel = cm.listSelections().slice(0); 88 | sel[i] = {head: info.newPos, anchor: info.newPos}; 89 | cm.setSelections(sel); 90 | if (info.indent) { 91 | cm.indentLine(info.newPos.line, null, true); 92 | cm.indentLine(info.newPos.line + 1, null, true); 93 | } 94 | } 95 | } 96 | 97 | function autoCloseCurrent(cm, typingSlash) { 98 | var ranges = cm.listSelections(), replacements = []; 99 | var head = typingSlash ? "/" : "") replacement += ">"; 126 | replacements[i] = replacement; 127 | } 128 | cm.replaceSelections(replacements); 129 | ranges = cm.listSelections(); 130 | for (var i = 0; i < ranges.length; i++) 131 | if (i == ranges.length - 1 || ranges[i].head.line < ranges[i + 1].head.line) 132 | cm.indentLine(ranges[i].head.line); 133 | } 134 | 135 | function autoCloseSlash(cm) { 136 | if (cm.getOption("disableInput")) return CodeMirror.Pass; 137 | return autoCloseCurrent(cm, true); 138 | } 139 | 140 | CodeMirror.commands.closeTag = function(cm) { return autoCloseCurrent(cm); }; 141 | 142 | function indexOf(collection, elt) { 143 | if (collection.indexOf) return collection.indexOf(elt); 144 | for (var i = 0, e = collection.length; i < e; ++i) 145 | if (collection[i] == elt) return i; 146 | return -1; 147 | } 148 | 149 | // If xml-fold is loaded, we use its functionality to try and verify 150 | // whether a given tag is actually unclosed. 151 | function closingTagExists(cm, tagName, pos, state, newTag) { 152 | if (!CodeMirror.scanForClosingTag) return false; 153 | var end = Math.min(cm.lastLine() + 1, pos.line + 500); 154 | var nextClose = CodeMirror.scanForClosingTag(cm, pos, null, end); 155 | if (!nextClose || nextClose.tag != tagName) return false; 156 | var cx = state.context; 157 | // If the immediate wrapping context contains onCx instances of 158 | // the same tag, a closing tag only exists if there are at least 159 | // that many closing tags of that type following. 160 | for (var onCx = newTag ? 1 : 0; cx && cx.tagName == tagName; cx = cx.prev) ++onCx; 161 | pos = nextClose.to; 162 | for (var i = 1; i < onCx; i++) { 163 | var next = CodeMirror.scanForClosingTag(cm, pos, null, end); 164 | if (!next || next.tag != tagName) return false; 165 | pos = next.to; 166 | } 167 | return true; 168 | } 169 | }); 170 | -------------------------------------------------------------------------------- /static/js/vendor/addons/css-hint.js: -------------------------------------------------------------------------------- 1 | // CodeMirror, copyright (c) by Marijn Haverbeke and others 2 | // Distributed under an MIT license: http://codemirror.net/LICENSE 3 | 4 | (function(mod) { 5 | if (typeof exports == "object" && typeof module == "object") // CommonJS 6 | mod(require("../../lib/codemirror"), require("../../mode/css/css")); 7 | else if (typeof define == "function" && define.amd) // AMD 8 | define(["../../lib/codemirror", "../../mode/css/css"], mod); 9 | else // Plain browser env 10 | mod(CodeMirror); 11 | })(function(CodeMirror) { 12 | "use strict"; 13 | 14 | var pseudoClasses = {link: 1, visited: 1, active: 1, hover: 1, focus: 1, 15 | "first-letter": 1, "first-line": 1, "first-child": 1, 16 | before: 1, after: 1, lang: 1}; 17 | 18 | CodeMirror.registerHelper("hint", "css", function(cm) { 19 | var cur = cm.getCursor(), token = cm.getTokenAt(cur); 20 | var inner = CodeMirror.innerMode(cm.getMode(), token.state); 21 | if (inner.mode.name != "css") return; 22 | 23 | if (token.type == "keyword" && "!important".indexOf(token.string) == 0) 24 | return {list: ["!important"], from: CodeMirror.Pos(cur.line, token.start), 25 | to: CodeMirror.Pos(cur.line, token.end)}; 26 | 27 | var start = token.start, end = cur.ch, word = token.string.slice(0, end - start); 28 | if (/[^\w$_-]/.test(word)) { 29 | word = ""; start = end = cur.ch; 30 | } 31 | 32 | var spec = CodeMirror.resolveMode("text/css"); 33 | 34 | var result = []; 35 | function add(keywords) { 36 | for (var name in keywords) 37 | if (!word || name.lastIndexOf(word, 0) == 0) 38 | result.push(name); 39 | } 40 | 41 | var st = inner.state.state; 42 | if (st == "pseudo" || token.type == "variable-3") { 43 | add(pseudoClasses); 44 | } else if (st == "block" || st == "maybeprop") { 45 | add(spec.propertyKeywords); 46 | } else if (st == "prop" || st == "parens" || st == "at" || st == "params") { 47 | add(spec.valueKeywords); 48 | add(spec.colorKeywords); 49 | } else if (st == "media" || st == "media_parens") { 50 | add(spec.mediaTypes); 51 | add(spec.mediaFeatures); 52 | } 53 | 54 | if (result.length) return { 55 | list: result, 56 | from: CodeMirror.Pos(cur.line, start), 57 | to: CodeMirror.Pos(cur.line, end) 58 | }; 59 | }); 60 | }); 61 | -------------------------------------------------------------------------------- /static/js/vendor/addons/html-hint.js: -------------------------------------------------------------------------------- 1 | // CodeMirror, copyright (c) by Marijn Haverbeke and others 2 | // Distributed under an MIT license: http://codemirror.net/LICENSE 3 | 4 | (function(mod) { 5 | if (typeof exports == "object" && typeof module == "object") // CommonJS 6 | mod(require("../../lib/codemirror"), require("./xml-hint")); 7 | else if (typeof define == "function" && define.amd) // AMD 8 | define(["../../lib/codemirror", "./xml-hint"], mod); 9 | else // Plain browser env 10 | mod(CodeMirror); 11 | })(function(CodeMirror) { 12 | "use strict"; 13 | 14 | var langs = "ab aa af ak sq am ar an hy as av ae ay az bm ba eu be bn bh bi bs br bg my ca ch ce ny zh cv kw co cr hr cs da dv nl dz en eo et ee fo fj fi fr ff gl ka de el gn gu ht ha he hz hi ho hu ia id ie ga ig ik io is it iu ja jv kl kn kr ks kk km ki rw ky kv kg ko ku kj la lb lg li ln lo lt lu lv gv mk mg ms ml mt mi mr mh mn na nv nb nd ne ng nn no ii nr oc oj cu om or os pa pi fa pl ps pt qu rm rn ro ru sa sc sd se sm sg sr gd sn si sk sl so st es su sw ss sv ta te tg th ti bo tk tl tn to tr ts tt tw ty ug uk ur uz ve vi vo wa cy wo fy xh yi yo za zu".split(" "); 15 | var targets = ["_blank", "_self", "_top", "_parent"]; 16 | var charsets = ["ascii", "utf-8", "utf-16", "latin1", "latin1"]; 17 | var methods = ["get", "post", "put", "delete"]; 18 | var encs = ["application/x-www-form-urlencoded", "multipart/form-data", "text/plain"]; 19 | var media = ["all", "screen", "print", "embossed", "braille", "handheld", "print", "projection", "screen", "tty", "tv", "speech", 20 | "3d-glasses", "resolution [>][<][=] [X]", "device-aspect-ratio: X/Y", "orientation:portrait", 21 | "orientation:landscape", "device-height: [X]", "device-width: [X]"]; 22 | var s = { attrs: {} }; // Simple tag, reused for a whole lot of tags 23 | 24 | var data = { 25 | a: { 26 | attrs: { 27 | href: null, ping: null, type: null, 28 | media: media, 29 | target: targets, 30 | hreflang: langs 31 | } 32 | }, 33 | abbr: s, 34 | acronym: s, 35 | address: s, 36 | applet: s, 37 | area: { 38 | attrs: { 39 | alt: null, coords: null, href: null, target: null, ping: null, 40 | media: media, hreflang: langs, type: null, 41 | shape: ["default", "rect", "circle", "poly"] 42 | } 43 | }, 44 | article: s, 45 | aside: s, 46 | audio: { 47 | attrs: { 48 | src: null, mediagroup: null, 49 | crossorigin: ["anonymous", "use-credentials"], 50 | preload: ["none", "metadata", "auto"], 51 | autoplay: ["", "autoplay"], 52 | loop: ["", "loop"], 53 | controls: ["", "controls"] 54 | } 55 | }, 56 | b: s, 57 | base: { attrs: { href: null, target: targets } }, 58 | basefont: s, 59 | bdi: s, 60 | bdo: s, 61 | big: s, 62 | blockquote: { attrs: { cite: null } }, 63 | body: s, 64 | br: s, 65 | button: { 66 | attrs: { 67 | form: null, formaction: null, name: null, value: null, 68 | autofocus: ["", "autofocus"], 69 | disabled: ["", "autofocus"], 70 | formenctype: encs, 71 | formmethod: methods, 72 | formnovalidate: ["", "novalidate"], 73 | formtarget: targets, 74 | type: ["submit", "reset", "button"] 75 | } 76 | }, 77 | canvas: { attrs: { width: null, height: null } }, 78 | caption: s, 79 | center: s, 80 | cite: s, 81 | code: s, 82 | col: { attrs: { span: null } }, 83 | colgroup: { attrs: { span: null } }, 84 | command: { 85 | attrs: { 86 | type: ["command", "checkbox", "radio"], 87 | label: null, icon: null, radiogroup: null, command: null, title: null, 88 | disabled: ["", "disabled"], 89 | checked: ["", "checked"] 90 | } 91 | }, 92 | data: { attrs: { value: null } }, 93 | datagrid: { attrs: { disabled: ["", "disabled"], multiple: ["", "multiple"] } }, 94 | datalist: { attrs: { data: null } }, 95 | dd: s, 96 | del: { attrs: { cite: null, datetime: null } }, 97 | details: { attrs: { open: ["", "open"] } }, 98 | dfn: s, 99 | dir: s, 100 | div: s, 101 | dl: s, 102 | dt: s, 103 | em: s, 104 | embed: { attrs: { src: null, type: null, width: null, height: null } }, 105 | eventsource: { attrs: { src: null } }, 106 | fieldset: { attrs: { disabled: ["", "disabled"], form: null, name: null } }, 107 | figcaption: s, 108 | figure: s, 109 | font: s, 110 | footer: s, 111 | form: { 112 | attrs: { 113 | action: null, name: null, 114 | "accept-charset": charsets, 115 | autocomplete: ["on", "off"], 116 | enctype: encs, 117 | method: methods, 118 | novalidate: ["", "novalidate"], 119 | target: targets 120 | } 121 | }, 122 | frame: s, 123 | frameset: s, 124 | h1: s, h2: s, h3: s, h4: s, h5: s, h6: s, 125 | head: { 126 | attrs: {}, 127 | children: ["title", "base", "link", "style", "meta", "script", "noscript", "command"] 128 | }, 129 | header: s, 130 | hgroup: s, 131 | hr: s, 132 | html: { 133 | attrs: { manifest: null }, 134 | children: ["head", "body"] 135 | }, 136 | i: s, 137 | iframe: { 138 | attrs: { 139 | src: null, srcdoc: null, name: null, width: null, height: null, 140 | sandbox: ["allow-top-navigation", "allow-same-origin", "allow-forms", "allow-scripts"], 141 | seamless: ["", "seamless"] 142 | } 143 | }, 144 | img: { 145 | attrs: { 146 | alt: null, src: null, ismap: null, usemap: null, width: null, height: null, 147 | crossorigin: ["anonymous", "use-credentials"] 148 | } 149 | }, 150 | input: { 151 | attrs: { 152 | alt: null, dirname: null, form: null, formaction: null, 153 | height: null, list: null, max: null, maxlength: null, min: null, 154 | name: null, pattern: null, placeholder: null, size: null, src: null, 155 | step: null, value: null, width: null, 156 | accept: ["audio/*", "video/*", "image/*"], 157 | autocomplete: ["on", "off"], 158 | autofocus: ["", "autofocus"], 159 | checked: ["", "checked"], 160 | disabled: ["", "disabled"], 161 | formenctype: encs, 162 | formmethod: methods, 163 | formnovalidate: ["", "novalidate"], 164 | formtarget: targets, 165 | multiple: ["", "multiple"], 166 | readonly: ["", "readonly"], 167 | required: ["", "required"], 168 | type: ["hidden", "text", "search", "tel", "url", "email", "password", "datetime", "date", "month", 169 | "week", "time", "datetime-local", "number", "range", "color", "checkbox", "radio", 170 | "file", "submit", "image", "reset", "button"] 171 | } 172 | }, 173 | ins: { attrs: { cite: null, datetime: null } }, 174 | kbd: s, 175 | keygen: { 176 | attrs: { 177 | challenge: null, form: null, name: null, 178 | autofocus: ["", "autofocus"], 179 | disabled: ["", "disabled"], 180 | keytype: ["RSA"] 181 | } 182 | }, 183 | label: { attrs: { "for": null, form: null } }, 184 | legend: s, 185 | li: { attrs: { value: null } }, 186 | link: { 187 | attrs: { 188 | href: null, type: null, 189 | hreflang: langs, 190 | media: media, 191 | sizes: ["all", "16x16", "16x16 32x32", "16x16 32x32 64x64"] 192 | } 193 | }, 194 | map: { attrs: { name: null } }, 195 | mark: s, 196 | menu: { attrs: { label: null, type: ["list", "context", "toolbar"] } }, 197 | meta: { 198 | attrs: { 199 | content: null, 200 | charset: charsets, 201 | name: ["viewport", "application-name", "author", "description", "generator", "keywords"], 202 | "http-equiv": ["content-language", "content-type", "default-style", "refresh"] 203 | } 204 | }, 205 | meter: { attrs: { value: null, min: null, low: null, high: null, max: null, optimum: null } }, 206 | nav: s, 207 | noframes: s, 208 | noscript: s, 209 | object: { 210 | attrs: { 211 | data: null, type: null, name: null, usemap: null, form: null, width: null, height: null, 212 | typemustmatch: ["", "typemustmatch"] 213 | } 214 | }, 215 | ol: { attrs: { reversed: ["", "reversed"], start: null, type: ["1", "a", "A", "i", "I"] } }, 216 | optgroup: { attrs: { disabled: ["", "disabled"], label: null } }, 217 | option: { attrs: { disabled: ["", "disabled"], label: null, selected: ["", "selected"], value: null } }, 218 | output: { attrs: { "for": null, form: null, name: null } }, 219 | p: s, 220 | param: { attrs: { name: null, value: null } }, 221 | pre: s, 222 | progress: { attrs: { value: null, max: null } }, 223 | q: { attrs: { cite: null } }, 224 | rp: s, 225 | rt: s, 226 | ruby: s, 227 | s: s, 228 | samp: s, 229 | script: { 230 | attrs: { 231 | type: ["text/javascript"], 232 | src: null, 233 | async: ["", "async"], 234 | defer: ["", "defer"], 235 | charset: charsets 236 | } 237 | }, 238 | section: s, 239 | select: { 240 | attrs: { 241 | form: null, name: null, size: null, 242 | autofocus: ["", "autofocus"], 243 | disabled: ["", "disabled"], 244 | multiple: ["", "multiple"] 245 | } 246 | }, 247 | small: s, 248 | source: { attrs: { src: null, type: null, media: null } }, 249 | span: s, 250 | strike: s, 251 | strong: s, 252 | style: { 253 | attrs: { 254 | type: ["text/css"], 255 | media: media, 256 | scoped: null 257 | } 258 | }, 259 | sub: s, 260 | summary: s, 261 | sup: s, 262 | table: s, 263 | tbody: s, 264 | td: { attrs: { colspan: null, rowspan: null, headers: null } }, 265 | textarea: { 266 | attrs: { 267 | dirname: null, form: null, maxlength: null, name: null, placeholder: null, 268 | rows: null, cols: null, 269 | autofocus: ["", "autofocus"], 270 | disabled: ["", "disabled"], 271 | readonly: ["", "readonly"], 272 | required: ["", "required"], 273 | wrap: ["soft", "hard"] 274 | } 275 | }, 276 | tfoot: s, 277 | th: { attrs: { colspan: null, rowspan: null, headers: null, scope: ["row", "col", "rowgroup", "colgroup"] } }, 278 | thead: s, 279 | time: { attrs: { datetime: null } }, 280 | title: s, 281 | tr: s, 282 | track: { 283 | attrs: { 284 | src: null, label: null, "default": null, 285 | kind: ["subtitles", "captions", "descriptions", "chapters", "metadata"], 286 | srclang: langs 287 | } 288 | }, 289 | tt: s, 290 | u: s, 291 | ul: s, 292 | "var": s, 293 | video: { 294 | attrs: { 295 | src: null, poster: null, width: null, height: null, 296 | crossorigin: ["anonymous", "use-credentials"], 297 | preload: ["auto", "metadata", "none"], 298 | autoplay: ["", "autoplay"], 299 | mediagroup: ["movie"], 300 | muted: ["", "muted"], 301 | controls: ["", "controls"] 302 | } 303 | }, 304 | wbr: s 305 | }; 306 | 307 | var globalAttrs = { 308 | accesskey: ["a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "n", "o", "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z", "0", "1", "2", "3", "4", "5", "6", "7", "8", "9"], 309 | "class": null, 310 | contenteditable: ["true", "false"], 311 | contextmenu: null, 312 | dir: ["ltr", "rtl", "auto"], 313 | draggable: ["true", "false", "auto"], 314 | dropzone: ["copy", "move", "link", "string:", "file:"], 315 | hidden: ["hidden"], 316 | id: null, 317 | inert: ["inert"], 318 | itemid: null, 319 | itemprop: null, 320 | itemref: null, 321 | itemscope: ["itemscope"], 322 | itemtype: null, 323 | lang: ["en", "es"], 324 | spellcheck: ["true", "false"], 325 | style: null, 326 | tabindex: ["1", "2", "3", "4", "5", "6", "7", "8", "9"], 327 | title: null, 328 | translate: ["yes", "no"], 329 | onclick: null, 330 | rel: ["stylesheet", "alternate", "author", "bookmark", "help", "license", "next", "nofollow", "noreferrer", "prefetch", "prev", "search", "tag"] 331 | }; 332 | function populate(obj) { 333 | for (var attr in globalAttrs) if (globalAttrs.hasOwnProperty(attr)) 334 | obj.attrs[attr] = globalAttrs[attr]; 335 | } 336 | 337 | populate(s); 338 | for (var tag in data) if (data.hasOwnProperty(tag) && data[tag] != s) 339 | populate(data[tag]); 340 | 341 | CodeMirror.htmlSchema = data; 342 | function htmlHint(cm, options) { 343 | var local = {schemaInfo: data}; 344 | if (options) for (var opt in options) local[opt] = options[opt]; 345 | return CodeMirror.hint.xml(cm, local); 346 | } 347 | CodeMirror.registerHelper("hint", "html", htmlHint); 348 | }); 349 | -------------------------------------------------------------------------------- /static/js/vendor/addons/javascript-hint.js: -------------------------------------------------------------------------------- 1 | // CodeMirror, copyright (c) by Marijn Haverbeke and others 2 | // Distributed under an MIT license: http://codemirror.net/LICENSE 3 | 4 | (function(mod) { 5 | if (typeof exports == "object" && typeof module == "object") // CommonJS 6 | mod(require("../../lib/codemirror")); 7 | else if (typeof define == "function" && define.amd) // AMD 8 | define(["../../lib/codemirror"], mod); 9 | else // Plain browser env 10 | mod(CodeMirror); 11 | })(function(CodeMirror) { 12 | var Pos = CodeMirror.Pos; 13 | 14 | function forEach(arr, f) { 15 | for (var i = 0, e = arr.length; i < e; ++i) f(arr[i]); 16 | } 17 | 18 | function arrayContains(arr, item) { 19 | if (!Array.prototype.indexOf) { 20 | var i = arr.length; 21 | while (i--) { 22 | if (arr[i] === item) { 23 | return true; 24 | } 25 | } 26 | return false; 27 | } 28 | return arr.indexOf(item) != -1; 29 | } 30 | 31 | function scriptHint(editor, keywords, getToken, options) { 32 | // Find the token at the cursor 33 | var cur = editor.getCursor(), token = getToken(editor, cur); 34 | if (/\b(?:string|comment)\b/.test(token.type)) return; 35 | token.state = CodeMirror.innerMode(editor.getMode(), token.state).state; 36 | 37 | // If it's not a 'word-style' token, ignore the token. 38 | if (!/^[\w$_]*$/.test(token.string)) { 39 | token = {start: cur.ch, end: cur.ch, string: "", state: token.state, 40 | type: token.string == "." ? "property" : null}; 41 | } else if (token.end > cur.ch) { 42 | token.end = cur.ch; 43 | token.string = token.string.slice(0, cur.ch - token.start); 44 | } 45 | 46 | var tprop = token; 47 | // If it is a property, find out what it is a property of. 48 | while (tprop.type == "property") { 49 | tprop = getToken(editor, Pos(cur.line, tprop.start)); 50 | if (tprop.string != ".") return; 51 | tprop = getToken(editor, Pos(cur.line, tprop.start)); 52 | if (!context) var context = []; 53 | context.push(tprop); 54 | } 55 | return {list: getCompletions(token, context, keywords, options), 56 | from: Pos(cur.line, token.start), 57 | to: Pos(cur.line, token.end)}; 58 | } 59 | 60 | function javascriptHint(editor, options) { 61 | return scriptHint(editor, javascriptKeywords, 62 | function (e, cur) {return e.getTokenAt(cur);}, 63 | options); 64 | }; 65 | CodeMirror.registerHelper("hint", "javascript", javascriptHint); 66 | 67 | function getCoffeeScriptToken(editor, cur) { 68 | // This getToken, it is for coffeescript, imitates the behavior of 69 | // getTokenAt method in javascript.js, that is, returning "property" 70 | // type and treat "." as indepenent token. 71 | var token = editor.getTokenAt(cur); 72 | if (cur.ch == token.start + 1 && token.string.charAt(0) == '.') { 73 | token.end = token.start; 74 | token.string = '.'; 75 | token.type = "property"; 76 | } 77 | else if (/^\.[\w$_]*$/.test(token.string)) { 78 | token.type = "property"; 79 | token.start++; 80 | token.string = token.string.replace(/\./, ''); 81 | } 82 | return token; 83 | } 84 | 85 | function coffeescriptHint(editor, options) { 86 | return scriptHint(editor, coffeescriptKeywords, getCoffeeScriptToken, options); 87 | } 88 | CodeMirror.registerHelper("hint", "coffeescript", coffeescriptHint); 89 | 90 | var stringProps = ("charAt charCodeAt indexOf lastIndexOf substring substr slice trim trimLeft trimRight " + 91 | "toUpperCase toLowerCase split concat match replace search").split(" "); 92 | var arrayProps = ("length concat join splice push pop shift unshift slice reverse sort indexOf " + 93 | "lastIndexOf every some filter forEach map reduce reduceRight ").split(" "); 94 | var funcProps = "prototype apply call bind".split(" "); 95 | var javascriptKeywords = ("break case catch continue debugger default delete do else false finally for function " + 96 | "if in instanceof new null return switch throw true try typeof var void while with").split(" "); 97 | var coffeescriptKeywords = ("and break catch class continue delete do else extends false finally for " + 98 | "if in instanceof isnt new no not null of off on or return switch then throw true try typeof until void while with yes").split(" "); 99 | 100 | function forAllProps(obj, callback) { 101 | if (!Object.getOwnPropertyNames || !Object.getPrototypeOf) { 102 | for (var name in obj) callback(name) 103 | } else { 104 | for (var o = obj; o; o = Object.getPrototypeOf(o)) 105 | Object.getOwnPropertyNames(o).forEach(callback) 106 | } 107 | } 108 | 109 | function getCompletions(token, context, keywords, options) { 110 | var found = [], start = token.string, global = options && options.globalScope || window; 111 | function maybeAdd(str) { 112 | if (str.lastIndexOf(start, 0) == 0 && !arrayContains(found, str)) found.push(str); 113 | } 114 | function gatherCompletions(obj) { 115 | if (typeof obj == "string") forEach(stringProps, maybeAdd); 116 | else if (obj instanceof Array) forEach(arrayProps, maybeAdd); 117 | else if (obj instanceof Function) forEach(funcProps, maybeAdd); 118 | forAllProps(obj, maybeAdd) 119 | } 120 | 121 | if (context && context.length) { 122 | // If this is a property, see if it belongs to some object we can 123 | // find in the current environment. 124 | var obj = context.pop(), base; 125 | if (obj.type && obj.type.indexOf("variable") === 0) { 126 | if (options && options.additionalContext) 127 | base = options.additionalContext[obj.string]; 128 | if (!options || options.useGlobalScope !== false) 129 | base = base || global[obj.string]; 130 | } else if (obj.type == "string") { 131 | base = ""; 132 | } else if (obj.type == "atom") { 133 | base = 1; 134 | } else if (obj.type == "function") { 135 | if (global.jQuery != null && (obj.string == '$' || obj.string == 'jQuery') && 136 | (typeof global.jQuery == 'function')) 137 | base = global.jQuery(); 138 | else if (global._ != null && (obj.string == '_') && (typeof global._ == 'function')) 139 | base = global._(); 140 | } 141 | while (base != null && context.length) 142 | base = base[context.pop().string]; 143 | if (base != null) gatherCompletions(base); 144 | } else { 145 | // If not, just look in the global object and any local scope 146 | // (reading into JS mode internals to get at the local and global variables) 147 | for (var v = token.state.localVars; v; v = v.next) maybeAdd(v.name); 148 | for (var v = token.state.globalVars; v; v = v.next) maybeAdd(v.name); 149 | if (!options || options.useGlobalScope !== false) 150 | gatherCompletions(global); 151 | forEach(keywords, maybeAdd); 152 | } 153 | return found; 154 | } 155 | }); 156 | -------------------------------------------------------------------------------- /static/js/vendor/addons/matchbrackets.js: -------------------------------------------------------------------------------- 1 | // CodeMirror, copyright (c) by Marijn Haverbeke and others 2 | // Distributed under an MIT license: http://codemirror.net/LICENSE 3 | 4 | (function(mod) { 5 | if (typeof exports == "object" && typeof module == "object") // CommonJS 6 | mod(require("../../lib/codemirror")); 7 | else if (typeof define == "function" && define.amd) // AMD 8 | define(["../../lib/codemirror"], mod); 9 | else // Plain browser env 10 | mod(CodeMirror); 11 | })(function(CodeMirror) { 12 | var ie_lt8 = /MSIE \d/.test(navigator.userAgent) && 13 | (document.documentMode == null || document.documentMode < 8); 14 | 15 | var Pos = CodeMirror.Pos; 16 | 17 | var matching = {"(": ")>", ")": "(<", "[": "]>", "]": "[<", "{": "}>", "}": "{<"}; 18 | 19 | function findMatchingBracket(cm, where, strict, config) { 20 | var line = cm.getLineHandle(where.line), pos = where.ch - 1; 21 | var match = (pos >= 0 && matching[line.text.charAt(pos)]) || matching[line.text.charAt(++pos)]; 22 | if (!match) return null; 23 | var dir = match.charAt(1) == ">" ? 1 : -1; 24 | if (strict && (dir > 0) != (pos == where.ch)) return null; 25 | var style = cm.getTokenTypeAt(Pos(where.line, pos + 1)); 26 | 27 | var found = scanForBracket(cm, Pos(where.line, pos + (dir > 0 ? 1 : 0)), dir, style || null, config); 28 | if (found == null) return null; 29 | return {from: Pos(where.line, pos), to: found && found.pos, 30 | match: found && found.ch == match.charAt(0), forward: dir > 0}; 31 | } 32 | 33 | // bracketRegex is used to specify which type of bracket to scan 34 | // should be a regexp, e.g. /[[\]]/ 35 | // 36 | // Note: If "where" is on an open bracket, then this bracket is ignored. 37 | // 38 | // Returns false when no bracket was found, null when it reached 39 | // maxScanLines and gave up 40 | function scanForBracket(cm, where, dir, style, config) { 41 | var maxScanLen = (config && config.maxScanLineLength) || 10000; 42 | var maxScanLines = (config && config.maxScanLines) || 1000; 43 | 44 | var stack = []; 45 | var re = config && config.bracketRegex ? config.bracketRegex : /[(){}[\]]/; 46 | var lineEnd = dir > 0 ? Math.min(where.line + maxScanLines, cm.lastLine() + 1) 47 | : Math.max(cm.firstLine() - 1, where.line - maxScanLines); 48 | for (var lineNo = where.line; lineNo != lineEnd; lineNo += dir) { 49 | var line = cm.getLine(lineNo); 50 | if (!line) continue; 51 | var pos = dir > 0 ? 0 : line.length - 1, end = dir > 0 ? line.length : -1; 52 | if (line.length > maxScanLen) continue; 53 | if (lineNo == where.line) pos = where.ch - (dir < 0 ? 1 : 0); 54 | for (; pos != end; pos += dir) { 55 | var ch = line.charAt(pos); 56 | if (re.test(ch) && (style === undefined || cm.getTokenTypeAt(Pos(lineNo, pos + 1)) == style)) { 57 | var match = matching[ch]; 58 | if ((match.charAt(1) == ">") == (dir > 0)) stack.push(ch); 59 | else if (!stack.length) return {pos: Pos(lineNo, pos), ch: ch}; 60 | else stack.pop(); 61 | } 62 | } 63 | } 64 | return lineNo - dir == (dir > 0 ? cm.lastLine() : cm.firstLine()) ? false : null; 65 | } 66 | 67 | function matchBrackets(cm, autoclear, config) { 68 | // Disable brace matching in long lines, since it'll cause hugely slow updates 69 | var maxHighlightLen = cm.state.matchBrackets.maxHighlightLineLength || 1000; 70 | var marks = [], ranges = cm.listSelections(); 71 | for (var i = 0; i < ranges.length; i++) { 72 | var match = ranges[i].empty() && findMatchingBracket(cm, ranges[i].head, false, config); 73 | if (match && cm.getLine(match.from.line).length <= maxHighlightLen) { 74 | var style = match.match ? "CodeMirror-matchingbracket" : "CodeMirror-nonmatchingbracket"; 75 | marks.push(cm.markText(match.from, Pos(match.from.line, match.from.ch + 1), {className: style})); 76 | if (match.to && cm.getLine(match.to.line).length <= maxHighlightLen) 77 | marks.push(cm.markText(match.to, Pos(match.to.line, match.to.ch + 1), {className: style})); 78 | } 79 | } 80 | 81 | if (marks.length) { 82 | // Kludge to work around the IE bug from issue #1193, where text 83 | // input stops going to the textare whever this fires. 84 | if (ie_lt8 && cm.state.focused) cm.focus(); 85 | 86 | var clear = function() { 87 | cm.operation(function() { 88 | for (var i = 0; i < marks.length; i++) marks[i].clear(); 89 | }); 90 | }; 91 | if (autoclear) setTimeout(clear, 800); 92 | else return clear; 93 | } 94 | } 95 | 96 | var currentlyHighlighted = null; 97 | function doMatchBrackets(cm) { 98 | cm.operation(function() { 99 | if (currentlyHighlighted) {currentlyHighlighted(); currentlyHighlighted = null;} 100 | currentlyHighlighted = matchBrackets(cm, false, cm.state.matchBrackets); 101 | }); 102 | } 103 | 104 | CodeMirror.defineOption("matchBrackets", false, function(cm, val, old) { 105 | if (old && old != CodeMirror.Init) { 106 | cm.off("cursorActivity", doMatchBrackets); 107 | if (currentlyHighlighted) {currentlyHighlighted(); currentlyHighlighted = null;} 108 | } 109 | if (val) { 110 | cm.state.matchBrackets = typeof val == "object" ? val : {}; 111 | cm.on("cursorActivity", doMatchBrackets); 112 | } 113 | }); 114 | 115 | CodeMirror.defineExtension("matchBrackets", function() {matchBrackets(this, true);}); 116 | CodeMirror.defineExtension("findMatchingBracket", function(pos, strict, config){ 117 | return findMatchingBracket(this, pos, strict, config); 118 | }); 119 | CodeMirror.defineExtension("scanForBracket", function(pos, dir, style, config){ 120 | return scanForBracket(this, pos, dir, style, config); 121 | }); 122 | }); 123 | -------------------------------------------------------------------------------- /static/js/vendor/addons/matchtags.js: -------------------------------------------------------------------------------- 1 | // CodeMirror, copyright (c) by Marijn Haverbeke and others 2 | // Distributed under an MIT license: http://codemirror.net/LICENSE 3 | 4 | (function(mod) { 5 | if (typeof exports == "object" && typeof module == "object") // CommonJS 6 | mod(require("../../lib/codemirror"), require("../fold/xml-fold")); 7 | else if (typeof define == "function" && define.amd) // AMD 8 | define(["../../lib/codemirror", "../fold/xml-fold"], mod); 9 | else // Plain browser env 10 | mod(CodeMirror); 11 | })(function(CodeMirror) { 12 | "use strict"; 13 | 14 | CodeMirror.defineOption("matchTags", false, function(cm, val, old) { 15 | if (old && old != CodeMirror.Init) { 16 | cm.off("cursorActivity", doMatchTags); 17 | cm.off("viewportChange", maybeUpdateMatch); 18 | clear(cm); 19 | } 20 | if (val) { 21 | cm.state.matchBothTags = typeof val == "object" && val.bothTags; 22 | cm.on("cursorActivity", doMatchTags); 23 | cm.on("viewportChange", maybeUpdateMatch); 24 | doMatchTags(cm); 25 | } 26 | }); 27 | 28 | function clear(cm) { 29 | if (cm.state.tagHit) cm.state.tagHit.clear(); 30 | if (cm.state.tagOther) cm.state.tagOther.clear(); 31 | cm.state.tagHit = cm.state.tagOther = null; 32 | } 33 | 34 | function doMatchTags(cm) { 35 | cm.state.failedTagMatch = false; 36 | cm.operation(function() { 37 | clear(cm); 38 | if (cm.somethingSelected()) return; 39 | var cur = cm.getCursor(), range = cm.getViewport(); 40 | range.from = Math.min(range.from, cur.line); range.to = Math.max(cur.line + 1, range.to); 41 | var match = CodeMirror.findMatchingTag(cm, cur, range); 42 | if (!match) return; 43 | if (cm.state.matchBothTags) { 44 | var hit = match.at == "open" ? match.open : match.close; 45 | if (hit) cm.state.tagHit = cm.markText(hit.from, hit.to, {className: "CodeMirror-matchingtag"}); 46 | } 47 | var other = match.at == "close" ? match.open : match.close; 48 | if (other) 49 | cm.state.tagOther = cm.markText(other.from, other.to, {className: "CodeMirror-matchingtag"}); 50 | else 51 | cm.state.failedTagMatch = true; 52 | }); 53 | } 54 | 55 | function maybeUpdateMatch(cm) { 56 | if (cm.state.failedTagMatch) doMatchTags(cm); 57 | } 58 | 59 | CodeMirror.commands.toMatchingTag = function(cm) { 60 | var found = CodeMirror.findMatchingTag(cm, cm.getCursor()); 61 | if (found) { 62 | var other = found.at == "close" ? found.open : found.close; 63 | if (other) cm.extendSelection(other.to, other.from); 64 | } 65 | }; 66 | }); 67 | -------------------------------------------------------------------------------- /static/js/vendor/addons/show-hint.js: -------------------------------------------------------------------------------- 1 | // CodeMirror, copyright (c) by Marijn Haverbeke and others 2 | // Distributed under an MIT license: http://codemirror.net/LICENSE 3 | 4 | (function(mod) { 5 | if (typeof exports == "object" && typeof module == "object") // CommonJS 6 | mod(require("../../lib/codemirror")); 7 | else if (typeof define == "function" && define.amd) // AMD 8 | define(["../../lib/codemirror"], mod); 9 | else // Plain browser env 10 | mod(CodeMirror); 11 | })(function(CodeMirror) { 12 | "use strict"; 13 | 14 | var HINT_ELEMENT_CLASS = "CodeMirror-hint"; 15 | var ACTIVE_HINT_ELEMENT_CLASS = "CodeMirror-hint-active"; 16 | 17 | // This is the old interface, kept around for now to stay 18 | // backwards-compatible. 19 | CodeMirror.showHint = function(cm, getHints, options) { 20 | if (!getHints) return cm.showHint(options); 21 | if (options && options.async) getHints.async = true; 22 | var newOpts = {hint: getHints}; 23 | if (options) for (var prop in options) newOpts[prop] = options[prop]; 24 | return cm.showHint(newOpts); 25 | }; 26 | 27 | CodeMirror.defineExtension("showHint", function(options) { 28 | options = parseOptions(this, this.getCursor("start"), options); 29 | var selections = this.listSelections() 30 | if (selections.length > 1) return; 31 | // By default, don't allow completion when something is selected. 32 | // A hint function can have a `supportsSelection` property to 33 | // indicate that it can handle selections. 34 | if (this.somethingSelected()) { 35 | if (!options.hint.supportsSelection) return; 36 | // Don't try with cross-line selections 37 | for (var i = 0; i < selections.length; i++) 38 | if (selections[i].head.line != selections[i].anchor.line) return; 39 | } 40 | 41 | if (this.state.completionActive) this.state.completionActive.close(); 42 | var completion = this.state.completionActive = new Completion(this, options); 43 | if (!completion.options.hint) return; 44 | 45 | CodeMirror.signal(this, "startCompletion", this); 46 | completion.update(true); 47 | }); 48 | 49 | function Completion(cm, options) { 50 | this.cm = cm; 51 | this.options = options; 52 | this.widget = null; 53 | this.debounce = 0; 54 | this.tick = 0; 55 | this.startPos = this.cm.getCursor("start"); 56 | this.startLen = this.cm.getLine(this.startPos.line).length - this.cm.getSelection().length; 57 | 58 | var self = this; 59 | cm.on("cursorActivity", this.activityFunc = function() { self.cursorActivity(); }); 60 | } 61 | 62 | var requestAnimationFrame = window.requestAnimationFrame || function(fn) { 63 | return setTimeout(fn, 1000/60); 64 | }; 65 | var cancelAnimationFrame = window.cancelAnimationFrame || clearTimeout; 66 | 67 | Completion.prototype = { 68 | close: function() { 69 | if (!this.active()) return; 70 | this.cm.state.completionActive = null; 71 | this.tick = null; 72 | this.cm.off("cursorActivity", this.activityFunc); 73 | 74 | if (this.widget && this.data) CodeMirror.signal(this.data, "close"); 75 | if (this.widget) this.widget.close(); 76 | CodeMirror.signal(this.cm, "endCompletion", this.cm); 77 | }, 78 | 79 | active: function() { 80 | return this.cm.state.completionActive == this; 81 | }, 82 | 83 | pick: function(data, i) { 84 | var completion = data.list[i]; 85 | if (completion.hint) completion.hint(this.cm, data, completion); 86 | else this.cm.replaceRange(getText(completion), completion.from || data.from, 87 | completion.to || data.to, "complete"); 88 | CodeMirror.signal(data, "pick", completion); 89 | this.close(); 90 | }, 91 | 92 | cursorActivity: function() { 93 | if (this.debounce) { 94 | cancelAnimationFrame(this.debounce); 95 | this.debounce = 0; 96 | } 97 | 98 | var pos = this.cm.getCursor(), line = this.cm.getLine(pos.line); 99 | if (pos.line != this.startPos.line || line.length - pos.ch != this.startLen - this.startPos.ch || 100 | pos.ch < this.startPos.ch || this.cm.somethingSelected() || 101 | (pos.ch && this.options.closeCharacters.test(line.charAt(pos.ch - 1)))) { 102 | this.close(); 103 | } else { 104 | var self = this; 105 | this.debounce = requestAnimationFrame(function() {self.update();}); 106 | if (this.widget) this.widget.disable(); 107 | } 108 | }, 109 | 110 | update: function(first) { 111 | if (this.tick == null) return 112 | var self = this, myTick = ++this.tick 113 | fetchHints(this.options.hint, this.cm, this.options, function(data) { 114 | if (self.tick == myTick) self.finishUpdate(data, first) 115 | }) 116 | }, 117 | 118 | finishUpdate: function(data, first) { 119 | if (this.data) CodeMirror.signal(this.data, "update"); 120 | 121 | var picked = (this.widget && this.widget.picked) || (first && this.options.completeSingle); 122 | if (this.widget) this.widget.close(); 123 | 124 | if (data && this.data && isNewCompletion(this.data, data)) return; 125 | this.data = data; 126 | 127 | if (data && data.list.length) { 128 | if (picked && data.list.length == 1) { 129 | this.pick(data, 0); 130 | } else { 131 | this.widget = new Widget(this, data); 132 | CodeMirror.signal(data, "shown"); 133 | } 134 | } 135 | } 136 | }; 137 | 138 | function isNewCompletion(old, nw) { 139 | var moved = CodeMirror.cmpPos(nw.from, old.from) 140 | return moved > 0 && old.to.ch - old.from.ch != nw.to.ch - nw.from.ch 141 | } 142 | 143 | function parseOptions(cm, pos, options) { 144 | var editor = cm.options.hintOptions; 145 | var out = {}; 146 | for (var prop in defaultOptions) out[prop] = defaultOptions[prop]; 147 | if (editor) for (var prop in editor) 148 | if (editor[prop] !== undefined) out[prop] = editor[prop]; 149 | if (options) for (var prop in options) 150 | if (options[prop] !== undefined) out[prop] = options[prop]; 151 | if (out.hint.resolve) out.hint = out.hint.resolve(cm, pos) 152 | return out; 153 | } 154 | 155 | function getText(completion) { 156 | if (typeof completion == "string") return completion; 157 | else return completion.text; 158 | } 159 | 160 | function buildKeyMap(completion, handle) { 161 | var baseMap = { 162 | Up: function() {handle.moveFocus(-1);}, 163 | Down: function() {handle.moveFocus(1);}, 164 | PageUp: function() {handle.moveFocus(-handle.menuSize() + 1, true);}, 165 | PageDown: function() {handle.moveFocus(handle.menuSize() - 1, true);}, 166 | Home: function() {handle.setFocus(0);}, 167 | End: function() {handle.setFocus(handle.length - 1);}, 168 | Enter: handle.pick, 169 | Tab: handle.pick, 170 | Esc: handle.close 171 | }; 172 | var custom = completion.options.customKeys; 173 | var ourMap = custom ? {} : baseMap; 174 | function addBinding(key, val) { 175 | var bound; 176 | if (typeof val != "string") 177 | bound = function(cm) { return val(cm, handle); }; 178 | // This mechanism is deprecated 179 | else if (baseMap.hasOwnProperty(val)) 180 | bound = baseMap[val]; 181 | else 182 | bound = val; 183 | ourMap[key] = bound; 184 | } 185 | if (custom) 186 | for (var key in custom) if (custom.hasOwnProperty(key)) 187 | addBinding(key, custom[key]); 188 | var extra = completion.options.extraKeys; 189 | if (extra) 190 | for (var key in extra) if (extra.hasOwnProperty(key)) 191 | addBinding(key, extra[key]); 192 | return ourMap; 193 | } 194 | 195 | function getHintElement(hintsElement, el) { 196 | while (el && el != hintsElement) { 197 | if (el.nodeName.toUpperCase() === "LI" && el.parentNode == hintsElement) return el; 198 | el = el.parentNode; 199 | } 200 | } 201 | 202 | function Widget(completion, data) { 203 | this.completion = completion; 204 | this.data = data; 205 | this.picked = false; 206 | var widget = this, cm = completion.cm; 207 | 208 | var hints = this.hints = document.createElement("ul"); 209 | hints.className = "CodeMirror-hints"; 210 | this.selectedHint = data.selectedHint || 0; 211 | 212 | var completions = data.list; 213 | for (var i = 0; i < completions.length; ++i) { 214 | var elt = hints.appendChild(document.createElement("li")), cur = completions[i]; 215 | var className = HINT_ELEMENT_CLASS + (i != this.selectedHint ? "" : " " + ACTIVE_HINT_ELEMENT_CLASS); 216 | if (cur.className != null) className = cur.className + " " + className; 217 | elt.className = className; 218 | if (cur.render) cur.render(elt, data, cur); 219 | else elt.appendChild(document.createTextNode(cur.displayText || getText(cur))); 220 | elt.hintId = i; 221 | } 222 | 223 | var pos = cm.cursorCoords(completion.options.alignWithWord ? data.from : null); 224 | var left = pos.left, top = pos.bottom, below = true; 225 | hints.style.left = left + "px"; 226 | hints.style.top = top + "px"; 227 | // If we're at the edge of the screen, then we want the menu to appear on the left of the cursor. 228 | var winW = window.innerWidth || Math.max(document.body.offsetWidth, document.documentElement.offsetWidth); 229 | var winH = window.innerHeight || Math.max(document.body.offsetHeight, document.documentElement.offsetHeight); 230 | (completion.options.container || document.body).appendChild(hints); 231 | var box = hints.getBoundingClientRect(), overlapY = box.bottom - winH; 232 | var scrolls = hints.scrollHeight > hints.clientHeight + 1 233 | var startScroll = cm.getScrollInfo(); 234 | 235 | if (overlapY > 0) { 236 | var height = box.bottom - box.top, curTop = pos.top - (pos.bottom - box.top); 237 | if (curTop - height > 0) { // Fits above cursor 238 | hints.style.top = (top = pos.top - height) + "px"; 239 | below = false; 240 | } else if (height > winH) { 241 | hints.style.height = (winH - 5) + "px"; 242 | hints.style.top = (top = pos.bottom - box.top) + "px"; 243 | var cursor = cm.getCursor(); 244 | if (data.from.ch != cursor.ch) { 245 | pos = cm.cursorCoords(cursor); 246 | hints.style.left = (left = pos.left) + "px"; 247 | box = hints.getBoundingClientRect(); 248 | } 249 | } 250 | } 251 | var overlapX = box.right - winW; 252 | if (overlapX > 0) { 253 | if (box.right - box.left > winW) { 254 | hints.style.width = (winW - 5) + "px"; 255 | overlapX -= (box.right - box.left) - winW; 256 | } 257 | hints.style.left = (left = pos.left - overlapX) + "px"; 258 | } 259 | if (scrolls) for (var node = hints.firstChild; node; node = node.nextSibling) 260 | node.style.paddingRight = cm.display.nativeBarWidth + "px" 261 | 262 | cm.addKeyMap(this.keyMap = buildKeyMap(completion, { 263 | moveFocus: function(n, avoidWrap) { widget.changeActive(widget.selectedHint + n, avoidWrap); }, 264 | setFocus: function(n) { widget.changeActive(n); }, 265 | menuSize: function() { return widget.screenAmount(); }, 266 | length: completions.length, 267 | close: function() { completion.close(); }, 268 | pick: function() { widget.pick(); }, 269 | data: data 270 | })); 271 | 272 | if (completion.options.closeOnUnfocus) { 273 | var closingOnBlur; 274 | cm.on("blur", this.onBlur = function() { closingOnBlur = setTimeout(function() { completion.close(); }, 100); }); 275 | cm.on("focus", this.onFocus = function() { clearTimeout(closingOnBlur); }); 276 | } 277 | 278 | cm.on("scroll", this.onScroll = function() { 279 | var curScroll = cm.getScrollInfo(), editor = cm.getWrapperElement().getBoundingClientRect(); 280 | var newTop = top + startScroll.top - curScroll.top; 281 | var point = newTop - (window.pageYOffset || (document.documentElement || document.body).scrollTop); 282 | if (!below) point += hints.offsetHeight; 283 | if (point <= editor.top || point >= editor.bottom) return completion.close(); 284 | hints.style.top = newTop + "px"; 285 | hints.style.left = (left + startScroll.left - curScroll.left) + "px"; 286 | }); 287 | 288 | CodeMirror.on(hints, "dblclick", function(e) { 289 | var t = getHintElement(hints, e.target || e.srcElement); 290 | if (t && t.hintId != null) {widget.changeActive(t.hintId); widget.pick();} 291 | }); 292 | 293 | CodeMirror.on(hints, "click", function(e) { 294 | var t = getHintElement(hints, e.target || e.srcElement); 295 | if (t && t.hintId != null) { 296 | widget.changeActive(t.hintId); 297 | if (completion.options.completeOnSingleClick) widget.pick(); 298 | } 299 | }); 300 | 301 | CodeMirror.on(hints, "mousedown", function() { 302 | setTimeout(function(){cm.focus();}, 20); 303 | }); 304 | 305 | CodeMirror.signal(data, "select", completions[0], hints.firstChild); 306 | return true; 307 | } 308 | 309 | Widget.prototype = { 310 | close: function() { 311 | if (this.completion.widget != this) return; 312 | this.completion.widget = null; 313 | this.hints.parentNode.removeChild(this.hints); 314 | this.completion.cm.removeKeyMap(this.keyMap); 315 | 316 | var cm = this.completion.cm; 317 | if (this.completion.options.closeOnUnfocus) { 318 | cm.off("blur", this.onBlur); 319 | cm.off("focus", this.onFocus); 320 | } 321 | cm.off("scroll", this.onScroll); 322 | }, 323 | 324 | disable: function() { 325 | this.completion.cm.removeKeyMap(this.keyMap); 326 | var widget = this; 327 | this.keyMap = {Enter: function() { widget.picked = true; }}; 328 | this.completion.cm.addKeyMap(this.keyMap); 329 | }, 330 | 331 | pick: function() { 332 | this.completion.pick(this.data, this.selectedHint); 333 | }, 334 | 335 | changeActive: function(i, avoidWrap) { 336 | if (i >= this.data.list.length) 337 | i = avoidWrap ? this.data.list.length - 1 : 0; 338 | else if (i < 0) 339 | i = avoidWrap ? 0 : this.data.list.length - 1; 340 | if (this.selectedHint == i) return; 341 | var node = this.hints.childNodes[this.selectedHint]; 342 | node.className = node.className.replace(" " + ACTIVE_HINT_ELEMENT_CLASS, ""); 343 | node = this.hints.childNodes[this.selectedHint = i]; 344 | node.className += " " + ACTIVE_HINT_ELEMENT_CLASS; 345 | if (node.offsetTop < this.hints.scrollTop) 346 | this.hints.scrollTop = node.offsetTop - 3; 347 | else if (node.offsetTop + node.offsetHeight > this.hints.scrollTop + this.hints.clientHeight) 348 | this.hints.scrollTop = node.offsetTop + node.offsetHeight - this.hints.clientHeight + 3; 349 | CodeMirror.signal(this.data, "select", this.data.list[this.selectedHint], node); 350 | }, 351 | 352 | screenAmount: function() { 353 | return Math.floor(this.hints.clientHeight / this.hints.firstChild.offsetHeight) || 1; 354 | } 355 | }; 356 | 357 | function applicableHelpers(cm, helpers) { 358 | if (!cm.somethingSelected()) return helpers 359 | var result = [] 360 | for (var i = 0; i < helpers.length; i++) 361 | if (helpers[i].supportsSelection) result.push(helpers[i]) 362 | return result 363 | } 364 | 365 | function fetchHints(hint, cm, options, callback) { 366 | if (hint.async) { 367 | hint(cm, callback, options) 368 | } else { 369 | var result = hint(cm, options) 370 | if (result && result.then) result.then(callback) 371 | else callback(result) 372 | } 373 | } 374 | 375 | function resolveAutoHints(cm, pos) { 376 | var helpers = cm.getHelpers(pos, "hint"), words 377 | if (helpers.length) { 378 | var resolved = function(cm, callback, options) { 379 | var app = applicableHelpers(cm, helpers); 380 | function run(i) { 381 | if (i == app.length) return callback(null) 382 | fetchHints(app[i], cm, options, function(result) { 383 | if (result && result.list.length > 0) callback(result) 384 | else run(i + 1) 385 | }) 386 | } 387 | run(0) 388 | } 389 | resolved.async = true 390 | resolved.supportsSelection = true 391 | return resolved 392 | } else if (words = cm.getHelper(cm.getCursor(), "hintWords")) { 393 | return function(cm) { return CodeMirror.hint.fromList(cm, {words: words}) } 394 | } else if (CodeMirror.hint.anyword) { 395 | return function(cm, options) { return CodeMirror.hint.anyword(cm, options) } 396 | } else { 397 | return function() {} 398 | } 399 | } 400 | 401 | CodeMirror.registerHelper("hint", "auto", { 402 | resolve: resolveAutoHints 403 | }); 404 | 405 | CodeMirror.registerHelper("hint", "fromList", function(cm, options) { 406 | var cur = cm.getCursor(), token = cm.getTokenAt(cur); 407 | var to = CodeMirror.Pos(cur.line, token.end); 408 | if (token.string && /\w/.test(token.string[token.string.length - 1])) { 409 | var term = token.string, from = CodeMirror.Pos(cur.line, token.start); 410 | } else { 411 | var term = "", from = to; 412 | } 413 | var found = []; 414 | for (var i = 0; i < options.words.length; i++) { 415 | var word = options.words[i]; 416 | if (word.slice(0, term.length) == term) 417 | found.push(word); 418 | } 419 | 420 | if (found.length) return {list: found, from: from, to: to}; 421 | }); 422 | 423 | CodeMirror.commands.autocomplete = CodeMirror.showHint; 424 | 425 | var defaultOptions = { 426 | hint: CodeMirror.hint.auto, 427 | completeSingle: true, 428 | alignWithWord: true, 429 | closeCharacters: /[\s()\[\]{};:>,]/, 430 | closeOnUnfocus: true, 431 | completeOnSingleClick: true, 432 | container: null, 433 | customKeys: null, 434 | extraKeys: null 435 | }; 436 | 437 | CodeMirror.defineOption("hintOptions", null); 438 | }); 439 | -------------------------------------------------------------------------------- /static/js/vendor/addons/xml-fold.js: -------------------------------------------------------------------------------- 1 | // CodeMirror, copyright (c) by Marijn Haverbeke and others 2 | // Distributed under an MIT license: http://codemirror.net/LICENSE 3 | 4 | (function(mod) { 5 | if (typeof exports == "object" && typeof module == "object") // CommonJS 6 | mod(require("../../lib/codemirror")); 7 | else if (typeof define == "function" && define.amd) // AMD 8 | define(["../../lib/codemirror"], mod); 9 | else // Plain browser env 10 | mod(CodeMirror); 11 | })(function(CodeMirror) { 12 | "use strict"; 13 | 14 | var Pos = CodeMirror.Pos; 15 | function cmp(a, b) { return a.line - b.line || a.ch - b.ch; } 16 | 17 | var nameStartChar = "A-Z_a-z\\u00C0-\\u00D6\\u00D8-\\u00F6\\u00F8-\\u02FF\\u0370-\\u037D\\u037F-\\u1FFF\\u200C-\\u200D\\u2070-\\u218F\\u2C00-\\u2FEF\\u3001-\\uD7FF\\uF900-\\uFDCF\\uFDF0-\\uFFFD"; 18 | var nameChar = nameStartChar + "\-\:\.0-9\\u00B7\\u0300-\\u036F\\u203F-\\u2040"; 19 | var xmlTagStart = new RegExp("<(/?)([" + nameStartChar + "][" + nameChar + "]*)", "g"); 20 | 21 | function Iter(cm, line, ch, range) { 22 | this.line = line; this.ch = ch; 23 | this.cm = cm; this.text = cm.getLine(line); 24 | this.min = range ? Math.max(range.from, cm.firstLine()) : cm.firstLine(); 25 | this.max = range ? Math.min(range.to - 1, cm.lastLine()) : cm.lastLine(); 26 | } 27 | 28 | function tagAt(iter, ch) { 29 | var type = iter.cm.getTokenTypeAt(Pos(iter.line, ch)); 30 | return type && /\btag\b/.test(type); 31 | } 32 | 33 | function nextLine(iter) { 34 | if (iter.line >= iter.max) return; 35 | iter.ch = 0; 36 | iter.text = iter.cm.getLine(++iter.line); 37 | return true; 38 | } 39 | function prevLine(iter) { 40 | if (iter.line <= iter.min) return; 41 | iter.text = iter.cm.getLine(--iter.line); 42 | iter.ch = iter.text.length; 43 | return true; 44 | } 45 | 46 | function toTagEnd(iter) { 47 | for (;;) { 48 | var gt = iter.text.indexOf(">", iter.ch); 49 | if (gt == -1) { if (nextLine(iter)) continue; else return; } 50 | if (!tagAt(iter, gt + 1)) { iter.ch = gt + 1; continue; } 51 | var lastSlash = iter.text.lastIndexOf("/", gt); 52 | var selfClose = lastSlash > -1 && !/\S/.test(iter.text.slice(lastSlash + 1, gt)); 53 | iter.ch = gt + 1; 54 | return selfClose ? "selfClose" : "regular"; 55 | } 56 | } 57 | function toTagStart(iter) { 58 | for (;;) { 59 | var lt = iter.ch ? iter.text.lastIndexOf("<", iter.ch - 1) : -1; 60 | if (lt == -1) { if (prevLine(iter)) continue; else return; } 61 | if (!tagAt(iter, lt + 1)) { iter.ch = lt; continue; } 62 | xmlTagStart.lastIndex = lt; 63 | iter.ch = lt; 64 | var match = xmlTagStart.exec(iter.text); 65 | if (match && match.index == lt) return match; 66 | } 67 | } 68 | 69 | function toNextTag(iter) { 70 | for (;;) { 71 | xmlTagStart.lastIndex = iter.ch; 72 | var found = xmlTagStart.exec(iter.text); 73 | if (!found) { if (nextLine(iter)) continue; else return; } 74 | if (!tagAt(iter, found.index + 1)) { iter.ch = found.index + 1; continue; } 75 | iter.ch = found.index + found[0].length; 76 | return found; 77 | } 78 | } 79 | function toPrevTag(iter) { 80 | for (;;) { 81 | var gt = iter.ch ? iter.text.lastIndexOf(">", iter.ch - 1) : -1; 82 | if (gt == -1) { if (prevLine(iter)) continue; else return; } 83 | if (!tagAt(iter, gt + 1)) { iter.ch = gt; continue; } 84 | var lastSlash = iter.text.lastIndexOf("/", gt); 85 | var selfClose = lastSlash > -1 && !/\S/.test(iter.text.slice(lastSlash + 1, gt)); 86 | iter.ch = gt + 1; 87 | return selfClose ? "selfClose" : "regular"; 88 | } 89 | } 90 | 91 | function findMatchingClose(iter, tag) { 92 | var stack = []; 93 | for (;;) { 94 | var next = toNextTag(iter), end, startLine = iter.line, startCh = iter.ch - (next ? next[0].length : 0); 95 | if (!next || !(end = toTagEnd(iter))) return; 96 | if (end == "selfClose") continue; 97 | if (next[1]) { // closing tag 98 | for (var i = stack.length - 1; i >= 0; --i) if (stack[i] == next[2]) { 99 | stack.length = i; 100 | break; 101 | } 102 | if (i < 0 && (!tag || tag == next[2])) return { 103 | tag: next[2], 104 | from: Pos(startLine, startCh), 105 | to: Pos(iter.line, iter.ch) 106 | }; 107 | } else { // opening tag 108 | stack.push(next[2]); 109 | } 110 | } 111 | } 112 | function findMatchingOpen(iter, tag) { 113 | var stack = []; 114 | for (;;) { 115 | var prev = toPrevTag(iter); 116 | if (!prev) return; 117 | if (prev == "selfClose") { toTagStart(iter); continue; } 118 | var endLine = iter.line, endCh = iter.ch; 119 | var start = toTagStart(iter); 120 | if (!start) return; 121 | if (start[1]) { // closing tag 122 | stack.push(start[2]); 123 | } else { // opening tag 124 | for (var i = stack.length - 1; i >= 0; --i) if (stack[i] == start[2]) { 125 | stack.length = i; 126 | break; 127 | } 128 | if (i < 0 && (!tag || tag == start[2])) return { 129 | tag: start[2], 130 | from: Pos(iter.line, iter.ch), 131 | to: Pos(endLine, endCh) 132 | }; 133 | } 134 | } 135 | } 136 | 137 | CodeMirror.registerHelper("fold", "xml", function(cm, start) { 138 | var iter = new Iter(cm, start.line, 0); 139 | for (;;) { 140 | var openTag = toNextTag(iter), end; 141 | if (!openTag || iter.line != start.line || !(end = toTagEnd(iter))) return; 142 | if (!openTag[1] && end != "selfClose") { 143 | var startPos = Pos(iter.line, iter.ch); 144 | var endPos = findMatchingClose(iter, openTag[2]); 145 | return endPos && {from: startPos, to: endPos.from}; 146 | } 147 | } 148 | }); 149 | CodeMirror.findMatchingTag = function(cm, pos, range) { 150 | var iter = new Iter(cm, pos.line, pos.ch, range); 151 | if (iter.text.indexOf(">") == -1 && iter.text.indexOf("<") == -1) return; 152 | var end = toTagEnd(iter), to = end && Pos(iter.line, iter.ch); 153 | var start = end && toTagStart(iter); 154 | if (!end || !start || cmp(iter, pos) > 0) return; 155 | var here = {from: Pos(iter.line, iter.ch), to: to, tag: start[2]}; 156 | if (end == "selfClose") return {open: here, close: null, at: "open"}; 157 | 158 | if (start[1]) { // closing tag 159 | return {open: findMatchingOpen(iter, start[2]), close: here, at: "close"}; 160 | } else { // opening tag 161 | iter = new Iter(cm, to.line, to.ch, range); 162 | return {open: here, close: findMatchingClose(iter, start[2]), at: "open"}; 163 | } 164 | }; 165 | 166 | CodeMirror.findEnclosingTag = function(cm, pos, range, tag) { 167 | var iter = new Iter(cm, pos.line, pos.ch, range); 168 | for (;;) { 169 | var open = findMatchingOpen(iter, tag); 170 | if (!open) break; 171 | var forward = new Iter(cm, pos.line, pos.ch, range); 172 | var close = findMatchingClose(forward, open.tag); 173 | if (close) return {open: open, close: close}; 174 | } 175 | }; 176 | 177 | // Used by addon/edit/closetag.js 178 | CodeMirror.scanForClosingTag = function(cm, pos, name, end) { 179 | var iter = new Iter(cm, pos.line, pos.ch, end ? {from: 0, to: end} : null); 180 | return findMatchingClose(iter, name); 181 | }; 182 | }); 183 | -------------------------------------------------------------------------------- /static/js/vendor/addons/xml-hint.js: -------------------------------------------------------------------------------- 1 | // CodeMirror, copyright (c) by Marijn Haverbeke and others 2 | // Distributed under an MIT license: http://codemirror.net/LICENSE 3 | 4 | (function(mod) { 5 | if (typeof exports == "object" && typeof module == "object") // CommonJS 6 | mod(require("../../lib/codemirror")); 7 | else if (typeof define == "function" && define.amd) // AMD 8 | define(["../../lib/codemirror"], mod); 9 | else // Plain browser env 10 | mod(CodeMirror); 11 | })(function(CodeMirror) { 12 | "use strict"; 13 | 14 | var Pos = CodeMirror.Pos; 15 | 16 | function getHints(cm, options) { 17 | var tags = options && options.schemaInfo; 18 | var quote = (options && options.quoteChar) || '"'; 19 | if (!tags) return; 20 | var cur = cm.getCursor(), token = cm.getTokenAt(cur); 21 | if (token.end > cur.ch) { 22 | token.end = cur.ch; 23 | token.string = token.string.slice(0, cur.ch - token.start); 24 | } 25 | var inner = CodeMirror.innerMode(cm.getMode(), token.state); 26 | if (inner.mode.name != "xml") return; 27 | var result = [], replaceToken = false, prefix; 28 | var tag = /\btag\b/.test(token.type) && !/>$/.test(token.string); 29 | var tagName = tag && /^\w/.test(token.string), tagStart; 30 | 31 | if (tagName) { 32 | var before = cm.getLine(cur.line).slice(Math.max(0, token.start - 2), token.start); 33 | var tagType = /<\/$/.test(before) ? "close" : /<$/.test(before) ? "open" : null; 34 | if (tagType) tagStart = token.start - (tagType == "close" ? 2 : 1); 35 | } else if (tag && token.string == "<") { 36 | tagType = "open"; 37 | } else if (tag && token.string == ""); 57 | } else { 58 | // Attribute completion 59 | var curTag = tags[inner.state.tagName], attrs = curTag && curTag.attrs; 60 | var globalAttrs = tags["!attrs"]; 61 | if (!attrs && !globalAttrs) return; 62 | if (!attrs) { 63 | attrs = globalAttrs; 64 | } else if (globalAttrs) { // Combine tag-local and global attributes 65 | var set = {}; 66 | for (var nm in globalAttrs) if (globalAttrs.hasOwnProperty(nm)) set[nm] = globalAttrs[nm]; 67 | for (var nm in attrs) if (attrs.hasOwnProperty(nm)) set[nm] = attrs[nm]; 68 | attrs = set; 69 | } 70 | if (token.type == "string" || token.string == "=") { // A value 71 | var before = cm.getRange(Pos(cur.line, Math.max(0, cur.ch - 60)), 72 | Pos(cur.line, token.type == "string" ? token.start : token.end)); 73 | var atName = before.match(/([^\s\u00a0=<>\"\']+)=$/), atValues; 74 | if (!atName || !attrs.hasOwnProperty(atName[1]) || !(atValues = attrs[atName[1]])) return; 75 | if (typeof atValues == 'function') atValues = atValues.call(this, cm); // Functions can be used to supply values for autocomplete widget 76 | if (token.type == "string") { 77 | prefix = token.string; 78 | var n = 0; 79 | if (/['"]/.test(token.string.charAt(0))) { 80 | quote = token.string.charAt(0); 81 | prefix = token.string.slice(1); 82 | n++; 83 | } 84 | var len = token.string.length; 85 | if (/['"]/.test(token.string.charAt(len - 1))) { 86 | quote = token.string.charAt(len - 1); 87 | prefix = token.string.substr(n, len - 2); 88 | } 89 | replaceToken = true; 90 | } 91 | for (var i = 0; i < atValues.length; ++i) if (!prefix || atValues[i].lastIndexOf(prefix, 0) == 0) 92 | result.push(quote + atValues[i] + quote); 93 | } else { // An attribute name 94 | if (token.type == "attribute") { 95 | prefix = token.string; 96 | replaceToken = true; 97 | } 98 | for (var attr in attrs) if (attrs.hasOwnProperty(attr) && (!prefix || attr.lastIndexOf(prefix, 0) == 0)) 99 | result.push(attr); 100 | } 101 | } 102 | return { 103 | list: result, 104 | from: replaceToken ? Pos(cur.line, tagStart == null ? token.start : tagStart) : cur, 105 | to: replaceToken ? Pos(cur.line, token.end) : cur 106 | }; 107 | } 108 | 109 | CodeMirror.registerHelper("hint", "xml", getHints); 110 | }); 111 | -------------------------------------------------------------------------------- /static/js/vendor/syntaxmodes/htmlmixed.js: -------------------------------------------------------------------------------- 1 | // CodeMirror, copyright (c) by Marijn Haverbeke and others 2 | // Distributed under an MIT license: http://codemirror.net/LICENSE 3 | 4 | (function(mod) { 5 | if (typeof exports == "object" && typeof module == "object") // CommonJS 6 | mod(require("../../lib/codemirror"), require("../xml/xml"), require("../javascript/javascript"), require("../css/css")); 7 | else if (typeof define == "function" && define.amd) // AMD 8 | define(["../../lib/codemirror", "../xml/xml", "../javascript/javascript", "../css/css"], mod); 9 | else // Plain browser env 10 | mod(CodeMirror); 11 | })(function(CodeMirror) { 12 | "use strict"; 13 | 14 | var defaultTags = { 15 | script: [ 16 | ["lang", /(javascript|babel)/i, "javascript"], 17 | ["type", /^(?:text|application)\/(?:x-)?(?:java|ecma)script$|^$/i, "javascript"], 18 | ["type", /./, "text/plain"], 19 | [null, null, "javascript"] 20 | ], 21 | style: [ 22 | ["lang", /^css$/i, "css"], 23 | ["type", /^(text\/)?(x-)?(stylesheet|css)$/i, "css"], 24 | ["type", /./, "text/plain"], 25 | [null, null, "css"] 26 | ] 27 | }; 28 | 29 | function maybeBackup(stream, pat, style) { 30 | var cur = stream.current(), close = cur.search(pat); 31 | if (close > -1) { 32 | stream.backUp(cur.length - close); 33 | } else if (cur.match(/<\/?$/)) { 34 | stream.backUp(cur.length); 35 | if (!stream.match(pat, false)) stream.match(cur); 36 | } 37 | return style; 38 | } 39 | 40 | var attrRegexpCache = {}; 41 | function getAttrRegexp(attr) { 42 | var regexp = attrRegexpCache[attr]; 43 | if (regexp) return regexp; 44 | return attrRegexpCache[attr] = new RegExp("\\s+" + attr + "\\s*=\\s*('|\")?([^'\"]+)('|\")?\\s*"); 45 | } 46 | 47 | function getAttrValue(text, attr) { 48 | var match = text.match(getAttrRegexp(attr)) 49 | return match ? /^\s*(.*?)\s*$/.exec(match[2])[1] : "" 50 | } 51 | 52 | function getTagRegexp(tagName, anchored) { 53 | return new RegExp((anchored ? "^" : "") + "<\/\s*" + tagName + "\s*>", "i"); 54 | } 55 | 56 | function addTags(from, to) { 57 | for (var tag in from) { 58 | var dest = to[tag] || (to[tag] = []); 59 | var source = from[tag]; 60 | for (var i = source.length - 1; i >= 0; i--) 61 | dest.unshift(source[i]) 62 | } 63 | } 64 | 65 | function findMatchingMode(tagInfo, tagText) { 66 | for (var i = 0; i < tagInfo.length; i++) { 67 | var spec = tagInfo[i]; 68 | if (!spec[0] || spec[1].test(getAttrValue(tagText, spec[0]))) return spec[2]; 69 | } 70 | } 71 | 72 | CodeMirror.defineMode("htmlmixed", function (config, parserConfig) { 73 | var htmlMode = CodeMirror.getMode(config, { 74 | name: "xml", 75 | htmlMode: true, 76 | multilineTagIndentFactor: parserConfig.multilineTagIndentFactor, 77 | multilineTagIndentPastTag: parserConfig.multilineTagIndentPastTag 78 | }); 79 | 80 | var tags = {}; 81 | var configTags = parserConfig && parserConfig.tags, configScript = parserConfig && parserConfig.scriptTypes; 82 | addTags(defaultTags, tags); 83 | if (configTags) addTags(configTags, tags); 84 | if (configScript) for (var i = configScript.length - 1; i >= 0; i--) 85 | tags.script.unshift(["type", configScript[i].matches, configScript[i].mode]) 86 | 87 | function html(stream, state) { 88 | var style = htmlMode.token(stream, state.htmlState), tag = /\btag\b/.test(style), tagName 89 | if (tag && !/[<>\s\/]/.test(stream.current()) && 90 | (tagName = state.htmlState.tagName && state.htmlState.tagName.toLowerCase()) && 91 | tags.hasOwnProperty(tagName)) { 92 | state.inTag = tagName + " " 93 | } else if (state.inTag && tag && />$/.test(stream.current())) { 94 | var inTag = /^([\S]+) (.*)/.exec(state.inTag) 95 | state.inTag = null 96 | var modeSpec = stream.current() == ">" && findMatchingMode(tags[inTag[1]], inTag[2]) 97 | var mode = CodeMirror.getMode(config, modeSpec) 98 | var endTagA = getTagRegexp(inTag[1], true), endTag = getTagRegexp(inTag[1], false); 99 | state.token = function (stream, state) { 100 | if (stream.match(endTagA, false)) { 101 | state.token = html; 102 | state.localState = state.localMode = null; 103 | return null; 104 | } 105 | return maybeBackup(stream, endTag, state.localMode.token(stream, state.localState)); 106 | }; 107 | state.localMode = mode; 108 | state.localState = CodeMirror.startState(mode, htmlMode.indent(state.htmlState, "")); 109 | } else if (state.inTag) { 110 | state.inTag += stream.current() 111 | if (stream.eol()) state.inTag += " " 112 | } 113 | return style; 114 | }; 115 | 116 | return { 117 | startState: function () { 118 | var state = CodeMirror.startState(htmlMode); 119 | return {token: html, inTag: null, localMode: null, localState: null, htmlState: state}; 120 | }, 121 | 122 | copyState: function (state) { 123 | var local; 124 | if (state.localState) { 125 | local = CodeMirror.copyState(state.localMode, state.localState); 126 | } 127 | return {token: state.token, inTag: state.inTag, 128 | localMode: state.localMode, localState: local, 129 | htmlState: CodeMirror.copyState(htmlMode, state.htmlState)}; 130 | }, 131 | 132 | token: function (stream, state) { 133 | return state.token(stream, state); 134 | }, 135 | 136 | indent: function (state, textAfter) { 137 | if (!state.localMode || /^\s*<\//.test(textAfter)) 138 | return htmlMode.indent(state.htmlState, textAfter); 139 | else if (state.localMode.indent) 140 | return state.localMode.indent(state.localState, textAfter); 141 | else 142 | return CodeMirror.Pass; 143 | }, 144 | 145 | innerMode: function (state) { 146 | return {state: state.localState || state.htmlState, mode: state.localMode || htmlMode}; 147 | } 148 | }; 149 | }, "xml", "javascript", "css"); 150 | 151 | CodeMirror.defineMIME("text/html", "htmlmixed"); 152 | }); 153 | -------------------------------------------------------------------------------- /static/js/vendor/syntaxmodes/php.js: -------------------------------------------------------------------------------- 1 | // CodeMirror, copyright (c) by Marijn Haverbeke and others 2 | // Distributed under an MIT license: http://codemirror.net/LICENSE 3 | 4 | (function(mod) { 5 | if (typeof exports == "object" && typeof module == "object") // CommonJS 6 | mod(require("../../lib/codemirror"), require("../htmlmixed/htmlmixed"), require("../clike/clike")); 7 | else if (typeof define == "function" && define.amd) // AMD 8 | define(["../../lib/codemirror", "../htmlmixed/htmlmixed", "../clike/clike"], mod); 9 | else // Plain browser env 10 | mod(CodeMirror); 11 | })(function(CodeMirror) { 12 | "use strict"; 13 | 14 | function keywords(str) { 15 | var obj = {}, words = str.split(" "); 16 | for (var i = 0; i < words.length; ++i) obj[words[i]] = true; 17 | return obj; 18 | } 19 | 20 | // Helper for phpString 21 | function matchSequence(list, end, escapes) { 22 | if (list.length == 0) return phpString(end); 23 | return function (stream, state) { 24 | var patterns = list[0]; 25 | for (var i = 0; i < patterns.length; i++) if (stream.match(patterns[i][0])) { 26 | state.tokenize = matchSequence(list.slice(1), end); 27 | return patterns[i][1]; 28 | } 29 | state.tokenize = phpString(end, escapes); 30 | return "string"; 31 | }; 32 | } 33 | function phpString(closing, escapes) { 34 | return function(stream, state) { return phpString_(stream, state, closing, escapes); }; 35 | } 36 | function phpString_(stream, state, closing, escapes) { 37 | // "Complex" syntax 38 | if (escapes !== false && stream.match("${", false) || stream.match("{$", false)) { 39 | state.tokenize = null; 40 | return "string"; 41 | } 42 | 43 | // Simple syntax 44 | if (escapes !== false && stream.match(/^\$[a-zA-Z_][a-zA-Z0-9_]*/)) { 45 | // After the variable name there may appear array or object operator. 46 | if (stream.match("[", false)) { 47 | // Match array operator 48 | state.tokenize = matchSequence([ 49 | [["[", null]], 50 | [[/\d[\w\.]*/, "number"], 51 | [/\$[a-zA-Z_][a-zA-Z0-9_]*/, "variable-2"], 52 | [/[\w\$]+/, "variable"]], 53 | [["]", null]] 54 | ], closing, escapes); 55 | } 56 | if (stream.match(/\-\>\w/, false)) { 57 | // Match object operator 58 | state.tokenize = matchSequence([ 59 | [["->", null]], 60 | [[/[\w]+/, "variable"]] 61 | ], closing, escapes); 62 | } 63 | return "variable-2"; 64 | } 65 | 66 | var escaped = false; 67 | // Normal string 68 | while (!stream.eol() && 69 | (escaped || escapes === false || 70 | (!stream.match("{$", false) && 71 | !stream.match(/^(\$[a-zA-Z_][a-zA-Z0-9_]*|\$\{)/, false)))) { 72 | if (!escaped && stream.match(closing)) { 73 | state.tokenize = null; 74 | state.tokStack.pop(); state.tokStack.pop(); 75 | break; 76 | } 77 | escaped = stream.next() == "\\" && !escaped; 78 | } 79 | return "string"; 80 | } 81 | 82 | var phpKeywords = "abstract and array as break case catch class clone const continue declare default " + 83 | "do else elseif enddeclare endfor endforeach endif endswitch endwhile extends final " + 84 | "for foreach function global goto if implements interface instanceof namespace " + 85 | "new or private protected public static switch throw trait try use var while xor " + 86 | "die echo empty exit eval include include_once isset list require require_once return " + 87 | "print unset __halt_compiler self static parent yield insteadof finally"; 88 | var phpAtoms = "true false null TRUE FALSE NULL __CLASS__ __DIR__ __FILE__ __LINE__ __METHOD__ __FUNCTION__ __NAMESPACE__ __TRAIT__"; 89 | var phpBuiltin = "func_num_args func_get_arg func_get_args strlen strcmp strncmp strcasecmp strncasecmp each error_reporting define defined trigger_error user_error set_error_handler restore_error_handler get_declared_classes get_loaded_extensions extension_loaded get_extension_funcs debug_backtrace constant bin2hex hex2bin sleep usleep time mktime gmmktime strftime gmstrftime strtotime date gmdate getdate localtime checkdate flush wordwrap htmlspecialchars htmlentities html_entity_decode md5 md5_file crc32 getimagesize image_type_to_mime_type phpinfo phpversion phpcredits strnatcmp strnatcasecmp substr_count strspn strcspn strtok strtoupper strtolower strpos strrpos strrev hebrev hebrevc nl2br basename dirname pathinfo stripslashes stripcslashes strstr stristr strrchr str_shuffle str_word_count strcoll substr substr_replace quotemeta ucfirst ucwords strtr addslashes addcslashes rtrim str_replace str_repeat count_chars chunk_split trim ltrim strip_tags similar_text explode implode setlocale localeconv parse_str str_pad chop strchr sprintf printf vprintf vsprintf sscanf fscanf parse_url urlencode urldecode rawurlencode rawurldecode readlink linkinfo link unlink exec system escapeshellcmd escapeshellarg passthru shell_exec proc_open proc_close rand srand getrandmax mt_rand mt_srand mt_getrandmax base64_decode base64_encode abs ceil floor round is_finite is_nan is_infinite bindec hexdec octdec decbin decoct dechex base_convert number_format fmod ip2long long2ip getenv putenv getopt microtime gettimeofday getrusage uniqid quoted_printable_decode set_time_limit get_cfg_var magic_quotes_runtime set_magic_quotes_runtime get_magic_quotes_gpc get_magic_quotes_runtime import_request_variables error_log serialize unserialize memory_get_usage var_dump var_export debug_zval_dump print_r highlight_file show_source highlight_string ini_get ini_get_all ini_set ini_alter ini_restore get_include_path set_include_path restore_include_path setcookie header headers_sent connection_aborted connection_status ignore_user_abort parse_ini_file is_uploaded_file move_uploaded_file intval floatval doubleval strval gettype settype is_null is_resource is_bool is_long is_float is_int is_integer is_double is_real is_numeric is_string is_array is_object is_scalar ereg ereg_replace eregi eregi_replace split spliti join sql_regcase dl pclose popen readfile rewind rmdir umask fclose feof fgetc fgets fgetss fread fopen fpassthru ftruncate fstat fseek ftell fflush fwrite fputs mkdir rename copy tempnam tmpfile file file_get_contents file_put_contents stream_select stream_context_create stream_context_set_params stream_context_set_option stream_context_get_options stream_filter_prepend stream_filter_append fgetcsv flock get_meta_tags stream_set_write_buffer set_file_buffer set_socket_blocking stream_set_blocking socket_set_blocking stream_get_meta_data stream_register_wrapper stream_wrapper_register stream_set_timeout socket_set_timeout socket_get_status realpath fnmatch fsockopen pfsockopen pack unpack get_browser crypt opendir closedir chdir getcwd rewinddir readdir dir glob fileatime filectime filegroup fileinode filemtime fileowner fileperms filesize filetype file_exists is_writable is_writeable is_readable is_executable is_file is_dir is_link stat lstat chown touch clearstatcache mail ob_start ob_flush ob_clean ob_end_flush ob_end_clean ob_get_flush ob_get_clean ob_get_length ob_get_level ob_get_status ob_get_contents ob_implicit_flush ob_list_handlers ksort krsort natsort natcasesort asort arsort sort rsort usort uasort uksort shuffle array_walk count end prev next reset current key min max in_array array_search extract compact array_fill range array_multisort array_push array_pop array_shift array_unshift array_splice array_slice array_merge array_merge_recursive array_keys array_values array_count_values array_reverse array_reduce array_pad array_flip array_change_key_case array_rand array_unique array_intersect array_intersect_assoc array_diff array_diff_assoc array_sum array_filter array_map array_chunk array_key_exists array_intersect_key array_combine array_column pos sizeof key_exists assert assert_options version_compare ftok str_rot13 aggregate session_name session_module_name session_save_path session_id session_regenerate_id session_decode session_register session_unregister session_is_registered session_encode session_start session_destroy session_unset session_set_save_handler session_cache_limiter session_cache_expire session_set_cookie_params session_get_cookie_params session_write_close preg_match preg_match_all preg_replace preg_replace_callback preg_split preg_quote preg_grep overload ctype_alnum ctype_alpha ctype_cntrl ctype_digit ctype_lower ctype_graph ctype_print ctype_punct ctype_space ctype_upper ctype_xdigit virtual apache_request_headers apache_note apache_lookup_uri apache_child_terminate apache_setenv apache_response_headers apache_get_version getallheaders mysql_connect mysql_pconnect mysql_close mysql_select_db mysql_create_db mysql_drop_db mysql_query mysql_unbuffered_query mysql_db_query mysql_list_dbs mysql_list_tables mysql_list_fields mysql_list_processes mysql_error mysql_errno mysql_affected_rows mysql_insert_id mysql_result mysql_num_rows mysql_num_fields mysql_fetch_row mysql_fetch_array mysql_fetch_assoc mysql_fetch_object mysql_data_seek mysql_fetch_lengths mysql_fetch_field mysql_field_seek mysql_free_result mysql_field_name mysql_field_table mysql_field_len mysql_field_type mysql_field_flags mysql_escape_string mysql_real_escape_string mysql_stat mysql_thread_id mysql_client_encoding mysql_get_client_info mysql_get_host_info mysql_get_proto_info mysql_get_server_info mysql_info mysql mysql_fieldname mysql_fieldtable mysql_fieldlen mysql_fieldtype mysql_fieldflags mysql_selectdb mysql_createdb mysql_dropdb mysql_freeresult mysql_numfields mysql_numrows mysql_listdbs mysql_listtables mysql_listfields mysql_db_name mysql_dbname mysql_tablename mysql_table_name pg_connect pg_pconnect pg_close pg_connection_status pg_connection_busy pg_connection_reset pg_host pg_dbname pg_port pg_tty pg_options pg_ping pg_query pg_send_query pg_cancel_query pg_fetch_result pg_fetch_row pg_fetch_assoc pg_fetch_array pg_fetch_object pg_fetch_all pg_affected_rows pg_get_result pg_result_seek pg_result_status pg_free_result pg_last_oid pg_num_rows pg_num_fields pg_field_name pg_field_num pg_field_size pg_field_type pg_field_prtlen pg_field_is_null pg_get_notify pg_get_pid pg_result_error pg_last_error pg_last_notice pg_put_line pg_end_copy pg_copy_to pg_copy_from pg_trace pg_untrace pg_lo_create pg_lo_unlink pg_lo_open pg_lo_close pg_lo_read pg_lo_write pg_lo_read_all pg_lo_import pg_lo_export pg_lo_seek pg_lo_tell pg_escape_string pg_escape_bytea pg_unescape_bytea pg_client_encoding pg_set_client_encoding pg_meta_data pg_convert pg_insert pg_update pg_delete pg_select pg_exec pg_getlastoid pg_cmdtuples pg_errormessage pg_numrows pg_numfields pg_fieldname pg_fieldsize pg_fieldtype pg_fieldnum pg_fieldprtlen pg_fieldisnull pg_freeresult pg_result pg_loreadall pg_locreate pg_lounlink pg_loopen pg_loclose pg_loread pg_lowrite pg_loimport pg_loexport http_response_code get_declared_traits getimagesizefromstring socket_import_stream stream_set_chunk_size trait_exists header_register_callback class_uses session_status session_register_shutdown echo print global static exit array empty eval isset unset die include require include_once require_once json_decode json_encode json_last_error json_last_error_msg curl_close curl_copy_handle curl_errno curl_error curl_escape curl_exec curl_file_create curl_getinfo curl_init curl_multi_add_handle curl_multi_close curl_multi_exec curl_multi_getcontent curl_multi_info_read curl_multi_init curl_multi_remove_handle curl_multi_select curl_multi_setopt curl_multi_strerror curl_pause curl_reset curl_setopt_array curl_setopt curl_share_close curl_share_init curl_share_setopt curl_strerror curl_unescape curl_version mysqli_affected_rows mysqli_autocommit mysqli_change_user mysqli_character_set_name mysqli_close mysqli_commit mysqli_connect_errno mysqli_connect_error mysqli_connect mysqli_data_seek mysqli_debug mysqli_dump_debug_info mysqli_errno mysqli_error_list mysqli_error mysqli_fetch_all mysqli_fetch_array mysqli_fetch_assoc mysqli_fetch_field_direct mysqli_fetch_field mysqli_fetch_fields mysqli_fetch_lengths mysqli_fetch_object mysqli_fetch_row mysqli_field_count mysqli_field_seek mysqli_field_tell mysqli_free_result mysqli_get_charset mysqli_get_client_info mysqli_get_client_stats mysqli_get_client_version mysqli_get_connection_stats mysqli_get_host_info mysqli_get_proto_info mysqli_get_server_info mysqli_get_server_version mysqli_info mysqli_init mysqli_insert_id mysqli_kill mysqli_more_results mysqli_multi_query mysqli_next_result mysqli_num_fields mysqli_num_rows mysqli_options mysqli_ping mysqli_prepare mysqli_query mysqli_real_connect mysqli_real_escape_string mysqli_real_query mysqli_reap_async_query mysqli_refresh mysqli_rollback mysqli_select_db mysqli_set_charset mysqli_set_local_infile_default mysqli_set_local_infile_handler mysqli_sqlstate mysqli_ssl_set mysqli_stat mysqli_stmt_init mysqli_store_result mysqli_thread_id mysqli_thread_safe mysqli_use_result mysqli_warning_count"; 90 | CodeMirror.registerHelper("hintWords", "php", [phpKeywords, phpAtoms, phpBuiltin].join(" ").split(" ")); 91 | CodeMirror.registerHelper("wordChars", "php", /[\w$]/); 92 | 93 | var phpConfig = { 94 | name: "clike", 95 | helperType: "php", 96 | keywords: keywords(phpKeywords), 97 | blockKeywords: keywords("catch do else elseif for foreach if switch try while finally"), 98 | defKeywords: keywords("class function interface namespace trait"), 99 | atoms: keywords(phpAtoms), 100 | builtin: keywords(phpBuiltin), 101 | multiLineStrings: true, 102 | hooks: { 103 | "$": function(stream) { 104 | stream.eatWhile(/[\w\$_]/); 105 | return "variable-2"; 106 | }, 107 | "<": function(stream, state) { 108 | var before; 109 | if (before = stream.match(/<<\s*/)) { 110 | var quoted = stream.eat(/['"]/); 111 | stream.eatWhile(/[\w\.]/); 112 | var delim = stream.current().slice(before[0].length + (quoted ? 2 : 1)); 113 | if (quoted) stream.eat(quoted); 114 | if (delim) { 115 | (state.tokStack || (state.tokStack = [])).push(delim, 0); 116 | state.tokenize = phpString(delim, quoted != "'"); 117 | return "string"; 118 | } 119 | } 120 | return false; 121 | }, 122 | "#": function(stream) { 123 | while (!stream.eol() && !stream.match("?>", false)) stream.next(); 124 | return "comment"; 125 | }, 126 | "/": function(stream) { 127 | if (stream.eat("/")) { 128 | while (!stream.eol() && !stream.match("?>", false)) stream.next(); 129 | return "comment"; 130 | } 131 | return false; 132 | }, 133 | '"': function(_stream, state) { 134 | (state.tokStack || (state.tokStack = [])).push('"', 0); 135 | state.tokenize = phpString('"'); 136 | return "string"; 137 | }, 138 | "{": function(_stream, state) { 139 | if (state.tokStack && state.tokStack.length) 140 | state.tokStack[state.tokStack.length - 1]++; 141 | return false; 142 | }, 143 | "}": function(_stream, state) { 144 | if (state.tokStack && state.tokStack.length > 0 && 145 | !--state.tokStack[state.tokStack.length - 1]) { 146 | state.tokenize = phpString(state.tokStack[state.tokStack.length - 2]); 147 | } 148 | return false; 149 | } 150 | } 151 | }; 152 | 153 | CodeMirror.defineMode("php", function(config, parserConfig) { 154 | var htmlMode = CodeMirror.getMode(config, "text/html"); 155 | var phpMode = CodeMirror.getMode(config, phpConfig); 156 | 157 | function dispatch(stream, state) { 158 | var isPHP = state.curMode == phpMode; 159 | if (stream.sol() && state.pending && state.pending != '"' && state.pending != "'") state.pending = null; 160 | if (!isPHP) { 161 | if (stream.match(/^<\?\w*/)) { 162 | state.curMode = phpMode; 163 | if (!state.php) state.php = CodeMirror.startState(phpMode, htmlMode.indent(state.html, "")) 164 | state.curState = state.php; 165 | return "meta"; 166 | } 167 | if (state.pending == '"' || state.pending == "'") { 168 | while (!stream.eol() && stream.next() != state.pending) {} 169 | var style = "string"; 170 | } else if (state.pending && stream.pos < state.pending.end) { 171 | stream.pos = state.pending.end; 172 | var style = state.pending.style; 173 | } else { 174 | var style = htmlMode.token(stream, state.curState); 175 | } 176 | if (state.pending) state.pending = null; 177 | var cur = stream.current(), openPHP = cur.search(/<\?/), m; 178 | if (openPHP != -1) { 179 | if (style == "string" && (m = cur.match(/[\'\"]$/)) && !/\?>/.test(cur)) state.pending = m[0]; 180 | else state.pending = {end: stream.pos, style: style}; 181 | stream.backUp(cur.length - openPHP); 182 | } 183 | return style; 184 | } else if (isPHP && state.php.tokenize == null && stream.match("?>")) { 185 | state.curMode = htmlMode; 186 | state.curState = state.html; 187 | if (!state.php.context || !state.php.context.prev) state.php = null; 188 | return "meta"; 189 | } else { 190 | return phpMode.token(stream, state.curState); 191 | } 192 | } 193 | 194 | return { 195 | startState: function() { 196 | var html = CodeMirror.startState(htmlMode) 197 | var php = parserConfig.startOpen ? CodeMirror.startState(phpMode) : null 198 | return {html: html, 199 | php: php, 200 | curMode: parserConfig.startOpen ? phpMode : htmlMode, 201 | curState: parserConfig.startOpen ? php : html, 202 | pending: null}; 203 | }, 204 | 205 | copyState: function(state) { 206 | var html = state.html, htmlNew = CodeMirror.copyState(htmlMode, html), 207 | php = state.php, phpNew = php && CodeMirror.copyState(phpMode, php), cur; 208 | if (state.curMode == htmlMode) cur = htmlNew; 209 | else cur = phpNew; 210 | return {html: htmlNew, php: phpNew, curMode: state.curMode, curState: cur, 211 | pending: state.pending}; 212 | }, 213 | 214 | token: dispatch, 215 | 216 | indent: function(state, textAfter) { 217 | if ((state.curMode != phpMode && /^\s*<\//.test(textAfter)) || 218 | (state.curMode == phpMode && /^\?>/.test(textAfter))) 219 | return htmlMode.indent(state.html, textAfter); 220 | return state.curMode.indent(state.curState, textAfter); 221 | }, 222 | 223 | blockCommentStart: "/*", 224 | blockCommentEnd: "*/", 225 | lineComment: "//", 226 | 227 | innerMode: function(state) { return {state: state.curState, mode: state.curMode}; } 228 | }; 229 | }, "htmlmixed", "clike"); 230 | 231 | CodeMirror.defineMIME("application/x-httpd-php", "php"); 232 | CodeMirror.defineMIME("application/x-httpd-php-open", {name: "php", startOpen: true}); 233 | CodeMirror.defineMIME("text/x-php", phpConfig); 234 | }); 235 | -------------------------------------------------------------------------------- /static/js/vendor/syntaxmodes/xml.js: -------------------------------------------------------------------------------- 1 | // CodeMirror, copyright (c) by Marijn Haverbeke and others 2 | // Distributed under an MIT license: http://codemirror.net/LICENSE 3 | 4 | (function(mod) { 5 | if (typeof exports == "object" && typeof module == "object") // CommonJS 6 | mod(require("../../lib/codemirror")); 7 | else if (typeof define == "function" && define.amd) // AMD 8 | define(["../../lib/codemirror"], mod); 9 | else // Plain browser env 10 | mod(CodeMirror); 11 | })(function(CodeMirror) { 12 | "use strict"; 13 | 14 | var htmlConfig = { 15 | autoSelfClosers: {'area': true, 'base': true, 'br': true, 'col': true, 'command': true, 16 | 'embed': true, 'frame': true, 'hr': true, 'img': true, 'input': true, 17 | 'keygen': true, 'link': true, 'meta': true, 'param': true, 'source': true, 18 | 'track': true, 'wbr': true, 'menuitem': true}, 19 | implicitlyClosed: {'dd': true, 'li': true, 'optgroup': true, 'option': true, 'p': true, 20 | 'rp': true, 'rt': true, 'tbody': true, 'td': true, 'tfoot': true, 21 | 'th': true, 'tr': true}, 22 | contextGrabbers: { 23 | 'dd': {'dd': true, 'dt': true}, 24 | 'dt': {'dd': true, 'dt': true}, 25 | 'li': {'li': true}, 26 | 'option': {'option': true, 'optgroup': true}, 27 | 'optgroup': {'optgroup': true}, 28 | 'p': {'address': true, 'article': true, 'aside': true, 'blockquote': true, 'dir': true, 29 | 'div': true, 'dl': true, 'fieldset': true, 'footer': true, 'form': true, 30 | 'h1': true, 'h2': true, 'h3': true, 'h4': true, 'h5': true, 'h6': true, 31 | 'header': true, 'hgroup': true, 'hr': true, 'menu': true, 'nav': true, 'ol': true, 32 | 'p': true, 'pre': true, 'section': true, 'table': true, 'ul': true}, 33 | 'rp': {'rp': true, 'rt': true}, 34 | 'rt': {'rp': true, 'rt': true}, 35 | 'tbody': {'tbody': true, 'tfoot': true}, 36 | 'td': {'td': true, 'th': true}, 37 | 'tfoot': {'tbody': true}, 38 | 'th': {'td': true, 'th': true}, 39 | 'thead': {'tbody': true, 'tfoot': true}, 40 | 'tr': {'tr': true} 41 | }, 42 | doNotIndent: {"pre": true}, 43 | allowUnquoted: true, 44 | allowMissing: true, 45 | caseFold: true 46 | } 47 | 48 | var xmlConfig = { 49 | autoSelfClosers: {}, 50 | implicitlyClosed: {}, 51 | contextGrabbers: {}, 52 | doNotIndent: {}, 53 | allowUnquoted: false, 54 | allowMissing: false, 55 | caseFold: false 56 | } 57 | 58 | CodeMirror.defineMode("xml", function(editorConf, config_) { 59 | var indentUnit = editorConf.indentUnit 60 | var config = {} 61 | var defaults = config_.htmlMode ? htmlConfig : xmlConfig 62 | for (var prop in defaults) config[prop] = defaults[prop] 63 | for (var prop in config_) config[prop] = config_[prop] 64 | 65 | // Return variables for tokenizers 66 | var type, setStyle; 67 | 68 | function inText(stream, state) { 69 | function chain(parser) { 70 | state.tokenize = parser; 71 | return parser(stream, state); 72 | } 73 | 74 | var ch = stream.next(); 75 | if (ch == "<") { 76 | if (stream.eat("!")) { 77 | if (stream.eat("[")) { 78 | if (stream.match("CDATA[")) return chain(inBlock("atom", "]]>")); 79 | else return null; 80 | } else if (stream.match("--")) { 81 | return chain(inBlock("comment", "-->")); 82 | } else if (stream.match("DOCTYPE", true, true)) { 83 | stream.eatWhile(/[\w\._\-]/); 84 | return chain(doctype(1)); 85 | } else { 86 | return null; 87 | } 88 | } else if (stream.eat("?")) { 89 | stream.eatWhile(/[\w\._\-]/); 90 | state.tokenize = inBlock("meta", "?>"); 91 | return "meta"; 92 | } else { 93 | type = stream.eat("/") ? "closeTag" : "openTag"; 94 | state.tokenize = inTag; 95 | return "tag bracket"; 96 | } 97 | } else if (ch == "&") { 98 | var ok; 99 | if (stream.eat("#")) { 100 | if (stream.eat("x")) { 101 | ok = stream.eatWhile(/[a-fA-F\d]/) && stream.eat(";"); 102 | } else { 103 | ok = stream.eatWhile(/[\d]/) && stream.eat(";"); 104 | } 105 | } else { 106 | ok = stream.eatWhile(/[\w\.\-:]/) && stream.eat(";"); 107 | } 108 | return ok ? "atom" : "error"; 109 | } else { 110 | stream.eatWhile(/[^&<]/); 111 | return null; 112 | } 113 | } 114 | inText.isInText = true; 115 | 116 | function inTag(stream, state) { 117 | var ch = stream.next(); 118 | if (ch == ">" || (ch == "/" && stream.eat(">"))) { 119 | state.tokenize = inText; 120 | type = ch == ">" ? "endTag" : "selfcloseTag"; 121 | return "tag bracket"; 122 | } else if (ch == "=") { 123 | type = "equals"; 124 | return null; 125 | } else if (ch == "<") { 126 | state.tokenize = inText; 127 | state.state = baseState; 128 | state.tagName = state.tagStart = null; 129 | var next = state.tokenize(stream, state); 130 | return next ? next + " tag error" : "tag error"; 131 | } else if (/[\'\"]/.test(ch)) { 132 | state.tokenize = inAttribute(ch); 133 | state.stringStartCol = stream.column(); 134 | return state.tokenize(stream, state); 135 | } else { 136 | stream.match(/^[^\s\u00a0=<>\"\']*[^\s\u00a0=<>\"\'\/]/); 137 | return "word"; 138 | } 139 | } 140 | 141 | function inAttribute(quote) { 142 | var closure = function(stream, state) { 143 | while (!stream.eol()) { 144 | if (stream.next() == quote) { 145 | state.tokenize = inTag; 146 | break; 147 | } 148 | } 149 | return "string"; 150 | }; 151 | closure.isInAttribute = true; 152 | return closure; 153 | } 154 | 155 | function inBlock(style, terminator) { 156 | return function(stream, state) { 157 | while (!stream.eol()) { 158 | if (stream.match(terminator)) { 159 | state.tokenize = inText; 160 | break; 161 | } 162 | stream.next(); 163 | } 164 | return style; 165 | }; 166 | } 167 | function doctype(depth) { 168 | return function(stream, state) { 169 | var ch; 170 | while ((ch = stream.next()) != null) { 171 | if (ch == "<") { 172 | state.tokenize = doctype(depth + 1); 173 | return state.tokenize(stream, state); 174 | } else if (ch == ">") { 175 | if (depth == 1) { 176 | state.tokenize = inText; 177 | break; 178 | } else { 179 | state.tokenize = doctype(depth - 1); 180 | return state.tokenize(stream, state); 181 | } 182 | } 183 | } 184 | return "meta"; 185 | }; 186 | } 187 | 188 | function Context(state, tagName, startOfLine) { 189 | this.prev = state.context; 190 | this.tagName = tagName; 191 | this.indent = state.indented; 192 | this.startOfLine = startOfLine; 193 | if (config.doNotIndent.hasOwnProperty(tagName) || (state.context && state.context.noIndent)) 194 | this.noIndent = true; 195 | } 196 | function popContext(state) { 197 | if (state.context) state.context = state.context.prev; 198 | } 199 | function maybePopContext(state, nextTagName) { 200 | var parentTagName; 201 | while (true) { 202 | if (!state.context) { 203 | return; 204 | } 205 | parentTagName = state.context.tagName; 206 | if (!config.contextGrabbers.hasOwnProperty(parentTagName) || 207 | !config.contextGrabbers[parentTagName].hasOwnProperty(nextTagName)) { 208 | return; 209 | } 210 | popContext(state); 211 | } 212 | } 213 | 214 | function baseState(type, stream, state) { 215 | if (type == "openTag") { 216 | state.tagStart = stream.column(); 217 | return tagNameState; 218 | } else if (type == "closeTag") { 219 | return closeTagNameState; 220 | } else { 221 | return baseState; 222 | } 223 | } 224 | function tagNameState(type, stream, state) { 225 | if (type == "word") { 226 | state.tagName = stream.current(); 227 | setStyle = "tag"; 228 | return attrState; 229 | } else { 230 | setStyle = "error"; 231 | return tagNameState; 232 | } 233 | } 234 | function closeTagNameState(type, stream, state) { 235 | if (type == "word") { 236 | var tagName = stream.current(); 237 | if (state.context && state.context.tagName != tagName && 238 | config.implicitlyClosed.hasOwnProperty(state.context.tagName)) 239 | popContext(state); 240 | if ((state.context && state.context.tagName == tagName) || config.matchClosing === false) { 241 | setStyle = "tag"; 242 | return closeState; 243 | } else { 244 | setStyle = "tag error"; 245 | return closeStateErr; 246 | } 247 | } else { 248 | setStyle = "error"; 249 | return closeStateErr; 250 | } 251 | } 252 | 253 | function closeState(type, _stream, state) { 254 | if (type != "endTag") { 255 | setStyle = "error"; 256 | return closeState; 257 | } 258 | popContext(state); 259 | return baseState; 260 | } 261 | function closeStateErr(type, stream, state) { 262 | setStyle = "error"; 263 | return closeState(type, stream, state); 264 | } 265 | 266 | function attrState(type, _stream, state) { 267 | if (type == "word") { 268 | setStyle = "attribute"; 269 | return attrEqState; 270 | } else if (type == "endTag" || type == "selfcloseTag") { 271 | var tagName = state.tagName, tagStart = state.tagStart; 272 | state.tagName = state.tagStart = null; 273 | if (type == "selfcloseTag" || 274 | config.autoSelfClosers.hasOwnProperty(tagName)) { 275 | maybePopContext(state, tagName); 276 | } else { 277 | maybePopContext(state, tagName); 278 | state.context = new Context(state, tagName, tagStart == state.indented); 279 | } 280 | return baseState; 281 | } 282 | setStyle = "error"; 283 | return attrState; 284 | } 285 | function attrEqState(type, stream, state) { 286 | if (type == "equals") return attrValueState; 287 | if (!config.allowMissing) setStyle = "error"; 288 | return attrState(type, stream, state); 289 | } 290 | function attrValueState(type, stream, state) { 291 | if (type == "string") return attrContinuedState; 292 | if (type == "word" && config.allowUnquoted) {setStyle = "string"; return attrState;} 293 | setStyle = "error"; 294 | return attrState(type, stream, state); 295 | } 296 | function attrContinuedState(type, stream, state) { 297 | if (type == "string") return attrContinuedState; 298 | return attrState(type, stream, state); 299 | } 300 | 301 | return { 302 | startState: function(baseIndent) { 303 | var state = {tokenize: inText, 304 | state: baseState, 305 | indented: baseIndent || 0, 306 | tagName: null, tagStart: null, 307 | context: null} 308 | if (baseIndent != null) state.baseIndent = baseIndent 309 | return state 310 | }, 311 | 312 | token: function(stream, state) { 313 | if (!state.tagName && stream.sol()) 314 | state.indented = stream.indentation(); 315 | 316 | if (stream.eatSpace()) return null; 317 | type = null; 318 | var style = state.tokenize(stream, state); 319 | if ((style || type) && style != "comment") { 320 | setStyle = null; 321 | state.state = state.state(type || style, stream, state); 322 | if (setStyle) 323 | style = setStyle == "error" ? style + " error" : setStyle; 324 | } 325 | return style; 326 | }, 327 | 328 | indent: function(state, textAfter, fullLine) { 329 | var context = state.context; 330 | // Indent multi-line strings (e.g. css). 331 | if (state.tokenize.isInAttribute) { 332 | if (state.tagStart == state.indented) 333 | return state.stringStartCol + 1; 334 | else 335 | return state.indented + indentUnit; 336 | } 337 | if (context && context.noIndent) return CodeMirror.Pass; 338 | if (state.tokenize != inTag && state.tokenize != inText) 339 | return fullLine ? fullLine.match(/^(\s*)/)[0].length : 0; 340 | // Indent the starts of attribute names. 341 | if (state.tagName) { 342 | if (config.multilineTagIndentPastTag !== false) 343 | return state.tagStart + state.tagName.length + 2; 344 | else 345 | return state.tagStart + indentUnit * (config.multilineTagIndentFactor || 1); 346 | } 347 | if (config.alignCDATA && /$/, 376 | blockCommentStart: "", 378 | 379 | configuration: config.htmlMode ? "html" : "xml", 380 | helperType: config.htmlMode ? "html" : "xml", 381 | 382 | skipAttribute: function(state) { 383 | if (state.state == attrValueState) 384 | state.state = attrState 385 | } 386 | }; 387 | }); 388 | 389 | CodeMirror.defineMIME("text/xml", "xml"); 390 | CodeMirror.defineMIME("application/xml", "xml"); 391 | if (!CodeMirror.mimeModes.hasOwnProperty("text/html")) 392 | CodeMirror.defineMIME("text/html", {name: "xml", htmlMode: true}); 393 | 394 | }); 395 | --------------------------------------------------------------------------------