├── src ├── styl │ ├── imports │ │ ├── variables.styl │ │ ├── main.styl │ │ └── universal.styl │ └── index.styl ├── static │ ├── icon │ │ ├── icon128.png │ │ ├── icon16.png │ │ └── icon48.png │ ├── fonts │ │ ├── fira-mono.woff │ │ └── fira-mono.woff2 │ └── svg │ │ ├── edit.svg │ │ ├── close.svg │ │ ├── view.svg │ │ ├── bin.svg │ │ ├── save.svg │ │ └── settings.svg ├── pug │ ├── index.pug │ └── includes │ │ ├── head.pug │ │ └── body.pug ├── manifest.json ├── markdown.css └── index.js ├── assets ├── demo.gif └── screenshots │ ├── ss1.png │ ├── ss2.png │ ├── ss3.png │ └── promotional1.png ├── .travis.yml ├── .github └── FUNDING.yml ├── now.json ├── LICENSE ├── webpack.config.js ├── package.json ├── README.md └── .gitignore /src/styl/imports/variables.styl: -------------------------------------------------------------------------------- 1 | // Colours 2 | 3 | -------------------------------------------------------------------------------- /assets/demo.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/plibither8/markdown-new-tab/HEAD/assets/demo.gif -------------------------------------------------------------------------------- /assets/screenshots/ss1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/plibither8/markdown-new-tab/HEAD/assets/screenshots/ss1.png -------------------------------------------------------------------------------- /assets/screenshots/ss2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/plibither8/markdown-new-tab/HEAD/assets/screenshots/ss2.png -------------------------------------------------------------------------------- /assets/screenshots/ss3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/plibither8/markdown-new-tab/HEAD/assets/screenshots/ss3.png -------------------------------------------------------------------------------- /src/static/icon/icon128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/plibither8/markdown-new-tab/HEAD/src/static/icon/icon128.png -------------------------------------------------------------------------------- /src/static/icon/icon16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/plibither8/markdown-new-tab/HEAD/src/static/icon/icon16.png -------------------------------------------------------------------------------- /src/static/icon/icon48.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/plibither8/markdown-new-tab/HEAD/src/static/icon/icon48.png -------------------------------------------------------------------------------- /src/static/fonts/fira-mono.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/plibither8/markdown-new-tab/HEAD/src/static/fonts/fira-mono.woff -------------------------------------------------------------------------------- /src/static/fonts/fira-mono.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/plibither8/markdown-new-tab/HEAD/src/static/fonts/fira-mono.woff2 -------------------------------------------------------------------------------- /assets/screenshots/promotional1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/plibither8/markdown-new-tab/HEAD/assets/screenshots/promotional1.png -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - 8.11.1 4 | install: 5 | - npm install 6 | script: 7 | - npm run lint 8 | - npm run build -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: plibither8 4 | patreon: plibither8 5 | custom: ['https://paypal.me/plibither8', 'https://www.buymeacoffee.com/plibither8'] 6 | -------------------------------------------------------------------------------- /now.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "mdnt", 3 | "version": 2, 4 | "github": { 5 | "enabled": true, 6 | "silent": true 7 | }, 8 | "builds": [{ 9 | "src": "package.json", 10 | "use": "@now/static-build" 11 | }] 12 | } 13 | -------------------------------------------------------------------------------- /src/pug/index.pug: -------------------------------------------------------------------------------- 1 | //Jai Hind. 2 | 3 | doctype html 4 | 5 | html(lang='en') 6 | 7 | head 8 | 9 | include ./includes/head.pug 10 | 11 | body.flex 12 | 13 | include ./includes/body.pug 14 | 15 | script(src="activate-power-mode.js") 16 | -------------------------------------------------------------------------------- /src/styl/imports/main.styl: -------------------------------------------------------------------------------- 1 | @charset "utf-8" 2 | 3 | @font-face 4 | font-family 'Fira Mono' 5 | src url(/static/fonts/fira-mono.woff2) format('woff2'), 6 | url(/static/fonts/fira-mono.woff) format('woff') 7 | 8 | 9 | ::selection 10 | background #fffffffe 11 | color black 12 | 13 | html, body, :root 14 | width 100% 15 | height 100% 16 | background #000 17 | font-family 'Fira Mono' 18 | 19 | @import "variables" 20 | @import "universal" -------------------------------------------------------------------------------- /src/pug/includes/head.pug: -------------------------------------------------------------------------------- 1 | title New Tab 2 | 3 | meta(charset="utf-8") 4 | meta(lang="en") 5 | meta(name="viewport" content="width=device-width, initial-scale=1.0") 6 | meta(http-equiv="X-UA-Compatible" content="IE=edge") 7 | 8 | meta(name="author" content="Mihir Chaturvedi (plibither8)") 9 | meta(name="description" content="Markdown-styled 'New Tab' page") 10 | 11 | link(rel="stylesheet" href="markdown.css") 12 | 13 | script(src="browser-polyfill.min.js") 14 | -------------------------------------------------------------------------------- /src/styl/imports/universal.styl: -------------------------------------------------------------------------------- 1 | * 2 | padding 0 3 | margin 0 4 | position relative 5 | box-sizing border-box 6 | text-rendering optimizeLegibility 7 | 8 | 9 | *.flex 10 | display flex 11 | align-items center 12 | justify-content center 13 | 14 | *.noselect 15 | user-select none 16 | 17 | *.nodisplay 18 | display none 19 | 20 | *.nodrag 21 | user-drag none 22 | -webkit-user-drag none 23 | 24 | 25 | *.container 26 | width 100% 27 | height 100% 28 | justify-content center -------------------------------------------------------------------------------- /src/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Markdown New Tab", 3 | "short_name": "Markdown New Tab", 4 | "version": "0.0.0", 5 | "description": "Take down notes 🗒️, save reminders ⏰, paste links 🔗, create checklists ☑️ all using markdown... directly in your New Tab!", 6 | "manifest_version": 2, 7 | "permissions": [ 8 | "storage" 9 | ], 10 | "chrome_url_overrides": { 11 | "newtab": "index.html" 12 | }, 13 | "author": "Mihir Chaturvedi (plibither8)", 14 | "icons": { 15 | "16": "static/icon/icon16.png", 16 | "48": "static/icon/icon48.png", 17 | "128": "static/icon/icon128.png" 18 | }, 19 | "applications": { 20 | "gecko": { 21 | "id": "markdownnewtab@mihir.ch" 22 | } 23 | } 24 | } -------------------------------------------------------------------------------- /src/static/svg/edit.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /src/static/svg/close.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2019-, Mihir Chaturvedi 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of 4 | this software and associated documentation files (the "Software"), to deal in 5 | the Software without restriction, including without limitation the rights to 6 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 7 | the Software, and to permit persons to whom the Software is furnished to do so, 8 | subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 15 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 16 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 17 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 18 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /src/static/svg/view.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | -------------------------------------------------------------------------------- /src/static/svg/bin.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | const path = require('path'); 3 | const SizePlugin = require('size-plugin'); 4 | const CopyWebpackPlugin = require('copy-webpack-plugin'); 5 | const HtmlWebpackPlugin = require('html-webpack-plugin'); 6 | const TerserPlugin = require('terser-webpack-plugin'); 7 | 8 | module.exports = () => ({ 9 | devtool: 'sourcemap', 10 | stats: 'errors-only', 11 | entry: { 12 | index: './src/index' 13 | }, 14 | output: { 15 | path: path.join(__dirname, 'dist'), 16 | filename: '[name].js' 17 | }, 18 | module: { 19 | rules: [{ 20 | test: /\.pug/, 21 | use: 'pug-loader' 22 | }, 23 | { 24 | test: /\.styl/, 25 | use: [ 26 | 'style-loader', 27 | 'css-loader', 28 | 'stylus-loader' 29 | ] 30 | }] 31 | }, 32 | plugins: [ 33 | new SizePlugin(), 34 | new CopyWebpackPlugin([ 35 | { 36 | context: './src', 37 | from: '*', 38 | ignore: '*.js' 39 | }, 40 | { 41 | from: 'src/static', 42 | to: 'static' 43 | }, 44 | { 45 | from: 'node_modules/webextension-polyfill/dist/browser-polyfill.min.js' 46 | }, 47 | { 48 | from: 'node_modules/activate-power-mode/dist/activate-power-mode.js' 49 | } 50 | ]), 51 | new HtmlWebpackPlugin({ 52 | title: 'Markdown New Tab', 53 | template: './src/pug/index.pug' 54 | }) 55 | ], 56 | optimization: { 57 | // Without this, function names will be garbled and enableFeature won't work 58 | concatenateModules: true, 59 | 60 | // Automatically enabled on production; keeps it somewhat readable for AMO reviewers 61 | minimizer: [ 62 | new TerserPlugin({ 63 | parallel: true, 64 | terserOptions: { 65 | mangle: false, 66 | compress: false, 67 | output: { 68 | beautify: true, 69 | indent_level: 2 // eslint-disable-line camelcase 70 | } 71 | } 72 | }) 73 | ] 74 | } 75 | }); 76 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "private": true, 3 | "scripts": { 4 | "lint": "xo", 5 | "lint-fix": "xo --fix", 6 | "clean": "rimraf dist", 7 | "build": "webpack --mode=production", 8 | "watch": "webpack --mode=development --watch", 9 | "watch:firefox": "web-ext run --source-dir=dist", 10 | "version": "dot-json dist/manifest.json version $VER", 11 | "release:amo": "cd dist && web-ext-submit --api-key=\"$WEB_EXT_API_KEY\" --api-secret=\"$WEB_EXT_API_SECRET\"", 12 | "release:cws": "cd dist && webstore upload --auto-publish", 13 | "release": "VER=$(daily-version) npm-run-all clean build version release:amo create-git-tag", 14 | "create-git-tag": "git tag $VER -m $VER && git push origin $VER" 15 | }, 16 | "dependencies": { 17 | "activate-power-mode": "^1.0.0", 18 | "dateformat": "^3.0.3", 19 | "indent-textarea": "^1.0.4", 20 | "showdown": "^1.9.0", 21 | "timeago.js": "^4.0.0-beta.2" 22 | }, 23 | "devDependencies": { 24 | "chrome-webstore-upload-cli": "^1.2.0", 25 | "copy-webpack-plugin": "^5.0.0", 26 | "css-loader": "^3.0.0", 27 | "daily-version": "^0.12.0", 28 | "dot-json": "^1.1.0", 29 | "html-webpack-plugin": "^3.2.0", 30 | "npm-run-all": "^4.1.5", 31 | "pug": "^2.0.4", 32 | "pug-loader": "^2.4.0", 33 | "rimraf": "^2.6.3", 34 | "size-plugin": "^1.1.2", 35 | "style-loader": "^0.23.1", 36 | "stylus": "^0.54.5", 37 | "stylus-loader": "^3.0.2", 38 | "terser-webpack-plugin": "^1.2.3", 39 | "web-ext": "^3.0.0", 40 | "web-ext-submit": "^2.9.3", 41 | "webext-options-sync": "^0.16.0", 42 | "webextension-polyfill": "^0.4.0", 43 | "webpack": "^4.29.4", 44 | "webpack-cli": "^3.2.3", 45 | "xo": "^0.24.0" 46 | }, 47 | "xo": { 48 | "envs": [ 49 | "browser" 50 | ], 51 | "globals": [ 52 | "browser", 53 | "POWERMODE" 54 | ], 55 | "rules": { 56 | "import/no-unassigned-import": 0, 57 | "import/no-named-as-default": 0, 58 | "no-script-url": 0, 59 | "brace-style": 0 60 | } 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /src/static/svg/save.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 7 | 8 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | -------------------------------------------------------------------------------- /src/static/svg/settings.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | 7 | 28 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | -------------------------------------------------------------------------------- /src/pug/includes/body.pug: -------------------------------------------------------------------------------- 1 | .container.flex 2 | 3 | section.main.flex.noblur 4 | 5 | textarea(autofocus spellcheck="false") 6 | 7 | .markdown-body.nodisplay 8 | 9 | .buttons.flex.noselect 10 | 11 | .button#edit 12 | img(src="/static/svg/edit.svg").nodrag 13 | 14 | .button#save 15 | img(src="/static/svg/save.svg").nodrag 16 | 17 | section.history.flex.nodisplay 18 | 19 | .header.flex 20 | 21 | h1 Revision History 22 | 23 | .noselect.close#closeHistory 24 | img(src="/static/svg/close.svg").nodrag 25 | 26 | .list 27 | 28 | section.settings.flex.nodisplay 29 | 30 | .header.flex 31 | 32 | h1 Settings 33 | 34 | .noselect.close#closeSettings 35 | img(src="/static/svg/close.svg").nodrag 36 | 37 | .body.noselect 38 | 39 | .item.flex(data-setting='saveHistory') 40 | 41 | .main.flex 42 | label Save Revision History 43 | p 44 | | If disabled, saving of revision history will be paused. 45 | br 46 | | Saved revisions will still be viewable. 47 | 48 | .switch.flex 49 | .box 50 | 51 | .item.flex(data-setting='cursorLastPosition') 52 | 53 | .main.flex 54 | label Save Cursor Position 55 | p Start editing where you left off last time you saved the document. If disabled, cursor is always positioned at the start. 56 | 57 | .switch.flex 58 | .box 59 | 60 | .item.flex(data-setting='returnKeyToggle') 61 | 62 | .main.flex 63 | label Use ⌘/^ + ↵ for editing and saving 64 | p Instead of saving and editing with ⌘/^ + S and ⌘/^ + X respectively, use ⌘/^ + ↵ (return/enter key) for toggling edit and save modes. 65 | 66 | .switch.flex 67 | .box 68 | 69 | .item.flex(data-setting='enablePowerMode') 70 | 71 | .main.flex 72 | label Enable POWER MODE 73 | p When typing, display animated particles thrown out from the cursor 74 | 75 | .switch.flex 76 | .box 77 | 78 | .item.dateFormat.flex(data-setting='dateFormat') 79 | 80 | .main.flex 81 | label Date Format 82 | p Customise date/time format to be displayed on the bottom of the window. Refer to 83 | a(href='https://github.com/felixge/node-dateformat#mask-options') this table 84 | |  to see how to properly format (refresh page to see the changes) 85 | form 86 | input(type='text' name='dateFormat' placeholder='dd/mm/yyyy - HH:MM:ss') 87 | input(type='submit' name='submit' value='Save') 88 | 89 | .item.customCss.flex(data-setting='customCss') 90 | 91 | .main.flex 92 | label Custom CSS 93 | p Add custom CSS that will override current page styles 94 | form 95 | textarea(name='dateFormat' placeholder='Custom CSS' rows=5) 96 | input(type='submit' name='submit' value='Save') 97 | 98 | //- .item.flex(data-setting='PowerModeColor') 99 | 100 | //- .main.flex 101 | //- label Colored POWER MODE 102 | //- p Add colors to particles from POWER MODE (if enabled) 103 | 104 | //- .switch.flex 105 | //- .box 106 | 107 | 108 | //- .item.flex(data-setting='PowerModeShake') 109 | 110 | //- .main.flex 111 | //- label Shake on POWER MODE 112 | //- p If POWER MODE is enabled, entire screen will "shake" when typing 113 | 114 | //- .switch.flex 115 | //- .box 116 | 117 | 118 | section.bar.flex.noselect 119 | 120 | .button.flex#settings 121 | img(src="/static/svg/settings.svg").nodrag 122 | p#time 123 | p#lastEdited 124 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Markdown New Tab 2 | 3 | [link-cws]: https://chrome.google.com/webstore/detail/demppioeofcekpjcnlkmdjbabifjnokj "Version published on Chrome Web Store" 4 | [link-amo]: https://addons.mozilla.org/en-US/firefox/addon/markdown-new-tab/ "Version published on Mozilla Add-ons" 5 | 6 | [![Licence](https://img.shields.io/badge/License-MIT-green.svg?style=flat-square)](LICENSE) 7 | [![Build Status](https://img.shields.io/travis/plibither8/markdown-new-tab/master.svg?style=flat-square)](https://travis-ci.org/plibither8/markdown-new-tab) 8 | [![Chrome Web Store](https://img.shields.io/chrome-web-store/users/demppioeofcekpjcnlkmdjbabifjnokj.svg?label=chrome%20users&style=flat-square)][link-cws] 9 | [![Mozilla Add-on](https://img.shields.io/amo/users/markdown-new-tab.svg?label=firefox%20users&style=flat-square)][link-amo] 10 | ![Made with love in India](https://madewithlove.now.sh/in?heart=true&colorB=%23ff701f&template=flat-square) 11 | 12 | > Take down notes 🗒️, save reminders ⏰, paste links 🔗, create checklists ☑️ or tables, all using markdown... directly in your 'New Tab' page! Markdown New Tab is a replacement for the default 'New Tab' page on Google Chrome 🆕 🎉. 13 | 14 |

