├── 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 |
10 |
--------------------------------------------------------------------------------
/src/static/svg/close.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
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 |
45 |
--------------------------------------------------------------------------------
/src/static/svg/bin.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
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 |
52 |
--------------------------------------------------------------------------------
/src/static/svg/settings.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
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 | [](LICENSE)
7 | [](https://travis-ci.org/plibither8/markdown-new-tab)
8 | [][link-cws]
9 | [][link-amo]
10 | 
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 |
17 |
18 | 
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 | [](https://www.patreon.com/plibither8)
86 |
87 | [](https://paypal.me/plibither8)
88 |
89 | [](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 |
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 |
--------------------------------------------------------------------------------