├── .prettierignore ├── .editorconfig ├── images ├── demo.jpg ├── folder.png ├── intro.png ├── wechat.png └── screenshot.png ├── fonts └── icomoon.woff ├── .prettierrc ├── background.js ├── lib ├── codemirror │ ├── lib │ │ ├── util │ │ │ ├── simple-hint.css │ │ │ ├── dialog.css │ │ │ ├── runmode.js │ │ │ ├── loadmode.js │ │ │ ├── continuelist.js │ │ │ ├── match-highlighter.js │ │ │ ├── overlay.js │ │ │ ├── dialog.js │ │ │ ├── xml-hint.js │ │ │ ├── multiplex.js │ │ │ ├── runmode-standalone.js │ │ │ ├── simple-hint.js │ │ │ ├── pig-hint.js │ │ │ ├── searchcursor.js │ │ │ ├── javascript-hint.js │ │ │ ├── search.js │ │ │ ├── closetag.js │ │ │ ├── foldcode.js │ │ │ └── formatting.js │ │ └── codemirror.css │ ├── gfm │ │ ├── index.html │ │ └── gfm.js │ ├── xml │ │ └── index.html │ ├── javascript │ │ └── index.html │ ├── overlay.js │ ├── htmlmixed │ │ └── htmlmixed.js │ └── markdown │ │ └── index.html └── md5.js ├── package.json ├── .gitignore ├── .vscode └── settings.json ├── LICENSE ├── content_script ├── wxtoken.js ├── blog.inject.js └── weekly.inject.js ├── manifest.json ├── README.md ├── scripts ├── getContent.js ├── imgReduce.js ├── common.js ├── editor.js └── weeklyInjector.js ├── styles ├── base16-light.css ├── prism-funky.css ├── prism-tomorrow.css ├── prism-okaidia.css ├── prism-dark.css ├── prism.css ├── prism-solarizedlight.css ├── prism-xonokai.css └── prism-twilight.css └── index.html /.prettierignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | dist 3 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | end_of_line = lf -------------------------------------------------------------------------------- /images/demo.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AngusFu/wx-platform-editor/HEAD/images/demo.jpg -------------------------------------------------------------------------------- /fonts/icomoon.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AngusFu/wx-platform-editor/HEAD/fonts/icomoon.woff -------------------------------------------------------------------------------- /images/folder.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AngusFu/wx-platform-editor/HEAD/images/folder.png -------------------------------------------------------------------------------- /images/intro.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AngusFu/wx-platform-editor/HEAD/images/intro.png -------------------------------------------------------------------------------- /images/wechat.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AngusFu/wx-platform-editor/HEAD/images/wechat.png -------------------------------------------------------------------------------- /images/screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AngusFu/wx-platform-editor/HEAD/images/screenshot.png -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "printWidth": 80, 3 | "trailingComma": "none", 4 | "tabWidth": 2, 5 | "semi": false, 6 | "singleQuote": true, 7 | "useTabs": false, 8 | "bracketSpacing": true, 9 | "insertPragma": false, 10 | "arrowParens": "avoid", 11 | "htmlWhitespaceSensitivity": "ignore" 12 | } 13 | -------------------------------------------------------------------------------- /background.js: -------------------------------------------------------------------------------- 1 | chrome.browserAction.onClicked.addListener(function updateIcon() { 2 | chrome.tabs.query( 3 | { 4 | url: new URL('./index.html', location.href).href 5 | }, 6 | function(tabs) { 7 | if (!tabs.length) { 8 | chrome.tabs.create({ 9 | url: './index.html' 10 | }) 11 | } else { 12 | chrome.tabs.update(tabs[0].id, { 13 | selected: true 14 | }) 15 | } 16 | } 17 | ) 18 | }) 19 | -------------------------------------------------------------------------------- /lib/codemirror/lib/util/simple-hint.css: -------------------------------------------------------------------------------- 1 | .CodeMirror-completions { 2 | position: absolute; 3 | z-index: 10; 4 | overflow: hidden; 5 | -webkit-box-shadow: 2px 3px 5px rgba(0, 0, 0, 0.2); 6 | -moz-box-shadow: 2px 3px 5px rgba(0, 0, 0, 0.2); 7 | box-shadow: 2px 3px 5px rgba(0, 0, 0, 0.2); 8 | } 9 | .CodeMirror-completions select { 10 | background: #fafafa; 11 | outline: none; 12 | border: none; 13 | padding: 0; 14 | margin: 0; 15 | font-family: monospace; 16 | } 17 | -------------------------------------------------------------------------------- /lib/codemirror/lib/util/dialog.css: -------------------------------------------------------------------------------- 1 | .CodeMirror-dialog { 2 | position: relative; 3 | } 4 | 5 | .CodeMirror-dialog > div { 6 | position: absolute; 7 | top: 0; 8 | left: 0; 9 | right: 0; 10 | background: white; 11 | border-bottom: 1px solid #eee; 12 | z-index: 15; 13 | padding: 0.1em 0.8em; 14 | overflow: hidden; 15 | color: #333; 16 | } 17 | 18 | .CodeMirror-dialog input { 19 | border: none; 20 | outline: none; 21 | background: transparent; 22 | width: 20em; 23 | color: inherit; 24 | font-family: monospace; 25 | } 26 | 27 | .CodeMirror-dialog button { 28 | font-size: 70%; 29 | } 30 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "wx-platform-editor-master", 3 | "version": "2.0.0", 4 | "description": "微信公众号编辑助手插件", 5 | "main": "background.js", 6 | "directories": { 7 | "lib": "lib" 8 | }, 9 | "scripts": { 10 | "format": "pretty-quick --pattern \"**/*.*(js|jsx|scss|css)\"" 11 | }, 12 | "repository": { 13 | "type": "git", 14 | "url": "git+https://github.com/AngusFu/wx-platform-editor.git" 15 | }, 16 | "author": "", 17 | "license": "ISC", 18 | "bugs": { 19 | "url": "https://github.com/AngusFu/wx-platform-editor/issues" 20 | }, 21 | "homepage": "https://github.com/AngusFu/wx-platform-editor#readme", 22 | "devDependencies": { 23 | "prettier": "^1.18.2", 24 | "pretty-quick": "^1.11.1" 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | 6 | # Runtime data 7 | pids 8 | *.pid 9 | *.seed 10 | 11 | # Directory for instrumented libs generated by jscoverage/JSCover 12 | lib-cov 13 | 14 | # Coverage directory used by tools like istanbul 15 | coverage 16 | 17 | # nyc test coverage 18 | .nyc_output 19 | 20 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 21 | .grunt 22 | 23 | # node-waf configuration 24 | .lock-wscript 25 | 26 | # Compiled binary addons (http://nodejs.org/api/addons.html) 27 | build/Release 28 | 29 | # Dependency directories 30 | node_modules 31 | jspm_packages 32 | 33 | # Optional npm cache directory 34 | .npm 35 | 36 | # Optional REPL history 37 | .node_repl_history 38 | 39 | # OSX 40 | .DS_Store 41 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | // 将设置放入此文件中以覆盖默认设置 2 | { 3 | "editor.insertSpaces": true, 4 | "editor.fontSize": 18, 5 | "editor.wrappingColumn": 100, 6 | "editor.mouseWheelScrollSensitivity": 2, 7 | "editor.renderWhitespace": "all", 8 | "editor.renderIndentGuides": true, 9 | "editor.renderControlCharacters": true, 10 | "editor.tabSize": 2, 11 | "typescript.check.tscVersion": false, 12 | "emmet.preferences": { 13 | "css.autoInsertVendorPrefixes": false, 14 | "scss.autoInsertVendorPrefixes": false, 15 | "sass.autoInsertVendorPrefixes": false, 16 | "less.autoInsertVendorPrefixes": false 17 | }, 18 | "files.associations": { 19 | "*.html": "html", 20 | "*.css": "css", 21 | "*.demo": "html" 22 | }, 23 | "window.zoomLevel": -1, 24 | "vsicons.dontShowNewVersionMessage": true, 25 | "jshint.enable": false 26 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 文蔺 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /content_script/wxtoken.js: -------------------------------------------------------------------------------- 1 | /** 2 | * get wexin token 3 | * so that we can open editor directly 4 | * using a template string 5 | */ 6 | let url = new URL(location.href) 7 | let params = new URLSearchParams(url.search) 8 | let type = params.get('t') 9 | let token = params.get('token') 10 | 11 | let hasError = 12 | document.querySelector('.page_error') || 13 | document.querySelector('.page_timeout') 14 | let isSendMsg = /masssendpage\?/.test(location.href) 15 | let isEditor = /media\/appmsg_edit/.test(type) 16 | 17 | // any wexin page that contains token 18 | // except editor page 19 | if (token && !hasError && !isEditor && !isSendMsg) { 20 | console.log(token) 21 | 22 | const onMessage = { 23 | noop() {}, 24 | 25 | shakehands() { 26 | chrome.runtime.sendMessage(null, { 27 | type: 'token', 28 | token 29 | }) 30 | } 31 | } 32 | 33 | // listen for handshake 34 | chrome.runtime.onMessage.addListener(function(message) { 35 | console.info(message) 36 | ;(onMessage[message.type] || onMessage['noop'])(message) 37 | }) 38 | 39 | // send message at once 40 | chrome.runtime.sendMessage(null, { 41 | type: 'token', 42 | token 43 | }) 44 | } 45 | -------------------------------------------------------------------------------- /manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "微信公众号编辑助手", 3 | "description": "weixin public platform editor", 4 | "version": "1.0.3", 5 | "permissions": [ 6 | "tabs", 7 | "activeTab", 8 | "cookies", 9 | "storage", 10 | "webNavigation", 11 | "clipboardRead", 12 | "clipboardWrite", 13 | "\u003Call_urls\u003E" 14 | ], 15 | "browser_action": { 16 | "default_icon": "./images/wechat.png" 17 | }, 18 | "icons": { 19 | "128": "./images/wechat.png" 20 | }, 21 | "content_scripts": [{ 22 | "run_at": "document_end", 23 | "matches": ["https://mp.weixin.qq.com/cgi-bin/appmsg?t=media/appmsg_edit*"], 24 | "js": ["./content_script/blog.inject.js", "./content_script/weekly.inject.js"] 25 | }, { 26 | "run_at": "document_end", 27 | "matches": ["https://mp.weixin.qq.com/cgi-bin/*"], 28 | "js": ["./content_script/wxtoken.js"] 29 | }], 30 | "background": { 31 | "scripts": ["./background.js"] 32 | }, 33 | "options_page": "./index.html", 34 | "manifest_version": 2, 35 | "content_security_policy": "script-src 'self' 'unsafe-eval'; object-src 'self'" 36 | } -------------------------------------------------------------------------------- /lib/codemirror/gfm/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CodeMirror: GFM mode 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 |

CodeMirror: GFM mode

18 | 19 | 20 |
37 | 38 | 46 | 47 | 48 | 49 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 介绍下最近刚刚完成的简单的微信公众号编辑助手插件,可用于抓取博客,生成 `Markdown`,经过渲染后,可以注入一键注入到公众号编辑器:帮你省去 C+V 步骤,也无需从其他网页保存图片然后再手动上传。 2 | 3 | 当然,也可以当作代码高亮的工具来使用。如果您只需要高亮功能,建议访问 [https://angusfu.github.io/wx-editor/](https://angusfu.github.io/wx-editor/)。 4 | 5 | ![截图](./images/screenshot.png) 6 | 7 | ## Features 8 | - 专为微信图文推送定制,`Markdown` 编辑省心省力 9 | - 代码高亮保证好用,七种 Prims 代码主题任你选 10 | - 踩过的坑足够多,成功避开微信过滤机制造成视觉效果偏离预期 11 | - **无需 CV 大法**,一键帮你写入微信 12 | - **无需繁杂的保存图片、上传流程**,替你完成全部上传工作 13 | - 直接抓取[众成翻译](http://www.zcfy.cc/)的 Markdown,快速便捷 14 | - 使用 [Mercury Web Parser](https://mercury.postlight.com/) 抓取任意博客文章页 15 | - 内置[奇舞周刊](https://weekly.75team.com)编辑工具 16 | 17 | ## 使用方式 18 | 1. 下载 zip 压缩包到 PC 本地,并解压 19 | 2. 在 Chrome 中打开扩展程序设置页 [chrome://extensions/](chrome://extensions/) 20 | 3. 右上角勾选`开发者模式` 21 | 4. 点击`加载已解压的扩展程序`按钮,选择刚刚解压得到的文件夹 22 | 5. 完成,记得启用扩展程序哟~ 23 | 6. 需要使用本插件的时候,请点击对应的图标: 24 | 25 | ## 选项说明 26 | - **New**:点击后会弹出输入框,可以输入新的文章地址 27 | - **Code Style**: 用于更换代码高亮主题 28 | - **Weekly**:奇舞周刊编辑工具 29 | - **Copy**:编辑完成后,将带有样式的代码复制到剪切板 30 | - **Inject**:编辑完成后,点击`Inject`即可 31 | 32 | ![截图](./images/intro.png) 33 | 34 | ## 注意事项 35 | 1. 所有图片尽量不超过 **2 M** 36 | 2. 仅支持 `jpg, gif, png` 等格式 37 | 3. **不支持** `webp, svg` 等其他格式 38 | 4. 发生问题的图片,在插入微信编辑器中时,会以默认图替代 39 | 5. 编辑完成后,请务必到微信中预览... 40 | 6. 编辑时请随时做好**整理代码格式**的准备(虽然程序真的已经帮你做了一部分) 41 | 7. 如果只需要代码高亮功能,请访问 [https://angusfu.github.io/wx-editor/](https://angusfu.github.io/wx-editor/) 42 | 43 | ## DEMO 44 | 45 | ![微信图文效果](./images/demo.jpg) 46 | 47 | ## TODO 48 | 49 | - 外站相对路径解析(主要是解决图片的问题): 暂不解决 50 | - 编辑器暂不支持行内 HTML 标签高亮(需要进一步学习 CodeMirror) 51 | - 微信公众号后台页面匹配需要更加精准 52 | - 应该能够允许自定义样式 53 | - 缓存问题,需要考虑容量 & 过期的问题 54 | -------------------------------------------------------------------------------- /lib/codemirror/xml/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CodeMirror: XML mode 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 |

CodeMirror: XML mode

14 |
26 | 32 |

The XML mode supports two configuration parameters:

33 |
34 |
htmlMode (boolean)
35 |
This switches the mode to parse HTML instead of XML. This 36 | means attributes do not have to be quoted, and some elements 37 | (such as br) do not require a closing tag.
38 |
alignCDATA (boolean)
39 |
Setting this to true will force the opening tag of CDATA 40 | blocks to not be indented.
41 |
42 | 43 |

MIME types defined: application/xml, text/html.

44 | 45 | 46 | -------------------------------------------------------------------------------- /scripts/getContent.js: -------------------------------------------------------------------------------- 1 | /** 2 | * zcfy related utils 3 | */ 4 | const zcfyRegex = /^https?:\/\/(www\.)?zcfy\.cc\/article\// 5 | const isZcfyURL = url => zcfyRegex.test(url) 6 | 7 | /** 8 | * fetch content by postlight 9 | */ 10 | const fetchByPostLight = url => { 11 | let addr = `https://mercury.postlight.com/parser?url=${url}` 12 | 13 | return fetch(addr, { 14 | headers: { 15 | 'Content-Type': 'application/json', 16 | 'x-api-key': '4zZPsARTwZKZpHqMBtyKdMhdbxzI7rrptlu6kA8V' 17 | } 18 | }).catch(e => { 19 | console.error(e) 20 | return null 21 | }) 22 | } 23 | 24 | /** 25 | * deal with text contents 26 | */ 27 | const getMarkdownContent = url => { 28 | let config = { 29 | type: 'html', 30 | origUrl: url, 31 | url 32 | } 33 | 34 | if (isZcfyURL(url)) { 35 | // article page 36 | let reId = /zcfy\.cc\/article\/([^\/]+)/ 37 | let id = url.match(reId)[1] 38 | let api = `http://www.zcfy.cc/api/articleDetail?token=7eec4e73-23e3-435f-a1fd-3b7f2368850a&id=${id}` 39 | 40 | return fetch(api) 41 | .then(r => r.json()) 42 | .then(obj => { 43 | var data = obj.data 44 | 45 | return { 46 | title: data.title, 47 | author: data.translator_nickname || data.translator_name, 48 | url: data.seo_url_base, 49 | content: data.content, 50 | type: 'md' 51 | } 52 | }) 53 | } 54 | 55 | return fetchByPostLight(url) 56 | .then(r => r.json()) 57 | .catch(e => { 58 | console.error(e) 59 | return null 60 | }) 61 | .then(obj => { 62 | if (!obj) { 63 | throw 'got null content' 64 | } 65 | 66 | let { title, author, content } = obj 67 | 68 | return { 69 | title, 70 | author, 71 | url: url, 72 | content: generateMdText(content), 73 | type: 'html' 74 | } 75 | }) 76 | } 77 | -------------------------------------------------------------------------------- /lib/codemirror/lib/util/runmode.js: -------------------------------------------------------------------------------- 1 | CodeMirror.runMode = function(string, modespec, callback, options) { 2 | function esc(str) { 3 | return str.replace(/[<&]/, function(ch) { 4 | return ch == '<' ? '<' : '&' 5 | }) 6 | } 7 | 8 | var mode = CodeMirror.getMode(CodeMirror.defaults, modespec) 9 | var isNode = callback.nodeType == 1 10 | var tabSize = (options && options.tabSize) || CodeMirror.defaults.tabSize 11 | if (isNode) { 12 | var node = callback, 13 | accum = [], 14 | col = 0 15 | callback = function(text, style) { 16 | if (text == '\n') { 17 | accum.push('
') 18 | col = 0 19 | return 20 | } 21 | var escaped = '' 22 | // HTML-escape and replace tabs 23 | for (var pos = 0; ; ) { 24 | var idx = text.indexOf('\t', pos) 25 | if (idx == -1) { 26 | escaped += esc(text.slice(pos)) 27 | col += text.length - pos 28 | break 29 | } else { 30 | col += idx - pos 31 | escaped += esc(text.slice(pos, idx)) 32 | var size = tabSize - (col % tabSize) 33 | col += size 34 | for (var i = 0; i < size; ++i) escaped += ' ' 35 | pos = idx + 1 36 | } 37 | } 38 | 39 | if (style) 40 | accum.push('' + escaped + '') 41 | else accum.push(escaped) 42 | } 43 | } 44 | var lines = CodeMirror.splitLines(string), 45 | state = CodeMirror.startState(mode) 46 | for (var i = 0, e = lines.length; i < e; ++i) { 47 | if (i) callback('\n') 48 | var stream = new CodeMirror.StringStream(lines[i]) 49 | while (!stream.eol()) { 50 | var style = mode.token(stream, state) 51 | callback(stream.current(), style, i, stream.start) 52 | stream.start = stream.pos 53 | } 54 | } 55 | if (isNode) node.innerHTML = accum.join('') 56 | } 57 | -------------------------------------------------------------------------------- /lib/codemirror/lib/util/loadmode.js: -------------------------------------------------------------------------------- 1 | ;(function() { 2 | if (!CodeMirror.modeURL) CodeMirror.modeURL = '../mode/%N/%N.js' 3 | 4 | var loading = {} 5 | function splitCallback(cont, n) { 6 | var countDown = n 7 | return function() { 8 | if (--countDown == 0) cont() 9 | } 10 | } 11 | function ensureDeps(mode, cont) { 12 | var deps = CodeMirror.modes[mode].dependencies 13 | if (!deps) return cont() 14 | var missing = [] 15 | for (var i = 0; i < deps.length; ++i) { 16 | if (!CodeMirror.modes.hasOwnProperty(deps[i])) missing.push(deps[i]) 17 | } 18 | if (!missing.length) return cont() 19 | var split = splitCallback(cont, missing.length) 20 | for (var i = 0; i < missing.length; ++i) 21 | CodeMirror.requireMode(missing[i], split) 22 | } 23 | 24 | CodeMirror.requireMode = function(mode, cont) { 25 | if (typeof mode != 'string') mode = mode.name 26 | if (CodeMirror.modes.hasOwnProperty(mode)) return ensureDeps(mode, cont) 27 | if (loading.hasOwnProperty(mode)) return loading[mode].push(cont) 28 | 29 | var script = document.createElement('script') 30 | script.src = CodeMirror.modeURL.replace(/%N/g, mode) 31 | var others = document.getElementsByTagName('script')[0] 32 | others.parentNode.insertBefore(script, others) 33 | var list = (loading[mode] = [cont]) 34 | var count = 0, 35 | poll = setInterval(function() { 36 | if (++count > 100) return clearInterval(poll) 37 | if (CodeMirror.modes.hasOwnProperty(mode)) { 38 | clearInterval(poll) 39 | loading[mode] = null 40 | ensureDeps(mode, function() { 41 | for (var i = 0; i < list.length; ++i) list[i]() 42 | }) 43 | } 44 | }, 200) 45 | } 46 | 47 | CodeMirror.autoLoadMode = function(instance, mode) { 48 | if (!CodeMirror.modes.hasOwnProperty(mode)) 49 | CodeMirror.requireMode(mode, function() { 50 | instance.setOption('mode', instance.getOption('mode')) 51 | }) 52 | } 53 | })() 54 | -------------------------------------------------------------------------------- /lib/codemirror/lib/util/continuelist.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') 6 | // CommonJS 7 | mod(require('../../lib/codemirror')) 8 | else if (typeof define == 'function' && define.amd) 9 | // AMD 10 | define(['../../lib/codemirror'], mod) 11 | // Plain browser env 12 | else mod(CodeMirror) 13 | })(function(CodeMirror) { 14 | 'use strict' 15 | 16 | var listRE = /^(\s*)(>[> ]*|[*+-]\s|(\d+)\.)(\s*)/, 17 | emptyListRE = /^(\s*)(>[> ]*|[*+-]|(\d+)\.)(\s*)$/, 18 | unorderedListRE = /[*+-]\s/ 19 | 20 | CodeMirror.commands.newlineAndIndentContinueMarkdownList = function(cm) { 21 | if (cm.getOption('disableInput')) return CodeMirror.Pass 22 | var ranges = cm.listSelections(), 23 | replacements = [] 24 | for (var i = 0; i < ranges.length; i++) { 25 | var pos = ranges[i].head 26 | var eolState = cm.getStateAfter(pos.line) 27 | var inList = eolState.list !== false 28 | var inQuote = eolState.quote !== 0 29 | 30 | var line = cm.getLine(pos.line), 31 | match = listRE.exec(line) 32 | if (!ranges[i].empty() || (!inList && !inQuote) || !match) { 33 | cm.execCommand('newlineAndIndent') 34 | return 35 | } 36 | if (emptyListRE.test(line)) { 37 | cm.replaceRange( 38 | '', 39 | { 40 | line: pos.line, 41 | ch: 0 42 | }, 43 | { 44 | line: pos.line, 45 | ch: pos.ch + 1 46 | } 47 | ) 48 | replacements[i] = '\n' 49 | } else { 50 | var indent = match[1], 51 | after = match[4] 52 | var bullet = 53 | unorderedListRE.test(match[2]) || match[2].indexOf('>') >= 0 54 | ? match[2] 55 | : parseInt(match[3], 10) + 1 + '.' 56 | 57 | replacements[i] = '\n' + indent + bullet + after 58 | } 59 | } 60 | 61 | cm.replaceSelections(replacements) 62 | } 63 | }) 64 | -------------------------------------------------------------------------------- /lib/codemirror/lib/util/match-highlighter.js: -------------------------------------------------------------------------------- 1 | // Define match-highlighter commands. Depends on searchcursor.js 2 | // Use by attaching the following function call to the onCursorActivity event: 3 | //myCodeMirror.matchHighlight(minChars); 4 | // And including a special span.CodeMirror-matchhighlight css class (also optionally a separate one for .CodeMirror-focused -- see demo matchhighlighter.html) 5 | 6 | ;(function() { 7 | var DEFAULT_MIN_CHARS = 2 8 | 9 | function MatchHighlightState() { 10 | this.marked = [] 11 | } 12 | function getMatchHighlightState(cm) { 13 | return ( 14 | cm._matchHighlightState || 15 | (cm._matchHighlightState = new MatchHighlightState()) 16 | ) 17 | } 18 | 19 | function clearMarks(cm) { 20 | var state = getMatchHighlightState(cm) 21 | for (var i = 0; i < state.marked.length; ++i) state.marked[i].clear() 22 | state.marked = [] 23 | } 24 | 25 | function markDocument(cm, className, minChars) { 26 | clearMarks(cm) 27 | minChars = typeof minChars !== 'undefined' ? minChars : DEFAULT_MIN_CHARS 28 | if ( 29 | cm.somethingSelected() && 30 | cm.getSelection().replace(/^\s+|\s+$/g, '').length >= minChars 31 | ) { 32 | var state = getMatchHighlightState(cm) 33 | var query = cm.getSelection() 34 | cm.operation(function() { 35 | if (cm.lineCount() < 2000) { 36 | // This is too expensive on big documents. 37 | for (var cursor = cm.getSearchCursor(query); cursor.findNext(); ) { 38 | //Only apply matchhighlight to the matches other than the one actually selected 39 | if ( 40 | !( 41 | cursor.from().line === cm.getCursor(true).line && 42 | cursor.from().ch === cm.getCursor(true).ch 43 | ) 44 | ) 45 | state.marked.push( 46 | cm.markText(cursor.from(), cursor.to(), className) 47 | ) 48 | } 49 | } 50 | }) 51 | } 52 | } 53 | 54 | CodeMirror.defineExtension('matchHighlight', function(className, minChars) { 55 | markDocument(this, className, minChars) 56 | }) 57 | })() 58 | -------------------------------------------------------------------------------- /scripts/imgReduce.js: -------------------------------------------------------------------------------- 1 | ;(function() { 2 | function dataURItoBlob(dataURI) { 3 | var byteString, mimestring 4 | 5 | if (dataURI.split(',')[0].indexOf('base64') !== -1) { 6 | byteString = atob(dataURI.split(',')[1]) 7 | } else { 8 | byteString = decodeURI(dataURI.split(',')[1]) 9 | } 10 | 11 | mimestring = dataURI 12 | .split(',')[0] 13 | .split(':')[1] 14 | .split(';')[0] 15 | 16 | var content = new Array() 17 | for (var i = 0; i < byteString.length; i++) { 18 | content[i] = byteString.charCodeAt(i) 19 | } 20 | 21 | var blobImg, 22 | arrays = new Uint8Array(content) 23 | try { 24 | blobImg = new Blob([arrays], { 25 | type: mimestring 26 | }) 27 | } catch (e) {} 28 | return blobImg 29 | } 30 | 31 | function imgScale(src, opt, cbk) { 32 | if (!src) return cbk(false) 33 | var _canvas = document.createElement('canvas') 34 | var tImg = new Image() 35 | tImg.onload = function() { 36 | var _context = _canvas.getContext('2d'), 37 | tw = this.width * opt.scale, 38 | th = this.height * opt.scale 39 | _canvas.width = tw 40 | _canvas.height = th 41 | 42 | _context.drawImage(tImg, 0, 0, tw, th) 43 | 44 | src = _canvas.toDataURL(opt.type, opt.quality) 45 | var blob = dataURItoBlob(src) 46 | cbk(blob) 47 | } 48 | tImg.src = src 49 | } 50 | 51 | function imageReduce(file, cbk, opts) { 52 | var opt = Object.assign( 53 | {}, 54 | { 55 | scale: 0.9, 56 | quality: 0.99, 57 | type: 'image/jpeg' 58 | }, 59 | opts || {} 60 | ) 61 | 62 | var fReader = new FileReader() 63 | fReader.onload = function(e) { 64 | var result = e.target.result 65 | imgScale(result, opt, function(file) { 66 | cbk(file) 67 | }) 68 | } 69 | fReader.readAsDataURL(file) 70 | } 71 | 72 | window.imgReduce = function(files, opt) { 73 | return new Promise(resolve => { 74 | imageReduce( 75 | files, 76 | files => { 77 | resolve(files) 78 | }, 79 | opt 80 | ) 81 | }) 82 | } 83 | })() 84 | -------------------------------------------------------------------------------- /styles/base16-light.css: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | Name: Base16 Default Light 4 | Author: Chris Kempson (http://chriskempson.com) 5 | 6 | CodeMirror template by Jan T. Sott (https://github.com/idleberg/base16-chrome-devtools) 7 | Original Base16 color scheme by Chris Kempson (https://github.com/chriskempson/base16) 8 | 9 | */ 10 | 11 | .cm-s-base16-light.CodeMirror { 12 | background: #f5f5f5; 13 | color: #202020; 14 | } 15 | .cm-s-base16-light div.CodeMirror-selected { 16 | background: #e0e0e0 !important; 17 | } 18 | .cm-s-base16-light.CodeMirror ::selection { 19 | background: #e0e0e0; 20 | } 21 | .cm-s-base16-light.CodeMirror ::-moz-selection { 22 | background: #e0e0e0; 23 | } 24 | .cm-s-base16-light .CodeMirror-gutters { 25 | background: #f5f5f5; 26 | border-right: 0px; 27 | } 28 | .cm-s-base16-light .CodeMirror-guttermarker { 29 | color: #ac4142; 30 | } 31 | .cm-s-base16-light .CodeMirror-guttermarker-subtle { 32 | color: #b0b0b0; 33 | } 34 | .cm-s-base16-light .CodeMirror-linenumber { 35 | color: #b0b0b0; 36 | } 37 | .cm-s-base16-light .CodeMirror-cursor { 38 | border-left: 1px solid #505050 !important; 39 | } 40 | 41 | .cm-s-base16-light span.cm-comment { 42 | color: #8f5536; 43 | } 44 | .cm-s-base16-light span.cm-atom { 45 | color: #aa759f; 46 | } 47 | .cm-s-base16-light span.cm-number { 48 | color: #aa759f; 49 | } 50 | 51 | .cm-s-base16-light span.cm-property, 52 | .cm-s-base16-light span.cm-attribute { 53 | color: #90a959; 54 | } 55 | .cm-s-base16-light span.cm-keyword { 56 | color: #ac4142; 57 | } 58 | .cm-s-base16-light span.cm-string { 59 | color: #f4bf75; 60 | } 61 | 62 | .cm-s-base16-light span.cm-variable { 63 | color: #90a959; 64 | } 65 | .cm-s-base16-light span.cm-variable-2 { 66 | color: #6a9fb5; 67 | } 68 | .cm-s-base16-light span.cm-def { 69 | color: #d28445; 70 | } 71 | .cm-s-base16-light span.cm-bracket { 72 | color: #202020; 73 | } 74 | .cm-s-base16-light span.cm-tag { 75 | color: #ac4142; 76 | } 77 | .cm-s-base16-light span.cm-link { 78 | color: #aa759f; 79 | } 80 | .cm-s-base16-light span.cm-error { 81 | background: #ac4142; 82 | color: #505050; 83 | } 84 | 85 | .cm-s-base16-light .CodeMirror-activeline-background { 86 | background: #dddcdc !important; 87 | } 88 | .cm-s-base16-light .CodeMirror-matchingbracket { 89 | text-decoration: underline; 90 | color: white !important; 91 | } 92 | -------------------------------------------------------------------------------- /lib/codemirror/lib/util/overlay.js: -------------------------------------------------------------------------------- 1 | // Utility function that allows modes to be combined. The mode given 2 | // as the base argument takes care of most of the normal mode 3 | // functionality, but a second (typically simple) mode is used, which 4 | // can override the style of text. Both modes get to parse all of the 5 | // text, but when both assign a non-null style to a piece of code, the 6 | // overlay wins, unless the combine argument was true, in which case 7 | // the styles are combined. 8 | 9 | // overlayParser is the old, deprecated name 10 | CodeMirror.overlayMode = CodeMirror.overlayParser = function( 11 | base, 12 | overlay, 13 | combine 14 | ) { 15 | return { 16 | startState: function() { 17 | return { 18 | base: CodeMirror.startState(base), 19 | overlay: CodeMirror.startState(overlay), 20 | basePos: 0, 21 | baseCur: null, 22 | overlayPos: 0, 23 | overlayCur: null 24 | } 25 | }, 26 | copyState: function(state) { 27 | return { 28 | base: CodeMirror.copyState(base, state.base), 29 | overlay: CodeMirror.copyState(overlay, state.overlay), 30 | basePos: state.basePos, 31 | baseCur: null, 32 | overlayPos: state.overlayPos, 33 | overlayCur: null 34 | } 35 | }, 36 | 37 | token: function(stream, state) { 38 | if (stream.start == state.basePos) { 39 | state.baseCur = base.token(stream, state.base) 40 | state.basePos = stream.pos 41 | } 42 | if (stream.start == state.overlayPos) { 43 | stream.pos = stream.start 44 | state.overlayCur = overlay.token(stream, state.overlay) 45 | state.overlayPos = stream.pos 46 | } 47 | stream.pos = Math.min(state.basePos, state.overlayPos) 48 | if (stream.eol()) state.basePos = state.overlayPos = 0 49 | 50 | if (state.overlayCur == null) return state.baseCur 51 | if (state.baseCur != null && combine) 52 | return state.baseCur + ' ' + state.overlayCur 53 | else return state.overlayCur 54 | }, 55 | 56 | indent: 57 | base.indent && 58 | function(state, textAfter) { 59 | return base.indent(state.base, textAfter) 60 | }, 61 | electricChars: base.electricChars, 62 | 63 | innerMode: function(state) { 64 | return { state: state.base, mode: base } 65 | } 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /styles/prism-funky.css: -------------------------------------------------------------------------------- 1 | /** 2 | * prism.js Funky theme 3 | * Based on “Polyfilling the gaps” talk slides http://lea.verou.me/polyfilling-the-gaps/ 4 | * @author Lea Verou 5 | */ 6 | 7 | code[class*='language-'], 8 | pre[class*='language-'] { 9 | font-family: Consolas, Monaco, 'Andale Mono', 'Ubuntu Mono', monospace; 10 | text-align: left; 11 | white-space: pre; 12 | word-spacing: normal; 13 | word-break: normal; 14 | word-wrap: normal; 15 | line-height: 1.5; 16 | 17 | -moz-tab-size: 4; 18 | -o-tab-size: 4; 19 | tab-size: 4; 20 | 21 | -webkit-hyphens: none; 22 | -moz-hyphens: none; 23 | -ms-hyphens: none; 24 | hyphens: none; 25 | } 26 | 27 | /* Code blocks */ 28 | pre[class*='language-'] { 29 | padding: 0.4em 0.8em; 30 | margin: 0.5em 0; 31 | overflow: auto; 32 | background: #121558; 33 | background-size: 10px 10px; 34 | } 35 | 36 | code[class*='language-'] { 37 | background: black; 38 | color: white; 39 | box-shadow: -0.3em 0 0 0.3em black, 0.3em 0 0 0.3em black; 40 | } 41 | 42 | /* Inline code */ 43 | :not(pre) > code[class*='language-'] { 44 | padding: 0.2em; 45 | border-radius: 0.3em; 46 | box-shadow: none; 47 | white-space: normal; 48 | } 49 | 50 | .token.comment, 51 | .token.prolog, 52 | .token.doctype, 53 | .token.cdata { 54 | color: #aaa; 55 | } 56 | 57 | .token.punctuation { 58 | color: #999; 59 | } 60 | 61 | .namespace { 62 | opacity: 0.7; 63 | } 64 | 65 | .token.property, 66 | .token.tag, 67 | .token.boolean, 68 | .token.number, 69 | .token.constant, 70 | .token.symbol { 71 | color: #0cf; 72 | } 73 | 74 | .token.selector, 75 | .token.attr-name, 76 | .token.string, 77 | .token.char, 78 | .token.builtin { 79 | color: yellow; 80 | } 81 | 82 | .token.operator, 83 | .token.entity, 84 | .token.url, 85 | .language-css .token.string, 86 | .toke.variable, 87 | .token.inserted { 88 | color: yellowgreen; 89 | } 90 | 91 | .token.atrule, 92 | .token.attr-value, 93 | .token.keyword { 94 | color: deeppink; 95 | } 96 | 97 | .token.regex, 98 | .token.important { 99 | color: orange; 100 | } 101 | 102 | .token.important, 103 | .token.bold { 104 | font-weight: bold; 105 | } 106 | .token.italic { 107 | font-style: italic; 108 | } 109 | 110 | .token.entity { 111 | cursor: help; 112 | } 113 | 114 | .token.deleted { 115 | color: red; 116 | } 117 | -------------------------------------------------------------------------------- /styles/prism-tomorrow.css: -------------------------------------------------------------------------------- 1 | /** 2 | * prism.js tomorrow night eighties for JavaScript, CoffeeScript, CSS and HTML 3 | * Based on https://github.com/chriskempson/tomorrow-theme 4 | * @author Rose Pritchard 5 | */ 6 | 7 | code[class*='language-'], 8 | pre[class*='language-'] { 9 | color: #ccc; 10 | background: none; 11 | font-family: Consolas, Monaco, 'Andale Mono', 'Ubuntu Mono', monospace; 12 | text-align: left; 13 | white-space: pre; 14 | word-spacing: normal; 15 | word-break: normal; 16 | word-wrap: normal; 17 | line-height: 1.5; 18 | 19 | -moz-tab-size: 4; 20 | -o-tab-size: 4; 21 | tab-size: 4; 22 | 23 | -webkit-hyphens: none; 24 | -moz-hyphens: none; 25 | -ms-hyphens: none; 26 | hyphens: none; 27 | } 28 | 29 | /* Code blocks */ 30 | pre[class*='language-'] { 31 | padding: 1em; 32 | margin: 0.5em 0; 33 | overflow: auto; 34 | } 35 | 36 | :not(pre) > code[class*='language-'], 37 | pre[class*='language-'] { 38 | background: #2d2d2d; 39 | } 40 | 41 | /* Inline code */ 42 | :not(pre) > code[class*='language-'] { 43 | padding: 0.1em; 44 | border-radius: 0.3em; 45 | white-space: normal; 46 | } 47 | 48 | .token.comment, 49 | .token.block-comment, 50 | .token.prolog, 51 | .token.doctype, 52 | .token.cdata { 53 | color: #999; 54 | } 55 | 56 | .token.punctuation { 57 | color: #ccc; 58 | } 59 | 60 | .token.tag, 61 | .token.attr-name, 62 | .token.namespace, 63 | .token.deleted { 64 | color: #e2777a; 65 | } 66 | 67 | .token.function-name { 68 | color: #6196cc; 69 | } 70 | 71 | .token.boolean, 72 | .token.number, 73 | .token.function { 74 | color: #f08d49; 75 | } 76 | 77 | .token.property, 78 | .token.class-name, 79 | .token.constant, 80 | .token.symbol { 81 | color: #f8c555; 82 | } 83 | 84 | .token.selector, 85 | .token.important, 86 | .token.atrule, 87 | .token.keyword, 88 | .token.builtin { 89 | color: #cc99cd; 90 | } 91 | 92 | .token.string, 93 | .token.char, 94 | .token.attr-value, 95 | .token.regex, 96 | .token.variable { 97 | color: #7ec699; 98 | } 99 | 100 | .token.operator, 101 | .token.entity, 102 | .token.url { 103 | color: #67cdcc; 104 | } 105 | 106 | .token.important, 107 | .token.bold { 108 | font-weight: bold; 109 | } 110 | .token.italic { 111 | font-style: italic; 112 | } 113 | 114 | .token.entity { 115 | cursor: help; 116 | } 117 | 118 | .token.inserted { 119 | color: green; 120 | } 121 | -------------------------------------------------------------------------------- /styles/prism-okaidia.css: -------------------------------------------------------------------------------- 1 | /** 2 | * okaidia theme for JavaScript, CSS and HTML 3 | * Loosely based on Monokai textmate theme by http://www.monokai.nl/ 4 | * @author ocodia 5 | */ 6 | 7 | code[class*='language-'], 8 | pre[class*='language-'] { 9 | color: #f8f8f2; 10 | background: none; 11 | text-shadow: 0 1px rgba(0, 0, 0, 0.3); 12 | font-family: Consolas, Monaco, 'Andale Mono', 'Ubuntu Mono', monospace; 13 | text-align: left; 14 | white-space: pre; 15 | word-spacing: normal; 16 | word-break: normal; 17 | word-wrap: normal; 18 | line-height: 1.5; 19 | 20 | -moz-tab-size: 4; 21 | -o-tab-size: 4; 22 | tab-size: 4; 23 | 24 | -webkit-hyphens: none; 25 | -moz-hyphens: none; 26 | -ms-hyphens: none; 27 | hyphens: none; 28 | } 29 | 30 | /* Code blocks */ 31 | pre[class*='language-'] { 32 | padding: 1em; 33 | margin: 0.5em 0; 34 | overflow: auto; 35 | border-radius: 0.3em; 36 | } 37 | 38 | :not(pre) > code[class*='language-'], 39 | pre[class*='language-'] { 40 | background: #272822; 41 | } 42 | 43 | /* Inline code */ 44 | :not(pre) > code[class*='language-'] { 45 | padding: 0.1em; 46 | border-radius: 0.3em; 47 | white-space: normal; 48 | } 49 | 50 | .token.comment, 51 | .token.prolog, 52 | .token.doctype, 53 | .token.cdata { 54 | color: slategray; 55 | } 56 | 57 | .token.punctuation { 58 | color: #f8f8f2; 59 | } 60 | 61 | .namespace { 62 | opacity: 0.7; 63 | } 64 | 65 | .token.property, 66 | .token.tag, 67 | .token.constant, 68 | .token.symbol, 69 | .token.deleted { 70 | color: #f92672; 71 | } 72 | 73 | .token.boolean, 74 | .token.number { 75 | color: #ae81ff; 76 | } 77 | 78 | .token.selector, 79 | .token.attr-name, 80 | .token.string, 81 | .token.char, 82 | .token.builtin, 83 | .token.inserted { 84 | color: #a6e22e; 85 | } 86 | 87 | .token.operator, 88 | .token.entity, 89 | .token.url, 90 | .language-css .token.string, 91 | .style .token.string, 92 | .token.variable { 93 | color: #f8f8f2; 94 | } 95 | 96 | .token.atrule, 97 | .token.attr-value, 98 | .token.function { 99 | color: #e6db74; 100 | } 101 | 102 | .token.keyword { 103 | color: #66d9ef; 104 | } 105 | 106 | .token.regex, 107 | .token.important { 108 | color: #fd971f; 109 | } 110 | 111 | .token.important, 112 | .token.bold { 113 | font-weight: bold; 114 | } 115 | .token.italic { 116 | font-style: italic; 117 | } 118 | 119 | .token.entity { 120 | cursor: help; 121 | } 122 | -------------------------------------------------------------------------------- /lib/codemirror/lib/util/dialog.js: -------------------------------------------------------------------------------- 1 | // Open simple dialogs on top of an editor. Relies on dialog.css. 2 | 3 | ;(function() { 4 | function dialogDiv(cm, template) { 5 | var wrap = cm.getWrapperElement() 6 | var dialog = wrap.insertBefore( 7 | document.createElement('div'), 8 | wrap.firstChild 9 | ) 10 | dialog.className = 'CodeMirror-dialog' 11 | dialog.innerHTML = '
' + template + '
' 12 | return dialog 13 | } 14 | 15 | CodeMirror.defineExtension('openDialog', function(template, callback) { 16 | var dialog = dialogDiv(this, template) 17 | var closed = false, 18 | me = this 19 | function close() { 20 | if (closed) return 21 | closed = true 22 | dialog.parentNode.removeChild(dialog) 23 | } 24 | var inp = dialog.getElementsByTagName('input')[0], 25 | button 26 | if (inp) { 27 | CodeMirror.connect(inp, 'keydown', function(e) { 28 | if (e.keyCode == 13 || e.keyCode == 27) { 29 | CodeMirror.e_stop(e) 30 | close() 31 | me.focus() 32 | if (e.keyCode == 13) callback(inp.value) 33 | } 34 | }) 35 | inp.focus() 36 | CodeMirror.connect(inp, 'blur', close) 37 | } else if ((button = dialog.getElementsByTagName('button')[0])) { 38 | CodeMirror.connect(button, 'click', function() { 39 | close() 40 | me.focus() 41 | }) 42 | button.focus() 43 | CodeMirror.connect(button, 'blur', close) 44 | } 45 | return close 46 | }) 47 | 48 | CodeMirror.defineExtension('openConfirm', function(template, callbacks) { 49 | var dialog = dialogDiv(this, template) 50 | var buttons = dialog.getElementsByTagName('button') 51 | var closed = false, 52 | me = this, 53 | blurring = 1 54 | function close() { 55 | if (closed) return 56 | closed = true 57 | dialog.parentNode.removeChild(dialog) 58 | me.focus() 59 | } 60 | buttons[0].focus() 61 | for (var i = 0; i < buttons.length; ++i) { 62 | var b = buttons[i] 63 | ;(function(callback) { 64 | CodeMirror.connect(b, 'click', function(e) { 65 | CodeMirror.e_preventDefault(e) 66 | close() 67 | if (callback) callback(me) 68 | }) 69 | })(callbacks[i]) 70 | CodeMirror.connect(b, 'blur', function() { 71 | --blurring 72 | setTimeout(function() { 73 | if (blurring <= 0) close() 74 | }, 200) 75 | }) 76 | CodeMirror.connect(b, 'focus', function() { 77 | ++blurring 78 | }) 79 | } 80 | }) 81 | })() 82 | -------------------------------------------------------------------------------- /styles/prism-dark.css: -------------------------------------------------------------------------------- 1 | /** 2 | * prism.js Dark theme for JavaScript, CSS and HTML 3 | * Based on the slides of the talk “/Reg(exp){2}lained/” 4 | * @author Lea Verou 5 | */ 6 | 7 | code[class*='language-'], 8 | pre[class*='language-'] { 9 | color: white; 10 | background: none; 11 | text-shadow: 0 -0.1em 0.2em black; 12 | font-family: Consolas, Monaco, 'Andale Mono', 'Ubuntu Mono', monospace; 13 | text-align: left; 14 | white-space: pre; 15 | word-spacing: normal; 16 | word-break: normal; 17 | word-wrap: normal; 18 | line-height: 1.5; 19 | 20 | -moz-tab-size: 4; 21 | -o-tab-size: 4; 22 | tab-size: 4; 23 | 24 | -webkit-hyphens: none; 25 | -moz-hyphens: none; 26 | -ms-hyphens: none; 27 | hyphens: none; 28 | } 29 | 30 | @media print { 31 | code[class*='language-'], 32 | pre[class*='language-'] { 33 | text-shadow: none; 34 | } 35 | } 36 | 37 | pre[class*='language-'], 38 | :not(pre) > code[class*='language-'] { 39 | background: hsl(30, 20%, 25%); 40 | } 41 | 42 | /* Code blocks */ 43 | pre[class*='language-'] { 44 | padding: 1em; 45 | margin: 0.5em 0; 46 | overflow: auto; 47 | border: 0.3em solid hsl(30, 20%, 40%); 48 | border-radius: 0.5em; 49 | box-shadow: 1px 1px 0.5em black inset; 50 | } 51 | 52 | /* Inline code */ 53 | :not(pre) > code[class*='language-'] { 54 | padding: 0.15em 0.2em 0.05em; 55 | border-radius: 0.3em; 56 | border: 0.13em solid hsl(30, 20%, 40%); 57 | box-shadow: 1px 1px 0.3em -0.1em black inset; 58 | white-space: normal; 59 | } 60 | 61 | .token.comment, 62 | .token.prolog, 63 | .token.doctype, 64 | .token.cdata { 65 | color: hsl(30, 20%, 50%); 66 | } 67 | 68 | .token.punctuation { 69 | opacity: 0.7; 70 | } 71 | 72 | .namespace { 73 | opacity: 0.7; 74 | } 75 | 76 | .token.property, 77 | .token.tag, 78 | .token.boolean, 79 | .token.number, 80 | .token.constant, 81 | .token.symbol { 82 | color: hsl(350, 40%, 70%); 83 | } 84 | 85 | .token.selector, 86 | .token.attr-name, 87 | .token.string, 88 | .token.char, 89 | .token.builtin, 90 | .token.inserted { 91 | color: hsl(75, 70%, 60%); 92 | } 93 | 94 | .token.operator, 95 | .token.entity, 96 | .token.url, 97 | .language-css .token.string, 98 | .style .token.string, 99 | .token.variable { 100 | color: hsl(40, 90%, 60%); 101 | } 102 | 103 | .token.atrule, 104 | .token.attr-value, 105 | .token.keyword { 106 | color: hsl(350, 40%, 70%); 107 | } 108 | 109 | .token.regex, 110 | .token.important { 111 | color: #e90; 112 | } 113 | 114 | .token.important, 115 | .token.bold { 116 | font-weight: bold; 117 | } 118 | .token.italic { 119 | font-style: italic; 120 | } 121 | 122 | .token.entity { 123 | cursor: help; 124 | } 125 | 126 | .token.deleted { 127 | color: red; 128 | } 129 | -------------------------------------------------------------------------------- /lib/codemirror/javascript/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CodeMirror: JavaScript mode 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 |

CodeMirror: JavaScript mode

14 | 15 |
64 | 65 | 71 | 72 |

JavaScript mode supports a single configuration 73 | option, json, which will set the mode to expect JSON 74 | data rather than a JavaScript program.

75 | 76 |

MIME types defined: text/javascript, application/json.

77 | 78 | 79 | -------------------------------------------------------------------------------- /styles/prism.css: -------------------------------------------------------------------------------- 1 | /** 2 | * prism.js default theme for JavaScript, CSS and HTML 3 | * Based on dabblet (http://dabblet.com) 4 | * @author Lea Verou 5 | */ 6 | 7 | code[class*='language-'], 8 | pre[class*='language-'] { 9 | color: black; 10 | background: none; 11 | text-shadow: 0 1px white; 12 | font-family: Consolas, Monaco, 'Andale Mono', 'Ubuntu Mono', monospace; 13 | text-align: left; 14 | white-space: pre; 15 | word-spacing: normal; 16 | word-break: normal; 17 | word-wrap: normal; 18 | line-height: 1.5; 19 | 20 | -moz-tab-size: 4; 21 | -o-tab-size: 4; 22 | tab-size: 4; 23 | 24 | -webkit-hyphens: none; 25 | -moz-hyphens: none; 26 | -ms-hyphens: none; 27 | hyphens: none; 28 | } 29 | 30 | pre[class*='language-']::-moz-selection, 31 | pre[class*='language-'] ::-moz-selection, 32 | code[class*='language-']::-moz-selection, 33 | code[class*='language-'] ::-moz-selection { 34 | text-shadow: none; 35 | background: #b3d4fc; 36 | } 37 | 38 | pre[class*='language-']::selection, 39 | pre[class*='language-'] ::selection, 40 | code[class*='language-']::selection, 41 | code[class*='language-'] ::selection { 42 | text-shadow: none; 43 | background: #b3d4fc; 44 | } 45 | 46 | @media print { 47 | code[class*='language-'], 48 | pre[class*='language-'] { 49 | text-shadow: none; 50 | } 51 | } 52 | 53 | /* Code blocks */ 54 | pre[class*='language-'] { 55 | padding: 1em; 56 | margin: 0.5em 0; 57 | overflow: auto; 58 | } 59 | 60 | :not(pre) > code[class*='language-'], 61 | pre[class*='language-'] { 62 | background: #f5f2f0; 63 | } 64 | 65 | /* Inline code */ 66 | :not(pre) > code[class*='language-'] { 67 | padding: 0.1em; 68 | border-radius: 0.3em; 69 | white-space: normal; 70 | } 71 | 72 | .token.comment, 73 | .token.prolog, 74 | .token.doctype, 75 | .token.cdata { 76 | color: slategray; 77 | } 78 | 79 | .token.punctuation { 80 | color: #999; 81 | } 82 | 83 | .namespace { 84 | opacity: 0.7; 85 | } 86 | 87 | .token.property, 88 | .token.tag, 89 | .token.boolean, 90 | .token.number, 91 | .token.constant, 92 | .token.symbol, 93 | .token.deleted { 94 | color: #905; 95 | } 96 | 97 | .token.selector, 98 | .token.attr-name, 99 | .token.string, 100 | .token.char, 101 | .token.builtin, 102 | .token.inserted { 103 | color: #690; 104 | } 105 | 106 | .token.operator, 107 | .token.entity, 108 | .token.url, 109 | .language-css .token.string, 110 | .style .token.string { 111 | color: #a67f59; 112 | background: hsla(0, 0%, 100%, 0.5); 113 | } 114 | 115 | .token.atrule, 116 | .token.attr-value, 117 | .token.keyword { 118 | color: #07a; 119 | } 120 | 121 | .token.function { 122 | color: #dd4a68; 123 | } 124 | 125 | .token.regex, 126 | .token.important, 127 | .token.variable { 128 | color: #e90; 129 | } 130 | 131 | .token.important, 132 | .token.bold { 133 | font-weight: bold; 134 | } 135 | .token.italic { 136 | font-style: italic; 137 | } 138 | 139 | .token.entity { 140 | cursor: help; 141 | } 142 | -------------------------------------------------------------------------------- /lib/codemirror/lib/util/xml-hint.js: -------------------------------------------------------------------------------- 1 | ;(function() { 2 | CodeMirror.xmlHints = [] 3 | 4 | CodeMirror.xmlHint = function(cm, simbol) { 5 | if (simbol.length > 0) { 6 | var cursor = cm.getCursor() 7 | cm.replaceSelection(simbol) 8 | cursor = { line: cursor.line, ch: cursor.ch + 1 } 9 | cm.setCursor(cursor) 10 | } 11 | 12 | // dirty hack for simple-hint to receive getHint event on space 13 | var getTokenAt = editor.getTokenAt 14 | 15 | editor.getTokenAt = function() { 16 | return 'disabled' 17 | } 18 | CodeMirror.simpleHint(cm, getHint) 19 | 20 | editor.getTokenAt = getTokenAt 21 | } 22 | 23 | var getHint = function(cm) { 24 | var cursor = cm.getCursor() 25 | 26 | if (cursor.ch > 0) { 27 | var text = cm.getRange({ line: 0, ch: 0 }, cursor) 28 | var typed = '' 29 | var simbol = '' 30 | for (var i = text.length - 1; i >= 0; i--) { 31 | if (text[i] == ' ' || text[i] == '<') { 32 | simbol = text[i] 33 | break 34 | } else { 35 | typed = text[i] + typed 36 | } 37 | } 38 | 39 | text = text.slice(0, text.length - typed.length) 40 | 41 | var path = getActiveElement(cm, text) + simbol 42 | var hints = CodeMirror.xmlHints[path] 43 | 44 | if (typeof hints === 'undefined') hints = [''] 45 | else { 46 | hints = hints.slice(0) 47 | for (var i = hints.length - 1; i >= 0; i--) { 48 | if (hints[i].indexOf(typed) != 0) hints.splice(i, 1) 49 | } 50 | } 51 | 52 | return { 53 | list: hints, 54 | from: { line: cursor.line, ch: cursor.ch - typed.length }, 55 | to: cursor 56 | } 57 | } 58 | } 59 | 60 | var getActiveElement = function(codeMirror, text) { 61 | var element = '' 62 | 63 | if (text.length >= 0) { 64 | var regex = new RegExp('<([^!?][^\\s/>]*).*?>', 'g') 65 | 66 | var matches = [] 67 | var match 68 | while ((match = regex.exec(text)) != null) { 69 | matches.push({ 70 | tag: match[1], 71 | selfclose: match[0].slice(match[0].length - 2) === '/>' 72 | }) 73 | } 74 | 75 | for (var i = matches.length - 1, skip = 0; i >= 0; i--) { 76 | var item = matches[i] 77 | 78 | if (item.tag[0] == '/') { 79 | skip++ 80 | } else if (item.selfclose == false) { 81 | if (skip > 0) { 82 | skip-- 83 | } else { 84 | element = '<' + item.tag + '>' + element 85 | } 86 | } 87 | } 88 | 89 | element += getOpenTag(text) 90 | } 91 | 92 | return element 93 | } 94 | 95 | var getOpenTag = function(text) { 96 | var open = text.lastIndexOf('<') 97 | var close = text.lastIndexOf('>') 98 | 99 | if (close < open) { 100 | text = text.slice(open) 101 | 102 | if (text != '<') { 103 | var space = text.indexOf(' ') 104 | if (space < 0) space = text.indexOf('\t') 105 | if (space < 0) space = text.indexOf('\n') 106 | 107 | if (space < 0) space = text.length 108 | 109 | return text.slice(0, space) 110 | } 111 | } 112 | 113 | return '' 114 | } 115 | })() 116 | -------------------------------------------------------------------------------- /lib/codemirror/lib/util/multiplex.js: -------------------------------------------------------------------------------- 1 | CodeMirror.multiplexingMode = function(outer /*, others */) { 2 | // Others should be {open, close, mode [, delimStyle]} objects 3 | var others = Array.prototype.slice.call(arguments, 1) 4 | var n_others = others.length 5 | 6 | function indexOf(string, pattern, from) { 7 | if (typeof pattern == 'string') return string.indexOf(pattern, from) 8 | var m = pattern.exec(from ? string.slice(from) : string) 9 | return m ? m.index + from : -1 10 | } 11 | 12 | return { 13 | startState: function() { 14 | return { 15 | outer: CodeMirror.startState(outer), 16 | innerActive: null, 17 | inner: null 18 | } 19 | }, 20 | 21 | copyState: function(state) { 22 | return { 23 | outer: CodeMirror.copyState(outer, state.outer), 24 | innerActive: state.innerActive, 25 | inner: 26 | state.innerActive && 27 | CodeMirror.copyState(state.innerActive.mode, state.inner) 28 | } 29 | }, 30 | 31 | token: function(stream, state) { 32 | if (!state.innerActive) { 33 | var cutOff = Infinity, 34 | oldContent = stream.string 35 | for (var i = 0; i < n_others; ++i) { 36 | var other = others[i] 37 | var found = indexOf(oldContent, other.open, stream.pos) 38 | if (found == stream.pos) { 39 | stream.match(other.open) 40 | state.innerActive = other 41 | state.inner = CodeMirror.startState( 42 | other.mode, 43 | outer.indent ? outer.indent(state.outer, '') : 0 44 | ) 45 | return other.delimStyle 46 | } else if (found != -1 && found < cutOff) { 47 | cutOff = found 48 | } 49 | } 50 | if (cutOff != Infinity) stream.string = oldContent.slice(0, cutOff) 51 | var outerToken = outer.token(stream, state.outer) 52 | if (cutOff != Infinity) stream.string = oldContent 53 | return outerToken 54 | } else { 55 | var curInner = state.innerActive, 56 | oldContent = stream.string 57 | var found = indexOf(oldContent, curInner.close, stream.pos) 58 | if (found == stream.pos) { 59 | stream.match(curInner.close) 60 | state.innerActive = state.inner = null 61 | return curInner.delimStyle 62 | } 63 | if (found > -1) stream.string = oldContent.slice(0, found) 64 | var innerToken = curInner.mode.token(stream, state.inner) 65 | if (found > -1) stream.string = oldContent 66 | var cur = stream.current(), 67 | found = cur.indexOf(curInner.close) 68 | if (found > -1) stream.backUp(cur.length - found) 69 | return innerToken 70 | } 71 | }, 72 | 73 | indent: function(state, textAfter) { 74 | var mode = state.innerActive ? state.innerActive.mode : outer 75 | if (!mode.indent) return CodeMirror.Pass 76 | return mode.indent( 77 | state.innerActive ? state.inner : state.outer, 78 | textAfter 79 | ) 80 | }, 81 | 82 | electricChars: outer.electricChars, 83 | 84 | innerMode: function(state) { 85 | return state.inner 86 | ? { state: state.inner, mode: state.innerActive.mode } 87 | : { state: state.outer, mode: outer } 88 | } 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /styles/prism-solarizedlight.css: -------------------------------------------------------------------------------- 1 | /* 2 | Solarized Color Schemes originally by Ethan Schoonover 3 | http://ethanschoonover.com/solarized 4 | 5 | Ported for PrismJS by Hector Matos 6 | Website: https://krakendev.io 7 | Twitter Handle: https://twitter.com/allonsykraken) 8 | */ 9 | 10 | /* 11 | SOLARIZED HEX 12 | --------- ------- 13 | base03 #002b36 14 | base02 #073642 15 | base01 #586e75 16 | base00 #657b83 17 | base0 #839496 18 | base1 #93a1a1 19 | base2 #eee8d5 20 | base3 #fdf6e3 21 | yellow #b58900 22 | orange #cb4b16 23 | red #dc322f 24 | magenta #d33682 25 | violet #6c71c4 26 | blue #268bd2 27 | cyan #2aa198 28 | green #859900 29 | */ 30 | 31 | code[class*='language-'], 32 | pre[class*='language-'] { 33 | color: #657b83; /* base00 */ 34 | font-family: Consolas, Monaco, 'Andale Mono', 'Ubuntu Mono', monospace; 35 | text-align: left; 36 | white-space: pre; 37 | word-spacing: normal; 38 | word-break: normal; 39 | word-wrap: normal; 40 | 41 | line-height: 1.5; 42 | 43 | -moz-tab-size: 4; 44 | -o-tab-size: 4; 45 | tab-size: 4; 46 | 47 | -webkit-hyphens: none; 48 | -moz-hyphens: none; 49 | -ms-hyphens: none; 50 | hyphens: none; 51 | } 52 | 53 | pre[class*='language-']::-moz-selection, 54 | pre[class*='language-'] ::-moz-selection, 55 | code[class*='language-']::-moz-selection, 56 | code[class*='language-'] ::-moz-selection { 57 | background: #073642; /* base02 */ 58 | } 59 | 60 | pre[class*='language-']::selection, 61 | pre[class*='language-'] ::selection, 62 | code[class*='language-']::selection, 63 | code[class*='language-'] ::selection { 64 | background: #073642; /* base02 */ 65 | } 66 | 67 | /* Code blocks */ 68 | pre[class*='language-'] { 69 | padding: 1em; 70 | margin: 0.5em 0; 71 | overflow: auto; 72 | border-radius: 0.3em; 73 | } 74 | 75 | :not(pre) > code[class*='language-'], 76 | pre[class*='language-'] { 77 | background-color: #fdf6e3; /* base3 */ 78 | } 79 | 80 | /* Inline code */ 81 | :not(pre) > code[class*='language-'] { 82 | padding: 0.1em; 83 | border-radius: 0.3em; 84 | } 85 | 86 | .token.comment, 87 | .token.prolog, 88 | .token.doctype, 89 | .token.cdata { 90 | color: #93a1a1; /* base1 */ 91 | } 92 | 93 | .token.punctuation { 94 | color: #586e75; /* base01 */ 95 | } 96 | 97 | .namespace { 98 | opacity: 0.7; 99 | } 100 | 101 | .token.property, 102 | .token.tag, 103 | .token.boolean, 104 | .token.number, 105 | .token.constant, 106 | .token.symbol, 107 | .token.deleted { 108 | color: #268bd2; /* blue */ 109 | } 110 | 111 | .token.selector, 112 | .token.attr-name, 113 | .token.string, 114 | .token.char, 115 | .token.builtin, 116 | .token.url, 117 | .token.inserted { 118 | color: #2aa198; /* cyan */ 119 | } 120 | 121 | .token.entity { 122 | color: #657b83; /* base00 */ 123 | background: #eee8d5; /* base2 */ 124 | } 125 | 126 | .token.atrule, 127 | .token.attr-value, 128 | .token.keyword { 129 | color: #859900; /* green */ 130 | } 131 | 132 | .token.function { 133 | color: #b58900; /* yellow */ 134 | } 135 | 136 | .token.regex, 137 | .token.important, 138 | .token.variable { 139 | color: #cb4b16; /* orange */ 140 | } 141 | 142 | .token.important, 143 | .token.bold { 144 | font-weight: bold; 145 | } 146 | .token.italic { 147 | font-style: italic; 148 | } 149 | 150 | .token.entity { 151 | cursor: help; 152 | } 153 | -------------------------------------------------------------------------------- /lib/codemirror/overlay.js: -------------------------------------------------------------------------------- 1 | // CodeMirror, copyright (c) by Marijn Haverbeke and others 2 | // Distributed under an MIT license: http://codemirror.net/LICENSE 3 | 4 | // Utility function that allows modes to be combined. The mode given 5 | // as the base argument takes care of most of the normal mode 6 | // functionality, but a second (typically simple) mode is used, which 7 | // can override the style of text. Both modes get to parse all of the 8 | // text, but when both assign a non-null style to a piece of code, the 9 | // overlay wins, unless the combine argument was true and not overridden, 10 | // or state.overlay.combineTokens was true, in which case the styles are 11 | // combined. 12 | 13 | ;(function(mod) { 14 | if (typeof exports == 'object' && typeof module == 'object') 15 | // CommonJS 16 | mod(require('../../lib/codemirror')) 17 | else if (typeof define == 'function' && define.amd) 18 | // AMD 19 | define(['../../lib/codemirror'], mod) 20 | // Plain browser env 21 | else mod(CodeMirror) 22 | })(function(CodeMirror) { 23 | 'use strict' 24 | 25 | CodeMirror.overlayMode = function(base, overlay, combine) { 26 | return { 27 | startState: function() { 28 | return { 29 | base: CodeMirror.startState(base), 30 | overlay: CodeMirror.startState(overlay), 31 | basePos: 0, 32 | baseCur: null, 33 | overlayPos: 0, 34 | overlayCur: null, 35 | streamSeen: null 36 | } 37 | }, 38 | copyState: function(state) { 39 | return { 40 | base: CodeMirror.copyState(base, state.base), 41 | overlay: CodeMirror.copyState(overlay, state.overlay), 42 | basePos: state.basePos, 43 | baseCur: null, 44 | overlayPos: state.overlayPos, 45 | overlayCur: null 46 | } 47 | }, 48 | 49 | token: function(stream, state) { 50 | if ( 51 | stream != state.streamSeen || 52 | Math.min(state.basePos, state.overlayPos) < stream.start 53 | ) { 54 | state.streamSeen = stream 55 | state.basePos = state.overlayPos = stream.start 56 | } 57 | 58 | if (stream.start == state.basePos) { 59 | state.baseCur = base.token(stream, state.base) 60 | state.basePos = stream.pos 61 | } 62 | if (stream.start == state.overlayPos) { 63 | stream.pos = stream.start 64 | state.overlayCur = overlay.token(stream, state.overlay) 65 | state.overlayPos = stream.pos 66 | } 67 | stream.pos = Math.min(state.basePos, state.overlayPos) 68 | 69 | // state.overlay.combineTokens always takes precedence over combine, 70 | // unless set to null 71 | if (state.overlayCur == null) return state.baseCur 72 | else if ( 73 | (state.baseCur != null && state.overlay.combineTokens) || 74 | (combine && state.overlay.combineTokens == null) 75 | ) 76 | return state.baseCur + ' ' + state.overlayCur 77 | else return state.overlayCur 78 | }, 79 | 80 | indent: 81 | base.indent && 82 | function(state, textAfter) { 83 | return base.indent(state.base, textAfter) 84 | }, 85 | electricChars: base.electricChars, 86 | 87 | innerMode: function(state) { 88 | return { state: state.base, mode: base } 89 | }, 90 | 91 | blankLine: function(state) { 92 | if (base.blankLine) base.blankLine(state.base) 93 | if (overlay.blankLine) overlay.blankLine(state.overlay) 94 | } 95 | } 96 | } 97 | }) 98 | -------------------------------------------------------------------------------- /lib/codemirror/lib/util/runmode-standalone.js: -------------------------------------------------------------------------------- 1 | /* Just enough of CodeMirror to run runMode under node.js */ 2 | 3 | function splitLines(string) { 4 | return string.split(/\r?\n|\r/) 5 | } 6 | 7 | function StringStream(string) { 8 | this.pos = this.start = 0 9 | this.string = string 10 | } 11 | StringStream.prototype = { 12 | eol: function() { 13 | return this.pos >= this.string.length 14 | }, 15 | sol: function() { 16 | return this.pos == 0 17 | }, 18 | peek: function() { 19 | return this.string.charAt(this.pos) || null 20 | }, 21 | next: function() { 22 | if (this.pos < this.string.length) return this.string.charAt(this.pos++) 23 | }, 24 | eat: function(match) { 25 | var ch = this.string.charAt(this.pos) 26 | if (typeof match == 'string') var ok = ch == match 27 | else var ok = ch && (match.test ? match.test(ch) : match(ch)) 28 | if (ok) { 29 | ++this.pos 30 | return ch 31 | } 32 | }, 33 | eatWhile: function(match) { 34 | var start = this.pos 35 | while (this.eat(match)) {} 36 | return this.pos > start 37 | }, 38 | eatSpace: function() { 39 | var start = this.pos 40 | while (/[\s\u00a0]/.test(this.string.charAt(this.pos))) ++this.pos 41 | return this.pos > start 42 | }, 43 | skipToEnd: function() { 44 | this.pos = this.string.length 45 | }, 46 | skipTo: function(ch) { 47 | var found = this.string.indexOf(ch, this.pos) 48 | if (found > -1) { 49 | this.pos = found 50 | return true 51 | } 52 | }, 53 | backUp: function(n) { 54 | this.pos -= n 55 | }, 56 | column: function() { 57 | return this.start 58 | }, 59 | indentation: function() { 60 | return 0 61 | }, 62 | match: function(pattern, consume, caseInsensitive) { 63 | if (typeof pattern == 'string') { 64 | function cased(str) { 65 | return caseInsensitive ? str.toLowerCase() : str 66 | } 67 | if (cased(this.string).indexOf(cased(pattern), this.pos) == this.pos) { 68 | if (consume !== false) this.pos += pattern.length 69 | return true 70 | } 71 | } else { 72 | var match = this.string.slice(this.pos).match(pattern) 73 | if (match && consume !== false) this.pos += match[0].length 74 | return match 75 | } 76 | }, 77 | current: function() { 78 | return this.string.slice(this.start, this.pos) 79 | } 80 | } 81 | exports.StringStream = StringStream 82 | 83 | exports.startState = function(mode, a1, a2) { 84 | return mode.startState ? mode.startState(a1, a2) : true 85 | } 86 | 87 | var modes = (exports.modes = {}), 88 | mimeModes = (exports.mimeModes = {}) 89 | exports.defineMode = function(name, mode) { 90 | modes[name] = mode 91 | } 92 | exports.defineMIME = function(mime, spec) { 93 | mimeModes[mime] = spec 94 | } 95 | exports.getMode = function(options, spec) { 96 | if (typeof spec == 'string' && mimeModes.hasOwnProperty(spec)) 97 | spec = mimeModes[spec] 98 | if (typeof spec == 'string') 99 | var mname = spec, 100 | config = {} 101 | else if (spec != null) 102 | var mname = spec.name, 103 | config = spec 104 | var mfactory = modes[mname] 105 | if (!mfactory) throw new Error('Unknown mode: ' + spec) 106 | return mfactory(options, config || {}) 107 | } 108 | 109 | exports.runMode = function(string, modespec, callback) { 110 | var mode = exports.getMode({ indentUnit: 2 }, modespec) 111 | var lines = splitLines(string), 112 | state = exports.startState(mode) 113 | for (var i = 0, e = lines.length; i < e; ++i) { 114 | if (i) callback('\n') 115 | var stream = new exports.StringStream(lines[i]) 116 | while (!stream.eol()) { 117 | var style = mode.token(stream, state) 118 | callback(stream.current(), style, i, stream.start) 119 | stream.start = stream.pos 120 | } 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /styles/prism-xonokai.css: -------------------------------------------------------------------------------- 1 | /** 2 | * xonokai theme for JavaScript, CSS and HTML 3 | * based on: https://github.com/MoOx/sass-prism-theme-base by Maxime Thirouin ~ MoOx --> http://moox.fr/ , which is Loosely based on Monokai textmate theme by http://www.monokai.nl/ 4 | * license: MIT; http://moox.mit-license.org/ 5 | */ 6 | code[class*='language-'], 7 | pre[class*='language-'] { 8 | -moz-tab-size: 2; 9 | -o-tab-size: 2; 10 | tab-size: 2; 11 | -webkit-hyphens: none; 12 | -moz-hyphens: none; 13 | -ms-hyphens: none; 14 | hyphens: none; 15 | white-space: pre; 16 | white-space: pre-wrap; 17 | word-wrap: normal; 18 | font-family: Menlo, Monaco, 'Courier New', monospace; 19 | font-size: 14px; 20 | color: #76d9e6; 21 | text-shadow: none; 22 | } 23 | pre[class*='language-'], 24 | :not(pre) > code[class*='language-'] { 25 | background: #2a2a2a; 26 | } 27 | pre[class*='language-'] { 28 | padding: 15px; 29 | border-radius: 4px; 30 | border: 1px solid #e1e1e8; 31 | overflow: auto; 32 | } 33 | 34 | pre[class*='language-'] { 35 | position: relative; 36 | } 37 | pre[class*='language-'] code { 38 | white-space: pre; 39 | display: block; 40 | } 41 | 42 | :not(pre) > code[class*='language-'] { 43 | padding: 0.15em 0.2em 0.05em; 44 | border-radius: 0.3em; 45 | border: 0.13em solid #7a6652; 46 | box-shadow: 1px 1px 0.3em -0.1em #000 inset; 47 | } 48 | .token.namespace { 49 | opacity: 0.7; 50 | } 51 | .token.comment, 52 | .token.prolog, 53 | .token.doctype, 54 | .token.cdata { 55 | color: #6f705e; 56 | } 57 | .token.operator, 58 | .token.boolean, 59 | .token.number { 60 | color: #a77afe; 61 | } 62 | .token.attr-name, 63 | .token.string { 64 | color: #e6d06c; 65 | } 66 | .token.entity, 67 | .token.url, 68 | .language-css .token.string, 69 | .style .token.string { 70 | color: #e6d06c; 71 | } 72 | .token.selector, 73 | .token.inserted { 74 | color: #a6e22d; 75 | } 76 | .token.atrule, 77 | .token.attr-value, 78 | .token.keyword, 79 | .token.important, 80 | .token.deleted { 81 | color: #ef3b7d; 82 | } 83 | .token.regex, 84 | .token.statement { 85 | color: #76d9e6; 86 | } 87 | .token.placeholder, 88 | .token.variable { 89 | color: #fff; 90 | } 91 | .token.important, 92 | .token.statement, 93 | .token.bold { 94 | font-weight: bold; 95 | } 96 | .token.punctuation { 97 | color: #bebec5; 98 | } 99 | .token.entity { 100 | cursor: help; 101 | } 102 | .token.italic { 103 | font-style: italic; 104 | } 105 | 106 | code.language-markup { 107 | color: #f9f9f9; 108 | } 109 | code.language-markup .token.tag { 110 | color: #ef3b7d; 111 | } 112 | code.language-markup .token.attr-name { 113 | color: #a6e22d; 114 | } 115 | code.language-markup .token.attr-value { 116 | color: #e6d06c; 117 | } 118 | code.language-markup .token.style, 119 | code.language-markup .token.script { 120 | color: #76d9e6; 121 | } 122 | code.language-markup .token.script .token.keyword { 123 | color: #76d9e6; 124 | } 125 | 126 | /* Line highlight plugin */ 127 | pre[class*='language-'][data-line] { 128 | position: relative; 129 | padding: 1em 0 1em 3em; 130 | } 131 | pre[data-line] .line-highlight { 132 | position: absolute; 133 | left: 0; 134 | right: 0; 135 | padding: 0; 136 | margin-top: 1em; 137 | background: rgba(255, 255, 255, 0.08); 138 | pointer-events: none; 139 | line-height: inherit; 140 | white-space: pre; 141 | } 142 | pre[data-line] .line-highlight:before, 143 | pre[data-line] .line-highlight[data-end]:after { 144 | content: attr(data-start); 145 | position: absolute; 146 | top: 0.4em; 147 | left: 0.6em; 148 | min-width: 1em; 149 | padding: 0.2em 0.5em; 150 | background-color: rgba(255, 255, 255, 0.4); 151 | color: black; 152 | font: bold 65%/1 sans-serif; 153 | height: 1em; 154 | line-height: 1em; 155 | text-align: center; 156 | border-radius: 999px; 157 | text-shadow: none; 158 | box-shadow: 0 1px 1px rgba(255, 255, 255, 0.7); 159 | } 160 | pre[data-line] .line-highlight[data-end]:after { 161 | content: attr(data-end); 162 | top: auto; 163 | bottom: 0.4em; 164 | } 165 | -------------------------------------------------------------------------------- /lib/codemirror/lib/util/simple-hint.js: -------------------------------------------------------------------------------- 1 | ;(function() { 2 | CodeMirror.simpleHint = function(editor, getHints, givenOptions) { 3 | // Determine effective options based on given values and defaults. 4 | var options = {}, 5 | defaults = CodeMirror.simpleHint.defaults 6 | for (var opt in defaults) 7 | if (defaults.hasOwnProperty(opt)) 8 | options[opt] = (givenOptions && givenOptions.hasOwnProperty(opt) 9 | ? givenOptions 10 | : defaults)[opt] 11 | 12 | function collectHints(previousToken) { 13 | // We want a single cursor position. 14 | if (editor.somethingSelected()) return 15 | 16 | var tempToken = editor.getTokenAt(editor.getCursor()) 17 | 18 | // Don't show completions if token has changed and the option is set. 19 | if ( 20 | options.closeOnTokenChange && 21 | previousToken != null && 22 | (tempToken.start != previousToken.start || 23 | tempToken.className != previousToken.className) 24 | ) { 25 | return 26 | } 27 | 28 | var result = getHints(editor) 29 | if (!result || !result.list.length) return 30 | var completions = result.list 31 | function insert(str) { 32 | editor.replaceRange(str, result.from, result.to) 33 | } 34 | // When there is only one completion, use it directly. 35 | if (completions.length == 1) { 36 | insert(completions[0]) 37 | return true 38 | } 39 | 40 | // Build the select widget 41 | var complete = document.createElement('div') 42 | complete.className = 'CodeMirror-completions' 43 | var sel = complete.appendChild(document.createElement('select')) 44 | // Opera doesn't move the selection when pressing up/down in a 45 | // multi-select, but it does properly support the size property on 46 | // single-selects, so no multi-select is necessary. 47 | if (!window.opera) sel.multiple = true 48 | for (var i = 0; i < completions.length; ++i) { 49 | var opt = sel.appendChild(document.createElement('option')) 50 | opt.appendChild(document.createTextNode(completions[i])) 51 | } 52 | sel.firstChild.selected = true 53 | sel.size = Math.min(10, completions.length) 54 | var pos = editor.cursorCoords() 55 | complete.style.left = pos.x + 'px' 56 | complete.style.top = pos.yBot + 'px' 57 | document.body.appendChild(complete) 58 | // If we're at the edge of the screen, then we want the menu to appear on the left of the cursor. 59 | var winW = 60 | window.innerWidth || 61 | Math.max( 62 | document.body.offsetWidth, 63 | document.documentElement.offsetWidth 64 | ) 65 | if (winW - pos.x < sel.clientWidth) 66 | complete.style.left = pos.x - sel.clientWidth + 'px' 67 | // Hack to hide the scrollbar. 68 | if (completions.length <= 10) 69 | complete.style.width = sel.clientWidth - 1 + 'px' 70 | 71 | var done = false 72 | function close() { 73 | if (done) return 74 | done = true 75 | complete.parentNode.removeChild(complete) 76 | } 77 | function pick() { 78 | insert(completions[sel.selectedIndex]) 79 | close() 80 | setTimeout(function() { 81 | editor.focus() 82 | }, 50) 83 | } 84 | CodeMirror.connect(sel, 'blur', close) 85 | CodeMirror.connect(sel, 'keydown', function(event) { 86 | var code = event.keyCode 87 | // Enter 88 | if (code == 13) { 89 | CodeMirror.e_stop(event) 90 | pick() 91 | } 92 | // Escape 93 | else if (code == 27) { 94 | CodeMirror.e_stop(event) 95 | close() 96 | editor.focus() 97 | } else if ( 98 | code != 38 && 99 | code != 40 && 100 | code != 33 && 101 | code != 34 && 102 | !CodeMirror.isModifierKey(event) 103 | ) { 104 | close() 105 | editor.focus() 106 | // Pass the event to the CodeMirror instance so that it can handle things like backspace properly. 107 | editor.triggerOnKeyDown(event) 108 | // Don't show completions if the code is backspace and the option is set. 109 | if (!options.closeOnBackspace || code != 8) { 110 | setTimeout(function() { 111 | collectHints(tempToken) 112 | }, 50) 113 | } 114 | } 115 | }) 116 | CodeMirror.connect(sel, 'dblclick', pick) 117 | 118 | sel.focus() 119 | // Opera sometimes ignores focusing a freshly created node 120 | if (window.opera) 121 | setTimeout(function() { 122 | if (!done) sel.focus() 123 | }, 100) 124 | return true 125 | } 126 | return collectHints() 127 | } 128 | CodeMirror.simpleHint.defaults = { 129 | closeOnBackspace: true, 130 | closeOnTokenChange: false 131 | } 132 | })() 133 | -------------------------------------------------------------------------------- /lib/codemirror/gfm/gfm.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') 6 | // CommonJS 7 | mod( 8 | require('../../lib/codemirror'), 9 | require('../markdown/markdown'), 10 | require('../../addon/mode/overlay') 11 | ) 12 | else if (typeof define == 'function' && define.amd) 13 | // AMD 14 | define([ 15 | '../../lib/codemirror', 16 | '../markdown/markdown', 17 | '../../addon/mode/overlay' 18 | ], mod) 19 | // Plain browser env 20 | else mod(CodeMirror) 21 | })(function(CodeMirror) { 22 | 'use strict' 23 | 24 | CodeMirror.defineMode( 25 | 'gfm', 26 | function(config, modeConfig) { 27 | var codeDepth = 0 28 | function blankLine(state) { 29 | state.code = false 30 | return null 31 | } 32 | var gfmOverlay = { 33 | startState: function() { 34 | return { 35 | code: false, 36 | codeBlock: false, 37 | ateSpace: false 38 | } 39 | }, 40 | copyState: function(s) { 41 | return { 42 | code: s.code, 43 | codeBlock: s.codeBlock, 44 | ateSpace: s.ateSpace 45 | } 46 | }, 47 | token: function(stream, state) { 48 | state.combineTokens = null 49 | 50 | // Hack to prevent formatting override inside code blocks (block and inline) 51 | if (state.codeBlock) { 52 | if (stream.match(/^```/)) { 53 | state.codeBlock = false 54 | return null 55 | } 56 | stream.skipToEnd() 57 | return null 58 | } 59 | if (stream.sol()) { 60 | state.code = false 61 | } 62 | if (stream.sol() && stream.match(/^```/)) { 63 | stream.skipToEnd() 64 | state.codeBlock = true 65 | return null 66 | } 67 | // If this block is changed, it may need to be updated in Markdown mode 68 | if (stream.peek() === '`') { 69 | stream.next() 70 | var before = stream.pos 71 | stream.eatWhile('`') 72 | var difference = 1 + stream.pos - before 73 | if (!state.code) { 74 | codeDepth = difference 75 | state.code = true 76 | } else { 77 | if (difference === codeDepth) { 78 | // Must be exact 79 | state.code = false 80 | } 81 | } 82 | return null 83 | } else if (state.code) { 84 | stream.next() 85 | return null 86 | } 87 | // Check if space. If so, links can be formatted later on 88 | if (stream.eatSpace()) { 89 | state.ateSpace = true 90 | return null 91 | } 92 | if (stream.sol() || state.ateSpace) { 93 | state.ateSpace = false 94 | if ( 95 | stream.match( 96 | /^(?:[a-zA-Z0-9\-_]+\/)?(?:[a-zA-Z0-9\-_]+@)?(?:[a-f0-9]{7,40}\b)/ 97 | ) 98 | ) { 99 | // User/Project@SHA 100 | // User@SHA 101 | // SHA 102 | state.combineTokens = true 103 | return 'link' 104 | } else if ( 105 | stream.match( 106 | /^(?:[a-zA-Z0-9\-_]+\/)?(?:[a-zA-Z0-9\-_]+)?#[0-9]+\b/ 107 | ) 108 | ) { 109 | // User/Project#Num 110 | // User#Num 111 | // #Num 112 | state.combineTokens = true 113 | return 'link' 114 | } 115 | } 116 | if ( 117 | stream.match( 118 | /^((?:[a-z][\w-]+:(?:\/{1,3}|[a-z0-9%])|www\d{0,3}[.]|[a-z0-9.\-]+[.][a-z]{2,4}\/)(?:[^\s()<>]|\([^\s()<>]*\))+(?:\([^\s()<>]*\)|[^\s`*!()\[\]{};:'".,<>?«»“”‘’]))/i 119 | ) && 120 | stream.string.slice(stream.start - 2, stream.start) != '](' 121 | ) { 122 | // URLs 123 | // Taken from http://daringfireball.net/2010/07/improved_regex_for_matching_urls 124 | // And then (issue #1160) simplified to make it not crash the Chrome Regexp engine 125 | state.combineTokens = true 126 | return 'link' 127 | } 128 | stream.next() 129 | return null 130 | }, 131 | blankLine: blankLine 132 | } 133 | 134 | var markdownConfig = { 135 | underscoresBreakWords: false, 136 | taskLists: true, 137 | fencedCodeBlocks: true, 138 | strikethrough: true 139 | } 140 | for (var attr in modeConfig) { 141 | markdownConfig[attr] = modeConfig[attr] 142 | } 143 | markdownConfig.name = 'markdown' 144 | CodeMirror.defineMIME('gfmBase', markdownConfig) 145 | return CodeMirror.overlayMode( 146 | CodeMirror.getMode(config, 'gfmBase'), 147 | gfmOverlay 148 | ) 149 | }, 150 | 'markdown' 151 | ) 152 | }) 153 | -------------------------------------------------------------------------------- /styles/prism-twilight.css: -------------------------------------------------------------------------------- 1 | /** 2 | * prism.js Twilight theme 3 | * Based (more or less) on the Twilight theme originally of Textmate fame. 4 | * @author Remy Bach 5 | */ 6 | code[class*='language-'], 7 | pre[class*='language-'] { 8 | color: white; 9 | background: none; 10 | font-family: Consolas, Monaco, 'Andale Mono', 'Ubuntu Mono', monospace; 11 | text-align: left; 12 | text-shadow: 0 -0.1em 0.2em black; 13 | white-space: pre; 14 | word-spacing: normal; 15 | word-break: normal; 16 | word-wrap: normal; 17 | line-height: 1.5; 18 | 19 | -moz-tab-size: 4; 20 | -o-tab-size: 4; 21 | tab-size: 4; 22 | 23 | -webkit-hyphens: none; 24 | -moz-hyphens: none; 25 | -ms-hyphens: none; 26 | hyphens: none; 27 | } 28 | 29 | pre[class*='language-'], 30 | :not(pre) > code[class*='language-'] { 31 | background: hsl(0, 0%, 8%); /* #141414 */ 32 | } 33 | 34 | /* Code blocks */ 35 | pre[class*='language-'] { 36 | border-radius: 0.5em; 37 | border: 0.3em solid hsl(0, 0%, 33%); /* #282A2B */ 38 | box-shadow: 1px 1px 0.5em black inset; 39 | margin: 0.5em 0; 40 | overflow: auto; 41 | padding: 1em; 42 | } 43 | 44 | pre[class*='language-']::-moz-selection { 45 | /* Firefox */ 46 | background: hsl(200, 4%, 16%); /* #282A2B */ 47 | } 48 | 49 | pre[class*='language-']::selection { 50 | /* Safari */ 51 | background: hsl(200, 4%, 16%); /* #282A2B */ 52 | } 53 | 54 | /* Text Selection colour */ 55 | pre[class*='language-']::-moz-selection, 56 | pre[class*='language-'] ::-moz-selection, 57 | code[class*='language-']::-moz-selection, 58 | code[class*='language-'] ::-moz-selection { 59 | text-shadow: none; 60 | background: hsla(0, 0%, 93%, 0.15); /* #EDEDED */ 61 | } 62 | 63 | pre[class*='language-']::selection, 64 | pre[class*='language-'] ::selection, 65 | code[class*='language-']::selection, 66 | code[class*='language-'] ::selection { 67 | text-shadow: none; 68 | background: hsla(0, 0%, 93%, 0.15); /* #EDEDED */ 69 | } 70 | 71 | /* Inline code */ 72 | :not(pre) > code[class*='language-'] { 73 | border-radius: 0.3em; 74 | border: 0.13em solid hsl(0, 0%, 33%); /* #545454 */ 75 | box-shadow: 1px 1px 0.3em -0.1em black inset; 76 | padding: 0.15em 0.2em 0.05em; 77 | white-space: normal; 78 | } 79 | 80 | .token.comment, 81 | .token.prolog, 82 | .token.doctype, 83 | .token.cdata { 84 | color: hsl(0, 0%, 47%); /* #777777 */ 85 | } 86 | 87 | .token.punctuation { 88 | opacity: 0.7; 89 | } 90 | 91 | .namespace { 92 | opacity: 0.7; 93 | } 94 | 95 | .token.tag, 96 | .token.boolean, 97 | .token.number, 98 | .token.deleted { 99 | color: hsl(14, 58%, 55%); /* #CF6A4C */ 100 | } 101 | 102 | .token.keyword, 103 | .token.property, 104 | .token.selector, 105 | .token.constant, 106 | .token.symbol, 107 | .token.builtin { 108 | color: hsl(53, 89%, 79%); /* #F9EE98 */ 109 | } 110 | 111 | .token.attr-name, 112 | .token.attr-value, 113 | .token.string, 114 | .token.char, 115 | .token.operator, 116 | .token.entity, 117 | .token.url, 118 | .language-css .token.string, 119 | .style .token.string, 120 | .token.variable, 121 | .token.inserted { 122 | color: hsl(76, 21%, 52%); /* #8F9D6A */ 123 | } 124 | 125 | .token.atrule { 126 | color: hsl(218, 22%, 55%); /* #7587A6 */ 127 | } 128 | 129 | .token.regex, 130 | .token.important { 131 | color: hsl(42, 75%, 65%); /* #E9C062 */ 132 | } 133 | 134 | .token.important, 135 | .token.bold { 136 | font-weight: bold; 137 | } 138 | .token.italic { 139 | font-style: italic; 140 | } 141 | 142 | .token.entity { 143 | cursor: help; 144 | } 145 | 146 | pre[data-line] { 147 | padding: 1em 0 1em 3em; 148 | position: relative; 149 | } 150 | 151 | /* Markup */ 152 | .language-markup .token.tag, 153 | .language-markup .token.attr-name, 154 | .language-markup .token.punctuation { 155 | color: hsl(33, 33%, 52%); /* #AC885B */ 156 | } 157 | 158 | /* Make the tokens sit above the line highlight so the colours don't look faded. */ 159 | .token { 160 | position: relative; 161 | z-index: 1; 162 | } 163 | 164 | .line-highlight { 165 | background: hsla(0, 0%, 33%, 0.25); /* #545454 */ 166 | background: linear-gradient( 167 | to right, 168 | hsla(0, 0%, 33%, 0.1) 70%, 169 | hsla(0, 0%, 33%, 0) 170 | ); /* #545454 */ 171 | border-bottom: 1px dashed hsl(0, 0%, 33%); /* #545454 */ 172 | border-top: 1px dashed hsl(0, 0%, 33%); /* #545454 */ 173 | left: 0; 174 | line-height: inherit; 175 | margin-top: 0.75em; /* Same as .prism’s padding-top */ 176 | padding: inherit 0; 177 | pointer-events: none; 178 | position: absolute; 179 | right: 0; 180 | white-space: pre; 181 | z-index: 0; 182 | } 183 | 184 | .line-highlight:before, 185 | .line-highlight[data-end]:after { 186 | background-color: hsl(215, 15%, 59%); /* #8794A6 */ 187 | border-radius: 999px; 188 | box-shadow: 0 1px white; 189 | color: hsl(24, 20%, 95%); /* #F5F2F0 */ 190 | content: attr(data-start); 191 | font: bold 65%/1.5 sans-serif; 192 | left: 0.6em; 193 | min-width: 1em; 194 | padding: 0 0.5em; 195 | position: absolute; 196 | text-align: center; 197 | text-shadow: none; 198 | top: 0.4em; 199 | vertical-align: 0.3em; 200 | } 201 | 202 | .line-highlight[data-end]:after { 203 | bottom: 0.4em; 204 | content: attr(data-end); 205 | top: auto; 206 | } 207 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 微信公众号助手 6 | 7 | 8 | 9 | 10 | 16 | 17 | 18 |
19 |
20 | 作者: 21 | 22 |    23 | 文章地址: 24 | 25 |
26 | 27 |
28 | New 29 | Code Style 30 | Weekly 31 | Copy 32 | Inject 33 |
34 |
35 | 36 |
37 |
38 | 39 |
40 |
41 |
42 | 43 |
44 |
45 | 52 |
53 | 请输入正确格式的 url 54 |
55 | 确认抓取 56 |
57 |
58 | 59 |
60 |
61 |
62 | 66 | 73 | 74 | 75 | 83 | 84 | 85 |
86 | 91 | 94 |
95 | 96 | 97 |
98 | 103 | 106 |
107 | 108 | 109 | 115 |
116 | 117 |
118 | 122 | 123 | 124 | 128 | 129 | 130 | 周刊编辑完成 131 |
132 |
133 |
134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | -------------------------------------------------------------------------------- /lib/codemirror/lib/util/pig-hint.js: -------------------------------------------------------------------------------- 1 | ;(function() { 2 | function forEach(arr, f) { 3 | for (var i = 0, e = arr.length; i < e; ++i) f(arr[i]) 4 | } 5 | 6 | function arrayContains(arr, item) { 7 | if (!Array.prototype.indexOf) { 8 | var i = arr.length 9 | while (i--) { 10 | if (arr[i] === item) { 11 | return true 12 | } 13 | } 14 | return false 15 | } 16 | return arr.indexOf(item) != -1 17 | } 18 | 19 | function scriptHint(editor, keywords, getToken) { 20 | // Find the token at the cursor 21 | var cur = editor.getCursor(), 22 | token = getToken(editor, cur), 23 | tprop = token 24 | // If it's not a 'word-style' token, ignore the token. 25 | 26 | if (!/^[\w$_]*$/.test(token.string)) { 27 | token = tprop = { 28 | start: cur.ch, 29 | end: cur.ch, 30 | string: '', 31 | state: token.state, 32 | className: token.string == ':' ? 'pig-type' : null 33 | } 34 | } 35 | 36 | if (!context) var context = [] 37 | context.push(tprop) 38 | 39 | var completionList = getCompletions(token, context) 40 | completionList = completionList.sort() 41 | //prevent autocomplete for last word, instead show dropdown with one word 42 | if (completionList.length == 1) { 43 | completionList.push(' ') 44 | } 45 | 46 | return { 47 | list: completionList, 48 | from: { line: cur.line, ch: token.start }, 49 | to: { line: cur.line, ch: token.end } 50 | } 51 | } 52 | 53 | CodeMirror.pigHint = function(editor) { 54 | return scriptHint(editor, pigKeywordsU, function(e, cur) { 55 | return e.getTokenAt(cur) 56 | }) 57 | } 58 | 59 | function toTitleCase(str) { 60 | return str.replace(/(?:^|\s)\w/g, function(match) { 61 | return match.toUpperCase() 62 | }) 63 | } 64 | 65 | var pigKeywords = 66 | 'VOID IMPORT RETURNS DEFINE LOAD FILTER FOREACH ORDER CUBE DISTINCT COGROUP ' + 67 | 'JOIN CROSS UNION SPLIT INTO IF OTHERWISE ALL AS BY USING INNER OUTER ONSCHEMA PARALLEL ' + 68 | 'PARTITION GROUP AND OR NOT GENERATE FLATTEN ASC DESC IS STREAM THROUGH STORE MAPREDUCE ' + 69 | 'SHIP CACHE INPUT OUTPUT STDERROR STDIN STDOUT LIMIT SAMPLE LEFT RIGHT FULL EQ GT LT GTE LTE ' + 70 | 'NEQ MATCHES TRUE FALSE' 71 | var pigKeywordsU = pigKeywords.split(' ') 72 | var pigKeywordsL = pigKeywords.toLowerCase().split(' ') 73 | 74 | var pigTypes = 75 | 'BOOLEAN INT LONG FLOAT DOUBLE CHARARRAY BYTEARRAY BAG TUPLE MAP' 76 | var pigTypesU = pigTypes.split(' ') 77 | var pigTypesL = pigTypes.toLowerCase().split(' ') 78 | 79 | var pigBuiltins = 80 | 'ABS ACOS ARITY ASIN ATAN AVG BAGSIZE BINSTORAGE BLOOM BUILDBLOOM CBRT CEIL ' + 81 | 'CONCAT COR COS COSH COUNT COUNT_STAR COV CONSTANTSIZE CUBEDIMENSIONS DIFF DISTINCT DOUBLEABS ' + 82 | 'DOUBLEAVG DOUBLEBASE DOUBLEMAX DOUBLEMIN DOUBLEROUND DOUBLESUM EXP FLOOR FLOATABS FLOATAVG ' + 83 | 'FLOATMAX FLOATMIN FLOATROUND FLOATSUM GENERICINVOKER INDEXOF INTABS INTAVG INTMAX INTMIN ' + 84 | 'INTSUM INVOKEFORDOUBLE INVOKEFORFLOAT INVOKEFORINT INVOKEFORLONG INVOKEFORSTRING INVOKER ' + 85 | 'ISEMPTY JSONLOADER JSONMETADATA JSONSTORAGE LAST_INDEX_OF LCFIRST LOG LOG10 LOWER LONGABS ' + 86 | 'LONGAVG LONGMAX LONGMIN LONGSUM MAX MIN MAPSIZE MONITOREDUDF NONDETERMINISTIC OUTPUTSCHEMA ' + 87 | 'PIGSTORAGE PIGSTREAMING RANDOM REGEX_EXTRACT REGEX_EXTRACT_ALL REPLACE ROUND SIN SINH SIZE ' + 88 | 'SQRT STRSPLIT SUBSTRING SUM STRINGCONCAT STRINGMAX STRINGMIN STRINGSIZE TAN TANH TOBAG ' + 89 | 'TOKENIZE TOMAP TOP TOTUPLE TRIM TEXTLOADER TUPLESIZE UCFIRST UPPER UTF8STORAGECONVERTER' 90 | var pigBuiltinsU = pigBuiltins 91 | .split(' ') 92 | .join('() ') 93 | .split(' ') 94 | var pigBuiltinsL = pigBuiltins 95 | .toLowerCase() 96 | .split(' ') 97 | .join('() ') 98 | .split(' ') 99 | var pigBuiltinsC = ( 100 | 'BagSize BinStorage Bloom BuildBloom ConstantSize CubeDimensions DoubleAbs ' + 101 | 'DoubleAvg DoubleBase DoubleMax DoubleMin DoubleRound DoubleSum FloatAbs FloatAvg FloatMax ' + 102 | 'FloatMin FloatRound FloatSum GenericInvoker IntAbs IntAvg IntMax IntMin IntSum ' + 103 | 'InvokeForDouble InvokeForFloat InvokeForInt InvokeForLong InvokeForString Invoker ' + 104 | 'IsEmpty JsonLoader JsonMetadata JsonStorage LongAbs LongAvg LongMax LongMin LongSum MapSize ' + 105 | 'MonitoredUDF Nondeterministic OutputSchema PigStorage PigStreaming StringConcat StringMax ' + 106 | 'StringMin StringSize TextLoader TupleSize Utf8StorageConverter' 107 | ) 108 | .split(' ') 109 | .join('() ') 110 | .split(' ') 111 | 112 | function getCompletions(token, context) { 113 | var found = [], 114 | start = token.string 115 | function maybeAdd(str) { 116 | if (str.indexOf(start) == 0 && !arrayContains(found, str)) found.push(str) 117 | } 118 | 119 | function gatherCompletions(obj) { 120 | if (obj == ':') { 121 | forEach(pigTypesL, maybeAdd) 122 | } else { 123 | forEach(pigBuiltinsU, maybeAdd) 124 | forEach(pigBuiltinsL, maybeAdd) 125 | forEach(pigBuiltinsC, maybeAdd) 126 | forEach(pigTypesU, maybeAdd) 127 | forEach(pigTypesL, maybeAdd) 128 | forEach(pigKeywordsU, maybeAdd) 129 | forEach(pigKeywordsL, maybeAdd) 130 | } 131 | } 132 | 133 | if (context) { 134 | // If this is a property, see if it belongs to some object we can 135 | // find in the current environment. 136 | var obj = context.pop(), 137 | base 138 | 139 | if (obj.className == 'pig-word') base = obj.string 140 | else if (obj.className == 'pig-type') base = ':' + obj.string 141 | 142 | while (base != null && context.length) base = base[context.pop().string] 143 | if (base != null) gatherCompletions(base) 144 | } 145 | return found 146 | } 147 | })() 148 | -------------------------------------------------------------------------------- /lib/codemirror/lib/util/searchcursor.js: -------------------------------------------------------------------------------- 1 | ;(function() { 2 | function SearchCursor(cm, query, pos, caseFold) { 3 | this.atOccurrence = false 4 | this.cm = cm 5 | if (caseFold == null && typeof query == 'string') caseFold = false 6 | 7 | pos = pos ? cm.clipPos(pos) : { line: 0, ch: 0 } 8 | this.pos = { from: pos, to: pos } 9 | 10 | // The matches method is filled in based on the type of query. 11 | // It takes a position and a direction, and returns an object 12 | // describing the next occurrence of the query, or null if no 13 | // more matches were found. 14 | if (typeof query != 'string') { 15 | // Regexp match 16 | if (!query.global) 17 | query = new RegExp(query.source, query.ignoreCase ? 'ig' : 'g') 18 | this.matches = function(reverse, pos) { 19 | if (reverse) { 20 | query.lastIndex = 0 21 | var line = cm.getLine(pos.line).slice(0, pos.ch), 22 | match = query.exec(line), 23 | start = 0 24 | while (match) { 25 | start += match.index + 1 26 | line = line.slice(start) 27 | query.lastIndex = 0 28 | var newmatch = query.exec(line) 29 | if (newmatch) match = newmatch 30 | else break 31 | } 32 | start-- 33 | } else { 34 | query.lastIndex = pos.ch 35 | var line = cm.getLine(pos.line), 36 | match = query.exec(line), 37 | start = match && match.index 38 | } 39 | if (match) 40 | return { 41 | from: { line: pos.line, ch: start }, 42 | to: { line: pos.line, ch: start + match[0].length }, 43 | match: match 44 | } 45 | } 46 | } else { 47 | // String query 48 | if (caseFold) query = query.toLowerCase() 49 | var fold = caseFold 50 | ? function(str) { 51 | return str.toLowerCase() 52 | } 53 | : function(str) { 54 | return str 55 | } 56 | var target = query.split('\n') 57 | // Different methods for single-line and multi-line queries 58 | if (target.length == 1) 59 | this.matches = function(reverse, pos) { 60 | var line = fold(cm.getLine(pos.line)), 61 | len = query.length, 62 | match 63 | if ( 64 | reverse 65 | ? pos.ch >= len && 66 | (match = line.lastIndexOf(query, pos.ch - len)) != -1 67 | : (match = line.indexOf(query, pos.ch)) != -1 68 | ) 69 | return { 70 | from: { line: pos.line, ch: match }, 71 | to: { line: pos.line, ch: match + len } 72 | } 73 | } 74 | else 75 | this.matches = function(reverse, pos) { 76 | var ln = pos.line, 77 | idx = reverse ? target.length - 1 : 0, 78 | match = target[idx], 79 | line = fold(cm.getLine(ln)) 80 | var offsetA = reverse 81 | ? line.indexOf(match) + match.length 82 | : line.lastIndexOf(match) 83 | if ( 84 | reverse 85 | ? offsetA >= pos.ch || offsetA != match.length 86 | : offsetA <= pos.ch || offsetA != line.length - match.length 87 | ) 88 | return 89 | for (;;) { 90 | if (reverse ? !ln : ln == cm.lineCount() - 1) return 91 | line = fold(cm.getLine((ln += reverse ? -1 : 1))) 92 | match = target[reverse ? --idx : ++idx] 93 | if (idx > 0 && idx < target.length - 1) { 94 | if (line != match) return 95 | else continue 96 | } 97 | var offsetB = reverse 98 | ? line.lastIndexOf(match) 99 | : line.indexOf(match) + match.length 100 | if ( 101 | reverse 102 | ? offsetB != line.length - match.length 103 | : offsetB != match.length 104 | ) 105 | return 106 | var start = { line: pos.line, ch: offsetA }, 107 | end = { line: ln, ch: offsetB } 108 | return { from: reverse ? end : start, to: reverse ? start : end } 109 | } 110 | } 111 | } 112 | } 113 | 114 | SearchCursor.prototype = { 115 | findNext: function() { 116 | return this.find(false) 117 | }, 118 | findPrevious: function() { 119 | return this.find(true) 120 | }, 121 | 122 | find: function(reverse) { 123 | var self = this, 124 | pos = this.cm.clipPos(reverse ? this.pos.from : this.pos.to) 125 | function savePosAndFail(line) { 126 | var pos = { line: line, ch: 0 } 127 | self.pos = { from: pos, to: pos } 128 | self.atOccurrence = false 129 | return false 130 | } 131 | 132 | for (;;) { 133 | if ((this.pos = this.matches(reverse, pos))) { 134 | this.atOccurrence = true 135 | return this.pos.match || true 136 | } 137 | if (reverse) { 138 | if (!pos.line) return savePosAndFail(0) 139 | pos = { 140 | line: pos.line - 1, 141 | ch: this.cm.getLine(pos.line - 1).length 142 | } 143 | } else { 144 | var maxLine = this.cm.lineCount() 145 | if (pos.line == maxLine - 1) return savePosAndFail(maxLine) 146 | pos = { line: pos.line + 1, ch: 0 } 147 | } 148 | } 149 | }, 150 | 151 | from: function() { 152 | if (this.atOccurrence) return this.pos.from 153 | }, 154 | to: function() { 155 | if (this.atOccurrence) return this.pos.to 156 | }, 157 | 158 | replace: function(newText) { 159 | var self = this 160 | if (this.atOccurrence) 161 | self.pos.to = this.cm.replaceRange(newText, self.pos.from, self.pos.to) 162 | } 163 | } 164 | 165 | CodeMirror.defineExtension('getSearchCursor', function(query, pos, caseFold) { 166 | return new SearchCursor(this, query, pos, caseFold) 167 | }) 168 | })() 169 | -------------------------------------------------------------------------------- /lib/codemirror/lib/util/javascript-hint.js: -------------------------------------------------------------------------------- 1 | ;(function() { 2 | function forEach(arr, f) { 3 | for (var i = 0, e = arr.length; i < e; ++i) f(arr[i]) 4 | } 5 | 6 | function arrayContains(arr, item) { 7 | if (!Array.prototype.indexOf) { 8 | var i = arr.length 9 | while (i--) { 10 | if (arr[i] === item) { 11 | return true 12 | } 13 | } 14 | return false 15 | } 16 | return arr.indexOf(item) != -1 17 | } 18 | 19 | function scriptHint(editor, keywords, getToken) { 20 | // Find the token at the cursor 21 | var cur = editor.getCursor(), 22 | token = getToken(editor, cur), 23 | tprop = token 24 | // If it's not a 'word-style' token, ignore the token. 25 | if (!/^[\w$_]*$/.test(token.string)) { 26 | token = tprop = { 27 | start: cur.ch, 28 | end: cur.ch, 29 | string: '', 30 | state: token.state, 31 | className: token.string == '.' ? 'property' : null 32 | } 33 | } 34 | // If it is a property, find out what it is a property of. 35 | while (tprop.className == 'property') { 36 | tprop = getToken(editor, { line: cur.line, ch: tprop.start }) 37 | if (tprop.string != '.') return 38 | tprop = getToken(editor, { line: cur.line, ch: tprop.start }) 39 | if (tprop.string == ')') { 40 | var level = 1 41 | do { 42 | tprop = getToken(editor, { line: cur.line, ch: tprop.start }) 43 | switch (tprop.string) { 44 | case ')': 45 | level++ 46 | break 47 | case '(': 48 | level-- 49 | break 50 | default: 51 | break 52 | } 53 | } while (level > 0) 54 | tprop = getToken(editor, { line: cur.line, ch: tprop.start }) 55 | if (tprop.className == 'variable') tprop.className = 'function' 56 | else return // no clue 57 | } 58 | if (!context) var context = [] 59 | context.push(tprop) 60 | } 61 | return { 62 | list: getCompletions(token, context, keywords), 63 | from: { line: cur.line, ch: token.start }, 64 | to: { line: cur.line, ch: token.end } 65 | } 66 | } 67 | 68 | CodeMirror.javascriptHint = function(editor) { 69 | return scriptHint(editor, javascriptKeywords, function(e, cur) { 70 | return e.getTokenAt(cur) 71 | }) 72 | } 73 | 74 | function getCoffeeScriptToken(editor, cur) { 75 | // This getToken, it is for coffeescript, imitates the behavior of 76 | // getTokenAt method in javascript.js, that is, returning "property" 77 | // type and treat "." as indepenent token. 78 | var token = editor.getTokenAt(cur) 79 | if (cur.ch == token.start + 1 && token.string.charAt(0) == '.') { 80 | token.end = token.start 81 | token.string = '.' 82 | token.className = 'property' 83 | } else if (/^\.[\w$_]*$/.test(token.string)) { 84 | token.className = 'property' 85 | token.start++ 86 | token.string = token.string.replace(/\./, '') 87 | } 88 | return token 89 | } 90 | 91 | CodeMirror.coffeescriptHint = function(editor) { 92 | return scriptHint(editor, coffeescriptKeywords, getCoffeeScriptToken) 93 | } 94 | 95 | var stringProps = ( 96 | 'charAt charCodeAt indexOf lastIndexOf substring substr slice trim trimLeft trimRight ' + 97 | 'toUpperCase toLowerCase split concat match replace search' 98 | ).split(' ') 99 | var arrayProps = ( 100 | 'length concat join splice push pop shift unshift slice reverse sort indexOf ' + 101 | 'lastIndexOf every some filter forEach map reduce reduceRight ' 102 | ).split(' ') 103 | var funcProps = 'prototype apply call bind'.split(' ') 104 | var javascriptKeywords = ( 105 | 'break case catch continue debugger default delete do else false finally for function ' + 106 | 'if in instanceof new null return switch throw true try typeof var void while with' 107 | ).split(' ') 108 | var coffeescriptKeywords = ( 109 | 'and break catch class continue delete do else extends false finally for ' + 110 | 'if in instanceof isnt new no not null of off on or return switch then throw true try typeof until void while with yes' 111 | ).split(' ') 112 | 113 | function getCompletions(token, context, keywords) { 114 | var found = [], 115 | start = token.string 116 | function maybeAdd(str) { 117 | if (str.indexOf(start) == 0 && !arrayContains(found, str)) found.push(str) 118 | } 119 | function gatherCompletions(obj) { 120 | if (typeof obj == 'string') forEach(stringProps, maybeAdd) 121 | else if (obj instanceof Array) forEach(arrayProps, maybeAdd) 122 | else if (obj instanceof Function) forEach(funcProps, maybeAdd) 123 | for (var name in obj) maybeAdd(name) 124 | } 125 | 126 | if (context) { 127 | // If this is a property, see if it belongs to some object we can 128 | // find in the current environment. 129 | var obj = context.pop(), 130 | base 131 | if (obj.className == 'variable') base = window[obj.string] 132 | else if (obj.className == 'string') base = '' 133 | else if (obj.className == 'atom') base = 1 134 | else if (obj.className == 'function') { 135 | if ( 136 | window.jQuery != null && 137 | (obj.string == '$' || obj.string == 'jQuery') && 138 | typeof window.jQuery == 'function' 139 | ) 140 | base = window.jQuery() 141 | else if ( 142 | window._ != null && 143 | obj.string == '_' && 144 | typeof window._ == 'function' 145 | ) 146 | base = window._() 147 | } 148 | while (base != null && context.length) base = base[context.pop().string] 149 | if (base != null) gatherCompletions(base) 150 | } else { 151 | // If not, just look in the window object and any local scope 152 | // (reading into JS mode internals to get at the local variables) 153 | for (var v = token.state.localVars; v; v = v.next) maybeAdd(v.name) 154 | gatherCompletions(window) 155 | forEach(keywords, maybeAdd) 156 | } 157 | return found 158 | } 159 | })() 160 | -------------------------------------------------------------------------------- /scripts/common.js: -------------------------------------------------------------------------------- 1 | const log = console.log.bind(console) 2 | const create = tag => document.createElement(tag) 3 | const getDOM = s => document.querySelector(s) 4 | const getAll = (s, target = document) => { 5 | return Array.prototype.slice.call(target.querySelectorAll(s)) 6 | } 7 | 8 | // 插入 loading 9 | const Loading = (function() { 10 | var html = 11 | '
' 13 | let div = document.createElement('div') 14 | div.innerHTML = html 15 | document.body.appendChild(div) 16 | let elem = getAll('#loading-wx', div)[0] 17 | 18 | return { 19 | show: () => (elem.style.display = 'block'), 20 | hide: () => (elem.style.display = 'none') 21 | } 22 | })() 23 | 24 | /** 25 | * md5 26 | */ 27 | const md5 = (function() { 28 | let cache = new Map() 29 | 30 | return s => { 31 | if (cache.has(s)) { 32 | return cache.get(s) 33 | } else { 34 | let hash = CryptoJS.MD5(String(s)).toString() 35 | cache.set(s, hash) 36 | return hash 37 | } 38 | } 39 | })() 40 | 41 | /** 42 | * get/set Object using localStorage 43 | */ 44 | const store = { 45 | db: window.localStorage, 46 | prefix: '__wx__', 47 | set(key, value) { 48 | this.db.setItem(this.prefix + key, JSON.stringify(value)) 49 | }, 50 | 51 | get(key) { 52 | return ( 53 | (this.has(key) && JSON.parse(this.db.getItem(this.prefix + key))) || null 54 | ) 55 | }, 56 | 57 | has(key) { 58 | return !!this.db.getItem(this.prefix + key) 59 | }, 60 | 61 | keys() { 62 | let prefix = this.prefix 63 | return Object.keys(this.db) 64 | .filter(k => k.indexOf(prefix) === 0) 65 | .map(k => k.slice(prefix.length)) 66 | } 67 | } 68 | 69 | /** 70 | * generate DOM from string 71 | */ 72 | const divWrap = html => { 73 | let div = create('div') 74 | div.innerHTML = html 75 | return div 76 | } 77 | 78 | /** 79 | * dispatch event 80 | */ 81 | const dispatch = (elem, name) => { 82 | let evt = document.createEvent('HTMLEvents') 83 | evt.initEvent(name, false, false) 84 | elem.dispatchEvent(evt) 85 | } 86 | 87 | /** 88 | * copy text 89 | */ 90 | const copy = element => { 91 | let range = document.createRange() 92 | range.selectNode(element) 93 | 94 | let selection = window.getSelection() 95 | if (selection.rangeCount > 0) { 96 | selection.removeAllRanges() 97 | } 98 | selection.addRange(range) 99 | document.execCommand('copy') 100 | selection.removeAllRanges() 101 | } 102 | 103 | /** 104 | * paste 105 | */ 106 | const paste = () => { 107 | let textarea = createHelperTextArea() 108 | textarea.focus() 109 | document.execCommand('paste') 110 | textarea.remove() 111 | } 112 | 113 | /** 114 | * helper 115 | * create a temp textarea 116 | */ 117 | const createHelperTextArea = () => { 118 | const helperTextArea = create('textarea') 119 | helperTextArea.setAttribute('helper', 'true') 120 | helperTextArea.style.width = 0 121 | helperTextArea.style.height = 0 122 | document.body.appendChild(helperTextArea) 123 | return helperTextArea 124 | } 125 | 126 | /** 127 | * read blob as DataURI 128 | */ 129 | const blobToDataURI = blob => 130 | new Promise((resolve, reject) => { 131 | let reader = new FileReader() 132 | reader.readAsDataURL(blob) 133 | reader.onload = e => resolve(e.target.result) 134 | }) 135 | 136 | /** 137 | * https://zhitu.isux.us/ 138 | */ 139 | const isuxUpload = blob => 140 | new Promise((resolve, reject) => { 141 | let fileType = blob.type.replace('image/', '') 142 | let fileName = Date.now() + '.' + fileType 143 | let oriSize = (blob.size / 1024) | 0 144 | 145 | let file = new File([blob], fileName, { 146 | type: blob.type, 147 | lastModified: new Date(Date.now() - Math.random() * 1000 * 3600 * 24 * 50) 148 | }) 149 | 150 | let data = new FormData() 151 | data.append('name', fileName) 152 | // 10,20,30, ..., 100 153 | // TODO: set options for choosing `compress` param 154 | data.append('compress', 10) 155 | data.append('oriSize', oriSize) 156 | data.append('type', fileType) 157 | data.append('fileSelect', file) 158 | data.append('pngLess', 1) 159 | data.append('isOa', 0) 160 | data.append('typeChange', 1) 161 | 162 | let xhr = new XMLHttpRequest() 163 | 164 | xhr.onreadystatechange = function() { 165 | if (xhr.readyState === 4 && xhr.status === 200) { 166 | try { 167 | let res = JSON.parse(xhr.response) 168 | if (res.code === 0) { 169 | console.info('[isux compress]:', res) 170 | resolve(`https:${res.url}`) 171 | } else { 172 | throw res 173 | } 174 | } catch (e) { 175 | reject({ 176 | error: e, 177 | text: 'Error occurred: [' + xhr.responseText + ']' 178 | }) 179 | } 180 | } 181 | } 182 | 183 | xhr.open('POST', 'https://zhitu.isux.us/index.php/preview/imgcompress') 184 | xhr.send(data) 185 | }) 186 | 187 | /** 188 | * use `zhitu.isux.us` 189 | */ 190 | const isuxCompress = blob => { 191 | return isuxUpload(blob) 192 | .then(url => fetch(url)) 193 | .then(res => res.blob()) 194 | } 195 | 196 | /** 197 | * fetch image 198 | * transform it into BASE64 199 | */ 200 | const fetchImage = ({ url, middleware }) => { 201 | middleware = 202 | middleware || 203 | function(o) { 204 | return o 205 | } 206 | 207 | return fetch(url) 208 | .then(r => r.blob()) 209 | .then(middleware) 210 | .then(blobToDataURI) 211 | .catch(e => null) 212 | } 213 | -------------------------------------------------------------------------------- /lib/codemirror/lib/util/search.js: -------------------------------------------------------------------------------- 1 | // Define search commands. Depends on dialog.js or another 2 | // implementation of the openDialog method. 3 | 4 | // Replace works a little oddly -- it will do the replace on the next 5 | // Ctrl-G (or whatever is bound to findNext) press. You prevent a 6 | // replace by making sure the match is no longer selected when hitting 7 | // Ctrl-G. 8 | 9 | ;(function() { 10 | function SearchState() { 11 | this.posFrom = this.posTo = this.query = null 12 | this.marked = [] 13 | } 14 | function getSearchState(cm) { 15 | return cm._searchState || (cm._searchState = new SearchState()) 16 | } 17 | function getSearchCursor(cm, query, pos) { 18 | // Heuristic: if the query string is all lowercase, do a case insensitive search. 19 | return cm.getSearchCursor( 20 | query, 21 | pos, 22 | typeof query == 'string' && query == query.toLowerCase() 23 | ) 24 | } 25 | function dialog(cm, text, shortText, f) { 26 | if (cm.openDialog) cm.openDialog(text, f) 27 | else f(prompt(shortText, '')) 28 | } 29 | function confirmDialog(cm, text, shortText, fs) { 30 | if (cm.openConfirm) cm.openConfirm(text, fs) 31 | else if (confirm(shortText)) fs[0]() 32 | } 33 | function parseQuery(query) { 34 | var isRE = query.match(/^\/(.*)\/([a-z]*)$/) 35 | return isRE 36 | ? new RegExp(isRE[1], isRE[2].indexOf('i') == -1 ? '' : 'i') 37 | : query 38 | } 39 | var queryDialog = 40 | 'Search: (Use /re/ syntax for regexp search)' 41 | function doSearch(cm, rev) { 42 | var state = getSearchState(cm) 43 | if (state.query) return findNext(cm, rev) 44 | dialog(cm, queryDialog, 'Search for:', function(query) { 45 | cm.operation(function() { 46 | if (!query || state.query) return 47 | state.query = parseQuery(query) 48 | if (cm.lineCount() < 2000) { 49 | // This is too expensive on big documents. 50 | for ( 51 | var cursor = getSearchCursor(cm, state.query); 52 | cursor.findNext(); 53 | 54 | ) 55 | state.marked.push( 56 | cm.markText(cursor.from(), cursor.to(), 'CodeMirror-searching') 57 | ) 58 | } 59 | state.posFrom = state.posTo = cm.getCursor() 60 | findNext(cm, rev) 61 | }) 62 | }) 63 | } 64 | function findNext(cm, rev) { 65 | cm.operation(function() { 66 | var state = getSearchState(cm) 67 | var cursor = getSearchCursor( 68 | cm, 69 | state.query, 70 | rev ? state.posFrom : state.posTo 71 | ) 72 | if (!cursor.find(rev)) { 73 | cursor = getSearchCursor( 74 | cm, 75 | state.query, 76 | rev ? { line: cm.lineCount() - 1 } : { line: 0, ch: 0 } 77 | ) 78 | if (!cursor.find(rev)) return 79 | } 80 | cm.setSelection(cursor.from(), cursor.to()) 81 | state.posFrom = cursor.from() 82 | state.posTo = cursor.to() 83 | }) 84 | } 85 | function clearSearch(cm) { 86 | cm.operation(function() { 87 | var state = getSearchState(cm) 88 | if (!state.query) return 89 | state.query = null 90 | for (var i = 0; i < state.marked.length; ++i) state.marked[i].clear() 91 | state.marked.length = 0 92 | }) 93 | } 94 | 95 | var replaceQueryDialog = 96 | 'Replace: (Use /re/ syntax for regexp search)' 97 | var replacementQueryDialog = 'With: ' 98 | var doReplaceConfirm = 99 | 'Replace? ' 100 | function replace(cm, all) { 101 | dialog(cm, replaceQueryDialog, 'Replace:', function(query) { 102 | if (!query) return 103 | query = parseQuery(query) 104 | dialog(cm, replacementQueryDialog, 'Replace with:', function(text) { 105 | if (all) { 106 | cm.compoundChange(function() { 107 | cm.operation(function() { 108 | for ( 109 | var cursor = getSearchCursor(cm, query); 110 | cursor.findNext(); 111 | 112 | ) { 113 | if (typeof query != 'string') { 114 | var match = cm 115 | .getRange(cursor.from(), cursor.to()) 116 | .match(query) 117 | cursor.replace( 118 | text.replace(/\$(\d)/, function(w, i) { 119 | return match[i] 120 | }) 121 | ) 122 | } else cursor.replace(text) 123 | } 124 | }) 125 | }) 126 | } else { 127 | clearSearch(cm) 128 | var cursor = getSearchCursor(cm, query, cm.getCursor()) 129 | function advance() { 130 | var start = cursor.from(), 131 | match 132 | if (!(match = cursor.findNext())) { 133 | cursor = getSearchCursor(cm, query) 134 | if ( 135 | !(match = cursor.findNext()) || 136 | (start && 137 | cursor.from().line == start.line && 138 | cursor.from().ch == start.ch) 139 | ) 140 | return 141 | } 142 | cm.setSelection(cursor.from(), cursor.to()) 143 | confirmDialog(cm, doReplaceConfirm, 'Replace?', [ 144 | function() { 145 | doReplace(match) 146 | }, 147 | advance 148 | ]) 149 | } 150 | function doReplace(match) { 151 | cursor.replace( 152 | typeof query == 'string' 153 | ? text 154 | : text.replace(/\$(\d)/, function(w, i) { 155 | return match[i] 156 | }) 157 | ) 158 | advance() 159 | } 160 | advance() 161 | } 162 | }) 163 | }) 164 | } 165 | 166 | CodeMirror.commands.find = function(cm) { 167 | clearSearch(cm) 168 | doSearch(cm) 169 | } 170 | CodeMirror.commands.findNext = doSearch 171 | CodeMirror.commands.findPrev = function(cm) { 172 | doSearch(cm, true) 173 | } 174 | CodeMirror.commands.clearSearch = clearSearch 175 | CodeMirror.commands.replace = replace 176 | CodeMirror.commands.replaceAll = function(cm) { 177 | replace(cm, true) 178 | } 179 | })() 180 | -------------------------------------------------------------------------------- /lib/codemirror/htmlmixed/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') 6 | // CommonJS 7 | mod( 8 | require('../../lib/codemirror'), 9 | require('../xml/xml'), 10 | require('../javascript/javascript'), 11 | require('../css/css') 12 | ) 13 | else if (typeof define == 'function' && define.amd) 14 | // AMD 15 | define([ 16 | '../../lib/codemirror', 17 | '../xml/xml', 18 | '../javascript/javascript', 19 | '../css/css' 20 | ], mod) 21 | // Plain browser env 22 | else mod(CodeMirror) 23 | })(function(CodeMirror) { 24 | 'use strict' 25 | 26 | CodeMirror.defineMode( 27 | 'htmlmixed', 28 | function(config, parserConfig) { 29 | var htmlMode = CodeMirror.getMode(config, { 30 | name: 'xml', 31 | htmlMode: true, 32 | multilineTagIndentFactor: parserConfig.multilineTagIndentFactor, 33 | multilineTagIndentPastTag: parserConfig.multilineTagIndentPastTag 34 | }) 35 | var cssMode = CodeMirror.getMode(config, 'css') 36 | 37 | var scriptTypes = [], 38 | scriptTypesConf = parserConfig && parserConfig.scriptTypes 39 | scriptTypes.push({ 40 | matches: /^(?:text|application)\/(?:x-)?(?:java|ecma)script$|^$/i, 41 | mode: CodeMirror.getMode(config, 'javascript') 42 | }) 43 | if (scriptTypesConf) 44 | for (var i = 0; i < scriptTypesConf.length; ++i) { 45 | var conf = scriptTypesConf[i] 46 | scriptTypes.push({ 47 | matches: conf.matches, 48 | mode: conf.mode && CodeMirror.getMode(config, conf.mode) 49 | }) 50 | } 51 | scriptTypes.push({ 52 | matches: /./, 53 | mode: CodeMirror.getMode(config, 'text/plain') 54 | }) 55 | 56 | function html(stream, state) { 57 | var tagName = state.htmlState.tagName 58 | if (tagName) tagName = tagName.toLowerCase() 59 | var style = htmlMode.token(stream, state.htmlState) 60 | if ( 61 | tagName == 'script' && 62 | /\btag\b/.test(style) && 63 | stream.current() == '>' 64 | ) { 65 | // Script block: mode to change to depends on type attribute 66 | var scriptType = stream.string 67 | .slice(Math.max(0, stream.pos - 100), stream.pos) 68 | .match(/\btype\s*=\s*("[^"]+"|'[^']+'|\S+)[^<]*$/i) 69 | scriptType = scriptType ? scriptType[1] : '' 70 | if (scriptType && /[\"\']/.test(scriptType.charAt(0))) 71 | scriptType = scriptType.slice(1, scriptType.length - 1) 72 | for (var i = 0; i < scriptTypes.length; ++i) { 73 | var tp = scriptTypes[i] 74 | if ( 75 | typeof tp.matches == 'string' 76 | ? scriptType == tp.matches 77 | : tp.matches.test(scriptType) 78 | ) { 79 | if (tp.mode) { 80 | state.token = script 81 | state.localMode = tp.mode 82 | state.localState = 83 | tp.mode.startState && 84 | tp.mode.startState(htmlMode.indent(state.htmlState, '')) 85 | } 86 | break 87 | } 88 | } 89 | } else if ( 90 | tagName == 'style' && 91 | /\btag\b/.test(style) && 92 | stream.current() == '>' 93 | ) { 94 | state.token = css 95 | state.localMode = cssMode 96 | state.localState = cssMode.startState( 97 | htmlMode.indent(state.htmlState, '') 98 | ) 99 | } 100 | return style 101 | } 102 | function maybeBackup(stream, pat, style) { 103 | var cur = stream.current() 104 | var close = cur.search(pat), 105 | m 106 | if (close > -1) stream.backUp(cur.length - close) 107 | else if ((m = cur.match(/<\/?$/))) { 108 | stream.backUp(cur.length) 109 | if (!stream.match(pat, false)) stream.match(cur) 110 | } 111 | return style 112 | } 113 | function script(stream, state) { 114 | if (stream.match(/^<\/\s*script\s*>/i, false)) { 115 | state.token = html 116 | state.localState = state.localMode = null 117 | return null 118 | } 119 | return maybeBackup( 120 | stream, 121 | /<\/\s*script\s*>/, 122 | state.localMode.token(stream, state.localState) 123 | ) 124 | } 125 | function css(stream, state) { 126 | if (stream.match(/^<\/\s*style\s*>/i, false)) { 127 | state.token = html 128 | state.localState = state.localMode = null 129 | return null 130 | } 131 | return maybeBackup( 132 | stream, 133 | /<\/\s*style\s*>/, 134 | cssMode.token(stream, state.localState) 135 | ) 136 | } 137 | 138 | return { 139 | startState: function() { 140 | var state = htmlMode.startState() 141 | return { 142 | token: html, 143 | localMode: null, 144 | localState: null, 145 | htmlState: state 146 | } 147 | }, 148 | 149 | copyState: function(state) { 150 | if (state.localState) 151 | var local = CodeMirror.copyState(state.localMode, state.localState) 152 | return { 153 | token: state.token, 154 | localMode: state.localMode, 155 | localState: local, 156 | htmlState: CodeMirror.copyState(htmlMode, state.htmlState) 157 | } 158 | }, 159 | 160 | token: function(stream, state) { 161 | return state.token(stream, state) 162 | }, 163 | 164 | indent: function(state, textAfter) { 165 | if (!state.localMode || /^\s*<\//.test(textAfter)) 166 | return htmlMode.indent(state.htmlState, textAfter) 167 | else if (state.localMode.indent) 168 | return state.localMode.indent(state.localState, textAfter) 169 | else return CodeMirror.Pass 170 | }, 171 | 172 | innerMode: function(state) { 173 | return { 174 | state: state.localState || state.htmlState, 175 | mode: state.localMode || htmlMode 176 | } 177 | } 178 | } 179 | }, 180 | 'xml', 181 | 'javascript', 182 | 'css' 183 | ) 184 | 185 | CodeMirror.defineMIME('text/html', 'htmlmixed') 186 | }) 187 | -------------------------------------------------------------------------------- /lib/codemirror/lib/util/closetag.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Tag-closer extension for CodeMirror. 3 | * 4 | * This extension adds a "closeTag" utility function that can be used with key bindings to 5 | * insert a matching end tag after the ">" character of a start tag has been typed. It can 6 | * also complete " 17 | * Contributed under the same license terms as CodeMirror. 18 | */ 19 | ;(function() { 20 | /** Option that allows tag closing behavior to be toggled. Default is true. */ 21 | CodeMirror.defaults['closeTagEnabled'] = true 22 | 23 | /** Array of tag names to add indentation after the start tag for. Default is the list of block-level html tags. */ 24 | CodeMirror.defaults['closeTagIndent'] = [ 25 | 'applet', 26 | 'blockquote', 27 | 'body', 28 | 'button', 29 | 'div', 30 | 'dl', 31 | 'fieldset', 32 | 'form', 33 | 'frameset', 34 | 'h1', 35 | 'h2', 36 | 'h3', 37 | 'h4', 38 | 'h5', 39 | 'h6', 40 | 'head', 41 | 'html', 42 | 'iframe', 43 | 'layer', 44 | 'legend', 45 | 'object', 46 | 'ol', 47 | 'p', 48 | 'select', 49 | 'table', 50 | 'ul' 51 | ] 52 | 53 | /** Array of tag names where an end tag is forbidden. */ 54 | CodeMirror.defaults['closeTagVoid'] = [ 55 | 'area', 56 | 'base', 57 | 'br', 58 | 'col', 59 | 'command', 60 | 'embed', 61 | 'hr', 62 | 'img', 63 | 'input', 64 | 'keygen', 65 | 'link', 66 | 'meta', 67 | 'param', 68 | 'source', 69 | 'track', 70 | 'wbr' 71 | ] 72 | 73 | function innerState(cm, state) { 74 | return CodeMirror.innerMode(cm.getMode(), state).state 75 | } 76 | 77 | /** 78 | * Call during key processing to close tags. Handles the key event if the tag is closed, otherwise throws CodeMirror.Pass. 79 | * - cm: The editor instance. 80 | * - ch: The character being processed. 81 | * - indent: Optional. An array of tag names to indent when closing. Omit or pass true to use the default indentation tag list defined in the 'closeTagIndent' option. 82 | * Pass false to disable indentation. Pass an array to override the default list of tag names. 83 | * - vd: Optional. An array of tag names that should not be closed. Omit to use the default void (end tag forbidden) tag list defined in the 'closeTagVoid' option. Ignored in xml mode. 84 | */ 85 | CodeMirror.defineExtension('closeTag', function(cm, ch, indent, vd) { 86 | if (!cm.getOption('closeTagEnabled')) { 87 | throw CodeMirror.Pass 88 | } 89 | 90 | /* 91 | * Relevant structure of token: 92 | * 93 | * htmlmixed 94 | * className 95 | * state 96 | * htmlState 97 | * type 98 | * tagName 99 | * context 100 | * tagName 101 | * mode 102 | * 103 | * xml 104 | * className 105 | * state 106 | * tagName 107 | * type 108 | */ 109 | 110 | var pos = cm.getCursor() 111 | var tok = cm.getTokenAt(pos) 112 | var state = innerState(cm, tok.state) 113 | 114 | if (state) { 115 | if (ch == '>') { 116 | var type = state.type 117 | 118 | if (tok.className == 'tag' && type == 'closeTag') { 119 | throw CodeMirror.Pass // Don't process the '>' at the end of an end-tag. 120 | } 121 | 122 | cm.replaceSelection('>') // Mode state won't update until we finish the tag. 123 | pos = { line: pos.line, ch: pos.ch + 1 } 124 | cm.setCursor(pos) 125 | 126 | tok = cm.getTokenAt(cm.getCursor()) 127 | state = innerState(cm, tok.state) 128 | if (!state) throw CodeMirror.Pass 129 | var type = state.type 130 | 131 | if (tok.className == 'tag' && type != 'selfcloseTag') { 132 | var tagName = state.tagName 133 | if (tagName.length > 0 && shouldClose(cm, vd, tagName)) { 134 | insertEndTag(cm, indent, pos, tagName) 135 | } 136 | return 137 | } 138 | 139 | // Undo the '>' insert and allow cm to handle the key instead. 140 | cm.setSelection({ line: pos.line, ch: pos.ch - 1 }, pos) 141 | cm.replaceSelection('') 142 | } else if (ch == '/') { 143 | if (tok.className == 'tag' && tok.string == '<') { 144 | var ctx = state.context, 145 | tagName = ctx ? ctx.tagName : '' 146 | if (tagName.length > 0) { 147 | completeEndTag(cm, pos, tagName) 148 | return 149 | } 150 | } 151 | } 152 | } 153 | 154 | throw CodeMirror.Pass // Bubble if not handled 155 | }) 156 | 157 | function insertEndTag(cm, indent, pos, tagName) { 158 | if (shouldIndent(cm, indent, tagName)) { 159 | cm.replaceSelection('\n\n', 'end') 160 | cm.indentLine(pos.line + 1) 161 | cm.indentLine(pos.line + 2) 162 | cm.setCursor({ line: pos.line + 1, ch: cm.getLine(pos.line + 1).length }) 163 | } else { 164 | cm.replaceSelection('') 165 | cm.setCursor(pos) 166 | } 167 | } 168 | 169 | function shouldIndent(cm, indent, tagName) { 170 | if (typeof indent == 'undefined' || indent == null || indent == true) { 171 | indent = cm.getOption('closeTagIndent') 172 | } 173 | if (!indent) { 174 | indent = [] 175 | } 176 | return indexOf(indent, tagName.toLowerCase()) != -1 177 | } 178 | 179 | function shouldClose(cm, vd, tagName) { 180 | if (cm.getOption('mode') == 'xml') { 181 | return true // always close xml tags 182 | } 183 | if (typeof vd == 'undefined' || vd == null) { 184 | vd = cm.getOption('closeTagVoid') 185 | } 186 | if (!vd) { 187 | vd = [] 188 | } 189 | return indexOf(vd, tagName.toLowerCase()) == -1 190 | } 191 | 192 | // C&P from codemirror.js...would be nice if this were visible to utilities. 193 | function indexOf(collection, elt) { 194 | if (collection.indexOf) return collection.indexOf(elt) 195 | for (var i = 0, e = collection.length; i < e; ++i) 196 | if (collection[i] == elt) return i 197 | return -1 198 | } 199 | 200 | function completeEndTag(cm, pos, tagName) { 201 | cm.replaceSelection('/' + tagName + '>') 202 | cm.setCursor({ line: pos.line, ch: pos.ch + tagName.length + 2 }) 203 | } 204 | })() 205 | -------------------------------------------------------------------------------- /lib/codemirror/lib/util/foldcode.js: -------------------------------------------------------------------------------- 1 | // the tagRangeFinder function is 2 | // Copyright (C) 2011 by Daniel Glazman 3 | // released under the MIT license (../../LICENSE) like the rest of CodeMirror 4 | CodeMirror.tagRangeFinder = function(cm, line, hideEnd) { 5 | var nameStartChar = 6 | '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' 7 | var nameChar = nameStartChar + '-:.0-9\\u00B7\\u0300-\\u036F\\u203F-\\u2040' 8 | var xmlNAMERegExp = new RegExp('^[' + nameStartChar + '][' + nameChar + ']*') 9 | 10 | var lineText = cm.getLine(line) 11 | var found = false 12 | var tag = null 13 | var pos = 0 14 | while (!found) { 15 | pos = lineText.indexOf('<', pos) 16 | if (-1 == pos) 17 | // no tag on line 18 | return 19 | if (pos + 1 < lineText.length && lineText[pos + 1] == '/') { 20 | // closing tag 21 | pos++ 22 | continue 23 | } 24 | // ok we weem to have a start tag 25 | if (!lineText.substr(pos + 1).match(xmlNAMERegExp)) { 26 | // not a tag name... 27 | pos++ 28 | continue 29 | } 30 | var gtPos = lineText.indexOf('>', pos + 1) 31 | if (-1 == gtPos) { 32 | // end of start tag not in line 33 | var l = line + 1 34 | var foundGt = false 35 | var lastLine = cm.lineCount() 36 | while (l < lastLine && !foundGt) { 37 | var lt = cm.getLine(l) 38 | var gt = lt.indexOf('>') 39 | if (-1 != gt) { 40 | // found a > 41 | foundGt = true 42 | var slash = lt.lastIndexOf('/', gt) 43 | if (-1 != slash && slash < gt) { 44 | var str = lineText.substr(slash, gt - slash + 1) 45 | if (!str.match(/\/\s*\>/)) { 46 | // yep, that's the end of empty tag 47 | if (hideEnd === true) l++ 48 | return l 49 | } 50 | } 51 | } 52 | l++ 53 | } 54 | found = true 55 | } else { 56 | var slashPos = lineText.lastIndexOf('/', gtPos) 57 | if (-1 == slashPos) { 58 | // cannot be empty tag 59 | found = true 60 | // don't continue 61 | } else { 62 | // empty tag? 63 | // check if really empty tag 64 | var str = lineText.substr(slashPos, gtPos - slashPos + 1) 65 | if (!str.match(/\/\s*\>/)) { 66 | // finally not empty 67 | found = true 68 | // don't continue 69 | } 70 | } 71 | } 72 | if (found) { 73 | var subLine = lineText.substr(pos + 1) 74 | tag = subLine.match(xmlNAMERegExp) 75 | if (tag) { 76 | // we have an element name, wooohooo ! 77 | tag = tag[0] 78 | // do we have the close tag on same line ??? 79 | if (-1 != lineText.indexOf('', pos)) { 80 | // yep 81 | found = false 82 | } 83 | // we don't, so we have a candidate... 84 | } else found = false 85 | } 86 | if (!found) pos++ 87 | } 88 | 89 | if (found) { 90 | var startTag = 91 | '(\\<\\/' + 92 | tag + 93 | '\\>)|(\\<' + 94 | tag + 95 | '\\>)|(\\<' + 96 | tag + 97 | '\\s)|(\\<' + 98 | tag + 99 | '$)' 100 | var startTagRegExp = new RegExp(startTag, 'g') 101 | var endTag = '' 102 | var depth = 1 103 | var l = line + 1 104 | var lastLine = cm.lineCount() 105 | while (l < lastLine) { 106 | lineText = cm.getLine(l) 107 | var match = lineText.match(startTagRegExp) 108 | if (match) { 109 | for (var i = 0; i < match.length; i++) { 110 | if (match[i] == endTag) depth-- 111 | else depth++ 112 | if (!depth) { 113 | if (hideEnd === true) l++ 114 | return l 115 | } 116 | } 117 | } 118 | l++ 119 | } 120 | return 121 | } 122 | } 123 | 124 | CodeMirror.braceRangeFinder = function(cm, line, hideEnd) { 125 | var lineText = cm.getLine(line), 126 | at = lineText.length, 127 | startChar, 128 | tokenType 129 | for (;;) { 130 | var found = lineText.lastIndexOf('{', at) 131 | if (found < 0) break 132 | tokenType = cm.getTokenAt({ line: line, ch: found }).className 133 | if (!/^(comment|string)/.test(tokenType)) { 134 | startChar = found 135 | break 136 | } 137 | at = found - 1 138 | } 139 | if (startChar == null || lineText.lastIndexOf('}') > startChar) return 140 | var count = 1, 141 | lastLine = cm.lineCount(), 142 | end 143 | outer: for (var i = line + 1; i < lastLine; ++i) { 144 | var text = cm.getLine(i), 145 | pos = 0 146 | for (;;) { 147 | var nextOpen = text.indexOf('{', pos), 148 | nextClose = text.indexOf('}', pos) 149 | if (nextOpen < 0) nextOpen = text.length 150 | if (nextClose < 0) nextClose = text.length 151 | pos = Math.min(nextOpen, nextClose) 152 | if (pos == text.length) break 153 | if (cm.getTokenAt({ line: i, ch: pos + 1 }).className == tokenType) { 154 | if (pos == nextOpen) ++count 155 | else if (!--count) { 156 | end = i 157 | break outer 158 | } 159 | } 160 | ++pos 161 | } 162 | } 163 | if (end == null || end == line + 1) return 164 | if (hideEnd === true) end++ 165 | return end 166 | } 167 | 168 | CodeMirror.indentRangeFinder = function(cm, line) { 169 | var tabSize = cm.getOption('tabSize') 170 | var myIndent = cm.getLineHandle(line).indentation(tabSize), 171 | last 172 | for (var i = line + 1, end = cm.lineCount(); i < end; ++i) { 173 | var handle = cm.getLineHandle(i) 174 | if (!/^\s*$/.test(handle.text)) { 175 | if (handle.indentation(tabSize) <= myIndent) break 176 | last = i 177 | } 178 | } 179 | if (!last) return null 180 | return last + 1 181 | } 182 | 183 | CodeMirror.newFoldFunction = function(rangeFinder, markText, hideEnd) { 184 | var folded = [] 185 | if (markText == null) 186 | markText = 187 | '
%N%' 188 | 189 | function isFolded(cm, n) { 190 | for (var i = 0; i < folded.length; ++i) { 191 | var start = cm.lineInfo(folded[i].start) 192 | if (!start) folded.splice(i--, 1) 193 | else if (start.line == n) return { pos: i, region: folded[i] } 194 | } 195 | } 196 | 197 | function expand(cm, region) { 198 | cm.clearMarker(region.start) 199 | for (var i = 0; i < region.hidden.length; ++i) cm.showLine(region.hidden[i]) 200 | } 201 | 202 | return function(cm, line) { 203 | cm.operation(function() { 204 | var known = isFolded(cm, line) 205 | if (known) { 206 | folded.splice(known.pos, 1) 207 | expand(cm, known.region) 208 | } else { 209 | var end = rangeFinder(cm, line, hideEnd) 210 | if (end == null) return 211 | var hidden = [] 212 | for (var i = line + 1; i < end; ++i) { 213 | var handle = cm.hideLine(i) 214 | if (handle) hidden.push(handle) 215 | } 216 | var first = cm.setMarker(line, markText) 217 | var region = { start: first, hidden: hidden } 218 | cm.onDeleteLine(first, function() { 219 | expand(cm, region) 220 | }) 221 | folded.push(region) 222 | } 223 | }) 224 | } 225 | } 226 | -------------------------------------------------------------------------------- /lib/codemirror/lib/util/formatting.js: -------------------------------------------------------------------------------- 1 | // ============== Formatting extensions ============================ 2 | ;(function() { 3 | // Define extensions for a few modes 4 | CodeMirror.extendMode('css', { 5 | commentStart: '/*', 6 | commentEnd: '*/', 7 | wordWrapChars: [';', '\\{', '\\}'], 8 | autoFormatLineBreaks: function(text) { 9 | return text.replace(new RegExp('(;|\\{|\\})([^\r\n])', 'g'), '$1\n$2') 10 | } 11 | }) 12 | 13 | function jsNonBreakableBlocks(text) { 14 | var nonBreakableRegexes = [ 15 | /for\s*?\((.*?)\)/, 16 | /\"(.*?)(\"|$)/, 17 | /\'(.*?)(\'|$)/, 18 | /\/\*(.*?)(\*\/|$)/, 19 | /\/\/.*/ 20 | ] 21 | var nonBreakableBlocks = [] 22 | for (var i = 0; i < nonBreakableRegexes.length; i++) { 23 | var curPos = 0 24 | while (curPos < text.length) { 25 | var m = text.substr(curPos).match(nonBreakableRegexes[i]) 26 | if (m != null) { 27 | nonBreakableBlocks.push({ 28 | start: curPos + m.index, 29 | end: curPos + m.index + m[0].length 30 | }) 31 | curPos += m.index + Math.max(1, m[0].length) 32 | } else { 33 | // No more matches 34 | break 35 | } 36 | } 37 | } 38 | nonBreakableBlocks.sort(function(a, b) { 39 | return a.start - b.start 40 | }) 41 | 42 | return nonBreakableBlocks 43 | } 44 | 45 | CodeMirror.extendMode('javascript', { 46 | commentStart: '/*', 47 | commentEnd: '*/', 48 | wordWrapChars: [';', '\\{', '\\}'], 49 | 50 | autoFormatLineBreaks: function(text) { 51 | var curPos = 0 52 | var reLinesSplitter = /(;|\{|\})([^\r\n;])/g 53 | var nonBreakableBlocks = jsNonBreakableBlocks(text) 54 | if (nonBreakableBlocks != null) { 55 | var res = '' 56 | for (var i = 0; i < nonBreakableBlocks.length; i++) { 57 | if (nonBreakableBlocks[i].start > curPos) { 58 | // Break lines till the block 59 | res += text 60 | .substring(curPos, nonBreakableBlocks[i].start) 61 | .replace(reLinesSplitter, '$1\n$2') 62 | curPos = nonBreakableBlocks[i].start 63 | } 64 | if ( 65 | nonBreakableBlocks[i].start <= curPos && 66 | nonBreakableBlocks[i].end >= curPos 67 | ) { 68 | // Skip non-breakable block 69 | res += text.substring(curPos, nonBreakableBlocks[i].end) 70 | curPos = nonBreakableBlocks[i].end 71 | } 72 | } 73 | if (curPos < text.length) 74 | res += text.substr(curPos).replace(reLinesSplitter, '$1\n$2') 75 | return res 76 | } else { 77 | return text.replace(reLinesSplitter, '$1\n$2') 78 | } 79 | } 80 | }) 81 | 82 | CodeMirror.extendMode('xml', { 83 | commentStart: '', 85 | wordWrapChars: ['>'], 86 | 87 | autoFormatLineBreaks: function(text) { 88 | var lines = text.split('\n') 89 | var reProcessedPortion = new RegExp( 90 | '(^\\s*?<|^[^<]*?)(.+)(>\\s*?$|[^>]*?$)' 91 | ) 92 | var reOpenBrackets = new RegExp('<', 'g') 93 | var reCloseBrackets = new RegExp('(>)([^\r\n])', 'g') 94 | for (var i = 0; i < lines.length; i++) { 95 | var mToProcess = lines[i].match(reProcessedPortion) 96 | if (mToProcess != null && mToProcess.length > 3) { 97 | // The line starts with whitespaces and ends with whitespaces 98 | lines[i] = 99 | mToProcess[1] + 100 | mToProcess[2] 101 | .replace(reOpenBrackets, '\n$&') 102 | .replace(reCloseBrackets, '$1\n$2') + 103 | mToProcess[3] 104 | continue 105 | } 106 | } 107 | return lines.join('\n') 108 | } 109 | }) 110 | 111 | function localModeAt(cm, pos) { 112 | return CodeMirror.innerMode(cm.getMode(), cm.getTokenAt(pos).state).mode 113 | } 114 | 115 | function enumerateModesBetween(cm, line, start, end) { 116 | var outer = cm.getMode(), 117 | text = cm.getLine(line) 118 | if (end == null) end = text.length 119 | if (!outer.innerMode) return [{ from: start, to: end, mode: outer }] 120 | var state = cm.getTokenAt({ line: line, ch: start }).state 121 | var mode = CodeMirror.innerMode(outer, state).mode 122 | var found = [], 123 | stream = new CodeMirror.StringStream(text) 124 | stream.pos = stream.start = start 125 | for (;;) { 126 | outer.token(stream, state) 127 | var curMode = CodeMirror.innerMode(outer, state).mode 128 | if (curMode != mode) { 129 | var cut = stream.start 130 | // Crappy heuristic to deal with the fact that a change in 131 | // mode can occur both at the end and the start of a token, 132 | // and we don't know which it was. 133 | if (mode.name == 'xml' && text.charAt(stream.pos - 1) == '>') 134 | cut = stream.pos 135 | found.push({ from: start, to: cut, mode: mode }) 136 | start = cut 137 | mode = curMode 138 | } 139 | if (stream.pos >= end) break 140 | stream.start = stream.pos 141 | } 142 | if (start < end) found.push({ from: start, to: end, mode: mode }) 143 | return found 144 | } 145 | 146 | // Comment/uncomment the specified range 147 | CodeMirror.defineExtension('commentRange', function(isComment, from, to) { 148 | var curMode = localModeAt(this, from), 149 | cm = this 150 | this.operation(function() { 151 | if (isComment) { 152 | // Comment range 153 | cm.replaceRange(curMode.commentEnd, to) 154 | cm.replaceRange(curMode.commentStart, from) 155 | if (from.line == to.line && from.ch == to.ch) 156 | // An empty comment inserted - put cursor inside 157 | cm.setCursor(from.line, from.ch + curMode.commentStart.length) 158 | } else { 159 | // Uncomment range 160 | var selText = cm.getRange(from, to) 161 | var startIndex = selText.indexOf(curMode.commentStart) 162 | var endIndex = selText.lastIndexOf(curMode.commentEnd) 163 | if (startIndex > -1 && endIndex > -1 && endIndex > startIndex) { 164 | // Take string till comment start 165 | selText = 166 | selText.substr(0, startIndex) + 167 | // From comment start till comment end 168 | selText.substring( 169 | startIndex + curMode.commentStart.length, 170 | endIndex 171 | ) + 172 | // From comment end till string end 173 | selText.substr(endIndex + curMode.commentEnd.length) 174 | } 175 | cm.replaceRange(selText, from, to) 176 | } 177 | }) 178 | }) 179 | 180 | // Applies automatic mode-aware indentation to the specified range 181 | CodeMirror.defineExtension('autoIndentRange', function(from, to) { 182 | var cmInstance = this 183 | this.operation(function() { 184 | for (var i = from.line; i <= to.line; i++) { 185 | cmInstance.indentLine(i, 'smart') 186 | } 187 | }) 188 | }) 189 | 190 | // Applies automatic formatting to the specified range 191 | CodeMirror.defineExtension('autoFormatRange', function(from, to) { 192 | var cm = this 193 | cm.operation(function() { 194 | for (var cur = from.line, end = to.line; cur <= end; ++cur) { 195 | var f = { line: cur, ch: cur == from.line ? from.ch : 0 } 196 | var t = { line: cur, ch: cur == end ? to.ch : null } 197 | var modes = enumerateModesBetween(cm, cur, f.ch, t.ch), 198 | mangled = '' 199 | var text = cm.getRange(f, t) 200 | for (var i = 0; i < modes.length; ++i) { 201 | var part = 202 | modes.length > 1 ? text.slice(modes[i].from, modes[i].to) : text 203 | if (mangled) mangled += '\n' 204 | if (modes[i].mode.autoFormatLineBreaks) { 205 | mangled += modes[i].mode.autoFormatLineBreaks(part) 206 | } else mangled += text 207 | } 208 | if (mangled != text) { 209 | for ( 210 | var count = 0, pos = mangled.indexOf('\n'); 211 | pos != -1; 212 | pos = mangled.indexOf('\n', pos + 1), ++count 213 | ) {} 214 | cm.replaceRange(mangled, f, t) 215 | cur += count 216 | end += count 217 | } 218 | } 219 | for (var cur = from.line + 1; cur <= end; ++cur) 220 | cm.indentLine(cur, 'smart') 221 | cm.setSelection(from, cm.getCursor(false)) 222 | }) 223 | }) 224 | })() 225 | -------------------------------------------------------------------------------- /scripts/editor.js: -------------------------------------------------------------------------------- 1 | /** 2 | * fix `width, height` properties on 3 | * transform them to inline css values 4 | */ 5 | const fixImageDimension = img => { 6 | let width = parseInt(img.getAttribute('width')) 7 | let height = parseInt(img.getAttribute('height')) 8 | 9 | if (!isNaN(width)) { 10 | img.style.width = width + 'px' 11 | img.removeAttribute('width') 12 | } 13 | if (!isNaN(height)) { 14 | img.style.height = height + 'px' 15 | img.removeAttribute('height') 16 | } 17 | } 18 | 19 | /** 20 | * weixin preprocess 21 | */ 22 | const wxProof = content => { 23 | let offlineDIV = divWrap(content) 24 | let query = (s, cb) => getAll(s, offlineDIV).forEach(el => cb && cb(el)) 25 | 26 | // remove `meta` 27 | query('meta', a => a.remove()) 28 | query('link', a => a.remove()) 29 | query('script', a => a.remove()) 30 | 31 | // 处理代码换行 32 | query('pre', pre => { 33 | // pre>code.lang-x 34 | let codeElem = pre.firstElementChild 35 | let lines = codeElem.innerHTML.trim().split('\n') 36 | 37 | let match = codeElem.className.match(/(?:^|\s)lang-([^\s]+)/) 38 | let lang = (match && match[1]) || '' 39 | 40 | pre.innerHTML = lines 41 | .map(line => { 42 | line = line.replace(/(^\s+)/g, m => ' '.repeat(m.length)) 43 | return !!line.trim() 44 | ? `

${line}

` 45 | : `


` 46 | }) 47 | .join('') 48 | 49 | pre.classList.add(`language-${lang}`) 50 | }) 51 | 52 | // 处理行间 code 样式 53 | query('code', code => { 54 | let span = create('span') 55 | span.innerHTML = code.innerHTML 56 | span.className = 'code' 57 | code.parentNode.replaceChild(span, code) 58 | }) 59 | 60 | // 处理行间 li 样式 61 | query('li', li => { 62 | li.innerHTML = '

' + li.innerHTML + '

' 63 | }) 64 | 65 | // blockquote 添加类名 66 | query('blockquote', blockquote => { 67 | blockquote.className = 'blockquote' 68 | 69 | let lines = blockquote.firstElementChild.innerHTML.trim().split('\n') 70 | 71 | lines = lines.map(line => { 72 | line = line.replace(/(^\s+)/g, m => ' '.repeat(m.length)) 73 | return !!line.trim() ? `

${line}

` : `


` 74 | }) 75 | blockquote.innerHTML = lines.join('') 76 | }) 77 | 78 | // 所有 pre 外面包裹一层 blockquote 79 | // 使用 figure 等标签都不行 80 | // 会导致微信编辑器中粘贴时会多出一个 p 标签 81 | query('pre', pre => { 82 | let clone = pre.cloneNode() 83 | clone.innerHTML = pre.innerHTML 84 | 85 | let wrap = create('blockquote') 86 | wrap.className = 'code-wrap' 87 | wrap.appendChild(clone) 88 | 89 | pre.parentNode.replaceChild(wrap, pre) 90 | }) 91 | 92 | // 处理所有 a 链接 93 | // query('a', a => { 94 | // let span = create('span'); 95 | // span.innerHTML = a.innerHTML; 96 | // span.className = 'link'; 97 | // a.parentNode.replaceChild(span, a); 98 | // }); 99 | 100 | // img 处理 101 | // 只针对单个成一段的 img 102 | query('img', img => { 103 | // case:

104 | var isFirstElemChild = img.parentNode.firstElementChild === img 105 | if (isFirstElemChild) { 106 | img.parentNode.className += 'img-wrap' 107 | } 108 | fixImageDimension(img) 109 | }) 110 | // h2 处理 111 | // h2标签内嵌span 112 | // query('h2', img => { 113 | // // case:

114 | // var isFirstElemChild = img.parentNode.firstElementChild === img; 115 | // if (isFirstElemChild) { 116 | // img.parentNode.className += 'img-wrap'; 117 | // } 118 | // fixImageDimension(img); 119 | // }); 120 | 121 | // list 122 | // let listNum = create('img'); 123 | // listNum.src = 'https://p1.ssl.qhimg.com/t01fb3e28751b757288.png'; 124 | // query('li', li => { 125 | // li.insertAdjacentElement('afterbegin', listNum.cloneNode()); 126 | // }); 127 | 128 | return offlineDIV.innerHTML 129 | } 130 | 131 | /** 132 | * render markdown content 133 | */ 134 | const renderPreview = function(targetDOM, markdown) { 135 | store.set('__lastContent', markdown) 136 | 137 | var weeklyStart = ` 138 |

编者按:这里是前言,可以随便写~~~~

139 | ` 140 | var weeklyEnd = ` 141 |

关于奇舞周刊

142 |

《奇舞周刊》是360公司专业前端团队「奇舞团」运营的前端技术社区。关注公众号后,直接发送链接到后台即可给我们投稿。

143 |

奇舞团是360集团最大的大前端团队,代表集团参与W3C和Ecma会员(TC39)工作。奇舞团非常重视人才培养,有工程师、讲师、翻译官、业务接口人、团队Leader等多种发展方向供员工选择,并辅以提供相应的技术力、专业力、通用力、领导力等培训课程。奇舞团以开放和求贤的心态欢迎各种优秀人才关注和加入奇舞团。

144 | 145 | ` 146 | 147 | //添加文章前边部门和文章末尾部门 148 | targetDOM.innerHTML = wxProof( 149 | weeklyStart + window.marked(markdown) + weeklyEnd 150 | ) 151 | } 152 | 153 | /** 154 | * transform clipboard data (text/html) 155 | * to markdown text 156 | */ 157 | const generateMdText = content => { 158 | return toMarkdown(content, { 159 | gfm: false, 160 | converters: [ 161 | { 162 | filter: 'code', 163 | replacement: function(t, n) { 164 | return /\n/.test(t) ? t : '`' + t + '`' 165 | } 166 | }, 167 | { 168 | filter: 'pre', 169 | replacement: function(t, n) { 170 | let lang = '' 171 | let result = t 172 | 173 | let firstChild = n.children[0] 174 | if (firstChild) { 175 | let match = firstChild.className.match( 176 | /(^|\s)(lang|language)-([^\s]+)/ 177 | ) 178 | lang = (match && match[3]) || '' 179 | } 180 | // TODO: deal with language... 181 | // this is a really annoying thing 182 | switch (lang) { 183 | case 'js': 184 | case 'javascript': 185 | result = js_beautify(t) 186 | break 187 | case 'css': 188 | result = css_beautify(t) 189 | break 190 | case 'html': 191 | result = html_beautify(t) 192 | break 193 | } 194 | 195 | return '\n```' + lang + '\n' + result + '\n```\n' 196 | } 197 | }, 198 | { 199 | filter: 'span', 200 | replacement: function(t, n) { 201 | return t 202 | } 203 | }, 204 | { 205 | filter: ['section', 'div'], 206 | replacement: function(t, n) { 207 | return '\n\n' + t + '\n\n' 208 | } 209 | } 210 | ] 211 | }) 212 | } 213 | 214 | // configure window.marked 215 | window.marked.setOptions({ 216 | highlight(code, lang, callback) { 217 | lang = lang === 'js' ? 'javascript' : lang || 'javascript' 218 | let langConfig = Prism.languages[lang] || Prism.languages.javascript 219 | return Prism.highlight(code, langConfig) 220 | } 221 | }) 222 | 223 | window.MdEditor = CodeMirror.fromTextArea(getDOM('#jsMdEditor'), { 224 | mode: 'gfm', 225 | lineNumbers: false, 226 | matchBrackets: true, 227 | lineWrapping: true, 228 | theme: 'base16-light', 229 | extraKeys: { 230 | Enter: 'newlineAndIndentContinueMarkdownList' 231 | } 232 | }) 233 | 234 | // getter/setter of editor's text content 235 | MdEditor.val = function(val) { 236 | if (!val) { 237 | return this.getValue() 238 | } else { 239 | return this.setValue(val) 240 | } 241 | } 242 | 243 | // replace/insert content 244 | MdEditor.paste = function(data) { 245 | let cursor = this.getCursor() 246 | // replace 247 | if (this.somethingSelected()) { 248 | this.replaceSelection(data) 249 | } 250 | // insert 251 | else { 252 | this.replaceRange(data, cursor) 253 | } 254 | } 255 | 256 | // view sync 257 | MdEditor.on('change', editor => { 258 | renderPreview(getDOM('.md-preview'), editor.val()) 259 | }) 260 | 261 | // paste on editor 262 | getDOM('.md-editor').addEventListener('paste', function(e) { 263 | e.preventDefault() 264 | 265 | let data = e.clipboardData.getData('text/html') 266 | let markdown = '' 267 | 268 | if (!data) { 269 | markdown = e.clipboardData.getData('text/plain') 270 | } else { 271 | let divDOM = divWrap(data) 272 | let query = (s, cb) => getAll(s, divDOM).forEach(el => cb && cb(el)) 273 | 274 | query('*', el => { 275 | el.removeAttribute('style') 276 | el.removeAttribute('class') 277 | }) 278 | query('meta', a => a.remove()) 279 | query('link', a => a.remove()) 280 | query('script', a => a.remove()) 281 | 282 | markdown = generateMdText(divDOM.innerHTML) 283 | } 284 | 285 | MdEditor.paste(markdown) 286 | }) 287 | -------------------------------------------------------------------------------- /content_script/blog.inject.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 1. upload images on notification `upload` 3 | * 2. inject HTML on notification `inject` 4 | */ 5 | 6 | /** 7 | * basic utils 8 | */ 9 | const qs = sel => document.querySelector(sel) 10 | const qsa = sel => Array.from(document.querySelectorAll(sel)) 11 | const isVisible = el => { 12 | let rect = el.getBoundingClientRect() 13 | return rect.width > 0 && rect.height > 0 14 | } 15 | 16 | /** 17 | * weixin editor element BODY 18 | */ 19 | const getWxEditor = () => { 20 | let visibleFrames = qsa('iframe[id^=ueditor_]').filter(isVisible) 21 | if (!visibleFrames.length) { 22 | return null 23 | } 24 | return visibleFrames[0].contentDocument.body 25 | } 26 | 27 | /** 28 | * random filename 29 | * 30 | * @retrurn {String} 31 | */ 32 | const guid = function() { 33 | return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx' 34 | .replace(/[xy]/g, function(c) { 35 | var r = (Math.random() * 16) | 0, 36 | v = c == 'x' ? r : (r & 0x3) | 0x8 37 | return v.toString(16) 38 | }) 39 | .toUpperCase() 40 | } 41 | 42 | /** 43 | * random date in the last 50 days 44 | * 45 | * @return {Date} 46 | */ 47 | const getRandomDate = function() { 48 | return new Date(Date.now() - Math.random() * 1000 * 3600 * 24 * 50) 49 | } 50 | 51 | /** 52 | * dataURI to Blob 53 | * 54 | * @param {Base64 String} dataURI 55 | * @return {Blob} 56 | */ 57 | const dataURItoBlob = function(dataURI) { 58 | var byteString, mimestring 59 | 60 | if (dataURI.split(',')[0].indexOf('base64') !== -1) { 61 | byteString = atob(dataURI.split(',')[1]) 62 | } else { 63 | byteString = decodeURI(dataURI.split(',')[1]) 64 | } 65 | 66 | mimestring = dataURI 67 | .split(',')[0] 68 | .split(':')[1] 69 | .split(';')[0] 70 | 71 | var content = new Array() 72 | for (var i = 0; i < byteString.length; i++) { 73 | content[i] = byteString.charCodeAt(i) 74 | } 75 | 76 | var blobImg, 77 | arrays = new Uint8Array(content) 78 | try { 79 | blobImg = new Blob([arrays], { 80 | type: mimestring 81 | }) 82 | } catch (e) {} 83 | return blobImg 84 | } 85 | 86 | /** 87 | * get related params violently 88 | * so that we can upload images here, 89 | * all we need are held by `window.wx`, 90 | * but we don't have access to global vars here, 91 | * yet we can reach DOMs. 92 | * and fortunately `window.wx` was assigned in a 93 | * inline script, aha, comes the trick! 94 | */ 95 | const uploadImage = (function() { 96 | // 暴力破解 97 | var scripts = Array.from( 98 | document.querySelectorAll('script[type="text/javascript"]') 99 | ).filter(s => s.src === '') 100 | var len = scripts.length 101 | var content = '' 102 | 103 | while (len--) 104 | if ((content = scripts[len].textContent)) { 105 | if (/try\s*\{\s*[\s\S]*window\.wx\s*\=\s*\{\s*\n/m.test(content)) { 106 | try { 107 | eval(content) 108 | } catch (e) {} 109 | break 110 | } 111 | } 112 | 113 | if (!content || !wx) { 114 | console.warn('window.wx.data parsing failed!') 115 | return function() { 116 | Promise.resolve('{}') 117 | } 118 | } 119 | 120 | // groupid=3 121 | // put all images to the category `文章配图`, 122 | // in this way, we'll get less bothered. 123 | // by following API, we can get all available categories: 124 | // https://mp.weixin.qq.com/cgi-bin/filepage?1=1&token=129636898&lang=zh_CN&token=129636898&lang=zh_CN&f=json&ajax=1&random=0.8964656583498374&group_id=0&begin=0&count=10&type=2 125 | // in this way, customization is no fairy. 126 | var url = `https://mp.weixin.qq.com/cgi-bin/filetransfer?action=upload_material&f=json&scene=1&writetype=doublewrite&groupid=3&ticket_id=${wx.data.user_name}&ticket=${wx.data.ticket}&svr_time=${wx.data.time}&token=${wx.data.t}&lang=zh_CN&seq=1` 127 | 128 | return function(base64) { 129 | var blob = dataURItoBlob(base64) 130 | var fileType = blob.type 131 | var typeName = fileType.replace('image/', '') 132 | var fileExt = typeName === 'svg+xml' ? 'svg' : typeName 133 | var fileName = `${guid()}.${fileExt}` 134 | 135 | // why not Blob directly? 136 | // since the prop `filename` will not change, 137 | // uploading work fails. 138 | // show time for File API: 139 | // see https://www.w3.org/TR/FileAPI/ 140 | var file = new File([blob], fileName, { 141 | type: fileType, 142 | lastModified: getRandomDate() 143 | }) 144 | var fileSize = file.size 145 | 146 | return new Promise(function(resolve, reject) { 147 | // validate first 148 | // 1. invalid type 149 | if (!/jpeg|png|gif/i.test(fileExt)) { 150 | resolve( 151 | JSON.stringify(ERROR_IMAGES[fileExt] || ERROR_IMAGES['typeError']) 152 | ) 153 | return 154 | } 155 | // 2. size of image too large 156 | if (fileSize >= 2 * 1024 * 1024) { 157 | resolve( 158 | JSON.stringify(ERROR_IMAGES[fileExt === 'gif' ? 'gif' : 'sizeError']) 159 | ) 160 | return 161 | } 162 | 163 | var form = new FormData() 164 | form.append('file', file) 165 | form.append('name', fileName) 166 | form.append('type', fileType) 167 | form.append('size', fileSize) 168 | form.append('id', 'WU_FILE_0') 169 | form.append('lastModifiedDate', file.lastModifiedDate) 170 | 171 | var xhr = new XMLHttpRequest() 172 | xhr.open('POST', url) 173 | xhr.send(form) 174 | xhr.onerror = xhr.ontimeout = reject 175 | xhr.onreadystatechange = () => { 176 | if (/^2\d{2}$/.test(xhr.status) && xhr.readyState === 4) { 177 | resolve(xhr.response) 178 | } 179 | } 180 | }) 181 | } 182 | })() 183 | 184 | /** 185 | * error warning images 186 | */ 187 | const ERROR_IMAGES = { 188 | gif: { 189 | cdn_url: 190 | 'https://mmbiz.qlogo.cn/mmbiz_png/wic3OZ3sEjfwAGmvH7C0ROMb9aAfjvickkJI3TurmjVUd20tGB8fd1kgddYI45OkvcrZlvnu4vAjkS0ibSiafHco5g/0?wx_fmt=png', 191 | cdn_id: 515897144, 192 | is_placeholder: true 193 | }, 194 | svg: { 195 | cdn_url: 196 | 'https://mmbiz.qlogo.cn/mmbiz_png/wic3OZ3sEjfwAGmvH7C0ROMb9aAfjvickkfIVWBgj34x3hIV71YeDr9S8qCHPZquiaIm5db4jcI4s379QcSyCfAsA/0?wx_fmt=png', 197 | cdn_id: 515897145, 198 | is_placeholder: true 199 | }, 200 | webp: { 201 | cdn_url: 202 | 'https://mmbiz.qlogo.cn/mmbiz_png/wic3OZ3sEjfyrMXEzibBfqZsMhpl1aoibcK5SJ6Aib3exfj1v0UgNrXUvpibU0dwyESN3uHda5PGRwIRnxL8tA8FqSQ/0?wx_fmt=png', 203 | cdn_id: 515897167, 204 | is_placeholder: true 205 | }, 206 | sizeError: { 207 | cdn_url: 208 | 'https://mmbiz.qlogo.cn/mmbiz_png/wic3OZ3sEjfyrMXEzibBfqZsMhpl1aoibcKj4Cvx37S18WjHrvS8TP4eybIhkdr07B3jzsQbeWUia9hIulnUeNDXSQ/0?wx_fmt=png', 209 | cdn_id: 515897166, 210 | is_placeholder: true 211 | }, 212 | typeError: { 213 | cdn_url: 214 | 'https://mmbiz.qlogo.cn/mmbiz_png/wic3OZ3sEjfyrMXEzibBfqZsMhpl1aoibcKE20R9gsYOuiaz5AZ1ezUbT7iaIuliawQowL0Dibj3ElvJr70K6Q1ChyicdA/0?wx_fmt=png', 215 | cdn_id: 515897169, 216 | is_placeholder: true 217 | } 218 | } 219 | 220 | const onMessage = { 221 | noop() {}, 222 | 223 | inject(message) { 224 | // hide editor "placeholder" 225 | qs('.editor_content_placeholder ').style.display = 'none' 226 | 227 | let editor = getWxEditor() 228 | editor.innerHTML = message.data 229 | 230 | // author 231 | if (message.author) { 232 | //qs('#author').value = message.author.slice(0, 8); 233 | } 234 | 235 | // url 236 | if (message.url) { 237 | if (!qs('.js_url_checkbox').checked) { 238 | qs('.js_url_checkbox').click() 239 | } 240 | 241 | qs('.js_url').value = message.url 242 | } 243 | 244 | // TODO: we can do more... 245 | }, 246 | 247 | upload(message) { 248 | uploadImage(message.data) 249 | .then(res => JSON.parse(res)) 250 | .then(data => { 251 | let msg = { 252 | hash: message.hash, 253 | type: message.type 254 | } 255 | 256 | if (data && data['cdn_url']) { 257 | msg.data = { 258 | is_placeholder: data['is_placeholder'], 259 | cdn_url: data['cdn_url'], 260 | cdn_id: data['content'] 261 | } 262 | } else { 263 | msg.err = JSON.stringify(data) 264 | } 265 | 266 | return msg 267 | }) 268 | .catch(e => ({ err: e })) 269 | .then(msg => chrome.runtime.sendMessage(null, msg)) 270 | } 271 | } 272 | 273 | chrome.runtime.onMessage.addListener(function(message) { 274 | console.info(message) 275 | ;(onMessage[message.type] || onMessage['noop'])(message) 276 | }) 277 | -------------------------------------------------------------------------------- /scripts/weeklyInjector.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 奇舞周刊微信工具 3 | */ 4 | const weeklyInjector = { 5 | element: getDOM('.pop-form-mask'), 6 | cache: {}, 7 | abstract: [ 8 | '又到周刊时间啦,快快看本期周刊君为你准备了什么吧~', 9 | '新一期周刊出炉啦~周末转眼又来到了,周末愉快~', 10 | '周刊君如约而至~周末愉快~', 11 | '一周飞逝~周刊君如约而至~周末愉快~', 12 | '快,奇舞周刊喊你来充电啦~' 13 | ], 14 | leading: [ 15 | '约定好的周五又来啦,今天的天空特别蓝,周刊君的心情棒棒哒~ 周末嗨起来~', 16 | '时间过得好快,转眼又到周五~ 新一期周刊出炉啦,虽然周刊君的 bug 还没改完 TAT,啊快赐予周刊君力量!~', 17 | '时间过得好快,又到周五啦~ 周刊君赴约来啦~', 18 | '来来来~ 转眼间又到周五啦~ 周末到啦,不要放弃学习哟~ 加油吧童鞋~', 19 | '嘿又到周五啦~ 又一个周末即将来临,奇舞周刊如约而至。好开森~' 20 | ], 21 | // get random text 22 | getText: function(key) { 23 | let data = (Array.isArray(this[key]) && this[key]) || [] 24 | return data[~~(data.length * Math.random())] 25 | }, 26 | 27 | // newest index 28 | fetchNewestIndex: function() { 29 | return new Promise(function(resolve, reject) { 30 | let xhr = new XMLHttpRequest() 31 | xhr.open('get', 'https://weekly.75team.com/rss.php') 32 | xhr.send(null) 33 | 34 | xhr.onreadystatechange = function() { 35 | if (!/^2\d{2}$/.test(xhr.status) || xhr.readyState < 4) return 36 | let response = xhr.responseText 37 | let reNewest = /https?\:\/\/www\.75team.com\/weekly\/issue(\d{1,5})\.html<\/link>/ 38 | let match = response.match(reNewest) 39 | 40 | if (match && match[1]) { 41 | resolve(match[1]) 42 | } else { 43 | reject() 44 | } 45 | } 46 | 47 | xhr.onerror = xhr.ontimeout = function() { 48 | reject() 49 | } 50 | }) 51 | }, 52 | 53 | /** 54 | * 抓取周刊数据 55 | * @param {Number} index 期数 56 | * @return {Promise} 57 | */ 58 | fetchWeekly: function(index) { 59 | let self = this 60 | let cache = this.cache 61 | let url = `https://weekly.75team.com/issue${index}.html` 62 | let err = { 63 | error: `第${index}期周刊不存在!` 64 | } 65 | 66 | this.origin = url 67 | getDOM('.net-err').style.display = 'none' 68 | if (cache[index]) { 69 | return Promise.resolve(cache[index]) 70 | } 71 | 72 | return fetch(url) 73 | .then(r => r.text()) 74 | .then(function(text) { 75 | let data = text.match( 76 | /
<\/section>\s*