15 | Preview it! 16 |

17 | 18 | ![Demo GIF](/assets/demo.gif) 19 | 20 | ## Install 21 | 22 | - [**Chrome** extension][link-cws] [][link-cws] 23 | - [**Firefox** add-on][link-amo] [][link-amo] 24 | - **Opera** extension: Use [this Opera extension](https://addons.opera.com/en/extensions/details/download-chrome-extension-9/) to install the Chrome version. 25 | 26 | ## About 27 | 28 | Markdown New Tab is a replacement for the default Google Chrome new tab page. Refer to [this brilliant guide](https://github.github.com/gfm/) to get familiar with the markdown syntax. 29 | 30 | ### Features 31 | 32 | * Take down _**`styled`**_ notes 🗒️, create checklists ☑️, links 🔗, tables and reminders ⏰, add images 🖼️ (and all other frills associated with markdown [M↓]) 33 | 34 | * ⏰ 💾 Automatically saves (and deletes) revision history for you to look back and reminisce 35 | 36 | * ⌨️ Use [keyboard shortcuts](#Usage) to toggle between edit and save the notes 37 | 38 | * 💪 💪 ACTIVATE POWERMODE! (enable in settings) 39 | 40 | * Sync notes, revision history and settings between all Chrome browsers you are logged into 41 | 42 | ### Upcoming 43 | 44 | * Change background and foreground colours 45 | 46 | * Split editing to show live preview 47 | 48 | ## Usage 49 | 50 | * You can edit and save the notes either by pressing the buttons on the top right, or by using the shortcuts Ctrl + X (or Cmd + X on Mac) to edit the text and Ctrl + S (or Cmd + S on Mac) to save the text. 51 | 52 | * To save and edit the notes by using Ctrl + (or Cmd + on Mac), go to settings and enable it. 53 | 54 | * Revision history can be accessed by clicking "Last Edited: ____" on the bottom right corner. 55 | 56 | ## Development 57 | 58 | 1. Clone this repo: 59 | 60 | ```sh 61 | $ git clone https://github.com/plibither8/markdown-new-tab 62 | ``` 63 | 64 | 2. Open Chrome and go to `chrome://extensions` 65 | 3. Enable 'Developer Mode' by checking the tickbox (on the top of the page). 66 | 4. Click the 'Load Unpacked Extension' button and select the `dist/` folder of the cloned repository. 67 | 5. The extension should be loaded now and the 'New Tab' page should be Markdown New Tab. 🎉 68 | 69 | > The code makes use of `localStorage()` to save the raw text, revision history, last edited time and date, settings and last cursor position. 70 | 71 | ### Testing in Firefox 72 | 73 | In Firefox the extension can be installed temporarily until you restart the browser. To do so: 74 | 75 | 1. enter `about:debugging` in the URL bar 76 | 2. click "Load Temporary Add-on" 77 | 3. open the extension's directory in your local repo and select [`dist/manifest.json`](dist/manifest.json) 78 | 79 | More info [here](https://developer.mozilla.org/en-US/Add-ons/WebExtensions/Temporary_Installation_in_Firefox). 80 | 81 | ## Donate 82 | 83 | Markdown New Tab was made by me during my study-breaks and free time. If you like and have enjoyed it, please consider donating a small amount (any amount will be really appreciated!) to support and sustain its development. Thank you! 84 | 85 | [![Patreon](https://img.shields.io/badge/Become%20a-patreon-orange.svg?style=for-the-badge&logo=patreon)](https://www.patreon.com/plibither8) 86 | 87 | [![Donate](https://img.shields.io/badge/donate-PayPal-blue.svg?style=for-the-badge&logo=paypal)](https://paypal.me/plibither8) 88 | 89 | [![Buy Me A Coffee](https://www.buymeacoffee.com/assets/img/custom_images/purple_img.png)](https://www.buymeacoffee.com/plibither8) 90 | 91 | ### Thanks 92 | 93 | * **Browserstack**: This project is tested with Browserstack 94 | 95 | ## License 96 | 97 | Copyright (c) Mihir Chaturvedi. All rights reserved. 98 | 99 | Licensed under the [MIT](LICENSE) License. 100 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | dist 2 | 3 | # Created by https://www.gitignore.io/api/node,macos,windows,webstorm,sublimetext,visualstudiocode 4 | 5 | ### macOS ### 6 | *.DS_Store 7 | .AppleDouble 8 | .LSOverride 9 | 10 | # Icon must end with two \r 11 | Icon 12 | 13 | # Thumbnails 14 | ._* 15 | 16 | # Files that might appear in the root of a volume 17 | .DocumentRevisions-V100 18 | .fseventsd 19 | .Spotlight-V100 20 | .TemporaryItems 21 | .Trashes 22 | .VolumeIcon.icns 23 | .com.apple.timemachine.donotpresent 24 | 25 | # Directories potentially created on remote AFP share 26 | .AppleDB 27 | .AppleDesktop 28 | Network Trash Folder 29 | Temporary Items 30 | .apdisk 31 | 32 | ### Node ### 33 | # Logs 34 | logs 35 | *.log 36 | npm-debug.log* 37 | yarn-debug.log* 38 | yarn-error.log* 39 | 40 | # Runtime data 41 | pids 42 | *.pid 43 | *.seed 44 | *.pid.lock 45 | 46 | # Directory for instrumented libs generated by jscoverage/JSCover 47 | lib-cov 48 | 49 | # Coverage directory used by tools like istanbul 50 | coverage 51 | 52 | # nyc test coverage 53 | .nyc_output 54 | 55 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 56 | .grunt 57 | 58 | # Bower dependency directory (https://bower.io/) 59 | bower_components 60 | 61 | # node-waf configuration 62 | .lock-wscript 63 | 64 | # Compiled binary addons (http://nodejs.org/api/addons.html) 65 | build/Release 66 | 67 | # Dependency directories 68 | node_modules/ 69 | jspm_packages/ 70 | 71 | # Typescript v1 declaration files 72 | typings/ 73 | 74 | # Optional npm cache directory 75 | .npm 76 | 77 | # Optional eslint cache 78 | .eslintcache 79 | 80 | # Optional REPL history 81 | .node_repl_history 82 | 83 | # Output of 'npm pack' 84 | *.tgz 85 | 86 | # Yarn Integrity file 87 | .yarn-integrity 88 | 89 | # dotenv environment variables file 90 | .env 91 | 92 | 93 | ### SublimeText ### 94 | # cache files for sublime text 95 | *.tmlanguage.cache 96 | *.tmPreferences.cache 97 | *.stTheme.cache 98 | 99 | # workspace files are user-specific 100 | *.sublime-workspace 101 | 102 | # project files should be checked into the repository, unless a significant 103 | # proportion of contributors will probably not be using SublimeText 104 | # *.sublime-project 105 | 106 | # sftp configuration file 107 | sftp-config.json 108 | 109 | # Package control specific files 110 | Package Control.last-run 111 | Package Control.ca-list 112 | Package Control.ca-bundle 113 | Package Control.system-ca-bundle 114 | Package Control.cache/ 115 | Package Control.ca-certs/ 116 | Package Control.merged-ca-bundle 117 | Package Control.user-ca-bundle 118 | oscrypto-ca-bundle.crt 119 | bh_unicode_properties.cache 120 | 121 | # Sublime-github package stores a github token in this file 122 | # https://packagecontrol.io/packages/sublime-github 123 | GitHub.sublime-settings 124 | 125 | ### VisualStudioCode ### 126 | .vscode/* 127 | !.vscode/settings.json 128 | !.vscode/tasks.json 129 | !.vscode/launch.json 130 | !.vscode/extensions.json 131 | .history 132 | 133 | ### WebStorm ### 134 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and Webstorm 135 | # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 136 | 137 | # User-specific stuff: 138 | .idea/**/workspace.xml 139 | .idea/**/tasks.xml 140 | .idea/dictionaries 141 | 142 | # Sensitive or high-churn files: 143 | .idea/**/dataSources/ 144 | .idea/**/dataSources.ids 145 | .idea/**/dataSources.xml 146 | .idea/**/dataSources.local.xml 147 | .idea/**/sqlDataSources.xml 148 | .idea/**/dynamic.xml 149 | .idea/**/uiDesigner.xml 150 | 151 | # Gradle: 152 | .idea/**/gradle.xml 153 | .idea/**/libraries 154 | 155 | # CMake 156 | cmake-build-debug/ 157 | 158 | # Mongo Explorer plugin: 159 | .idea/**/mongoSettings.xml 160 | 161 | ## File-based project format: 162 | *.iws 163 | 164 | ## Plugin-specific files: 165 | 166 | # IntelliJ 167 | /out/ 168 | 169 | # mpeltonen/sbt-idea plugin 170 | .idea_modules/ 171 | 172 | # JIRA plugin 173 | atlassian-ide-plugin.xml 174 | 175 | # Cursive Clojure plugin 176 | .idea/replstate.xml 177 | 178 | # Ruby plugin and RubyMine 179 | /.rakeTasks 180 | 181 | # Crashlytics plugin (for Android Studio and IntelliJ) 182 | com_crashlytics_export_strings.xml 183 | crashlytics.properties 184 | crashlytics-build.properties 185 | fabric.properties 186 | 187 | ### WebStorm Patch ### 188 | # Comment Reason: https://github.com/joeblau/gitignore.io/issues/186#issuecomment-215987721 189 | 190 | # *.iml 191 | # modules.xml 192 | # .idea/misc.xml 193 | # *.ipr 194 | 195 | # Sonarlint plugin 196 | .idea/sonarlint 197 | 198 | ### Windows ### 199 | # Windows thumbnail cache files 200 | Thumbs.db 201 | ehthumbs.db 202 | ehthumbs_vista.db 203 | 204 | # Folder config file 205 | Desktop.ini 206 | 207 | # Recycle Bin used on file shares 208 | $RECYCLE.BIN/ 209 | 210 | # Windows Installer files 211 | *.cab 212 | *.msi 213 | *.msm 214 | *.msp 215 | 216 | # Windows shortcuts 217 | *.lnk 218 | 219 | # End of https://www.gitignore.io/api/node,macos,windows,webstorm,sublimetext,visualstudiocode 220 | -------------------------------------------------------------------------------- /src/styl/index.styl: -------------------------------------------------------------------------------- 1 | @require "imports/main" 2 | 3 | .container 4 | flex-direction column 5 | overflow hidden 6 | 7 | section.bar 8 | height 40px 9 | width 100% 10 | background #000 11 | color #fff 12 | position fixed 13 | bottom 0 14 | font-size 16px 15 | border-top 1px solid white 16 | z-index 4 17 | 18 | #lastEdited 19 | position absolute 20 | right 0 21 | border-left 1px solid white 22 | line-height 40px 23 | padding 0 10px 24 | cursor pointer 25 | 26 | &:hover::after 27 | content 'CLICK TO VIEW REVISION HISTORY' 28 | position absolute 29 | font-size 12px 30 | color #cccccc 31 | top -100% 32 | left -1px 33 | text-align center 34 | width 100% 35 | border-top 1px solid white 36 | border-left 1px solid white 37 | white-space nowrap 38 | 39 | .button 40 | padding 0 41 | height 100% 42 | width 40px 43 | position absolute 44 | left 0 45 | 46 | section.main 47 | height 100% 48 | width 100% 49 | align-items flex-start 50 | margin-bottom 40px 51 | font-size 20px 52 | transition 0.1s all linear 53 | 54 | textarea, .markdown-body 55 | background inherit 56 | border none 57 | height 100% 58 | max-width 1000px 59 | width 95% 60 | padding 5vh 61 | color white 62 | line-height 1.4em 63 | outline none 64 | resize none 65 | overflow-y auto 66 | 67 | &::-webkit-scrollbar 68 | width 15px 69 | background #000 70 | 71 | &::-webkit-scrollbar-thumb 72 | background #000 73 | border 1px solid white 74 | 75 | textarea 76 | font-size 18px 77 | font-family 'Fira Mono' 78 | letter-spacing 0.5px 79 | 80 | &.blur 81 | filter blur(3px) 82 | user-select none 83 | &.noblur 84 | filter blur(0px) 85 | user-select default 86 | 87 | .buttons 88 | flex-direction column 89 | margin 5vh 0 0 2vh 90 | 91 | .button 92 | cursor pointer 93 | padding 10px 94 | font-family monospace 95 | 96 | img 97 | cursor pointer 98 | height 20px 99 | width auto 100 | 101 | &:hover 102 | outline 1px solid white 103 | 104 | 105 | section.history, section.settings 106 | height 80vh 107 | max-width 1200px 108 | width 95% 109 | border 1px solid white 110 | position absolute 111 | background rgba(0,0,0,0.8) 112 | top 5vh 113 | color white 114 | flex-direction column 115 | justify-content flex-start 116 | 117 | .header 118 | width 100% 119 | padding 20px 120 | cursor move 121 | cursor -webkit-grab 122 | border-bottom 1px solid #333 123 | 124 | h1 125 | font-size 35px 126 | text-align center 127 | font-weight 100 128 | flex 1 129 | 130 | .close 131 | cursor pointer 132 | padding 10px 133 | font-family monospace 134 | 135 | img 136 | cursor pointer 137 | height 20px 138 | width auto 139 | 140 | &:hover 141 | outline 1px solid white 142 | 143 | &.z-index-2 144 | z-index 2 145 | 146 | &.z-index-3 147 | z-index 3 148 | 149 | section.history 150 | 151 | .list 152 | width 100% 153 | height 100% 154 | padding 30px 155 | overflow-y scroll 156 | 157 | &::-webkit-scrollbar 158 | width 15px 159 | background #000 160 | 161 | &::-webkit-scrollbar-thumb 162 | background #000 163 | border 1px solid white 164 | border-right none 165 | 166 | .item 167 | width 100% 168 | margin 40px 0 0 0 169 | 170 | textarea 171 | width 100% 172 | height 50vh 173 | background #1119 174 | border-radius 7px 175 | overflow-y scroll 176 | padding 20px 177 | resize none 178 | outline none 179 | color white 180 | font-family 'Fira Mono' 181 | font-size 16px 182 | border none 183 | margin 0 184 | 185 | &::-webkit-scrollbar 186 | width 10px 187 | background #000 188 | 189 | &::-webkit-scrollbar-thumb 190 | background #000 191 | border 1px solid white 192 | 193 | .markdown-body 194 | padding 20px 195 | background #1119 196 | border-radius 7px 197 | max-height 50vh 198 | overflow-y scroll 199 | 200 | &::-webkit-scrollbar 201 | width 10px 202 | background #000 203 | 204 | &::-webkit-scrollbar-thumb 205 | background #000 206 | border 1px solid white 207 | 208 | p 209 | font-size 16px 210 | 211 | &.id 212 | color #bbb 213 | display inline 214 | font-size 20px 215 | font-weight bold 216 | 217 | &.date 218 | display inline 219 | padding-left 20px 220 | font-weight bold 221 | font-size 23px 222 | 223 | .label 224 | padding-bottom 20px 225 | justify-content space-between 226 | 227 | section.settings 228 | 229 | .body 230 | height 100% 231 | width 100% 232 | overflow-y auto 233 | 234 | &::-webkit-scrollbar 235 | width 15px 236 | background #000 237 | 238 | &::-webkit-scrollbar-thumb 239 | background #000 240 | border 1px solid white 241 | border-right none 242 | 243 | .item 244 | padding 25px 40px 245 | border-bottom 1px solid #333 246 | cursor pointer 247 | 248 | .main 249 | justify-content space-between 250 | align-items flex-start 251 | flex-direction column 252 | flex 1 253 | 254 | label 255 | font-size 24px 256 | line-height 1 257 | 258 | p 259 | font-size 16px 260 | color #bbb 261 | margin-top 10px 262 | margin-right 20px 263 | 264 | &.dateFormat, 265 | &.customCss 266 | cursor default 267 | 268 | a 269 | color white 270 | font-weight bold 271 | text-decoration none 272 | 273 | &:hover 274 | text-decoration underline 275 | 276 | form 277 | margin 5px -10px 278 | width 100% 279 | 280 | input 281 | margin 10px 282 | background none 283 | border 1px solid white 284 | padding 5px 10px 285 | font-size 16px 286 | font-family 'Fira Mono' 287 | color white 288 | outline none 289 | 290 | &[type='text'] 291 | width 300px 292 | &[type='submit'] 293 | cursor pointer 294 | &.saved 295 | border-color green 296 | 297 | &:hover, 298 | &:active, 299 | &:focus 300 | outline none 301 | 302 | textarea 303 | display block 304 | width 100% 305 | font-family SFMono-Regular, Consolas, 'Liberation Mono', Menlo, Courier, monospace 306 | line-height 1.5 307 | resize vertical 308 | -moz-tab-size 4 !important 309 | tab-size 4 !important 310 | box-sizing border-box 311 | margin-top 5px 312 | margin-left 10px 313 | background black 314 | border 1px solid white 315 | box-shadow none 316 | color white 317 | padding 5px 318 | font-size 16px 319 | margin-bottom 5px 320 | 321 | .switch 322 | height 30px 323 | width 30px 324 | border 2px solid white 325 | align-items center 326 | 327 | .box 328 | height 20px 329 | width 20px 330 | transition 0.2s all linear 331 | 332 | &.on 333 | .box 334 | background white 335 | 336 | &.off 337 | .box 338 | background none -------------------------------------------------------------------------------- /src/markdown.css: -------------------------------------------------------------------------------- 1 | @font-face { 2 | font-family: octicons-link; 3 | src: url(data:font/woff;charset=utf-8;base64,d09GRgABAAAAAAZwABAAAAAACFQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABEU0lHAAAGaAAAAAgAAAAIAAAAAUdTVUIAAAZcAAAACgAAAAoAAQAAT1MvMgAAAyQAAABJAAAAYFYEU3RjbWFwAAADcAAAAEUAAACAAJThvmN2dCAAAATkAAAABAAAAAQAAAAAZnBnbQAAA7gAAACyAAABCUM+8IhnYXNwAAAGTAAAABAAAAAQABoAI2dseWYAAAFsAAABPAAAAZwcEq9taGVhZAAAAsgAAAA0AAAANgh4a91oaGVhAAADCAAAABoAAAAkCA8DRGhtdHgAAAL8AAAADAAAAAwGAACfbG9jYQAAAsAAAAAIAAAACABiATBtYXhwAAACqAAAABgAAAAgAA8ASm5hbWUAAAToAAABQgAAAlXu73sOcG9zdAAABiwAAAAeAAAAME3QpOBwcmVwAAAEbAAAAHYAAAB/aFGpk3jaTY6xa8JAGMW/O62BDi0tJLYQincXEypYIiGJjSgHniQ6umTsUEyLm5BV6NDBP8Tpts6F0v+k/0an2i+itHDw3v2+9+DBKTzsJNnWJNTgHEy4BgG3EMI9DCEDOGEXzDADU5hBKMIgNPZqoD3SilVaXZCER3/I7AtxEJLtzzuZfI+VVkprxTlXShWKb3TBecG11rwoNlmmn1P2WYcJczl32etSpKnziC7lQyWe1smVPy/Lt7Kc+0vWY/gAgIIEqAN9we0pwKXreiMasxvabDQMM4riO+qxM2ogwDGOZTXxwxDiycQIcoYFBLj5K3EIaSctAq2kTYiw+ymhce7vwM9jSqO8JyVd5RH9gyTt2+J/yUmYlIR0s04n6+7Vm1ozezUeLEaUjhaDSuXHwVRgvLJn1tQ7xiuVv/ocTRF42mNgZGBgYGbwZOBiAAFGJBIMAAizAFoAAABiAGIAznjaY2BkYGAA4in8zwXi+W2+MjCzMIDApSwvXzC97Z4Ig8N/BxYGZgcgl52BCSQKAA3jCV8CAABfAAAAAAQAAEB42mNgZGBg4f3vACQZQABIMjKgAmYAKEgBXgAAeNpjYGY6wTiBgZWBg2kmUxoDA4MPhGZMYzBi1AHygVLYQUCaawqDA4PChxhmh/8ODDEsvAwHgMKMIDnGL0x7gJQCAwMAJd4MFwAAAHjaY2BgYGaA4DAGRgYQkAHyGMF8NgYrIM3JIAGVYYDT+AEjAwuDFpBmA9KMDEwMCh9i/v8H8sH0/4dQc1iAmAkALaUKLgAAAHjaTY9LDsIgEIbtgqHUPpDi3gPoBVyRTmTddOmqTXThEXqrob2gQ1FjwpDvfwCBdmdXC5AVKFu3e5MfNFJ29KTQT48Ob9/lqYwOGZxeUelN2U2R6+cArgtCJpauW7UQBqnFkUsjAY/kOU1cP+DAgvxwn1chZDwUbd6CFimGXwzwF6tPbFIcjEl+vvmM/byA48e6tWrKArm4ZJlCbdsrxksL1AwWn/yBSJKpYbq8AXaaTb8AAHja28jAwOC00ZrBeQNDQOWO//sdBBgYGRiYWYAEELEwMTE4uzo5Zzo5b2BxdnFOcALxNjA6b2ByTswC8jYwg0VlNuoCTWAMqNzMzsoK1rEhNqByEyerg5PMJlYuVueETKcd/89uBpnpvIEVomeHLoMsAAe1Id4AAAAAAAB42oWQT07CQBTGv0JBhagk7HQzKxca2sJCE1hDt4QF+9JOS0nbaaYDCQfwCJ7Au3AHj+LO13FMmm6cl7785vven0kBjHCBhfpYuNa5Ph1c0e2Xu3jEvWG7UdPDLZ4N92nOm+EBXuAbHmIMSRMs+4aUEd4Nd3CHD8NdvOLTsA2GL8M9PODbcL+hD7C1xoaHeLJSEao0FEW14ckxC+TU8TxvsY6X0eLPmRhry2WVioLpkrbp84LLQPGI7c6sOiUzpWIWS5GzlSgUzzLBSikOPFTOXqly7rqx0Z1Q5BAIoZBSFihQYQOOBEdkCOgXTOHA07HAGjGWiIjaPZNW13/+lm6S9FT7rLHFJ6fQbkATOG1j2OFMucKJJsxIVfQORl+9Jyda6Sl1dUYhSCm1dyClfoeDve4qMYdLEbfqHf3O/AdDumsjAAB42mNgYoAAZQYjBmyAGYQZmdhL8zLdDEydARfoAqIAAAABAAMABwAKABMAB///AA8AAQAAAAAAAAAAAAAAAAABAAAAAA==) format('woff'); 4 | } 5 | 6 | .markdown-body { 7 | -ms-text-size-adjust: 100%; 8 | -webkit-text-size-adjust: 100%; 9 | line-height: 1.5; 10 | color: #ffffff; 11 | font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol"; 12 | font-size: 16px; 13 | line-height: 1.5; 14 | word-wrap: break-word; 15 | } 16 | 17 | .markdown-body .pl-c { 18 | color: #6a737d; 19 | } 20 | 21 | .markdown-body .pl-c1, 22 | .markdown-body .pl-s .pl-v { 23 | color: #005cc5; 24 | } 25 | 26 | .markdown-body .pl-e, 27 | .markdown-body .pl-en { 28 | color: #6f42c1; 29 | } 30 | 31 | .markdown-body .pl-smi, 32 | .markdown-body .pl-s .pl-s1 { 33 | color: #24292e; 34 | } 35 | 36 | .markdown-body .pl-ent { 37 | color: #22863a; 38 | } 39 | 40 | .markdown-body .pl-k { 41 | color: #d73a49; 42 | } 43 | 44 | .markdown-body .pl-s, 45 | .markdown-body .pl-pds, 46 | .markdown-body .pl-s .pl-pse .pl-s1, 47 | .markdown-body .pl-sr, 48 | .markdown-body .pl-sr .pl-cce, 49 | .markdown-body .pl-sr .pl-sre, 50 | .markdown-body .pl-sr .pl-sra { 51 | color: #032f62; 52 | } 53 | 54 | .markdown-body .pl-v, 55 | .markdown-body .pl-smw { 56 | color: #e36209; 57 | } 58 | 59 | .markdown-body .pl-bu { 60 | color: #b31d28; 61 | } 62 | 63 | .markdown-body .pl-ii { 64 | color: #fafbfc; 65 | background-color: #b31d28; 66 | } 67 | 68 | .markdown-body .pl-c2 { 69 | color: #fafbfc; 70 | background-color: #d73a49; 71 | } 72 | 73 | .markdown-body .pl-c2::before { 74 | content: "^M"; 75 | } 76 | 77 | .markdown-body .pl-sr .pl-cce { 78 | font-weight: bold; 79 | color: #22863a; 80 | } 81 | 82 | .markdown-body .pl-ml { 83 | color: #735c0f; 84 | } 85 | 86 | .markdown-body .pl-mh, 87 | .markdown-body .pl-mh .pl-en, 88 | .markdown-body .pl-ms { 89 | font-weight: bold; 90 | color: #005cc5; 91 | } 92 | 93 | .markdown-body .pl-mi { 94 | font-style: italic; 95 | color: #24292e; 96 | } 97 | 98 | .markdown-body .pl-mb { 99 | font-weight: bold; 100 | color: #24292e; 101 | } 102 | 103 | .markdown-body .pl-md { 104 | color: #b31d28; 105 | background-color: #ffeef0; 106 | } 107 | 108 | .markdown-body .pl-mi1 { 109 | color: #22863a; 110 | background-color: #f0fff4; 111 | } 112 | 113 | .markdown-body .pl-mc { 114 | color: #e36209; 115 | background-color: #ffebda; 116 | } 117 | 118 | .markdown-body .pl-mi2 { 119 | color: #f6f8fa; 120 | background-color: #005cc5; 121 | } 122 | 123 | .markdown-body .pl-mdr { 124 | font-weight: bold; 125 | color: #6f42c1; 126 | } 127 | 128 | .markdown-body .pl-ba { 129 | color: #586069; 130 | } 131 | 132 | .markdown-body .pl-sg { 133 | color: #959da5; 134 | } 135 | 136 | .markdown-body .pl-corl { 137 | text-decoration: underline; 138 | color: #032f62; 139 | } 140 | 141 | .markdown-body .octicon { 142 | display: inline-block; 143 | vertical-align: text-top; 144 | fill: currentColor; 145 | } 146 | 147 | .markdown-body a { 148 | background-color: transparent; 149 | } 150 | 151 | .markdown-body a:active, 152 | .markdown-body a:hover { 153 | outline-width: 0; 154 | } 155 | 156 | .markdown-body strong { 157 | font-weight: inherit; 158 | } 159 | 160 | .markdown-body strong { 161 | font-weight: bolder; 162 | } 163 | 164 | .markdown-body h1 { 165 | font-size: 2em; 166 | margin: 0.67em 0; 167 | } 168 | 169 | .markdown-body img { 170 | border-style: none; 171 | } 172 | 173 | .markdown-body code, 174 | .markdown-body kbd, 175 | .markdown-body pre { 176 | font-family: monospace, monospace; 177 | font-size: 1em; 178 | } 179 | 180 | .markdown-body hr { 181 | box-sizing: content-box; 182 | height: 0; 183 | overflow: visible; 184 | } 185 | 186 | .markdown-body input { 187 | font: inherit; 188 | margin: 0; 189 | } 190 | 191 | .markdown-body input { 192 | overflow: visible; 193 | } 194 | 195 | .markdown-body [type="checkbox"] { 196 | box-sizing: border-box; 197 | padding: 0; 198 | } 199 | 200 | .markdown-body * { 201 | box-sizing: border-box; 202 | } 203 | 204 | .markdown-body input { 205 | font-family: inherit; 206 | font-size: inherit; 207 | line-height: inherit; 208 | } 209 | 210 | .markdown-body a { 211 | color: #8bc34a; 212 | text-decoration: none; 213 | } 214 | 215 | .markdown-body a:hover { 216 | text-decoration: underline; 217 | } 218 | 219 | .markdown-body strong { 220 | font-weight: 600; 221 | } 222 | 223 | .markdown-body hr { 224 | height: 0; 225 | margin: 15px 0; 226 | overflow: hidden; 227 | background: transparent; 228 | border: 0; 229 | border-bottom: 1px solid #292929; 230 | } 231 | 232 | .markdown-body hr::before { 233 | display: table; 234 | content: ""; 235 | } 236 | 237 | .markdown-body hr::after { 238 | display: table; 239 | clear: both; 240 | content: ""; 241 | } 242 | 243 | .markdown-body table { 244 | border-spacing: 0; 245 | border-collapse: collapse; 246 | } 247 | 248 | .markdown-body td, 249 | .markdown-body th { 250 | padding: 0; 251 | } 252 | 253 | .markdown-body h1, 254 | .markdown-body h2, 255 | .markdown-body h3, 256 | .markdown-body h4, 257 | .markdown-body h5, 258 | .markdown-body h6 { 259 | margin-top: 0; 260 | margin-bottom: 0; 261 | } 262 | 263 | .markdown-body h1 { 264 | font-size: 32px; 265 | font-weight: 600; 266 | } 267 | 268 | .markdown-body h2 { 269 | font-size: 24px; 270 | font-weight: 600; 271 | } 272 | 273 | .markdown-body h3 { 274 | font-size: 20px; 275 | font-weight: 600; 276 | } 277 | 278 | .markdown-body h4 { 279 | font-size: 16px; 280 | font-weight: 600; 281 | } 282 | 283 | .markdown-body h5 { 284 | font-size: 14px; 285 | font-weight: 600; 286 | } 287 | 288 | .markdown-body h6 { 289 | font-size: 12px; 290 | font-weight: 600; 291 | } 292 | 293 | .markdown-body p { 294 | margin-top: 0; 295 | margin-bottom: 10px; 296 | } 297 | 298 | .markdown-body blockquote { 299 | margin: 0; 300 | } 301 | 302 | .markdown-body ul, 303 | .markdown-body ol { 304 | padding-left: 0; 305 | margin-top: 0; 306 | margin-bottom: 0; 307 | } 308 | 309 | .markdown-body ol ol, 310 | .markdown-body ul ol { 311 | list-style-type: lower-roman; 312 | } 313 | 314 | .markdown-body ul ul ol, 315 | .markdown-body ul ol ol, 316 | .markdown-body ol ul ol, 317 | .markdown-body ol ol ol { 318 | list-style-type: lower-alpha; 319 | } 320 | 321 | .markdown-body dd { 322 | margin-left: 0; 323 | } 324 | 325 | .markdown-body code { 326 | font-family: "SFMono-Regular", Consolas, "Liberation Mono", Menlo, Courier, monospace; 327 | font-size: 12px; 328 | } 329 | 330 | .markdown-body pre { 331 | margin-top: 0; 332 | margin-bottom: 0; 333 | font-family: "SFMono-Regular", Consolas, "Liberation Mono", Menlo, Courier, monospace; 334 | font-size: 12px; 335 | } 336 | 337 | .markdown-body .octicon { 338 | vertical-align: text-bottom; 339 | } 340 | 341 | .markdown-body .pl-0 { 342 | padding-left: 0 !important; 343 | } 344 | 345 | .markdown-body .pl-1 { 346 | padding-left: 4px !important; 347 | } 348 | 349 | .markdown-body .pl-2 { 350 | padding-left: 8px !important; 351 | } 352 | 353 | .markdown-body .pl-3 { 354 | padding-left: 16px !important; 355 | } 356 | 357 | .markdown-body .pl-4 { 358 | padding-left: 24px !important; 359 | } 360 | 361 | .markdown-body .pl-5 { 362 | padding-left: 32px !important; 363 | } 364 | 365 | .markdown-body .pl-6 { 366 | padding-left: 40px !important; 367 | } 368 | 369 | .markdown-body::before { 370 | display: table; 371 | content: ""; 372 | } 373 | 374 | .markdown-body::after { 375 | display: table; 376 | clear: both; 377 | content: ""; 378 | } 379 | 380 | .markdown-body>*:first-child { 381 | margin-top: 0 !important; 382 | } 383 | 384 | .markdown-body>*:last-child { 385 | margin-bottom: 0 !important; 386 | } 387 | 388 | .markdown-body a:not([href]) { 389 | color: inherit; 390 | text-decoration: none; 391 | } 392 | 393 | .markdown-body .anchor { 394 | float: left; 395 | padding-right: 4px; 396 | margin-left: -20px; 397 | line-height: 1; 398 | } 399 | 400 | .markdown-body .anchor:focus { 401 | outline: none; 402 | } 403 | 404 | .markdown-body p, 405 | .markdown-body blockquote, 406 | .markdown-body ul, 407 | .markdown-body ol, 408 | .markdown-body dl, 409 | .markdown-body table, 410 | .markdown-body pre { 411 | margin-top: 0; 412 | margin-bottom: 16px; 413 | } 414 | 415 | .markdown-body hr { 416 | height: 0.25em; 417 | padding: 0; 418 | margin: 24px 0; 419 | background-color: #292929; 420 | border: 0; 421 | } 422 | 423 | .markdown-body blockquote { 424 | padding: 0 1em; 425 | color: #7f8a96; 426 | border-left: 0.25em solid #dfe2e5; 427 | } 428 | 429 | .markdown-body blockquote>:first-child { 430 | margin-top: 0; 431 | } 432 | 433 | .markdown-body blockquote>:last-child { 434 | margin-bottom: 0; 435 | } 436 | 437 | .markdown-body kbd { 438 | display: inline-block; 439 | padding: 3px 5px; 440 | font-size: 11px; 441 | line-height: 10px; 442 | color: #444d56; 443 | vertical-align: middle; 444 | background-color: #fafbfc; 445 | border: solid 1px #c6cbd1; 446 | border-bottom-color: #959da5; 447 | border-radius: 3px; 448 | box-shadow: inset 0 -1px 0 #959da5; 449 | } 450 | 451 | .markdown-body h1, 452 | .markdown-body h2, 453 | .markdown-body h3, 454 | .markdown-body h4, 455 | .markdown-body h5, 456 | .markdown-body h6 { 457 | margin-top: 24px; 458 | margin-bottom: 16px; 459 | font-weight: 600; 460 | line-height: 1.25; 461 | } 462 | 463 | .markdown-body h1 .octicon-link, 464 | .markdown-body h2 .octicon-link, 465 | .markdown-body h3 .octicon-link, 466 | .markdown-body h4 .octicon-link, 467 | .markdown-body h5 .octicon-link, 468 | .markdown-body h6 .octicon-link { 469 | color: #1b1f23; 470 | vertical-align: middle; 471 | visibility: hidden; 472 | } 473 | 474 | .markdown-body h1:hover .anchor, 475 | .markdown-body h2:hover .anchor, 476 | .markdown-body h3:hover .anchor, 477 | .markdown-body h4:hover .anchor, 478 | .markdown-body h5:hover .anchor, 479 | .markdown-body h6:hover .anchor { 480 | text-decoration: none; 481 | } 482 | 483 | .markdown-body h1:hover .anchor .octicon-link, 484 | .markdown-body h2:hover .anchor .octicon-link, 485 | .markdown-body h3:hover .anchor .octicon-link, 486 | .markdown-body h4:hover .anchor .octicon-link, 487 | .markdown-body h5:hover .anchor .octicon-link, 488 | .markdown-body h6:hover .anchor .octicon-link { 489 | visibility: visible; 490 | } 491 | 492 | .markdown-body h1 { 493 | padding-bottom: 0.3em; 494 | font-size: 2em; 495 | border-bottom: 1px solid #4e4e4e; 496 | } 497 | 498 | .markdown-body h2 { 499 | padding-bottom: 0.3em; 500 | font-size: 1.5em; 501 | border-bottom: 1px solid #4e4e4e; 502 | } 503 | 504 | .markdown-body h3 { 505 | font-size: 1.25em; 506 | } 507 | 508 | .markdown-body h4 { 509 | font-size: 1em; 510 | } 511 | 512 | .markdown-body h5 { 513 | font-size: 0.875em; 514 | } 515 | 516 | .markdown-body h6 { 517 | font-size: 0.85em; 518 | color: #86929e; 519 | } 520 | 521 | .markdown-body ul, 522 | .markdown-body ol { 523 | padding-left: 2em; 524 | } 525 | 526 | .markdown-body ul ul, 527 | .markdown-body ul ol, 528 | .markdown-body ol ol, 529 | .markdown-body ol ul { 530 | margin-top: 0; 531 | margin-bottom: 0; 532 | } 533 | 534 | .markdown-body li { 535 | word-wrap: break-all; 536 | } 537 | 538 | .markdown-body li>p { 539 | margin-top: 16px; 540 | } 541 | 542 | .markdown-body li+li { 543 | margin-top: 0.25em; 544 | } 545 | 546 | .markdown-body dl { 547 | padding: 0; 548 | } 549 | 550 | .markdown-body dl dt { 551 | padding: 0; 552 | margin-top: 16px; 553 | font-size: 1em; 554 | font-style: italic; 555 | font-weight: 600; 556 | } 557 | 558 | .markdown-body dl dd { 559 | padding: 0 16px; 560 | margin-bottom: 16px; 561 | } 562 | 563 | .markdown-body table { 564 | display: block; 565 | width: 100%; 566 | overflow: auto; 567 | } 568 | 569 | .markdown-body table th { 570 | font-weight: 600; 571 | } 572 | 573 | .markdown-body table th, 574 | .markdown-body table td { 575 | padding: 6px 13px; 576 | border: 1px solid #6b6b6b; 577 | } 578 | 579 | .markdown-body table tr { 580 | background-color: #000000; 581 | border-top: 1px solid #000000; 582 | } 583 | 584 | .markdown-body table tr:nth-child(2n) { 585 | background-color: #121213; 586 | } 587 | 588 | .markdown-body img { 589 | max-width: 100%; 590 | box-sizing: content-box; 591 | background-color: #000; 592 | } 593 | 594 | .markdown-body img[align=right] { 595 | padding-left: 20px; 596 | } 597 | 598 | .markdown-body img[align=left] { 599 | padding-right: 20px; 600 | } 601 | 602 | .markdown-body code { 603 | padding: 0.2em 0.4em; 604 | margin: 0; 605 | font-size: 85%; 606 | background-color: rgba(27, 31, 35, 0.05); 607 | border-radius: 3px; 608 | } 609 | 610 | .markdown-body pre { 611 | word-wrap: normal; 612 | } 613 | 614 | .markdown-body pre>code { 615 | padding: 0; 616 | margin: 0; 617 | font-size: 100%; 618 | word-break: normal; 619 | white-space: pre; 620 | background: transparent; 621 | border: 0; 622 | } 623 | 624 | .markdown-body .highlight { 625 | margin-bottom: 16px; 626 | } 627 | 628 | .markdown-body .highlight pre { 629 | margin-bottom: 0; 630 | word-break: normal; 631 | } 632 | 633 | .markdown-body .highlight pre, 634 | .markdown-body pre { 635 | padding: 16px; 636 | overflow: auto; 637 | font-size: 85%; 638 | line-height: 1.45; 639 | background-color: #131313; 640 | border-radius: 3px; 641 | } 642 | 643 | .markdown-body pre code { 644 | display: inline; 645 | max-width: auto; 646 | padding: 0; 647 | margin: 0; 648 | overflow: visible; 649 | line-height: inherit; 650 | word-wrap: normal; 651 | background-color: transparent; 652 | border: 0; 653 | } 654 | 655 | .markdown-body .full-commit .btn-outline:not(:disabled):hover { 656 | color: #005cc5; 657 | border-color: #005cc5; 658 | } 659 | 660 | .markdown-body kbd { 661 | display: inline-block; 662 | padding: 3px 5px; 663 | font: 11px "SFMono-Regular", Consolas, "Liberation Mono", Menlo, Courier, monospace; 664 | line-height: 10px; 665 | color: #444d56; 666 | vertical-align: middle; 667 | background-color: #fafbfc; 668 | border: solid 1px #d1d5da; 669 | border-bottom-color: #c6cbd1; 670 | border-radius: 3px; 671 | box-shadow: inset 0 -1px 0 #c6cbd1; 672 | } 673 | 674 | .markdown-body :checked+.radio-label { 675 | position: relative; 676 | z-index: 1; 677 | border-color: #0366d6; 678 | } 679 | 680 | .markdown-body .task-list-item { 681 | list-style-type: none; 682 | } 683 | 684 | .markdown-body .task-list-item+.task-list-item { 685 | margin-top: 3px; 686 | } 687 | 688 | .markdown-body .task-list-item input { 689 | margin: 0 0.2em 0.25em -1.6em; 690 | vertical-align: middle; 691 | } 692 | 693 | .markdown-body hr { 694 | border-bottom-color: #eee; 695 | } -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import './styl/index.styl'; 2 | 3 | import showdown from 'showdown'; 4 | import dateformat from 'dateformat'; 5 | import indentTextarea from 'indent-textarea'; 6 | import {format} from 'timeago.js'; 7 | 8 | indentTextarea.watch('.customCss textarea'); 9 | 10 | /** 11 | * Define helper functions 12 | */ 13 | 14 | /** 15 | * Define shorthand function to replace `document.querySelector` 16 | * to make it easier to write and understand 17 | * @param {Node} el - CSS selector element which needs to be `querySelector`ed 18 | * @returns qureied element 19 | */ 20 | const getHtmlElement = el => { 21 | return document.querySelector(el); 22 | }; 23 | 24 | /** 25 | * Define shorthand function to replace `element.classList.add` 26 | * @param {Node} el - CSS selector element whose class needs to be modified 27 | * @param {String} className - class name that needs to be appended to classlist 28 | */ 29 | const addClass = (el, className) => { 30 | el.classList.add(className); 31 | }; 32 | 33 | /** 34 | * Define shorthand function to replace `element.classList.remove` 35 | * @param {Node} el - CSS selector element whose class needs to be modified 36 | * @param {String} className - class name that needs to be removed from classlist 37 | */ 38 | const removeClass = (el, className) => { 39 | el.classList.remove(className); 40 | }; 41 | 42 | /** 43 | * Wrapper: localStorage.setItem 44 | */ 45 | const syncStorageSet = async (name, value) => { 46 | await localStorage.setItem(name, value); 47 | const item = {}; 48 | item[name] = value; 49 | if (browser.storage) { 50 | await browser.storage.sync.set(item); 51 | } 52 | }; 53 | 54 | /** 55 | * Declare global variables 56 | */ 57 | const renderBox = getHtmlElement('.markdown-body'); 58 | const textarea = getHtmlElement('textarea'); 59 | const mainSection = getHtmlElement('section.main'); 60 | const historySection = getHtmlElement('section.history'); 61 | const settingsSection = getHtmlElement('section.settings'); 62 | let rawText; 63 | let isPowerModeEventListenerSet = false; // Boolean to prevent multiple eventlisteners 64 | const activeModals = []; // Array of active modals 65 | let saveHistory; // Settings.saveHistory Boolean 66 | let cursorLastPosition; // Settings.cursorLastPosition Boolean 67 | let returnKeyToggle; // Settings.returnKeyToggle Boolean 68 | let sectionMainEventListener; // Section.main eventListener (defined in `openModal` function) 69 | let converter; // Main markdown rendering converter (defined in `initiate` function) 70 | 71 | /** 72 | * Toggle between renderBox and textarea 73 | * @param n - If n = 1, display renderBox, else display textarea 74 | */ 75 | const toggleDisplay = n => { 76 | if (n) { 77 | removeClass(textarea, 'nodisplay'); 78 | addClass(renderBox, 'nodisplay'); 79 | } else { 80 | addClass(textarea, 'nodisplay'); 81 | removeClass(renderBox, 'nodisplay'); 82 | } 83 | }; 84 | 85 | /** 86 | * Move the textarea caret to the start of the 87 | * line instead of the last line so that it is visible 88 | * From https://stackoverflow.com/a/8190890/ 89 | */ 90 | const moveCaretToStart = () => { 91 | if (typeof textarea.selectionStart === 'number') { 92 | textarea.selectionStart = 0; 93 | textarea.selectionEnd = 0; 94 | } else if (typeof textarea.createTextRange !== 'undefined') { 95 | textarea.focus(); 96 | const range = textarea.createTextRange(); 97 | range.collapse(true); 98 | range.select(); 99 | } 100 | }; 101 | 102 | // Main edit function 103 | const edit = () => { 104 | toggleDisplay(1); 105 | textarea.focus(); 106 | 107 | if (cursorLastPosition) { 108 | textarea.selectionStart = Number(localStorage.getItem('cursorLastPosition')); 109 | } else { 110 | moveCaretToStart(); 111 | textarea.scrollTop = 0; 112 | } 113 | 114 | // Toggle button display 115 | removeClass(getHtmlElement('#save'), 'nodisplay'); 116 | addClass(getHtmlElement('#edit'), 'nodisplay'); 117 | }; 118 | 119 | // Main save function 120 | const save = (saveRevHist = 1) => { 121 | syncStorageSet('cursorLastPosition', textarea.selectionStart); 122 | 123 | toggleDisplay(0); 124 | const text = textarea.value; 125 | const html = converter.makeHtml(text); 126 | renderBox.innerHTML = html; 127 | 128 | if (html !== converter.makeHtml(rawText)) { 129 | syncStorageSet('rawText', text); 130 | rawText = text; 131 | if (saveHistory && saveRevHist) { 132 | syncStorageSet('lastEdited', (new Date()).toString()); 133 | setHistory(); 134 | } 135 | } 136 | 137 | // Toggle button display 138 | removeClass(getHtmlElement('#edit'), 'nodisplay'); 139 | addClass(getHtmlElement('#save'), 'nodisplay'); 140 | }; 141 | 142 | /** 143 | * @returns Array of history items 144 | */ 145 | const getHistory = () => { 146 | const rawHistory = localStorage.getItem('history'); 147 | const history = rawHistory === null ? [] : JSON.parse(rawHistory); 148 | return history; 149 | }; 150 | 151 | /** 152 | * Add new history item to history array 153 | * and then update `history` item in localStorage 154 | */ 155 | const setHistory = () => { 156 | const history = getHistory(); 157 | const historyItem = { 158 | date: (new Date()), 159 | text: rawText 160 | }; 161 | history.unshift(historyItem); 162 | if (history.length > 10) { 163 | history.pop(); 164 | } 165 | 166 | syncStorageSet('history', JSON.stringify(history)); 167 | }; 168 | 169 | /** 170 | * Display history item markdown 171 | * @param {Node} item - History item for which markdown must be rendered 172 | */ 173 | const displayMarkdown = item => { 174 | const text = decodeURIComponent(escape(atob(item.getAttribute('data-text')))); 175 | const mdBody = item.children[1]; 176 | const textarea = item.children[2]; 177 | 178 | mdBody.innerHTML = converter.makeHtml(text); 179 | removeClass(mdBody, 'nodisplay'); 180 | addClass(textarea, 'nodisplay'); 181 | }; 182 | 183 | /** 184 | * Display history item rawttext 185 | * @param {Node} item - History item for which textarea must be populated with rawtext 186 | */ 187 | const displayTextarea = item => { 188 | const text = decodeURIComponent(escape(atob(item.getAttribute('data-text')))); 189 | const mdBody = item.children[1]; 190 | const textarea = item.children[2]; 191 | 192 | addClass(mdBody, 'nodisplay'); 193 | removeClass(textarea, 'nodisplay'); 194 | textarea.innerHTML = text; 195 | }; 196 | 197 | /** 198 | * Main revision history function 199 | */ 200 | const populateHistoryHtml = () => { 201 | let listElements = ''; 202 | const history = getHistory(); 203 | const {length} = history; 204 | 205 | history.forEach((item, id) => { 206 | const parsedDate = new Date(Date.parse(item.date)); 207 | const textBase64 = btoa(unescape(encodeURIComponent(item.text))); // Save rawtext as base64 208 | 209 | listElements += 210 | `
211 |
212 |
213 |

