├── Makefile ├── Readme.md ├── config.json ├── package.json ├── public ├── addon │ └── simplescrollbars.js ├── index.html ├── lib │ ├── codemirror.css │ └── codemirror.js ├── mode │ └── javascript │ │ ├── index.html │ │ ├── javascript.js │ │ ├── json-ld.html │ │ ├── test.js │ │ └── typescript.html ├── search.svg ├── src │ ├── form.js │ ├── index.js │ ├── item.html │ └── routes.js └── style.css ├── server.js └── webpack.config.js /Makefile: -------------------------------------------------------------------------------- 1 | 2 | @build: 3 | @webpack -w -d 4 | 5 | .PHONY: build 6 | -------------------------------------------------------------------------------- /Readme.md: -------------------------------------------------------------------------------- 1 | # Mockapi 2 | 3 | Mockapi helps you create a mock api service with JSON response quickly, so you 4 | can test your client with this fake service. 5 | 6 | interface 7 | 8 | TODO: custom response status and headers 9 | 10 | Warning: only tested on current Chrome. 11 | 12 | ## Features 13 | 14 | * Create, remove, filter, delete and sort api 15 | * Import and export the api list 16 | * Shortcut, use `⌘ s` to save current api 17 | 18 | ## Install 19 | 20 | Make sure you have [nodejs](https://nodejs.org) and [redis](http://redis.io/) 21 | installed. 22 | 23 | git clone git@github.com:chemzqm/mockapi.git 24 | npm install && npm run build 25 | 26 | ## Usage 27 | 28 | Configure your redis and server port in `config.json` if needed, run command: 29 | 30 | ``` 31 | node server.js 32 | ``` 33 | 34 | Open your browser at `http://localhost:4000` (default port) 35 | 36 | You can make use of http://www.json-generator.com/ to generate the json data, 37 | and https://www.getpostman.com/ to check the http api. 38 | 39 | ## MIT license 40 | Copyright (c) 2016 chemzqm@gmail.com 41 | 42 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 43 | 44 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 45 | 46 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 47 | -------------------------------------------------------------------------------- /config.json: -------------------------------------------------------------------------------- 1 | { 2 | "redis": { 3 | "host": "127.0.0.1", 4 | "port": 6379 5 | }, 6 | "port": 4000 7 | } 8 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "api-service", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "build": "./node_modules/.bin/webpack" 8 | }, 9 | "author": "chemzqm@gmail.com", 10 | "license": "MIT", 11 | "dependencies": { 12 | "co": "^4.6.0", 13 | "co-body": "^4.0.0", 14 | "co-redis": "^2.1.0", 15 | "component-classes": "^1.2.5", 16 | "component-closest": "^1.0.0", 17 | "component-delegate": "^0.2.3", 18 | "component-emitter": "^1.2.0", 19 | "component-event": "^0.1.4", 20 | "component-file-picker": "^0.2.1", 21 | "component-notice": "0.0.15", 22 | "component-spin": "^0.1.1", 23 | "domify": "^1.4.0", 24 | "js-base64": "^2.1.9", 25 | "koa": "^1.2.0", 26 | "koa-cors": "0.0.16", 27 | "koa-liveload": "0.0.2", 28 | "koa-logger": "^1.3.1", 29 | "koa-router": "^5.4.0", 30 | "koa-static": "^2.0.0", 31 | "nprogress": "^0.2.0", 32 | "radio-component": "0.0.2", 33 | "redis": "^2.5.0", 34 | "request-component": "0.0.1", 35 | "sweet-sortable": "^0.3.2", 36 | "transitionend-property": "0.0.2" 37 | }, 38 | "browser": { 39 | "request": "request-component", 40 | "notice": "component-notice", 41 | "emitter": "component-emitter", 42 | "event": "component-event", 43 | "delegate": "component-delegate", 44 | "closest": "component-closest", 45 | "classes": "component-classes", 46 | "spin": "component-spin", 47 | "radio": "radio-component", 48 | "file-picker": "component-file-picker" 49 | }, 50 | "devDependencies": { 51 | "html-loader": "^0.4.3", 52 | "json-loader": "^0.5.4", 53 | "webpack": "^1.12.14" 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /public/addon/simplescrollbars.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 | function Bar(cls, orientation, scroll) { 15 | this.orientation = orientation; 16 | this.scroll = scroll; 17 | this.screen = this.total = this.size = 1; 18 | this.pos = 0; 19 | 20 | this.node = document.createElement("div"); 21 | this.node.className = cls + "-" + orientation; 22 | this.inner = this.node.appendChild(document.createElement("div")); 23 | 24 | var self = this; 25 | CodeMirror.on(this.inner, "mousedown", function(e) { 26 | if (e.which != 1) return; 27 | CodeMirror.e_preventDefault(e); 28 | var axis = self.orientation == "horizontal" ? "pageX" : "pageY"; 29 | var start = e[axis], startpos = self.pos; 30 | function done() { 31 | CodeMirror.off(document, "mousemove", move); 32 | CodeMirror.off(document, "mouseup", done); 33 | } 34 | function move(e) { 35 | if (e.which != 1) return done(); 36 | self.moveTo(startpos + (e[axis] - start) * (self.total / self.size)); 37 | } 38 | CodeMirror.on(document, "mousemove", move); 39 | CodeMirror.on(document, "mouseup", done); 40 | }); 41 | 42 | CodeMirror.on(this.node, "click", function(e) { 43 | CodeMirror.e_preventDefault(e); 44 | var innerBox = self.inner.getBoundingClientRect(), where; 45 | if (self.orientation == "horizontal") 46 | where = e.clientX < innerBox.left ? -1 : e.clientX > innerBox.right ? 1 : 0; 47 | else 48 | where = e.clientY < innerBox.top ? -1 : e.clientY > innerBox.bottom ? 1 : 0; 49 | self.moveTo(self.pos + where * self.screen); 50 | }); 51 | 52 | function onWheel(e) { 53 | var moved = CodeMirror.wheelEventPixels(e)[self.orientation == "horizontal" ? "x" : "y"]; 54 | var oldPos = self.pos; 55 | self.moveTo(self.pos + moved); 56 | if (self.pos != oldPos) CodeMirror.e_preventDefault(e); 57 | } 58 | CodeMirror.on(this.node, "mousewheel", onWheel); 59 | CodeMirror.on(this.node, "DOMMouseScroll", onWheel); 60 | } 61 | 62 | Bar.prototype.moveTo = function(pos, update) { 63 | if (pos < 0) pos = 0; 64 | if (pos > this.total - this.screen) pos = this.total - this.screen; 65 | if (pos == this.pos) return; 66 | this.pos = pos; 67 | this.inner.style[this.orientation == "horizontal" ? "left" : "top"] = 68 | (pos * (this.size / this.total)) + "px"; 69 | if (update !== false) this.scroll(pos, this.orientation); 70 | }; 71 | 72 | var minButtonSize = 10; 73 | 74 | Bar.prototype.update = function(scrollSize, clientSize, barSize) { 75 | this.screen = clientSize; 76 | this.total = scrollSize; 77 | this.size = barSize; 78 | 79 | var buttonSize = this.screen * (this.size / this.total); 80 | if (buttonSize < minButtonSize) { 81 | this.size -= minButtonSize - buttonSize; 82 | buttonSize = minButtonSize; 83 | } 84 | this.inner.style[this.orientation == "horizontal" ? "width" : "height"] = 85 | buttonSize + "px"; 86 | this.inner.style[this.orientation == "horizontal" ? "left" : "top"] = 87 | this.pos * (this.size / this.total) + "px"; 88 | }; 89 | 90 | function SimpleScrollbars(cls, place, scroll) { 91 | this.addClass = cls; 92 | this.horiz = new Bar(cls, "horizontal", scroll); 93 | place(this.horiz.node); 94 | this.vert = new Bar(cls, "vertical", scroll); 95 | place(this.vert.node); 96 | this.width = null; 97 | } 98 | 99 | SimpleScrollbars.prototype.update = function(measure) { 100 | if (this.width == null) { 101 | var style = window.getComputedStyle ? window.getComputedStyle(this.horiz.node) : this.horiz.node.currentStyle; 102 | if (style) this.width = parseInt(style.height); 103 | } 104 | var width = this.width || 0; 105 | 106 | var needsH = measure.scrollWidth > measure.clientWidth + 1; 107 | var needsV = measure.scrollHeight > measure.clientHeight + 1; 108 | this.vert.node.style.display = needsV ? "block" : "none"; 109 | this.horiz.node.style.display = needsH ? "block" : "none"; 110 | 111 | if (needsV) { 112 | this.vert.update(measure.scrollHeight, measure.clientHeight, 113 | measure.viewHeight - (needsH ? width : 0)); 114 | this.vert.node.style.display = "block"; 115 | this.vert.node.style.bottom = needsH ? width + "px" : "0"; 116 | } 117 | if (needsH) { 118 | this.horiz.update(measure.scrollWidth, measure.clientWidth, 119 | measure.viewWidth - (needsV ? width : 0) - measure.barLeft); 120 | this.horiz.node.style.right = needsV ? width + "px" : "0"; 121 | this.horiz.node.style.left = measure.barLeft + "px"; 122 | } 123 | 124 | return {right: needsV ? width : 0, bottom: needsH ? width : 0}; 125 | }; 126 | 127 | SimpleScrollbars.prototype.setScrollTop = function(pos) { 128 | this.vert.moveTo(pos, false); 129 | }; 130 | 131 | SimpleScrollbars.prototype.setScrollLeft = function(pos) { 132 | this.horiz.moveTo(pos, false); 133 | }; 134 | 135 | SimpleScrollbars.prototype.clear = function() { 136 | var parent = this.horiz.node.parentNode; 137 | parent.removeChild(this.horiz.node); 138 | parent.removeChild(this.vert.node); 139 | }; 140 | 141 | CodeMirror.scrollbarModel.simple = function(place, scroll) { 142 | return new SimpleScrollbars("CodeMirror-simplescroll", place, scroll); 143 | }; 144 | CodeMirror.scrollbarModel.overlay = function(place, scroll) { 145 | return new SimpleScrollbars("CodeMirror-overlayscroll", place, scroll); 146 | }; 147 | }); 148 | -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | API 配置 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 |
18 |
19 |
20 | 21 | 22 |
23 |
24 |
    25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 | 33 |
