├── .babelrc ├── .editorconfig ├── .eslintrc ├── .gitattributes ├── .github └── workflows │ └── build.yml ├── .gitignore ├── LICENSE ├── README.md ├── app ├── eme.sh ├── eme │ ├── config.js │ ├── context-menu.js │ ├── event.js │ ├── menu.js │ ├── shell.js │ ├── utils.js │ ├── utils │ │ ├── electron-config.js │ │ └── theme.js │ └── window.js ├── index.html ├── index.js ├── math-typesetting.md ├── package.json ├── resources │ └── icon.png ├── vendor │ ├── css │ │ ├── print.css │ │ └── spinners.css │ ├── github-markdown-css │ │ └── github-markdown.css │ ├── katex │ │ ├── fonts │ │ │ ├── KaTeX_AMS-Regular.eot │ │ │ ├── KaTeX_AMS-Regular.ttf │ │ │ ├── KaTeX_AMS-Regular.woff │ │ │ ├── KaTeX_AMS-Regular.woff2 │ │ │ ├── KaTeX_Caligraphic-Bold.eot │ │ │ ├── KaTeX_Caligraphic-Bold.ttf │ │ │ ├── KaTeX_Caligraphic-Bold.woff │ │ │ ├── KaTeX_Caligraphic-Bold.woff2 │ │ │ ├── KaTeX_Caligraphic-Regular.eot │ │ │ ├── KaTeX_Caligraphic-Regular.ttf │ │ │ ├── KaTeX_Caligraphic-Regular.woff │ │ │ ├── KaTeX_Caligraphic-Regular.woff2 │ │ │ ├── KaTeX_Fraktur-Bold.eot │ │ │ ├── KaTeX_Fraktur-Bold.ttf │ │ │ ├── KaTeX_Fraktur-Bold.woff │ │ │ ├── KaTeX_Fraktur-Bold.woff2 │ │ │ ├── KaTeX_Fraktur-Regular.eot │ │ │ ├── KaTeX_Fraktur-Regular.ttf │ │ │ ├── KaTeX_Fraktur-Regular.woff │ │ │ ├── KaTeX_Fraktur-Regular.woff2 │ │ │ ├── KaTeX_Main-Bold.eot │ │ │ ├── KaTeX_Main-Bold.ttf │ │ │ ├── KaTeX_Main-Bold.woff │ │ │ ├── KaTeX_Main-Bold.woff2 │ │ │ ├── KaTeX_Main-Italic.eot │ │ │ ├── KaTeX_Main-Italic.ttf │ │ │ ├── KaTeX_Main-Italic.woff │ │ │ ├── KaTeX_Main-Italic.woff2 │ │ │ ├── KaTeX_Main-Regular.eot │ │ │ ├── KaTeX_Main-Regular.ttf │ │ │ ├── KaTeX_Main-Regular.woff │ │ │ ├── KaTeX_Main-Regular.woff2 │ │ │ ├── KaTeX_Math-BoldItalic.eot │ │ │ ├── KaTeX_Math-BoldItalic.ttf │ │ │ ├── KaTeX_Math-BoldItalic.woff │ │ │ ├── KaTeX_Math-BoldItalic.woff2 │ │ │ ├── KaTeX_Math-Italic.eot │ │ │ ├── KaTeX_Math-Italic.ttf │ │ │ ├── KaTeX_Math-Italic.woff │ │ │ ├── KaTeX_Math-Italic.woff2 │ │ │ ├── KaTeX_Math-Regular.eot │ │ │ ├── KaTeX_Math-Regular.ttf │ │ │ ├── KaTeX_Math-Regular.woff │ │ │ ├── KaTeX_Math-Regular.woff2 │ │ │ ├── KaTeX_SansSerif-Bold.eot │ │ │ ├── KaTeX_SansSerif-Bold.ttf │ │ │ ├── KaTeX_SansSerif-Bold.woff │ │ │ ├── KaTeX_SansSerif-Bold.woff2 │ │ │ ├── KaTeX_SansSerif-Italic.eot │ │ │ ├── KaTeX_SansSerif-Italic.ttf │ │ │ ├── KaTeX_SansSerif-Italic.woff │ │ │ ├── KaTeX_SansSerif-Italic.woff2 │ │ │ ├── KaTeX_SansSerif-Regular.eot │ │ │ ├── KaTeX_SansSerif-Regular.ttf │ │ │ ├── KaTeX_SansSerif-Regular.woff │ │ │ ├── KaTeX_SansSerif-Regular.woff2 │ │ │ ├── KaTeX_Script-Regular.eot │ │ │ ├── KaTeX_Script-Regular.ttf │ │ │ ├── KaTeX_Script-Regular.woff │ │ │ ├── KaTeX_Script-Regular.woff2 │ │ │ ├── KaTeX_Size1-Regular.eot │ │ │ ├── KaTeX_Size1-Regular.ttf │ │ │ ├── KaTeX_Size1-Regular.woff │ │ │ ├── KaTeX_Size1-Regular.woff2 │ │ │ ├── KaTeX_Size2-Regular.eot │ │ │ ├── KaTeX_Size2-Regular.ttf │ │ │ ├── KaTeX_Size2-Regular.woff │ │ │ ├── KaTeX_Size2-Regular.woff2 │ │ │ ├── KaTeX_Size3-Regular.eot │ │ │ ├── KaTeX_Size3-Regular.ttf │ │ │ ├── KaTeX_Size3-Regular.woff │ │ │ ├── KaTeX_Size3-Regular.woff2 │ │ │ ├── KaTeX_Size4-Regular.eot │ │ │ ├── KaTeX_Size4-Regular.ttf │ │ │ ├── KaTeX_Size4-Regular.woff │ │ │ ├── KaTeX_Size4-Regular.woff2 │ │ │ ├── KaTeX_Typewriter-Regular.eot │ │ │ ├── KaTeX_Typewriter-Regular.ttf │ │ │ ├── KaTeX_Typewriter-Regular.woff │ │ │ └── KaTeX_Typewriter-Regular.woff2 │ │ ├── katex.js │ │ ├── katex.min.css │ │ └── src │ │ │ ├── Lexer.js │ │ │ ├── Options.js │ │ │ ├── ParseError.js │ │ │ ├── Parser.js │ │ │ ├── Settings.js │ │ │ ├── Style.js │ │ │ ├── buildCommon.js │ │ │ ├── buildHTML.js │ │ │ ├── buildMathML.js │ │ │ ├── buildTree.js │ │ │ ├── delimiter.js │ │ │ ├── domTree.js │ │ │ ├── environments.js │ │ │ ├── fontMetrics.js │ │ │ ├── fontMetricsData.js │ │ │ ├── functions.js │ │ │ ├── mathMLTree.js │ │ │ ├── parseData.js │ │ │ ├── parseTree.js │ │ │ ├── symbols.js │ │ │ └── utils.js │ ├── markdown-it-katex │ │ └── index.js │ └── mousetrap │ │ ├── global-bind.js │ │ └── mousetrap.js ├── welcome-guide.md └── yarn.lock ├── build └── icons │ ├── 512x512.png │ ├── icon.icns │ ├── icon.ico │ ├── markdown_file_association.icns │ └── markdown_file_association.ico ├── electron-builder.json ├── media └── preview.png ├── package.json ├── scripts ├── webpack.config.dev.js ├── webpack.config.js └── webpack.config.prod.js ├── src ├── app.vue ├── components │ ├── footer.vue │ ├── header.vue │ ├── main.vue │ ├── preference-pane.vue │ ├── svg-icon.vue │ ├── tip.vue │ └── writing-modes.vue ├── css │ ├── codemirror │ │ ├── base16-light.css │ │ ├── editor-dialog.css │ │ ├── editor-reset.css │ │ ├── editor-scrollbar.css │ │ └── tomorrow-night-bright.css │ ├── highlight │ │ ├── github.css │ │ └── tomorrow-night-bright.css │ ├── normalize.css │ ├── reset.css │ └── theme │ │ ├── dark.css │ │ └── light.css ├── index.ts ├── svg-shim.d.ts ├── svg │ ├── align-horizontal-middle.svg │ ├── arrow-left.svg │ ├── arrow-right.svg │ ├── eye.svg │ ├── pencil.svg │ └── settings.svg ├── utils │ ├── common.ts │ ├── dialog.js │ ├── dom.js │ ├── event.js │ ├── fs-promise.js │ ├── gist.js │ ├── handle-error.js │ ├── key.js │ ├── make-html.js │ ├── markdown.js │ ├── os.js │ ├── resolve-path.js │ ├── sanitize.js │ ├── tab.js │ ├── tildify.ts │ ├── tips.js │ ├── transitions.js │ └── wordcount.ts ├── views │ └── about.vue ├── vue-shims.d.ts └── vuex │ ├── modules │ ├── app.js │ └── editor.js │ ├── store.d.ts │ └── store.js ├── tsconfig.json └── yarn.lock /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | plugins: ["@babel/plugin-syntax-object-rest-spread", 3 | "@babel/plugin-transform-runtime", 4 | "@babel/plugin-transform-spread", 5 | "@babel/plugin-proposal-object-rest-spread"] 6 | } 7 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | indent_style = space 5 | indent_size = 2 6 | end_of_line = lf 7 | charset = utf-8 8 | trim_trailing_whitespace = true 9 | insert_final_newline = true 10 | 11 | [*.md] 12 | trim_trailing_whitespace = false 13 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "xo-space/esnext", 3 | "plugins": [ 4 | "vue" 5 | ], 6 | "rules": { 7 | "semi": ["error", "never"], 8 | "curly": ["error", "multi-line"], 9 | "no-warning-comments": 0, 10 | "max-lines": 0, 11 | "quote-props": ["error", "as-needed"], 12 | "guard-for-in": 0 13 | }, 14 | "env": { 15 | "browser": "true" 16 | }, 17 | "globals": { 18 | "Mousetrap": true 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | * text=auto 2 | *.vue linguist-language=JavaScript 3 | -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | # Copyright 2020 The EME Authors 2 | 3 | name: Build_Check 4 | on: [push] 5 | 6 | env: 7 | BUILD_TYPE: Debug 8 | 9 | jobs: 10 | build: 11 | name: Build_Check 12 | runs-on: ubuntu-latest 13 | steps: 14 | - uses: actions/checkout@v1 15 | - name: Prepare Node.js 16 | uses: actions/setup-node@v1 17 | with: 18 | node-version: 10 19 | - name: Install Dependencies 20 | run: | 21 | yarn install 22 | - name: Build App 23 | run: | 24 | yarn build 25 | - name: Build 26 | env: 27 | GH_TOKEN: ${{ secrets.github_token }} 28 | run: | 29 | yarn linux 30 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | *.log 3 | .DS_Store 4 | /app/libvue 5 | /dist 6 | stats.html 7 | stats.json 8 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 EGOIST 0x142857@gmail.com 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # EME [![version](https://img.shields.io/github/release/egoist/eme.svg?style=flat-square)](https://github.com/egoist/eme/releases) [![downloads](https://img.shields.io/github/downloads/egoist/eme/total.svg?style=flat-square)](https://github.com/egoist/eme/releases) [![downloads latest](https://img.shields.io/github/downloads/egoist/eme/latest/total.svg?style=flat-square)](https://github.com/egoist/eme/releases/latest) [![build](https://github.com/egoist/eme/workflows/Build_Check/badge.svg)](https://circleci.com/gh/egoist/eme) [![donate](https://img.shields.io/badge/$-donate-ff69b4.svg?maxAge=2592000&style=flat-square)](#donate) 2 | 3 | ![preview](/media/preview.png) 4 | 5 | ## Download 6 | 7 | You can manually download the latest release [here](https://github.com/egoist/eme/releases) 8 | 9 | ## Features 10 | 11 | - It just suits, show editor or preview or both just as you wish. 12 | - Focus mode, writing without distractions. 13 | - Exportable, from Markdown to HTML/PDF... You name it. 14 | - Supporting math typesetting, good for students and professionals. 15 | - Auto-sync files to GitHub Gist after being saved, optional. 16 | 17 | ## WIKI 18 | 19 | - [Key bindings](https://github.com/egoist/eme/wiki/Key-bindings) 20 | - [...more](https://github.com/egoist/eme/wiki) 21 | 22 | ## Contribute 23 | 24 | Pull requests are always welcome! Check out the [these issues](https://github.com/egoist/eme/issues?q=is%3Aissue+is%3Aopen+label%3A%22contribution+welcome%22) to get started fast. 25 | 26 | 27 | 1. [Fork](https://help.github.com/articles/fork-a-repo/) this repository to your own GitHub account and then [clone](https://help.github.com/articles/cloning-a-repository/) it to your local device 28 | 2. Install the dependencies: `yarn` 29 | 3. Build the code and watch for changes: `yarn watch` 30 | 4. In a new tab, start the application: `yarn start` 31 | 32 | If you want to build the binary for a specified platform, run the command: 33 | 34 | ```bash 35 | $ yarn mac # .dmg 36 | $ yarn win # .exe 37 | $ yarn linux # .deb 38 | ``` 39 | 40 | After that, you'll see the binaries in the `./dist` folder! 41 | 42 | ## Donate 43 | 44 | If you are enjoying this app, please consider making a donation to keep it alive, I will try my best to dedicate more time or even full time to work on it. 😉 45 | 46 | - [Donate via Paypal](https://www.paypal.me/egoistian/10) 47 | - [Donate via Wechat](http://ww4.sinaimg.cn/large/a15b4afegw1f72ib6rj67j20u00tvgnj.jpg) 48 | - [Donate via Alipay](http://ww4.sinaimg.cn/large/a15b4afegw1f72ib54hybj20qo0nndh5.jpg) 49 | - [Donate via Bitcoin](http://ww4.sinaimg.cn/large/a15b4afegw1f72icbcu0gj202s02sdfl.jpg) `1NUSDCWti9FBJLiUxaLY1zJnwcSDc5Tfci` 50 | 51 | If you are not available for this, simply spreading the word for us would help too! 52 | 53 | ## License 54 | 55 | MIT © [EGOIST](https://github.com/egoist) 56 | -------------------------------------------------------------------------------- /app/eme.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | if [ "$(uname)" == 'Darwin' ]; then 4 | OS='Mac' 5 | else 6 | echo "Your platform ($(uname -a)) is not supported." 7 | exit 1 8 | fi 9 | 10 | while getopts ":wtfvh-:" opt; do 11 | case "$opt" in 12 | -) 13 | case "${OPTARG}" in 14 | wait) 15 | WAIT=1 16 | ;; 17 | help|version) 18 | REDIRECT_STDERR=1 19 | EXPECT_OUTPUT=1 20 | ;; 21 | foreground|test) 22 | EXPECT_OUTPUT=1 23 | ;; 24 | esac 25 | ;; 26 | w) 27 | WAIT=1 28 | ;; 29 | h|v) 30 | REDIRECT_STDERR=1 31 | EXPECT_OUTPUT=1 32 | ;; 33 | f|t) 34 | EXPECT_OUTPUT=1 35 | ;; 36 | esac 37 | done 38 | 39 | if [ $REDIRECT_STDERR ]; then 40 | exec 2> /dev/null 41 | fi 42 | 43 | if [ $EXPECT_OUTPUT ]; then 44 | export ELECTRON_ENABLE_LOGGING=1 45 | fi 46 | 47 | if [ $OS == 'Mac' ]; then 48 | EME_APP_NAME="EME.app" 49 | if [ -z "${EME_PATH}" ]; then 50 | if [ -x "/Applications/$EME_APP_NAME" ]; then 51 | EME_PATH="/Applications" 52 | elif [ -x "$HOME/Applications/$EME_APP_NAME" ]; then 53 | EME_PATH="$HOME/Applications" 54 | else 55 | if [ ! -x "$EME_PATH/$EME_APP_NAME" ]; then 56 | echo "Cannot locate EME.app, it is usually located /Applications. Set the EME_PATH environment variable to the directory containing EME.app. " 57 | exit 1 58 | fi 59 | fi 60 | fi 61 | 62 | if [ $EXPECT_OUTPUT ]; then 63 | "$EME_PATH/$EME_APP_NAME/Contents/MacOS/EME" --executed-from="$(pwd)" --pid=$$ $* 64 | exit $? 65 | else 66 | open -a "$EME_PATH/$EME_APP_NAME" -n --args --executed-from="$(pwd)" --pid=$$ --path-environment="$PATH" $* 67 | fi 68 | fi 69 | -------------------------------------------------------------------------------- /app/eme/config.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | const os = require('os') 3 | const Config = require('./utils/electron-config') 4 | 5 | const cmdOrCtrl = os.platform() === 'darwin' ? 'command' : 'ctrl' 6 | 7 | const config = new Config({ 8 | defaults: { 9 | recentFiles: [], 10 | lastAppState: { 11 | tabs: [], 12 | currentTabIndex: null 13 | }, 14 | gists: {}, 15 | settings: { 16 | theme: 'light', 17 | themeControl: 'system', 18 | writingMode: 'default', 19 | tokens: { 20 | github: '' 21 | }, 22 | editor: { 23 | theme: 'base16-light', 24 | font: `"fira code", menlo, "lucida console"`, 25 | fontSize: 16, 26 | tabSize: 2, 27 | indentWithTabs: false 28 | }, 29 | preview: { 30 | highlight: 'github', 31 | font: `"fira code", menlo, "lucida console"`, 32 | codeFont: 'inherit', 33 | fontSize: 16 34 | }, 35 | autoSaveGist: false, 36 | keys: { 37 | openNewTab: `${cmdOrCtrl}+t`, 38 | openFile: `${cmdOrCtrl}+o`, 39 | openLastSession: `${cmdOrCtrl}+l`, 40 | switchWritingMode: `${cmdOrCtrl}+shift+m`, 41 | distractionFreeMode: `${cmdOrCtrl}+j`, 42 | vimMode: `${cmdOrCtrl}+i`, 43 | focusMode: `${cmdOrCtrl}+\\`, 44 | toggleNightMode: `${cmdOrCtrl}+shift+n` 45 | } 46 | } 47 | } 48 | }) 49 | 50 | module.exports = config 51 | -------------------------------------------------------------------------------- /app/eme/context-menu.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | const { 3 | MenuItem, 4 | shell 5 | } = require('electron') 6 | 7 | module.exports = { 8 | append(params) { 9 | const text = params.selectionText 10 | return [ 11 | new MenuItem({ 12 | type: 'separator' 13 | }), 14 | new MenuItem({ 15 | label: 'Search in Google', 16 | enabled: Boolean(text), 17 | click() { 18 | if (text) { 19 | shell.openExternal(`https://www.google.com/search?q=${text}`) 20 | } 21 | } 22 | }) 23 | ] 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /app/eme/event.js: -------------------------------------------------------------------------------- 1 | const EventEmitter = require('events') 2 | 3 | const event = new EventEmitter() 4 | 5 | module.exports = event 6 | -------------------------------------------------------------------------------- /app/eme/menu.js: -------------------------------------------------------------------------------- 1 | // Copyright 2020 @The EME Authors 2 | 3 | 'use strict' 4 | 5 | const axios = require('axios') 6 | const compare = require('semver-compare') 7 | const config = require('./config') 8 | const event = require('./event') 9 | const path = require('path') 10 | const tildify = require('tildify') 11 | const { 12 | app, 13 | dialog, 14 | Menu, 15 | shell 16 | } = require('electron') 17 | const { changeTheme, isSystemInNightMode } = require('./utils/theme') 18 | const { InstallShell } = require('./shell') 19 | const _ = require('./utils') 20 | 21 | const version = app.getVersion() 22 | const checkForUpdates = { 23 | label: 'Check for Updates', 24 | click(item, focusedWindow) { 25 | axios.get('https://api.github.com/repos/egoist/eme/releases/latest') 26 | .then(res => { 27 | const latestVersion = res.data.tag_name.substr(1) 28 | const hasUpdates = compare(latestVersion, version) === 1 29 | if (hasUpdates) { 30 | const answer = dialog.showMessageBox(focusedWindow, { 31 | type: 'question', 32 | message: 'Update', 33 | detail: `A newer version ${latestVersion} is available, are you willing to download the updates?`, 34 | buttons: ['OK', 'Cancel'] 35 | }) 36 | if (answer === 0) { 37 | shell.openExternal(`https://github.com/egoist/eme/releases/tag/v${latestVersion}`) 38 | } 39 | } else { 40 | dialog.showMessageBox(focusedWindow, { 41 | type: 'info', 42 | message: 'No Updates', 43 | detail: `Current version ${version} is already up to date!`, 44 | buttons: ['OK'] 45 | }) 46 | } 47 | }) 48 | .catch(err => { 49 | dialog.showErrorBox('Update', `${err.name}: ${err.message}`) 50 | }) 51 | } 52 | } 53 | 54 | module.exports = cb => { 55 | const openFileInWindow = async (win, file) => { 56 | if (win) { 57 | win.webContents.send('open-file', file) 58 | } else { 59 | win = cb.createWindow() 60 | win.webContents.on('did-finish-load', () => { 61 | win.webContents.send('open-file', file) 62 | }) 63 | } 64 | } 65 | 66 | let recentFiles = config.get('recentFiles').map(file => ({ 67 | label: tildify(file), 68 | file, 69 | click(item, focusedWindow) { 70 | openFileInWindow(focusedWindow, item.file) 71 | } 72 | })) 73 | if (recentFiles.length === 0) { 74 | recentFiles = [{ 75 | label: '(empty)' 76 | }] 77 | } 78 | 79 | const settings = config.get('settings') 80 | const keys = settings.keys 81 | 82 | const template = [ 83 | { 84 | label: 'File', 85 | submenu: [ 86 | { 87 | label: 'New Tab', 88 | accelerator: keys.openNewTab, 89 | click(item, focusedWindow) { 90 | if (focusedWindow) { 91 | focusedWindow.webContents.send('new-tab') 92 | } else { 93 | cb.createWindow() 94 | } 95 | } 96 | }, 97 | { 98 | label: 'Open', 99 | accelerator: keys.openFile, 100 | click(item, focusedWindow) { 101 | dialog.showOpenDialog(focusedWindow, { 102 | properties: ['openFile', 'multiSelections'] 103 | }).then(result => { 104 | if (result && !result.canceled) { 105 | const locationsToOpen = result.filePaths 106 | locationsToOpen.forEach(singleLocation => { 107 | openFileInWindow(focusedWindow, singleLocation) 108 | }) 109 | } 110 | }).catch(err => { 111 | console.log(err) 112 | }) 113 | } 114 | }, 115 | { 116 | label: 'Open Last Session', 117 | accelerator: keys.openLastSession, 118 | click(item, focusedWindow) { 119 | if (focusedWindow) focusedWindow.webContents.send('open-last-session') 120 | } 121 | }, 122 | { 123 | label: 'Open Recent', 124 | submenu: recentFiles.concat([ 125 | { 126 | type: 'separator' 127 | }, 128 | { 129 | label: 'Clear all', 130 | click() { 131 | config.set('recentFiles', []) 132 | event.emit('reload-menu') 133 | } 134 | } 135 | ]) 136 | }, 137 | { 138 | type: 'separator' 139 | }, 140 | { 141 | label: 'Save', 142 | accelerator: 'CmdOrCtrl+S', 143 | click(item, focusedWindow) { 144 | if (focusedWindow) focusedWindow.webContents.send('file-save') 145 | } 146 | }, 147 | { 148 | label: 'Save As', 149 | accelerator: 'CmdOrCtrl+S+Shift', 150 | click(item, focusedWindow) { 151 | if (focusedWindow) focusedWindow.webContents.send('file-save-as') 152 | } 153 | }, 154 | { 155 | label: 'Rename', 156 | accelerator: 'CmdOrCtrl+Shift+R', 157 | click(item, focusedWindow) { 158 | if (focusedWindow) focusedWindow.webContents.send('file-rename') 159 | } 160 | }, 161 | { 162 | type: 'separator' 163 | }, 164 | { 165 | label: 'Export as PDF', 166 | accelerator: 'CmdOrCtrl+Shift+P', 167 | click(item, focusedWindow) { 168 | if (focusedWindow) focusedWindow.webContents.send('show-save-pdf-dialog') 169 | } 170 | } 171 | ] 172 | }, 173 | { 174 | label: 'Edit', 175 | submenu: [ 176 | { 177 | role: 'undo' 178 | }, 179 | { 180 | role: 'redo' 181 | }, 182 | { 183 | type: 'separator' 184 | }, 185 | { 186 | role: 'cut' 187 | }, 188 | { 189 | role: 'copy' 190 | }, 191 | { 192 | role: 'paste' 193 | }, 194 | { 195 | role: 'pasteandmatchstyle' 196 | }, 197 | { 198 | role: 'delete' 199 | }, 200 | { 201 | role: 'selectall' 202 | } 203 | ] 204 | }, 205 | { 206 | label: 'View', 207 | submenu: [ 208 | { 209 | label: 'Reload', 210 | visible: Boolean(_.isDev), 211 | accelerator: 'CmdOrCtrl+R', 212 | click(item, focusedWindow) { 213 | if (focusedWindow) focusedWindow.reload() 214 | } 215 | }, 216 | { 217 | role: 'togglefullscreen' 218 | }, 219 | { 220 | type: 'separator' 221 | }, 222 | { 223 | label: 'Theme', 224 | submenu: [ 225 | { 226 | label: 'System Setting', 227 | type: 'radio', 228 | checked: settings.themeControl === 'system', 229 | click(item, focusedWindow) { 230 | var theme = isSystemInNightMode() ? 'dark' : 'light' 231 | changeTheme(focusedWindow, 'system', theme) 232 | } 233 | }, 234 | { 235 | label: 'Light', 236 | type: 'radio', 237 | checked: settings.themeControl === 'manual' && settings.theme === 'light', 238 | click(item, focusedWindow) { 239 | changeTheme(focusedWindow, 'manual', 'light') 240 | } 241 | }, 242 | { 243 | label: "Night", 244 | type: 'radio', 245 | checked: settings.themeControl === 'manual' && settings.theme === 'dark', 246 | click(item, focusedWindow) { 247 | changeTheme(focusedWindow, 'manual', 'dark') 248 | } 249 | } 250 | ], 251 | }, 252 | { 253 | type: 'separator' 254 | }, 255 | { 256 | label: 'Toggle Distraction Free mode', 257 | accelerator: keys.distractionFreeMode, 258 | click(item, focusedWindow) { 259 | if (focusedWindow) focusedWindow.webContents.send('toggle-distraction-free-mode') 260 | } 261 | }, 262 | { 263 | label: 'Toggle Focus Mode', 264 | accelerator: keys.focusMode, 265 | click(item, focusedWindow) { 266 | if (focusedWindow) focusedWindow.webContents.send('toggle-focus-mode') 267 | } 268 | }, 269 | { 270 | label: 'Toggle Vim Mode', 271 | accelerator: keys.vimMode, 272 | click(item, focusedWindow) { 273 | if (focusedWindow) focusedWindow.webContents.send('toggle-vim-mode') 274 | } 275 | }, 276 | { 277 | type: 'separator' 278 | }, 279 | { 280 | label: 'Switch Writing Mode', 281 | accelerator: keys.switchWritingMode, 282 | click(item, focusedWindow) { 283 | if (focusedWindow) focusedWindow.webContents.send('switch-writing-mode') 284 | } 285 | }, 286 | { 287 | type: 'separator' 288 | }, 289 | { 290 | label: 'Toggle Developer Tools', 291 | accelerator: process.platform === 'darwin' ? 'Alt+Command+I' : 'Ctrl+Shift+I', 292 | click(item, focusedWindow) { 293 | if (focusedWindow) focusedWindow.webContents.toggleDevTools() 294 | } 295 | } 296 | ] 297 | }, 298 | { 299 | role: 'window', 300 | submenu: [ 301 | { 302 | label: 'Close', 303 | accelerator: 'CmdOrCtrl+W', 304 | click(item, focusedWindow) { 305 | if (focusedWindow) focusedWindow.webContents.send('close-current-tab') 306 | } 307 | }, 308 | { 309 | label: 'Minimize', 310 | accelerator: 'CmdOrCtrl+M', 311 | role: 'minimize' 312 | }, 313 | { 314 | label: 'Zoom', 315 | role: 'zoom' 316 | }, 317 | { 318 | type: 'separator' 319 | }, 320 | { 321 | label: 'Bring All to Front', 322 | role: 'front' 323 | } 324 | ] 325 | }, 326 | { 327 | role: 'help', 328 | submenu: [ 329 | { 330 | label: 'Report bugs', 331 | click() { 332 | shell.openExternal('http://github.com/egoist/eme/issues') 333 | } 334 | }, 335 | { 336 | type: 'separator' 337 | }, 338 | { 339 | label: 'Welcome Guide', 340 | click(item, focusedWindow) { 341 | const file = path.join(__dirname, '../welcome-guide.md') 342 | openFileInWindow(focusedWindow, file) 343 | } 344 | }, 345 | { 346 | label: 'Math Typesetting', 347 | click(item, focusedWindow) { 348 | const file = path.join(__dirname, '../math-typesetting.md') 349 | openFileInWindow(focusedWindow, file) 350 | } 351 | } 352 | ] 353 | } 354 | ] 355 | 356 | if (process.platform === 'darwin') { 357 | const name = app.name 358 | template.unshift({ 359 | label: name, 360 | submenu: [ 361 | { 362 | role: 'about' 363 | }, 364 | { 365 | type: 'separator' 366 | }, 367 | checkForUpdates, 368 | { 369 | type: 'separator' 370 | }, 371 | { 372 | label: 'Install Shell Command', 373 | click() { 374 | new InstallShell().installShell() 375 | } 376 | }, 377 | { 378 | type: 'separator' 379 | }, 380 | { 381 | role: 'hide' 382 | }, 383 | { 384 | role: 'hideothers' 385 | }, 386 | { 387 | role: 'unhide' 388 | }, 389 | { 390 | type: 'separator' 391 | }, 392 | { 393 | label: 'Quit', 394 | accelerator: 'Command+Q', 395 | click(item, focusedWindow) { 396 | if (focusedWindow) { 397 | focusedWindow.webContents.send('close-and-exit') 398 | } else { 399 | app.exit(0) 400 | } 401 | } 402 | } 403 | ] 404 | }) 405 | } else { 406 | template[template.length - 1].submenu.push( 407 | { 408 | type: 'separator' 409 | }, 410 | checkForUpdates, 411 | { 412 | label: 'About', 413 | click() { 414 | cb.showAboutWindow() 415 | } 416 | } 417 | ) 418 | } 419 | 420 | return Menu.buildFromTemplate(template) 421 | } 422 | -------------------------------------------------------------------------------- /app/eme/shell.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const path = require('path') 4 | const spawn = require('child_process').spawn 5 | const yargs = require('yargs') 6 | const {app} = require('electron') 7 | 8 | class InstallShell { 9 | 10 | installShell() { 11 | const commandName = 'eme' 12 | const commandPath = path.join(this.getResourcesDirectory(), 'app', 'eme.sh') 13 | const destinationPath = path.join(this.getInstallDirectory(), commandName) 14 | this.createSymLink(commandPath, destinationPath) 15 | } 16 | 17 | getInstallDirectory() { 18 | return '/usr/local/bin' 19 | } 20 | 21 | getResourcesDirectory() { 22 | return process.resourcesPath 23 | } 24 | 25 | createSymLink(commandPath, destinationPath) { 26 | try { 27 | spawn('rm', ['-f', destinationPath]) 28 | spawn('mkdir', ['-p', path.dirname(destinationPath)]) 29 | spawn('ln', ['-s', commandPath, destinationPath]) 30 | } catch (e) { 31 | console.log(e) 32 | } 33 | } 34 | } 35 | 36 | function writeFullVersion() { 37 | console.log( 38 | ` 39 | Atom : ${app.getVersion()} 40 | Electron: ${process.versions.electron} 41 | Chrome : ${process.versions.chrome} 42 | Node : ${process.versions.node} 43 | ` 44 | ) 45 | } 46 | 47 | function parseShellCommand() { 48 | const options = yargs(process.argv.slice(1)) 49 | .usage( 50 | ` 51 | EME - Elegant Markdown Editor 52 | 53 | Usage: eme [options] [path ...] 54 | ` 55 | ) 56 | .alias('h', 'help') 57 | .boolean('h') 58 | .describe('h', 'Print this usage message.') 59 | .alias('v', 'version') 60 | .boolean('v') 61 | .describe('v', 'Print the version information.') 62 | 63 | const argv = options.argv 64 | if (argv.version) { 65 | writeFullVersion() 66 | process.exit(0) 67 | } 68 | 69 | if (argv.help) { 70 | options.showHelp('log') 71 | process.exit(0) 72 | } 73 | 74 | const pathsToOpen = argv._ 75 | const resourcePath = argv.executedFrom 76 | return { 77 | resourcePath, pathsToOpen 78 | } 79 | } 80 | 81 | module.exports = { 82 | InstallShell, 83 | parseShellCommand 84 | } 85 | -------------------------------------------------------------------------------- /app/eme/utils.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | const _ = module.exports = {} 3 | 4 | _.isDev = process.env.NODE_ENV === 'development' 5 | -------------------------------------------------------------------------------- /app/eme/utils/electron-config.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | const electron = require('electron') 3 | const Conf = require('conf') 4 | 5 | class ElectronConfig extends Conf { 6 | constructor(opts) { 7 | opts = Object.assign({name: 'config'}, opts) 8 | opts.cwd = (electron.app || electron.remote.app).getPath('userData') 9 | opts.configName = opts.name 10 | delete opts.name 11 | super(opts) 12 | } 13 | } 14 | 15 | module.exports = ElectronConfig 16 | -------------------------------------------------------------------------------- /app/eme/utils/theme.js: -------------------------------------------------------------------------------- 1 | // Copyright 2020 @TwoCookingMice 2 | 3 | 'use strict' 4 | 5 | const { nativeTheme } = require('electron') 6 | 7 | function isSystemInNightMode() { 8 | return nativeTheme.shouldUseDarkColors 9 | } 10 | 11 | function changeTheme(win, themeControl, theme) { 12 | if (win) { 13 | win.webContents.send('change-theme', themeControl, theme) 14 | } 15 | } 16 | 17 | module.exports = { changeTheme, isSystemInNightMode } 18 | -------------------------------------------------------------------------------- /app/eme/window.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | const path = require('path') 3 | const electron = require('electron') 4 | const { 5 | BrowserWindow, 6 | shell 7 | } = electron 8 | const config = require('./config') 9 | 10 | const isDev = process.env.NODE_ENV === 'development' 11 | 12 | class Window { 13 | constructor() { 14 | this.wins = 0 15 | } 16 | 17 | createWindow({ 18 | homepage = `file://${path.join(__dirname, '../index.html')}`, 19 | windowState = {} 20 | } = {}) { 21 | const win = new BrowserWindow(Object.assign({ 22 | name: 'EME', 23 | width: 800, 24 | height: 600, 25 | minWidth: 430, 26 | minHeight: 250, 27 | titleBarStyle: 'hidden-inset', 28 | webPreferences: { 29 | nodeIntegration: true 30 | } 31 | }, windowState)) 32 | 33 | const web = win.webContents 34 | 35 | win.loadURL(homepage) 36 | 37 | win.webContents.on('new-window', (e, url) => { 38 | e.preventDefault() 39 | shell.openExternal(url) 40 | }) 41 | 42 | win.on('close', () => { 43 | web.send('close-window') 44 | }) 45 | 46 | win.on('closed', () => { 47 | this.wins-- 48 | }) 49 | 50 | win.on('focus', () => { 51 | web.send('win-focus') 52 | }) 53 | 54 | win.on('enter-full-screen', () => { 55 | web.send('enter-full-screen') 56 | }) 57 | 58 | win.on('leave-full-screen', () => { 59 | web.send('leave-full-screen') 60 | }) 61 | 62 | if (isDev) { 63 | const installExtension = require('electron-devtools-installer') 64 | installExtension.default(installExtension.VUEJS_DEVTOOLS) 65 | .then(name => console.log(`Added Extension: ${name}`)) 66 | .catch(err => console.log('An error occurred: ', err)) 67 | } 68 | 69 | this.wins++ 70 | win.$state = { 71 | unsaved: 0 72 | } 73 | win.$config = config 74 | 75 | return win 76 | } 77 | } 78 | 79 | module.exports = new Window() 80 | -------------------------------------------------------------------------------- /app/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | EME 6 | 7 | 8 | 9 | 10 | 11 | 12 |
13 | 14 |
15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /app/index.js: -------------------------------------------------------------------------------- 1 | // Copyright 2020 @The EME Authors 2 | 3 | 'use strict' 4 | const buildMenu = require('./eme/menu') 5 | const config = require('./eme/config') 6 | const contextMenu = require('./eme/context-menu') 7 | const emeWindow = require('./eme/window') 8 | const event = require('./eme/event') 9 | const fs = require('fs') 10 | const os = require('os') 11 | const path = require('path') 12 | const pkg = require('./package.json') 13 | const windowStateKeeper = require('electron-window-state') 14 | const { 15 | app, 16 | BrowserWindow, 17 | Menu, 18 | ipcMain, 19 | dialog, 20 | nativeTheme 21 | } = require('electron') 22 | const { isDev } = require('./eme/utils') 23 | const { changeTheme, isSystemInNightMode } = require('./eme/utils/theme') 24 | const { parseShellCommand } = require('./eme/shell') 25 | 26 | require('electron-context-menu')(contextMenu) 27 | 28 | const platform = os.platform() 29 | 30 | const createMainWindow = () => { 31 | const windowState = windowStateKeeper({ 32 | defaultWidth: 800, 33 | defaultHeight: 600 34 | }) 35 | if (platform === 'linux') { 36 | windowState.icon = path.join(__dirname, 'resources/icon.png') 37 | } 38 | const win = emeWindow.createWindow({windowState}) 39 | windowState.manage(win) 40 | return win 41 | } 42 | 43 | const showAboutWindow = () => { 44 | dialog.showMessageBox({ 45 | title: pkg.productName, 46 | message: pkg.productName, 47 | type: 'info', 48 | detail: [ 49 | `Version ${app.getVersion()}`, 50 | `Shell ${process.versions.electron}`, 51 | `Renderer ${process.versions.chrome}`, 52 | `Node ${process.versions.node}` 53 | ].join('\n'), 54 | buttons: ['OK'], 55 | noLink: true 56 | }, () => null) 57 | } 58 | 59 | const menuOptions = { 60 | createWindow: emeWindow.createWindow, 61 | showAboutWindow 62 | } 63 | 64 | const appMenu = buildMenu(menuOptions) 65 | 66 | const reloadMenu = () => { 67 | Menu.setApplicationMenu(buildMenu(menuOptions)) 68 | } 69 | 70 | let mainWindow // eslint-disable-line 71 | let pdfWindow // eslint-disable-line 72 | 73 | let locationToOpen = null // Cache locationToOpen when app is not ready. 74 | 75 | app.on('ready', () => { 76 | const argv = parseShellCommand() 77 | Menu.setApplicationMenu(appMenu) 78 | mainWindow = createMainWindow() 79 | 80 | if (!isDev) { 81 | const {pathsToOpen, resourcePath} = argv 82 | const pathToOpen = pathsToOpen[0] 83 | 84 | if (pathsToOpen && resourcePath) { 85 | locationToOpen = path.resolve(resourcePath, pathToOpen) 86 | } 87 | 88 | // Open the file 89 | if (locationToOpen != null) { 90 | mainWindow.webContents.on('did-finish-load', () => { 91 | mainWindow.webContents.send('open-file', locationToOpen) 92 | locationToOpen = null 93 | }) 94 | } 95 | } 96 | 97 | if (platform === 'darwin') { 98 | mainWindow.setSheetOffset(36) 99 | } 100 | }) 101 | 102 | app.on('window-all-closed', () => { 103 | if (platform !== 'darwin') { 104 | app.quit() 105 | } else { 106 | mainWindow = null 107 | } 108 | }) 109 | 110 | app.on('activate', (e, hasVisibleWindows) => { 111 | // If the application is not ready, then 112 | // just return and wait when the app is ready. 113 | if (!app.isReady()) { 114 | return 115 | } 116 | 117 | if (mainWindow == null) { 118 | mainWindow = createMainWindow() 119 | if (platform === 'darwin') { 120 | mainWindow.setSheetOffset(36) 121 | } 122 | } 123 | 124 | if (!hasVisibleWindows) { 125 | mainWindow.show() 126 | } 127 | 128 | // Open the file 129 | if (locationToOpen != null) { 130 | mainWindow.webContents.on('did-finish-load', () => { 131 | mainWindow.webContents.send('open-file', locationToOpen) 132 | locationToOpen = null 133 | }) 134 | } 135 | }) 136 | 137 | app.on('open-file', (e, filePath) => { 138 | e.preventDefault() 139 | console.log(filePath) 140 | 141 | if (mainWindow == null) { 142 | locationToOpen = filePath 143 | } else { 144 | mainWindow.webContents.send('open-file', filePath) 145 | } 146 | }) 147 | 148 | ipcMain.on('close-focus-window', () => { 149 | BrowserWindow.getFocusedWindow().close() 150 | }) 151 | 152 | // TODO: refactor 153 | ipcMain.on('print-to-pdf', (e, html) => { 154 | pdfWindow = new BrowserWindow({show: false}) 155 | const tempPath = path.join(os.tmpdir(), `eme-export-pdf.${Date.now()}.html`) 156 | fs.writeFileSync(tempPath, html, 'utf8') 157 | console.log(tempPath) 158 | pdfWindow.loadURL(`file://${tempPath}`) 159 | }) 160 | 161 | ipcMain.on('pdf-window-ready', (e, options) => { 162 | const page = pdfWindow.webContents 163 | page.on('did-finish-load', () => { 164 | page.printToPDF({ 165 | pageSize: 'A4' 166 | }, (err, pdfData) => { 167 | if (err) { 168 | return console.log(err) 169 | } 170 | fs.writeFile(options.saveTo, pdfData, err => { 171 | pdfWindow.destroy() 172 | pdfWindow = undefined 173 | mainWindow.webContents.send('finish-exporting-pdf', err, options.saveTo) 174 | }) 175 | }) 176 | }) 177 | }) 178 | 179 | ipcMain.on('add-recent-file', (e, filePath) => { 180 | const files = config.get('recentFiles') 181 | const existing = files.indexOf(filePath) 182 | if (existing === -1) { 183 | // add to the head 184 | files.unshift(filePath) 185 | if (files.length > 10) { 186 | files.pop() 187 | } 188 | } else { 189 | // remove the existing one 190 | // add to the head 191 | files.splice(existing, 1) 192 | files.unshift(filePath) 193 | } 194 | 195 | config.set('recentFiles', files) 196 | reloadMenu() 197 | }) 198 | 199 | ipcMain.on('remove-recent-file', (e, fileToRemove) => { 200 | config.set('recentFiles', config.get('recentFiles').filter(filePath => { 201 | return filePath !== fileToRemove 202 | })) 203 | reloadMenu() 204 | }) 205 | 206 | ipcMain.on('log', (e, msg) => { 207 | console.log(JSON.stringify(msg, null, 2)) 208 | }) 209 | 210 | ipcMain.on('reload-menu', () => { 211 | reloadMenu() 212 | }) 213 | 214 | event.on('reload-menu', () => { 215 | reloadMenu() 216 | }) 217 | 218 | // Electron uses **nativeTheme** API to support native theme 219 | // on MacOS and Windows. We register a listener here to adjust 220 | // app's appearance when native theme of system changes. 221 | nativeTheme.on('updated', () => { 222 | const themeControl = config.get('settings').themeControl 223 | if (themeControl === 'system') { 224 | const theme = isSystemInNightMode() ? 'dark' : 'light' 225 | if (mainWindow) { 226 | changeTheme(mainWindow, themeControl, theme) 227 | } 228 | } 229 | }) 230 | -------------------------------------------------------------------------------- /app/math-typesetting.md: -------------------------------------------------------------------------------- 1 | # Math typesetting 2 | 3 | EME uses [KaTex](https://khan.github.io/KaTeX/) to render math symbols. 4 | You can write $\frac{inline}{expressions}$ by using single `$`. 5 | For a block, use double `$$`: 6 | $$ 7 | \frac{this}{a} \frac{is}{block} 8 | $$ 9 | 10 | ## Symbols 11 | 12 | This list of symbols is copied from [here](http://web.ift.uib.no/Teori/KURS/WRK/TeX/symALL.html). 13 | 14 | ### Greek Letters 15 | 16 | $$ 17 | \alpha \theta o \tau 18 | \beta \vartheta \pi \upsilon 19 | \gamma \gamma \varpi \phi 20 | \delta \kappa \rho \varphi 21 | \epsilon \lambda \varrho \chi 22 | \varepsilon \mu \sigma \psi 23 | \zeta \nu \varsigma \omega 24 | \eta \xi 25 | $$ 26 | 27 | $$ 28 | \Gamma \Lambda \Sigma \Psi 29 | \Delta \Xi \Upsilon \Omega 30 | \Theta \Pi \Phi 31 | $$ 32 | 33 | ### Binary Operation Symbols 34 | 35 | $$ 36 | \pm \cap \diamond \oplus 37 | \mp \cup \bigtriangleup \ominus 38 | \times \uplus \bigtriangledown \otimes 39 | \div \sqcap \triangleleft \oslash 40 | \ast \sqcup \triangleright \odot 41 | \star \vee \lhd \bigcirc 42 | \circ \wedge \rhd \dagger 43 | \bullet \setminus \unlhd \ddagger 44 | \cdot \wr \unrhd \amalg 45 | + - 46 | $$ 47 | 48 | ### Relation Symbols 49 | 50 | $$ 51 | \leq \geq \equiv \models 52 | \prec \succ \sim \perp 53 | \preceq \succeq \simeq \mid 54 | \ll \gg \asymp \parallel 55 | \subset \supset \approx \bowtie 56 | \subseteq \supseteq \cong \Join 57 | \sqsubset \sqsupset \neq \smile 58 | \sqsubseteq \sqsupseteq \doteq \frown 59 | \in \ni \propto = 60 | \vdash \dashv < > 61 | : 62 | $$ 63 | 64 | ### Punctuation Symbols 65 | 66 | $$ 67 | , ; \colon \ldotp \cdotp 68 | $$ 69 | 70 | ### Arrow Symbols 71 | 72 | $$ 73 | \leftarrow \longleftarrow \uparrow 74 | \Leftarrow \Longleftarrow \Uparrow 75 | \rightarrow \longrightarrow \downarrow 76 | \Rightarrow \Longrightarrow \Downarrow 77 | \leftrightarrow \longleftrightarrow \updownarrow 78 | \Leftrightarrow \Longleftrightarrow \Updownarrow 79 | \mapsto \longmapsto \nearrow 80 | \hookleftarrow \hookrightarrow \searrow 81 | \leftharpoonup \rightharpoonup \swarrow 82 | \leftharpoondown \rightharpoondown \nwarrow 83 | \rightleftharpoons \leadsto 84 | $$ 85 | 86 | ### Miscellaneous Symbols 87 | 88 | $$ 89 | \ldots \cdots \vdots \ddots 90 | \aleph \prime \forall \infty 91 | \hbar \emptyset \exists \Box 92 | \imath \nabla \neg \Diamond 93 | \jmath \surd \flat \triangle 94 | \ell \top \natural \clubsuit 95 | \wp \bot \sharp \diamondsuit 96 | \Re \| \backslash \heartsuit 97 | \Im \angle \partial \spadesuit 98 | \mho . | 99 | $$ 100 | 101 | ### Variable-sized Symbols 102 | 103 | $$ 104 | \sum \bigcap \bigodot 105 | \prod \bigcup \bigotimes 106 | \coprod \bigsqcup \bigoplus 107 | \int \bigvee \biguplus 108 | \oint \bigwedge 109 | $$ 110 | 111 | ### Log-like Symbols 112 | 113 | $$ 114 | \arccos \cos \csc \exp \ker \limsup \min \sinh 115 | \arcsin \cosh \deg \gcd \lg \ln \Pr \sup 116 | $$ 117 | $$ 118 | \arctan \cot \det \hom \lim \log \sec \tan 119 | \arg \coth \dim \inf \liminf \max \sin \tanh 120 | $$ 121 | 122 | ### Delimiters 123 | 124 | $$ 125 | ( ) \uparrow \Uparrow 126 | [ ] \downarrow \Downarrow 127 | \{ \} \updownarrow \Updownarrow 128 | \lfloor \rfloor \lceil \rceil 129 | \langle \rangle / \backslash 130 | | \| 131 | $$ 132 | 133 | ### Large Delimiters 134 | 135 | $$ 136 | \rmoustache \lmoustache \rgroup \lgroup 137 | $$ 138 | 139 | ### Math mode accents 140 | 141 | $$ 142 | \hat{a} \acute{a} \bar{a} \dot{a} \breve{a} 143 | \check{a} \grave{a} \vec{a} \ddot{a} \tilde{a} 144 | $$ 145 | 146 | ### Some other constructions 147 | 148 | $$ 149 | \sqrt{abc} \sqrt[n]{abc} \frac{abc}{xyz} \underline{abc} \overline{abc} 150 | $$ 151 | -------------------------------------------------------------------------------- /app/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "EME", 3 | "productName": "EME", 4 | "version": "0.15.1", 5 | "description": "Elegant Markdown Editor", 6 | "author": { 7 | "name": "The EME Authors", 8 | "email": "0x142857@gmail.com" 9 | }, 10 | "homepage": "https://github.com/egoist/eme", 11 | "main": "index.js", 12 | "scripts": { 13 | "test": "echo \"Error: no test specified\" && exit 1" 14 | }, 15 | "license": "MIT", 16 | "dependencies": { 17 | "axios": "^0.18.1", 18 | "conf": "github:egoist-robot/conf", 19 | "electron-context-menu": "^0.4.0", 20 | "electron-window-state": "^3.0.3", 21 | "front-matter": "^4.0.1", 22 | "match-at": "^0.1.0", 23 | "semver-compare": "^1.0.0", 24 | "tildify": "^1.2.0", 25 | "yargs": "^5.0.0" 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /app/resources/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/egoist/eme/c7c2b4fdb0df703473792d4ad7a369d8c0adccaf/app/resources/icon.png -------------------------------------------------------------------------------- /app/vendor/css/print.css: -------------------------------------------------------------------------------- 1 | body { 2 | margin: 0; 3 | -webkit-print-color-adjust: exact; 4 | } 5 | .page-break { 6 | display: block; 7 | page-break-before: always; 8 | } 9 | -------------------------------------------------------------------------------- /app/vendor/css/spinners.css: -------------------------------------------------------------------------------- 1 | /* --- Basics --- */ 2 | 3 | .loading { 4 | display: inline-block; 5 | overflow: hidden; 6 | height: 1.3em; 7 | margin-top: -0.3em; 8 | line-height: 1.5em; 9 | vertical-align: text-bottom; 10 | } 11 | 12 | .loading::after { 13 | display: inline-table; 14 | white-space: pre; 15 | text-align: left; 16 | } 17 | 18 | /* --- Types --- */ 19 | 20 | /* default loading is ellip */ 21 | .loading::after { 22 | content: "\A.\A..\A..."; 23 | animation: spin4 2s steps(4) infinite; 24 | } 25 | 26 | .loading.line::after { 27 | content: "/\A–\A\\\A|"; 28 | text-align: center; 29 | animation: spin4 1s steps(4) infinite; 30 | } 31 | 32 | .loading.line2::after { 33 | content: "╲\A│\A╱\A─"; 34 | text-align: center; 35 | animation: spin4 1s steps(4) infinite; 36 | } 37 | 38 | .loading.plus::after { 39 | content: "┽\A╀\A┾\A╁"; 40 | animation: spin4 1s steps(4) infinite; 41 | } 42 | 43 | .loading.dots::after { 44 | content: "⠋\A⠙\A⠹\A⠸\A⠼\A⠴\A⠦\A⠧\A⠇\A⠏"; 45 | animation: spin10 1s steps(10) infinite; 46 | } 47 | 48 | .loading.dots2::after { 49 | content: "⠋\A⠙\A⠚\A⠞\A⠖\A⠦\A⠴\A⠲\A⠳"; 50 | animation: spin9 1s steps(9) infinite; 51 | } 52 | 53 | .loading.dots3::after { 54 | content: "⋮\A⋰\A⋯\A⋱"; 55 | text-align: center; 56 | animation: spin4 1s steps(4) infinite; 57 | } 58 | 59 | .loading.lifting::after { 60 | content: "꜈꜍\A꜉꜎\A꜊꜏\A꜋꜐\A꜌꜑"; 61 | animation: spin5 .5s steps(5) infinite alternate; 62 | } 63 | 64 | .loading.hamburger::after { 65 | content: "☱\A☲\A☴"; 66 | animation: spin3 .3s steps(3) infinite alternate; 67 | } 68 | 69 | .loading.bar::after { 70 | content: "▏\A▎\A▍\A▌\A▋\A▊\A▉"; 71 | animation: spin7 1s steps(7) infinite alternate; 72 | } 73 | 74 | .loading.bar2::after { 75 | content: "▁\A▂\A▃\A▄\A▅\A▆\A▇\A█"; 76 | animation: spin8 2s steps(8) infinite alternate; 77 | } 78 | 79 | .loading.circle::after { 80 | content: "◴\A◷\A◶\A◵"; 81 | animation: spin4 1s steps(4) infinite; 82 | } 83 | 84 | .loading.open-circle::after { 85 | content: "◜\A◠\A◝\A◞\A◡\A◟"; 86 | animation: spin6 .6s steps(6) infinite; 87 | } 88 | 89 | .loading.arrow::after { 90 | content: "←\A↖\A↑\A↗\A→\A↘\A↓\A↙"; 91 | animation: spin8 1s steps(8) infinite; 92 | } 93 | 94 | .loading.triangle::after { 95 | content: "◢\A◣\A◤\A◥"; 96 | animation: spin4 1s steps(4) infinite; 97 | } 98 | 99 | .loading.triangles::after { 100 | content: "▹▹▹▹▹\A ▸▹▹▹▹\A ▹▸▹▹▹\A ▹▹▸▹▹\A ▹▹▹▸▹\A ▹▹▹▹▸"; 101 | animation: spin6 1s steps(6) infinite; 102 | } 103 | 104 | .loading.beam::after { 105 | content: "\A= \A == \A === \A ====\A ===\A ==\A =\A"; 106 | animation: spin9 1.2s steps(9) infinite; 107 | font-family: monospace; 108 | } 109 | 110 | .loading.bullet::after { 111 | content: " ● \A ● \A ● \A ● \A ●\A ● \A ● \A ● \A ● \A ● "; 112 | animation: spin10 1s steps(10) infinite; 113 | } 114 | 115 | .loading.bullseye::after { 116 | content: "◎◎◎\A◉◎◎\A◎◉◎\A◎◎◉"; 117 | animation: spin4 1s steps(4) infinite; 118 | } 119 | 120 | .loading.rhomb::after { 121 | content: "◇◇◇\A◈◇◇\A◇◈◇\A◇◇◈"; 122 | animation: spin4 1s steps(4) infinite; 123 | } 124 | 125 | .loading.fish::after { 126 | content: ">))'>\A  >))'>\A   >))'>\A    >))'>\A     >))'>\A     <'((<\A    <'((<\A   <'((<\A  <'((<\A <'((<\A"; 127 | animation: spin10 5s steps(10) infinite; 128 | } 129 | 130 | .loading.toggle::after { 131 | content: "⊶\A⊷"; 132 | animation: spin2 1s steps(2) infinite; 133 | } 134 | 135 | .loading.countdown::after { 136 | content: "0\A 1\A 2\A 3\A 4\A 5\A 6\A 7\A 8\A 9"; 137 | animation: spin10 10s steps(10) reverse; 138 | } 139 | 140 | .loading.time::after { 141 | content: "🕐\A🕑\A🕒\A🕓\A🕔\A🕕\A🕖\A🕗\A🕘\A🕙\A🕚\A🕛"; 142 | animation: spin12 3s steps(12) infinite; 143 | width: 1.3em; 144 | } 145 | 146 | .loading.hearts::after { 147 | content: "💛\A💙\A💜\A💚"; 148 | animation: spin4 2s steps(4) infinite; 149 | width: 1.3em; 150 | } 151 | 152 | .loading.earth::after { 153 | content: "🌍\A🌎\A🌏"; 154 | animation: spin3 1s steps(3) infinite; 155 | width: 1.3em; 156 | } 157 | 158 | .loading.moon::after { 159 | content: "🌑\A🌒\A🌓\A🌔\A🌕\A🌖\A🌗\A🌘"; 160 | animation: spin8 2s steps(8) infinite; 161 | width: 1.3em; 162 | } 163 | 164 | .loading.monkey::after { 165 | content: "🙈\A🙉\A🙊"; 166 | animation: spin3 1.5s steps(3) infinite; 167 | width: 1.3em; 168 | } 169 | 170 | .loading.runner::after { 171 | content: "🚶\A🏃"; 172 | animation: spin2 1s steps(2) infinite; 173 | width: 1.3em; 174 | } 175 | 176 | .loading.words::after { 177 | content: "Loading\A Still loading\A Mostly done\A A bit more \A Almost done\A Ready-ish"; 178 | animation: spin6 12s steps(6) infinite; 179 | } 180 | 181 | /* --- Animations --- */ 182 | 183 | @keyframes spin1 { to { transform: translateY( -1.5em); } } 184 | @keyframes spin2 { to { transform: translateY( -3.0em); } } 185 | @keyframes spin3 { to { transform: translateY( -4.5em); } } 186 | @keyframes spin4 { to { transform: translateY( -6.0em); } } 187 | @keyframes spin5 { to { transform: translateY( -7.5em); } } 188 | @keyframes spin6 { to { transform: translateY( -9.0em); } } 189 | @keyframes spin7 { to { transform: translateY(-10.5em); } } 190 | @keyframes spin8 { to { transform: translateY(-12.0em); } } 191 | @keyframes spin9 { to { transform: translateY(-13.5em); } } 192 | @keyframes spin10 { to { transform: translateY(-15.0em); } } 193 | @keyframes spin11 { to { transform: translateY(-16.5em); } } 194 | @keyframes spin12 { to { transform: translateY(-18.0em); } } 195 | -------------------------------------------------------------------------------- /app/vendor/katex/fonts/KaTeX_AMS-Regular.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/egoist/eme/c7c2b4fdb0df703473792d4ad7a369d8c0adccaf/app/vendor/katex/fonts/KaTeX_AMS-Regular.eot -------------------------------------------------------------------------------- /app/vendor/katex/fonts/KaTeX_AMS-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/egoist/eme/c7c2b4fdb0df703473792d4ad7a369d8c0adccaf/app/vendor/katex/fonts/KaTeX_AMS-Regular.ttf -------------------------------------------------------------------------------- /app/vendor/katex/fonts/KaTeX_AMS-Regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/egoist/eme/c7c2b4fdb0df703473792d4ad7a369d8c0adccaf/app/vendor/katex/fonts/KaTeX_AMS-Regular.woff -------------------------------------------------------------------------------- /app/vendor/katex/fonts/KaTeX_AMS-Regular.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/egoist/eme/c7c2b4fdb0df703473792d4ad7a369d8c0adccaf/app/vendor/katex/fonts/KaTeX_AMS-Regular.woff2 -------------------------------------------------------------------------------- /app/vendor/katex/fonts/KaTeX_Caligraphic-Bold.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/egoist/eme/c7c2b4fdb0df703473792d4ad7a369d8c0adccaf/app/vendor/katex/fonts/KaTeX_Caligraphic-Bold.eot -------------------------------------------------------------------------------- /app/vendor/katex/fonts/KaTeX_Caligraphic-Bold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/egoist/eme/c7c2b4fdb0df703473792d4ad7a369d8c0adccaf/app/vendor/katex/fonts/KaTeX_Caligraphic-Bold.ttf -------------------------------------------------------------------------------- /app/vendor/katex/fonts/KaTeX_Caligraphic-Bold.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/egoist/eme/c7c2b4fdb0df703473792d4ad7a369d8c0adccaf/app/vendor/katex/fonts/KaTeX_Caligraphic-Bold.woff -------------------------------------------------------------------------------- /app/vendor/katex/fonts/KaTeX_Caligraphic-Bold.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/egoist/eme/c7c2b4fdb0df703473792d4ad7a369d8c0adccaf/app/vendor/katex/fonts/KaTeX_Caligraphic-Bold.woff2 -------------------------------------------------------------------------------- /app/vendor/katex/fonts/KaTeX_Caligraphic-Regular.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/egoist/eme/c7c2b4fdb0df703473792d4ad7a369d8c0adccaf/app/vendor/katex/fonts/KaTeX_Caligraphic-Regular.eot -------------------------------------------------------------------------------- /app/vendor/katex/fonts/KaTeX_Caligraphic-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/egoist/eme/c7c2b4fdb0df703473792d4ad7a369d8c0adccaf/app/vendor/katex/fonts/KaTeX_Caligraphic-Regular.ttf -------------------------------------------------------------------------------- /app/vendor/katex/fonts/KaTeX_Caligraphic-Regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/egoist/eme/c7c2b4fdb0df703473792d4ad7a369d8c0adccaf/app/vendor/katex/fonts/KaTeX_Caligraphic-Regular.woff -------------------------------------------------------------------------------- /app/vendor/katex/fonts/KaTeX_Caligraphic-Regular.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/egoist/eme/c7c2b4fdb0df703473792d4ad7a369d8c0adccaf/app/vendor/katex/fonts/KaTeX_Caligraphic-Regular.woff2 -------------------------------------------------------------------------------- /app/vendor/katex/fonts/KaTeX_Fraktur-Bold.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/egoist/eme/c7c2b4fdb0df703473792d4ad7a369d8c0adccaf/app/vendor/katex/fonts/KaTeX_Fraktur-Bold.eot -------------------------------------------------------------------------------- /app/vendor/katex/fonts/KaTeX_Fraktur-Bold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/egoist/eme/c7c2b4fdb0df703473792d4ad7a369d8c0adccaf/app/vendor/katex/fonts/KaTeX_Fraktur-Bold.ttf -------------------------------------------------------------------------------- /app/vendor/katex/fonts/KaTeX_Fraktur-Bold.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/egoist/eme/c7c2b4fdb0df703473792d4ad7a369d8c0adccaf/app/vendor/katex/fonts/KaTeX_Fraktur-Bold.woff -------------------------------------------------------------------------------- /app/vendor/katex/fonts/KaTeX_Fraktur-Bold.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/egoist/eme/c7c2b4fdb0df703473792d4ad7a369d8c0adccaf/app/vendor/katex/fonts/KaTeX_Fraktur-Bold.woff2 -------------------------------------------------------------------------------- /app/vendor/katex/fonts/KaTeX_Fraktur-Regular.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/egoist/eme/c7c2b4fdb0df703473792d4ad7a369d8c0adccaf/app/vendor/katex/fonts/KaTeX_Fraktur-Regular.eot -------------------------------------------------------------------------------- /app/vendor/katex/fonts/KaTeX_Fraktur-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/egoist/eme/c7c2b4fdb0df703473792d4ad7a369d8c0adccaf/app/vendor/katex/fonts/KaTeX_Fraktur-Regular.ttf -------------------------------------------------------------------------------- /app/vendor/katex/fonts/KaTeX_Fraktur-Regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/egoist/eme/c7c2b4fdb0df703473792d4ad7a369d8c0adccaf/app/vendor/katex/fonts/KaTeX_Fraktur-Regular.woff -------------------------------------------------------------------------------- /app/vendor/katex/fonts/KaTeX_Fraktur-Regular.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/egoist/eme/c7c2b4fdb0df703473792d4ad7a369d8c0adccaf/app/vendor/katex/fonts/KaTeX_Fraktur-Regular.woff2 -------------------------------------------------------------------------------- /app/vendor/katex/fonts/KaTeX_Main-Bold.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/egoist/eme/c7c2b4fdb0df703473792d4ad7a369d8c0adccaf/app/vendor/katex/fonts/KaTeX_Main-Bold.eot -------------------------------------------------------------------------------- /app/vendor/katex/fonts/KaTeX_Main-Bold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/egoist/eme/c7c2b4fdb0df703473792d4ad7a369d8c0adccaf/app/vendor/katex/fonts/KaTeX_Main-Bold.ttf -------------------------------------------------------------------------------- /app/vendor/katex/fonts/KaTeX_Main-Bold.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/egoist/eme/c7c2b4fdb0df703473792d4ad7a369d8c0adccaf/app/vendor/katex/fonts/KaTeX_Main-Bold.woff -------------------------------------------------------------------------------- /app/vendor/katex/fonts/KaTeX_Main-Bold.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/egoist/eme/c7c2b4fdb0df703473792d4ad7a369d8c0adccaf/app/vendor/katex/fonts/KaTeX_Main-Bold.woff2 -------------------------------------------------------------------------------- /app/vendor/katex/fonts/KaTeX_Main-Italic.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/egoist/eme/c7c2b4fdb0df703473792d4ad7a369d8c0adccaf/app/vendor/katex/fonts/KaTeX_Main-Italic.eot -------------------------------------------------------------------------------- /app/vendor/katex/fonts/KaTeX_Main-Italic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/egoist/eme/c7c2b4fdb0df703473792d4ad7a369d8c0adccaf/app/vendor/katex/fonts/KaTeX_Main-Italic.ttf -------------------------------------------------------------------------------- /app/vendor/katex/fonts/KaTeX_Main-Italic.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/egoist/eme/c7c2b4fdb0df703473792d4ad7a369d8c0adccaf/app/vendor/katex/fonts/KaTeX_Main-Italic.woff -------------------------------------------------------------------------------- /app/vendor/katex/fonts/KaTeX_Main-Italic.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/egoist/eme/c7c2b4fdb0df703473792d4ad7a369d8c0adccaf/app/vendor/katex/fonts/KaTeX_Main-Italic.woff2 -------------------------------------------------------------------------------- /app/vendor/katex/fonts/KaTeX_Main-Regular.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/egoist/eme/c7c2b4fdb0df703473792d4ad7a369d8c0adccaf/app/vendor/katex/fonts/KaTeX_Main-Regular.eot -------------------------------------------------------------------------------- /app/vendor/katex/fonts/KaTeX_Main-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/egoist/eme/c7c2b4fdb0df703473792d4ad7a369d8c0adccaf/app/vendor/katex/fonts/KaTeX_Main-Regular.ttf -------------------------------------------------------------------------------- /app/vendor/katex/fonts/KaTeX_Main-Regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/egoist/eme/c7c2b4fdb0df703473792d4ad7a369d8c0adccaf/app/vendor/katex/fonts/KaTeX_Main-Regular.woff -------------------------------------------------------------------------------- /app/vendor/katex/fonts/KaTeX_Main-Regular.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/egoist/eme/c7c2b4fdb0df703473792d4ad7a369d8c0adccaf/app/vendor/katex/fonts/KaTeX_Main-Regular.woff2 -------------------------------------------------------------------------------- /app/vendor/katex/fonts/KaTeX_Math-BoldItalic.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/egoist/eme/c7c2b4fdb0df703473792d4ad7a369d8c0adccaf/app/vendor/katex/fonts/KaTeX_Math-BoldItalic.eot -------------------------------------------------------------------------------- /app/vendor/katex/fonts/KaTeX_Math-BoldItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/egoist/eme/c7c2b4fdb0df703473792d4ad7a369d8c0adccaf/app/vendor/katex/fonts/KaTeX_Math-BoldItalic.ttf -------------------------------------------------------------------------------- /app/vendor/katex/fonts/KaTeX_Math-BoldItalic.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/egoist/eme/c7c2b4fdb0df703473792d4ad7a369d8c0adccaf/app/vendor/katex/fonts/KaTeX_Math-BoldItalic.woff -------------------------------------------------------------------------------- /app/vendor/katex/fonts/KaTeX_Math-BoldItalic.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/egoist/eme/c7c2b4fdb0df703473792d4ad7a369d8c0adccaf/app/vendor/katex/fonts/KaTeX_Math-BoldItalic.woff2 -------------------------------------------------------------------------------- /app/vendor/katex/fonts/KaTeX_Math-Italic.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/egoist/eme/c7c2b4fdb0df703473792d4ad7a369d8c0adccaf/app/vendor/katex/fonts/KaTeX_Math-Italic.eot -------------------------------------------------------------------------------- /app/vendor/katex/fonts/KaTeX_Math-Italic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/egoist/eme/c7c2b4fdb0df703473792d4ad7a369d8c0adccaf/app/vendor/katex/fonts/KaTeX_Math-Italic.ttf -------------------------------------------------------------------------------- /app/vendor/katex/fonts/KaTeX_Math-Italic.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/egoist/eme/c7c2b4fdb0df703473792d4ad7a369d8c0adccaf/app/vendor/katex/fonts/KaTeX_Math-Italic.woff -------------------------------------------------------------------------------- /app/vendor/katex/fonts/KaTeX_Math-Italic.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/egoist/eme/c7c2b4fdb0df703473792d4ad7a369d8c0adccaf/app/vendor/katex/fonts/KaTeX_Math-Italic.woff2 -------------------------------------------------------------------------------- /app/vendor/katex/fonts/KaTeX_Math-Regular.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/egoist/eme/c7c2b4fdb0df703473792d4ad7a369d8c0adccaf/app/vendor/katex/fonts/KaTeX_Math-Regular.eot -------------------------------------------------------------------------------- /app/vendor/katex/fonts/KaTeX_Math-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/egoist/eme/c7c2b4fdb0df703473792d4ad7a369d8c0adccaf/app/vendor/katex/fonts/KaTeX_Math-Regular.ttf -------------------------------------------------------------------------------- /app/vendor/katex/fonts/KaTeX_Math-Regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/egoist/eme/c7c2b4fdb0df703473792d4ad7a369d8c0adccaf/app/vendor/katex/fonts/KaTeX_Math-Regular.woff -------------------------------------------------------------------------------- /app/vendor/katex/fonts/KaTeX_Math-Regular.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/egoist/eme/c7c2b4fdb0df703473792d4ad7a369d8c0adccaf/app/vendor/katex/fonts/KaTeX_Math-Regular.woff2 -------------------------------------------------------------------------------- /app/vendor/katex/fonts/KaTeX_SansSerif-Bold.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/egoist/eme/c7c2b4fdb0df703473792d4ad7a369d8c0adccaf/app/vendor/katex/fonts/KaTeX_SansSerif-Bold.eot -------------------------------------------------------------------------------- /app/vendor/katex/fonts/KaTeX_SansSerif-Bold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/egoist/eme/c7c2b4fdb0df703473792d4ad7a369d8c0adccaf/app/vendor/katex/fonts/KaTeX_SansSerif-Bold.ttf -------------------------------------------------------------------------------- /app/vendor/katex/fonts/KaTeX_SansSerif-Bold.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/egoist/eme/c7c2b4fdb0df703473792d4ad7a369d8c0adccaf/app/vendor/katex/fonts/KaTeX_SansSerif-Bold.woff -------------------------------------------------------------------------------- /app/vendor/katex/fonts/KaTeX_SansSerif-Bold.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/egoist/eme/c7c2b4fdb0df703473792d4ad7a369d8c0adccaf/app/vendor/katex/fonts/KaTeX_SansSerif-Bold.woff2 -------------------------------------------------------------------------------- /app/vendor/katex/fonts/KaTeX_SansSerif-Italic.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/egoist/eme/c7c2b4fdb0df703473792d4ad7a369d8c0adccaf/app/vendor/katex/fonts/KaTeX_SansSerif-Italic.eot -------------------------------------------------------------------------------- /app/vendor/katex/fonts/KaTeX_SansSerif-Italic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/egoist/eme/c7c2b4fdb0df703473792d4ad7a369d8c0adccaf/app/vendor/katex/fonts/KaTeX_SansSerif-Italic.ttf -------------------------------------------------------------------------------- /app/vendor/katex/fonts/KaTeX_SansSerif-Italic.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/egoist/eme/c7c2b4fdb0df703473792d4ad7a369d8c0adccaf/app/vendor/katex/fonts/KaTeX_SansSerif-Italic.woff -------------------------------------------------------------------------------- /app/vendor/katex/fonts/KaTeX_SansSerif-Italic.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/egoist/eme/c7c2b4fdb0df703473792d4ad7a369d8c0adccaf/app/vendor/katex/fonts/KaTeX_SansSerif-Italic.woff2 -------------------------------------------------------------------------------- /app/vendor/katex/fonts/KaTeX_SansSerif-Regular.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/egoist/eme/c7c2b4fdb0df703473792d4ad7a369d8c0adccaf/app/vendor/katex/fonts/KaTeX_SansSerif-Regular.eot -------------------------------------------------------------------------------- /app/vendor/katex/fonts/KaTeX_SansSerif-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/egoist/eme/c7c2b4fdb0df703473792d4ad7a369d8c0adccaf/app/vendor/katex/fonts/KaTeX_SansSerif-Regular.ttf -------------------------------------------------------------------------------- /app/vendor/katex/fonts/KaTeX_SansSerif-Regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/egoist/eme/c7c2b4fdb0df703473792d4ad7a369d8c0adccaf/app/vendor/katex/fonts/KaTeX_SansSerif-Regular.woff -------------------------------------------------------------------------------- /app/vendor/katex/fonts/KaTeX_SansSerif-Regular.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/egoist/eme/c7c2b4fdb0df703473792d4ad7a369d8c0adccaf/app/vendor/katex/fonts/KaTeX_SansSerif-Regular.woff2 -------------------------------------------------------------------------------- /app/vendor/katex/fonts/KaTeX_Script-Regular.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/egoist/eme/c7c2b4fdb0df703473792d4ad7a369d8c0adccaf/app/vendor/katex/fonts/KaTeX_Script-Regular.eot -------------------------------------------------------------------------------- /app/vendor/katex/fonts/KaTeX_Script-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/egoist/eme/c7c2b4fdb0df703473792d4ad7a369d8c0adccaf/app/vendor/katex/fonts/KaTeX_Script-Regular.ttf -------------------------------------------------------------------------------- /app/vendor/katex/fonts/KaTeX_Script-Regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/egoist/eme/c7c2b4fdb0df703473792d4ad7a369d8c0adccaf/app/vendor/katex/fonts/KaTeX_Script-Regular.woff -------------------------------------------------------------------------------- /app/vendor/katex/fonts/KaTeX_Script-Regular.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/egoist/eme/c7c2b4fdb0df703473792d4ad7a369d8c0adccaf/app/vendor/katex/fonts/KaTeX_Script-Regular.woff2 -------------------------------------------------------------------------------- /app/vendor/katex/fonts/KaTeX_Size1-Regular.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/egoist/eme/c7c2b4fdb0df703473792d4ad7a369d8c0adccaf/app/vendor/katex/fonts/KaTeX_Size1-Regular.eot -------------------------------------------------------------------------------- /app/vendor/katex/fonts/KaTeX_Size1-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/egoist/eme/c7c2b4fdb0df703473792d4ad7a369d8c0adccaf/app/vendor/katex/fonts/KaTeX_Size1-Regular.ttf -------------------------------------------------------------------------------- /app/vendor/katex/fonts/KaTeX_Size1-Regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/egoist/eme/c7c2b4fdb0df703473792d4ad7a369d8c0adccaf/app/vendor/katex/fonts/KaTeX_Size1-Regular.woff -------------------------------------------------------------------------------- /app/vendor/katex/fonts/KaTeX_Size1-Regular.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/egoist/eme/c7c2b4fdb0df703473792d4ad7a369d8c0adccaf/app/vendor/katex/fonts/KaTeX_Size1-Regular.woff2 -------------------------------------------------------------------------------- /app/vendor/katex/fonts/KaTeX_Size2-Regular.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/egoist/eme/c7c2b4fdb0df703473792d4ad7a369d8c0adccaf/app/vendor/katex/fonts/KaTeX_Size2-Regular.eot -------------------------------------------------------------------------------- /app/vendor/katex/fonts/KaTeX_Size2-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/egoist/eme/c7c2b4fdb0df703473792d4ad7a369d8c0adccaf/app/vendor/katex/fonts/KaTeX_Size2-Regular.ttf -------------------------------------------------------------------------------- /app/vendor/katex/fonts/KaTeX_Size2-Regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/egoist/eme/c7c2b4fdb0df703473792d4ad7a369d8c0adccaf/app/vendor/katex/fonts/KaTeX_Size2-Regular.woff -------------------------------------------------------------------------------- /app/vendor/katex/fonts/KaTeX_Size2-Regular.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/egoist/eme/c7c2b4fdb0df703473792d4ad7a369d8c0adccaf/app/vendor/katex/fonts/KaTeX_Size2-Regular.woff2 -------------------------------------------------------------------------------- /app/vendor/katex/fonts/KaTeX_Size3-Regular.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/egoist/eme/c7c2b4fdb0df703473792d4ad7a369d8c0adccaf/app/vendor/katex/fonts/KaTeX_Size3-Regular.eot -------------------------------------------------------------------------------- /app/vendor/katex/fonts/KaTeX_Size3-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/egoist/eme/c7c2b4fdb0df703473792d4ad7a369d8c0adccaf/app/vendor/katex/fonts/KaTeX_Size3-Regular.ttf -------------------------------------------------------------------------------- /app/vendor/katex/fonts/KaTeX_Size3-Regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/egoist/eme/c7c2b4fdb0df703473792d4ad7a369d8c0adccaf/app/vendor/katex/fonts/KaTeX_Size3-Regular.woff -------------------------------------------------------------------------------- /app/vendor/katex/fonts/KaTeX_Size3-Regular.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/egoist/eme/c7c2b4fdb0df703473792d4ad7a369d8c0adccaf/app/vendor/katex/fonts/KaTeX_Size3-Regular.woff2 -------------------------------------------------------------------------------- /app/vendor/katex/fonts/KaTeX_Size4-Regular.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/egoist/eme/c7c2b4fdb0df703473792d4ad7a369d8c0adccaf/app/vendor/katex/fonts/KaTeX_Size4-Regular.eot -------------------------------------------------------------------------------- /app/vendor/katex/fonts/KaTeX_Size4-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/egoist/eme/c7c2b4fdb0df703473792d4ad7a369d8c0adccaf/app/vendor/katex/fonts/KaTeX_Size4-Regular.ttf -------------------------------------------------------------------------------- /app/vendor/katex/fonts/KaTeX_Size4-Regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/egoist/eme/c7c2b4fdb0df703473792d4ad7a369d8c0adccaf/app/vendor/katex/fonts/KaTeX_Size4-Regular.woff -------------------------------------------------------------------------------- /app/vendor/katex/fonts/KaTeX_Size4-Regular.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/egoist/eme/c7c2b4fdb0df703473792d4ad7a369d8c0adccaf/app/vendor/katex/fonts/KaTeX_Size4-Regular.woff2 -------------------------------------------------------------------------------- /app/vendor/katex/fonts/KaTeX_Typewriter-Regular.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/egoist/eme/c7c2b4fdb0df703473792d4ad7a369d8c0adccaf/app/vendor/katex/fonts/KaTeX_Typewriter-Regular.eot -------------------------------------------------------------------------------- /app/vendor/katex/fonts/KaTeX_Typewriter-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/egoist/eme/c7c2b4fdb0df703473792d4ad7a369d8c0adccaf/app/vendor/katex/fonts/KaTeX_Typewriter-Regular.ttf -------------------------------------------------------------------------------- /app/vendor/katex/fonts/KaTeX_Typewriter-Regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/egoist/eme/c7c2b4fdb0df703473792d4ad7a369d8c0adccaf/app/vendor/katex/fonts/KaTeX_Typewriter-Regular.woff -------------------------------------------------------------------------------- /app/vendor/katex/fonts/KaTeX_Typewriter-Regular.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/egoist/eme/c7c2b4fdb0df703473792d4ad7a369d8c0adccaf/app/vendor/katex/fonts/KaTeX_Typewriter-Regular.woff2 -------------------------------------------------------------------------------- /app/vendor/katex/katex.js: -------------------------------------------------------------------------------- 1 | /* eslint no-console:0 */ 2 | /** 3 | * This is the main entry point for KaTeX. Here, we expose functions for 4 | * rendering expressions either to DOM nodes or to markup strings. 5 | * 6 | * We also expose the ParseError class to check if errors thrown from KaTeX are 7 | * errors in the expression, or errors in javascript handling. 8 | */ 9 | 10 | var ParseError = require("./src/ParseError"); 11 | var Settings = require("./src/Settings"); 12 | 13 | var buildTree = require("./src/buildTree"); 14 | var parseTree = require("./src/parseTree"); 15 | var utils = require("./src/utils"); 16 | 17 | /** 18 | * Parse and build an expression, and place that expression in the DOM node 19 | * given. 20 | */ 21 | var render = function(expression, baseNode, options) { 22 | utils.clearNode(baseNode); 23 | 24 | var settings = new Settings(options); 25 | 26 | var tree = parseTree(expression, settings); 27 | var node = buildTree(tree, expression, settings).toNode(); 28 | 29 | baseNode.appendChild(node); 30 | }; 31 | 32 | // KaTeX's styles don't work properly in quirks mode. Print out an error, and 33 | // disable rendering. 34 | if (typeof document !== "undefined") { 35 | if (document.compatMode !== "CSS1Compat") { 36 | typeof console !== "undefined" && console.warn( 37 | "Warning: KaTeX doesn't work in quirks mode. Make sure your " + 38 | "website has a suitable doctype."); 39 | 40 | render = function() { 41 | throw new ParseError("KaTeX doesn't work in quirks mode."); 42 | }; 43 | } 44 | } 45 | 46 | /** 47 | * Parse and build an expression, and return the markup for that. 48 | */ 49 | var renderToString = function(expression, options) { 50 | var settings = new Settings(options); 51 | 52 | var tree = parseTree(expression, settings); 53 | return buildTree(tree, expression, settings).toMarkup(); 54 | }; 55 | 56 | /** 57 | * Parse an expression and return the parse tree. 58 | */ 59 | var generateParseTree = function(expression, options) { 60 | var settings = new Settings(options); 61 | return parseTree(expression, settings); 62 | }; 63 | 64 | module.exports = { 65 | render: render, 66 | renderToString: renderToString, 67 | /** 68 | * NOTE: This method is not currently recommended for public use. 69 | * The internal tree representation is unstable and is very likely 70 | * to change. Use at your own risk. 71 | */ 72 | __parse: generateParseTree, 73 | ParseError: ParseError, 74 | }; 75 | -------------------------------------------------------------------------------- /app/vendor/katex/src/Lexer.js: -------------------------------------------------------------------------------- 1 | /** 2 | * The Lexer class handles tokenizing the input in various ways. Since our 3 | * parser expects us to be able to backtrack, the lexer allows lexing from any 4 | * given starting point. 5 | * 6 | * Its main exposed function is the `lex` function, which takes a position to 7 | * lex from and a type of token to lex. It defers to the appropriate `_innerLex` 8 | * function. 9 | * 10 | * The various `_innerLex` functions perform the actual lexing of different 11 | * kinds. 12 | */ 13 | 14 | var matchAt = require("match-at"); 15 | 16 | var ParseError = require("./ParseError"); 17 | 18 | // The main lexer class 19 | function Lexer(input) { 20 | this._input = input; 21 | } 22 | 23 | // The resulting token returned from `lex`. 24 | function Token(text, data, position) { 25 | this.text = text; 26 | this.data = data; 27 | this.position = position; 28 | } 29 | 30 | /* The following tokenRegex 31 | * - matches typical whitespace (but not NBSP etc.) using its first group 32 | * - matches symbol combinations which result in a single output character 33 | * - does not match any control character \x00-\x1f except whitespace 34 | * - does not match a bare backslash 35 | * - matches any ASCII character except those just mentioned 36 | * - does not match the BMP private use area \uE000-\uF8FF 37 | * - does not match bare surrogate code units 38 | * - matches any BMP character except for those just described 39 | * - matches any valid Unicode surrogate pair 40 | * - matches a backslash followed by one or more letters 41 | * - matches a backslash followed by any BMP character, including newline 42 | * Just because the Lexer matches something doesn't mean it's valid input: 43 | * If there is no matching function or symbol definition, the Parser will 44 | * still reject the input. 45 | */ 46 | var tokenRegex = new RegExp( 47 | "([ \r\n\t]+)|(" + // whitespace 48 | "---?" + // special combinations 49 | "|[!-\\[\\]-\u2027\u202A-\uD7FF\uF900-\uFFFF]" + // single codepoint 50 | "|[\uD800-\uDBFF][\uDC00-\uDFFF]" + // surrogate pair 51 | "|\\\\(?:[a-zA-Z]+|[^\uD800-\uDFFF])" + // function name 52 | ")" 53 | ); 54 | 55 | var whitespaceRegex = /\s*/; 56 | 57 | /** 58 | * This function lexes a single normal token. It takes a position and 59 | * whether it should completely ignore whitespace or not. 60 | */ 61 | Lexer.prototype._innerLex = function(pos, ignoreWhitespace) { 62 | var input = this._input; 63 | if (pos === input.length) { 64 | return new Token("EOF", null, pos); 65 | } 66 | var match = matchAt(tokenRegex, input, pos); 67 | if (match === null) { 68 | throw new ParseError( 69 | "Unexpected character: '" + input[pos] + "'", 70 | this, pos); 71 | } else if (match[2]) { // matched non-whitespace 72 | return new Token(match[2], null, pos + match[2].length); 73 | } else if (ignoreWhitespace) { 74 | return this._innerLex(pos + match[1].length, true); 75 | } else { // concatenate whitespace to a single space 76 | return new Token(" ", null, pos + match[1].length); 77 | } 78 | }; 79 | 80 | // A regex to match a CSS color (like #ffffff or BlueViolet) 81 | var cssColor = /#[a-z0-9]+|[a-z]+/i; 82 | 83 | /** 84 | * This function lexes a CSS color. 85 | */ 86 | Lexer.prototype._innerLexColor = function(pos) { 87 | var input = this._input; 88 | 89 | // Ignore whitespace 90 | var whitespace = matchAt(whitespaceRegex, input, pos)[0]; 91 | pos += whitespace.length; 92 | 93 | var match; 94 | if ((match = matchAt(cssColor, input, pos))) { 95 | // If we look like a color, return a color 96 | return new Token(match[0], null, pos + match[0].length); 97 | } else { 98 | throw new ParseError("Invalid color", this, pos); 99 | } 100 | }; 101 | 102 | // A regex to match a dimension. Dimensions look like 103 | // "1.2em" or ".4pt" or "1 ex" 104 | var sizeRegex = /(-?)\s*(\d+(?:\.\d*)?|\.\d+)\s*([a-z]{2})/; 105 | 106 | /** 107 | * This function lexes a dimension. 108 | */ 109 | Lexer.prototype._innerLexSize = function(pos) { 110 | var input = this._input; 111 | 112 | // Ignore whitespace 113 | var whitespace = matchAt(whitespaceRegex, input, pos)[0]; 114 | pos += whitespace.length; 115 | 116 | var match; 117 | if ((match = matchAt(sizeRegex, input, pos))) { 118 | var unit = match[3]; 119 | // We only currently handle "em" and "ex" units 120 | if (unit !== "em" && unit !== "ex") { 121 | throw new ParseError("Invalid unit: '" + unit + "'", this, pos); 122 | } 123 | return new Token(match[0], { 124 | number: +(match[1] + match[2]), 125 | unit: unit, 126 | }, pos + match[0].length); 127 | } 128 | 129 | throw new ParseError("Invalid size", this, pos); 130 | }; 131 | 132 | /** 133 | * This function lexes a string of whitespace. 134 | */ 135 | Lexer.prototype._innerLexWhitespace = function(pos) { 136 | var input = this._input; 137 | 138 | var whitespace = matchAt(whitespaceRegex, input, pos)[0]; 139 | pos += whitespace.length; 140 | 141 | return new Token(whitespace[0], null, pos); 142 | }; 143 | 144 | /** 145 | * This function lexes a single token starting at `pos` and of the given mode. 146 | * Based on the mode, we defer to one of the `_innerLex` functions. 147 | */ 148 | Lexer.prototype.lex = function(pos, mode) { 149 | if (mode === "math") { 150 | return this._innerLex(pos, true); 151 | } else if (mode === "text") { 152 | return this._innerLex(pos, false); 153 | } else if (mode === "color") { 154 | return this._innerLexColor(pos); 155 | } else if (mode === "size") { 156 | return this._innerLexSize(pos); 157 | } else if (mode === "whitespace") { 158 | return this._innerLexWhitespace(pos); 159 | } 160 | }; 161 | 162 | module.exports = Lexer; 163 | -------------------------------------------------------------------------------- /app/vendor/katex/src/Options.js: -------------------------------------------------------------------------------- 1 | /** 2 | * This file contains information about the options that the Parser carries 3 | * around with it while parsing. Data is held in an `Options` object, and when 4 | * recursing, a new `Options` object can be created with the `.with*` and 5 | * `.reset` functions. 6 | */ 7 | 8 | /** 9 | * This is the main options class. It contains the style, size, color, and font 10 | * of the current parse level. It also contains the style and size of the parent 11 | * parse level, so size changes can be handled efficiently. 12 | * 13 | * Each of the `.with*` and `.reset` functions passes its current style and size 14 | * as the parentStyle and parentSize of the new options class, so parent 15 | * handling is taken care of automatically. 16 | */ 17 | function Options(data) { 18 | this.style = data.style; 19 | this.color = data.color; 20 | this.size = data.size; 21 | this.phantom = data.phantom; 22 | this.font = data.font; 23 | 24 | if (data.parentStyle === undefined) { 25 | this.parentStyle = data.style; 26 | } else { 27 | this.parentStyle = data.parentStyle; 28 | } 29 | 30 | if (data.parentSize === undefined) { 31 | this.parentSize = data.size; 32 | } else { 33 | this.parentSize = data.parentSize; 34 | } 35 | } 36 | 37 | /** 38 | * Returns a new options object with the same properties as "this". Properties 39 | * from "extension" will be copied to the new options object. 40 | */ 41 | Options.prototype.extend = function(extension) { 42 | var data = { 43 | style: this.style, 44 | size: this.size, 45 | color: this.color, 46 | parentStyle: this.style, 47 | parentSize: this.size, 48 | phantom: this.phantom, 49 | font: this.font, 50 | }; 51 | 52 | for (var key in extension) { 53 | if (extension.hasOwnProperty(key)) { 54 | data[key] = extension[key]; 55 | } 56 | } 57 | 58 | return new Options(data); 59 | }; 60 | 61 | /** 62 | * Create a new options object with the given style. 63 | */ 64 | Options.prototype.withStyle = function(style) { 65 | return this.extend({ 66 | style: style, 67 | }); 68 | }; 69 | 70 | /** 71 | * Create a new options object with the given size. 72 | */ 73 | Options.prototype.withSize = function(size) { 74 | return this.extend({ 75 | size: size, 76 | }); 77 | }; 78 | 79 | /** 80 | * Create a new options object with the given color. 81 | */ 82 | Options.prototype.withColor = function(color) { 83 | return this.extend({ 84 | color: color, 85 | }); 86 | }; 87 | 88 | /** 89 | * Create a new options object with "phantom" set to true. 90 | */ 91 | Options.prototype.withPhantom = function() { 92 | return this.extend({ 93 | phantom: true, 94 | }); 95 | }; 96 | 97 | /** 98 | * Create a new options objects with the give font. 99 | */ 100 | Options.prototype.withFont = function(font) { 101 | return this.extend({ 102 | font: font, 103 | }); 104 | }; 105 | 106 | /** 107 | * Create a new options object with the same style, size, and color. This is 108 | * used so that parent style and size changes are handled correctly. 109 | */ 110 | Options.prototype.reset = function() { 111 | return this.extend({}); 112 | }; 113 | 114 | /** 115 | * A map of color names to CSS colors. 116 | * TODO(emily): Remove this when we have real macros 117 | */ 118 | var colorMap = { 119 | "katex-blue": "#6495ed", 120 | "katex-orange": "#ffa500", 121 | "katex-pink": "#ff00af", 122 | "katex-red": "#df0030", 123 | "katex-green": "#28ae7b", 124 | "katex-gray": "gray", 125 | "katex-purple": "#9d38bd", 126 | "katex-blueA": "#c7e9f1", 127 | "katex-blueB": "#9cdceb", 128 | "katex-blueC": "#58c4dd", 129 | "katex-blueD": "#29abca", 130 | "katex-blueE": "#1c758a", 131 | "katex-tealA": "#acead7", 132 | "katex-tealB": "#76ddc0", 133 | "katex-tealC": "#5cd0b3", 134 | "katex-tealD": "#55c1a7", 135 | "katex-tealE": "#49a88f", 136 | "katex-greenA": "#c9e2ae", 137 | "katex-greenB": "#a6cf8c", 138 | "katex-greenC": "#83c167", 139 | "katex-greenD": "#77b05d", 140 | "katex-greenE": "#699c52", 141 | "katex-goldA": "#f7c797", 142 | "katex-goldB": "#f9b775", 143 | "katex-goldC": "#f0ac5f", 144 | "katex-goldD": "#e1a158", 145 | "katex-goldE": "#c78d46", 146 | "katex-redA": "#f7a1a3", 147 | "katex-redB": "#ff8080", 148 | "katex-redC": "#fc6255", 149 | "katex-redD": "#e65a4c", 150 | "katex-redE": "#cf5044", 151 | "katex-maroonA": "#ecabc1", 152 | "katex-maroonB": "#ec92ab", 153 | "katex-maroonC": "#c55f73", 154 | "katex-maroonD": "#a24d61", 155 | "katex-maroonE": "#94424f", 156 | "katex-purpleA": "#caa3e8", 157 | "katex-purpleB": "#b189c6", 158 | "katex-purpleC": "#9a72ac", 159 | "katex-purpleD": "#715582", 160 | "katex-purpleE": "#644172", 161 | "katex-mintA": "#f5f9e8", 162 | "katex-mintB": "#edf2df", 163 | "katex-mintC": "#e0e5cc", 164 | "katex-grayA": "#fdfdfd", 165 | "katex-grayB": "#f7f7f7", 166 | "katex-grayC": "#eeeeee", 167 | "katex-grayD": "#dddddd", 168 | "katex-grayE": "#cccccc", 169 | "katex-grayF": "#aaaaaa", 170 | "katex-grayG": "#999999", 171 | "katex-grayH": "#555555", 172 | "katex-grayI": "#333333", 173 | "katex-kaBlue": "#314453", 174 | "katex-kaGreen": "#639b24", 175 | }; 176 | 177 | /** 178 | * Gets the CSS color of the current options object, accounting for the 179 | * `colorMap`. 180 | */ 181 | Options.prototype.getColor = function() { 182 | if (this.phantom) { 183 | return "transparent"; 184 | } else { 185 | return colorMap[this.color] || this.color; 186 | } 187 | }; 188 | 189 | module.exports = Options; 190 | -------------------------------------------------------------------------------- /app/vendor/katex/src/ParseError.js: -------------------------------------------------------------------------------- 1 | /** 2 | * This is the ParseError class, which is the main error thrown by KaTeX 3 | * functions when something has gone wrong. This is used to distinguish internal 4 | * errors from errors in the expression that the user provided. 5 | */ 6 | function ParseError(message, lexer, position) { 7 | var error = "KaTeX parse error: " + message; 8 | 9 | if (lexer !== undefined && position !== undefined) { 10 | // If we have the input and a position, make the error a bit fancier 11 | 12 | // Prepend some information 13 | error += " at position " + position + ": "; 14 | 15 | // Get the input 16 | var input = lexer._input; 17 | // Insert a combining underscore at the correct position 18 | input = input.slice(0, position) + "\u0332" + 19 | input.slice(position); 20 | 21 | // Extract some context from the input and add it to the error 22 | var begin = Math.max(0, position - 15); 23 | var end = position + 15; 24 | error += input.slice(begin, end); 25 | } 26 | 27 | // Some hackery to make ParseError a prototype of Error 28 | // See http://stackoverflow.com/a/8460753 29 | var self = new Error(error); 30 | self.name = "ParseError"; 31 | self.__proto__ = ParseError.prototype; 32 | 33 | self.position = position; 34 | return self; 35 | } 36 | 37 | // More hackery 38 | ParseError.prototype.__proto__ = Error.prototype; 39 | 40 | module.exports = ParseError; 41 | -------------------------------------------------------------------------------- /app/vendor/katex/src/Settings.js: -------------------------------------------------------------------------------- 1 | /** 2 | * This is a module for storing settings passed into KaTeX. It correctly handles 3 | * default settings. 4 | */ 5 | 6 | /** 7 | * Helper function for getting a default value if the value is undefined 8 | */ 9 | function get(option, defaultValue) { 10 | return option === undefined ? defaultValue : option; 11 | } 12 | 13 | /** 14 | * The main Settings object 15 | * 16 | * The current options stored are: 17 | * - displayMode: Whether the expression should be typeset by default in 18 | * textstyle or displaystyle (default false) 19 | */ 20 | function Settings(options) { 21 | // allow null options 22 | options = options || {}; 23 | this.displayMode = get(options.displayMode, false); 24 | this.throwOnError = get(options.throwOnError, true); 25 | this.errorColor = get(options.errorColor, "#cc0000"); 26 | } 27 | 28 | module.exports = Settings; 29 | -------------------------------------------------------------------------------- /app/vendor/katex/src/Style.js: -------------------------------------------------------------------------------- 1 | /** 2 | * This file contains information and classes for the various kinds of styles 3 | * used in TeX. It provides a generic `Style` class, which holds information 4 | * about a specific style. It then provides instances of all the different kinds 5 | * of styles possible, and provides functions to move between them and get 6 | * information about them. 7 | */ 8 | 9 | /** 10 | * The main style class. Contains a unique id for the style, a size (which is 11 | * the same for cramped and uncramped version of a style), a cramped flag, and a 12 | * size multiplier, which gives the size difference between a style and 13 | * textstyle. 14 | */ 15 | function Style(id, size, multiplier, cramped) { 16 | this.id = id; 17 | this.size = size; 18 | this.cramped = cramped; 19 | this.sizeMultiplier = multiplier; 20 | } 21 | 22 | /** 23 | * Get the style of a superscript given a base in the current style. 24 | */ 25 | Style.prototype.sup = function() { 26 | return styles[sup[this.id]]; 27 | }; 28 | 29 | /** 30 | * Get the style of a subscript given a base in the current style. 31 | */ 32 | Style.prototype.sub = function() { 33 | return styles[sub[this.id]]; 34 | }; 35 | 36 | /** 37 | * Get the style of a fraction numerator given the fraction in the current 38 | * style. 39 | */ 40 | Style.prototype.fracNum = function() { 41 | return styles[fracNum[this.id]]; 42 | }; 43 | 44 | /** 45 | * Get the style of a fraction denominator given the fraction in the current 46 | * style. 47 | */ 48 | Style.prototype.fracDen = function() { 49 | return styles[fracDen[this.id]]; 50 | }; 51 | 52 | /** 53 | * Get the cramped version of a style (in particular, cramping a cramped style 54 | * doesn't change the style). 55 | */ 56 | Style.prototype.cramp = function() { 57 | return styles[cramp[this.id]]; 58 | }; 59 | 60 | /** 61 | * HTML class name, like "displaystyle cramped" 62 | */ 63 | Style.prototype.cls = function() { 64 | return sizeNames[this.size] + (this.cramped ? " cramped" : " uncramped"); 65 | }; 66 | 67 | /** 68 | * HTML Reset class name, like "reset-textstyle" 69 | */ 70 | Style.prototype.reset = function() { 71 | return resetNames[this.size]; 72 | }; 73 | 74 | // IDs of the different styles 75 | var D = 0; 76 | var Dc = 1; 77 | var T = 2; 78 | var Tc = 3; 79 | var S = 4; 80 | var Sc = 5; 81 | var SS = 6; 82 | var SSc = 7; 83 | 84 | // String names for the different sizes 85 | var sizeNames = [ 86 | "displaystyle textstyle", 87 | "textstyle", 88 | "scriptstyle", 89 | "scriptscriptstyle", 90 | ]; 91 | 92 | // Reset names for the different sizes 93 | var resetNames = [ 94 | "reset-textstyle", 95 | "reset-textstyle", 96 | "reset-scriptstyle", 97 | "reset-scriptscriptstyle", 98 | ]; 99 | 100 | // Instances of the different styles 101 | var styles = [ 102 | new Style(D, 0, 1.0, false), 103 | new Style(Dc, 0, 1.0, true), 104 | new Style(T, 1, 1.0, false), 105 | new Style(Tc, 1, 1.0, true), 106 | new Style(S, 2, 0.7, false), 107 | new Style(Sc, 2, 0.7, true), 108 | new Style(SS, 3, 0.5, false), 109 | new Style(SSc, 3, 0.5, true), 110 | ]; 111 | 112 | // Lookup tables for switching from one style to another 113 | var sup = [S, Sc, S, Sc, SS, SSc, SS, SSc]; 114 | var sub = [Sc, Sc, Sc, Sc, SSc, SSc, SSc, SSc]; 115 | var fracNum = [T, Tc, S, Sc, SS, SSc, SS, SSc]; 116 | var fracDen = [Tc, Tc, Sc, Sc, SSc, SSc, SSc, SSc]; 117 | var cramp = [Dc, Dc, Tc, Tc, Sc, Sc, SSc, SSc]; 118 | 119 | // We only export some of the styles. Also, we don't export the `Style` class so 120 | // no more styles can be generated. 121 | module.exports = { 122 | DISPLAY: styles[D], 123 | TEXT: styles[T], 124 | SCRIPT: styles[S], 125 | SCRIPTSCRIPT: styles[SS], 126 | }; 127 | -------------------------------------------------------------------------------- /app/vendor/katex/src/buildTree.js: -------------------------------------------------------------------------------- 1 | var buildHTML = require("./buildHTML"); 2 | var buildMathML = require("./buildMathML"); 3 | var buildCommon = require("./buildCommon"); 4 | var Options = require("./Options"); 5 | var Settings = require("./Settings"); 6 | var Style = require("./Style"); 7 | 8 | var makeSpan = buildCommon.makeSpan; 9 | 10 | var buildTree = function(tree, expression, settings) { 11 | settings = settings || new Settings({}); 12 | 13 | var startStyle = Style.TEXT; 14 | if (settings.displayMode) { 15 | startStyle = Style.DISPLAY; 16 | } 17 | 18 | // Setup the default options 19 | var options = new Options({ 20 | style: startStyle, 21 | size: "size5", 22 | }); 23 | 24 | // `buildHTML` sometimes messes with the parse tree (like turning bins -> 25 | // ords), so we build the MathML version first. 26 | var mathMLNode = buildMathML(tree, expression, options); 27 | var htmlNode = buildHTML(tree, options); 28 | 29 | var katexNode = makeSpan(["katex"], [ 30 | mathMLNode, htmlNode, 31 | ]); 32 | 33 | if (settings.displayMode) { 34 | return makeSpan(["katex-display"], [katexNode]); 35 | } else { 36 | return katexNode; 37 | } 38 | }; 39 | 40 | module.exports = buildTree; 41 | -------------------------------------------------------------------------------- /app/vendor/katex/src/domTree.js: -------------------------------------------------------------------------------- 1 | /** 2 | * These objects store the data about the DOM nodes we create, as well as some 3 | * extra data. They can then be transformed into real DOM nodes with the 4 | * `toNode` function or HTML markup using `toMarkup`. They are useful for both 5 | * storing extra properties on the nodes, as well as providing a way to easily 6 | * work with the DOM. 7 | * 8 | * Similar functions for working with MathML nodes exist in mathMLTree.js. 9 | */ 10 | 11 | var utils = require("./utils"); 12 | 13 | /** 14 | * Create an HTML className based on a list of classes. In addition to joining 15 | * with spaces, we also remove null or empty classes. 16 | */ 17 | var createClass = function(classes) { 18 | classes = classes.slice(); 19 | for (var i = classes.length - 1; i >= 0; i--) { 20 | if (!classes[i]) { 21 | classes.splice(i, 1); 22 | } 23 | } 24 | 25 | return classes.join(" "); 26 | }; 27 | 28 | /** 29 | * This node represents a span node, with a className, a list of children, and 30 | * an inline style. It also contains information about its height, depth, and 31 | * maxFontSize. 32 | */ 33 | function span(classes, children, height, depth, maxFontSize, style) { 34 | this.classes = classes || []; 35 | this.children = children || []; 36 | this.height = height || 0; 37 | this.depth = depth || 0; 38 | this.maxFontSize = maxFontSize || 0; 39 | this.style = style || {}; 40 | this.attributes = {}; 41 | } 42 | 43 | /** 44 | * Sets an arbitrary attribute on the span. Warning: use this wisely. Not all 45 | * browsers support attributes the same, and having too many custom attributes 46 | * is probably bad. 47 | */ 48 | span.prototype.setAttribute = function(attribute, value) { 49 | this.attributes[attribute] = value; 50 | }; 51 | 52 | /** 53 | * Convert the span into an HTML node 54 | */ 55 | span.prototype.toNode = function() { 56 | var span = document.createElement("span"); 57 | 58 | // Apply the class 59 | span.className = createClass(this.classes); 60 | 61 | // Apply inline styles 62 | for (var style in this.style) { 63 | if (Object.prototype.hasOwnProperty.call(this.style, style)) { 64 | span.style[style] = this.style[style]; 65 | } 66 | } 67 | 68 | // Apply attributes 69 | for (var attr in this.attributes) { 70 | if (Object.prototype.hasOwnProperty.call(this.attributes, attr)) { 71 | span.setAttribute(attr, this.attributes[attr]); 72 | } 73 | } 74 | 75 | // Append the children, also as HTML nodes 76 | for (var i = 0; i < this.children.length; i++) { 77 | span.appendChild(this.children[i].toNode()); 78 | } 79 | 80 | return span; 81 | }; 82 | 83 | /** 84 | * Convert the span into an HTML markup string 85 | */ 86 | span.prototype.toMarkup = function() { 87 | var markup = " 0) { 197 | span = document.createElement("span"); 198 | span.style.marginRight = this.italic + "em"; 199 | } 200 | 201 | if (this.classes.length > 0) { 202 | span = span || document.createElement("span"); 203 | span.className = createClass(this.classes); 204 | } 205 | 206 | for (var style in this.style) { 207 | if (this.style.hasOwnProperty(style)) { 208 | span = span || document.createElement("span"); 209 | span.style[style] = this.style[style]; 210 | } 211 | } 212 | 213 | if (span) { 214 | span.appendChild(node); 215 | return span; 216 | } else { 217 | return node; 218 | } 219 | }; 220 | 221 | /** 222 | * Creates markup for a symbol node. 223 | */ 224 | symbolNode.prototype.toMarkup = function() { 225 | // TODO(alpert): More duplication than I'd like from 226 | // span.prototype.toMarkup and symbolNode.prototype.toNode... 227 | var needsSpan = false; 228 | 229 | var markup = " 0) { 241 | styles += "margin-right:" + this.italic + "em;"; 242 | } 243 | for (var style in this.style) { 244 | if (this.style.hasOwnProperty(style)) { 245 | styles += utils.hyphenate(style) + ":" + this.style[style] + ";"; 246 | } 247 | } 248 | 249 | if (styles) { 250 | needsSpan = true; 251 | markup += " style=\"" + utils.escape(styles) + "\""; 252 | } 253 | 254 | var escaped = utils.escape(this.value); 255 | if (needsSpan) { 256 | markup += ">"; 257 | markup += escaped; 258 | markup += ""; 259 | return markup; 260 | } else { 261 | return escaped; 262 | } 263 | }; 264 | 265 | module.exports = { 266 | span: span, 267 | documentFragment: documentFragment, 268 | symbolNode: symbolNode, 269 | }; 270 | -------------------------------------------------------------------------------- /app/vendor/katex/src/environments.js: -------------------------------------------------------------------------------- 1 | /* eslint no-constant-condition:0 */ 2 | var fontMetrics = require("./fontMetrics"); 3 | var parseData = require("./parseData"); 4 | var ParseError = require("./ParseError"); 5 | 6 | var ParseNode = parseData.ParseNode; 7 | 8 | /** 9 | * Parse the body of the environment, with rows delimited by \\ and 10 | * columns delimited by &, and create a nested list in row-major order 11 | * with one group per cell. 12 | */ 13 | function parseArray(parser, result) { 14 | var row = []; 15 | var body = [row]; 16 | var rowGaps = []; 17 | while (true) { 18 | var cell = parser.parseExpression(false, null); 19 | row.push(new ParseNode("ordgroup", cell, parser.mode)); 20 | var next = parser.nextToken.text; 21 | if (next === "&") { 22 | parser.consume(); 23 | } else if (next === "\\end") { 24 | break; 25 | } else if (next === "\\\\" || next === "\\cr") { 26 | var cr = parser.parseFunction(); 27 | rowGaps.push(cr.value.size); 28 | row = []; 29 | body.push(row); 30 | } else { 31 | // TODO: Clean up the following hack once #385 got merged 32 | var pos = Math.min(parser.pos + 1, parser.lexer._input.length); 33 | throw new ParseError("Expected & or \\\\ or \\end", 34 | parser.lexer, pos); 35 | } 36 | } 37 | result.body = body; 38 | result.rowGaps = rowGaps; 39 | return new ParseNode(result.type, result, parser.mode); 40 | } 41 | 42 | /* 43 | * An environment definition is very similar to a function definition: 44 | * it is declared with a name or a list of names, a set of properties 45 | * and a handler containing the actual implementation. 46 | * 47 | * The properties include: 48 | * - numArgs: The number of arguments after the \begin{name} function. 49 | * - argTypes: (optional) Just like for a function 50 | * - allowedInText: (optional) Whether or not the environment is allowed inside 51 | * text mode (default false) (not enforced yet) 52 | * - numOptionalArgs: (optional) Just like for a function 53 | * A bare number instead of that object indicates the numArgs value. 54 | * 55 | * The handler function will receive two arguments 56 | * - context: information and references provided by the parser 57 | * - args: an array of arguments passed to \begin{name} 58 | * The context contains the following properties: 59 | * - envName: the name of the environment, one of the listed names. 60 | * - parser: the parser object 61 | * - lexer: the lexer object 62 | * - positions: the positions associated with these arguments from args. 63 | * The handler must return a ParseResult. 64 | */ 65 | 66 | function defineEnvironment(names, props, handler) { 67 | if (typeof names === "string") { 68 | names = [names]; 69 | } 70 | if (typeof props === "number") { 71 | props = { numArgs: props }; 72 | } 73 | // Set default values of environments 74 | var data = { 75 | numArgs: props.numArgs || 0, 76 | argTypes: props.argTypes, 77 | greediness: 1, 78 | allowedInText: !!props.allowedInText, 79 | numOptionalArgs: props.numOptionalArgs || 0, 80 | handler: handler, 81 | }; 82 | for (var i = 0; i < names.length; ++i) { 83 | module.exports[names[i]] = data; 84 | } 85 | } 86 | 87 | // Arrays are part of LaTeX, defined in lttab.dtx so its documentation 88 | // is part of the source2e.pdf file of LaTeX2e source documentation. 89 | defineEnvironment("array", { 90 | numArgs: 1, 91 | }, function(context, args) { 92 | var colalign = args[0]; 93 | colalign = colalign.value.map ? colalign.value : [colalign]; 94 | var cols = colalign.map(function(node) { 95 | var ca = node.value; 96 | if ("lcr".indexOf(ca) !== -1) { 97 | return { 98 | type: "align", 99 | align: ca, 100 | }; 101 | } else if (ca === "|") { 102 | return { 103 | type: "separator", 104 | separator: "|", 105 | }; 106 | } 107 | throw new ParseError( 108 | "Unknown column alignment: " + node.value, 109 | context.lexer, context.positions[1]); 110 | }); 111 | var res = { 112 | type: "array", 113 | cols: cols, 114 | hskipBeforeAndAfter: true, // \@preamble in lttab.dtx 115 | }; 116 | res = parseArray(context.parser, res); 117 | return res; 118 | }); 119 | 120 | // The matrix environments of amsmath builds on the array environment 121 | // of LaTeX, which is discussed above. 122 | defineEnvironment([ 123 | "matrix", 124 | "pmatrix", 125 | "bmatrix", 126 | "Bmatrix", 127 | "vmatrix", 128 | "Vmatrix", 129 | ], { 130 | }, function(context) { 131 | var delimiters = { 132 | "matrix": null, 133 | "pmatrix": ["(", ")"], 134 | "bmatrix": ["[", "]"], 135 | "Bmatrix": ["\\{", "\\}"], 136 | "vmatrix": ["|", "|"], 137 | "Vmatrix": ["\\Vert", "\\Vert"], 138 | }[context.envName]; 139 | var res = { 140 | type: "array", 141 | hskipBeforeAndAfter: false, // \hskip -\arraycolsep in amsmath 142 | }; 143 | res = parseArray(context.parser, res); 144 | if (delimiters) { 145 | res = new ParseNode("leftright", { 146 | body: [res], 147 | left: delimiters[0], 148 | right: delimiters[1], 149 | }, context.mode); 150 | } 151 | return res; 152 | }); 153 | 154 | // A cases environment (in amsmath.sty) is almost equivalent to 155 | // \def\arraystretch{1.2}% 156 | // \left\{\begin{array}{@{}l@{\quad}l@{}} … \end{array}\right. 157 | defineEnvironment("cases", { 158 | }, function(context) { 159 | var res = { 160 | type: "array", 161 | arraystretch: 1.2, 162 | cols: [{ 163 | type: "align", 164 | align: "l", 165 | pregap: 0, 166 | postgap: fontMetrics.metrics.quad, 167 | }, { 168 | type: "align", 169 | align: "l", 170 | pregap: 0, 171 | postgap: 0, 172 | }], 173 | }; 174 | res = parseArray(context.parser, res); 175 | res = new ParseNode("leftright", { 176 | body: [res], 177 | left: "\\{", 178 | right: ".", 179 | }, context.mode); 180 | return res; 181 | }); 182 | 183 | // An aligned environment is like the align* environment 184 | // except it operates within math mode. 185 | // Note that we assume \nomallineskiplimit to be zero, 186 | // so that \strut@ is the same as \strut. 187 | defineEnvironment("aligned", { 188 | }, function(context) { 189 | var res = { 190 | type: "array", 191 | cols: [], 192 | }; 193 | res = parseArray(context.parser, res); 194 | var emptyGroup = new ParseNode("ordgroup", [], context.mode); 195 | var numCols = 0; 196 | res.value.body.forEach(function(row) { 197 | var i; 198 | for (i = 1; i < row.length; i += 2) { 199 | row[i].value.unshift(emptyGroup); 200 | } 201 | if (numCols < row.length) { 202 | numCols = row.length; 203 | } 204 | }); 205 | for (var i = 0; i < numCols; ++i) { 206 | var align = "r"; 207 | var pregap = 0; 208 | if (i % 2 === 1) { 209 | align = "l"; 210 | } else if (i > 0) { 211 | pregap = 2; // one \qquad between columns 212 | } 213 | res.value.cols[i] = { 214 | type: "align", 215 | align: align, 216 | pregap: pregap, 217 | postgap: 0, 218 | }; 219 | } 220 | return res; 221 | }); 222 | -------------------------------------------------------------------------------- /app/vendor/katex/src/fontMetrics.js: -------------------------------------------------------------------------------- 1 | /* eslint no-unused-vars:0 */ 2 | 3 | var Style = require("./Style"); 4 | 5 | /** 6 | * This file contains metrics regarding fonts and individual symbols. The sigma 7 | * and xi variables, as well as the metricMap map contain data extracted from 8 | * TeX, TeX font metrics, and the TTF files. These data are then exposed via the 9 | * `metrics` variable and the getCharacterMetrics function. 10 | */ 11 | 12 | // These font metrics are extracted from TeX by using 13 | // \font\a=cmmi10 14 | // \showthe\fontdimenX\a 15 | // where X is the corresponding variable number. These correspond to the font 16 | // parameters of the symbol fonts. In TeX, there are actually three sets of 17 | // dimensions, one for each of textstyle, scriptstyle, and scriptscriptstyle, 18 | // but we only use the textstyle ones, and scale certain dimensions accordingly. 19 | // See the TeXbook, page 441. 20 | var sigma1 = 0.025; 21 | var sigma2 = 0; 22 | var sigma3 = 0; 23 | var sigma4 = 0; 24 | var sigma5 = 0.431; 25 | var sigma6 = 1; 26 | var sigma7 = 0; 27 | var sigma8 = 0.677; 28 | var sigma9 = 0.394; 29 | var sigma10 = 0.444; 30 | var sigma11 = 0.686; 31 | var sigma12 = 0.345; 32 | var sigma13 = 0.413; 33 | var sigma14 = 0.363; 34 | var sigma15 = 0.289; 35 | var sigma16 = 0.150; 36 | var sigma17 = 0.247; 37 | var sigma18 = 0.386; 38 | var sigma19 = 0.050; 39 | var sigma20 = 2.390; 40 | var sigma21 = 1.01; 41 | var sigma21Script = 0.81; 42 | var sigma21ScriptScript = 0.71; 43 | var sigma22 = 0.250; 44 | 45 | // These font metrics are extracted from TeX by using 46 | // \font\a=cmex10 47 | // \showthe\fontdimenX\a 48 | // where X is the corresponding variable number. These correspond to the font 49 | // parameters of the extension fonts (family 3). See the TeXbook, page 441. 50 | var xi1 = 0; 51 | var xi2 = 0; 52 | var xi3 = 0; 53 | var xi4 = 0; 54 | var xi5 = 0.431; 55 | var xi6 = 1; 56 | var xi7 = 0; 57 | var xi8 = 0.04; 58 | var xi9 = 0.111; 59 | var xi10 = 0.166; 60 | var xi11 = 0.2; 61 | var xi12 = 0.6; 62 | var xi13 = 0.1; 63 | 64 | // This value determines how large a pt is, for metrics which are defined in 65 | // terms of pts. 66 | // This value is also used in katex.less; if you change it make sure the values 67 | // match. 68 | var ptPerEm = 10.0; 69 | 70 | // The space between adjacent `|` columns in an array definition. From 71 | // `\showthe\doublerulesep` in LaTeX. 72 | var doubleRuleSep = 2.0 / ptPerEm; 73 | 74 | /** 75 | * This is just a mapping from common names to real metrics 76 | */ 77 | var metrics = { 78 | xHeight: sigma5, 79 | quad: sigma6, 80 | num1: sigma8, 81 | num2: sigma9, 82 | num3: sigma10, 83 | denom1: sigma11, 84 | denom2: sigma12, 85 | sup1: sigma13, 86 | sup2: sigma14, 87 | sup3: sigma15, 88 | sub1: sigma16, 89 | sub2: sigma17, 90 | supDrop: sigma18, 91 | subDrop: sigma19, 92 | axisHeight: sigma22, 93 | defaultRuleThickness: xi8, 94 | bigOpSpacing1: xi9, 95 | bigOpSpacing2: xi10, 96 | bigOpSpacing3: xi11, 97 | bigOpSpacing4: xi12, 98 | bigOpSpacing5: xi13, 99 | ptPerEm: ptPerEm, 100 | emPerEx: sigma5 / sigma6, 101 | doubleRuleSep: doubleRuleSep, 102 | 103 | // TODO(alpert): Missing parallel structure here. We should probably add 104 | // style-specific metrics for all of these. 105 | delim1: sigma20, 106 | getDelim2: function(style) { 107 | if (style.size === Style.TEXT.size) { 108 | return sigma21; 109 | } else if (style.size === Style.SCRIPT.size) { 110 | return sigma21Script; 111 | } else if (style.size === Style.SCRIPTSCRIPT.size) { 112 | return sigma21ScriptScript; 113 | } 114 | throw new Error("Unexpected style size: " + style.size); 115 | }, 116 | }; 117 | 118 | // This map contains a mapping from font name and character code to character 119 | // metrics, including height, depth, italic correction, and skew (kern from the 120 | // character to the corresponding \skewchar) 121 | // This map is generated via `make metrics`. It should not be changed manually. 122 | var metricMap = require("./fontMetricsData"); 123 | 124 | /** 125 | * This function is a convenience function for looking up information in the 126 | * metricMap table. It takes a character as a string, and a style. 127 | * 128 | * Note: the `width` property may be undefined if fontMetricsData.js wasn't 129 | * built using `Make extended_metrics`. 130 | */ 131 | var getCharacterMetrics = function(character, style) { 132 | var metrics = metricMap[style][character.charCodeAt(0)]; 133 | if (metrics) { 134 | return { 135 | depth: metrics[0], 136 | height: metrics[1], 137 | italic: metrics[2], 138 | skew: metrics[3], 139 | width: metrics[4], 140 | }; 141 | } 142 | }; 143 | 144 | module.exports = { 145 | metrics: metrics, 146 | getCharacterMetrics: getCharacterMetrics, 147 | }; 148 | -------------------------------------------------------------------------------- /app/vendor/katex/src/mathMLTree.js: -------------------------------------------------------------------------------- 1 | /** 2 | * These objects store data about MathML nodes. This is the MathML equivalent 3 | * of the types in domTree.js. Since MathML handles its own rendering, and 4 | * since we're mainly using MathML to improve accessibility, we don't manage 5 | * any of the styling state that the plain DOM nodes do. 6 | * 7 | * The `toNode` and `toMarkup` functions work simlarly to how they do in 8 | * domTree.js, creating namespaced DOM nodes and HTML text markup respectively. 9 | */ 10 | 11 | var utils = require("./utils"); 12 | 13 | /** 14 | * This node represents a general purpose MathML node of any type. The 15 | * constructor requires the type of node to create (for example, `"mo"` or 16 | * `"mspace"`, corresponding to `` and `` tags). 17 | */ 18 | function MathNode(type, children) { 19 | this.type = type; 20 | this.attributes = {}; 21 | this.children = children || []; 22 | } 23 | 24 | /** 25 | * Sets an attribute on a MathML node. MathML depends on attributes to convey a 26 | * semantic content, so this is used heavily. 27 | */ 28 | MathNode.prototype.setAttribute = function(name, value) { 29 | this.attributes[name] = value; 30 | }; 31 | 32 | /** 33 | * Converts the math node into a MathML-namespaced DOM element. 34 | */ 35 | MathNode.prototype.toNode = function() { 36 | var node = document.createElementNS( 37 | "http://www.w3.org/1998/Math/MathML", this.type); 38 | 39 | for (var attr in this.attributes) { 40 | if (Object.prototype.hasOwnProperty.call(this.attributes, attr)) { 41 | node.setAttribute(attr, this.attributes[attr]); 42 | } 43 | } 44 | 45 | for (var i = 0; i < this.children.length; i++) { 46 | node.appendChild(this.children[i].toNode()); 47 | } 48 | 49 | return node; 50 | }; 51 | 52 | /** 53 | * Converts the math node into an HTML markup string. 54 | */ 55 | MathNode.prototype.toMarkup = function() { 56 | var markup = "<" + this.type; 57 | 58 | // Add the attributes 59 | for (var attr in this.attributes) { 60 | if (Object.prototype.hasOwnProperty.call(this.attributes, attr)) { 61 | markup += " " + attr + "=\""; 62 | markup += utils.escape(this.attributes[attr]); 63 | markup += "\""; 64 | } 65 | } 66 | 67 | markup += ">"; 68 | 69 | for (var i = 0; i < this.children.length; i++) { 70 | markup += this.children[i].toMarkup(); 71 | } 72 | 73 | markup += ""; 74 | 75 | return markup; 76 | }; 77 | 78 | /** 79 | * This node represents a piece of text. 80 | */ 81 | function TextNode(text) { 82 | this.text = text; 83 | } 84 | 85 | /** 86 | * Converts the text node into a DOM text node. 87 | */ 88 | TextNode.prototype.toNode = function() { 89 | return document.createTextNode(this.text); 90 | }; 91 | 92 | /** 93 | * Converts the text node into HTML markup (which is just the text itself). 94 | */ 95 | TextNode.prototype.toMarkup = function() { 96 | return utils.escape(this.text); 97 | }; 98 | 99 | module.exports = { 100 | MathNode: MathNode, 101 | TextNode: TextNode, 102 | }; 103 | -------------------------------------------------------------------------------- /app/vendor/katex/src/parseData.js: -------------------------------------------------------------------------------- 1 | /** 2 | * The resulting parse tree nodes of the parse tree. 3 | */ 4 | function ParseNode(type, value, mode) { 5 | this.type = type; 6 | this.value = value; 7 | this.mode = mode; 8 | } 9 | 10 | module.exports = { 11 | ParseNode: ParseNode, 12 | }; 13 | 14 | -------------------------------------------------------------------------------- /app/vendor/katex/src/parseTree.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Provides a single function for parsing an expression using a Parser 3 | * TODO(emily): Remove this 4 | */ 5 | 6 | var Parser = require("./Parser"); 7 | 8 | /** 9 | * Parses an expression using a Parser, then returns the parsed result. 10 | */ 11 | var parseTree = function(toParse, settings) { 12 | var parser = new Parser(toParse, settings); 13 | 14 | return parser.parse(); 15 | }; 16 | 17 | module.exports = parseTree; 18 | -------------------------------------------------------------------------------- /app/vendor/katex/src/utils.js: -------------------------------------------------------------------------------- 1 | /** 2 | * This file contains a list of utility functions which are useful in other 3 | * files. 4 | */ 5 | 6 | /** 7 | * Provide an `indexOf` function which works in IE8, but defers to native if 8 | * possible. 9 | */ 10 | var nativeIndexOf = Array.prototype.indexOf; 11 | var indexOf = function(list, elem) { 12 | if (list == null) { 13 | return -1; 14 | } 15 | if (nativeIndexOf && list.indexOf === nativeIndexOf) { 16 | return list.indexOf(elem); 17 | } 18 | var i = 0; 19 | var l = list.length; 20 | for (; i < l; i++) { 21 | if (list[i] === elem) { 22 | return i; 23 | } 24 | } 25 | return -1; 26 | }; 27 | 28 | /** 29 | * Return whether an element is contained in a list 30 | */ 31 | var contains = function(list, elem) { 32 | return indexOf(list, elem) !== -1; 33 | }; 34 | 35 | /** 36 | * Provide a default value if a setting is undefined 37 | */ 38 | var deflt = function(setting, defaultIfUndefined) { 39 | return setting === undefined ? defaultIfUndefined : setting; 40 | }; 41 | 42 | // hyphenate and escape adapted from Facebook's React under Apache 2 license 43 | 44 | var uppercase = /([A-Z])/g; 45 | var hyphenate = function(str) { 46 | return str.replace(uppercase, "-$1").toLowerCase(); 47 | }; 48 | 49 | var ESCAPE_LOOKUP = { 50 | "&": "&", 51 | ">": ">", 52 | "<": "<", 53 | "\"": """, 54 | "'": "'", 55 | }; 56 | 57 | var ESCAPE_REGEX = /[&><"']/g; 58 | 59 | function escaper(match) { 60 | return ESCAPE_LOOKUP[match]; 61 | } 62 | 63 | /** 64 | * Escapes text to prevent scripting attacks. 65 | * 66 | * @param {*} text Text value to escape. 67 | * @return {string} An escaped string. 68 | */ 69 | function escape(text) { 70 | return ("" + text).replace(ESCAPE_REGEX, escaper); 71 | } 72 | 73 | /** 74 | * A function to set the text content of a DOM element in all supported 75 | * browsers. Note that we don't define this if there is no document. 76 | */ 77 | var setTextContent; 78 | if (typeof document !== "undefined") { 79 | var testNode = document.createElement("span"); 80 | if ("textContent" in testNode) { 81 | setTextContent = function(node, text) { 82 | node.textContent = text; 83 | }; 84 | } else { 85 | setTextContent = function(node, text) { 86 | node.innerText = text; 87 | }; 88 | } 89 | } 90 | 91 | /** 92 | * A function to clear a node. 93 | */ 94 | function clearNode(node) { 95 | setTextContent(node, ""); 96 | } 97 | 98 | module.exports = { 99 | contains: contains, 100 | deflt: deflt, 101 | escape: escape, 102 | hyphenate: hyphenate, 103 | indexOf: indexOf, 104 | setTextContent: setTextContent, 105 | clearNode: clearNode, 106 | }; 107 | -------------------------------------------------------------------------------- /app/vendor/markdown-it-katex/index.js: -------------------------------------------------------------------------------- 1 | /* Process inline math */ 2 | /* 3 | Like markdown-it-simplemath, this is a stripped down, simplified version of: 4 | https://github.com/runarberg/markdown-it-math 5 | 6 | It differs in that it takes (a subset of) LaTeX as input and relies on KaTeX 7 | for rendering output. 8 | */ 9 | 10 | /*jslint node: true */ 11 | 'use strict'; 12 | 13 | var katex = require('../katex/katex'); 14 | 15 | // Test if potential opening or closing delimieter 16 | // Assumes that there is a "$" at state.src[pos] 17 | function isValidDelim(state, pos) { 18 | var prevChar, nextChar, 19 | max = state.posMax, 20 | can_open = true, 21 | can_close = true; 22 | 23 | prevChar = pos > 0 ? state.src.charCodeAt(pos - 1) : -1; 24 | nextChar = pos + 1 <= max ? state.src.charCodeAt(pos + 1) : -1; 25 | 26 | // Check non-whitespace conditions for opening and closing, and 27 | // check that closing delimeter isn't followed by a number 28 | if (prevChar === 0x20/* " " */ || prevChar === 0x09/* \t */ || 29 | (nextChar >= 0x30/* "0" */ && nextChar <= 0x39/* "9" */)) { 30 | can_close = false; 31 | } 32 | if (nextChar === 0x20/* " " */ || nextChar === 0x09/* \t */) { 33 | can_open = false; 34 | } 35 | 36 | return { 37 | can_open: can_open, 38 | can_close: can_close 39 | }; 40 | } 41 | 42 | function math_inline(state, silent) { 43 | var start, match, token, res, pos, esc_count; 44 | 45 | if (state.src[state.pos] !== "$") { return false; } 46 | 47 | res = isValidDelim(state, state.pos); 48 | if (!res.can_open) { 49 | if (!silent) { state.pending += "$"; } 50 | state.pos += 1; 51 | return true; 52 | } 53 | 54 | // First check for and bypass all properly escaped delimieters 55 | // This loop will assume that the first leading backtick can not 56 | // be the first character in state.src, which is known since 57 | // we have found an opening delimieter already. 58 | start = state.pos + 1; 59 | match = start; 60 | while ( (match = state.src.indexOf("$", match)) !== -1) { 61 | // Found potential $, look for escapes, pos will point to 62 | // first non escape when complete 63 | pos = match - 1; 64 | while (state.src[pos] === "\\") { pos -= 1; } 65 | 66 | // Even number of escapes, potential closing delimiter found 67 | if ( ((match - pos) % 2) == 1 ) { break; } 68 | match += 1; 69 | } 70 | 71 | // No closing delimter found. Consume $ and continue. 72 | if (match === -1) { 73 | if (!silent) { state.pending += "$"; } 74 | state.pos = start; 75 | return true; 76 | } 77 | 78 | // Check if we have empty content, ie: $$. Do not parse. 79 | if (match - start === 0) { 80 | if (!silent) { state.pending += "$$"; } 81 | state.pos = start + 1; 82 | return true; 83 | } 84 | 85 | // Check for valid closing delimiter 86 | res = isValidDelim(state, match); 87 | if (!res.can_close) { 88 | if (!silent) { state.pending += "$"; } 89 | state.pos = start; 90 | return true; 91 | } 92 | 93 | if (!silent) { 94 | token = state.push('math_inline', 'math', 0); 95 | token.markup = "$"; 96 | token.content = state.src.slice(start, match); 97 | } 98 | 99 | state.pos = match + 1; 100 | return true; 101 | } 102 | 103 | function math_block(state, start, end, silent){ 104 | var firstLine, lastLine, next, lastPos, found = false, token, 105 | pos = state.bMarks[start] + state.tShift[start], 106 | max = state.eMarks[start] 107 | 108 | if(pos + 2 > max){ return false; } 109 | if(state.src.slice(pos,pos+2)!=='$$'){ return false; } 110 | 111 | pos += 2; 112 | firstLine = state.src.slice(pos,max); 113 | 114 | if(silent){ return true; } 115 | if(firstLine.trim().slice(-2)==='$$'){ 116 | // Single line expression 117 | firstLine = firstLine.trim().slice(0, -2); 118 | found = true; 119 | } 120 | 121 | for(next = start; !found; ){ 122 | 123 | next++; 124 | 125 | if(next >= end){ break; } 126 | 127 | pos = state.bMarks[next]+state.tShift[next]; 128 | max = state.eMarks[next]; 129 | 130 | if(pos < max && state.tShift[next] < state.blkIndent){ 131 | // non-empty line with negative indent should stop the list: 132 | break; 133 | } 134 | 135 | if(state.src.slice(pos,max).trim().slice(-2)==='$$'){ 136 | lastPos = state.src.slice(0,max).lastIndexOf('$$'); 137 | lastLine = state.src.slice(pos,lastPos); 138 | found = true; 139 | } 140 | 141 | } 142 | 143 | state.line = next + 1; 144 | 145 | token = state.push('math_block', 'math', 0); 146 | token.block = true; 147 | token.content = (firstLine && firstLine.trim() ? firstLine + '\n' : '') 148 | + state.getLines(start + 1, next, state.tShift[start], true) 149 | + (lastLine && lastLine.trim() ? lastLine : ''); 150 | token.map = [ start, state.line ]; 151 | token.markup = '$$'; 152 | return true; 153 | } 154 | 155 | module.exports = function math_plugin(md, options) { 156 | // Default options 157 | 158 | options = options || {}; 159 | 160 | // set KaTeX as the renderer for markdown-it-simplemath 161 | var katexInline = function(latex){ 162 | options.displayMode = false; 163 | try{ 164 | return katex.renderToString(latex, options); 165 | } 166 | catch(error){ 167 | if(options.throwOnError){ console.log(error); } 168 | return latex; 169 | } 170 | }; 171 | 172 | var inlineRenderer = function(tokens, idx){ 173 | return katexInline(tokens[idx].content); 174 | }; 175 | 176 | var katexBlock = function(latex){ 177 | options.displayMode = true; 178 | try{ 179 | return "

" + katex.renderToString(latex, options) + "

"; 180 | } 181 | catch(error){ 182 | if(options.throwOnError){ console.log(error); } 183 | return latex; 184 | } 185 | } 186 | 187 | var blockRenderer = function(tokens, idx){ 188 | return katexBlock(tokens[idx].content) + '\n'; 189 | } 190 | 191 | md.inline.ruler.after('escape', 'math_inline', math_inline); 192 | md.block.ruler.after('blockquote', 'math_block', math_block, { 193 | alt: [ 'paragraph', 'reference', 'blockquote', 'list' ] 194 | }); 195 | md.renderer.rules.math_inline = inlineRenderer; 196 | md.renderer.rules.math_block = blockRenderer; 197 | }; 198 | -------------------------------------------------------------------------------- /app/vendor/mousetrap/global-bind.js: -------------------------------------------------------------------------------- 1 | (function(a){var c={},d=a.prototype.stopCallback;a.prototype.stopCallback=function(e,b,a,f){return this.paused?!0:c[a]||c[f]?!1:d.call(this,e,b,a)};a.prototype.bindGlobal=function(a,b,d){this.bind(a,b,d);if(a instanceof Array)for(b=0;bl||k.hasOwnProperty(l)&&(n[k[l]]=l)}e=n[h]?"keydown":"keypress"}"keypress"==e&&g.length&&(e="keydown");return{key:c,modifiers:g,action:e}}function C(a,b){return null===a||a===t?!1:a===b?!0:C(a.parentNode,b)}function c(a){function b(a){a= 4 | a||{};var b=!1,m;for(m in n)a[m]?b=!0:n[m]=0;b||(w=!1)}function h(a,b,m,f,c,h){var g,e,k=[],l=m.type;if(!d._callbacks[a])return[];"keyup"==l&&v(a)&&(b=[a]);for(g=0;g":".","?":"/","|":"\\"},A={option:"alt",command:"meta","return":"enter", 9 | escape:"esc",plus:"+",mod:/Mac|iPod|iPhone|iPad/.test(navigator.platform)?"meta":"ctrl"},n;for(g=1;20>g;++g)k[111+g]="f"+g;for(g=0;9>=g;++g)k[g+96]=g;c.prototype.bind=function(a,b,c){a=a instanceof Array?a:[a];this._bindMultiple.call(this,a,b,c);return this};c.prototype.unbind=function(a,b){return this.bind.call(this,a,function(){},b)};c.prototype.trigger=function(a,b){if(this._directMap[a+":"+b])this._directMap[a+":"+b]({},a);return this};c.prototype.reset=function(){this._callbacks={};this._directMap= 10 | {};return this};c.prototype.stopCallback=function(a,b){return-1<(" "+b.className+" ").indexOf(" mousetrap ")||C(b,this.target)?!1:"INPUT"==b.tagName||"SELECT"==b.tagName||"TEXTAREA"==b.tagName||b.isContentEditable};c.prototype.handleKey=function(){return this._handleKey.apply(this,arguments)};c.addKeycodes=function(a){for(var b in a)a.hasOwnProperty(b)&&(k[b]=a[b]);n=null};c.init=function(){var a=c(t),b;for(b in a)"_"!==b.charAt(0)&&(c[b]=function(b){return function(){return a[b].apply(a,arguments)}}(b))}; 11 | c.init();r.Mousetrap=c;"undefined"!==typeof module&&module.exports&&(module.exports=c);"function"===typeof define&&define.amd&&define(function(){return c})}})("undefined"!==typeof window?window:null,"undefined"!==typeof window?document:null); 12 | -------------------------------------------------------------------------------- /app/welcome-guide.md: -------------------------------------------------------------------------------- 1 | ## Hola! 2 | 3 | The best time to learn Markdown is 10 years ago, the second is __now__! 4 | 5 | EME is trying to give you the best experience of writing in Markdown. 6 | 7 | Markdown shines ✨ 8 | 9 | ![shiny](http://ww4.sinaimg.cn/large/a15b4afegw1f6499jo5ruj20iw0anmxv.jpg) 10 | 11 | ## Focus Mode 12 | 13 | The first feature that most markdown editors do not have is, focus the paragraph you are actually writing. It reduces a lot of distractions when you are working on a long post. Use Ctrl/CMD + \ to toggle it. 14 | 15 | ## Distraction Free Mode 16 | 17 | One step further, you may want to hide the header and footer when you are writing in full-screen. Use Ctrl/CMD + J to toggle it. 18 | 19 | ## Vim Mode 20 | 21 | For developers, you may miss the vim key binding, here it is. Use Ctrl/CMD + I to toggle it. 22 | 23 | ## Shortcuts 24 | 25 | Please view: https://github.com/egoist/eme/wiki/Key-bindings 26 | -------------------------------------------------------------------------------- /build/icons/512x512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/egoist/eme/c7c2b4fdb0df703473792d4ad7a369d8c0adccaf/build/icons/512x512.png -------------------------------------------------------------------------------- /build/icons/icon.icns: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/egoist/eme/c7c2b4fdb0df703473792d4ad7a369d8c0adccaf/build/icons/icon.icns -------------------------------------------------------------------------------- /build/icons/icon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/egoist/eme/c7c2b4fdb0df703473792d4ad7a369d8c0adccaf/build/icons/icon.ico -------------------------------------------------------------------------------- /build/icons/markdown_file_association.icns: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/egoist/eme/c7c2b4fdb0df703473792d4ad7a369d8c0adccaf/build/icons/markdown_file_association.icns -------------------------------------------------------------------------------- /build/icons/markdown_file_association.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/egoist/eme/c7c2b4fdb0df703473792d4ad7a369d8c0adccaf/build/icons/markdown_file_association.ico -------------------------------------------------------------------------------- /electron-builder.json: -------------------------------------------------------------------------------- 1 | { 2 | "appId": "com.egoistian.eme", 3 | "asar": true, 4 | "copyright": "Copyright @2020 The EME Authors", 5 | "win": { 6 | "icon": "build/icons/icon.ico", 7 | "target": "squirrel" 8 | }, 9 | "linux": { 10 | "category": "public.app-category.utilities", 11 | "target": [ 12 | "deb", 13 | "tar.xz" 14 | ] 15 | }, 16 | "mac": { 17 | "icon": "build/icons/icon.icns" 18 | }, 19 | 20 | "fileAssociations": { 21 | "ext": "md", 22 | "icon": "build/icons/markdown_file_association.icns", 23 | "name": "Markdown File", 24 | "role": "Editor" 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /media/preview.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/egoist/eme/c7c2b4fdb0df703473792d4ad7a369d8c0adccaf/media/preview.png -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "eme", 3 | "description": "elegant markdown editor", 4 | "main": "app/index.js", 5 | "scripts": { 6 | "test": "npm run lint", 7 | "lint": "npm run lint:app && npm run lint:src", 8 | "lint:app": "eslint app/index.js app/eme --ext .js --fix", 9 | "lint:src": "eslint src/* src/* --ext .js --ext .vue --fix", 10 | "postinstall": "electron-builder install-app-deps", 11 | "app": "cross-env NODE_ENV=development electron app/ --inspect=5800", 12 | "build": "rm -rf app/dist && webpack --progress --config scripts/webpack.config.prod.js --profile --json > stats.json", 13 | "watch": "webpack --config scripts/webpack.config.dev.js --watch", 14 | "dist": "npm run mac && npm run linux && npm run win", 15 | "mac": "electron-builder --mac --config electron-builder.json", 16 | "linux": "electron-builder --linux --config electron-builder.json", 17 | "win": "electron-builder --config electron-builder.json", 18 | "stats": "webpack-bundle-analyzer stats.json" 19 | }, 20 | "repository": { 21 | "type": "git", 22 | "url": "git+https://github.com/egoist/eme.git" 23 | }, 24 | "keywords": [ 25 | "markdown", 26 | "editor" 27 | ], 28 | "devDependencies": { 29 | "autoprefixer": "^9.3.0", 30 | "@babel/core": "^7.10.0", 31 | "@babel/plugin-proposal-object-rest-spread": "^7.10.0", 32 | "@babel/plugin-syntax-object-rest-spread": "^7.8.3", 33 | "@babel/plugin-transform-runtime": "^7.10.0", 34 | "@babel/plugin-transform-spread": "^7.10.0", 35 | "babel-eslint": "^6.1.2", 36 | "babel-loader": "^8.1.0", 37 | "cross-env": "^2.0.0", 38 | "css-loader": "^0.23.1", 39 | "devtron": "^1.2.1", 40 | "electron": "^7.2.4", 41 | "electron-builder": "^22.0.0", 42 | "electron-builder-squirrel-windows": "^11.6.1", 43 | "electron-devtools-installer": "^2.0.0", 44 | "electron-packager": "^11.2.1", 45 | "eslint": "^3.12.2", 46 | "eslint-config-xo-space": "^0.15.0", 47 | "eslint-plugin-babel": "^4.0.0", 48 | "eslint-plugin-vue": "^2.0.0", 49 | "extract-text-webpack-plugin": "^2.1.2", 50 | "file-loader": "^0.9.0", 51 | "json-loader": "^0.5.4", 52 | "minimist": "^1.2.3", 53 | "postcss-import": "^12.0.0", 54 | "postcss-loader": "^1.2.1", 55 | "postcss-mixins": "^5.0.0", 56 | "postcss-nested": "^1.0.0", 57 | "postcss-simple-vars": "^3.0.0", 58 | "scripy": "^0.3.0", 59 | "shelljs": "^0.7.4", 60 | "style-loader": "^0.13.1", 61 | "svg-inline-loader": "^0.6.1", 62 | "ts-loader": "^3.5.0", 63 | "typescript": "^3.4.5", 64 | "url-loader": "^0.5.7", 65 | "vue": "^2.6.10", 66 | "vue-hot-reload-api": "^2.0.6", 67 | "vue-html-loader": "^1.2.3", 68 | "vue-loader": "^12.0.3", 69 | "vue-style-loader": "^1.0.0", 70 | "vue-template-compiler": "^2.6.10", 71 | "webpack": "^2.7.0", 72 | "webpack-bundle-analyzer": "^3.3.2", 73 | "webpack-hot-middleware": "^2.12.1" 74 | }, 75 | "dependencies": { 76 | "@wordpress/wordcount": "^2.2.0", 77 | "babel-runtime": "^6.11.6", 78 | "codemirror": "^5.17.0", 79 | "color-preset": "^0.1.1", 80 | "fetch-enhance": "^0.1.1", 81 | "highlight.js": "^9.5.0", 82 | "hint.css": "2.3.2", 83 | "markdown-it": "^7.0.0", 84 | "markdown-it-front-matter": "^0.1.2", 85 | "markdown-it-task-lists": "^1.4.0", 86 | "object-picker": "^0.2.0", 87 | "pify": "^2.3.0", 88 | "sanitize-html": "^1.13.0", 89 | "sumchecker": "^2.0.1", 90 | "unique-random-array": "^2.0.0", 91 | "vuex": "^2.1.1" 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /scripts/webpack.config.dev.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | const webpack = require('webpack') 3 | const config = require('./webpack.config') 4 | 5 | config.devtool = 'cheap-module-inline-source-map' 6 | config.plugins = config.plugins.concat([ 7 | new webpack.NoErrorsPlugin(), 8 | /*eslint-disable */ 9 | new webpack.DefinePlugin({ 10 | __DEV__: true, 11 | 'process.env.NODE_ENV': JSON.stringify('development') 12 | }) 13 | ]) 14 | 15 | module.exports = config 16 | -------------------------------------------------------------------------------- /scripts/webpack.config.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | const path = require('path') 3 | const webpack = require('webpack') 4 | const ExtractTextPlugin = require('extract-text-webpack-plugin') 5 | const appPkg = require('../app/package') 6 | 7 | const postcss = [ 8 | require('postcss-nested'), 9 | require('postcss-import')(), 10 | require('postcss-simple-vars'), 11 | require('postcss-mixins'), 12 | require('autoprefixer')({ 13 | overrideBrowsersList: ['last 2 Chrome versions'] 14 | }) 15 | ] 16 | 17 | module.exports = { 18 | entry: { 19 | app: './src/index.ts', 20 | vendor: ['vue', 'vuex'] 21 | }, 22 | output: { 23 | path: process.cwd() + '/app/libvue', 24 | filename: '[name].js' 25 | }, 26 | resolve: { 27 | extensions: ['.js', '.vue', '.css', '.json', '.ts'], 28 | alias: { 29 | 'vue$': 'vue/dist/vue.esm.js', 30 | src: path.join(__dirname, '../src'), 31 | utils: path.join(__dirname, '../src/utils'), 32 | components: path.join(__dirname, '../src/components'), 33 | css: path.join(__dirname, '../src/css'), 34 | directives: path.join(__dirname, '../src/directives'), 35 | store: path.join(__dirname, '../src/vuex/store') 36 | } 37 | }, 38 | module: { 39 | rules: [ 40 | { 41 | test: /\.tsx?$/, 42 | loaders: 'ts-loader', 43 | exclude: /node_modules/, 44 | options: { 45 | appendTsSuffixTo: [/\.vue$/], 46 | } 47 | }, 48 | { 49 | test: /\.vue$/, 50 | loader: 'vue-loader', 51 | options: { 52 | autoprefixer: false, 53 | postcss, 54 | loaders: { 55 | css: ExtractTextPlugin.extract({ 56 | fallback: 'vue-style-loader', 57 | use: 'css-loader?sourceMap' 58 | } 59 | ) 60 | } 61 | } 62 | }, 63 | { 64 | test: /\.js$/, 65 | loader: 'babel-loader', 66 | exclude: file => ( 67 | /node_modules/.test(file) 68 | ) 69 | }, 70 | { 71 | test: /\.svg$/, 72 | exclude: /node_modules/, 73 | loader: 'svg-inline-loader' 74 | }, 75 | { 76 | test: /\.css$/, 77 | exclude: /node_modules/, 78 | loader: ExtractTextPlugin.extract( 79 | { 80 | fallback: 'style-loader', 81 | use: 'css-loader!postcss-loader' 82 | } 83 | ) 84 | }, 85 | ], 86 | }, 87 | target: 'electron-renderer', 88 | plugins: [ 89 | new webpack.ExternalsPlugin('commonjs2', [ 90 | './vendor/markdown-it-katex', 91 | '../package.json' 92 | ].concat(Object.keys(appPkg.dependencies))), 93 | new ExtractTextPlugin('[name].css'), 94 | new webpack.optimize.CommonsChunkPlugin({ 95 | name: 'vendor', 96 | filename: 'vendor.js' 97 | }) 98 | ], 99 | 100 | devtool: '#eval-source-map' 101 | } 102 | -------------------------------------------------------------------------------- /scripts/webpack.config.prod.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | const webpack = require('webpack') 3 | const config = require('./webpack.config') 4 | 5 | config.plugins = config.plugins.concat([ 6 | new webpack.optimize.UglifyJsPlugin({ 7 | sourceMap: true, 8 | compressor: { 9 | warnings: false 10 | }, 11 | comments: false 12 | }), 13 | new webpack.DefinePlugin({ 14 | '__DEV__': false, 15 | 'process.env.NODE_ENV': JSON.stringify('production') 16 | }) 17 | ]) 18 | 19 | module.exports = config 20 | -------------------------------------------------------------------------------- /src/app.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 17 | 18 | 40 | 41 | 105 | -------------------------------------------------------------------------------- /src/components/footer.vue: -------------------------------------------------------------------------------- 1 | 79 | 80 | 116 | 117 | 160 | -------------------------------------------------------------------------------- /src/components/header.vue: -------------------------------------------------------------------------------- 1 | 148 | 149 | 195 | 196 | 295 | -------------------------------------------------------------------------------- /src/components/svg-icon.vue: -------------------------------------------------------------------------------- 1 | 7 | 8 | 45 | -------------------------------------------------------------------------------- /src/components/tip.vue: -------------------------------------------------------------------------------- 1 | 28 | 29 | 34 | 35 | 60 | -------------------------------------------------------------------------------- /src/components/writing-modes.vue: -------------------------------------------------------------------------------- 1 | 26 | 27 | 77 | -------------------------------------------------------------------------------- /src/css/codemirror/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-codemirror) 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; 17 | } 18 | .cm-s-base16-light .CodeMirror-line::selection, .cm-s-base16-light .CodeMirror-line> span::selection, .cm-s-base16-light .CodeMirror-line> span> span::selection { 19 | background: #e0e0e0; 20 | } 21 | .cm-s-base16-light .CodeMirror-line::-moz-selection, .cm-s-base16-light .CodeMirror-line> span::-moz-selection, .cm-s-base16-light .CodeMirror-line> span> span::-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; 39 | } 40 | .cm-s-base16-light span.cm-comment { 41 | color: #8f5536; 42 | } 43 | .cm-s-base16-light span.cm-atom { 44 | color: #aa759f; 45 | } 46 | .cm-s-base16-light span.cm-number { 47 | color: #aa759f; 48 | } 49 | .cm-s-base16-light span.cm-property, .cm-s-base16-light span.cm-attribute { 50 | color: #90a959; 51 | } 52 | .cm-s-base16-light span.cm-keyword { 53 | color: #ac4142; 54 | } 55 | .cm-s-base16-light span.cm-string { 56 | color: #f4bf75; 57 | } 58 | .cm-s-base16-light span.cm-variable { 59 | color: #90a959; 60 | } 61 | .cm-s-base16-light span.cm-variable-2 { 62 | color: #6a9fb5; 63 | } 64 | .cm-s-base16-light span.cm-def { 65 | color: #d28445; 66 | } 67 | .cm-s-base16-light span.cm-bracket { 68 | color: #202020; 69 | } 70 | .cm-s-base16-light span.cm-tag { 71 | color: #ac4142; 72 | } 73 | .cm-s-base16-light span.cm-link { 74 | color: #aa759f; 75 | } 76 | .cm-s-base16-light span.cm-error { 77 | background: #ac4142; 78 | color: #505050; 79 | } 80 | .cm-s-base16-light .CodeMirror-activeline-background { 81 | background: #DDDCDC; 82 | } 83 | .cm-s-base16-light .CodeMirror-matchingbracket { 84 | text-decoration: underline; 85 | color: black !important; 86 | } 87 | -------------------------------------------------------------------------------- /src/css/codemirror/editor-dialog.css: -------------------------------------------------------------------------------- 1 | .CodeMirror-dialog { 2 | position: absolute; 3 | left: 0; 4 | right: 0; 5 | background: inherit; 6 | z-index: 15; 7 | padding: 0 .8em; 8 | height: 1.75rem; 9 | line-height: 1.75rem; 10 | overflow: hidden; 11 | color: inherit; 12 | } 13 | 14 | .CodeMirror-dialog-top { 15 | top: 0; 16 | border-bottom: 1px solid #ddd; 17 | } 18 | 19 | .CodeMirror-dialog-bottom { 20 | bottom: 0; 21 | } 22 | 23 | .CodeMirror-dialog input { 24 | border: none; 25 | outline: none; 26 | background: transparent; 27 | width: 20em; 28 | color: inherit; 29 | font-family: inherit; 30 | } 31 | 32 | .CodeMirror-dialog button { 33 | font-size: 70%; 34 | } 35 | -------------------------------------------------------------------------------- /src/css/codemirror/editor-reset.css: -------------------------------------------------------------------------------- 1 | .editor { 2 | &.focus-mode { 3 | * { 4 | color: #ccc !important; 5 | } 6 | } 7 | .cm-s-base16-light { 8 | .CodeMirror-activeline-background { 9 | background-color: transparent; 10 | } 11 | .CodeMirror-activeline { 12 | * { 13 | color: #333 !important; 14 | } 15 | } 16 | } 17 | .CodeMirror-dialog input { 18 | font-family: inherit !important; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/css/codemirror/editor-scrollbar.css: -------------------------------------------------------------------------------- 1 | .CodeMirror-simplescroll-horizontal div, .CodeMirror-simplescroll-vertical div { 2 | position: absolute; 3 | background: #f0f0f0; 4 | -moz-box-sizing: border-box; 5 | box-sizing: border-box; 6 | border: 1px solid #e2e2e2; 7 | border-radius: 2px; 8 | } 9 | 10 | .CodeMirror-simplescroll-horizontal, .CodeMirror-simplescroll-vertical { 11 | position: absolute; 12 | z-index: 6; 13 | background: #eee; 14 | } 15 | 16 | .CodeMirror-simplescroll-horizontal { 17 | bottom: 0; left: 0; 18 | height: 8px; 19 | } 20 | .CodeMirror-simplescroll-horizontal div { 21 | bottom: 0; 22 | height: 100%; 23 | } 24 | 25 | .CodeMirror-simplescroll-vertical { 26 | right: 0; top: 0; 27 | width: 8px; 28 | background-color: transparent; 29 | visibility: hidden; 30 | cursor: default; 31 | } 32 | .editor:hover .CodeMirror-simplescroll-vertical { 33 | visibility: visible; 34 | } 35 | .CodeMirror-simplescroll-vertical div { 36 | right: 0; 37 | width: 100%; 38 | } 39 | 40 | 41 | .CodeMirror-overlayscroll .CodeMirror-scrollbar-filler, .CodeMirror-overlayscroll .CodeMirror-gutter-filler { 42 | display: none; 43 | } 44 | 45 | .CodeMirror-overlayscroll-horizontal div, .CodeMirror-overlayscroll-vertical div { 46 | position: absolute; 47 | background: #bcd; 48 | border-radius: 3px; 49 | } 50 | 51 | .CodeMirror-overlayscroll-horizontal, .CodeMirror-overlayscroll-vertical { 52 | position: absolute; 53 | z-index: 6; 54 | } 55 | 56 | .CodeMirror-overlayscroll-horizontal { 57 | bottom: 0; left: 0; 58 | height: 6px; 59 | } 60 | .CodeMirror-overlayscroll-horizontal div { 61 | bottom: 0; 62 | height: 100%; 63 | } 64 | 65 | .CodeMirror-overlayscroll-vertical { 66 | right: 0; top: 0; 67 | width: 6px; 68 | } 69 | .CodeMirror-overlayscroll-vertical div { 70 | right: 0; 71 | width: 100%; 72 | } 73 | -------------------------------------------------------------------------------- /src/css/codemirror/tomorrow-night-bright.css: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | Name: Tomorrow Night - Bright 4 | Author: Chris Kempson 5 | 6 | Port done by Gerard Braad 7 | 8 | */ 9 | 10 | .cm-s-tomorrow-night-bright.CodeMirror { background: #000000; color: #eaeaea; } 11 | .cm-s-tomorrow-night-bright div.CodeMirror-selected { background: #424242; } 12 | .cm-s-tomorrow-night-bright .CodeMirror-gutters { background: #000000; border-right: 0px; } 13 | .cm-s-tomorrow-night-bright .CodeMirror-guttermarker { color: #e78c45; } 14 | .cm-s-tomorrow-night-bright .CodeMirror-guttermarker-subtle { color: #777; } 15 | .cm-s-tomorrow-night-bright .CodeMirror-linenumber { color: #424242; } 16 | .cm-s-tomorrow-night-bright .CodeMirror-cursor { border-left: 1px solid #6A6A6A; } 17 | 18 | .cm-s-tomorrow-night-bright span.cm-comment { color: #d27b53; } 19 | .cm-s-tomorrow-night-bright span.cm-atom { color: #a16a94; } 20 | .cm-s-tomorrow-night-bright span.cm-number { color: #a16a94; } 21 | 22 | .cm-s-tomorrow-night-bright span.cm-property, .cm-s-tomorrow-night-bright span.cm-attribute { color: #99cc99; } 23 | .cm-s-tomorrow-night-bright span.cm-keyword { color: #d54e53; } 24 | .cm-s-tomorrow-night-bright span.cm-string { color: #e7c547; } 25 | 26 | .cm-s-tomorrow-night-bright span.cm-variable { color: #b9ca4a; } 27 | .cm-s-tomorrow-night-bright span.cm-variable-2 { color: #7aa6da; } 28 | .cm-s-tomorrow-night-bright span.cm-def { color: #e78c45; } 29 | .cm-s-tomorrow-night-bright span.cm-bracket { color: #eaeaea; } 30 | .cm-s-tomorrow-night-bright span.cm-tag { color: #d54e53; } 31 | .cm-s-tomorrow-night-bright span.cm-link { color: #a16a94; } 32 | .cm-s-tomorrow-night-bright span.cm-error { background: #d54e53; color: #6A6A6A; } 33 | 34 | .cm-s-tomorrow-night-bright .CodeMirror-activeline-background { background: #2a2a2a; } 35 | .cm-s-tomorrow-night-bright .CodeMirror-matchingbracket { text-decoration: underline; color: white !important; } 36 | -------------------------------------------------------------------------------- /src/css/highlight/github.css: -------------------------------------------------------------------------------- 1 | .github { 2 | /* 3 | 4 | github.com style (c) Vasily Polovnyov 5 | 6 | */ 7 | 8 | .hljs { 9 | display: block; 10 | overflow-x: auto; 11 | padding: 0.5em; 12 | color: #333; 13 | background: #f8f8f8; 14 | } 15 | 16 | .hljs-comment, 17 | .hljs-quote { 18 | color: #998; 19 | font-style: italic; 20 | } 21 | 22 | .hljs-keyword, 23 | .hljs-selector-tag, 24 | .hljs-subst { 25 | color: #333; 26 | font-weight: bold; 27 | } 28 | 29 | .hljs-number, 30 | .hljs-literal, 31 | .hljs-variable, 32 | .hljs-template-variable, 33 | .hljs-tag .hljs-attr { 34 | color: #008080; 35 | } 36 | 37 | .hljs-string, 38 | .hljs-doctag { 39 | color: #d14; 40 | } 41 | 42 | .hljs-title, 43 | .hljs-section, 44 | .hljs-selector-id { 45 | color: #900; 46 | font-weight: bold; 47 | } 48 | 49 | .hljs-subst { 50 | font-weight: normal; 51 | } 52 | 53 | .hljs-type, 54 | .hljs-class .hljs-title { 55 | color: #458; 56 | font-weight: bold; 57 | } 58 | 59 | .hljs-tag, 60 | .hljs-name, 61 | .hljs-attribute { 62 | color: #000080; 63 | font-weight: normal; 64 | } 65 | 66 | .hljs-regexp, 67 | .hljs-link { 68 | color: #009926; 69 | } 70 | 71 | .hljs-symbol, 72 | .hljs-bullet { 73 | color: #990073; 74 | } 75 | 76 | .hljs-built_in, 77 | .hljs-builtin-name { 78 | color: #0086b3; 79 | } 80 | 81 | .hljs-meta { 82 | color: #999; 83 | font-weight: bold; 84 | } 85 | 86 | .hljs-deletion { 87 | background: #fdd; 88 | } 89 | 90 | .hljs-addition { 91 | background: #dfd; 92 | } 93 | 94 | .hljs-emphasis { 95 | font-style: italic; 96 | } 97 | 98 | .hljs-strong { 99 | font-weight: bold; 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /src/css/highlight/tomorrow-night-bright.css: -------------------------------------------------------------------------------- 1 | .tomorrow-night-bright { 2 | /* Tomorrow Night Bright Theme */ 3 | /* Original theme - https://github.com/chriskempson/tomorrow-theme */ 4 | /* http://jmblog.github.com/color-themes-for-google-code-highlightjs */ 5 | 6 | /* Tomorrow Comment */ 7 | .hljs-comment, 8 | .hljs-quote { 9 | color: #969896; 10 | } 11 | 12 | /* Tomorrow Red */ 13 | .hljs-variable, 14 | .hljs-template-variable, 15 | .hljs-tag, 16 | .hljs-name, 17 | .hljs-selector-id, 18 | .hljs-selector-class, 19 | .hljs-regexp, 20 | .hljs-deletion { 21 | color: #d54e53; 22 | } 23 | 24 | /* Tomorrow Orange */ 25 | .hljs-number, 26 | .hljs-built_in, 27 | .hljs-builtin-name, 28 | .hljs-literal, 29 | .hljs-type, 30 | .hljs-params, 31 | .hljs-meta, 32 | .hljs-link { 33 | color: #e78c45; 34 | } 35 | 36 | /* Tomorrow Yellow */ 37 | .hljs-attribute { 38 | color: #e7c547; 39 | } 40 | 41 | /* Tomorrow Green */ 42 | .hljs-string, 43 | .hljs-symbol, 44 | .hljs-bullet, 45 | .hljs-addition { 46 | color: #b9ca4a; 47 | } 48 | 49 | /* Tomorrow Blue */ 50 | .hljs-title, 51 | .hljs-section { 52 | color: #7aa6da; 53 | } 54 | 55 | /* Tomorrow Purple */ 56 | .hljs-keyword, 57 | .hljs-selector-tag { 58 | color: #c397d8; 59 | } 60 | 61 | .hljs { 62 | display: block; 63 | overflow-x: auto; 64 | background: black; 65 | color: #eaeaea; 66 | padding: 0.5em; 67 | } 68 | 69 | .hljs-emphasis { 70 | font-style: italic; 71 | } 72 | 73 | .hljs-strong { 74 | font-weight: bold; 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /src/css/normalize.css: -------------------------------------------------------------------------------- 1 | html { 2 | font-size: 14px; 3 | } 4 | 5 | * { 6 | box-sizing: border-box; 7 | } 8 | 9 | body { 10 | margin: 0; 11 | font-size: 1rem; 12 | line-height: 1.5; 13 | overflow: hidden; 14 | text-rendering: geometricPrecision; 15 | font-family: -apple-system, BlinkMacSystemFont, 16 | 'avenir next', avenir, 17 | helvetica, 'helvetica neue', 18 | Ubuntu, 19 | 'segoe ui', arial, 20 | sans-serif; 21 | } 22 | 23 | html, 24 | body, 25 | #app, 26 | #eme { 27 | height: 100%; 28 | width: 100%; 29 | } 30 | -------------------------------------------------------------------------------- /src/css/reset.css: -------------------------------------------------------------------------------- 1 | .CodeMirror { 2 | font-family: inherit !important; 3 | } 4 | 5 | .markdown-body { 6 | word-break: break-word; 7 | font-family: inherit; 8 | 9 | *::-webkit-scrollbar { 10 | background-color: transparent; 11 | height: 8px; 12 | } 13 | 14 | *::-webkit-scrollbar-thumb { 15 | background-color: #f0f0f0; 16 | border-radius: 2px; 17 | border: 1px solid #e2e2e2; 18 | } 19 | 20 | kbd { 21 | line-height: 1em; 22 | font-size: .85em; 23 | } 24 | } 25 | 26 | .markdown-body ul{ 27 | padding-left: 20px; 28 | } 29 | 30 | .markdown-body { 31 | .task-list { 32 | padding-left: 0; 33 | .task-list-item { 34 | input { 35 | margin: 0; 36 | } 37 | } 38 | } 39 | } 40 | 41 | input, textarea { 42 | &:focus { 43 | outline: none; 44 | } 45 | } 46 | 47 | /* modified gridly */ 48 | .row { display: flex; } 49 | .col { flex: 1; } 50 | @media (max-width: 600px) { 51 | .row { flex-direction: column; } 52 | .col { flex: 0 0 auto; } 53 | } 54 | @media (min-width: 600px) { 55 | .col-tenth { flex: 0 0 10%; } 56 | .col-fifth { flex: 0 0 20%; } 57 | .col-quarter { flex: 0 0 25%; } 58 | .col-third { flex: 0 0 33.3333334%; } 59 | .col-half { flex: 0 0 50%; } 60 | } 61 | -------------------------------------------------------------------------------- /src/css/theme/dark.css: -------------------------------------------------------------------------------- 1 | @import "color-preset"; 2 | 3 | $dark: black; 4 | $light-dark: #333; 5 | $light: #ccc; 6 | 7 | .theme-dark { 8 | background-color: $dark; 9 | .header { 10 | background-color: $dark; 11 | border-bottom-color: $light-dark; 12 | .tab { 13 | border-bottom-color: $light-dark; 14 | border-left-color: $light-dark; 15 | .tab-title { 16 | color: $grey-500; 17 | } 18 | &.current-tab { 19 | background-color: $dark; 20 | .tab-title { 21 | color: white; 22 | } 23 | } 24 | } 25 | &:not(.single-tab) { 26 | .tab:last-child { 27 | border-right-color: $light-dark; 28 | } 29 | } 30 | &.single-tab { 31 | .tab { 32 | .tab-title { 33 | color: white; 34 | } 35 | &.current-tab { 36 | border-color: $light-dark; 37 | } 38 | } 39 | &:hover { 40 | .tab { 41 | &.current-tab { 42 | border-right-color: $light-dark; 43 | } 44 | } 45 | } 46 | } 47 | .settings-trigger { 48 | $color: $light; 49 | $colorActive: $grey-200; 50 | 51 | svg { 52 | color: $color; 53 | circle { 54 | color: $color; 55 | } 56 | } 57 | &:hover { 58 | svg { 59 | color: $colorActive; 60 | circle { 61 | color: $colorActive; 62 | } 63 | } 64 | } 65 | } 66 | } 67 | 68 | .editor { 69 | border-right-color: $light-dark; 70 | .CodeMirror { 71 | background-color: $dark !important; 72 | } 73 | } 74 | 75 | .footer, .footer * { 76 | color: $grey-100; 77 | } 78 | 79 | .footer { 80 | border-top-color: $light-dark; 81 | background-color: $dark; 82 | .footer-icon path { 83 | fill: $grey-100; 84 | } 85 | .footer-right .footer-icon-group .footer-icon-item { 86 | border-color: $light-dark; 87 | .footer-icon path { 88 | fill: #2f2e2e; 89 | } 90 | &:hover { 91 | .footer-icon path { 92 | fill: #ccc; 93 | } 94 | } 95 | &.active { 96 | .footer-icon path { 97 | fill: white; 98 | } 99 | } 100 | } 101 | } 102 | 103 | 104 | .tab-indicator { 105 | .dot { 106 | background-color: $light; 107 | } 108 | .cross { 109 | color: $light; 110 | } 111 | } 112 | 113 | .CodeMirror-simplescroll-horizontal div, 114 | .CodeMirror-simplescroll-vertical div { 115 | background-color: black; 116 | border-color: $grey-800; 117 | } 118 | 119 | .markdown-body { 120 | color: $light; 121 | h1, h2 { 122 | border-bottom-color: $light-dark; 123 | } 124 | kbd { 125 | background-color: transparent; 126 | color: $light; 127 | } 128 | 129 | pre { 130 | background-color: transparent; 131 | code { 132 | color: #50E3C2; 133 | } 134 | } 135 | 136 | *::-webkit-scrollbar-thumb { 137 | background-color: transparent; 138 | border-color: $grey-800; 139 | } 140 | 141 | code { 142 | color: white; 143 | } 144 | } 145 | } 146 | -------------------------------------------------------------------------------- /src/css/theme/light.css: -------------------------------------------------------------------------------- 1 | .theme-light { 2 | .header { 3 | background-color: white; 4 | border-bottom-color: #ddd; 5 | .tab { 6 | border-left-color: #ddd; 7 | border-bottom-color: #ddd; 8 | .tab-title { 9 | color: #999; 10 | } 11 | &.current-tab { 12 | border-left-color: #1976D2; 13 | background-color: white; 14 | .tab-title { 15 | color: #333; 16 | } 17 | } 18 | } 19 | &:not(.single-tab) { 20 | .tab:last-child { 21 | border-right-color: #ddd; 22 | } 23 | } 24 | &.single-tab { 25 | .tab { 26 | &.current-tab { 27 | border-color: #ddd; 28 | } 29 | } 30 | &:hover { 31 | .tab { 32 | &.current-tab { 33 | border-right-color: #ddd; 34 | } 35 | } 36 | } 37 | } 38 | .settings-trigger { 39 | $color: #b1b1b1; 40 | $colorActive: #666; 41 | 42 | svg { 43 | color: $color; 44 | circle { 45 | color: $color; 46 | } 47 | } 48 | &:hover { 49 | svg { 50 | color: $colorActive; 51 | circle { 52 | color: $colorActive; 53 | } 54 | } 55 | } 56 | } 57 | } 58 | 59 | .editor { 60 | border-right-color: #e3e3e3; 61 | .CodeMirror { 62 | background-color: white !important; 63 | } 64 | } 65 | 66 | .footer, .footer * { 67 | color: #666; 68 | } 69 | .footer { 70 | border-top-color: #c2c0c2; 71 | box-shadow: inset 0 1px 0 #f5f4f5; 72 | background-image: linear-gradient(to bottom,#e8e6e8 0,#d1cfd1 100%); 73 | .footer-icon path { 74 | fill: #666; 75 | } 76 | .footer-right { 77 | .footer-icon-group { 78 | .footer-icon-item { 79 | border-color: #c2c0c2 #c2c0c2 #a19fa1; 80 | box-shadow: 0 1px 1px rgba(0,0,0,.06); 81 | background-color: #fcfcfc; 82 | background-image: linear-gradient(to bottom,#fcfcfc 0,#f1f1f1 100%); 83 | &.active { 84 | background-color: #6d6c6d; 85 | background-image: none; 86 | color: white; 87 | border-color: transparent; 88 | .footer-icon { 89 | path { 90 | fill: white; 91 | } 92 | } 93 | } 94 | } 95 | } 96 | } 97 | } 98 | 99 | .tab-indicator { 100 | .dot { 101 | background-color: #4b89ff; 102 | } 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | import Vue from "vue"; 2 | import store from './vuex/store'; 3 | import app from './app.vue'; 4 | 5 | let v = new Vue({ 6 | el: '#eme', 7 | store, 8 | components: {app} 9 | }); 10 | -------------------------------------------------------------------------------- /src/svg-shim.d.ts: -------------------------------------------------------------------------------- 1 | declare module "*.svg" { 2 | const content: string; 3 | export default content; 4 | } 5 | -------------------------------------------------------------------------------- /src/svg/align-horizontal-middle.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /src/svg/arrow-left.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /src/svg/arrow-right.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /src/svg/eye.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /src/svg/pencil.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /src/svg/settings.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /src/utils/common.ts: -------------------------------------------------------------------------------- 1 | //Copyright 2019 The EME authors 2 | 3 | export function truncate(str: string, maxLength: number): string { 4 | if(str.length > maxLength) return str.substr(Math.max(0, maxLength - 3)) + "..."; 5 | return str; 6 | } 7 | -------------------------------------------------------------------------------- /src/utils/dialog.js: -------------------------------------------------------------------------------- 1 | import {remote} from 'electron' 2 | 3 | export default (() => { 4 | const dialog = {} 5 | Object.keys(remote.dialog).forEach(method => { 6 | dialog[method] = (...args) => { 7 | const result = remote.dialog[method](...args) 8 | return result 9 | } 10 | }) 11 | return dialog 12 | })() 13 | -------------------------------------------------------------------------------- /src/utils/dom.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const _ = module.exports = {} 4 | 5 | _.$ = document.querySelector.bind(document) 6 | 7 | _.$$ = document.querySelectorAll.bind(document) 8 | -------------------------------------------------------------------------------- /src/utils/event.js: -------------------------------------------------------------------------------- 1 | import EventEmitter from 'events' 2 | 3 | const event = new EventEmitter() 4 | 5 | export default event 6 | -------------------------------------------------------------------------------- /src/utils/fs-promise.js: -------------------------------------------------------------------------------- 1 | import fs from 'fs' 2 | import pify from 'pify' 3 | 4 | export default pify(fs) 5 | -------------------------------------------------------------------------------- /src/utils/gist.js: -------------------------------------------------------------------------------- 1 | import fetch from 'fetch-enhance' 2 | import store from 'store' 3 | 4 | export function createOrUpdateGist(payload, id) { 5 | const token = store.state.app.settings.tokens.github 6 | const headers = new Headers() 7 | 8 | if (token) { 9 | headers.append('Authorization', `token ${token}`) 10 | } 11 | const shouldUpdate = id && token 12 | const method = shouldUpdate ? 'PATCH' : 'POST' 13 | const url = `https://api.github.com/gists${shouldUpdate ? `/${id}` : ''}` 14 | return fetch(url, { 15 | method, 16 | headers, 17 | body: JSON.stringify(payload) 18 | }) 19 | } 20 | -------------------------------------------------------------------------------- /src/utils/handle-error.js: -------------------------------------------------------------------------------- 1 | import dialog from 'utils/dialog' 2 | import {remote} from 'electron' 3 | 4 | export default err => { 5 | return dialog.showMessageBox(remote.getCurrentWindow(), { 6 | message: err.name || 'Error', 7 | detail: err.message, 8 | buttons: ['OK'] 9 | }) 10 | } 11 | -------------------------------------------------------------------------------- /src/utils/key.js: -------------------------------------------------------------------------------- 1 | import {platform} from './os' 2 | 3 | export const cmdOrCtrl = platform === 'darwin' ? 'command' : 'ctrl' 4 | 5 | -------------------------------------------------------------------------------- /src/utils/make-html.js: -------------------------------------------------------------------------------- 1 | export default ({html, css, data}) => { 2 | return ` 3 | 4 | 5 | 6 | 7 | ${css ? css.map(style => ``).join('\n') : ''} 8 | EME 9 | 10 | 11 | ${Array.isArray(html) ? 12 | html 13 | .map(html => { 14 | const className = data.attrs.align || '' 15 | return `
16 | ${html} 17 |
` 18 | }) 19 | .join('
') : 20 | `
${html}
`} 21 | 22 | 25 | 41 | 42 | ` 43 | } 44 | -------------------------------------------------------------------------------- /src/utils/markdown.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable camelcase, max-params */ 2 | import MarkdownIt from 'markdown-it' 3 | import taskList from 'markdown-it-task-lists' 4 | import hljs from 'highlight.js/lib/highlight' 5 | import frontMatter from 'markdown-it-front-matter' 6 | import katex from './vendor/markdown-it-katex' 7 | 8 | const langs = [ 9 | 'cpp', 10 | 'coffeescript', 11 | 'css', 12 | 'dockerfile', 13 | 'elixir', 14 | 'elm', 15 | 'erlang', 16 | 'go', 17 | 'haskell', 18 | 'ini', 19 | 'javascript', 20 | 'less', 21 | 'lua', 22 | 'makefile', 23 | 'livescript', 24 | 'markdown', 25 | 'matlab', 26 | 'nginx', 27 | 'ocaml', 28 | 'perl', 29 | 'php', 30 | 'python', 31 | 'ruby', 32 | 'scala', 33 | 'rust', 34 | 'scss', 35 | 'sql', 36 | 'stylus', 37 | 'swift', 38 | 'typescript', 39 | 'xml', 40 | 'yaml' 41 | ] 42 | 43 | langs.forEach(lang => { 44 | hljs.registerLanguage(lang, require(`highlight.js/lib/languages/${lang}`)) 45 | }) 46 | 47 | const md = new MarkdownIt({ 48 | html: true, 49 | xhtmlOut: false, 50 | breaks: true, 51 | langPrefix: 'language-', 52 | linkify: true, 53 | typographer: true, 54 | quotes: '“”‘’', 55 | highlight(str, lang) { 56 | if (lang && hljs.getLanguage(lang)) { 57 | try { 58 | return hljs.highlight(lang, str).value 59 | } catch (__) {} 60 | } 61 | 62 | return '' 63 | } 64 | }) 65 | 66 | md.use(taskList) 67 | md.use(katex) 68 | md.use(frontMatter, fm => console.log(fm)) 69 | 70 | // add target _blank 71 | const defaultRender = md.renderer.rules.link_open || function (tokens, idx, options, env, self) { 72 | return self.renderToken(tokens, idx, options) 73 | } 74 | md.renderer.rules.link_open = function (tokens, idx, options, env, self) { 75 | const aIndex = tokens[idx].attrIndex('target') 76 | 77 | if (aIndex < 0) { 78 | tokens[idx].attrPush(['target', '_blank']) 79 | } else { 80 | tokens[idx].attrs[aIndex][1] = '_blank' 81 | } 82 | 83 | return defaultRender(tokens, idx, options, env, self) 84 | } 85 | 86 | export default md 87 | -------------------------------------------------------------------------------- /src/utils/os.js: -------------------------------------------------------------------------------- 1 | import os from 'os' 2 | 3 | const platform = os.platform() 4 | 5 | export { 6 | platform 7 | } 8 | -------------------------------------------------------------------------------- /src/utils/resolve-path.js: -------------------------------------------------------------------------------- 1 | import path from 'path' 2 | import {remote} from 'electron' 3 | 4 | export function appPath(...args) { 5 | return path.join(remote.app.getAppPath(), ...args) 6 | } 7 | -------------------------------------------------------------------------------- /src/utils/sanitize.js: -------------------------------------------------------------------------------- 1 | import sanitizeHtml from 'sanitize-html' 2 | 3 | function sanitizer(html) { 4 | return sanitizeHtml(html, sanitizer.config) 5 | } 6 | 7 | sanitizer.config = { 8 | allowedTags: sanitizeHtml.defaults.allowedTags.concat([ 9 | 'dd', 'del', 'div', 'dl', 'dt', 'h1', 'h2', 'iframe', 'img', 'input', 'ins', 'meta', 'path', 'pre', 's', 'span', 'sub', 'sup', 'svg' 10 | ]), 11 | allowedAttributes: { 12 | h1: ['id'], 13 | h2: ['id'], 14 | h3: ['id'], 15 | h4: ['id'], 16 | h5: ['id'], 17 | h6: ['id'], 18 | a: ['href', 'id', 'name', 'target', 'title', 'aria-hidden'], 19 | img: ['alt', 'id', 'src', 'width', 'height', 'align', 'valign', 'title', 'style'], 20 | p: ['align'], 21 | meta: ['name', 'content'], 22 | iframe: ['src', 'frameborder', 'allowfullscreen'], 23 | input: ['checked', 'class', 'disabled', 'type'], 24 | div: ['id'], 25 | pre: [], 26 | td: ['colspan', 'rowspan', 'style'], 27 | th: ['colspan', 'rowspan', 'style'], 28 | del: ['cite', 'datetime'], 29 | ins: ['cite', 'datetime'], 30 | path: ['d'], 31 | svg: ['aria-hidden', 'height', 'version', 'viewbox', 'width'], 32 | span: ['class', 'style'], 33 | ul: ['class'], 34 | li: ['class'] 35 | }, 36 | exclusiveFilter(frame) { 37 | // Allow Task List items 38 | if (frame.tag === 'input') { 39 | const isTaskItem = (frame.attribs.class && frame.attribs.class.indexOf('task-list-item-checkbox') > -1) 40 | const isCheckbox = (frame.attribs.type && frame.attribs.type === 'checkbox') 41 | const isDisabled = Object.prototype.hasOwnProperty.call(frame.attribs, 'disabled') 42 | return !(isTaskItem && isCheckbox && isDisabled) 43 | } 44 | 45 | // Allow YouTube iframes 46 | if (frame.tag !== 'iframe') return false 47 | return !String(frame.attribs.src).match(/^(https?:)?\/\/(www\.)?youtube\.com/) 48 | }, 49 | transformTags: { 50 | td: sanitizeCellStyle, 51 | th: sanitizeCellStyle 52 | } 53 | } 54 | 55 | // Allow table cell alignment 56 | function sanitizeCellStyle(tagName, attribs) { 57 | // if we don't add the 'style' to the allowedAttributes above, it will be 58 | // stripped out by the time we get here, so we have to filter out 59 | // everything but `text-align` in case something else tries to sneak in 60 | function cell(alignment) { 61 | const attributes = attribs 62 | if (alignment) { 63 | attributes.style = 'text-align:' + alignment 64 | } else { 65 | delete attributes.style 66 | } 67 | return { 68 | tagName, 69 | attribs: attributes 70 | } 71 | } 72 | 73 | // look for CSS `text-align` directives 74 | const alignmentRegEx = /text-align\s*:\s*(left|center|right)[\s;$]*/igm 75 | const result = alignmentRegEx.exec(attribs.style || '') 76 | return result ? cell(result[1]) : cell() 77 | } 78 | 79 | export default sanitizer 80 | -------------------------------------------------------------------------------- /src/utils/tab.js: -------------------------------------------------------------------------------- 1 | 2 | let id = 0 3 | 4 | export const tabId = { 5 | create() { 6 | return id++ 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /src/utils/tildify.ts: -------------------------------------------------------------------------------- 1 | // Tildify absolute paths 2 | 3 | import * as path from 'path'; 4 | import * as os from 'os'; 5 | 6 | export default function (currentPath: string): string { 7 | const normalizedPath = path.normalize(currentPath) + path.sep; 8 | const homeDir = os.homedir(); 9 | 10 | return (normalizedPath.indexOf(homeDir) === 0 ? 11 | normalizedPath.replace(homeDir + path.sep, `~${path.sep}`): 12 | normalizedPath.slice(0, -1)); 13 | } 14 | -------------------------------------------------------------------------------- /src/utils/tips.js: -------------------------------------------------------------------------------- 1 | import {platform} from './os' 2 | 3 | const metaKey = platform === 'darwin' ? 'Command' : 'Ctrl' 4 | 5 | export default [ 6 | `Use ${metaKey} \\ to toggle focus mode`, 7 | `You can drag and drop a file in the editor`, 8 | `Double click the header to create new tab`, 9 | `Use ${metaKey} Shift M to switch writing mode`, 10 | `Try distraction free mode ${metaKey} J when you enter full screen`, 11 | `Arrow keys are available in \`Preview Only\` mode to navigate between slides`, 12 | `At nights use ${metaKey} Shift N to make your eyes feel better` 13 | ] 14 | -------------------------------------------------------------------------------- /src/utils/transitions.js: -------------------------------------------------------------------------------- 1 | import store from 'src/vuex/store' 2 | 3 | const transition = { 4 | type: 'animation', 5 | beforeEnter() { 6 | store.commit('SLIDE_SWITCHING', true) 7 | }, 8 | afterLeave() { 9 | store.commit('SLIDE_SWITCHING', false) 10 | } 11 | } 12 | 13 | const types = [ 14 | 'bounce', 15 | 'slide', 16 | 'fade', 17 | 'zoom' 18 | ] 19 | 20 | /** 21 | * TODO Replace Vue.transition(`${type}-${direction}`) with a component that uses the new or element as its root 22 | * @deprecated in Vue2, use transition elements instead: https://vuejs.org/v2/guide/transitions.html#Reusable-Transitions 23 | */ 24 | export default Vue => { 25 | const makeTransition = (type, direction) => { 26 | const directions = direction === 'left' ? 27 | ['Right', 'Left'] : 28 | ['Left', 'Right'] 29 | 30 | Vue.transition(`${type}-${direction}`, { 31 | ...transition, 32 | enterClass: `${type}In${directions[0]}`, 33 | leaveClass: `${type}Out${directions[1]}` 34 | }) 35 | } 36 | 37 | for (const type of types) { 38 | makeTransition(type, 'left') 39 | makeTransition(type, 'right') 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/utils/wordcount.ts: -------------------------------------------------------------------------------- 1 | // A word counter for markdown contents 2 | // A wrapper for '@wordpress/wordcount' 3 | 4 | import { count } from '@wordpress/wordcount'; 5 | 6 | export default function (content: string): number { 7 | let cnContent: string = filterEn(content); 8 | let enContent: string = filterCn(content); 9 | 10 | return count(cnContent, 'characters_excluding_spaces', {}) + 11 | count(enContent, 'words', {}); 12 | } 13 | 14 | function filterCn(content: string): string { 15 | return content.replace(/[\u4E00-\u9FA5]/g, ' '); 16 | } 17 | 18 | function filterEn(content: string): string { 19 | let res = content.match(/[\u4E00-\u9FA5]/g || []); 20 | if (res == null) return ''; 21 | return res.join(' '); 22 | } 23 | -------------------------------------------------------------------------------- /src/views/about.vue: -------------------------------------------------------------------------------- 1 | 2 | 40 | 41 | 58 | 59 | 71 | -------------------------------------------------------------------------------- /src/vue-shims.d.ts: -------------------------------------------------------------------------------- 1 | declare module "*.vue" { 2 | import Vue from "vue"; 3 | export default Vue; 4 | } 5 | -------------------------------------------------------------------------------- /src/vuex/modules/app.js: -------------------------------------------------------------------------------- 1 | import {remote} from 'electron' 2 | 3 | const currentWindow = remote.getCurrentWindow() 4 | 5 | const state = { 6 | showPreferencePane: false, 7 | settings: JSON.parse(JSON.stringify(currentWindow.$config.get('settings'))) 8 | } 9 | 10 | const mutations = { 11 | TOGGLE_PREFERENCE_PANE(state) { 12 | state.showPreferencePane = !state.showPreferencePane 13 | }, 14 | UPDATE_SETTINGS(state, settings) { 15 | state.settings = settings 16 | }, 17 | CHANGE_THEME(state, theme) { 18 | if (theme == 'light') { 19 | state.settings.theme = 'light' 20 | state.settings.preview.highlight = 'github' 21 | state.settings.editor.theme = 'base16-light' 22 | } else { 23 | state.settings.theme = 'dark' 24 | state.settings.preview.highlight = 'tomorrow-night-bright' 25 | state.settings.editor.theme = 'tomorrow-night-bright' 26 | } 27 | }, 28 | CHANGE_THEME_CONTROL(state, themeControl) { 29 | state.settings.themeControl = themeControl 30 | } 31 | } 32 | 33 | export default { 34 | state, 35 | mutations 36 | } 37 | -------------------------------------------------------------------------------- /src/vuex/modules/editor.js: -------------------------------------------------------------------------------- 1 | import path from 'path' 2 | import {remote} from 'electron' 3 | import md from 'utils/markdown' 4 | import fm from 'front-matter' 5 | import sanitize from 'utils/sanitize' 6 | 7 | const win = remote.getCurrentWindow() 8 | win.$state.unsaved = 0 9 | 10 | const focusEditor = (tabs, index) => { 11 | const tab = tabs[index] 12 | if (tab && tab.editor) { 13 | tab.editor.refresh() 14 | tab.editor.focus() 15 | } 16 | } 17 | 18 | const renderHTML = tab => { 19 | const render = tab => md.render(tab.content).replace(/src="([^"]+)"/g, (m, p1) => { 20 | if (p1[0] === '.') { 21 | p1 = path.join(path.dirname(tab.filePath), p1) 22 | return `src="${p1}"` 23 | } 24 | return m 25 | }) 26 | 27 | const data = fm(tab.content) 28 | 29 | return { 30 | attrs: data.attributes, 31 | html: sanitize(render({content: data.body, filePath: tab.filePath})) 32 | } 33 | } 34 | 35 | const state = { 36 | tabs: [], 37 | draggingTab: false, 38 | currentTabIndex: 0 39 | } 40 | 41 | const mutations = { 42 | INIT_NEW_TAB(state, payload) { 43 | const tab = { 44 | ...payload, 45 | ...renderHTML(payload) 46 | } 47 | state.tabs.push(tab) 48 | state.currentTabIndex++ 49 | }, 50 | UPDATE_CONTENT(state, {index, content}) { 51 | const tab = state.tabs[index] 52 | tab.content = content 53 | // render html in non-writing mode 54 | if (tab.writingMode !== 'editor') { 55 | const parsed = renderHTML({ 56 | filePath: tab.filePath, 57 | content 58 | }) 59 | tab.html = parsed.html 60 | tab.attrs = parsed.attrs 61 | } 62 | }, 63 | UPDATE_FILE_PATH(state, {index, filePath}) { 64 | const tab = state.tabs[index] 65 | tab.filePath = filePath 66 | document.title = `${path.basename(filePath)} - EME` 67 | }, 68 | UPDATE_CONTENT_WITH_FILEPATH(state, { 69 | index, 70 | content, 71 | filePath, 72 | gist, 73 | watcher 74 | }) { 75 | const tab = state.tabs[index] 76 | const parsed = renderHTML({ 77 | content, 78 | filePath 79 | }) 80 | tab.content = content 81 | tab.html = parsed.html 82 | tab.attrs = parsed.attrs 83 | tab.filePath = filePath 84 | tab.gist = gist 85 | tab.watcher = watcher 86 | document.title = `${path.basename(filePath)} - EME` 87 | }, 88 | UPDATE_SAVE_STATUS(state, {index, saved}) { 89 | const tab = state.tabs[index] 90 | const fileName = tab.filePath ? 91 | path.basename(tab.filePath) : 92 | 'untitled' 93 | 94 | if (saved) { 95 | if (saved !== tab.saved) win.$state.unsaved-- 96 | document.title = `${fileName} - EME` 97 | } else { 98 | if (saved !== tab.saved) win.$state.unsaved++ 99 | document.title = `${fileName} * - EME` 100 | } 101 | 102 | tab.saved = saved 103 | }, 104 | UPDATE_RENAME_STATUS(state, {index, rename}) { 105 | state.tabs[index].rename = rename 106 | }, 107 | UPDATE_DRAGGING_STATUS(state, dragging) { 108 | state.draggingTab = dragging 109 | }, 110 | UPDATE_EDITOR_SPLIT(state, {index, split}) { 111 | state.tabs[index].split = split 112 | }, 113 | SET_EDITOR(state, {index, editor}) { 114 | state.currentTabIndex = index 115 | state.tabs[index].editor = editor 116 | }, 117 | SET_CURRENT_TAB(state, index) { 118 | state.currentTabIndex = index 119 | }, 120 | CLOSE_TAB(state, indexToClose) { 121 | const tab = state.tabs[indexToClose] 122 | if (tab && tab.watcher) { 123 | tab.watcher.close() 124 | } 125 | if (state.currentTabIndex !== 0 && indexToClose <= state.currentTabIndex) { 126 | state.currentTabIndex-- 127 | } 128 | setTimeout(() => { 129 | focusEditor(state.tabs, state.currentTabIndex) 130 | }, 0) 131 | state.tabs.splice(indexToClose, 1) 132 | }, 133 | SET_WRITING_MODE(state, {index, mode}) { 134 | const tab = state.tabs[index] 135 | if (tab.split === 100) { 136 | if (mode === 'default') tab.split = 50 137 | else if (mode === 'preview') tab.split = 0 138 | } else if (tab.split === 50) { 139 | if (mode === 'editor') tab.split = 100 140 | else if (mode === 'preview') tab.split = 0 141 | } else if (tab.split === 0) { 142 | if (mode === 'editor') tab.split = 100 143 | else if (mode === 'default') tab.split = 50 144 | } 145 | 146 | // if previous mode is writing mode 147 | // render html before switching 148 | if (tab.writingMode === 'editor') { 149 | const parsed = renderHTML(tab) 150 | tab.html = parsed.html 151 | tab.attrs = parsed.attrs 152 | } 153 | tab.writingMode = mode 154 | 155 | setTimeout(() => { 156 | tab.editor.refresh() 157 | if (mode !== 'preview') tab.editor.focus() 158 | }, 0) 159 | }, 160 | START_EXPORTING(state, {index}) { 161 | const tab = state.tabs[index] 162 | tab.exporting = true 163 | }, 164 | FINISH_EXPORTING_PDF(state, {index, pdf}) { 165 | const tab = state.tabs[index] 166 | tab.pdf = pdf 167 | tab.exporting = false 168 | }, 169 | REORDER_TABS(state, {oldIndex, newIndex}) { 170 | const tabs = state.tabs 171 | if (newIndex >= tabs.length) { 172 | let k = newIndex - tabs.length 173 | while ((k--) + 1) { 174 | tabs.push(undefined) 175 | } 176 | } 177 | tabs.splice(newIndex, 0, tabs.splice(oldIndex, 1)[0]) 178 | state.currentTabIndex = newIndex 179 | setTimeout(() => { 180 | focusEditor(state.tabs, state.currentTabIndex) 181 | }, 0) 182 | }, 183 | TOGGLE_FOCUS_MODE(state) { 184 | const tab = state.tabs[state.currentTabIndex] 185 | tab.isFocusMode = !tab.isFocusMode 186 | }, 187 | TOGGLE_VIM_MODE(state) { 188 | const tab = state.tabs[state.currentTabIndex] 189 | tab.isVimMode = !tab.isVimMode 190 | }, 191 | UPDATE_FILE_GIST(state, gistId) { 192 | const tab = state.tabs[state.currentTabIndex] 193 | tab.gist = gistId 194 | } 195 | } 196 | 197 | export default { 198 | state, 199 | mutations 200 | } 201 | -------------------------------------------------------------------------------- /src/vuex/store.d.ts: -------------------------------------------------------------------------------- 1 | declare let store: Vuex.Store; 2 | 3 | export default store; 4 | -------------------------------------------------------------------------------- /src/vuex/store.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import Vuex from 'vuex' 3 | 4 | Vue.use(Vuex) 5 | 6 | import app from './modules/app' 7 | import editor from './modules/editor' 8 | 9 | const store = new Vuex.Store({ 10 | modules: { 11 | app, 12 | editor 13 | } 14 | }) 15 | 16 | export default store 17 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "outDir": "./dist/", 4 | "allowJs": true, 5 | "allowSyntheticDefaultImports": true, 6 | "sourceMap": true, 7 | "strict": true, 8 | "noImplicitReturns": true, 9 | "module": "es2015", 10 | "moduleResolution": "node", 11 | "target": "es5", 12 | "experimentalDecorators": true 13 | }, 14 | "include": [ 15 | "./src/**/*", 16 | "./src/*" 17 | ] 18 | } 19 | --------------------------------------------------------------------------------