#${length - id}

214 |

${parsedDate.toLocaleString()}

215 |
216 |
217 |
218 | 219 |
220 |
221 | 222 |
223 |
224 |
225 |
226 | 227 |
`; 228 | }); 229 | 230 | getHtmlElement('section.history .list').innerHTML = listElements; 231 | 232 | /** 233 | * 1. Reverse order the array of `item`s to get in suitable, rawHistory adhering order 234 | * 2. Render each item's rawtext to markdown and display it 235 | * 3. Add event listeners to the buttons of the respective elements 236 | */ 237 | [...document.querySelectorAll('section.history .item')].reverse().forEach((item, index) => { 238 | displayMarkdown(item); 239 | 240 | // Both variable gets mapped to respective elements 241 | const [deleteButton, viewButton] = item.children[0].children[1].children; 242 | 243 | deleteButton.addEventListener('click', () => { 244 | history.splice(length - index - 1, 1); 245 | syncStorageSet('history', JSON.stringify(history)); 246 | populateHistoryHtml(); // Refresh the Revision History modal with updated content 247 | }); 248 | viewButton.addEventListener('click', () => { 249 | if (item.children[2].classList.contains('nodisplay')) { 250 | displayTextarea(item); 251 | } else { 252 | displayMarkdown(item); 253 | } 254 | }); 255 | }); 256 | }; 257 | 258 | /** 259 | * @returns Settings object 260 | */ 261 | const getSettings = () => { 262 | const rawSettings = localStorage.getItem('settings'); 263 | const settings = typeof rawSettings === 'string' ? JSON.parse(rawSettings) : null; 264 | return settings; 265 | }; 266 | 267 | /** 268 | * Change settings function 269 | * @param {String} key - Name of setting to be changed 270 | * @param {Boolean} value - Value of setting to be appied 271 | */ 272 | const setSettings = (key, value) => { 273 | let settings = getSettings(); 274 | 275 | /** 276 | * If settings is null, page is opened for the first time thus 277 | * initialise with these defaults 278 | */ 279 | 280 | if (settings === null || Object.keys(settings).length !== 6) { 281 | settings = { 282 | saveHistory: true, 283 | cursorLastPosition: true, 284 | returnKeyToggle: false, 285 | enablePowerMode: false, 286 | PowerModeColor: false, 287 | PowerModeShake: false 288 | }; 289 | } 290 | 291 | settings[key] = value; 292 | 293 | syncStorageSet('settings', JSON.stringify(settings)); 294 | }; 295 | 296 | /** 297 | * Main settings handler function 298 | */ 299 | 300 | const setEventListenersToSettings = async () => { 301 | const settingsItems = document.querySelectorAll('section.settings .item:not(.dateFormat)'); 302 | 303 | for (const item of settingsItems) { 304 | item.addEventListener('click', () => { 305 | settingsControl(item.dataset.setting); 306 | }); 307 | } 308 | 309 | const dateFormatForm = document.querySelector('section.settings .dateFormat form'); 310 | const dateFormatInput = dateFormatForm.querySelector('input[name="dateFormat"]'); 311 | const dateFormatSubmit = dateFormatForm.querySelector('input[type="submit"]'); 312 | dateFormatInput.value = localStorage.getItem('dateFormat') || 'dd/mm/yyyy - HH:MM:ss'; 313 | 314 | dateFormatForm.addEventListener('submit', async event => { 315 | event.preventDefault(); 316 | const {value} = dateFormatInput; 317 | await syncStorageSet('dateFormat', value.trim()); 318 | dateFormatSubmit.classList.add('saved'); 319 | setTimeout(() => { 320 | dateFormatSubmit.classList.remove('saved'); 321 | }, 500); 322 | }); 323 | 324 | const customCssForm = document.querySelector('section.settings .customCss form'); 325 | const customCssTextarea = customCssForm.querySelector('textarea'); 326 | const customCssSubmit = customCssForm.querySelector('input'); 327 | customCssTextarea.value = localStorage.getItem('customCss') || ''; 328 | 329 | customCssForm.addEventListener('submit', async event => { 330 | event.preventDefault(); 331 | const {value} = customCssTextarea; 332 | await syncStorageSet('customCss', value.trim()); 333 | customCssSubmit.classList.add('saved'); 334 | setTimeout(() => { 335 | customCssSubmit.classList.remove('saved'); 336 | }, 500); 337 | }); 338 | }; 339 | 340 | const settingsControl = (keyName = undefined) => { 341 | const settings = getSettings(); 342 | const settingsItems = document.querySelectorAll('section.settings .item:not(.dateFormat)'); 343 | 344 | for (const item of settingsItems) { 345 | const key = item.dataset.setting; 346 | const value = settings[key]; 347 | 348 | // Toggle class on item and change switch style 349 | removeClass(item, value ? 'off' : 'on'); 350 | addClass(item, value ? 'on' : 'off'); 351 | 352 | if (key === keyName) { 353 | setSettings(key, !value); 354 | settingsControl(); 355 | } 356 | } 357 | 358 | applySettings(); 359 | }; 360 | 361 | /** 362 | * Finally, apply the settings that have been set 363 | */ 364 | const applySettings = () => { 365 | const settings = getSettings(); 366 | 367 | // Save History 368 | saveHistory = settings.saveHistory; 369 | // Cursor at End of Document 370 | cursorLastPosition = settings.cursorLastPosition; 371 | // Toggle modes with Cmd/Ctrl + return key 372 | returnKeyToggle = settings.returnKeyToggle; 373 | // // Colored POWER MODE 374 | // POWERMODE.colorful = settings.PowerModeColor; 375 | // // Shake on POWER MODE 376 | // POWERMODE.shake = settings.PowerModeShake; 377 | // Enable POWER MODE 378 | if (settings.enablePowerMode && !isPowerModeEventListenerSet) { 379 | textarea.addEventListener('input', POWERMODE); 380 | isPowerModeEventListenerSet = true; 381 | } 382 | 383 | if (!settings.enablePowerMode && isPowerModeEventListenerSet) { 384 | textarea.removeEventListener('input', POWERMODE); 385 | isPowerModeEventListenerSet = false; 386 | } 387 | }; 388 | 389 | /** 390 | * Open modal 391 | * @param {Node} section - Modal element which is being opened 392 | * @param {Function} func - Function that will be performed if it exists 393 | */ 394 | const openModal = (section, func) => { 395 | // Mainly for `populateHistoryHtml` function 396 | if (func) { 397 | func(); 398 | } 399 | 400 | // Add modal to activeModals only if it is not only present in the array (prevent double addition if button is clicked twice) 401 | if (activeModals.indexOf(section) === -1) { 402 | activeModals.push(section); 403 | } 404 | 405 | // 1st modal to be opened 406 | if (activeModals.length === 1) { 407 | removeClass(section, 'z-index-3'); 408 | addClass(section, 'z-index-2'); 409 | } 410 | 411 | // 2nd modal to be opened 412 | else if (activeModals.length === 2) { 413 | removeClass(section, 'z-index-2'); 414 | addClass(section, 'z-index-3'); 415 | } 416 | 417 | removeClass(section, 'nodisplay'); 418 | removeClass(mainSection, 'noblur'); 419 | addClass(mainSection, 'blur'); 420 | 421 | // Add eventListener to section.main to enable closing modal by clicking outside the modal 422 | if (!sectionMainEventListener) { 423 | sectionMainEventListener = true; 424 | mainSection.addEventListener('click', () => { 425 | return closeModal(activeModals); 426 | }, false); 427 | } 428 | }; 429 | 430 | /** 431 | * Close modal 432 | * @param {Node} section - Modal element which is being closed 433 | */ 434 | const closeModal = section => { 435 | // If `section` is an Array pass elements of array through `closeModal` 436 | if (section.constructor === Array) { 437 | section.map(el => closeModal(el)); 438 | } 439 | 440 | // If section is an HTML element 441 | else { 442 | // Remove modal from activeModals array 443 | if (activeModals.indexOf(section) !== -1) { 444 | activeModals.splice(activeModals.indexOf(section), 1); 445 | } 446 | 447 | addClass(section, 'nodisplay'); 448 | } 449 | 450 | // If all modals are closed unblur section.main 451 | if (activeModals.length === 0) { 452 | removeClass(mainSection, 'blur'); 453 | addClass(mainSection, 'noblur'); 454 | } 455 | }; 456 | 457 | /** 458 | * Drag the revision modal with the header as the handle 459 | * Code borrowed and modified to es6 standards from: 460 | * https://www.w3schools.com/howto/howto_js_draggable.asp 461 | * 462 | * @param {String} name - name of modal to add draggability to 463 | */ 464 | const dragModal = name => { 465 | const el = getHtmlElement(`section.${name}`); 466 | let pos1 = 0; 467 | let pos2 = 0; 468 | let pos3 = 0; 469 | let pos4 = 0; 470 | 471 | const elementDrag = e => { 472 | e = e || window.event; 473 | e.preventDefault(); 474 | 475 | // Calculate new cursor position 476 | pos1 = pos3 - e.clientX; 477 | pos2 = pos4 - e.clientY; 478 | pos3 = e.clientX; 479 | pos4 = e.clientY; 480 | 481 | // Set element's new position 482 | el.style.top = (el.offsetTop - pos2) + 'px'; 483 | el.style.left = (el.offsetLeft - pos1) + 'px'; 484 | }; 485 | 486 | const closeDragElement = () => { 487 | // Stop moving when mouse button is released 488 | document.removeEventListener('mouseup', closeDragElement, false); 489 | document.removeEventListener('mousemove', elementDrag, false); 490 | }; 491 | 492 | const dragMouseDown = e => { 493 | e = e || window.event; 494 | e.preventDefault(); 495 | // Get mouse cursor position at startup 496 | pos3 = e.clientX; 497 | pos4 = e.clientY; 498 | document.addEventListener('mouseup', closeDragElement, false); 499 | document.addEventListener('mousemove', elementDrag, false); 500 | }; 501 | 502 | getHtmlElement(`section.${name} .header`).addEventListener('mousedown', dragMouseDown, false); 503 | }; 504 | 505 | /** 506 | * Simple time-display function for the bottom bar 507 | */ 508 | const timeDisplay = async () => { 509 | const timeEl = getHtmlElement('#time'); 510 | let dateFormatText = localStorage.getItem('dateFormat'); 511 | if (!dateFormatText || dateFormatText === '') { 512 | dateFormatText = 'dd/mm/yyyy - HH:MM:ss'; 513 | } 514 | 515 | setInterval(() => { 516 | const now = new Date(); 517 | const output = dateformat(now, dateFormatText); 518 | timeEl.innerHTML = output; 519 | }, 1000); 520 | }; 521 | 522 | /** 523 | * Main initiator function 524 | */ 525 | const initiate = async () => { 526 | const customCss = localStorage.getItem('customCss') || ''; 527 | 528 | if (customCss.trim().length > 0) { 529 | const style = document.createElement('style'); 530 | style.innerHTML = customCss; 531 | document.head.append(style); 532 | } 533 | 534 | rawText = localStorage.getItem('rawText'); 535 | 536 | /** 537 | * First things first: Set and apply settings 538 | */ 539 | await setEventListenersToSettings(); 540 | setSettings(); 541 | settingsControl(); 542 | 543 | /** 544 | * Initiate the markdown renderer 545 | * with specified options 546 | * 547 | * TODO: Allow user to manually config 548 | * these options 549 | */ 550 | converter = new showdown.Converter({ 551 | simplifiedAutoLink: true, 552 | excludeTrailingPunctuationFromURLs: true, 553 | strikethrough: true, 554 | tables: true, 555 | tasklist: true, 556 | ghCodeBlocks: true, 557 | smoothLivePreview: true, 558 | smartIndentationFix: true, 559 | simpleLineBreaks: true, 560 | openLinksInNewWindow: false, 561 | emoji: true 562 | }); 563 | 564 | /** 565 | * GitHub-styled markdown to allow 566 | * tasklists, tables, simple-line-breaks etc. 567 | */ 568 | converter.setFlavor('github'); 569 | 570 | /** 571 | * 1. Get `rawText` from localStorage and populate textarea with it 572 | * 2. Initiate first `save` to render markdown 573 | */ 574 | textarea.value = rawText === null ? `# Hello, world!\n\nStart editing right now by clicking the *edit* button or pressing ${navigator.platform.match('Mac') ? 'Cmd' : 'Ctrl'} + X.\n\nTo save the file click the *save* button or press ${navigator.platform.match('Mac') ? 'Cmd' : 'Ctrl'} + S.\n\nCheers!` : rawText; 575 | save(); 576 | 577 | // Enable modal dragging 578 | dragModal('history'); 579 | dragModal('settings'); 580 | 581 | // Initiate time display in bottom bar 582 | await timeDisplay(); 583 | 584 | /** 585 | * Last edited: _______ 586 | */ 587 | const lastEdited = localStorage.getItem('lastEdited'); 588 | if (lastEdited === '[object Object]') { 589 | const history = getHistory(); 590 | const actualLastEdited = history.length > 0 ? history[0].date : 0; 591 | syncStorageSet('lastEdited', actualLastEdited); 592 | } 593 | 594 | setInterval(async () => { 595 | let lastEdited = localStorage.getItem('lastEdited'); 596 | lastEdited = Number(lastEdited) === 0 ? undefined : lastEdited; 597 | getHtmlElement('#lastEdited').innerHTML = lastEdited ? 598 | `Last edited: ${format(new Date(lastEdited))}` : 599 | 'Last edited: Never'; 600 | }, 1000); 601 | 602 | /** 603 | * *************** 604 | * EVENT LISTENERS 605 | * *************** 606 | */ 607 | 608 | /** 609 | * Add event listeners to edit, save and modal buttons 610 | */ 611 | getHtmlElement('#edit').addEventListener('click', () => { 612 | edit(); 613 | }, false); 614 | getHtmlElement('#save').addEventListener('click', () => { 615 | save(); 616 | }, false); 617 | getHtmlElement('#lastEdited').addEventListener('click', () => { 618 | openModal(historySection, populateHistoryHtml); 619 | }, false); 620 | getHtmlElement('#closeHistory').addEventListener('click', () => { 621 | closeModal(historySection); 622 | }, false); 623 | getHtmlElement('#settings').addEventListener('click', () => { 624 | openModal(settingsSection); 625 | }, false); 626 | getHtmlElement('#closeSettings').addEventListener('click', () => { 627 | closeModal(settingsSection); 628 | }, false); 629 | 630 | /** 631 | * Capture keystrokes and perform respective functions: 632 | * 633 | * Ctrl + S => Save input (`save` function) 634 | * Ctrl + X => Edit input (`edit` function) 635 | * 636 | * Esc => Close modals 637 | */ 638 | document.addEventListener('keydown', e => { 639 | // Control Key 640 | if (navigator.platform.match('Mac') ? e.metaKey : e.ctrlKey) { 641 | if (returnKeyToggle) { 642 | if (e.keyCode === 13) { 643 | if (renderBox.classList.contains('nodisplay')) { 644 | e.preventDefault(); 645 | save(); 646 | } else { 647 | e.preventDefault(); 648 | edit(); 649 | } 650 | } 651 | } else if (e.keyCode === 83) { 652 | if (renderBox.classList.contains('nodisplay')) { 653 | e.preventDefault(); 654 | save(); 655 | } 656 | } else if (e.keyCode === 88) { 657 | if (textarea.classList.contains('nodisplay')) { 658 | e.preventDefault(); 659 | edit(); 660 | } 661 | } 662 | } 663 | // Escape key to close modals 664 | else if (e.keyCode === 27) { 665 | // Close each modal one-by-one from the last opened to the first opened 666 | if (activeModals.length > 0) { 667 | closeModal([...activeModals].pop()); 668 | } 669 | } 670 | }, false); 671 | 672 | /** 673 | * "Auto-save" on tab change when tab is 674 | * unfocused and edit mode is active 675 | */ 676 | document.addEventListener('visibilitychange', () => { 677 | if (document.hidden && renderBox.classList.contains('nodisplay')) { 678 | save(0); 679 | edit(); 680 | textarea.selectionStart = Number(localStorage.getItem('cursorLastPosition')); 681 | } 682 | }); 683 | }; 684 | 685 | /** 686 | * INITIATE!!! 687 | */ 688 | (async () => { 689 | /** 690 | * Browser Sync 691 | */ 692 | 693 | if (browser.storage) { 694 | browser.storage.sync.get().then(items => { 695 | if (!browser.runtime.error) { 696 | for (const [key, value] of Object.entries(items)) { 697 | localStorage.setItem(key, value); 698 | } 699 | } 700 | }); 701 | } 702 | 703 | await initiate(); 704 | })(); 705 | --------------------------------------------------------------------------------