34 |
35 | 36 |
37 | import 38 | export 39 |
40 |
41 |
42 |
43 |
44 |
45 | 46 |
47 |
48 |
49 |
50 | 52 | 53 | 54 | -------------------------------------------------------------------------------- /public/lib/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; 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-ruler { 92 | border-left: 1px solid #ccc; 93 | position: absolute; 94 | } 95 | 96 | /* DEFAULT THEME */ 97 | 98 | .cm-s-default .cm-header {color: blue;} 99 | .cm-s-default .cm-quote {color: #090;} 100 | .cm-negative {color: #d44;} 101 | .cm-positive {color: #292;} 102 | .cm-header, .cm-strong {font-weight: bold;} 103 | .cm-em {font-style: italic;} 104 | .cm-link {text-decoration: underline;} 105 | .cm-strikethrough {text-decoration: line-through;} 106 | 107 | .cm-s-default .cm-keyword {color: #708;} 108 | .cm-s-default .cm-atom {color: #219;} 109 | .cm-s-default .cm-number {color: #164;} 110 | .cm-s-default .cm-def {color: #00f;} 111 | .cm-s-default .cm-variable, 112 | .cm-s-default .cm-punctuation, 113 | .cm-s-default .cm-property, 114 | .cm-s-default .cm-operator {} 115 | .cm-s-default .cm-variable-2 {color: #05a;} 116 | .cm-s-default .cm-variable-3 {color: #085;} 117 | .cm-s-default .cm-comment {color: #a50;} 118 | .cm-s-default .cm-string {color: #a11;} 119 | .cm-s-default .cm-string-2 {color: #f50;} 120 | .cm-s-default .cm-meta {color: #555;} 121 | .cm-s-default .cm-qualifier {color: #555;} 122 | .cm-s-default .cm-builtin {color: #30a;} 123 | .cm-s-default .cm-bracket {color: #997;} 124 | .cm-s-default .cm-tag {color: #170;} 125 | .cm-s-default .cm-attribute {color: #00c;} 126 | .cm-s-default .cm-hr {color: #999;} 127 | .cm-s-default .cm-link {color: #00c;} 128 | 129 | .cm-s-default .cm-error {color: #f00;} 130 | .cm-invalidchar {color: #f00;} 131 | 132 | .CodeMirror-composing { border-bottom: 2px solid; } 133 | 134 | /* Default styles for common addons */ 135 | 136 | div.CodeMirror span.CodeMirror-matchingbracket {color: #0f0;} 137 | div.CodeMirror span.CodeMirror-nonmatchingbracket {color: #f22;} 138 | .CodeMirror-matchingtag { background: rgba(255, 150, 0, .3); } 139 | .CodeMirror-activeline-background {background: #e8f2ff;} 140 | 141 | /* STOP */ 142 | 143 | /* The rest of this file contains styles related to the mechanics of 144 | the editor. You probably shouldn't touch them. */ 145 | 146 | .CodeMirror { 147 | position: relative; 148 | overflow: hidden; 149 | background: white; 150 | } 151 | 152 | .CodeMirror-scroll { 153 | overflow: scroll !important; /* Things will break if this is overridden */ 154 | /* 30px is the magic margin used to hide the element's real scrollbars */ 155 | /* See overflow: hidden in .CodeMirror */ 156 | margin-bottom: -30px; margin-right: -30px; 157 | padding-bottom: 30px; 158 | height: 100%; 159 | outline: none; /* Prevent dragging from highlighting the element */ 160 | position: relative; 161 | } 162 | .CodeMirror-sizer { 163 | position: relative; 164 | border-right: 30px solid transparent; 165 | } 166 | 167 | /* The fake, visible scrollbars. Used to force redraw during scrolling 168 | before actual scrolling happens, thus preventing shaking and 169 | flickering artifacts. */ 170 | .CodeMirror-vscrollbar, .CodeMirror-hscrollbar, .CodeMirror-scrollbar-filler, .CodeMirror-gutter-filler { 171 | position: absolute; 172 | z-index: 6; 173 | display: none; 174 | } 175 | .CodeMirror-vscrollbar { 176 | right: 0; top: 0; 177 | overflow-x: hidden; 178 | overflow-y: scroll; 179 | } 180 | .CodeMirror-hscrollbar { 181 | bottom: 0; left: 0; 182 | overflow-y: hidden; 183 | overflow-x: scroll; 184 | } 185 | .CodeMirror-scrollbar-filler { 186 | right: 0; bottom: 0; 187 | } 188 | .CodeMirror-gutter-filler { 189 | left: 0; bottom: 0; 190 | } 191 | 192 | .CodeMirror-gutters { 193 | position: absolute; left: 0; top: 0; 194 | z-index: 3; 195 | } 196 | .CodeMirror-gutter { 197 | white-space: normal; 198 | height: 100%; 199 | display: inline-block; 200 | vertical-align: top; 201 | margin-bottom: -30px; 202 | /* Hack to make IE7 behave */ 203 | *zoom:1; 204 | *display:inline; 205 | } 206 | .CodeMirror-gutter-wrapper { 207 | position: absolute; 208 | z-index: 4; 209 | background: none !important; 210 | border: none !important; 211 | } 212 | .CodeMirror-gutter-background { 213 | position: absolute; 214 | top: 0; bottom: 0; 215 | z-index: 4; 216 | } 217 | .CodeMirror-gutter-elt { 218 | position: absolute; 219 | cursor: default; 220 | z-index: 4; 221 | } 222 | .CodeMirror-gutter-wrapper { 223 | -webkit-user-select: none; 224 | -moz-user-select: none; 225 | user-select: none; 226 | } 227 | 228 | .CodeMirror-lines { 229 | cursor: text; 230 | min-height: 1px; /* prevents collapsing before first draw */ 231 | } 232 | .CodeMirror pre { 233 | /* Reset some styles that the rest of the page might have set */ 234 | -moz-border-radius: 0; -webkit-border-radius: 0; border-radius: 0; 235 | border-width: 0; 236 | background: transparent; 237 | font-family: inherit; 238 | font-size: inherit; 239 | margin: 0; 240 | white-space: pre; 241 | word-wrap: normal; 242 | line-height: inherit; 243 | color: inherit; 244 | z-index: 2; 245 | position: relative; 246 | overflow: visible; 247 | -webkit-tap-highlight-color: transparent; 248 | } 249 | .CodeMirror-wrap pre { 250 | word-wrap: break-word; 251 | white-space: pre-wrap; 252 | word-break: normal; 253 | } 254 | 255 | .CodeMirror-linebackground { 256 | position: absolute; 257 | left: 0; right: 0; top: 0; bottom: 0; 258 | z-index: 0; 259 | } 260 | 261 | .CodeMirror-linewidget { 262 | position: relative; 263 | z-index: 2; 264 | overflow: auto; 265 | } 266 | 267 | .CodeMirror-widget {} 268 | 269 | .CodeMirror-code { 270 | outline: none; 271 | } 272 | 273 | /* Force content-box sizing for the elements where we expect it */ 274 | .CodeMirror-scroll, 275 | .CodeMirror-sizer, 276 | .CodeMirror-gutter, 277 | .CodeMirror-gutters, 278 | .CodeMirror-linenumber { 279 | -moz-box-sizing: content-box; 280 | box-sizing: content-box; 281 | } 282 | 283 | .CodeMirror-measure { 284 | position: absolute; 285 | width: 100%; 286 | height: 0; 287 | overflow: hidden; 288 | visibility: hidden; 289 | } 290 | 291 | .CodeMirror-cursor { position: absolute; } 292 | .CodeMirror-measure pre { position: static; } 293 | 294 | div.CodeMirror-cursors { 295 | visibility: hidden; 296 | position: relative; 297 | z-index: 3; 298 | } 299 | div.CodeMirror-dragcursors { 300 | visibility: visible; 301 | } 302 | 303 | .CodeMirror-focused div.CodeMirror-cursors { 304 | visibility: visible; 305 | } 306 | 307 | .CodeMirror-selected { background: #d9d9d9; } 308 | .CodeMirror-focused .CodeMirror-selected { background: #d7d4f0; } 309 | .CodeMirror-crosshair { cursor: crosshair; } 310 | .CodeMirror-line::selection, .CodeMirror-line > span::selection, .CodeMirror-line > span > span::selection { background: #d7d4f0; } 311 | .CodeMirror-line::-moz-selection, .CodeMirror-line > span::-moz-selection, .CodeMirror-line > span > span::-moz-selection { background: #d7d4f0; } 312 | 313 | .cm-searching { 314 | background: #ffa; 315 | background: rgba(255, 255, 0, .4); 316 | } 317 | 318 | /* IE7 hack to prevent it from returning funny offsetTops on the spans */ 319 | .CodeMirror span { *vertical-align: text-bottom; } 320 | 321 | /* Used to force a border model for a node */ 322 | .cm-force-border { padding-right: .1px; } 323 | 324 | @media print { 325 | /* Hide the cursor when printing */ 326 | .CodeMirror div.CodeMirror-cursors { 327 | visibility: hidden; 328 | } 329 | } 330 | 331 | /* See issue #2901 */ 332 | .cm-tab-wrap-hack:after { content: ''; } 333 | 334 | /* Help users use markselection to safely style text background */ 335 | span.CodeMirror-selectedtext { background: none; } 336 | -------------------------------------------------------------------------------- /public/mode/javascript/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | CodeMirror: JavaScript mode 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 27 | 28 |
29 |

JavaScript mode

30 | 31 | 32 |
81 | 82 | 90 | 91 |

92 | JavaScript mode supports several configuration options: 93 |

111 |

112 | 113 |

MIME types defined: text/javascript, application/json, application/ld+json, text/typescript, application/typescript.

114 |
115 | -------------------------------------------------------------------------------- /public/mode/javascript/javascript.js: -------------------------------------------------------------------------------- 1 | // CodeMirror, copyright (c) by Marijn Haverbeke and others 2 | // Distributed under an MIT license: http://codemirror.net/LICENSE 3 | 4 | // TODO actually recognize syntax of TypeScript constructs 5 | 6 | (function(mod) { 7 | if (typeof exports == "object" && typeof module == "object") // CommonJS 8 | mod(require("../../lib/codemirror")); 9 | else if (typeof define == "function" && define.amd) // AMD 10 | define(["../../lib/codemirror"], mod); 11 | else // Plain browser env 12 | mod(CodeMirror); 13 | })(function(CodeMirror) { 14 | "use strict"; 15 | 16 | function expressionAllowed(stream, state, backUp) { 17 | return /^(?:operator|sof|keyword c|case|new|[\[{}\(,;:]|=>)$/.test(state.lastType) || 18 | (state.lastType == "quasi" && /\{\s*$/.test(stream.string.slice(0, stream.pos - (backUp || 0)))) 19 | } 20 | 21 | CodeMirror.defineMode("javascript", function(config, parserConfig) { 22 | var indentUnit = config.indentUnit; 23 | var statementIndent = parserConfig.statementIndent; 24 | var jsonldMode = parserConfig.jsonld; 25 | var jsonMode = parserConfig.json || jsonldMode; 26 | var isTS = parserConfig.typescript; 27 | var wordRE = parserConfig.wordCharacters || /[\w$\xa1-\uffff]/; 28 | 29 | // Tokenizer 30 | 31 | var keywords = function(){ 32 | function kw(type) {return {type: type, style: "keyword"};} 33 | var A = kw("keyword a"), B = kw("keyword b"), C = kw("keyword c"); 34 | var operator = kw("operator"), atom = {type: "atom", style: "atom"}; 35 | 36 | var jsKeywords = { 37 | "if": kw("if"), "while": A, "with": A, "else": B, "do": B, "try": B, "finally": B, 38 | "return": C, "break": C, "continue": C, "new": kw("new"), "delete": C, "throw": C, "debugger": C, 39 | "var": kw("var"), "const": kw("var"), "let": kw("var"), 40 | "function": kw("function"), "catch": kw("catch"), 41 | "for": kw("for"), "switch": kw("switch"), "case": kw("case"), "default": kw("default"), 42 | "in": operator, "typeof": operator, "instanceof": operator, 43 | "true": atom, "false": atom, "null": atom, "undefined": atom, "NaN": atom, "Infinity": atom, 44 | "this": kw("this"), "class": kw("class"), "super": kw("atom"), 45 | "yield": C, "export": kw("export"), "import": kw("import"), "extends": C 46 | }; 47 | 48 | // Extend the 'normal' keywords with the TypeScript language extensions 49 | if (isTS) { 50 | var type = {type: "variable", style: "variable-3"}; 51 | var tsKeywords = { 52 | // object-like things 53 | "interface": kw("class"), 54 | "implements": C, 55 | "namespace": C, 56 | "module": kw("module"), 57 | "enum": kw("module"), 58 | 59 | // scope modifiers 60 | "public": kw("modifier"), 61 | "private": kw("modifier"), 62 | "protected": kw("modifier"), 63 | "abstract": kw("modifier"), 64 | 65 | // operators 66 | "as": operator, 67 | 68 | // types 69 | "string": type, "number": type, "boolean": type, "any": type 70 | }; 71 | 72 | for (var attr in tsKeywords) { 73 | jsKeywords[attr] = tsKeywords[attr]; 74 | } 75 | } 76 | 77 | return jsKeywords; 78 | }(); 79 | 80 | var isOperatorChar = /[+\-*&%=<>!?|~^]/; 81 | var isJsonldKeyword = /^@(context|id|value|language|type|container|list|set|reverse|index|base|vocab|graph)"/; 82 | 83 | function readRegexp(stream) { 84 | var escaped = false, next, inSet = false; 85 | while ((next = stream.next()) != null) { 86 | if (!escaped) { 87 | if (next == "/" && !inSet) return; 88 | if (next == "[") inSet = true; 89 | else if (inSet && next == "]") inSet = false; 90 | } 91 | escaped = !escaped && next == "\\"; 92 | } 93 | } 94 | 95 | // Used as scratch variables to communicate multiple values without 96 | // consing up tons of objects. 97 | var type, content; 98 | function ret(tp, style, cont) { 99 | type = tp; content = cont; 100 | return style; 101 | } 102 | function tokenBase(stream, state) { 103 | var ch = stream.next(); 104 | if (ch == '"' || ch == "'") { 105 | state.tokenize = tokenString(ch); 106 | return state.tokenize(stream, state); 107 | } else if (ch == "." && stream.match(/^\d+(?:[eE][+\-]?\d+)?/)) { 108 | return ret("number", "number"); 109 | } else if (ch == "." && stream.match("..")) { 110 | return ret("spread", "meta"); 111 | } else if (/[\[\]{}\(\),;\:\.]/.test(ch)) { 112 | return ret(ch); 113 | } else if (ch == "=" && stream.eat(">")) { 114 | return ret("=>", "operator"); 115 | } else if (ch == "0" && stream.eat(/x/i)) { 116 | stream.eatWhile(/[\da-f]/i); 117 | return ret("number", "number"); 118 | } else if (ch == "0" && stream.eat(/o/i)) { 119 | stream.eatWhile(/[0-7]/i); 120 | return ret("number", "number"); 121 | } else if (ch == "0" && stream.eat(/b/i)) { 122 | stream.eatWhile(/[01]/i); 123 | return ret("number", "number"); 124 | } else if (/\d/.test(ch)) { 125 | stream.match(/^\d*(?:\.\d*)?(?:[eE][+\-]?\d+)?/); 126 | return ret("number", "number"); 127 | } else if (ch == "/") { 128 | if (stream.eat("*")) { 129 | state.tokenize = tokenComment; 130 | return tokenComment(stream, state); 131 | } else if (stream.eat("/")) { 132 | stream.skipToEnd(); 133 | return ret("comment", "comment"); 134 | } else if (expressionAllowed(stream, state, 1)) { 135 | readRegexp(stream); 136 | stream.match(/^\b(([gimyu])(?![gimyu]*\2))+\b/); 137 | return ret("regexp", "string-2"); 138 | } else { 139 | stream.eatWhile(isOperatorChar); 140 | return ret("operator", "operator", stream.current()); 141 | } 142 | } else if (ch == "`") { 143 | state.tokenize = tokenQuasi; 144 | return tokenQuasi(stream, state); 145 | } else if (ch == "#") { 146 | stream.skipToEnd(); 147 | return ret("error", "error"); 148 | } else if (isOperatorChar.test(ch)) { 149 | stream.eatWhile(isOperatorChar); 150 | return ret("operator", "operator", stream.current()); 151 | } else if (wordRE.test(ch)) { 152 | stream.eatWhile(wordRE); 153 | var word = stream.current(), known = keywords.propertyIsEnumerable(word) && keywords[word]; 154 | return (known && state.lastType != ".") ? ret(known.type, known.style, word) : 155 | ret("variable", "variable", word); 156 | } 157 | } 158 | 159 | function tokenString(quote) { 160 | return function(stream, state) { 161 | var escaped = false, next; 162 | if (jsonldMode && stream.peek() == "@" && stream.match(isJsonldKeyword)){ 163 | state.tokenize = tokenBase; 164 | return ret("jsonld-keyword", "meta"); 165 | } 166 | while ((next = stream.next()) != null) { 167 | if (next == quote && !escaped) break; 168 | escaped = !escaped && next == "\\"; 169 | } 170 | if (!escaped) state.tokenize = tokenBase; 171 | return ret("string", "string"); 172 | }; 173 | } 174 | 175 | function tokenComment(stream, state) { 176 | var maybeEnd = false, ch; 177 | while (ch = stream.next()) { 178 | if (ch == "/" && maybeEnd) { 179 | state.tokenize = tokenBase; 180 | break; 181 | } 182 | maybeEnd = (ch == "*"); 183 | } 184 | return ret("comment", "comment"); 185 | } 186 | 187 | function tokenQuasi(stream, state) { 188 | var escaped = false, next; 189 | while ((next = stream.next()) != null) { 190 | if (!escaped && (next == "`" || next == "$" && stream.eat("{"))) { 191 | state.tokenize = tokenBase; 192 | break; 193 | } 194 | escaped = !escaped && next == "\\"; 195 | } 196 | return ret("quasi", "string-2", stream.current()); 197 | } 198 | 199 | var brackets = "([{}])"; 200 | // This is a crude lookahead trick to try and notice that we're 201 | // parsing the argument patterns for a fat-arrow function before we 202 | // actually hit the arrow token. It only works if the arrow is on 203 | // the same line as the arguments and there's no strange noise 204 | // (comments) in between. Fallback is to only notice when we hit the 205 | // arrow, and not declare the arguments as locals for the arrow 206 | // body. 207 | function findFatArrow(stream, state) { 208 | if (state.fatArrowAt) state.fatArrowAt = null; 209 | var arrow = stream.string.indexOf("=>", stream.start); 210 | if (arrow < 0) return; 211 | 212 | var depth = 0, sawSomething = false; 213 | for (var pos = arrow - 1; pos >= 0; --pos) { 214 | var ch = stream.string.charAt(pos); 215 | var bracket = brackets.indexOf(ch); 216 | if (bracket >= 0 && bracket < 3) { 217 | if (!depth) { ++pos; break; } 218 | if (--depth == 0) break; 219 | } else if (bracket >= 3 && bracket < 6) { 220 | ++depth; 221 | } else if (wordRE.test(ch)) { 222 | sawSomething = true; 223 | } else if (/["'\/]/.test(ch)) { 224 | return; 225 | } else if (sawSomething && !depth) { 226 | ++pos; 227 | break; 228 | } 229 | } 230 | if (sawSomething && !depth) state.fatArrowAt = pos; 231 | } 232 | 233 | // Parser 234 | 235 | var atomicTypes = {"atom": true, "number": true, "variable": true, "string": true, "regexp": true, "this": true, "jsonld-keyword": true}; 236 | 237 | function JSLexical(indented, column, type, align, prev, info) { 238 | this.indented = indented; 239 | this.column = column; 240 | this.type = type; 241 | this.prev = prev; 242 | this.info = info; 243 | if (align != null) this.align = align; 244 | } 245 | 246 | function inScope(state, varname) { 247 | for (var v = state.localVars; v; v = v.next) 248 | if (v.name == varname) return true; 249 | for (var cx = state.context; cx; cx = cx.prev) { 250 | for (var v = cx.vars; v; v = v.next) 251 | if (v.name == varname) return true; 252 | } 253 | } 254 | 255 | function parseJS(state, style, type, content, stream) { 256 | var cc = state.cc; 257 | // Communicate our context to the combinators. 258 | // (Less wasteful than consing up a hundred closures on every call.) 259 | cx.state = state; cx.stream = stream; cx.marked = null, cx.cc = cc; cx.style = style; 260 | 261 | if (!state.lexical.hasOwnProperty("align")) 262 | state.lexical.align = true; 263 | 264 | while(true) { 265 | var combinator = cc.length ? cc.pop() : jsonMode ? expression : statement; 266 | if (combinator(type, content)) { 267 | while(cc.length && cc[cc.length - 1].lex) 268 | cc.pop()(); 269 | if (cx.marked) return cx.marked; 270 | if (type == "variable" && inScope(state, content)) return "variable-2"; 271 | return style; 272 | } 273 | } 274 | } 275 | 276 | // Combinator utils 277 | 278 | var cx = {state: null, column: null, marked: null, cc: null}; 279 | function pass() { 280 | for (var i = arguments.length - 1; i >= 0; i--) cx.cc.push(arguments[i]); 281 | } 282 | function cont() { 283 | pass.apply(null, arguments); 284 | return true; 285 | } 286 | function register(varname) { 287 | function inList(list) { 288 | for (var v = list; v; v = v.next) 289 | if (v.name == varname) return true; 290 | return false; 291 | } 292 | var state = cx.state; 293 | cx.marked = "def"; 294 | if (state.context) { 295 | if (inList(state.localVars)) return; 296 | state.localVars = {name: varname, next: state.localVars}; 297 | } else { 298 | if (inList(state.globalVars)) return; 299 | if (parserConfig.globalVars) 300 | state.globalVars = {name: varname, next: state.globalVars}; 301 | } 302 | } 303 | 304 | // Combinators 305 | 306 | var defaultVars = {name: "this", next: {name: "arguments"}}; 307 | function pushcontext() { 308 | cx.state.context = {prev: cx.state.context, vars: cx.state.localVars}; 309 | cx.state.localVars = defaultVars; 310 | } 311 | function popcontext() { 312 | cx.state.localVars = cx.state.context.vars; 313 | cx.state.context = cx.state.context.prev; 314 | } 315 | function pushlex(type, info) { 316 | var result = function() { 317 | var state = cx.state, indent = state.indented; 318 | if (state.lexical.type == "stat") indent = state.lexical.indented; 319 | else for (var outer = state.lexical; outer && outer.type == ")" && outer.align; outer = outer.prev) 320 | indent = outer.indented; 321 | state.lexical = new JSLexical(indent, cx.stream.column(), type, null, state.lexical, info); 322 | }; 323 | result.lex = true; 324 | return result; 325 | } 326 | function poplex() { 327 | var state = cx.state; 328 | if (state.lexical.prev) { 329 | if (state.lexical.type == ")") 330 | state.indented = state.lexical.indented; 331 | state.lexical = state.lexical.prev; 332 | } 333 | } 334 | poplex.lex = true; 335 | 336 | function expect(wanted) { 337 | function exp(type) { 338 | if (type == wanted) return cont(); 339 | else if (wanted == ";") return pass(); 340 | else return cont(exp); 341 | }; 342 | return exp; 343 | } 344 | 345 | function statement(type, value) { 346 | if (type == "var") return cont(pushlex("vardef", value.length), vardef, expect(";"), poplex); 347 | if (type == "keyword a") return cont(pushlex("form"), expression, statement, poplex); 348 | if (type == "keyword b") return cont(pushlex("form"), statement, poplex); 349 | if (type == "{") return cont(pushlex("}"), block, poplex); 350 | if (type == ";") return cont(); 351 | if (type == "if") { 352 | if (cx.state.lexical.info == "else" && cx.state.cc[cx.state.cc.length - 1] == poplex) 353 | cx.state.cc.pop()(); 354 | return cont(pushlex("form"), expression, statement, poplex, maybeelse); 355 | } 356 | if (type == "function") return cont(functiondef); 357 | if (type == "for") return cont(pushlex("form"), forspec, statement, poplex); 358 | if (type == "variable") return cont(pushlex("stat"), maybelabel); 359 | if (type == "switch") return cont(pushlex("form"), expression, pushlex("}", "switch"), expect("{"), 360 | block, poplex, poplex); 361 | if (type == "case") return cont(expression, expect(":")); 362 | if (type == "default") return cont(expect(":")); 363 | if (type == "catch") return cont(pushlex("form"), pushcontext, expect("("), funarg, expect(")"), 364 | statement, poplex, popcontext); 365 | if (type == "class") return cont(pushlex("form"), className, poplex); 366 | if (type == "export") return cont(pushlex("stat"), afterExport, poplex); 367 | if (type == "import") return cont(pushlex("stat"), afterImport, poplex); 368 | if (type == "module") return cont(pushlex("form"), pattern, pushlex("}"), expect("{"), block, poplex, poplex) 369 | return pass(pushlex("stat"), expression, expect(";"), poplex); 370 | } 371 | function expression(type) { 372 | return expressionInner(type, false); 373 | } 374 | function expressionNoComma(type) { 375 | return expressionInner(type, true); 376 | } 377 | function expressionInner(type, noComma) { 378 | if (cx.state.fatArrowAt == cx.stream.start) { 379 | var body = noComma ? arrowBodyNoComma : arrowBody; 380 | if (type == "(") return cont(pushcontext, pushlex(")"), commasep(pattern, ")"), poplex, expect("=>"), body, popcontext); 381 | else if (type == "variable") return pass(pushcontext, pattern, expect("=>"), body, popcontext); 382 | } 383 | 384 | var maybeop = noComma ? maybeoperatorNoComma : maybeoperatorComma; 385 | if (atomicTypes.hasOwnProperty(type)) return cont(maybeop); 386 | if (type == "function") return cont(functiondef, maybeop); 387 | if (type == "keyword c") return cont(noComma ? maybeexpressionNoComma : maybeexpression); 388 | if (type == "(") return cont(pushlex(")"), maybeexpression, comprehension, expect(")"), poplex, maybeop); 389 | if (type == "operator" || type == "spread") return cont(noComma ? expressionNoComma : expression); 390 | if (type == "[") return cont(pushlex("]"), arrayLiteral, poplex, maybeop); 391 | if (type == "{") return contCommasep(objprop, "}", null, maybeop); 392 | if (type == "quasi") return pass(quasi, maybeop); 393 | if (type == "new") return cont(maybeTarget(noComma)); 394 | return cont(); 395 | } 396 | function maybeexpression(type) { 397 | if (type.match(/[;\}\)\],]/)) return pass(); 398 | return pass(expression); 399 | } 400 | function maybeexpressionNoComma(type) { 401 | if (type.match(/[;\}\)\],]/)) return pass(); 402 | return pass(expressionNoComma); 403 | } 404 | 405 | function maybeoperatorComma(type, value) { 406 | if (type == ",") return cont(expression); 407 | return maybeoperatorNoComma(type, value, false); 408 | } 409 | function maybeoperatorNoComma(type, value, noComma) { 410 | var me = noComma == false ? maybeoperatorComma : maybeoperatorNoComma; 411 | var expr = noComma == false ? expression : expressionNoComma; 412 | if (type == "=>") return cont(pushcontext, noComma ? arrowBodyNoComma : arrowBody, popcontext); 413 | if (type == "operator") { 414 | if (/\+\+|--/.test(value)) return cont(me); 415 | if (value == "?") return cont(expression, expect(":"), expr); 416 | return cont(expr); 417 | } 418 | if (type == "quasi") { return pass(quasi, me); } 419 | if (type == ";") return; 420 | if (type == "(") return contCommasep(expressionNoComma, ")", "call", me); 421 | if (type == ".") return cont(property, me); 422 | if (type == "[") return cont(pushlex("]"), maybeexpression, expect("]"), poplex, me); 423 | } 424 | function quasi(type, value) { 425 | if (type != "quasi") return pass(); 426 | if (value.slice(value.length - 2) != "${") return cont(quasi); 427 | return cont(expression, continueQuasi); 428 | } 429 | function continueQuasi(type) { 430 | if (type == "}") { 431 | cx.marked = "string-2"; 432 | cx.state.tokenize = tokenQuasi; 433 | return cont(quasi); 434 | } 435 | } 436 | function arrowBody(type) { 437 | findFatArrow(cx.stream, cx.state); 438 | return pass(type == "{" ? statement : expression); 439 | } 440 | function arrowBodyNoComma(type) { 441 | findFatArrow(cx.stream, cx.state); 442 | return pass(type == "{" ? statement : expressionNoComma); 443 | } 444 | function maybeTarget(noComma) { 445 | return function(type) { 446 | if (type == ".") return cont(noComma ? targetNoComma : target); 447 | else return pass(noComma ? expressionNoComma : expression); 448 | }; 449 | } 450 | function target(_, value) { 451 | if (value == "target") { cx.marked = "keyword"; return cont(maybeoperatorComma); } 452 | } 453 | function targetNoComma(_, value) { 454 | if (value == "target") { cx.marked = "keyword"; return cont(maybeoperatorNoComma); } 455 | } 456 | function maybelabel(type) { 457 | if (type == ":") return cont(poplex, statement); 458 | return pass(maybeoperatorComma, expect(";"), poplex); 459 | } 460 | function property(type) { 461 | if (type == "variable") {cx.marked = "property"; return cont();} 462 | } 463 | function objprop(type, value) { 464 | if (type == "variable" || cx.style == "keyword") { 465 | cx.marked = "property"; 466 | if (value == "get" || value == "set") return cont(getterSetter); 467 | return cont(afterprop); 468 | } else if (type == "number" || type == "string") { 469 | cx.marked = jsonldMode ? "property" : (cx.style + " property"); 470 | return cont(afterprop); 471 | } else if (type == "jsonld-keyword") { 472 | return cont(afterprop); 473 | } else if (type == "modifier") { 474 | return cont(objprop) 475 | } else if (type == "[") { 476 | return cont(expression, expect("]"), afterprop); 477 | } else if (type == "spread") { 478 | return cont(expression); 479 | } 480 | } 481 | function getterSetter(type) { 482 | if (type != "variable") return pass(afterprop); 483 | cx.marked = "property"; 484 | return cont(functiondef); 485 | } 486 | function afterprop(type) { 487 | if (type == ":") return cont(expressionNoComma); 488 | if (type == "(") return pass(functiondef); 489 | } 490 | function commasep(what, end) { 491 | function proceed(type) { 492 | if (type == ",") { 493 | var lex = cx.state.lexical; 494 | if (lex.info == "call") lex.pos = (lex.pos || 0) + 1; 495 | return cont(what, proceed); 496 | } 497 | if (type == end) return cont(); 498 | return cont(expect(end)); 499 | } 500 | return function(type) { 501 | if (type == end) return cont(); 502 | return pass(what, proceed); 503 | }; 504 | } 505 | function contCommasep(what, end, info) { 506 | for (var i = 3; i < arguments.length; i++) 507 | cx.cc.push(arguments[i]); 508 | return cont(pushlex(end, info), commasep(what, end), poplex); 509 | } 510 | function block(type) { 511 | if (type == "}") return cont(); 512 | return pass(statement, block); 513 | } 514 | function maybetype(type) { 515 | if (isTS && type == ":") return cont(typedef); 516 | } 517 | function maybedefault(_, value) { 518 | if (value == "=") return cont(expressionNoComma); 519 | } 520 | function typedef(type) { 521 | if (type == "variable") {cx.marked = "variable-3"; return cont();} 522 | } 523 | function vardef() { 524 | return pass(pattern, maybetype, maybeAssign, vardefCont); 525 | } 526 | function pattern(type, value) { 527 | if (type == "modifier") return cont(pattern) 528 | if (type == "variable") { register(value); return cont(); } 529 | if (type == "spread") return cont(pattern); 530 | if (type == "[") return contCommasep(pattern, "]"); 531 | if (type == "{") return contCommasep(proppattern, "}"); 532 | } 533 | function proppattern(type, value) { 534 | if (type == "variable" && !cx.stream.match(/^\s*:/, false)) { 535 | register(value); 536 | return cont(maybeAssign); 537 | } 538 | if (type == "variable") cx.marked = "property"; 539 | if (type == "spread") return cont(pattern); 540 | if (type == "}") return pass(); 541 | return cont(expect(":"), pattern, maybeAssign); 542 | } 543 | function maybeAssign(_type, value) { 544 | if (value == "=") return cont(expressionNoComma); 545 | } 546 | function vardefCont(type) { 547 | if (type == ",") return cont(vardef); 548 | } 549 | function maybeelse(type, value) { 550 | if (type == "keyword b" && value == "else") return cont(pushlex("form", "else"), statement, poplex); 551 | } 552 | function forspec(type) { 553 | if (type == "(") return cont(pushlex(")"), forspec1, expect(")"), poplex); 554 | } 555 | function forspec1(type) { 556 | if (type == "var") return cont(vardef, expect(";"), forspec2); 557 | if (type == ";") return cont(forspec2); 558 | if (type == "variable") return cont(formaybeinof); 559 | return pass(expression, expect(";"), forspec2); 560 | } 561 | function formaybeinof(_type, value) { 562 | if (value == "in" || value == "of") { cx.marked = "keyword"; return cont(expression); } 563 | return cont(maybeoperatorComma, forspec2); 564 | } 565 | function forspec2(type, value) { 566 | if (type == ";") return cont(forspec3); 567 | if (value == "in" || value == "of") { cx.marked = "keyword"; return cont(expression); } 568 | return pass(expression, expect(";"), forspec3); 569 | } 570 | function forspec3(type) { 571 | if (type != ")") cont(expression); 572 | } 573 | function functiondef(type, value) { 574 | if (value == "*") {cx.marked = "keyword"; return cont(functiondef);} 575 | if (type == "variable") {register(value); return cont(functiondef);} 576 | if (type == "(") return cont(pushcontext, pushlex(")"), commasep(funarg, ")"), poplex, statement, popcontext); 577 | } 578 | function funarg(type) { 579 | if (type == "spread") return cont(funarg); 580 | return pass(pattern, maybetype, maybedefault); 581 | } 582 | function className(type, value) { 583 | if (type == "variable") {register(value); return cont(classNameAfter);} 584 | } 585 | function classNameAfter(type, value) { 586 | if (value == "extends") return cont(expression, classNameAfter); 587 | if (type == "{") return cont(pushlex("}"), classBody, poplex); 588 | } 589 | function classBody(type, value) { 590 | if (type == "variable" || cx.style == "keyword") { 591 | if (value == "static") { 592 | cx.marked = "keyword"; 593 | return cont(classBody); 594 | } 595 | cx.marked = "property"; 596 | if (value == "get" || value == "set") return cont(classGetterSetter, functiondef, classBody); 597 | return cont(functiondef, classBody); 598 | } 599 | if (value == "*") { 600 | cx.marked = "keyword"; 601 | return cont(classBody); 602 | } 603 | if (type == ";") return cont(classBody); 604 | if (type == "}") return cont(); 605 | } 606 | function classGetterSetter(type) { 607 | if (type != "variable") return pass(); 608 | cx.marked = "property"; 609 | return cont(); 610 | } 611 | function afterExport(_type, value) { 612 | if (value == "*") { cx.marked = "keyword"; return cont(maybeFrom, expect(";")); } 613 | if (value == "default") { cx.marked = "keyword"; return cont(expression, expect(";")); } 614 | return pass(statement); 615 | } 616 | function afterImport(type) { 617 | if (type == "string") return cont(); 618 | return pass(importSpec, maybeFrom); 619 | } 620 | function importSpec(type, value) { 621 | if (type == "{") return contCommasep(importSpec, "}"); 622 | if (type == "variable") register(value); 623 | if (value == "*") cx.marked = "keyword"; 624 | return cont(maybeAs); 625 | } 626 | function maybeAs(_type, value) { 627 | if (value == "as") { cx.marked = "keyword"; return cont(importSpec); } 628 | } 629 | function maybeFrom(_type, value) { 630 | if (value == "from") { cx.marked = "keyword"; return cont(expression); } 631 | } 632 | function arrayLiteral(type) { 633 | if (type == "]") return cont(); 634 | return pass(expressionNoComma, maybeArrayComprehension); 635 | } 636 | function maybeArrayComprehension(type) { 637 | if (type == "for") return pass(comprehension, expect("]")); 638 | if (type == ",") return cont(commasep(maybeexpressionNoComma, "]")); 639 | return pass(commasep(expressionNoComma, "]")); 640 | } 641 | function comprehension(type) { 642 | if (type == "for") return cont(forspec, comprehension); 643 | if (type == "if") return cont(expression, comprehension); 644 | } 645 | 646 | function isContinuedStatement(state, textAfter) { 647 | return state.lastType == "operator" || state.lastType == "," || 648 | isOperatorChar.test(textAfter.charAt(0)) || 649 | /[,.]/.test(textAfter.charAt(0)); 650 | } 651 | 652 | // Interface 653 | 654 | return { 655 | startState: function(basecolumn) { 656 | var state = { 657 | tokenize: tokenBase, 658 | lastType: "sof", 659 | cc: [], 660 | lexical: new JSLexical((basecolumn || 0) - indentUnit, 0, "block", false), 661 | localVars: parserConfig.localVars, 662 | context: parserConfig.localVars && {vars: parserConfig.localVars}, 663 | indented: basecolumn || 0 664 | }; 665 | if (parserConfig.globalVars && typeof parserConfig.globalVars == "object") 666 | state.globalVars = parserConfig.globalVars; 667 | return state; 668 | }, 669 | 670 | token: function(stream, state) { 671 | if (stream.sol()) { 672 | if (!state.lexical.hasOwnProperty("align")) 673 | state.lexical.align = false; 674 | state.indented = stream.indentation(); 675 | findFatArrow(stream, state); 676 | } 677 | if (state.tokenize != tokenComment && stream.eatSpace()) return null; 678 | var style = state.tokenize(stream, state); 679 | if (type == "comment") return style; 680 | state.lastType = type == "operator" && (content == "++" || content == "--") ? "incdec" : type; 681 | return parseJS(state, style, type, content, stream); 682 | }, 683 | 684 | indent: function(state, textAfter) { 685 | if (state.tokenize == tokenComment) return CodeMirror.Pass; 686 | if (state.tokenize != tokenBase) return 0; 687 | var firstChar = textAfter && textAfter.charAt(0), lexical = state.lexical; 688 | // Kludge to prevent 'maybelse' from blocking lexical scope pops 689 | if (!/^\s*else\b/.test(textAfter)) for (var i = state.cc.length - 1; i >= 0; --i) { 690 | var c = state.cc[i]; 691 | if (c == poplex) lexical = lexical.prev; 692 | else if (c != maybeelse) break; 693 | } 694 | if (lexical.type == "stat" && firstChar == "}") lexical = lexical.prev; 695 | if (statementIndent && lexical.type == ")" && lexical.prev.type == "stat") 696 | lexical = lexical.prev; 697 | var type = lexical.type, closing = firstChar == type; 698 | 699 | if (type == "vardef") return lexical.indented + (state.lastType == "operator" || state.lastType == "," ? lexical.info + 1 : 0); 700 | else if (type == "form" && firstChar == "{") return lexical.indented; 701 | else if (type == "form") return lexical.indented + indentUnit; 702 | else if (type == "stat") 703 | return lexical.indented + (isContinuedStatement(state, textAfter) ? statementIndent || indentUnit : 0); 704 | else if (lexical.info == "switch" && !closing && parserConfig.doubleIndentSwitch != false) 705 | return lexical.indented + (/^(?:case|default)\b/.test(textAfter) ? indentUnit : 2 * indentUnit); 706 | else if (lexical.align) return lexical.column + (closing ? 0 : 1); 707 | else return lexical.indented + (closing ? 0 : indentUnit); 708 | }, 709 | 710 | electricInput: /^\s*(?:case .*?:|default:|\{|\})$/, 711 | blockCommentStart: jsonMode ? null : "/*", 712 | blockCommentEnd: jsonMode ? null : "*/", 713 | lineComment: jsonMode ? null : "//", 714 | fold: "brace", 715 | closeBrackets: "()[]{}''\"\"``", 716 | 717 | helperType: jsonMode ? "json" : "javascript", 718 | jsonldMode: jsonldMode, 719 | jsonMode: jsonMode, 720 | 721 | expressionAllowed: expressionAllowed, 722 | skipExpression: function(state) { 723 | var top = state.cc[state.cc.length - 1] 724 | if (top == expression || top == expressionNoComma) state.cc.pop() 725 | } 726 | }; 727 | }); 728 | 729 | CodeMirror.registerHelper("wordChars", "javascript", /[\w$]/); 730 | 731 | CodeMirror.defineMIME("text/javascript", "javascript"); 732 | CodeMirror.defineMIME("text/ecmascript", "javascript"); 733 | CodeMirror.defineMIME("application/javascript", "javascript"); 734 | CodeMirror.defineMIME("application/x-javascript", "javascript"); 735 | CodeMirror.defineMIME("application/ecmascript", "javascript"); 736 | CodeMirror.defineMIME("application/json", {name: "javascript", json: true}); 737 | CodeMirror.defineMIME("application/x-json", {name: "javascript", json: true}); 738 | CodeMirror.defineMIME("application/ld+json", {name: "javascript", jsonld: true}); 739 | CodeMirror.defineMIME("text/typescript", { name: "javascript", typescript: true }); 740 | CodeMirror.defineMIME("application/typescript", { name: "javascript", typescript: true }); 741 | 742 | }); 743 | -------------------------------------------------------------------------------- /public/mode/javascript/json-ld.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | CodeMirror: JSON-LD mode 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 27 | 28 |
29 |

JSON-LD mode

30 | 31 | 32 |
61 | 62 | 70 | 71 |

This is a specialization of the JavaScript mode.

72 |
73 | -------------------------------------------------------------------------------- /public/mode/javascript/test.js: -------------------------------------------------------------------------------- 1 | // CodeMirror, copyright (c) by Marijn Haverbeke and others 2 | // Distributed under an MIT license: http://codemirror.net/LICENSE 3 | 4 | (function() { 5 | var mode = CodeMirror.getMode({indentUnit: 2}, "javascript"); 6 | function MT(name) { test.mode(name, mode, Array.prototype.slice.call(arguments, 1)); } 7 | 8 | MT("locals", 9 | "[keyword function] [def foo]([def a], [def b]) { [keyword var] [def c] [operator =] [number 10]; [keyword return] [variable-2 a] [operator +] [variable-2 c] [operator +] [variable d]; }"); 10 | 11 | MT("comma-and-binop", 12 | "[keyword function](){ [keyword var] [def x] [operator =] [number 1] [operator +] [number 2], [def y]; }"); 13 | 14 | MT("destructuring", 15 | "([keyword function]([def a], [[[def b], [def c] ]]) {", 16 | " [keyword let] {[def d], [property foo]: [def c][operator =][number 10], [def x]} [operator =] [variable foo]([variable-2 a]);", 17 | " [[[variable-2 c], [variable y] ]] [operator =] [variable-2 c];", 18 | "})();"); 19 | 20 | MT("destructure_trailing_comma", 21 | "[keyword let] {[def a], [def b],} [operator =] [variable foo];", 22 | "[keyword let] [def c];"); // Parser still in good state? 23 | 24 | MT("class_body", 25 | "[keyword class] [def Foo] {", 26 | " [property constructor]() {}", 27 | " [property sayName]() {", 28 | " [keyword return] [string-2 `foo${][variable foo][string-2 }oo`];", 29 | " }", 30 | "}"); 31 | 32 | MT("class", 33 | "[keyword class] [def Point] [keyword extends] [variable SuperThing] {", 34 | " [property get] [property prop]() { [keyword return] [number 24]; }", 35 | " [property constructor]([def x], [def y]) {", 36 | " [keyword super]([string 'something']);", 37 | " [keyword this].[property x] [operator =] [variable-2 x];", 38 | " }", 39 | "}"); 40 | 41 | MT("import", 42 | "[keyword function] [def foo]() {", 43 | " [keyword import] [def $] [keyword from] [string 'jquery'];", 44 | " [keyword import] { [def encrypt], [def decrypt] } [keyword from] [string 'crypto'];", 45 | "}"); 46 | 47 | MT("const", 48 | "[keyword function] [def f]() {", 49 | " [keyword const] [[ [def a], [def b] ]] [operator =] [[ [number 1], [number 2] ]];", 50 | "}"); 51 | 52 | MT("for/of", 53 | "[keyword for]([keyword let] [def of] [keyword of] [variable something]) {}"); 54 | 55 | MT("generator", 56 | "[keyword function*] [def repeat]([def n]) {", 57 | " [keyword for]([keyword var] [def i] [operator =] [number 0]; [variable-2 i] [operator <] [variable-2 n]; [operator ++][variable-2 i])", 58 | " [keyword yield] [variable-2 i];", 59 | "}"); 60 | 61 | MT("quotedStringAddition", 62 | "[keyword let] [def f] [operator =] [variable a] [operator +] [string 'fatarrow'] [operator +] [variable c];"); 63 | 64 | MT("quotedFatArrow", 65 | "[keyword let] [def f] [operator =] [variable a] [operator +] [string '=>'] [operator +] [variable c];"); 66 | 67 | MT("fatArrow", 68 | "[variable array].[property filter]([def a] [operator =>] [variable-2 a] [operator +] [number 1]);", 69 | "[variable a];", // No longer in scope 70 | "[keyword let] [def f] [operator =] ([[ [def a], [def b] ]], [def c]) [operator =>] [variable-2 a] [operator +] [variable-2 c];", 71 | "[variable c];"); 72 | 73 | MT("spread", 74 | "[keyword function] [def f]([def a], [meta ...][def b]) {", 75 | " [variable something]([variable-2 a], [meta ...][variable-2 b]);", 76 | "}"); 77 | 78 | MT("comprehension", 79 | "[keyword function] [def f]() {", 80 | " [[([variable x] [operator +] [number 1]) [keyword for] ([keyword var] [def x] [keyword in] [variable y]) [keyword if] [variable pred]([variable-2 x]) ]];", 81 | " ([variable u] [keyword for] ([keyword var] [def u] [keyword of] [variable generateValues]()) [keyword if] ([variable-2 u].[property color] [operator ===] [string 'blue']));", 82 | "}"); 83 | 84 | MT("quasi", 85 | "[variable re][string-2 `fofdlakj${][variable x] [operator +] ([variable re][string-2 `foo`]) [operator +] [number 1][string-2 }fdsa`] [operator +] [number 2]"); 86 | 87 | MT("quasi_no_function", 88 | "[variable x] [operator =] [string-2 `fofdlakj${][variable x] [operator +] [string-2 `foo`] [operator +] [number 1][string-2 }fdsa`] [operator +] [number 2]"); 89 | 90 | MT("indent_statement", 91 | "[keyword var] [def x] [operator =] [number 10]", 92 | "[variable x] [operator +=] [variable y] [operator +]", 93 | " [atom Infinity]", 94 | "[keyword debugger];"); 95 | 96 | MT("indent_if", 97 | "[keyword if] ([number 1])", 98 | " [keyword break];", 99 | "[keyword else] [keyword if] ([number 2])", 100 | " [keyword continue];", 101 | "[keyword else]", 102 | " [number 10];", 103 | "[keyword if] ([number 1]) {", 104 | " [keyword break];", 105 | "} [keyword else] [keyword if] ([number 2]) {", 106 | " [keyword continue];", 107 | "} [keyword else] {", 108 | " [number 10];", 109 | "}"); 110 | 111 | MT("indent_for", 112 | "[keyword for] ([keyword var] [def i] [operator =] [number 0];", 113 | " [variable i] [operator <] [number 100];", 114 | " [variable i][operator ++])", 115 | " [variable doSomething]([variable i]);", 116 | "[keyword debugger];"); 117 | 118 | MT("indent_c_style", 119 | "[keyword function] [def foo]()", 120 | "{", 121 | " [keyword debugger];", 122 | "}"); 123 | 124 | MT("indent_else", 125 | "[keyword for] (;;)", 126 | " [keyword if] ([variable foo])", 127 | " [keyword if] ([variable bar])", 128 | " [number 1];", 129 | " [keyword else]", 130 | " [number 2];", 131 | " [keyword else]", 132 | " [number 3];"); 133 | 134 | MT("indent_funarg", 135 | "[variable foo]([number 10000],", 136 | " [keyword function]([def a]) {", 137 | " [keyword debugger];", 138 | "};"); 139 | 140 | MT("indent_below_if", 141 | "[keyword for] (;;)", 142 | " [keyword if] ([variable foo])", 143 | " [number 1];", 144 | "[number 2];"); 145 | 146 | MT("multilinestring", 147 | "[keyword var] [def x] [operator =] [string 'foo\\]", 148 | "[string bar'];"); 149 | 150 | MT("scary_regexp", 151 | "[string-2 /foo[[/]]bar/];"); 152 | 153 | MT("indent_strange_array", 154 | "[keyword var] [def x] [operator =] [[", 155 | " [number 1],,", 156 | " [number 2],", 157 | "]];", 158 | "[number 10];"); 159 | 160 | MT("param_default", 161 | "[keyword function] [def foo]([def x] [operator =] [string-2 `foo${][number 10][string-2 }bar`]) {", 162 | " [keyword return] [variable-2 x];", 163 | "}"); 164 | 165 | MT("new_target", 166 | "[keyword function] [def F]([def target]) {", 167 | " [keyword if] ([variable-2 target] [operator &&] [keyword new].[keyword target].[property name]) {", 168 | " [keyword return] [keyword new]", 169 | " .[keyword target];", 170 | " }", 171 | "}"); 172 | 173 | var jsonld_mode = CodeMirror.getMode( 174 | {indentUnit: 2}, 175 | {name: "javascript", jsonld: true} 176 | ); 177 | function LD(name) { 178 | test.mode(name, jsonld_mode, Array.prototype.slice.call(arguments, 1)); 179 | } 180 | 181 | LD("json_ld_keywords", 182 | '{', 183 | ' [meta "@context"]: {', 184 | ' [meta "@base"]: [string "http://example.com"],', 185 | ' [meta "@vocab"]: [string "http://xmlns.com/foaf/0.1/"],', 186 | ' [property "likesFlavor"]: {', 187 | ' [meta "@container"]: [meta "@list"]', 188 | ' [meta "@reverse"]: [string "@beFavoriteOf"]', 189 | ' },', 190 | ' [property "nick"]: { [meta "@container"]: [meta "@set"] },', 191 | ' [property "nick"]: { [meta "@container"]: [meta "@index"] }', 192 | ' },', 193 | ' [meta "@graph"]: [[ {', 194 | ' [meta "@id"]: [string "http://dbpedia.org/resource/John_Lennon"],', 195 | ' [property "name"]: [string "John Lennon"],', 196 | ' [property "modified"]: {', 197 | ' [meta "@value"]: [string "2010-05-29T14:17:39+02:00"],', 198 | ' [meta "@type"]: [string "http://www.w3.org/2001/XMLSchema#dateTime"]', 199 | ' }', 200 | ' } ]]', 201 | '}'); 202 | 203 | LD("json_ld_fake", 204 | '{', 205 | ' [property "@fake"]: [string "@fake"],', 206 | ' [property "@contextual"]: [string "@identifier"],', 207 | ' [property "user@domain.com"]: [string "@graphical"],', 208 | ' [property "@ID"]: [string "@@ID"]', 209 | '}'); 210 | })(); 211 | -------------------------------------------------------------------------------- /public/mode/javascript/typescript.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | CodeMirror: TypeScript mode 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 24 | 25 |
26 |

TypeScript mode

27 | 28 | 29 |
51 | 52 | 59 | 60 |

This is a specialization of the JavaScript mode.

61 |
62 | -------------------------------------------------------------------------------- /public/search.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/src/form.js: -------------------------------------------------------------------------------- 1 | /*global CodeMirror*/ 2 | var event = require('event') 3 | var request = require('request') 4 | var Notice = require('notice') 5 | var Emitter = require('emitter') 6 | var Nprogress = require('nprogress') 7 | 8 | var editor = CodeMirror.fromTextArea(document.getElementById('content'), { 9 | tabsize: 2, 10 | autofocus: true, 11 | lineNumbers: true, 12 | matchBrackets: true, 13 | lineWrapping: true, 14 | scrollbarStyle: 'simple' 15 | }) 16 | 17 | function Panel(root) { 18 | this.root = root 19 | this.input = this.root.querySelector('[name="route"]') 20 | var save = this.root.querySelector('.save') 21 | event.bind(save, 'click', this.save.bind(this)) 22 | } 23 | 24 | Emitter(Panel.prototype) 25 | 26 | Panel.prototype.save = function () { 27 | var route = this.input.value 28 | if (!route) { 29 | new Notice('route should not be empty', { 30 | type: 'error' 31 | }) 32 | return 33 | } 34 | if (!/^(GET|POST|PUT|DELETE|PATCH)\s/.test(route)) { 35 | new Notice('Need GET/POST/PUT/DELETE/PATH method', { 36 | type: 'error' 37 | }) 38 | return 39 | } 40 | if (/^\w+\shttp/.test(route)) { 41 | var method = route.match(/^\w+/)[0] 42 | route = method + ' '+ parseUrl(route.replace(method, '')) 43 | this.input.value = route 44 | } else if (!/\w+\s\//.test(route)) { 45 | new Notice('Invalid request url', { 46 | type: 'error' 47 | }) 48 | return 49 | } 50 | var content = editor.getValue() 51 | var self = this 52 | Nprogress.start() 53 | request 54 | .post('/_addapi') 55 | .type('form') 56 | .accept('json') 57 | .send({route: route, content: content}) 58 | .end(function (res) { 59 | Nprogress.done() 60 | if (res.error) { 61 | if (res.body && res.body.error) { 62 | new Notice(res.body.error, { 63 | type: 'error' 64 | }) 65 | } else { 66 | new Notice('request error ' + res.status, { 67 | type: 'error' 68 | }) 69 | } 70 | } else { 71 | new Notice('Saved:' + route, { 72 | type: 'success', 73 | duration: 2000 74 | }) 75 | self.emit('save', route) 76 | } 77 | }) 78 | } 79 | 80 | Panel.prototype.load = function (route) { 81 | Nprogress.start() 82 | var parts = route.split(' ') 83 | var self = this 84 | request(parts[0], parts[1]) 85 | .end(function (res) { 86 | Nprogress.done() 87 | if (res.error) { 88 | if (res.body && res.body.error) { 89 | new Notice(res.body.error, { 90 | type: 'error' 91 | }) 92 | } else { 93 | new Notice('request error ' + res.status, { 94 | type: 'error' 95 | }) 96 | } 97 | } else { 98 | self.input.value = route 99 | editor.setValue(res.text) 100 | } 101 | }) 102 | } 103 | 104 | Panel.prototype.empty = function () { 105 | this.input.value = '' 106 | editor.setValue('') 107 | } 108 | 109 | function parseUrl(url) { 110 | var parser = document.createElement('a') 111 | parser.href = url 112 | return parser.pathname + parser.search 113 | } 114 | 115 | module.exports = Panel 116 | -------------------------------------------------------------------------------- /public/src/index.js: -------------------------------------------------------------------------------- 1 | var event = require('event') 2 | var Form = require('./form') 3 | var Routes = require('./routes') 4 | var Base64 = require('js-base64').Base64; 5 | var Notice = require('notice') 6 | var filePicker = require('file-picker') 7 | var request = require('request') 8 | 9 | var routes = new Routes(document.querySelector('.left-panel')) 10 | var form = new Form(document.querySelector('.right-panel')) 11 | 12 | routes.on('active', function (li) { 13 | var route = li.querySelector('.text').textContent 14 | form.load(route) 15 | }) 16 | 17 | form.on('save', function (route) { 18 | var id = Base64.encode(route) 19 | if (routes.hasItem(route)) { 20 | routes.active(id, false) 21 | } else { 22 | routes.addItem(route, true) 23 | routes.active(id, false) 24 | } 25 | }) 26 | 27 | routes.on('empty', function () { 28 | form.empty() 29 | }) 30 | 31 | event.bind(document.getElementById('import'), 'click', function (e) { 32 | filePicker(function(files){ 33 | var file = files[0] 34 | var reader = new FileReader() 35 | reader.onload = function (e) { 36 | try { 37 | var obj = JSON.parse(reader.result) 38 | } catch(e) { 39 | new Notice('not valid json file', { 40 | type: 'error' 41 | }) 42 | } 43 | importApis(obj) 44 | } 45 | reader.readAsText(file) 46 | }) 47 | }) 48 | 49 | function importApis(data) { 50 | request 51 | .post('/_importapi') 52 | .type('json') 53 | .accept('json') 54 | .send(data) 55 | .end(function (res) { 56 | if (res.error) { 57 | if (res.body && res.body.error) { 58 | new Notice(res.body.error, { 59 | type: 'error' 60 | }) 61 | } else { 62 | new Notice('request error ' + res.status, { 63 | type: 'error' 64 | }) 65 | } 66 | } else { 67 | window.location.reload() 68 | } 69 | }) 70 | } 71 | 72 | event.bind(document, 'keydown', function (e) { 73 | if (e.keyCode == 83 && e.metaKey) { 74 | e.preventDefault() 75 | form.save() 76 | } 77 | }) 78 | -------------------------------------------------------------------------------- /public/src/item.html: -------------------------------------------------------------------------------- 1 |
  • 2 |
    {{route}}×
    3 |
  • 4 | -------------------------------------------------------------------------------- /public/src/routes.js: -------------------------------------------------------------------------------- 1 | var radio = require('radio') 2 | var Notice = require('notice') 3 | var request = require('request') 4 | var Emitter = require('emitter') 5 | var classes = require('classes') 6 | var event = require('event') 7 | var delegate = require('delegate') 8 | var closest = require('closest') 9 | var transitionEnd = require('transitionend-property') 10 | var spin = require('spin') 11 | var domify = require('domify') 12 | var template = require('./item.html') 13 | var Base64 = require('js-base64').Base64 14 | var Sortable = require('sweet-sortable') 15 | 16 | function Routes(root) { 17 | var search = root.querySelector('.search') 18 | this.root = root 19 | this.list = document.querySelector('#routes > ul') 20 | event.bind(search, 'input', this.filter.bind(this)) 21 | delegate.bind(this.list, '.close', 'click', this.removeItem.bind(this)) 22 | delegate.bind(this.list, '.item', 'click', this.activeItem.bind(this)) 23 | this.loadItems() 24 | var sortable = new Sortable(this.list) 25 | sortable.bind('li') 26 | sortable.on('update', this.saveOrder.bind(this)) 27 | } 28 | 29 | Emitter(Routes.prototype) 30 | 31 | Routes.prototype.saveOrder = function () { 32 | var lis = this.list.children 33 | var ids = [] 34 | for (var i = 0, l = lis.length; i < l; i++) { 35 | var id = lis[i].getAttribute('data-id') 36 | if (id) ids.push(id) 37 | } 38 | window.localStorage.setItem('routes', JSON.stringify(ids)) 39 | } 40 | 41 | Routes.prototype.loadItems = function () { 42 | var s = spin(this.root) 43 | var self = this 44 | request 45 | .get('/_listapi') 46 | .end(function (res) { 47 | s.remove() 48 | if (res.error) { 49 | if (res.body && res.body.error) { 50 | new Notice(res.body.error, { 51 | type: 'error' 52 | }) 53 | } else { 54 | new Notice('request error ' + res.status, { 55 | type: 'error' 56 | }) 57 | } 58 | } else { 59 | var keys = self.orderItems(res.body.keys) 60 | keys.forEach(function (key) { 61 | self.addItem(key) 62 | }) 63 | var active_id = window.localStorage.getItem('active') 64 | if (active_id != null) { 65 | self.active(active_id, true) 66 | } else { 67 | var li = self.list.children[0] 68 | if (li) self.active(li, true) 69 | } 70 | } 71 | }) 72 | } 73 | 74 | Routes.prototype.orderItems = function (keys) { 75 | var res = [] 76 | var routes = JSON.parse(window.localStorage.getItem('routes')) || [] 77 | for (var i = 0, l = routes.length; i < l; i++) { 78 | var route = Base64.decode(routes[i]) 79 | if (keys.indexOf(route) != -1) { 80 | res.push(route) 81 | } 82 | } 83 | keys.forEach(function (key) { 84 | if (res.indexOf(key) == -1) { 85 | res.unshift(key) 86 | } 87 | }) 88 | return res 89 | } 90 | 91 | Routes.prototype.addItem = function (route, prepend) { 92 | var el = domify(template.replace('{{route}}', route)) 93 | el.setAttribute('data-id', Base64.encode(route)) 94 | if (prepend) { 95 | classes(el).add('adding') 96 | if (this.list.children.length) { 97 | this.list.insertBefore(el, this.list.firstElementChild) 98 | } else { 99 | this.list.appendChild(el) 100 | } 101 | } else { 102 | this.list.appendChild(el) 103 | } 104 | setTimeout(function () { 105 | classes(el).remove('adding') 106 | }, 1000) 107 | if (prepend) this.saveOrder() 108 | } 109 | 110 | Routes.prototype.hasItem = function (route) { 111 | var id = Base64.encode(route) 112 | return this.list.querySelector('[data-id="' + id + '"]') != null 113 | } 114 | 115 | Routes.prototype.filter = function (e) { 116 | var val = e.target.value 117 | var lis = [].slice.call(this.list.querySelectorAll('li')) 118 | lis.forEach(function (li) { 119 | var text = li.textContent 120 | if (val == '' || text.indexOf(val) !== -1) { 121 | li.style.display = 'block' 122 | } else { 123 | li.style.display = 'none' 124 | } 125 | }) 126 | } 127 | 128 | Routes.prototype.removeItem = function (e) { 129 | var li = closest(e.target, 'li') 130 | if (classes(li).has('removing')) return 131 | e.stopPropagation() 132 | e.preventDefault() 133 | classes(li).add('removing') 134 | var route = li.querySelector('.text').textContent 135 | var self = this 136 | request 137 | .del('/_deleteapi') 138 | .query({route: route}) 139 | .end(function (res) { 140 | if (res.error) { 141 | if (res.body.error) { 142 | new Notice(res.body.error, { 143 | type: 'error' 144 | }) 145 | } else { 146 | new Notice('request error ' + res.status, { 147 | type: 'error' 148 | }) 149 | } 150 | } else { 151 | new Notice('Removed: ' + route, { 152 | type: 'success', 153 | duration: 2000 154 | }) 155 | self.emit('remove', route) 156 | event.bind(li, transitionEnd, function () { 157 | if (li.parentNode) li.parentNode.removeChild(li) 158 | self.saveOrder() 159 | }) 160 | classes(li).add('remove') 161 | if (self.list.children.length === 1) { 162 | return self.emit('empty') 163 | } 164 | if (classes(li).has('active')) { 165 | var lis = self.list.children 166 | for (var i = 0, l = lis.length; i < l; i++) { 167 | if (!classes(lis[i]).has('removing')) { 168 | self.active(lis[i], true) 169 | break; 170 | } 171 | } 172 | } 173 | } 174 | }) 175 | } 176 | 177 | Routes.prototype.active = function (li, emit) { 178 | if (typeof li === 'string') { 179 | li = this.list.querySelector('[data-id="'+li+'"]') 180 | if (!li && this.list.children.length) { 181 | li = this.list.children[0] 182 | } 183 | } 184 | if (!li) return 185 | if (classes(li).has('active')) return 186 | radio(li) 187 | if (emit) { 188 | window.localStorage.setItem('active', li.getAttribute('data-id')) 189 | this.emit('active', li) 190 | } 191 | } 192 | 193 | Routes.prototype.activeItem = function (e) { 194 | var li = closest(e.target, 'li') 195 | if (e.defaultPrevented) return 196 | if (!li) return 197 | this.active(li, true) 198 | } 199 | 200 | module.exports = Routes 201 | -------------------------------------------------------------------------------- /public/style.css: -------------------------------------------------------------------------------- 1 | body { 2 | font: 500 14px/1.6 "Helvetica Neue", Helvetica, sans-serif; 3 | padding: 0px; 4 | margin: 0px; 5 | overflow: hidden; 6 | -webkit-touch-callout: none; 7 | -webkit-text-size-adjust: 100%; 8 | -ms-text-size-adjust: 100%; 9 | text-size-adjust: 100%; 10 | -webkit-font-smoothing: antialiased; 11 | } 12 | *,*::after,*::before { 13 | -webkit-box-sizing: border-box; 14 | -moz-box-sizing: border-box; 15 | box-sizing: border-box; 16 | } 17 | a { 18 | color: #fff; 19 | text-decoration: none; 20 | } 21 | a:visited { 22 | color: #fff; 23 | } 24 | a:hover { 25 | color: #fff; 26 | } 27 | 28 | blockquote { 29 | padding: 10px 20px; 30 | margin: 0 0 20px; 31 | font-size: 17.5px; 32 | border-left: 5px solid #EEE; 33 | } 34 | code { 35 | font-family: Menlo,Monaco,Consolas,"Courier New",monospace; 36 | padding: 2px 4px; 37 | font-size: 90%; 38 | color: #C7254E; 39 | background-color: #F9F2F4; 40 | border-radius: 4px; 41 | } 42 | code, pre { 43 | font-family: Monaco, Menlo, Consolas, "Courier New", monospace; 44 | } 45 | pre { 46 | display: block; 47 | padding: 9.5px; 48 | margin: 0 0 10px; 49 | font-size: 13px; 50 | line-height: 1.428571429; 51 | color: #333333; 52 | word-break: break-all; 53 | word-wrap: break-word; 54 | background-color: #f5f5f5; 55 | border: 1px solid #cccccc; 56 | border-radius: 4px; 57 | white-space: pre-wrap; 58 | } 59 | .container { 60 | height: 100vh; 61 | position: relative; 62 | } 63 | .left-panel { 64 | position: absolute; 65 | left: 0px; 66 | top: 0px; 67 | bottom: 0px; 68 | width: 300px; 69 | overflow-y: auto; 70 | padding: 1.5em; 71 | border-right: 1px solid #f2f2f2; 72 | } 73 | .left-panel .header { 74 | position: relative; 75 | height: 60px; 76 | display: flex; 77 | align-items: center; 78 | justify-content: center; 79 | } 80 | .left-panel .header .search{ 81 | display: block; 82 | width: 180px; 83 | } 84 | .right-panel .body { 85 | overflow: hidden; 86 | padding: 5px 10px; 87 | } 88 | .right-panel { 89 | padding: 1.5em; 90 | position: absolute; 91 | right: 0px; 92 | left: 300px; 93 | top: 0px; 94 | bottom: 0px; 95 | flex: 0 1 auto; 96 | } 97 | .right-panel .header { 98 | height: 60px; 99 | display: flex; 100 | align-items: center; 101 | padding: 0 10px; 102 | } 103 | .right-panel .header a{ 104 | display: inline-block; 105 | } 106 | .btn { 107 | padding: 0.5em 1.5em; 108 | background-color: #000; 109 | color: #fff; 110 | margin-bottom: 0; 111 | font-weight: normal; 112 | text-align: center; 113 | vertical-align: middle; 114 | cursor: pointer; 115 | background-image: none; 116 | white-space: nowrap; 117 | font-size: 14px; 118 | line-height: 1.42857143; 119 | touch-action: manipulation; 120 | -webkit-user-select: none; 121 | user-select: none; 122 | border: none; 123 | border-radius: 8px; 124 | outline: none; 125 | } 126 | .btn:hover, 127 | .btn:active { 128 | color: #fff; 129 | background-color: #333; 130 | outline: none; 131 | } 132 | #routes { 133 | position: relative; 134 | padding: 10px 0px; 135 | margin: 2em auto; 136 | -webkit-user-select: none; 137 | user-select: none; 138 | -moz-user-select: none; 139 | -ms-user-select: none; 140 | -o-user-select: none; 141 | } 142 | #routes li { 143 | list-style: none; 144 | font-weight: lighter; 145 | line-height: 30px; 146 | padding: 5px 10px; 147 | margin: 0px; 148 | font-weight: 300; 149 | background-color: #fff; 150 | border-bottom: 1px solid #ccc; 151 | } 152 | #routes li .item{ 153 | position: relative; 154 | } 155 | #routes li.remove { 156 | transition: all 200ms ease-in; 157 | transform: translateX(-90px) 158 | } 159 | #routes li.adding { 160 | animation: 0.5s ease-in reverse adding; 161 | } 162 | #routes li:hover { 163 | cursor: pointer; 164 | } 165 | #routes li.active { 166 | color: #07A2EA; 167 | } 168 | #routes ul { 169 | padding: 0px; 170 | margin: 0px; 171 | } 172 | input[type="search"], 173 | input[type="text"] { 174 | display: block; 175 | width: 100%; 176 | height: 34px; 177 | padding: 6px 12px; 178 | font-size: 14px; 179 | line-height: 1.42857143; 180 | color: #000; 181 | background-color: #fff; 182 | background-image: none; 183 | border-top: none; 184 | border-left: none; 185 | border-right: none; 186 | border-bottom: 1px solid #ccc; 187 | border-radius: 0px; 188 | -webkit-transition: border-color ease-in-out 0.15s, box-shadow ease-in-out 0.15s; 189 | -o-transition: border-color ease-in-out 0.15s, box-shadow ease-in-out 0.15s; 190 | transition: border-color ease-in-out 0.15s, box-shadow ease-in-out 0.15s; 191 | } 192 | input[type="search"]:focus, 193 | input[type="text"]:focus { 194 | border-color: #66afe9; 195 | outline: 0; 196 | } 197 | 198 | /* 199 | * Simple Grid 200 | * Learn More - http://dallasbass.com/simple-grid-a-lightweight-responsive-css-grid/ 201 | * Project Page - http://thisisdallas.github.com/Simple-Grid/ 202 | * Author - Dallas Bass 203 | * 204 | * .grid.grid-pad>.col-4-12~.col-8-12 205 | */ 206 | [class*='col-'] { 207 | float: left; 208 | padding-right: 20px; 209 | } 210 | [class*='col-']:last-of-type { 211 | padding-right: 0px; 212 | } 213 | .grid { 214 | width: 100%; 215 | max-width: 1140px; 216 | min-width: 755px; 217 | margin: 0 auto; 218 | overflow: hidden; 219 | } 220 | .grid:after { 221 | content: ""; 222 | display: table; 223 | clear: both; 224 | } 225 | .grid-pad { 226 | padding: 20px 0 0px 20px; 227 | } 228 | .grid-pad > [class*='col-']:last-of-type { 229 | padding-right: 20px; 230 | } 231 | /* Content Columns */ 232 | .col-1-1 { 233 | width: 100%; 234 | } 235 | .col-2-3, .col-8-12 { 236 | width: 66.66%; 237 | } 238 | .col-1-2, .col-6-12 { 239 | width: 50%; 240 | } 241 | .col-1-3, .col-4-12 { 242 | width: 33.33%; 243 | } 244 | .col-1-4, .col-3-12 { 245 | width: 25%; 246 | } 247 | .col-1-5 { 248 | width: 20%; 249 | } 250 | .col-1-6, .col-2-12 { 251 | width: 16.667%; 252 | } 253 | .col-1-7 { 254 | width: 14.28%; 255 | } 256 | 257 | .col-1-8 { 258 | width: 12.5%; 259 | } 260 | .col-1-9 { 261 | width: 11.1%; 262 | } 263 | .col-1-10 { 264 | width: 10%; 265 | } 266 | .col-1-11 { 267 | width: 9.09%; 268 | } 269 | .col-1-12 { 270 | width: 8.33% 271 | } 272 | /* Layout Columns */ 273 | .col-11-12 { 274 | width: 91.66% 275 | } 276 | .col-10-12 { 277 | width: 83.333%; 278 | } 279 | .col-9-12 { 280 | width: 75%; 281 | } 282 | .col-5-12 { 283 | width: 41.66%; 284 | } 285 | .col-7-12 { 286 | width: 58.33% 287 | } 288 | @media handheld, only screen and (max-width: 767px) { 289 | .grid { 290 | width: 100%; 291 | min-width: 0; 292 | margin-left: 0px; 293 | margin-right: 0px; 294 | padding-left: 0px; 295 | padding-right: 0px; 296 | } 297 | [class*='col-'] { 298 | width: auto; 299 | float: none; 300 | margin-left: 0px; 301 | margin-right: 0px; 302 | margin-top: 10px; 303 | margin-bottom: 10px; 304 | padding-left: 20px; 305 | padding-right: 20px; 306 | } 307 | } 308 | .body .CodeMirror { 309 | height: calc(100vh - 100px) 310 | } 311 | a.close { 312 | position: absolute; 313 | top: -1px; 314 | right: 0px; 315 | text-decoration: none; 316 | color: #888; 317 | font-size: 16px; 318 | font-weight: bold; 319 | display: none; 320 | } 321 | li:hover a.close { 322 | display: block; 323 | } 324 | a.close:hover { 325 | color: black; 326 | } 327 | 328 | a.close:active { 329 | margin-top: 1px; 330 | } 331 | .notice-container { 332 | position: fixed; 333 | top: 0; 334 | left: 0; 335 | width: 100%; 336 | z-index: 999999; 337 | } 338 | .notice-container .notice-item { 339 | position: relative; 340 | font: 500 16px/1.8 "Georgia","Xin Gothic","Hiragino Sans GB","WenQuanYi Micro Hei",sans-serif; 341 | background: #fefefe; 342 | background: rgba(255,255,255,0.9); 343 | color: #565656; 344 | padding: 10px 20px; 345 | box-sizing: border-box; 346 | border-bottom: 1px solid #efefef; 347 | text-align: center; 348 | transition: all .2s ease-in-out; 349 | transform-origin: top; 350 | } 351 | /* colors from bootstrap */ 352 | .notice-container .notice-warning { 353 | background: #fcf8e3; 354 | background: rgba(252, 248, 227, 0.9); 355 | border-color: #fbeed5; 356 | color: #c09853; 357 | } 358 | 359 | .notice-container .notice-success { 360 | background: #EAFBE3; 361 | background: rgba(221, 242, 210, 0.9); 362 | border-color: #D6E9C6; 363 | color: #3FB16F; 364 | } 365 | 366 | .notice-container .notice-danger, 367 | .notice-container .notice-error { 368 | background: #f2dede; 369 | background: rgba(242, 222, 222, 0.9); 370 | border-color: #ebccd1; 371 | color: #a94442; 372 | } 373 | 374 | .notice-container .notice-content { 375 | color: inherit; 376 | text-decoration: none; 377 | margin: 0 auto; 378 | max-width: 650px; 379 | } 380 | .notice-container .notice-close { 381 | position: absolute; 382 | top: 5px; 383 | height: 40px; 384 | right: 20px; 385 | width: 40px; 386 | cursor: pointer; 387 | font: 400 normal 22px/40px "Arial", sans-serif; 388 | color: rgba(231, 76, 60, 0.6); 389 | } 390 | .notice-container .notice-dismiss { 391 | transform: rotateX(-75deg); 392 | opacity: 0; 393 | } 394 | .CodeMirror-simplescroll-horizontal div, .CodeMirror-simplescroll-vertical div { 395 | position: absolute; 396 | background: #ccc; 397 | -moz-box-sizing: border-box; 398 | box-sizing: border-box; 399 | border: 1px solid #bbb; 400 | border-radius: 2px; 401 | } 402 | 403 | .CodeMirror-simplescroll-horizontal, .CodeMirror-simplescroll-vertical { 404 | position: absolute; 405 | z-index: 6; 406 | background: #eee; 407 | } 408 | 409 | .CodeMirror-simplescroll-horizontal { 410 | bottom: 0; left: 0; 411 | height: 8px; 412 | } 413 | .CodeMirror-simplescroll-horizontal div { 414 | bottom: 0; 415 | height: 100%; 416 | } 417 | 418 | .CodeMirror-simplescroll-vertical { 419 | right: 0; top: 0; 420 | width: 8px; 421 | } 422 | .CodeMirror-simplescroll-vertical div { 423 | right: 0; 424 | width: 100%; 425 | } 426 | 427 | 428 | .CodeMirror-overlayscroll .CodeMirror-scrollbar-filler, .CodeMirror-overlayscroll .CodeMirror-gutter-filler { 429 | display: none; 430 | } 431 | 432 | .CodeMirror-overlayscroll-horizontal div, .CodeMirror-overlayscroll-vertical div { 433 | position: absolute; 434 | background: #bcd; 435 | border-radius: 3px; 436 | } 437 | 438 | .CodeMirror-overlayscroll-horizontal, .CodeMirror-overlayscroll-vertical { 439 | position: absolute; 440 | z-index: 6; 441 | } 442 | 443 | .CodeMirror-overlayscroll-horizontal { 444 | bottom: 0; left: 0; 445 | height: 6px; 446 | } 447 | .CodeMirror-overlayscroll-horizontal div { 448 | bottom: 0; 449 | height: 100%; 450 | } 451 | 452 | .CodeMirror-overlayscroll-vertical { 453 | right: 0; top: 0; 454 | width: 6px; 455 | } 456 | .CodeMirror-overlayscroll-vertical div { 457 | right: 0; 458 | width: 100%; 459 | } 460 | .icon-search { 461 | height: 14px; 462 | width: 14px; 463 | position: absolute; 464 | display: block; 465 | left: 40px; 466 | top: 23px; 467 | background-image: url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCA2Mi45OTMgNjIuOTkzIj48cGF0aCBkPSJNNjIuNTggNjAuNTk0TDQxLjMxMyAzOS4zM2MzLjcxMi00LjE4IDUuOTg4LTkuNjYgNS45ODgtMTUuNjc4QzQ3LjMgMTAuNjEgMzYuNjkzLjAwMiAyMy42NS4wMDIgMTAuNjEgMCAwIDEwLjYxIDAgMjMuNjVjMCAxMy4wNCAxMC42MSAyMy42NSAyMy42NSAyMy42NSA2LjAxNyAwIDExLjQ5OC0yLjI3NiAxNS42NzgtNS45ODhMNjAuNTkzIDYyLjU4YTEuNDAzIDEuNDAzIDAgMCAwIDEuOTg3IDBjLjU1LS41NS41NS0xLjQzNyAwLTEuOTg2ek0yMy42NSA0NC40OTJjLTExLjQ5IDAtMjAuODQtOS4zNDgtMjAuODQtMjAuODRzOS4zNS0yMC44NCAyMC44NC0yMC44NGMxMS40OTIgMCAyMC44NCA5LjM0OCAyMC44NCAyMC44NCAwIDUuMjQtMS45NTcgMTAuMDIzLTUuMTYyIDEzLjY5LS42Mi43MDUtMS4yOCAxLjM2Ny0xLjk4NyAxLjk4Ni0zLjY2NSAzLjIwNi04LjQ0OCA1LjE2NC0xMy42OSA1LjE2NHoiIGZpbGw9IiMwMTAwMDIiLz48L3N2Zz4=); 468 | } 469 | 470 | @keyframes adding { 471 | from { 472 | transform: translateX(0) 473 | } 474 | to { 475 | transform: translateX(-60px) 476 | } 477 | } 478 | /* Make clicks pass-through */ 479 | #nprogress { 480 | pointer-events: none; 481 | } 482 | 483 | #nprogress .bar { 484 | background: #29d; 485 | 486 | position: fixed; 487 | z-index: 1031; 488 | top: 0; 489 | left: 0; 490 | 491 | width: 100%; 492 | height: 2px; 493 | } 494 | 495 | /* Fancy blur effect */ 496 | #nprogress .peg { 497 | display: block; 498 | position: absolute; 499 | right: 0px; 500 | width: 100px; 501 | height: 100%; 502 | box-shadow: 0 0 10px #29d, 0 0 5px #29d; 503 | opacity: 1.0; 504 | 505 | -webkit-transform: rotate(3deg) translate(0px, -4px); 506 | -ms-transform: rotate(3deg) translate(0px, -4px); 507 | transform: rotate(3deg) translate(0px, -4px); 508 | } 509 | 510 | /* Remove these to get rid of the spinner */ 511 | #nprogress .spinner { 512 | display: block; 513 | position: fixed; 514 | z-index: 1031; 515 | top: 15px; 516 | right: 15px; 517 | } 518 | 519 | #nprogress .spinner-icon { 520 | width: 18px; 521 | height: 18px; 522 | box-sizing: border-box; 523 | 524 | border: solid 2px transparent; 525 | border-top-color: #29d; 526 | border-left-color: #29d; 527 | border-radius: 50%; 528 | 529 | -webkit-animation: nprogress-spinner 400ms linear infinite; 530 | animation: nprogress-spinner 400ms linear infinite; 531 | } 532 | 533 | .nprogress-custom-parent { 534 | overflow: hidden; 535 | position: relative; 536 | } 537 | 538 | .nprogress-custom-parent #nprogress .spinner, 539 | .nprogress-custom-parent #nprogress .bar { 540 | position: absolute; 541 | } 542 | 543 | @-webkit-keyframes nprogress-spinner { 544 | 0% { -webkit-transform: rotate(0deg); } 545 | 100% { -webkit-transform: rotate(360deg); } 546 | } 547 | @keyframes nprogress-spinner { 548 | 0% { transform: rotate(0deg); } 549 | 100% { transform: rotate(360deg); } 550 | } 551 | -------------------------------------------------------------------------------- /server.js: -------------------------------------------------------------------------------- 1 | var config = require('./config.json') 2 | var redisClient = require('redis').createClient(config.redis.port, config.redis.host) 3 | var parse = require('co-body') 4 | var cors = require('koa-cors') 5 | var wrapper = require('co-redis') 6 | var redisCo = wrapper(redisClient) 7 | var liveload = require('koa-liveload') 8 | var koa = require('koa') 9 | var app = koa() 10 | var router = require('koa-router')() 11 | var logger = require('koa-logger') 12 | 13 | 14 | router.get('/_listapi', function* (next) { 15 | var data = yield redisCo.hkeys('apis') 16 | this.type = 'json' 17 | this.body = {keys: data || []} 18 | }) 19 | 20 | router.delete('/_deleteapi', function* (next) { 21 | if (this.query && this.query.route) { 22 | yield redisCo.hdel('apis', this.query.route) 23 | this.status = 200 24 | } else { 25 | this.type = 'json' 26 | this.body = {error: 'route required'} 27 | this.status = 401 28 | } 29 | }) 30 | 31 | router.post('/_addapi', function* (next) { 32 | var body = yield parse.form(this.req,{limit: '30mb'} ) 33 | if (!body.route || !body.content || !/^(GET|PUT|POST|DELETE|PATCH)/.test(body.route)) { 34 | this.body = { error: 'route and content empty or invalid'} 35 | this.status = 401 36 | } else { 37 | yield redisCo.hset('apis', body.route, body.content) 38 | this.res.type = 'json' 39 | this.body = {success: true} 40 | } 41 | }) 42 | 43 | router.get('/_exportapi', function* () { 44 | var data = yield redisCo.hgetall('apis') 45 | this.body = JSON.stringify({data: data || {}}, null, ' ') 46 | this.response.attachment('apis.json') 47 | }) 48 | 49 | router.post('/_importapi', function* () { 50 | var body = yield parse.json(this.req, {limit: '30mb'}) 51 | if (!body.data) { 52 | this.body = { error: 'no data found'} 53 | this.status = 401 54 | } else { 55 | var data = body.data 56 | for (var key in data) { 57 | yield redisCo.hset('apis', key, data[key]) 58 | } 59 | this.res.type = 'json' 60 | this.body = {success: true} 61 | } 62 | }) 63 | 64 | app.use(cors()) 65 | app.use(router.routes()) 66 | 67 | app.use(function* (next) { 68 | // static resource 69 | if (/^\/(.*\.(js|css))?$/.test(this.path)) { 70 | yield next 71 | } else { 72 | var key = this.method + ' ' + this.path 73 | if (this.querystring) { 74 | key = key + '?' + this.querystring 75 | } 76 | var content = yield redisCo.hget('apis', key) 77 | if (content) { 78 | this.type = 'json' 79 | this.body = content 80 | } else { 81 | yield next 82 | } 83 | } 84 | }) 85 | 86 | var root = __dirname + '/public' 87 | app.use(logger()) 88 | app.use(liveload(root)) 89 | app.use(require('koa-static')(root, { 90 | index: 'index.html' 91 | })) 92 | 93 | app.listen(config.port, function () { 94 | console.log('server listening on ' + config.port) 95 | }) 96 | -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | entry: './public/src/index.js', 3 | output: { 4 | path: 'public', 5 | filename: 'bundle.js' 6 | }, 7 | module: { 8 | loaders: [ 9 | {test: /\.html$/, loader: 'html'} 10 | ] 11 | }, 12 | plugins: [] 13 | } 14 | --------------------------------------------------------------------------------