├── .editorconfig ├── .gitignore ├── CHANGELOG.md ├── LICENSE ├── README.md ├── background ├── compilers │ ├── commonmark.js │ ├── markdown-it.js │ ├── marked.js │ ├── remark.js │ ├── remarkable.js │ └── showdown.js ├── detect.js ├── icon.js ├── index.js ├── inject.js ├── mathjax.js ├── messages.js ├── storage.js ├── webrequest.js └── xhr.js ├── build ├── README.md ├── bootstrap │ ├── build.sh │ └── package.json ├── csso │ ├── build.sh │ └── package.json ├── markdown-it │ ├── build.sh │ ├── markdown-it.mjs │ ├── package.json │ └── rollup.mjs ├── marked │ ├── build.sh │ ├── marked.mjs │ ├── package.json │ └── rollup.mjs ├── mathjax │ ├── build.sh │ └── package.json ├── mdc │ ├── build.sh │ ├── mdc.mjs │ ├── mdc.scss │ ├── package.json │ └── rollup.mjs ├── mermaid │ ├── build.sh │ ├── fix-csp-issue.js │ └── package.json ├── mithril │ ├── build.sh │ └── package.json ├── package.sh ├── panzoom │ ├── build.sh │ └── package.json ├── prism │ ├── build.sh │ ├── fix-autoloader.js │ ├── fix-themes.js │ └── package.json ├── remark │ ├── build.sh │ ├── package.json │ ├── remark.mjs │ └── rollup.mjs └── themes │ ├── build.sh │ ├── fix-themes.js │ └── package.json ├── content ├── anchor.svg ├── autoreload.js ├── emoji.js ├── index.css ├── index.js ├── mathjax.js ├── mermaid.js ├── prism.js ├── scroll.js └── themes.css ├── firefox.md ├── icons ├── dark │ ├── 128x128.png │ ├── 16x16.png │ ├── 19x19.png │ ├── 38x38.png │ └── 48x48.png ├── default │ ├── 128x128.png │ ├── 16x16.png │ ├── 19x19.png │ ├── 38x38.png │ └── 48x48.png └── light │ ├── 128x128.png │ ├── 16x16.png │ ├── 19x19.png │ ├── 38x38.png │ └── 48x48.png ├── manifest.chrome.json ├── manifest.firefox.json ├── options ├── custom.js ├── index.css ├── index.html ├── index.js ├── origins.js └── settings.js └── popup ├── index.css ├── index.html └── index.js /.editorconfig: -------------------------------------------------------------------------------- 1 | # editorconfig.org 2 | root = true 3 | 4 | [*] 5 | indent_style = space 6 | indent_size = 2 7 | end_of_line = lf 8 | charset = utf-8 9 | trim_trailing_whitespace = true 10 | insert_final_newline = true 11 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | npm-debug.log 2 | coverage/ 3 | node_modules/ 4 | package-lock.json 5 | 6 | /themes/ 7 | /vendor/ 8 | /*.zip 9 | /manifest.json 10 | /background/index-compilers.js 11 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | 2 | # Change Log 3 | 4 | ## v5.3 - 2024-04-30 5 | - custom theme support 6 | - syntax highlighted raw markdown view 7 | - pan, zoom and resize mermaid diagrams 8 | - improved settings page 9 | - fix frontmatter being stripped in raw markdown view 10 | - fix anchor icon missing on origins with strict csp 11 | - fix emoji rendered in prism code blocks 12 | - update mermaid from v10.4.0 to v10.8.0 13 | - update marked from v9.0.3 to v12.0.1 14 | 15 | ## v5.2 - 2023-09-26 16 | - add markdown-it as default compiler 17 | - add support for markdown extended syntax 18 | - fix auto scroll on very large pages 19 | - fix detect wildcard subdomains in origin 20 | - fix allow port in origin 21 | - theme fixes 22 | - new default icon with an overlay 23 | - update mermaid from v9.4.0 to v10.4.0 24 | - update marked from v4 to v9 25 | 26 | ## v5.1 - 2023-03-14 27 | - new advanced options page for managing the enabled origins 28 | - each enabled origin now can have its own header and path detection setup 29 | - the header detection now includes the text/plain content type 30 | - header and path detection are now being applied together instead of one or the other 31 | - on hard reload automatically scroll to hash fragment or last known position 32 | - add missing tex/latex extensions for mathjax 33 | - strip yaml/toml frontmatter and use the title variable to set the page title 34 | - add favicon to markdown content pages 35 | - dark mode for the popup and the options page 36 | - bundle all prism languages and load them on demand 37 | - fix emoji regex to exclude html tags and code blocks 38 | - fix toc regex picking up header links 39 | - fix default github theme to be always light 40 | - print style fixes and other theme fixes 41 | - update marked from v4.1.1 to v4.2.5 42 | - update mermaid from v9.2.2 to v9.4.0 43 | - update github theme from v5.1.0 to v5.2.0 44 | 45 | ## v5.0 - 2022-12-05 46 | - migrate to manifest v3 47 | - update mathjax from v2 to v3 48 | - update mermaid from v8 to v9 49 | - update marked from v1 to v4 50 | - update remark from v13 to v14 51 | - update prism syntax support 52 | - add 30 new themes + update github themes 53 | - add dark theme support for prism and mermaid 54 | - add content width option 55 | - update table of content styles to match theme 56 | - expose syntax highlighting option 57 | - add hot autoreloading 58 | - add light icon option for dark browser theme 59 | - disabling CSP is no longer possible 60 | - customizing the page encoding is no longer possible 61 | 62 | ## v4.0 - 2020-12-24 63 | - add mermaid diagrams support 64 | - fix autoscroll in Chrome 87 65 | - update marked, remark and prism 66 | - update github theme 67 | - initial Edge release 68 | 69 | ## v3.9 - 2020-02-23 70 | - fix autoreload for file urls in Chrome 80 71 | 72 | ## v3.8 - 2020-01-05 73 | - remember ToC scroll position 74 | 75 | ## v3.7 - 2019-09-01 76 | - fixed incompatibility issue with other extensions 77 | - fix file:// URLs on 'Block third-party cookies' enabled 78 | - update marked, remark and prism 79 | - update github theme 80 | 81 | ## v3.6 - 2018-07-07 82 | - add autoreload content option 83 | - improve origin detection 84 | - show refresh button only on origins that need refreshing 85 | 86 | ## v3.5 - 2018-04-16 87 | - add csp option for each origin individually 88 | - add encoding option for each origin individually 89 | - improve content-type header detection for Firefox 90 | 91 | ## v3.4 - 2018-01-12 92 | - Firefox fixes 93 | 94 | ## v3.3 - 2017-11-22 95 | - patch content-type header in Firefox 96 | 97 | ## v3.2 - 2017-10-30 98 | - add mathjax support 99 | - migrate the UI to Material Components (MDC) 100 | - update remark compiler 101 | - update github theme 102 | - initial Firefox release 103 | 104 | ## v3.1 - 2017-04-28 105 | - add content-type header detection 106 | - add allow all origins button 107 | 108 | ## v3.0 - 2017-04-17 109 | - improve scroll logic 110 | - migrate to browser action 111 | - add emoji support 112 | 113 | ## v2.9 - 2017-04-01 114 | - add remark compiler 115 | 116 | ## v2.8 - 2017-03-29 117 | - update compiler options tab 118 | 119 | ## v2.7 - 2017-03-12 120 | - update github theme 121 | 122 | ## v2.6 - 2017-01-26 123 | - add table of content (ToC) 124 | - add content options tab 125 | - expose remember scroll position as option 126 | - update default path matching regex 127 | - update github-dark theme 128 | 129 | ## v2.5 - 2017-01-23 130 | - store scroll position for each path individually 131 | - add tabs to the extension popup 132 | 133 | ## v2.4 - 2017-01-02 134 | - load file urls dynamically 135 | - fix anchor links 136 | 137 | ## v2.3 - 2016-10-30 138 | - migrate to optional permissions 139 | - render based on manually allowed remote origins 140 | - add advanced options page to manage origins 141 | - overhaul the UI using Material Design (MDL) 142 | 143 | ## v2.2 - 2016-09-25 144 | - improve rendering time 145 | - update github theme 146 | 147 | ## v2.1 - 2016-09-13 148 | - add getcomposer.org to the list of excluded domains 149 | 150 | ## v2.0 - 2016-07-02 151 | - migrate the content script logic to mithril 152 | - update themes 153 | 154 | ## v1.8 - 2016-01-01 155 | - update marked 156 | - update github theme 157 | 158 | ## v1.7 - 2015-07-17 159 | - exclude bitbucket.org and gitlab.com from .md matching 160 | - match urls ending with anchor when showing the page action button 161 | 162 | ## v1.6 - 2015-04-21 163 | - update github theme 164 | - update prism 165 | 166 | ## v1.5 - 2014-10-19 167 | - update github theme 168 | 169 | ## v1.4 - 2014-08-20 170 | - update github theme 171 | 172 | ## v1.3 - 2014-07-31 173 | - fix scroll position on remote origins 174 | 175 | ## v1.2 - 2014-07-26 176 | - remember scroll position 177 | 178 | ## v1.1 - 2014-04-21 179 | - css theme fix 180 | 181 | ## v1.0 - 2014-04-09 182 | - render using marked compiler 183 | - raw markdown / rendered html toggle button 184 | - themes including a github one 185 | - compiler options 186 | - initial Chrome release 187 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2013-present, Simeon Velichkov (https://github.com/simov/markdown-viewer) 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | # Markdown Viewer / Browser Extension 3 | 4 | **Install: [Chrome]** / **[Firefox]** / **[Edge]** / **[Opera]** / **[Brave]** / **[Chromium]** / **[Vivaldi]** 5 | 6 | # Features 7 | 8 | - Secure by design 9 | - Render local and remote file URLs 10 | - Granular access to remote origins 11 | - Multiple markdown parsers 12 | - Full control over the compiler options ([markdown-it], [marked], [remark]) 13 | - 30+ Themes ([cleanrmd], [GitHub][github-theme]) 14 | - Custom theme support 15 | - GitHub Flavored Markdown (GFM) 16 | - Auto reload on file change 17 | - Syntax highlighted code blocks ([prism][prism]) 18 | - Table of Contents (ToC) 19 | - MathJax formulas ([mathjax]) 20 | - Mermaid diagrams ([mermaid]) 21 | - Convert emoji shortnames (icons provided free by [EmojiOne][emojione]) 22 | - Remember scroll position 23 | - Markdown Content-Type detection 24 | - Configurable Markdown file path detection 25 | - Settings synchronization 26 | - Raw and rendered markdown views 27 | - Free and Open Source 28 | 29 | # Table of Contents 30 | 31 | - **[After Install](#after-install)** 32 | - **[Themes](#themes)** 33 | - **[Compiler Options](#compiler-options)** 34 | - **[Content Options](#content-options)** 35 | - **[Manage Origins](#manage-origins)** 36 | - **[Syntax Examples](#syntax-examples)** 37 | 38 | > Additional documentation specific to [Firefox][firefox-docs] 39 | 40 | # After Install 41 | 42 | ## Local Files 43 | 44 | 1. Navigate to `chrome://extensions` 45 | 2. Locate the Markdown Viewer extension and click on the `Details` button 46 | 47 | ![img-extensions] 48 | 49 | 3. Make sure that the `Allow access to file URLs` switch is turned on 50 | 51 | ![img-file-access] 52 | 53 | ## Remote Files 54 | 55 | 1. Click on the Markdown Viewer icon and select [Advanced Options](#manage-origins) 56 | 2. Add the origins that you want enabled for the Markdown Viewer extension 57 | 58 | --- 59 | 60 | # Themes 61 | 62 | All themes support the following width options: 63 | 64 | - `auto` - automatically adjust the content width based on the screen size 65 | - `full` - 100% screen width 66 | - `wide` - fixed at 1400px 67 | - `large` - fixed at 1200px 68 | - `medium` - fixed at 992px 69 | - `small` - fixed at 768px 70 | - `tiny` - fixed at 576px 71 | 72 | The `auto` option on the `github` and `github-dark` themes has a fixed width with a surrounding border identical to a rendered `README.md` file for a repository hosted on github.com 73 | 74 | ## Custom Theme 75 | 76 | 1. Go to the Advanced Options and click on Settings 77 | 2. Select `CUSTOM` for Content Theme 78 | 3. Upload your Custom Theme below 79 | 4. Specify the Color Scheme of your theme 80 | 81 | > Your custom theme will be minified automatically on upload and it can be up to 8KB in size. 82 | 83 | > You can add `` to your markdown document to speed up development while working on your theme. Custom theme [example][custom-theme]. 84 | 85 | --- 86 | 87 | # Compiler Options 88 | 89 | Full **CommonMark** support including **GFM** tables and strikethrough **+** 90 | 91 | | Option | Default | Description 92 | | :- | :-: | :- 93 | | **abbr** | `false` | Abbreviation using `*[word]: Text` `` 94 | | **attr** | `false` | Custom attributes using `{}` curly brackets 95 | | **breaks** | `false` | Convert new lines `\n` in paragraphs into line breaks `
` 96 | | **cjk** | `false` | Suppress linebreaks between east asian characters 97 | | **deflist** | `false` | Definition list `
` 98 | | **footnote** | `false` | Footnotes `[^1]` `[^1]: a` 99 | | **html** | **`true`** | Enable HTML tags in source 100 | | **ins** | `false` | Inserted text `++a++` `` 101 | | **linkify** | **`true`** | Autoconvert URL-like text to links 102 | | **mark** | `false` | Marked text `==a==` `` 103 | | **sub** | `false` | Subscript `~a~` `` 104 | | **sup** | `false` | Superscript `^a^` `` 105 | | **tasklists** | `false` | Task lists `- [x]` 106 | | **typographer**| `false` | Enable some language-neutral replacement + quotes beautification 107 | | **xhtmlOut** | `false` | Use `/` to close single tags (`
`) 108 | 109 | --- 110 | 111 | # Content Options 112 | 113 | | Option | Default | Description 114 | | :- | :-: | :- 115 | | **autoreload** | `false` | Auto reload on file change 116 | | **emoji** | `false` | Convert emoji `:shortnames:` into EmojiOne images 117 | | **mathjax** | `false` | Render MathJax formulas 118 | | **mermaid** | `false` | Render Mermaid diagrams 119 | | **syntax** | **`true`** | Syntax highlighted fenced code blocks 120 | | **toc** | `false` | Generate Table of Contents 121 | 122 | ## Autoreload 123 | 124 | When enabled the extension will make a GET request every second to markdown files hosted on: 125 | 126 | - `file:///` URLs 127 | - any host that resolves to localhost IPv4 `127.0.0.1` or IPv6 `::1` 128 | 129 | ## Emoji 130 | 131 | Convert emoji :shortnames: into EmojiOne images: 132 | 133 | - Emoji shortnames like `:smile:` will be converted to :smile: using EmojiOne images. 134 | - Currently unicode symbols like `😄` and ASCII emoji like `:D` are not supported. 135 | 136 | ## MathJax 137 | 138 | The following MathJax delimiters are supported: 139 | 140 | - inline math: `\(math\)` and `$math$` 141 | - display math: `\[math\]` and `$$math$$` 142 | 143 | The following rules apply to your markdown content when MathJax is enabled: 144 | 145 | - Regular dollar sign `$` in text that is not part of a math formula should be escaped: `\$` 146 | - Regular markdown escaping for parentheses: `\(` and `\)`, and brackets: `\[` and `\]` is not supported. MathJax will convert anything between these delimiters to math formulas, unless they are wrapped in backticks: `` `\(` `` or fenced code blocks. 147 | 148 | ## Mermaid 149 | 150 | Render Mermaid diagrams wrapped in `mmd` or `mermaid` fenced code blocks: 151 | 152 | ```mmd 153 | sequenceDiagram 154 | ``` 155 | 156 | Alternatively diagrams can be wrapped in HTML tags: 157 | 158 | ```html 159 |

160 |   sequenceDiagram
161 | 
162 | ``` 163 | 164 | - resize the diagram container vertically by dragging the bottom right corner of the code block up or down 165 | - zoom in and out by holding down the Shift key and then using your mouse wheel 166 | - pan by holding down the left mouse button and drag in any direction 167 | 168 | ## Syntax 169 | 170 | Syntax highlighting for fenced code blocks: 171 | 172 | ```js 173 | var hello = 'hi' 174 | ``` 175 | 176 | Alternatively code blocks can be wrapped in HTML tags: 177 | 178 | ```html 179 |
var hello = 'hi'
180 | ``` 181 | 182 | > Full list of supported languages and their corresponding [aliases][prism-lang] to use. 183 | 184 | ## ToC 185 | 186 | Generates Table of Contents (ToC) based on the headers found in the markdown document. 187 | 188 | --- 189 | 190 | # Manage Origins 191 | 192 | Click on the Markdown Viewer icon and select `Advanced Options`. 193 | 194 | By default Markdown Viewer does not have access to any content: 195 | 196 | ![img-no-access] 197 | 198 | ## Enable File Access 199 | 200 | To enable access to file URLs follow [these steps](#local-files). 201 | 202 | In case access to local files was not enabled, an additional `Allow Access` button will be present next to the File Access header: 203 | 204 | ![img-file-access-allow] 205 | 206 | Clicking on it will point you to the built-in management page for the extension where you can toggle the `Allow access to file URLs` switch to enable it. 207 | 208 | ## Enable Site Access 209 | 210 | Access to individual sites can be enabled by copy/pasting a URL address into the Site Access text box and then clicking on the `Add` button next to it: 211 | 212 | ![img-site-access-add] 213 | 214 | > Access to both `http` and `https` protocols can be enabled by using a wildcard `*://raw.githubusercontent.com` 215 | 216 | > Access to all subdomains for a given hostname can be enabled by using a wildcard `https://*.githubusercontent.com` 217 | 218 | > Access to all ports on `localhost` can be enabled by adding `http://localhost`. 219 | > Access to a specific port can be enabled by adding `http://localhost:3000` 220 | 221 | ## Allow on All Sites 222 | 223 | Access to **all** sites can be enabled by clicking on the `Allow All` button next to the Site Access header: 224 | 225 | ![img-site-allow-all] 226 | 227 | > This is identical to adding the `*://*` pattern into the text box. 228 | 229 | ## Content Detection 230 | 231 | Each enabled origin has an option for content type header detection and path matching regular expression: 232 | 233 | ![img-site-access-enabled] 234 | 235 | ### Header Detection 236 | 237 | When this option is enabled the extension will check if the `content-type` header with a value of `text/markdown`, `text/x-markdown` or `text/plain` is present. 238 | 239 | ### Path Matching 240 | 241 | When this option is enabled the extension will check if the page URL matches the Path Matching RegExp. 242 | 243 | The default regular expression is: `\.(?:markdown|mdown|mkdn|md|mkd|mdwn|mdtxt|mdtext|text)(?:#.*|\?.*)?$` 244 | 245 | It is a simple regular expression that matches URLs ending with: 246 | 247 | - markdown file extension: `\.(?:markdown|mdown|mkdn|md|mkd|mdwn|mdtxt|mdtext|text)` 248 | - and optionally a hash or a querystring after that: `(?:#.*|\?.*)?` 249 | 250 | > The `?:` used in `(?:match)` stands for *non-capturing group* and it is there for performance reasons. 251 | 252 | You can modify the path matching regular expression for each enabled origin. The settings are being updated as you type. 253 | 254 | ### Path Matching Priority 255 | 256 | The enabled origins are matched from most to least specific: 257 | 258 | 1. `https://raw.githubusercontent.com` 259 | 2. `https://*.githubusercontent.com` 260 | 3. `*://raw.githubusercontent.com` 261 | 4. `*://*.githubusercontent.com` 262 | 5. `*://*` 263 | 264 | The matching origin with the highest priority will be picked and its header detection and path matching settings will be used to determine if the content should be rendered or not. 265 | 266 | > It is recommended to explicitly allow only the origins that you want the extension to have access to. 267 | 268 | ## Remove Origin 269 | 270 | Click on the `Remove` button for the origin that you want to remove. This removes the permission itself so that the extension can no longer access that origin. 271 | 272 | ## Refresh Origin 273 | 274 | The extension synchronizes your preferences across all of your devices if you have logged into your browser and enabled the sync feature. The list of your allowed origins is being synced too. However, the actual permissions that you grant using the consent popup cannot be synced. 275 | 276 | In case you have enabled a new origin on some of your devices you will have to explicitly allow it on your other devices. In this case the origin will be highlighted and an additional `Refresh` button will be present: 277 | 278 | ![img-site-refresh] 279 | 280 | Only origins that needs to be refreshed will be highlighted. The extension cannot access highlighted origins unless you click on the `Refresh` button. 281 | 282 | > In some cases access to previously allowed origins may get disabled. Make sure you check back the advanced options page or reload it and look for highlighted origins that needs to be refreshed. 283 | 284 | --- 285 | 286 | # Syntax Examples 287 | 288 | Examples about the Markdown syntax and all features available in Markdown Viewer can be found on [GitHub][syntax-github], [GitLab][syntax-gitlab] and [BitBucket][syntax-bitbucket]: 289 | 290 | - **elements.md** - quick overview of the Markdown syntax and a summary of the Markdown Viewer features 291 | - **syntax.md** - extensive list of Markdown syntax examples with different combinations and edge cases 292 | - **prism.md** - syntax highlighting examples 293 | - **mermaid.md** - different types of Mermaid diagrams 294 | - **mathjax.md** - MathJax examples and support documentation 295 | 296 | Allow the appropriate remote origin or pull any of the above repositories and access it on the `file:///` origin locally. 297 | 298 | --- 299 | 300 | # Manual Install 301 | 302 | The following instructions applies for: Chrome, Edge, Opera, Brave, Chromium and Vivaldi. 303 | 304 | Note that in any of the following cases you won't receive any future updates automatically! 305 | 306 | ## Load packed .crx 307 | 308 | 1. Go to [releases] and pick a release that you want to install 309 | 2. Download the `markdown-viewer.crx` file 310 | 3. Navigate to `chrome://extensions` 311 | 4. Drag and drop the `markdown-viewer.crx` file into the `chrome://extensions` page 312 | 313 | ## Load unpacked .zip 314 | 315 | 1. Go to [releases] and pick a release that you want to install 316 | 2. Download the `markdown-viewer.zip` file and extract it 317 | 3. Navigate to `chrome://extensions` 318 | 4. Make sure that the `Developer mode` switch is enabled 319 | 5. Click on the `Load unpacked` button and select the extracted directory 320 | 321 | ## Build 322 | 323 | 1. Clone this repository 324 | 2. Execute `sh build/package.sh chrome` (or `firefox` to build for Firefox) 325 | 3. Navigate to `chrome://extensions` 326 | 4. Make sure that the `Developer mode` switch is enabled 327 | 5. Click on the `Load unpacked` button and select the cloned directory 328 | 329 | ## Manifest v2 330 | 331 | 1. Clone the [mv2] or [compilers-mv2] branch (Markdown Viewer v4.0) 332 | 2. Navigate to `chrome://extensions` 333 | 3. Make sure that the `Developer mode` switch is enabled 334 | 4. Click on the `Load unpacked` button and select the cloned directory 335 | 336 | --- 337 | 338 | # License 339 | 340 | The MIT License (MIT) 341 | 342 | Copyright (c) 2013-present, Simeon Velichkov (https://github.com/simov/markdown-viewer) 343 | 344 | Permission is hereby granted, free of charge, to any person obtaining a copy 345 | of this software and associated documentation files (the "Software"), to deal 346 | in the Software without restriction, including without limitation the rights 347 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 348 | copies of the Software, and to permit persons to whom the Software is 349 | furnished to do so, subject to the following conditions: 350 | 351 | The above copyright notice and this permission notice shall be included in all 352 | copies or substantial portions of the Software. 353 | 354 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 355 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 356 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 357 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 358 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 359 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 360 | SOFTWARE. 361 | 362 | 363 | [chrome]: https://chromewebstore.google.com/detail/markdown-viewer/ckkdlimhmcjmikdlpkmbgfkaikojcbjk 364 | [firefox]: https://addons.mozilla.org/en-US/firefox/addon/markdown-viewer-chrome/ 365 | [edge]: https://microsoftedge.microsoft.com/addons/detail/markdown-viewer/cgfmehpekedojlmjepoimbfcafopimdg 366 | [opera]: https://chromewebstore.google.com/detail/markdown-viewer/ckkdlimhmcjmikdlpkmbgfkaikojcbjk 367 | [brave]: https://chromewebstore.google.com/detail/markdown-viewer/ckkdlimhmcjmikdlpkmbgfkaikojcbjk 368 | [chromium]: https://chromewebstore.google.com/detail/markdown-viewer/ckkdlimhmcjmikdlpkmbgfkaikojcbjk 369 | [vivaldi]: https://chromewebstore.google.com/detail/markdown-viewer/ckkdlimhmcjmikdlpkmbgfkaikojcbjk 370 | 371 | [marked]: https://github.com/markedjs/marked 372 | [remark]: https://github.com/remarkjs/remark 373 | [markdown-it]: https://github.com/markdown-it/markdown-it 374 | [commonmark]: https://github.com/commonmark/commonmark.js 375 | [showdown]: https://github.com/showdownjs/showdown 376 | [remarkable]: https://github.com/jonschlinkert/remarkable 377 | 378 | [emojione]: https://emojione.com 379 | [mathjax]: https://www.mathjax.org 380 | [mermaid]: https://mermaid.js.org 381 | [prism]: https://prismjs.com 382 | [github-theme]: https://github.com/sindresorhus/github-markdown-css 383 | [cleanrmd]: https://pkg.garrickadenbuie.com/cleanrmd/#themes 384 | 385 | [gfm]: https://github.github.com/gfm/ 386 | [prism-lang]: https://prismjs.com/#supported-languages 387 | [compilers]: https://github.com/simov/markdown-viewer/tree/compilers 388 | [releases]: https://github.com/simov/markdown-viewer/releases 389 | [mv2]: https://github.com/simov/markdown-viewer/tree/mv2 390 | [compilers-mv2]: https://github.com/simov/markdown-viewer/tree/compilers-mv2 391 | [firefox-docs]: https://github.com/simov/markdown-viewer/blob/main/firefox.md 392 | [custom-theme]: https://gist.github.com/simov/2a074a1c0123e6ba4bc2bfa6a67d3203 393 | 394 | [syntax-github]: https://github.com/simov/markdown-syntax 395 | [syntax-gitlab]: https://gitlab.com/simovelichkov/markdown-syntax 396 | [syntax-bitbucket]: https://bitbucket.org/simovelichkov/markdown-syntax 397 | 398 | [img-extensions]: https://i.imgur.com/kzullaI.png 399 | [img-file-access]: https://i.imgur.com/VVcPv0T.png 400 | [img-no-access]: https://i.imgur.com/U6mjgX0.png 401 | [img-file-access-allow]: https://i.imgur.com/2bStHeb.png 402 | [img-site-access-add]: https://i.imgur.com/CFg9JBt.png 403 | [img-site-allow-all]: https://i.imgur.com/MXZqFOB.png 404 | [img-site-access-enabled]: https://i.imgur.com/tFMzJ3l.png 405 | [img-site-refresh]: https://i.imgur.com/j0gATxT.png 406 | -------------------------------------------------------------------------------- /background/compilers/commonmark.js: -------------------------------------------------------------------------------- 1 | 2 | md.compilers.commonmark = (() => { 3 | var defaults = { 4 | safe: false, 5 | smart: false, 6 | } 7 | 8 | var description = { 9 | safe: 'Raw HTML will not be rendered', 10 | smart: [ 11 | 'Straight quotes will be made curly', 12 | '-- will be changed to an en dash', 13 | '--- will be changed to an em dash', 14 | 'and ... will be changed to ellipses' 15 | ].join('\n'), 16 | } 17 | 18 | var ctor = ({storage: {state}}) => ({ 19 | defaults, 20 | description, 21 | compile: (markdown) => (( 22 | reader = new commonmark.Parser(), 23 | writer = new commonmark.HtmlRenderer(state.commonmark) 24 | ) => 25 | writer.render(reader.parse(markdown)) 26 | )() 27 | }) 28 | 29 | return Object.assign(ctor, {defaults, description}) 30 | })() 31 | -------------------------------------------------------------------------------- /background/compilers/markdown-it.js: -------------------------------------------------------------------------------- 1 | 2 | var md = {compilers: {}} 3 | 4 | md.compilers['markdown-it'] = (() => { 5 | var defaults = { 6 | breaks: false, 7 | html: true, 8 | linkify: true, 9 | typographer: false, 10 | xhtmlOut: false, 11 | langPrefix: 'language-', 12 | quotes: '“”‘’', 13 | // plugins 14 | abbr: false, 15 | attrs: false, 16 | cjk: false, 17 | deflist: false, 18 | footnote: false, 19 | ins: false, 20 | mark: false, 21 | sub: false, 22 | sup: false, 23 | tasklists: false, 24 | } 25 | 26 | var description = { 27 | breaks: 'Convert \\n in paragraphs into
', 28 | html: 'Enable HTML tags in source', 29 | linkify: 'Autoconvert URL-like text to links', 30 | typographer: 'Enable some language-neutral replacement + quotes beautification', 31 | xhtmlOut: 'Use / to close single tags (
)', 32 | // plugins 33 | abbr: 'Abbreviation \n*[word]: Text', 34 | attrs: 'Custom attributes\n# header {#id}', 35 | cjk: 'Suppress linebreaks between east asian characters', 36 | deflist: 'Definition list
\ntitle\n: definition', 37 | footnote: 'Footnotes\nword[^1]\n[^1]: text', 38 | ins: 'Inserted text \n++text++', 39 | mark: 'Marked text \n==text==', 40 | sub: 'Subscript \n~text~', 41 | sup: 'Superscript \n^text^', 42 | tasklists: 'Task lists\n- [x]\n- [ ]', 43 | } 44 | 45 | var ctor = ({storage: {state}}) => ({ 46 | defaults, 47 | description, 48 | compile: (markdown) => 49 | mdit.mdit(state['markdown-it']) 50 | .use(mdit.anchor, { 51 | slugify: (s) => new mdit.slugger().slug(s) 52 | }) 53 | .use(state['markdown-it'].abbr ? mdit.abbr : () => {}) 54 | .use(state['markdown-it'].attrs ? mdit.attrs : () => {}) 55 | .use(state['markdown-it'].cjk ? mdit.cjk : () => {}) 56 | .use(state['markdown-it'].deflist ? mdit.deflist : () => {}) 57 | .use(state['markdown-it'].footnote ? mdit.footnote : () => {}) 58 | .use(state['markdown-it'].ins ? mdit.ins : () => {}) 59 | .use(state['markdown-it'].mark ? mdit.mark : () => {}) 60 | .use(state['markdown-it'].sub ? mdit.sub : () => {}) 61 | .use(state['markdown-it'].sup ? mdit.sup : () => {}) 62 | .use(state['markdown-it'].tasklists ? mdit.tasklists : () => {}) 63 | .render(markdown) 64 | }) 65 | 66 | return Object.assign(ctor, {defaults, description}) 67 | })() 68 | -------------------------------------------------------------------------------- /background/compilers/marked.js: -------------------------------------------------------------------------------- 1 | 2 | md.compilers.marked = (() => { 3 | var defaults = { 4 | breaks: false, 5 | gfm: true, 6 | pedantic: false, 7 | // plugins 8 | linkify: true, 9 | smartypants: false, 10 | } 11 | 12 | var description = { 13 | breaks: 'Enable GFM line breaks\n(requires the gfm option to be true)', 14 | gfm: 'Enable GFM\n(GitHub Flavored Markdown)', 15 | pedantic: 'Don\'t fix any of the original markdown\nbugs or poor behavior', 16 | // plugins 17 | linkify: 'Autoconvert URL-like text to links', 18 | smartypants: 'Use "smart" typographic punctuation\nfor things like quotes and dashes' 19 | } 20 | 21 | var ctor = ({storage: {state}}) => ({ 22 | defaults, 23 | description, 24 | compile: (markdown) => 25 | new marked.marked( 26 | state.marked, 27 | marked.headings(), 28 | state.marked.linkify ? marked.linkify() : () => {}, 29 | state.marked.smartypants ? marked.smartypants() : () => {}, 30 | ).parse(markdown) 31 | }) 32 | 33 | return Object.assign(ctor, {defaults, description}) 34 | })() 35 | -------------------------------------------------------------------------------- /background/compilers/remark.js: -------------------------------------------------------------------------------- 1 | 2 | md.compilers.remark = (() => { 3 | var defaults = { 4 | breaks: false, 5 | gfm: true, 6 | sanitize: false, 7 | } 8 | 9 | var description = { 10 | breaks: 'Exposes newline characters inside paragraphs as breaks', 11 | gfm: 'Toggle GFM (GitHub Flavored Markdown)', 12 | sanitize: 'Disable HTML tag rendering', 13 | } 14 | 15 | var ctor = ({storage: {state}}) => ({ 16 | defaults, 17 | description, 18 | compile: (markdown) => 19 | remark.remark() 20 | .use(remark.parse) 21 | .use(state.remark.gfm ? remark.gfm : undefined) 22 | .use(state.remark.breaks ? remark.breaks : undefined) 23 | .use(remark.stringify) 24 | .use(remark.slug) 25 | .use(remark.html, state.remark) // sanitize 26 | .processSync(markdown) 27 | .value 28 | }) 29 | 30 | return Object.assign(ctor, {defaults, description}) 31 | })() 32 | -------------------------------------------------------------------------------- /background/compilers/remarkable.js: -------------------------------------------------------------------------------- 1 | 2 | md.compilers.remarkable = (() => { 3 | var defaults = { 4 | breaks: false, 5 | html: true, 6 | linkify: true, 7 | typographer: false, 8 | xhtmlOut: false, 9 | langPrefix: 'language-', 10 | quotes: '“”‘’' 11 | } 12 | 13 | var description = { 14 | breaks: 'Convert \\n in paragraphs into
', 15 | html: 'Enable HTML tags in source', 16 | linkify: 'Autoconvert URL-like text to links', 17 | typographer: 'Enable some language-neutral replacement + quotes beautification', 18 | xhtmlOut: 'Use / to close single tags (
)' 19 | } 20 | 21 | var ctor = ({storage: {state}}) => ({ 22 | defaults, 23 | description, 24 | compile: (markdown) => 25 | new Remarkable('full', state.remarkable) 26 | .render(markdown) 27 | }) 28 | 29 | return Object.assign(ctor, {defaults, description}) 30 | })() 31 | -------------------------------------------------------------------------------- /background/compilers/showdown.js: -------------------------------------------------------------------------------- 1 | 2 | md.compilers.showdown = (() => { 3 | var defaults = null // see below 4 | 5 | var description = { 6 | disableForced4SpacesIndentedSublists: 'Disables the requirement of indenting nested sublists by 4 spaces', 7 | encodeEmails: 'Encode e-mail addresses through the use of Character Entities, transforming ASCII e-mail addresses into its equivalent decimal entities', 8 | ghCodeBlocks: 'Turn on/off GFM fenced code blocks support', 9 | ghCompatibleHeaderId: 'Generate header ids compatible with github style (spaces are replaced with dashes, a bunch of non alphanumeric chars are removed)', 10 | ghMentions: 'Enables github @mentions', 11 | literalMidWordUnderscores: 'Parse midword underscores as literal underscores', 12 | noHeaderId: 'Turn on/off generated header id', 13 | omitExtraWLInCodeBlocks: 'Omit the default extra whiteline added to code blocks', 14 | parseImgDimensions: 'Turn on/off image dimension parsing', 15 | prefixHeaderId: 'Specify a prefix to generated header ids', 16 | requireSpaceBeforeHeadingText: 'Makes adding a space between `#` and the header text mandatory (GFM Style)', 17 | simpleLineBreaks: 'Parses simple line breaks as
(GFM Style)', 18 | simplifiedAutoLink: 'Turn on/off GFM autolink style', 19 | smartIndentationFix: 'Tries to smartly fix indentation in es6 strings', 20 | smoothLivePreview: 'Prevents weird effects in live previews due to incomplete input', 21 | strikethrough: 'Turn on/off strikethrough support', 22 | tables: 'Turn on/off tables support', 23 | tablesHeaderId: 'Add an id to table headers', 24 | tasklists: 'Turn on/off GFM tasklist support', 25 | customizedHeaderId: 'Use text in curly braces as header id', 26 | rawPrefixHeaderId: 'Prevent modifying the prefix', 27 | rawHeaderId: 'Remove only spaces, \' and \' from generated header ids', 28 | tablesHeaderId: 'Adds an id property to table headers tags', 29 | openLinksInNewWindow: 'Open all links in new windows', 30 | backslashEscapesHTMLTags: 'Support for HTML Tag escaping', 31 | emoji: 'Enable emoji support', 32 | ellipsis: 'Replaces three dots with the ellipsis unicode character', 33 | metadata: 'Enable support for document metadata', 34 | splitAdjacentBlockquotes: 'Split adjacent blockquote blocks', 35 | } 36 | 37 | var flavor = (name) => { 38 | var options = showdown.getDefaultOptions() 39 | var flavor = showdown.getFlavorOptions(name) 40 | var result = {} 41 | for (var key in options) { 42 | result[key] = (flavor[key] !== undefined) ? flavor[key] : options[key] 43 | } 44 | return result 45 | } 46 | 47 | defaults = flavor('github') 48 | 49 | var ctor = ({storage: {state}}) => ({ 50 | defaults, 51 | description, 52 | compile: (markdown) => 53 | new showdown.Converter(state.showdown) 54 | .makeHtml(markdown) 55 | }) 56 | 57 | return Object.assign(ctor, {defaults, description}) 58 | })() 59 | -------------------------------------------------------------------------------- /background/detect.js: -------------------------------------------------------------------------------- 1 | 2 | md.detect = ({storage: {state}, inject}) => { 3 | 4 | var onwakeup = true 5 | 6 | var ff = (id, info, done) => { 7 | if (chrome.runtime.getBrowserInfo === undefined) { 8 | // chrome 9 | done('load') 10 | } 11 | else { 12 | var manifest = chrome.runtime.getManifest() 13 | if (manifest.browser_specific_settings && manifest.browser_specific_settings.gecko) { 14 | if (!info.url) { 15 | done('noop') 16 | } 17 | else { 18 | chrome.tabs.sendMessage(id, {message: 'ping'}) 19 | .then(() => done('noop')) 20 | .catch(() => done('load')) 21 | } 22 | } 23 | else { 24 | done('load') 25 | } 26 | } 27 | } 28 | 29 | var tab = (id, info, tab) => { 30 | 31 | if (info.status === 'loading') { 32 | ff(id, info, (action) => { 33 | if (action === 'noop') { 34 | return 35 | } 36 | // try 37 | chrome.scripting.executeScript({ 38 | target: {tabId: id}, 39 | func: () => 40 | JSON.stringify({ 41 | url: window.location.href, 42 | header: document.contentType, 43 | loaded: !!window.state, 44 | }) 45 | }, (res) => { 46 | if (chrome.runtime.lastError) { 47 | // origin not allowed 48 | return 49 | } 50 | 51 | try { 52 | var win = JSON.parse(res[0].result) 53 | if (!win) { 54 | return 55 | } 56 | } 57 | catch (err) { 58 | // JSON parse error 59 | return 60 | } 61 | 62 | if (win.loaded) { 63 | // anchor 64 | return 65 | } 66 | 67 | if (detect(win.header, win.url)) { 68 | if (onwakeup && chrome.webRequest) { 69 | onwakeup = false 70 | chrome.tabs.reload(id) 71 | } 72 | else { 73 | inject(id) 74 | } 75 | } 76 | }) 77 | }) 78 | } 79 | } 80 | 81 | var detect = (content, url) => { 82 | var location = new URL(url) 83 | 84 | var origin = 85 | state.origins[location.origin] || 86 | 87 | state.origins[location.protocol + '//' + location.hostname] || 88 | state.origins[location.protocol + '//' + location.host] || 89 | state.origins[location.protocol + '//*.' + location.hostname.replace(/^[^.]+\.(.*)/, '$1')] || 90 | state.origins[location.protocol + '//*.' + location.host.replace(/^[^.]+\.(.*)/, '$1')] || 91 | 92 | state.origins['*://' + location.hostname] || 93 | state.origins['*://' + location.host] || 94 | state.origins['*://*.' + location.hostname.replace(/^[^.]+\.(.*)/, '$1')] || 95 | state.origins['*://*.' + location.host.replace(/^[^.]+\.(.*)/, '$1')] || 96 | 97 | state.origins['*://*'] 98 | 99 | return ( 100 | (origin && origin.header && origin.path && origin.match && /\btext\/(?:(?:(?:x-)?markdown)|plain)\b/i.test(content) && new RegExp(origin.match).test(location.href)) || 101 | (origin && origin.header && !origin.path && /\btext\/(?:(?:(?:x-)?markdown)|plain)\b/i.test(content)) || 102 | (origin && origin.path && origin.match && !origin.header && new RegExp(origin.match).test(location.href)) 103 | ? origin 104 | : undefined 105 | ) 106 | } 107 | 108 | return {tab} 109 | } 110 | -------------------------------------------------------------------------------- /background/icon.js: -------------------------------------------------------------------------------- 1 | 2 | md.icon = ({storage: {state}}) => () => { 3 | 4 | setTimeout(() => 5 | chrome.action.setIcon({ 6 | path: [16, 19, 38, 48, 128].reduce((all, size) => ( 7 | all[size] = `/icons/${state.settings.icon}/${size}x${size}.png`, 8 | all 9 | ), {}) 10 | }) 11 | , 100) 12 | } 13 | -------------------------------------------------------------------------------- /background/index.js: -------------------------------------------------------------------------------- 1 | 2 | importScripts('/vendor/markdown-it.min.js') 3 | importScripts('/vendor/marked.min.js') 4 | importScripts('/vendor/remark.min.js') 5 | importScripts('/background/compilers/markdown-it.js') 6 | importScripts('/background/compilers/marked.js') 7 | importScripts('/background/compilers/remark.js') 8 | 9 | importScripts('/background/storage.js') 10 | importScripts('/background/webrequest.js') 11 | importScripts('/background/detect.js') 12 | importScripts('/background/inject.js') 13 | importScripts('/background/messages.js') 14 | importScripts('/background/mathjax.js') 15 | importScripts('/background/xhr.js') 16 | importScripts('/background/icon.js') 17 | 18 | ;(() => { 19 | var storage = md.storage(md) 20 | var inject = md.inject({storage}) 21 | var detect = md.detect({storage, inject}) 22 | var webrequest = md.webrequest({storage}) 23 | var mathjax = md.mathjax() 24 | var xhr = md.xhr() 25 | var icon = md.icon({storage}) 26 | 27 | var compilers = Object.keys(md.compilers) 28 | .reduce((all, compiler) => ( 29 | all[compiler] = md.compilers[compiler]({storage}), 30 | all 31 | ), {}) 32 | 33 | var messages = md.messages({storage, compilers, mathjax, xhr, webrequest, icon}) 34 | 35 | chrome.tabs.onUpdated.addListener(detect.tab) 36 | chrome.runtime.onMessage.addListener(messages) 37 | 38 | icon() 39 | })() 40 | -------------------------------------------------------------------------------- /background/inject.js: -------------------------------------------------------------------------------- 1 | 2 | md.inject = ({storage: {state}}) => (id) => { 3 | 4 | chrome.scripting.executeScript({ 5 | target: {tabId: id}, 6 | args: [{ 7 | theme: state.theme, 8 | raw: state.raw, 9 | themes: state.themes, 10 | content: state.content, 11 | compiler: state.compiler, 12 | custom: state.custom, 13 | icon: state.settings.icon, 14 | }], 15 | func: (_args) => { 16 | document.querySelector('pre').style.visibility = 'hidden' 17 | args = _args 18 | }, 19 | injectImmediately: true 20 | }) 21 | 22 | chrome.scripting.insertCSS({ 23 | target: {tabId: id}, 24 | files: [ 25 | '/content/index.css', 26 | '/content/themes.css', 27 | ] 28 | }) 29 | 30 | chrome.scripting.executeScript({ 31 | target: {tabId: id}, 32 | files: [ 33 | '/vendor/mithril.min.js', 34 | state.content.syntax && ['/vendor/prism.min.js', '/vendor/prism-autoloader.min.js', '/content/prism.js'], 35 | state.content.emoji && '/content/emoji.js', 36 | state.content.mermaid && ['/vendor/mermaid.min.js', '/vendor/panzoom.min.js', '/content/mermaid.js'], 37 | state.content.mathjax && ['/content/mathjax.js', '/vendor/mathjax/tex-mml-chtml.js'], 38 | '/content/index.js', 39 | '/content/scroll.js', 40 | state.content.autoreload && '/content/autoreload.js', 41 | ].filter(Boolean).flat(), 42 | injectImmediately: true 43 | }) 44 | 45 | } 46 | -------------------------------------------------------------------------------- /background/mathjax.js: -------------------------------------------------------------------------------- 1 | 2 | md.mathjax = () => { 3 | 4 | var delimiters = new RegExp([ 5 | /\$\$[^`]*?\$\$/, 6 | /\\\([^`]*?\\\)/, 7 | /\\\[[^`]*?\\\]/, 8 | /\\begin\{.*?\}[^`]*?\\end\{.*?\}/, 9 | /\$[^`\n]*?\$/, 10 | ] 11 | .map((regex) => `(?:${regex.source})`).join('|'), 'gi') 12 | 13 | var escape = (math) => 14 | math.replace(/[<>&]/gi, (symbol) => 15 | symbol === '>' ? '>' : 16 | symbol === '<' ? '<' : 17 | symbol === '&' ? '&': null 18 | ) 19 | 20 | var ctor = (map = {}) => ({ 21 | tokenize: (markdown) => 22 | markdown.replace(delimiters, (str, offset) => ( 23 | map[offset] = str, 24 | `?${offset}?` 25 | )) 26 | , 27 | detokenize: (html) => 28 | Object.keys(map) 29 | .reduce((html, offset) => 30 | html = html.replace(`?${offset}?`, () => escape(map[offset])), 31 | html 32 | ) 33 | }) 34 | 35 | return Object.assign(ctor, {delimiters, escape}) 36 | } 37 | -------------------------------------------------------------------------------- /background/messages.js: -------------------------------------------------------------------------------- 1 | 2 | md.messages = ({storage: {defaults, state, set}, compilers, mathjax, xhr, webrequest, icon}) => { 3 | 4 | return (req, sender, sendResponse) => { 5 | 6 | // content 7 | if (req.message === 'markdown') { 8 | var markdown = req.markdown 9 | 10 | if (state.content.mathjax) { 11 | var jax = mathjax() 12 | markdown = jax.tokenize(markdown) 13 | } 14 | 15 | var html = compilers[state.compiler].compile(markdown) 16 | 17 | if (state.content.mathjax) { 18 | html = jax.detokenize(html) 19 | } 20 | 21 | sendResponse({message: 'html', html}) 22 | } 23 | else if (req.message === 'autoreload') { 24 | xhr.get(req.location, (err, body) => { 25 | sendResponse({err, body}) 26 | }) 27 | } 28 | else if (req.message === 'prism') { 29 | chrome.scripting.executeScript({ 30 | target: {tabId: sender.tab.id}, 31 | files: [ 32 | `/vendor/prism/prism-${req.language}.min.js`, 33 | ], 34 | injectImmediately: true 35 | }, sendResponse) 36 | } 37 | else if (req.message === 'mathjax') { 38 | chrome.scripting.executeScript({ 39 | target: {tabId: sender.tab.id}, 40 | files: [ 41 | `/vendor/mathjax/extensions/${req.extension}.js`, 42 | ], 43 | injectImmediately: true 44 | }, sendResponse) 45 | } 46 | 47 | // popup 48 | else if (req.message === 'popup') { 49 | sendResponse(Object.assign({}, state, { 50 | options: state[state.compiler], 51 | description: compilers[state.compiler].description, 52 | compilers: Object.keys(compilers), 53 | themes: state.themes, 54 | settings: {theme: state.settings.theme} 55 | })) 56 | } 57 | else if (req.message === 'popup.theme') { 58 | set({theme: req.theme}) 59 | notifyContent({message: 'theme', theme: req.theme}) 60 | sendResponse() 61 | } 62 | else if (req.message === 'popup.raw') { 63 | set({raw: req.raw}) 64 | notifyContent({message: 'raw', raw: req.raw}) 65 | sendResponse() 66 | } 67 | else if (req.message === 'popup.themes') { 68 | set({themes: req.themes}) 69 | notifyContent({message: 'themes', themes: req.themes}) 70 | sendResponse() 71 | } 72 | else if (req.message === 'popup.defaults') { 73 | var options = Object.assign({}, defaults) 74 | options.origins = state.origins 75 | set(options) 76 | notifyContent({message: 'reload'}) 77 | sendResponse() 78 | } 79 | else if (req.message === 'popup.compiler.name') { 80 | set({compiler: req.compiler}) 81 | notifyContent({message: 'reload'}) 82 | sendResponse() 83 | } 84 | else if (req.message === 'popup.compiler.options') { 85 | set({[req.compiler]: req.options}) 86 | notifyContent({message: 'reload'}) 87 | sendResponse() 88 | } 89 | else if (req.message === 'popup.content') { 90 | set({content: req.content}) 91 | notifyContent({message: 'reload'}) 92 | webrequest() 93 | sendResponse() 94 | } 95 | else if (req.message === 'popup.advanced') { 96 | // ff: opens up about:addons with openOptionsPage 97 | if (/Firefox/.test(navigator.userAgent)) { 98 | chrome.management.getSelf((extension) => { 99 | chrome.tabs.create({url: extension.optionsUrl}) 100 | }) 101 | } 102 | else { 103 | chrome.runtime.openOptionsPage() 104 | } 105 | sendResponse() 106 | } 107 | 108 | // origins view 109 | else if (req.message === 'options.origins') { 110 | sendResponse({ 111 | origins: state.origins, 112 | match: state.match, 113 | }) 114 | } 115 | // origins options 116 | else if (req.message === 'origin.add') { 117 | state.origins[req.origin] = { 118 | header: true, 119 | path: true, 120 | match: defaults.match, 121 | } 122 | set({origins: state.origins}) 123 | sendResponse() 124 | } 125 | else if (req.message === 'origin.remove') { 126 | delete state.origins[req.origin] 127 | set({origins: state.origins}) 128 | webrequest() 129 | sendResponse() 130 | } 131 | else if (req.message === 'origin.update') { 132 | state.origins[req.origin] = req.options 133 | set({origins: state.origins}) 134 | webrequest() 135 | sendResponse() 136 | } 137 | 138 | // settings view 139 | else if (req.message === 'options.settings') { 140 | sendResponse(state.settings) 141 | } 142 | // settings options 143 | else if (req.message === 'options.icon') { 144 | set({settings: req.settings}) 145 | icon() 146 | sendResponse() 147 | } 148 | else if (req.message === 'options.theme') { 149 | set({settings: req.settings}) 150 | sendResponse() 151 | } 152 | else if (req.message === 'custom.get') { 153 | sendResponse(state.custom) 154 | } 155 | else if (req.message === 'custom.set') { 156 | set({custom: req.custom}).then(sendResponse).catch((err) => { 157 | if (/QUOTA_BYTES_PER_ITEM quota exceeded/.test(err.message)) { 158 | sendResponse({error: 'Minified theme exceeded 8KB in size!'}) 159 | } 160 | }) 161 | } 162 | 163 | return true 164 | } 165 | 166 | function notifyContent (req, res) { 167 | chrome.tabs.query({active: true, currentWindow: true}, (tabs) => { 168 | chrome.tabs.sendMessage(tabs[0].id, req, res) 169 | }) 170 | } 171 | } 172 | -------------------------------------------------------------------------------- /background/storage.js: -------------------------------------------------------------------------------- 1 | 2 | // chrome.storage.sync.clear() 3 | // chrome.permissions.getAll((p) => chrome.permissions.remove({origins: p.origins})) 4 | 5 | md.storage = ({compilers}) => { 6 | 7 | var defaults = md.storage.defaults(compilers) 8 | 9 | var state = {} 10 | 11 | async function set (options) { 12 | await chrome.storage.sync.set(options) 13 | Object.assign(state, options) 14 | } 15 | 16 | chrome.storage.sync.get((res) => { 17 | md.storage.bug(res) 18 | 19 | Object.assign(state, JSON.parse(JSON.stringify( 20 | !Object.keys(res).length ? defaults : res))) 21 | 22 | // in case of new providers from the compilers branch 23 | Object.keys(compilers).forEach((compiler) => { 24 | if (!state[compiler]) { 25 | state[compiler] = compilers[compiler].defaults 26 | } 27 | }) 28 | 29 | // mutate 30 | md.storage.migrations(state) 31 | 32 | set(state) 33 | }) 34 | 35 | return {defaults, state, set} 36 | } 37 | 38 | md.storage.defaults = (compilers) => { 39 | var match = '\\.(?:markdown|mdown|mkdn|md|mkd|mdwn|mdtxt|mdtext|text)(?:#.*|\\?.*)?$' 40 | 41 | var defaults = { 42 | theme: 'github', 43 | compiler: 'markdown-it', 44 | raw: false, 45 | match, 46 | themes: { 47 | width: 'auto', 48 | }, 49 | content: { 50 | autoreload: false, 51 | emoji: false, 52 | mathjax: false, 53 | mermaid: false, 54 | syntax: true, 55 | toc: false, 56 | }, 57 | origins: { 58 | 'file://': { 59 | header: true, 60 | path: true, 61 | match, 62 | } 63 | }, 64 | settings: { 65 | icon: 'default', 66 | theme: 'light', 67 | }, 68 | custom: { 69 | theme: '', 70 | color: 'auto', 71 | } 72 | } 73 | 74 | Object.keys(compilers).forEach((compiler) => { 75 | defaults[compiler] = compilers[compiler].defaults 76 | }) 77 | 78 | return defaults 79 | } 80 | 81 | md.storage.bug = (res) => { 82 | // reload extension bug 83 | chrome.permissions.getAll((permissions) => { 84 | var origins = Object.keys(res.origins || {}) 85 | chrome.permissions.remove({ 86 | origins: permissions.origins 87 | .filter((origin) => origins.indexOf(origin.slice(0, -2)) === -1) 88 | }) 89 | }) 90 | } 91 | 92 | md.storage.migrations = (state) => { 93 | // v3.6 -> v3.7 94 | if (typeof state.origins['file://'] === 'object') { 95 | state.origins['file://'].csp = false 96 | } 97 | if (typeof state.theme === 'string') { 98 | state.theme = { 99 | name: state.theme, 100 | url: chrome.runtime.getURL(`/themes/${state.theme}.css`) 101 | } 102 | } 103 | if (state.themes === undefined) { 104 | state.themes = [] 105 | } 106 | if (state.marked.tables !== undefined) { 107 | delete state.marked.tables 108 | } 109 | // v3.9 -> v4.0 110 | if (state.remark.commonmark !== undefined) { 111 | delete state.remark.commonmark 112 | } 113 | if (state.remark.pedantic !== undefined) { 114 | delete state.remark.pedantic 115 | } 116 | if (state.content.mermaid === undefined) { 117 | state.content.mermaid = false 118 | } 119 | if (state.themes === undefined || state.themes instanceof Array) { 120 | state.themes = {wide: false} 121 | } 122 | if (typeof state.theme === 'object') { 123 | state.theme = state.theme.name 124 | } 125 | // v4.0 -> v5.0 126 | Object.keys(state.origins).forEach((origin) => { 127 | state.origins[origin].csp = false 128 | state.origins[origin].encoding = '' 129 | }) 130 | if (state.marked.smartLists !== undefined) { 131 | delete state.marked.smartLists 132 | } 133 | if (state.content.syntax === undefined) { 134 | state.content.syntax = true 135 | } 136 | if (state.themes.wide !== undefined) { 137 | if (state.themes.wide) { 138 | state.themes.width = 'full' 139 | } 140 | delete state.themes.wide 141 | } 142 | if (state.icon === undefined) { 143 | state.icon = false 144 | } 145 | if (state.remark.footnotes !== undefined) { 146 | delete state.remark.footnotes 147 | } 148 | // v5.0 -> v5.1 149 | if (state.header !== null) { 150 | Object.keys(state.origins).forEach((origin) => { 151 | state.origins[origin].header = true 152 | state.origins[origin].path = true 153 | delete state.origins[origin].csp 154 | delete state.origins[origin].encoding 155 | }) 156 | state.header = null 157 | } 158 | if (state.content.scroll !== undefined) { 159 | delete state.content.scroll 160 | } 161 | if (state.settings === undefined) { 162 | state.settings = { 163 | icon: state.icon === true ? 'light' : 'dark', 164 | theme: 'light' 165 | } 166 | } 167 | // v5.1 -> v5.2 168 | if (state['markdown-it'].abbr === undefined) { 169 | Object.assign(state['markdown-it'], { 170 | abbr: false, 171 | attrs: false, 172 | cjk: false, 173 | deflist: false, 174 | footnote: false, 175 | ins: false, 176 | mark: false, 177 | sub: false, 178 | sup: false, 179 | tasklists: false, 180 | }) 181 | 182 | } 183 | if (state.marked.linkify === undefined) { 184 | Object.assign(state.marked, { 185 | linkify: true, 186 | }) 187 | delete state.marked.sanitize 188 | } 189 | // v5.2 -> v5.3 190 | if (state.custom === undefined) { 191 | state.custom = { 192 | theme: '', 193 | color: 'auto' 194 | } 195 | } 196 | } 197 | -------------------------------------------------------------------------------- /background/webrequest.js: -------------------------------------------------------------------------------- 1 | 2 | md.webrequest = ({storage: {state}}) => { 3 | 4 | var permissions = ['webRequest'] 5 | 6 | var filter = { 7 | urls: [''], 8 | types: ['main_frame', 'sub_frame'] 9 | } 10 | 11 | var onCompleted = ({ip, tabId}) => { 12 | if (ip && ip !== '127.0.0.1' && ip !== '::1') { 13 | setTimeout(() => { 14 | chrome.tabs.sendMessage(tabId, {message: 'autoreload'}) 15 | }, 500) 16 | } 17 | } 18 | 19 | var webrequest = () => { 20 | if (state.content.autoreload && !chrome.webRequest) { 21 | // request permissions 22 | chrome.permissions.request({permissions}, () => { 23 | // add listener 24 | chrome.webRequest.onCompleted.addListener(onCompleted, filter) 25 | }) 26 | } 27 | else if (!state.content.autoreload && chrome.webRequest) { 28 | // remove listener 29 | chrome.webRequest.onCompleted.removeListener(onCompleted) 30 | // remove permissions 31 | chrome.permissions.remove({permissions}, () => { 32 | chrome.webRequest = null 33 | }) 34 | } 35 | } 36 | 37 | // init 38 | if (chrome.webRequest) { 39 | chrome.webRequest.onCompleted.addListener(onCompleted, filter) 40 | } 41 | 42 | return webrequest 43 | } 44 | -------------------------------------------------------------------------------- /background/xhr.js: -------------------------------------------------------------------------------- 1 | 2 | md.xhr = () => { 3 | 4 | var get = (url, done) => { 5 | ;(async () => { 6 | await new Promise(async (resolve, reject) => { 7 | try { 8 | var res = await fetch(url + '?preventCache=' + Date.now()) 9 | done(null, await res.text()) 10 | } 11 | catch (err) { 12 | done(err) 13 | } 14 | }) 15 | })() 16 | } 17 | 18 | return {get} 19 | } 20 | -------------------------------------------------------------------------------- /build/README.md: -------------------------------------------------------------------------------- 1 | 2 | # Build 3 | 4 | Build the `themes` and `vendor` folders, and create the `markdown-viewer.zip` package: 5 | 6 | ```bash 7 | # pick a release tag 8 | git clone --depth 1 --branch 5.2 https://github.com/simov/markdown-viewer.git 9 | cd markdown-viewer/ 10 | # build 11 | sh build/package.sh 12 | ``` 13 | 14 | ## Build Dependencies 15 | 16 | - node >= 18 17 | - npm >= 10 18 | - git 19 | - zip 20 | 21 | ## Markdown Viewer Dependencies 22 | 23 | | module | version 24 | | :- | -: 25 | | bootstrap | 5.3.3 26 | | cleanrmd | 0.1.0 27 | | github-markdown-css | 5.5.1 28 | | mathjax | 3.2.2 29 | | mermaid | 10.8.0 30 | | mithril | 1.1.7 31 | | prismjs | 1.29.0 32 | | csso | 5.0.5 33 | | @panzoom/panzoom | 4.5.1 34 | | **markdown-it** 35 | | markdown-it | 13.0.1 36 | | markdown-it-abbr | 1.0.4 37 | | markdown-it-anchor | 8.6.7 38 | | markdown-it-attrs | 4.1.6 39 | | markdown-it-cjk-breaks | 1.1.3 40 | | markdown-it-deflist | 2.1.0 41 | | markdown-it-footnote | 3.0.3 42 | | markdown-it-ins | 3.0.1 43 | | markdown-it-mark | 3.0.1 44 | | markdown-it-sub | 1.0.0 45 | | markdown-it-sup | 1.0.0 46 | | markdown-it-task-lists | 2.1.1 47 | | github-slugger | 2.0.0 48 | | **marked** 49 | | marked | 12.0.1 50 | | marked-gfm-heading-id | 3.1.3 51 | | marked-linkify-it | 3.1.9 52 | | marked-smartypants | 1.1.6 53 | | **remark** 54 | | remark | 15.0.1 55 | | remark-breaks | 4.0.0 56 | | remark-gfm | 4.0.0 57 | | remark-html | 16.0.1 58 | | remark-slug | 7.0.1 59 | | **mdc** 60 | | @material/button | 0.37.1 61 | | @material/ripple | 0.37.1 62 | | @material/switch | 0.36.1 63 | | @material/tabs | 0.37.1 64 | | @material/textfield | 0.37.1 65 | -------------------------------------------------------------------------------- /build/bootstrap/build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # set current working directory to directory of the shell script 4 | cd "$(dirname "$0")" 5 | 6 | # before 7 | npm ci 2> /dev/null || npm i 8 | 9 | # copy 10 | cp node_modules/bootstrap/dist/css/bootstrap.min.css ../../vendor/bootstrap.min.css 11 | 12 | # after 13 | rm -rf node_modules/ 14 | -------------------------------------------------------------------------------- /build/bootstrap/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "markdown-viewer", 3 | "version": "0.0.0", 4 | "description": "Markdown Viewer / Browser Extension", 5 | "private": true, 6 | "dependencies": { 7 | "bootstrap": "5.3.3" 8 | }, 9 | "engines": { 10 | "node": ">=18.0.0" 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /build/csso/build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # set current working directory to directory of the shell script 4 | cd "$(dirname "$0")" 5 | 6 | # before 7 | npm ci 2> /dev/null || npm i 8 | 9 | # copy 10 | cp node_modules/csso/dist/csso.js ../../vendor/csso.min.js 11 | 12 | # after 13 | rm -rf node_modules/ 14 | -------------------------------------------------------------------------------- /build/csso/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "markdown-viewer", 3 | "version": "0.0.0", 4 | "description": "Markdown Viewer / Browser Extension", 5 | "private": true, 6 | "dependencies": { 7 | "csso": "5.0.5" 8 | }, 9 | "engines": { 10 | "node": ">=18.0.0" 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /build/markdown-it/build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # set current working directory to directory of the shell script 4 | cd "$(dirname "$0")" 5 | 6 | # before 7 | npm ci 2> /dev/null || npm i 8 | mkdir -p tmp 9 | 10 | # markdown-it.min.js 11 | npx rollup --config rollup.mjs --input markdown-it.mjs --file tmp/markdown-it.js 12 | npx terser --compress --mangle -- tmp/markdown-it.js > tmp/markdown-it.min.js 13 | 14 | # copy 15 | cp tmp/markdown-it.min.js ../../vendor/ 16 | 17 | # after 18 | rm -rf node_modules/ tmp/ 19 | -------------------------------------------------------------------------------- /build/markdown-it/markdown-it.mjs: -------------------------------------------------------------------------------- 1 | 2 | import mdit from 'markdown-it' 3 | import abbr from 'markdown-it-abbr' 4 | import anchor from 'markdown-it-anchor' 5 | import attrs from 'markdown-it-attrs' 6 | import cjk from 'markdown-it-cjk-breaks' 7 | import deflist from 'markdown-it-deflist' 8 | import footnote from 'markdown-it-footnote' 9 | import ins from 'markdown-it-ins' 10 | import mark from 'markdown-it-mark' 11 | import sub from 'markdown-it-sub' 12 | import sup from 'markdown-it-sup' 13 | import tasklists from 'markdown-it-task-lists' 14 | import slugger from 'github-slugger' 15 | 16 | export { 17 | mdit, 18 | abbr, 19 | anchor, 20 | attrs, 21 | cjk, 22 | deflist, 23 | footnote, 24 | ins, 25 | mark, 26 | sub, 27 | sup, 28 | tasklists, 29 | slugger, 30 | } 31 | -------------------------------------------------------------------------------- /build/markdown-it/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "markdown-viewer", 3 | "version": "0.0.0", 4 | "description": "Markdown Viewer / Browser Extension", 5 | "private": true, 6 | "dependencies": { 7 | "github-slugger": "2.0.0", 8 | "markdown-it": "13.0.1", 9 | "markdown-it-abbr": "1.0.4", 10 | "markdown-it-anchor": "8.6.7", 11 | "markdown-it-attrs": "4.1.6", 12 | "markdown-it-cjk-breaks": "1.1.3", 13 | "markdown-it-deflist": "2.1.0", 14 | "markdown-it-footnote": "3.0.3", 15 | "markdown-it-ins": "3.0.1", 16 | "markdown-it-mark": "3.0.1", 17 | "markdown-it-sub": "1.0.0", 18 | "markdown-it-sup": "1.0.0", 19 | "markdown-it-task-lists": "2.1.1" 20 | }, 21 | "devDependencies": { 22 | "@rollup/plugin-commonjs": "25.0.7", 23 | "@rollup/plugin-json": "6.1.0", 24 | "@rollup/plugin-node-resolve": "15.2.3", 25 | "rollup": "3.29.4", 26 | "rollup-plugin-polyfill-node": "0.13.0", 27 | "terser": "5.30.3" 28 | }, 29 | "engines": { 30 | "node": ">=18.0.0" 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /build/markdown-it/rollup.mjs: -------------------------------------------------------------------------------- 1 | 2 | import common from '@rollup/plugin-commonjs' 3 | import resolve from '@rollup/plugin-node-resolve' 4 | import json from '@rollup/plugin-json' 5 | import node from 'rollup-plugin-polyfill-node' 6 | 7 | 8 | export default { 9 | context: 'window', 10 | moduleContext: {id: 'window'}, 11 | 12 | plugins: [ 13 | common(), 14 | resolve(), 15 | json(), 16 | node(), 17 | ], 18 | 19 | output: { 20 | format: 'iife', 21 | name: 'mdit', 22 | }, 23 | } 24 | -------------------------------------------------------------------------------- /build/marked/build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # set current working directory to directory of the shell script 4 | cd "$(dirname "$0")" 5 | 6 | # before 7 | npm ci 2> /dev/null || npm i 8 | mkdir -p tmp 9 | 10 | # marked.min.js 11 | npx rollup --config rollup.mjs --input marked.mjs --file tmp/marked.js 12 | npx terser --compress --mangle -- tmp/marked.js > tmp/marked.min.js 13 | 14 | # copy 15 | cp tmp/marked.min.js ../../vendor/ 16 | 17 | # after 18 | rm -rf node_modules/ tmp/ 19 | -------------------------------------------------------------------------------- /build/marked/marked.mjs: -------------------------------------------------------------------------------- 1 | 2 | import {Marked as marked} from 'marked' 3 | import {gfmHeadingId as headings} from 'marked-gfm-heading-id' 4 | import linkify from 'marked-linkify-it' 5 | import {markedSmartypants as smartypants} from 'marked-smartypants' 6 | 7 | export { 8 | marked, 9 | headings, 10 | linkify, 11 | smartypants, 12 | } 13 | -------------------------------------------------------------------------------- /build/marked/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "markdown-viewer", 3 | "version": "0.0.0", 4 | "description": "Markdown Viewer / Browser Extension", 5 | "private": true, 6 | "dependencies": { 7 | "marked": "12.0.1", 8 | "marked-gfm-heading-id": "3.1.3", 9 | "marked-linkify-it": "3.1.9", 10 | "marked-smartypants": "1.1.6" 11 | }, 12 | "devDependencies": { 13 | "@rollup/plugin-commonjs": "23.0.4", 14 | "@rollup/plugin-node-resolve": "15.2.3", 15 | "rollup": "3.29.4", 16 | "rollup-plugin-polyfill-node": "0.13.0", 17 | "terser": "5.30.3" 18 | }, 19 | "engines": { 20 | "node": ">=18.0.0" 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /build/marked/rollup.mjs: -------------------------------------------------------------------------------- 1 | 2 | import common from '@rollup/plugin-commonjs' 3 | import resolve from '@rollup/plugin-node-resolve' 4 | import node from 'rollup-plugin-polyfill-node' 5 | 6 | 7 | export default { 8 | context: 'window', 9 | moduleContext: {id: 'window'}, 10 | 11 | plugins: [ 12 | common(), 13 | resolve(), 14 | node(), 15 | ], 16 | 17 | output: { 18 | format: 'iife', 19 | name: 'marked', 20 | }, 21 | } 22 | -------------------------------------------------------------------------------- /build/mathjax/build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # set current working directory to directory of the shell script 4 | cd "$(dirname "$0")" 5 | 6 | # before 7 | npm ci 2> /dev/null || npm i 8 | 9 | # copy 10 | # https://github.com/mathjax/MathJax#reducing-the-size-of-the-components-directory 11 | 12 | # tex-mml-chtml.js 13 | mkdir -p ../../vendor/mathjax 14 | cp node_modules/mathjax/es5/tex-mml-chtml.js ../../vendor/mathjax 15 | 16 | # extensions/ 17 | mkdir -p ../../vendor/mathjax/extensions 18 | cp node_modules/mathjax/es5/input/tex/extensions/*.js ../../vendor/mathjax/extensions/ 19 | 20 | # fonts/ 21 | mkdir -p ../../vendor/mathjax/fonts 22 | cp node_modules/mathjax/es5/output/chtml/fonts/woff-v2/*.woff ../../vendor/mathjax/fonts 23 | 24 | # after 25 | rm -rf node_modules/ 26 | -------------------------------------------------------------------------------- /build/mathjax/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "markdown-viewer", 3 | "version": "0.0.0", 4 | "description": "Markdown Viewer / Browser Extension", 5 | "private": true, 6 | "dependencies": { 7 | "mathjax": "3.2.2" 8 | }, 9 | "engines": { 10 | "node": ">=18.0.0" 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /build/mdc/build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # set current working directory to directory of the shell script 4 | cd "$(dirname "$0")" 5 | 6 | # before 7 | npm ci 2> /dev/null || npm i 8 | mkdir -p tmp 9 | 10 | # mdc.min.js 11 | npx rollup --config rollup.mjs --input mdc.mjs --file tmp/mdc.js 12 | npx terser --compress --mangle -- tmp/mdc.js > tmp/mdc.min.js 13 | 14 | # mdc.min.css 15 | npx node-sass --include-path node_modules/ mdc.scss tmp/mdc.css 16 | npx csso --input tmp/mdc.css --output tmp/mdc.min.css 17 | 18 | # copy 19 | cp tmp/mdc.min.* ../../vendor/ 20 | 21 | # after 22 | rm -rf node_modules/ tmp/ 23 | -------------------------------------------------------------------------------- /build/mdc/mdc.mjs: -------------------------------------------------------------------------------- 1 | 2 | import {MDCRipple} from '@material/ripple' 3 | import {MDCTabBar} from '@material/tabs' 4 | import {MDCTextField} from '@material/textfield' 5 | 6 | let ripple = {MDCRipple} 7 | let tabs = {MDCTabBar} 8 | let textfield = {MDCTextField} 9 | 10 | export { 11 | ripple, 12 | tabs, 13 | textfield, 14 | } 15 | -------------------------------------------------------------------------------- /build/mdc/mdc.scss: -------------------------------------------------------------------------------- 1 | 2 | $mdc-theme-primary: #607d8b; 3 | $mdc-theme-secondary: #607d8b; 4 | $mdc-theme-background: #fff; 5 | 6 | @import "@material/button/mdc-button"; 7 | @import "@material/ripple/mdc-ripple"; 8 | @import "@material/switch/mdc-switch"; 9 | @import "@material/tabs/mdc-tabs"; 10 | @import "@material/textfield/mdc-text-field"; 11 | -------------------------------------------------------------------------------- /build/mdc/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "markdown-viewer", 3 | "version": "0.0.0", 4 | "description": "Markdown Viewer / Browser Extension", 5 | "private": true, 6 | "dependencies": { 7 | "@material/button": "0.37.1", 8 | "@material/ripple": "0.37.1", 9 | "@material/switch": "0.36.1", 10 | "@material/tabs": "0.37.1", 11 | "@material/textfield": "0.37.1" 12 | }, 13 | "devDependencies": { 14 | "@rollup/plugin-commonjs": "23.0.2", 15 | "@rollup/plugin-node-resolve": "15.0.1", 16 | "csso-cli": "4.0.2", 17 | "node-sass": "9.0.0", 18 | "rollup": "3.29.4", 19 | "terser": "5.30.3" 20 | }, 21 | "engines": { 22 | "node": ">=18.0.0" 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /build/mdc/rollup.mjs: -------------------------------------------------------------------------------- 1 | 2 | import common from '@rollup/plugin-commonjs' 3 | import resolve from '@rollup/plugin-node-resolve' 4 | 5 | 6 | export default { 7 | 8 | context: 'window', 9 | moduleContext: {id: 'window'}, 10 | 11 | plugins: [ 12 | common(), 13 | resolve(), 14 | ], 15 | 16 | output: { 17 | format: 'iife', 18 | name: 'mdc', 19 | }, 20 | } 21 | -------------------------------------------------------------------------------- /build/mermaid/build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # set current working directory to directory of the shell script 4 | cd "$(dirname "$0")" 5 | 6 | # before 7 | npm ci 2> /dev/null || npm i 8 | mkdir -p tmp 9 | 10 | # mermaid.min.js 11 | node fix-csp-issue.js \ 12 | node_modules/mermaid/dist/mermaid.min.js \ 13 | tmp/mermaid.min.js 14 | 15 | # copy 16 | cp tmp/mermaid.min.js ../../vendor/mermaid.min.js 17 | 18 | # after 19 | rm -rf node_modules/ tmp/ 20 | -------------------------------------------------------------------------------- /build/mermaid/fix-csp-issue.js: -------------------------------------------------------------------------------- 1 | 2 | var fs = require('fs') 3 | var path = require('path') 4 | 5 | // mermaid.min.js 6 | var source = path.resolve(__dirname, process.argv[2]) 7 | var target = path.resolve(__dirname, process.argv[3]) 8 | fs.writeFileSync( 9 | target, 10 | fs.readFileSync(source, 'utf8') 11 | .replaceAll( 12 | // https://github.com/mermaid-js/mermaid/issues/5378 13 | // https://discourse.mozilla.org/t/cannot-inject-a-javascript-file-because-of-a-csp-limitations/128649 14 | 'Function("return this")', 15 | '(() => globalThis)' 16 | ), 17 | 'utf8' 18 | ) 19 | -------------------------------------------------------------------------------- /build/mermaid/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "markdown-viewer", 3 | "version": "0.0.0", 4 | "description": "Markdown Viewer / Browser Extension", 5 | "private": true, 6 | "dependencies": { 7 | "mermaid": "10.8.0" 8 | }, 9 | "engines": { 10 | "node": ">=18.0.0" 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /build/mithril/build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # set current working directory to directory of the shell script 4 | cd "$(dirname "$0")" 5 | 6 | # before 7 | npm ci 2> /dev/null || npm i 8 | 9 | # copy 10 | cp node_modules/mithril/mithril.min.js ../../vendor/mithril.min.js 11 | 12 | # after 13 | rm -rf node_modules/ 14 | -------------------------------------------------------------------------------- /build/mithril/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "markdown-viewer", 3 | "version": "0.0.0", 4 | "description": "Markdown Viewer / Browser Extension", 5 | "private": true, 6 | "dependencies": { 7 | "mithril": "1.1.7" 8 | }, 9 | "engines": { 10 | "node": ">=18.0.0" 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /build/package.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # exit if any of the intermediate steps fail 4 | set -e 5 | 6 | browser=$1 7 | 8 | if [ -z "$browser" ]; then 9 | echo "Specify target browser" 10 | echo "chrome, firefox" 11 | exit 1 12 | fi 13 | 14 | # set current working directory to directory of the shell script 15 | cd "$(dirname "$0")" 16 | 17 | # cleanup 18 | rm -rf ../themes 19 | rm -rf ../vendor 20 | rm -f ../markdown-viewer.zip 21 | mkdir -p ../themes 22 | mkdir -p ../vendor 23 | 24 | # build deps 25 | sh bootstrap/build.sh 26 | sh csso/build.sh 27 | sh markdown-it/build.sh 28 | sh marked/build.sh 29 | sh mathjax/build.sh 30 | sh mdc/build.sh 31 | sh mermaid/build.sh 32 | sh mithril/build.sh 33 | sh panzoom/build.sh 34 | sh prism/build.sh 35 | sh remark/build.sh 36 | sh themes/build.sh $browser 37 | 38 | # copy files 39 | mkdir -p tmp 40 | mkdir -p tmp/markdown-viewer 41 | cd .. 42 | cp -r background content icons options popup themes vendor LICENSE build/tmp/markdown-viewer/ 43 | 44 | # copy manifest.json 45 | if [ "$browser" = "chrome" ]; then 46 | cp manifest.chrome.json build/tmp/markdown-viewer/manifest.json 47 | cp manifest.chrome.json manifest.json 48 | elif [ "$browser" = "firefox" ]; then 49 | cp manifest.firefox.json build/tmp/markdown-viewer/manifest.json 50 | cp manifest.firefox.json manifest.json 51 | fi 52 | 53 | # archive the markdown-viewer folder itself 54 | if [ "$browser" = "chrome" ]; then 55 | cd build/tmp/ 56 | zip -r ../../markdown-viewer.zip markdown-viewer 57 | cd .. 58 | # archive the contents of the markdown-viewer folder 59 | elif [ "$browser" = "firefox" ]; then 60 | cd build/tmp/markdown-viewer/ 61 | zip -r ../../../markdown-viewer.zip . 62 | cd ../../ 63 | fi 64 | 65 | # cleanup 66 | rm -rf tmp/ 67 | -------------------------------------------------------------------------------- /build/panzoom/build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # set current working directory to directory of the shell script 4 | cd "$(dirname "$0")" 5 | 6 | # before 7 | npm ci 2> /dev/null || npm i 8 | 9 | # copy 10 | cp node_modules/@panzoom/panzoom/dist/panzoom.min.js ../../vendor/panzoom.min.js 11 | 12 | # after 13 | rm -rf node_modules/ 14 | -------------------------------------------------------------------------------- /build/panzoom/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "markdown-viewer", 3 | "version": "0.0.0", 4 | "description": "Markdown Viewer / Browser Extension", 5 | "private": true, 6 | "dependencies": { 7 | "@panzoom/panzoom": "4.5.1" 8 | }, 9 | "engines": { 10 | "node": ">=18.0.0" 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /build/prism/build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # set current working directory to directory of the shell script 4 | cd "$(dirname "$0")" 5 | 6 | # before 7 | npm ci 2> /dev/null || npm i 8 | mkdir -p tmp 9 | 10 | # build 11 | 12 | # prism.min.js 13 | npx terser --compress --mangle -- node_modules/prismjs/prism.js > tmp/prism.min.js 14 | 15 | # prism-autoloader.min.js 16 | node fix-autoloader.js \ 17 | node_modules/prismjs/plugins/autoloader/prism-autoloader.js \ 18 | tmp/prism-autoloader.js 19 | npx terser --compress --mangle -- tmp/prism-autoloader.js > tmp/prism-autoloader.min.js 20 | 21 | # prism.min.css 22 | # prism-okaidia.min.css 23 | npx csso --input node_modules/prismjs/themes/prism.css --output tmp/prism.min.css 24 | npx csso --input node_modules/prismjs/themes/prism-okaidia.css --output tmp/prism-okaidia.min.css 25 | node fix-themes.js \ 26 | tmp/prism.min.css \ 27 | tmp/prism-okaidia.min.css 28 | 29 | # copy 30 | cp tmp/prism.min.js ../../vendor/ 31 | cp tmp/prism-autoloader.min.js ../../vendor/ 32 | cp tmp/prism.min.css ../../vendor/ 33 | cp tmp/prism-okaidia.min.css ../../vendor/ 34 | # languages 35 | mkdir -p ../../vendor/prism/ 36 | cp node_modules/prismjs/components/prism-*.min.js ../../vendor/prism/ 37 | 38 | # after 39 | rm -rf node_modules/ tmp/ 40 | -------------------------------------------------------------------------------- /build/prism/fix-autoloader.js: -------------------------------------------------------------------------------- 1 | 2 | var fs = require('fs') 3 | var path = require('path') 4 | 5 | // prism-autoloader.js 6 | var source = path.resolve(__dirname, process.argv[2]) 7 | var target = path.resolve(__dirname, process.argv[3]) 8 | fs.writeFileSync( 9 | target, 10 | fs.readFileSync(source, 'utf8') 11 | .replace( 12 | // https://github.com/PrismJS/prism/issues/3654 13 | 'addScript(getLanguagePath(lang), function () {', 14 | 'Prism.plugins.autoloader.addScript(lang, function () {' 15 | ), 16 | 'utf8' 17 | ) 18 | -------------------------------------------------------------------------------- /build/prism/fix-themes.js: -------------------------------------------------------------------------------- 1 | 2 | var fs = require('fs') 3 | var path = require('path') 4 | 5 | // prism.min.css 6 | var file = path.resolve(__dirname, process.argv[2]) 7 | fs.writeFileSync( 8 | file, 9 | fs.readFileSync(file, 'utf8') 10 | .replace('background:0 0;', '') 11 | .replace('background:#f5f2f0', '') 12 | .replace('border-radius:.3em', '') 13 | .replace('padding:1em;', '') 14 | .replace('margin:.5em 0;', '') 15 | .replace('background:rgba(255,255,255,.5)', ''), 16 | 'utf8' 17 | ) 18 | 19 | // prism-okaidia.min.css 20 | var file = path.resolve(__dirname, process.argv[3]) 21 | fs.writeFileSync( 22 | file, 23 | fs.readFileSync(file, 'utf8') 24 | .replace('background:0 0;', '') 25 | .replace('background:#272822', '') 26 | .replace('border-radius:.3em', '') 27 | .replace('padding:1em;', '') 28 | .replace('margin:.5em 0;', ''), 29 | 'utf8' 30 | ) 31 | -------------------------------------------------------------------------------- /build/prism/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "markdown-viewer", 3 | "version": "0.0.0", 4 | "description": "Markdown Viewer / Browser Extension", 5 | "private": true, 6 | "dependencies": { 7 | "prismjs": "1.29.0" 8 | }, 9 | "devDependencies": { 10 | "csso-cli": "4.0.2", 11 | "terser": "5.30.3" 12 | }, 13 | "engines": { 14 | "node": ">=18.0.0" 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /build/remark/build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # set current working directory to directory of the shell script 4 | cd "$(dirname "$0")" 5 | 6 | # before 7 | npm ci 2> /dev/null || npm i 8 | mkdir -p tmp 9 | 10 | # remark.min.js 11 | npx rollup --config rollup.mjs --input remark.mjs --file tmp/remark.js 12 | npx terser --compress --mangle -- tmp/remark.js > tmp/remark.min.js 13 | 14 | # copy 15 | cp tmp/remark.min.js ../../vendor/ 16 | 17 | # after 18 | rm -rf node_modules/ tmp/ 19 | -------------------------------------------------------------------------------- /build/remark/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "markdown-viewer", 3 | "version": "0.0.0", 4 | "description": "Markdown Viewer / Browser Extension", 5 | "private": true, 6 | "dependencies": { 7 | "remark": "15.0.1", 8 | "remark-breaks": "4.0.0", 9 | "remark-gfm": "4.0.0", 10 | "remark-html": "16.0.1", 11 | "remark-slug": "7.0.1" 12 | }, 13 | "devDependencies": { 14 | "@rollup/plugin-commonjs": "25.0.7", 15 | "@rollup/plugin-node-resolve": "15.2.3", 16 | "rollup": "3.29.4", 17 | "rollup-plugin-polyfill-node": "0.13.0", 18 | "terser": "5.30.3" 19 | }, 20 | "engines": { 21 | "node": ">=18.0.0" 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /build/remark/remark.mjs: -------------------------------------------------------------------------------- 1 | 2 | import {remark} from 'remark' 3 | import parse from 'remark-parse' 4 | import stringify from 'remark-stringify' 5 | import gfm from 'remark-gfm' 6 | import breaks from 'remark-breaks' 7 | import html from 'remark-html' 8 | import slug from 'remark-slug' 9 | 10 | 11 | export { 12 | remark, 13 | parse, 14 | stringify, 15 | gfm, 16 | breaks, 17 | html, 18 | slug, 19 | } 20 | -------------------------------------------------------------------------------- /build/remark/rollup.mjs: -------------------------------------------------------------------------------- 1 | 2 | import common from '@rollup/plugin-commonjs' 3 | import resolve from '@rollup/plugin-node-resolve' 4 | import node from 'rollup-plugin-polyfill-node' 5 | 6 | 7 | export default { 8 | context: 'window', 9 | moduleContext: {id: 'window'}, 10 | 11 | plugins: [ 12 | common(), 13 | resolve(), 14 | node(), 15 | ], 16 | 17 | output: { 18 | format: 'iife', 19 | name: 'remark', 20 | }, 21 | } 22 | -------------------------------------------------------------------------------- /build/themes/build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | browser=$1 4 | 5 | # set current working directory to directory of the shell script 6 | cd "$(dirname "$0")" 7 | 8 | # before 9 | npm ci 2> /dev/null || npm i 10 | git -c https.proxy="" clone --depth 1 --branch v0.1.0 https://github.com/gadenbuie/cleanrmd.git 11 | mkdir -p ../../themes 12 | 13 | # minify 14 | 15 | # github 16 | npx csso --input node_modules/github-markdown-css/github-markdown-light.css --output ../../themes/github.css 17 | npx csso --input node_modules/github-markdown-css/github-markdown-dark.css --output ../../themes/github-dark.css 18 | 19 | # themes 20 | npx csso --input cleanrmd/inst/resources/almond/almond.css --output ../../themes/almond.css 21 | npx csso --input cleanrmd/inst/resources/awsm.css/awsm.css --output ../../themes/awsm.css 22 | npx csso --input cleanrmd/inst/resources/axist/axist.css --output ../../themes/axist.css 23 | npx csso --input cleanrmd/inst/resources/bamboo/bamboo.css --output ../../themes/bamboo.css 24 | npx csso --input cleanrmd/inst/resources/bullframe/bullframe.css --output ../../themes/bullframe.css 25 | npx csso --input cleanrmd/inst/resources/holiday/holiday.css --output ../../themes/holiday.css 26 | npx csso --input cleanrmd/inst/resources/kacit/kacit.css --output ../../themes/kacit.css 27 | npx csso --input cleanrmd/inst/resources/latex.css/latex.css --output ../../themes/latex.css 28 | # npx csso --input cleanrmd/inst/resources/markdown-air/markdown-air.css --output ../../themes/air.css 29 | npx csso --input cleanrmd/inst/resources/markdown-modest/markdown-modest.css --output ../../themes/modest.css 30 | npx csso --input cleanrmd/inst/resources/markdown-retro/markdown-retro.css --output ../../themes/retro.css 31 | # npx csso --input cleanrmd/inst/resources/markdown-splendor/markdown-splendor.css --output ../../themes/splendor.css 32 | npx csso --input cleanrmd/inst/resources/marx/marx.css --output ../../themes/marx.css 33 | npx csso --input cleanrmd/inst/resources/minicss/minicss.css --output ../../themes/mini.css 34 | npx csso --input cleanrmd/inst/resources/new.css/new.css --output ../../themes/new.css 35 | npx csso --input cleanrmd/inst/resources/no-class/no-class.css --output ../../themes/no-class.css 36 | npx csso --input cleanrmd/inst/resources/picocss/pico.css --output ../../themes/pico.css 37 | npx csso --input cleanrmd/inst/resources/sakura/sakura.css --output ../../themes/sakura.css 38 | npx csso --input cleanrmd/inst/resources/sakura-vader/sakura-vader.css --output ../../themes/sakura-vader.css 39 | npx csso --input cleanrmd/inst/resources/semantic/semantic.css --output ../../themes/semantic.css 40 | npx csso --input cleanrmd/inst/resources/simplecss/simple.css --output ../../themes/simple.css 41 | npx csso --input cleanrmd/inst/resources/style-sans/style-sans.css --output ../../themes/style-sans.css 42 | npx csso --input cleanrmd/inst/resources/style-serif/style-serif.css --output ../../themes/style-serif.css 43 | npx csso --input cleanrmd/inst/resources/stylize/stylize.css --output ../../themes/stylize.css 44 | npx csso --input cleanrmd/inst/resources/superstylin/superstylin.css --output ../../themes/superstylin.css 45 | npx csso --input cleanrmd/inst/resources/tacit/tacit.css --output ../../themes/tacit.css 46 | npx csso --input cleanrmd/inst/resources/vanilla/vanilla.css --output ../../themes/vanilla.css 47 | npx csso --input cleanrmd/inst/resources/water/water.css --output ../../themes/water.css 48 | npx csso --input cleanrmd/inst/resources/water-dark/water-dark.css --output ../../themes/water-dark.css 49 | npx csso --input cleanrmd/inst/resources/writ/writ.css --output ../../themes/writ.css 50 | 51 | # after 52 | rm -rf node_modules/ cleanrmd/ 53 | 54 | node fix-themes.js $browser 55 | -------------------------------------------------------------------------------- /build/themes/fix-themes.js: -------------------------------------------------------------------------------- 1 | 2 | var fs = require('fs') 3 | var path = require('path') 4 | var themes = path.resolve(__dirname, '../../themes/') 5 | 6 | var prefix = { 7 | chrome: 'chrome', 8 | firefox: 'moz' 9 | }[process.argv[2] || 'chrome'] 10 | 11 | fs.writeFileSync( 12 | path.resolve(themes, 'github.css'), 13 | fs.readFileSync(path.resolve(themes, 'github.css'), 'utf8') 14 | .replaceAll(/mask-image:url\(".*?"\)/g, `mask-image:url("${prefix}-extension://__MSG_@@extension_id__/content/anchor.svg")`), 15 | 'utf8' 16 | ) 17 | 18 | fs.writeFileSync( 19 | path.resolve(themes, 'github-dark.css'), 20 | fs.readFileSync(path.resolve(themes, 'github-dark.css'), 'utf8') 21 | .replaceAll(/mask-image:url\(".*?"\)/g, `mask-image:url("${prefix}-extension://__MSG_@@extension_id__/content/anchor.svg")`), 22 | 'utf8' 23 | ) 24 | 25 | fs.writeFileSync( 26 | path.resolve(themes, 'mini.css'), 27 | fs.readFileSync(path.resolve(themes, 'mini.css'), 'utf8') 28 | .replace('*,h5', 'body') 29 | .replace('*,html', 'body'), 30 | 'utf8' 31 | ) 32 | 33 | fs.writeFileSync( 34 | path.resolve(themes, 'latex.css'), 35 | fs.readFileSync(path.resolve(themes, 'latex.css'), 'utf8') 36 | .replace('scroll-behavior:smooth', ''), 37 | 'utf8' 38 | ) 39 | 40 | fs.writeFileSync( 41 | path.resolve(themes, 'simple.css'), 42 | fs.readFileSync(path.resolve(themes, 'simple.css'), 'utf8') 43 | .replace('scroll-behavior:smooth', ''), 44 | 'utf8' 45 | ) 46 | -------------------------------------------------------------------------------- /build/themes/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "markdown-viewer", 3 | "version": "0.0.0", 4 | "description": "Markdown Viewer / Browser Extension", 5 | "private": true, 6 | "dependencies": { 7 | "github-markdown-css": "5.5.1" 8 | }, 9 | "devDependencies": { 10 | "csso-cli": "4.0.2" 11 | }, 12 | "engines": { 13 | "node": ">=18.0.0" 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /content/anchor.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /content/autoreload.js: -------------------------------------------------------------------------------- 1 | 2 | ;(() => { 3 | var current = '' 4 | 5 | var response = (md) => { 6 | if (!current) { 7 | current = md 8 | } 9 | else if (current !== md) { 10 | state.reload.md = true 11 | current = md 12 | render(md) 13 | } 14 | } 15 | 16 | var xhr = new XMLHttpRequest() 17 | xhr.onreadystatechange = () => { 18 | if (xhr.readyState === 4) { 19 | response(xhr.responseText) 20 | } 21 | } 22 | 23 | var get = () => { 24 | if (location.protocol === 'file:') { 25 | chrome.runtime.sendMessage({ 26 | message: 'autoreload', 27 | location: location.href 28 | }, (res) => { 29 | if (res.err) { 30 | console.error(res.err) 31 | clearInterval(state.reload.interval) 32 | } 33 | else { 34 | response(res.body) 35 | } 36 | }) 37 | } 38 | else { 39 | xhr.open('GET', location.href + '?preventCache=' + Date.now(), true) 40 | try { 41 | xhr.send() 42 | } 43 | catch (err) { 44 | console.error(err) 45 | clearInterval(state.reload.interval) 46 | } 47 | } 48 | } 49 | 50 | get() 51 | state.reload.interval = setInterval(get, state.reload.ms) 52 | })() 53 | -------------------------------------------------------------------------------- /content/index.css: -------------------------------------------------------------------------------- 1 | 2 | /*---------------------------------------------------------------------------*/ 3 | /*global*/ 4 | 5 | html, body { 6 | padding: 0 !important; margin: 0 !important; 7 | width: auto !important; max-width: 100% !important; 8 | } 9 | 10 | details summary { 11 | cursor: pointer; 12 | } 13 | 14 | #_html, #_toc { 15 | word-wrap: break-word; 16 | visibility: hidden; 17 | } 18 | 19 | /*---------------------------------------------------------------------------*/ 20 | /*print*/ 21 | 22 | @media print { 23 | /*avoid page breaks immediately after these tags*/ 24 | h1, h2, h3, h4 { 25 | break-after: avoid; 26 | } 27 | /*avoid page breaks within these tags*/ 28 | pre, blockquote, summary, table, math, svg { 29 | break-inside: avoid; 30 | } 31 | /*hide sidebar*/ 32 | html body._toc-left { padding-left: 0px !important; } 33 | html body._toc-right { padding-right: 0px !important; } 34 | #_toc { display: none; } 35 | /*fix github themes auto*/ 36 | body._theme-github .markdown-body { border: 0; padding: 20px; } 37 | body._theme-github-dark .markdown-body { border: 0; padding: 20px; } 38 | /*wrap long code lines*/ 39 | pre, pre code, pre[class*=language-], code[class*=language-] 40 | { 41 | word-wrap: break-word !important; 42 | white-space: pre-wrap !important; 43 | } 44 | } 45 | 46 | /*---------------------------------------------------------------------------*/ 47 | /*raw markdown view*/ 48 | 49 | pre#_markdown, 50 | #_markdown > pre > code { 51 | word-wrap: break-word; 52 | white-space: pre-wrap; 53 | } 54 | 55 | /*---------------------------------------------------------------------------*/ 56 | /*all other themes*/ 57 | 58 | .markdown-theme { 59 | box-sizing: border-box; 60 | max-width: 100% !important; 61 | padding: 20px !important; 62 | margin: 0 auto !important; 63 | } 64 | .markdown-theme pre, 65 | .markdown-theme pre code { 66 | overflow: auto; 67 | overflow-x: auto; 68 | overflow-y: auto; 69 | } 70 | 71 | @media (max-width: 576px) { /*Extra small - none*/ 72 | .markdown-theme { width: auto !important; } 73 | } 74 | @media (min-width: 576px) and (max-width: 768px) { /*Small sm*/ 75 | .markdown-theme { width: 576px !important; } 76 | } 77 | @media (min-width: 768px) and (max-width: 992px) { /*Medium md*/ 78 | .markdown-theme { width: 768px !important; } 79 | } 80 | @media (min-width: 992px) and (max-width: 1200px) { /*Large lg*/ 81 | .markdown-theme { width: 992px !important; } 82 | } 83 | @media (min-width: 1200px) and (max-width: 1400px) { /*Extra large xl*/ 84 | .markdown-theme { width: 1200px !important; } 85 | } 86 | @media (min-width: 1400px) { /*Extra extra large xxl*/ 87 | .markdown-theme { width: 1400px !important; } 88 | } 89 | 90 | ._width-full, 91 | ._width-wide, 92 | ._width-large, 93 | ._width-medium, 94 | ._width-small, 95 | ._width-tiny { 96 | box-sizing: border-box; 97 | border: none !important; 98 | padding: 20px !important; 99 | min-width: auto !important; 100 | max-width: none !important; 101 | } 102 | 103 | ._width-full { 104 | width: 100% !important; 105 | margin: 0 !important; 106 | } 107 | ._width-wide { 108 | width: 1400px !important; 109 | } 110 | ._width-large { 111 | width: 1200px !important; 112 | } 113 | ._width-medium { 114 | width: 992px !important; 115 | } 116 | ._width-small { 117 | width: 768px !important; 118 | } 119 | ._width-tiny { 120 | width: 576px !important; 121 | } 122 | 123 | /*---------------------------------------------------------------------------*/ 124 | /*toc*/ 125 | 126 | @media (prefers-color-scheme: light) { 127 | body { 128 | --toc-delimiter: #e1e4e8; 129 | } 130 | } 131 | @media (prefers-color-scheme: dark) { 132 | body { 133 | --toc-delimiter: #30363d; 134 | } 135 | } 136 | body { 137 | display: flex; 138 | } 139 | body._toc-left { padding-left: 300px !important; } 140 | body._toc-right { padding-right: 300px !important; } 141 | #_toc { 142 | position: fixed; 143 | top: 0; bottom: 0; left: 0; 144 | width: 299px; 145 | height: 100%; 146 | border-right: 1px solid var(--toc-delimiter); 147 | overflow-y: auto; 148 | overflow-x: hidden; 149 | } 150 | #_toc ._ul { 151 | padding-left: 20px !important; 152 | margin: 0 !important; 153 | } 154 | #_toc > ._ul { 155 | padding: 0 0 0 10px !important; 156 | } 157 | #_toc > ._ul:first-child { 158 | padding-top: 15px !important; 159 | } 160 | #_toc > ._ul:last-child { 161 | padding-bottom: 15px !important; 162 | } 163 | #_toc ._ul a { 164 | border: 0 !important; 165 | padding: 5px 10px !important; 166 | display: block !important; 167 | } 168 | ._color-light #_toc { 169 | border-right: 1px solid #e1e4e8; 170 | } 171 | ._color-dark #_toc { 172 | border-right: 1px solid #30363d; 173 | } 174 | 175 | /*---------------------------------------------------------------------------*/ 176 | /*scrollbar*/ 177 | 178 | /*auto*/ 179 | @media (prefers-color-scheme: light) { 180 | body { 181 | --scrollbar-track: #efefef; 182 | --scrollbar-thumb: #d5d5d5; 183 | --scrollbar-thumb-hover: #c4c4c4; 184 | } 185 | } 186 | @media (prefers-color-scheme: dark) { 187 | body { 188 | --scrollbar-track: #424242; 189 | --scrollbar-thumb: #686868; 190 | --scrollbar-thumb-hover: #7b7b7b; 191 | } 192 | } 193 | ::-webkit-scrollbar, 194 | ::-webkit-scrollbar-corner { 195 | height: 10px; 196 | width: 10px; 197 | } 198 | ::-webkit-scrollbar-track { 199 | background: var(--scrollbar-track); 200 | border-radius: 6px; 201 | } 202 | ::-webkit-scrollbar-thumb { 203 | background: var(--scrollbar-thumb); 204 | border-radius: 6px; 205 | } 206 | ::-webkit-scrollbar-thumb:hover { 207 | background: var(--scrollbar-thumb-hover); 208 | } 209 | ._color-light::-webkit-scrollbar-track, ._color-light *::-webkit-scrollbar-track { 210 | background: #efefef; 211 | } 212 | ._color-light::-webkit-scrollbar-thumb, ._color-light *::-webkit-scrollbar-thumb { 213 | background: #d5d5d5; 214 | } 215 | ._color-light::-webkit-scrollbar-thumb:hover, ._color-light *::-webkit-scrollbar-thumb:hover { 216 | background: #c4c4c4; 217 | } 218 | /*dark*/ 219 | ._color-dark::-webkit-scrollbar-track, ._color-dark *::-webkit-scrollbar-track { 220 | background: #424242; 221 | } 222 | ._color-dark::-webkit-scrollbar-thumb, ._color-dark *::-webkit-scrollbar-thumb { 223 | background: #686868; 224 | } 225 | ._color-dark::-webkit-scrollbar-thumb:hover, ._color-dark *::-webkit-scrollbar-thumb:hover { 226 | background: #7b7b7b; 227 | } 228 | 229 | /*---------------------------------------------------------------------------*/ 230 | /*anchor link*/ 231 | 232 | /*github theme styles follows*/ 233 | .markdown-theme .octicon { 234 | display: inline-block; 235 | fill: currentColor; 236 | vertical-align: text-bottom; 237 | overflow: visible !important; 238 | } 239 | 240 | .markdown-theme .anchor { 241 | float: left; 242 | padding-right: 4px; 243 | margin-left: -20px; 244 | line-height: 1; 245 | } 246 | 247 | .markdown-theme .anchor:focus { 248 | outline: none; 249 | } 250 | 251 | .markdown-theme h1:hover .anchor .octicon-link:before, 252 | .markdown-theme h2:hover .anchor .octicon-link:before, 253 | .markdown-theme h3:hover .anchor .octicon-link:before, 254 | .markdown-theme h4:hover .anchor .octicon-link:before, 255 | .markdown-theme h5:hover .anchor .octicon-link:before, 256 | .markdown-theme h6:hover .anchor .octicon-link:before { 257 | width: 16px; 258 | height: 16px; 259 | content: ' '; 260 | display: inline-block; 261 | background-color: currentColor; 262 | mask-image: url("chrome-extension://__MSG_@@extension_id__/content/anchor.svg"); 263 | } 264 | @supports (-moz-appearance: none) { 265 | .markdown-theme h1:hover .anchor .octicon-link:before, 266 | .markdown-theme h2:hover .anchor .octicon-link:before, 267 | .markdown-theme h3:hover .anchor .octicon-link:before, 268 | .markdown-theme h4:hover .anchor .octicon-link:before, 269 | .markdown-theme h5:hover .anchor .octicon-link:before, 270 | .markdown-theme h6:hover .anchor .octicon-link:before { 271 | mask-image: url("moz-extension://__MSG_@@extension_id__/content/anchor.svg"); 272 | } 273 | } 274 | 275 | .markdown-theme h1 .octicon-link, 276 | .markdown-theme h2 .octicon-link, 277 | .markdown-theme h3 .octicon-link, 278 | .markdown-theme h4 .octicon-link, 279 | .markdown-theme h5 .octicon-link, 280 | .markdown-theme h6 .octicon-link { 281 | vertical-align: middle; 282 | visibility: hidden; 283 | } 284 | 285 | .markdown-theme h1:hover .anchor, 286 | .markdown-theme h2:hover .anchor, 287 | .markdown-theme h3:hover .anchor, 288 | .markdown-theme h4:hover .anchor, 289 | .markdown-theme h5:hover .anchor, 290 | .markdown-theme h6:hover .anchor { 291 | text-decoration: none; 292 | } 293 | 294 | .markdown-theme h1:hover .anchor .octicon-link, 295 | .markdown-theme h2:hover .anchor .octicon-link, 296 | .markdown-theme h3:hover .anchor .octicon-link, 297 | .markdown-theme h4:hover .anchor .octicon-link, 298 | .markdown-theme h5:hover .anchor .octicon-link, 299 | .markdown-theme h6:hover .anchor .octicon-link { 300 | visibility: visible; 301 | } 302 | 303 | /*auto*/ 304 | @media (prefers-color-scheme: light) { 305 | .markdown-theme { 306 | --anchor: #24292f; 307 | } 308 | } 309 | @media (prefers-color-scheme: dark) { 310 | .markdown-theme { 311 | --anchor: #c9d1d9; 312 | } 313 | } 314 | .markdown-theme .octicon-link { 315 | color: var(--anchor); 316 | } 317 | ._color-light .octicon-link { 318 | color: #24292f; 319 | } 320 | ._color-dark .octicon-link { 321 | color: #c9d1d9; 322 | } 323 | 324 | /*---------------------------------------------------------------------------*/ 325 | /*misc*/ 326 | 327 | /*resize*/ 328 | pre:has(> code.mermaid) { 329 | resize: vertical; 330 | } 331 | /*pan/zoom*/ 332 | .markdown-body code.mermaid, 333 | .markdown-theme code.mermaid { 334 | display: block; 335 | height: 100%; 336 | } 337 | 338 | /*mermaid text bold effect*/ 339 | svg[id^=mermaid] text { 340 | stroke: none !important; 341 | } 342 | 343 | /*emojione*/ 344 | .emojione { 345 | /* Emoji Sizing */ 346 | font-size: inherit; 347 | height: 3ex; 348 | width: 3.1ex; 349 | min-height: 20px; 350 | min-width: 20px; 351 | 352 | /* Inline alignment adjust the margins */ 353 | display: inline-block; 354 | margin: -.2ex .15em .2ex; 355 | line-height: normal; 356 | vertical-align: middle; 357 | } 358 | img.emojione { 359 | /* prevent img stretch */ 360 | width: auto; 361 | } 362 | -------------------------------------------------------------------------------- /content/index.js: -------------------------------------------------------------------------------- 1 | 2 | var $ = document.querySelector.bind(document) 3 | 4 | var state = { 5 | theme: args.theme, 6 | raw: args.raw, 7 | themes: args.themes, 8 | content: args.content, 9 | compiler: args.compiler, 10 | custom: args.custom, 11 | icon: args.icon, 12 | html: '', 13 | markdown: '', 14 | toc: '', 15 | reload: { 16 | interval: null, 17 | ms: 1000, 18 | md: false, 19 | }, 20 | _themes: { 21 | 'github': 'light', 22 | 'github-dark': 'dark', 23 | 'almond': 'light', 24 | // 'air': 'light', 25 | 'awsm': 'light', 26 | 'axist': 'light', 27 | 'bamboo': 'auto', 28 | 'bullframe': 'light', 29 | 'holiday': 'auto', 30 | 'kacit': 'light', 31 | 'latex': 'light', 32 | 'marx': 'light', 33 | 'mini': 'light', 34 | 'modest': 'light', 35 | 'new': 'auto', 36 | 'no-class': 'auto', 37 | 'pico': 'auto', 38 | 'retro': 'dark', 39 | 'sakura': 'light', 40 | 'sakura-vader': 'dark', 41 | 'semantic': 'light', 42 | 'simple': 'auto', 43 | // 'splendor': 'light', 44 | 'style-sans': 'light', 45 | 'style-serif': 'light', 46 | 'stylize': 'light', 47 | 'superstylin': 'auto', 48 | 'tacit': 'light', 49 | 'vanilla': 'auto', 50 | 'water': 'light', 51 | 'water-dark': 'dark', 52 | 'writ': 'light', 53 | 'custom': 'auto', 54 | } 55 | } 56 | 57 | chrome.runtime.onMessage.addListener((req, sender, sendResponse) => { 58 | if (req.message === 'reload') { 59 | location.reload(true) 60 | } 61 | else if (req.message === 'theme') { 62 | state.theme = req.theme 63 | m.redraw() 64 | } 65 | else if (req.message === 'themes') { 66 | state.themes = req.themes 67 | m.redraw() 68 | } 69 | else if (req.message === 'raw') { 70 | state.raw = req.raw 71 | state.reload.md = true 72 | m.redraw() 73 | } 74 | else if (req.message === 'autoreload') { 75 | clearInterval(state.reload.interval) 76 | } 77 | }) 78 | 79 | var oncreate = { 80 | html: () => { 81 | update() 82 | } 83 | } 84 | 85 | var onupdate = { 86 | html: () => { 87 | if (state.reload.md) { 88 | state.reload.md = false 89 | update(true) 90 | } 91 | }, 92 | theme: () => { 93 | if (state.content.mermaid) { 94 | setTimeout(() => mmd.render(), 0) 95 | } 96 | } 97 | } 98 | 99 | var update = (update) => { 100 | scroll(update) 101 | 102 | if (state.content.syntax) { 103 | setTimeout(() => Prism.highlightAll(), 20) 104 | } 105 | 106 | if (state.content.mermaid) { 107 | setTimeout(() => mmd.render(), 40) 108 | } 109 | 110 | if (state.content.mathjax) { 111 | setTimeout(() => mj.render(), 60) 112 | } 113 | } 114 | 115 | var render = (md) => { 116 | state.markdown = md 117 | chrome.runtime.sendMessage({ 118 | message: 'markdown', 119 | compiler: state.compiler, 120 | markdown: frontmatter(state.markdown) 121 | }, (res) => { 122 | state.html = res.html 123 | if (state.content.emoji) { 124 | state.html = emojinator(state.html) 125 | } 126 | if (state.content.mermaid) { 127 | state.html = state.html.replace( 128 | //gi, 129 | '' 130 | ) 131 | } 132 | if (state.content.toc) { 133 | state.toc = toc.render(state.html) 134 | } 135 | state.html = anchors(state.html) 136 | m.redraw() 137 | }) 138 | } 139 | 140 | function mount () { 141 | $('pre').style.display = 'none' 142 | var md = $('pre').innerText 143 | favicon() 144 | 145 | m.mount($('body'), { 146 | oninit: () => { 147 | render(md) 148 | }, 149 | view: () => { 150 | var dom = [] 151 | 152 | if (state.html) { 153 | state._themes.custom = state.custom.color 154 | 155 | var color = 156 | state._themes[state.theme] === 'dark' || 157 | (state._themes[state.theme] === 'auto' && window.matchMedia('(prefers-color-scheme: dark)').matches) 158 | ? 'dark' : 'light' 159 | 160 | $('body').classList.remove(...Array.from($('body').classList).filter((name) => /^_theme|_color/.test(name))) 161 | dom.push(m('link#_theme', { 162 | onupdate: onupdate.theme, 163 | rel: 'stylesheet', type: 'text/css', 164 | href: state.theme !== 'custom' ? chrome.runtime.getURL(`/themes/${state.theme}.css`) : '', 165 | })) 166 | $('body').classList.add(`_theme-${state.theme}`, `_color-${color}`) 167 | 168 | if (state.content.syntax) { 169 | dom.push(m('link#_prism', { 170 | rel: 'stylesheet', type: 'text/css', 171 | href: chrome.runtime.getURL(`/vendor/${color === 'dark' ? 'prism-okaidia' : 'prism'}.min.css`), 172 | })) 173 | } 174 | 175 | var theme = 176 | (/github(-dark)?/.test(state.theme) ? 'markdown-body' : 'markdown-theme') + 177 | (state.themes.width !== 'auto' ? ` _width-${state.themes.width}` : '') 178 | 179 | if (state.raw) { 180 | if (state.content.syntax) { 181 | dom.push(m('#_markdown', {oncreate: oncreate.html, onupdate: onupdate.html, class: theme}, 182 | m.trust(`
${_escape(state.markdown)}
`) 183 | )) 184 | } 185 | else { 186 | dom.push(m('pre#_markdown', {oncreate: oncreate.html, onupdate: onupdate.html}, state.markdown)) 187 | } 188 | } 189 | else { 190 | dom.push(m('#_html', {oncreate: oncreate.html, onupdate: onupdate.html, class: theme}, 191 | m.trust(state.html) 192 | )) 193 | } 194 | 195 | if (state.content.toc) { 196 | dom.push(m('#_toc.tex2jax-ignore', m.trust(state.toc))) 197 | state.raw ? $('body').classList.remove('_toc-left') : $('body').classList.add('_toc-left') 198 | } 199 | 200 | if (state.theme === 'custom') { 201 | dom.push(m('style', {type: 'text/css'}, state.custom.theme)) 202 | } 203 | } 204 | 205 | return dom 206 | } 207 | }) 208 | } 209 | 210 | var anchors = (html) => 211 | html.replace(/()/g, (header, _, id) => 212 | header + 213 | '' + 214 | '' 215 | ) 216 | 217 | var toc = (() => { 218 | var walk = (regex, string, group, result = [], match = regex.exec(string)) => 219 | !match ? result : walk(regex, string, group, result.concat(!group ? match[1] : 220 | group.reduce((all, name, index) => (all[name] = match[index + 1], all), {}))) 221 | return { 222 | render: (html) => 223 | walk( 224 | /(.*?)<\/h[1-6]>/gs, 225 | html, 226 | ['level', 'id', 'title'] 227 | ) 228 | .reduce((toc, {id, title, level}) => toc += 229 | ''.repeat(level) 232 | , '') 233 | } 234 | })() 235 | 236 | var frontmatter = (md) => { 237 | if (/^-{3}[\s\S]+?-{3}/.test(md)) { 238 | var [, yaml] = /^-{3}([\s\S]+?)-{3}/.exec(md) 239 | var title = /title: (?:'|")*(.*)(?:'|")*/.exec(yaml) 240 | title && (document.title = title[1]) 241 | } 242 | else if (/^\+{3}[\s\S]+?\+{3}/.test(md)) { 243 | var [, toml] = /^\+{3}([\s\S]+?)\+{3}/.exec(md) 244 | var title = /title = (?:'|"|`)*(.*)(?:'|"|`)*/.exec(toml) 245 | title && (document.title = title[1]) 246 | } 247 | return md.replace(/^(?:-|\+){3}[\s\S]+?(?:-|\+){3}/, '') 248 | } 249 | 250 | var favicon = () => { 251 | var favicon = document.createElement('link') 252 | favicon.rel = 'icon' 253 | favicon.href = chrome.runtime.getURL(`/icons/${state.icon}/16x16.png`) 254 | $('head').appendChild(favicon) 255 | } 256 | 257 | var _escape = (str) => 258 | str.replace(/[&<>]/g, (tag) => ({ 259 | '&': '&', 260 | '<': '<', 261 | '>': '>' 262 | }[tag] || tag)) 263 | 264 | if (document.readyState === 'complete') { 265 | mount() 266 | } 267 | else { 268 | var timeout = setInterval(() => { 269 | if (document.readyState === 'complete') { 270 | clearInterval(timeout) 271 | mount() 272 | } 273 | }, 0) 274 | } 275 | -------------------------------------------------------------------------------- /content/mathjax.js: -------------------------------------------------------------------------------- 1 | 2 | var MathJax = { 3 | loader: { 4 | pathFilters: [ 5 | ({name}) => name.startsWith('[tex]') ? false : true // keep the name 6 | ], 7 | require: (path) => path.startsWith('[tex]') ? 8 | chrome.runtime.sendMessage({ 9 | message: 'mathjax', 10 | extension: path.replace('[tex]/', '') 11 | }) : null 12 | }, 13 | tex: { 14 | inlineMath: [ 15 | ['$', '$'], 16 | ['\\(', '\\)'], 17 | ], 18 | displayMath: [ 19 | ['$$', '$$'], 20 | ['\\[', '\\]'], 21 | ], 22 | processEscapes: true 23 | }, 24 | showMathMenu: false, 25 | showProcessingMessages: false, 26 | messageStyle: 'none', 27 | skipStartupTypeset: true, // disable initial rendering 28 | positionToHash: false, 29 | options: { 30 | ignoreHtmlClass: 'tex2jax-ignore' 31 | }, 32 | chtml: { 33 | fontURL: chrome.runtime.getURL('/vendor/mathjax/fonts') 34 | }, 35 | startup: { 36 | typeset: false 37 | } 38 | } 39 | 40 | var mj = { 41 | loaded: false, 42 | render: () => { 43 | mj.loaded = false 44 | MathJax.typesetPromise().then(() => { 45 | setTimeout(() => mj.loaded = true, 20) 46 | }) 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /content/mermaid.js: -------------------------------------------------------------------------------- 1 | 2 | var mmd = (() => { 3 | var loaded = false 4 | 5 | var walk = (regex, string, result = [], match = regex.exec(string)) => 6 | !match ? result : walk(regex, string, result.concat(match[1])) 7 | 8 | return { 9 | render: () => { 10 | if (loaded) { 11 | var definitions = walk(/
([\s\S]+?)<\/code><\/pre>/gi, state.html)
12 | 
13 |         Array.from(document.querySelectorAll('pre code.mermaid')).forEach((diagram, index) => {
14 |           diagram.removeAttribute('data-processed')
15 |           diagram.innerHTML = definitions[index]
16 |         })
17 |       }
18 |       var theme =
19 |         state._themes[state.theme] === 'dark' ||
20 |         (state._themes[state.theme] === 'auto' && window.matchMedia('(prefers-color-scheme: dark)').matches)
21 |         ? 'dark' : 'default'
22 |       mermaid.initialize({theme})
23 |       mermaid.init({theme}, 'code.mermaid')
24 |       loaded = true
25 | 
26 |       var diagrams = Array.from(document.querySelectorAll('code.mermaid'))
27 |       var timeout = setInterval(() => {
28 |         var svg = Array.from(document.querySelectorAll('pre code.mermaid svg'))
29 |         if (diagrams.length === svg.length) {
30 |           clearInterval(timeout)
31 |           svg.forEach((diagram) => {
32 |             var panzoom = Panzoom(diagram, {canvas: true})
33 |             diagram.parentElement.parentElement.addEventListener('wheel', (e) => {
34 |               if (!e.shiftKey) return
35 |               panzoom.zoomWithWheel(e)
36 |             })
37 |           })
38 |         }
39 |       }, 50)
40 |     }
41 |   }
42 | })()
43 | 


--------------------------------------------------------------------------------
/content/prism.js:
--------------------------------------------------------------------------------
1 | 
2 | Prism.plugins.autoloader.addScript = (language, done) => {
3 |   chrome.runtime.sendMessage({
4 |     message: 'prism',
5 |     language
6 |   }, done)
7 | }
8 | 


--------------------------------------------------------------------------------
/content/scroll.js:
--------------------------------------------------------------------------------
  1 | 
  2 | var scroll = (() => {
  3 |   function onload (done) {
  4 |     Promise.all([
  5 |       new Promise((resolve) => {
  6 |         var timeout = setInterval(() => {
  7 |           if (document.styleSheets.length) {
  8 |             clearInterval(timeout)
  9 |             resolve()
 10 |           }
 11 |         }, 0)
 12 |       }),
 13 |       new Promise((resolve) => {
 14 |         var images = Array.from(document.querySelectorAll('img'))
 15 |         if (!images.length) {
 16 |           resolve()
 17 |         }
 18 |         else {
 19 |           Promise.race([
 20 |             new Promise((resolve) => {
 21 |               var loaded = 0
 22 |               images.forEach((img) => {
 23 |                 img.addEventListener('load', () => {
 24 |                   if (++loaded === images.length) {
 25 |                     resolve()
 26 |                   }
 27 |                 }, {once: true})
 28 |               })
 29 |             }),
 30 |             new Promise((resolve) => setTimeout(resolve, 500))
 31 |           ]).then(resolve)
 32 |         }
 33 |       }),
 34 |       new Promise((resolve) => {
 35 |         var code = Array.from(document.querySelectorAll('code[class^=language-]'))
 36 |         if (!state.content.syntax || !code.length) {
 37 |           resolve()
 38 |         }
 39 |         else {
 40 |           setTimeout(() => resolve(), 40)
 41 |         }
 42 |       }),
 43 |       new Promise((resolve) => {
 44 |         var diagrams = Array.from(document.querySelectorAll('code.mermaid'))
 45 |         if (!state.content.mermaid || !diagrams.length) {
 46 |           resolve()
 47 |         }
 48 |         else {
 49 |           var timeout = setInterval(() => {
 50 |             var svg = Array.from(document.querySelectorAll('code.mermaid svg'))
 51 |             if (diagrams.length === svg.length) {
 52 |               clearInterval(timeout)
 53 |               resolve()
 54 |             }
 55 |           }, 50)
 56 |         }
 57 |       }),
 58 |       new Promise((resolve) => {
 59 |         if (!state.content.mathjax) {
 60 |           resolve()
 61 |         }
 62 |         else {
 63 |           var timeout = setInterval(() => {
 64 |             if (mj.loaded) {
 65 |               clearInterval(timeout)
 66 |               resolve()
 67 |             }
 68 |           }, 50)
 69 |         }
 70 |       })
 71 |     ]).then(done)
 72 |   }
 73 |   function listen (container, done) {
 74 |     var listener = /html|body/i.test(container.nodeName) ? window : container
 75 |     var timeout = null
 76 |     listener.addEventListener('scroll', () => {
 77 |       clearTimeout(timeout)
 78 |       timeout = setTimeout(done, 100)
 79 |     })
 80 |   }
 81 |   function get (container, prefix, offset) {
 82 |     var key = prefix + location.origin + location.pathname
 83 |     if (offset) {
 84 |       container.scrollTop = offset
 85 |       return
 86 |     }
 87 |     try {
 88 |       container.scrollTop = parseInt(localStorage.getItem(key))
 89 |     }
 90 |     catch (err) {
 91 |       chrome.storage.local.get(key, (res) => {
 92 |         container.scrollTop = parseInt(res[key])
 93 |       })
 94 |     }
 95 |   }
 96 |   function set (container, prefix) {
 97 |     var key = prefix + location.origin + location.pathname
 98 |     listen(container, () => {
 99 |       try {
100 |         localStorage.setItem(key, container.scrollTop)
101 |       }
102 |       catch (err) {
103 |         chrome.storage.local.set({[key]: container.scrollTop})
104 |       }
105 |     })
106 |   }
107 |   var listening = false
108 |   return (update) => {
109 |     if ($('#_toc') && state.raw) $('#_toc').style.visibility = 'hidden'
110 |     onload(() => {
111 |       var container = ((html = $('html')) => (
112 |         html.scrollTop = 1,
113 |         html.scrollTop ? (html.scrollTop = 0, html) : $('body')
114 |       ))()
115 | 
116 |       if (!update && location.hash && document.getElementById(location.hash.slice(1))) {
117 |         get(container, 'md-', document.getElementById(location.hash.slice(1)).offsetTop)
118 |       }
119 |       else {
120 |         get(container, 'md-')
121 |       }
122 | 
123 |       if (state.content.toc) {
124 |         setTimeout(() => get($('#_toc'), 'md-toc-'), 10)
125 |       }
126 | 
127 |       if (!listening) {
128 |         listening = true
129 |         set(container, 'md-')
130 |         if (state.content.toc) {
131 |           setTimeout(() => set($('#_toc'), 'md-toc-'), 10)
132 |         }
133 |       }
134 | 
135 |       if ($('#_html')) $('#_html').style.visibility = 'visible'
136 |       if ($('#_toc') && !state.raw) $('#_toc').style.visibility = 'visible'
137 |     })
138 |   }
139 | })()
140 | 


--------------------------------------------------------------------------------
/content/themes.css:
--------------------------------------------------------------------------------
  1 | 
  2 | /*---------------------------------------------------------------------------*/
  3 | /*github theme*/
  4 | 
  5 | .markdown-body {
  6 |   overflow: auto;
  7 | 
  8 |   min-width: 830px;
  9 |   max-width: 830px;
 10 | 
 11 |   padding: 32px;
 12 |   margin: 20px auto !important;
 13 | }
 14 | .markdown-body #_html>*:first-child {
 15 |   margin-top: 0 !important;
 16 | }
 17 | .markdown-body #_html>*:last-child {
 18 |   margin-bottom: 0 !important;
 19 | }
 20 | .markdown-body img {
 21 |   background-color: transparent;
 22 | }
 23 | 
 24 | /*---------------------------------------------------------------------------*/
 25 | /*toc*/
 26 | 
 27 | /*github*/
 28 | ._theme-github #_toc { /*.markdown-body {*/
 29 |   background-color: var(--color-canvas-default);
 30 |   font-family: -apple-system,BlinkMacSystemFont,"Segoe UI",Helvetica,Arial,sans-serif,"Apple Color Emoji","Segoe UI Emoji";
 31 |   font-size: 16px;
 32 |   line-height: 1.5;
 33 |   word-wrap: break-word;
 34 | }
 35 | ._theme-github #_toc a { /*.markdown-body a {*/
 36 |   background-color: transparent;
 37 |   color: #0969da;
 38 |   text-decoration: none;
 39 | }
 40 | ._theme-github #_toc a:hover {
 41 |   text-decoration: underline;
 42 | }
 43 | 
 44 | /*github-dark*/
 45 | ._theme-github-dark #_toc { /*.markdown-body {*/
 46 |   background-color: #0d1117;
 47 |   font-family: -apple-system,BlinkMacSystemFont,"Segoe UI",Helvetica,Arial,sans-serif,"Apple Color Emoji","Segoe UI Emoji";
 48 |   font-size: 16px;
 49 |   line-height: 1.5;
 50 |   word-wrap: break-word;
 51 | }
 52 | ._theme-github-dark #_toc a { /*.markdown-body a {*/
 53 |   background-color: transparent;
 54 |   color: #58a6ff;
 55 |   text-decoration: none;
 56 | }
 57 | ._theme-github-dark #_toc a:hover {
 58 |   text-decoration: underline;
 59 | }
 60 | 
 61 | /*---------------------------------------------------------------------------*/
 62 | /*anchor link*/
 63 | 
 64 | /*fix hover*/
 65 | .markdown-theme .anchor {
 66 |   border-bottom: 0;
 67 | }
 68 | 
 69 | /*fix position*/
 70 | ._theme-almond .octicon,
 71 | ._theme-awsm .octicon,
 72 | ._theme-axist .octicon,
 73 | ._theme-kacit .octicon,
 74 | ._theme-mini .octicon,
 75 | ._theme-new .octicon,
 76 | ._theme-sakura .octicon,
 77 | ._theme-sakura-vader .octicon,
 78 | ._theme-semantic .octicon,
 79 | ._theme-simple .octicon,
 80 | ._theme-stylize .octicon,
 81 | ._theme-superstylin .octicon {
 82 |   line-height: 1px;
 83 | }
 84 | ._theme-splendor .anchor,
 85 | ._theme-splendor .octicon {
 86 |   padding: 0 !important;
 87 |   margin: 0 0 0 -10px !important;
 88 | }
 89 | ._theme-splendor h3 .octicon,
 90 | ._theme-splendor h4 .octicon,
 91 | ._theme-splendor h5 .octicon,
 92 | ._theme-splendor h6 .octicon
 93 | {
 94 |   position: relative;
 95 |   top: 4px;
 96 | }
 97 | ._theme-no-class h1 .octicon,
 98 | ._theme-no-class h2 .octicon,
 99 | ._theme-no-class h3 .octicon {
100 |   position: relative;
101 |   top: -5px;
102 | }
103 | ._theme-pico h1 .octicon,
104 | ._theme-pico h2 .octicon {
105 |   position: relative;
106 |   top: 5px;
107 | }
108 | ._theme-superstylin h1 .octicon {
109 |   position: relative;
110 |   top: -5px;
111 | }
112 | ._theme-writ .octicon {
113 |   line-height: 0;
114 | }
115 | ._theme-writ h2 .octicon {
116 |   position: relative;
117 |   top: 4px;
118 | }
119 | ._theme-writ h3 .octicon {
120 |   position: relative;
121 |   top: 10px;
122 | }
123 | 
124 | /*---------------------------------------------------------------------------*/
125 | /*task list checkboxes*/
126 | 
127 | ._theme-awsm input[type=checkbox],
128 | ._theme-kacit input[type=checkbox],
129 | ._theme-no-class input[type=checkbox],
130 | ._theme-semantic input[type=checkbox],
131 | ._theme-tacit input[type=checkbox],
132 | ._theme-vanilla input[type=checkbox] {
133 |   display: inline-block !important;
134 |   box-sizing: border-box !important;
135 |   padding: 0 !important;
136 |   margin: 0 !important;
137 | }
138 | ._theme-bullframe input[type=checkbox],
139 | ._theme-superstylin input[type=checkbox] {
140 |   position: relative;
141 |   top: 3px;
142 | }
143 | ._theme-mini input[type=checkbox] {
144 |   position: relative;
145 |   top: 4px;
146 | }
147 | ._theme-no-class input[type=checkbox] {
148 |   width: auto !important;
149 | }
150 | ._theme-vanilla input[type=checkbox] {
151 |   appearance: auto !important;
152 | }
153 | 
154 | /*---------------------------------------------------------------------------*/
155 | /*fix auto dark*/
156 | 
157 | /*set default background color*/
158 | ._theme-air,
159 | ._theme-marx,
160 | ._theme-modest,
161 | ._theme-splendor,
162 | ._theme-stylize,
163 | ._theme-writ {
164 |   background-color: #fff;
165 | }
166 | 
167 | /*set default text and link color*/
168 | ._theme-semantic { color: #000; }
169 | ._theme-semantic a { color: #0000ff; }
170 | ._theme-semantic a:visited { color: #800080; }
171 | ._theme-semantic a:active { color: #ff0000; }
172 | 
173 | /*---------------------------------------------------------------------------*/
174 | /*mermaid*/
175 | ._theme-mini code { line-height: normal; }
176 | ._theme-tacit * { max-width: none; }
177 | ._theme-kacit * { max-width: none; }
178 | 
179 | /*---------------------------------------------------------------------------*/
180 | /*panzoom*/
181 | 
182 | ._theme-mini pre > code.mermaid { padding: 0; }
183 | ._theme-superstylin pre > code.mermaid { background: none; padding: 0; margin: 0; border-radius: 0; }
184 | ._theme-superstylin pre:has(> code.mermaid) { background: #f6f6f6; padding: 1rem; margin-bottom: 1.563rem; border-radius: 10px; }
185 | ._theme-water pre > code.mermaid { padding: 0; background: none; border-radius: 0; }
186 | ._theme-water pre:has(> code.mermaid) { padding: 10px; background: #efefef; border-radius: 6px; }
187 | ._theme-water-dark pre > code.mermaid { padding: 0; background: none; border-radius: 0; }
188 | ._theme-water-dark pre:has(> code.mermaid) { padding: 10px; background: #161f27; border-radius: 6px; }
189 | @media (prefers-color-scheme: dark) {
190 |   ._theme-superstylin pre:has(> code.mermaid) { background: #595859; }
191 | }
192 | 
193 | /*---------------------------------------------------------------------------*/
194 | /*theme fixes*/
195 | 
196 | @media (prefers-color-scheme: dark) {
197 |   /*no-class - fix code block*/
198 |   ._theme-no-class pre {
199 |     background: #303030;
200 |   }
201 |   /*no-class - fix table*/
202 |   ._theme-no-class tbody tr:nth-of-type(odd) {
203 |     background-color: #303030;
204 |   }
205 | }
206 | 
207 | /*github*/
208 | ._theme-github {
209 |   background-color: #fff;
210 | }
211 | ._theme-github > pre {
212 |   color: #000;
213 | }
214 | ._theme-github .markdown-body {
215 |   border: 1px solid #e1e4e8;
216 | }
217 | 
218 | /*github-dark*/
219 | ._theme-github-dark {
220 |   background-color: #0d1117;
221 | }
222 | ._theme-github-dark > pre {
223 |   color: #fff;
224 | }
225 | ._theme-github-dark a {
226 |   color: #4493f8 !important;
227 | }
228 | ._theme-github-dark .markdown-body {
229 |   border: 1px solid #30363d;
230 | }
231 | 
232 | /*markdown-air - fix images*/
233 | ._theme-air img {
234 |   border-radius: 0;
235 |   width: auto;
236 |   height: auto;
237 | }
238 | 
239 | /*axist - fix lists*/
240 | ._theme-axist ul li p {
241 |   display: inline-block;
242 | }
243 | /*axist - fix fenced code blocks*/
244 | ._theme-axist pre::after {
245 |   content: none;
246 | }
247 | 
248 | /*kacit, tacit - fix mermaid diagrams size*/
249 | ._theme-kacit pre code,
250 | ._theme-tacit pre code {
251 |   display: block;
252 | }
253 | 
254 | /*mini - fix table*/
255 | ._theme-mini table {
256 |   overflow-x: clip !important;
257 |   max-height: none !important;
258 | }
259 | /*mini - fix special symbols*/
260 | ._theme-mini blockquote::before {
261 |   content: "“";
262 | }
263 | ._theme-mini [type=checkbox]:checked:before {
264 |   content: "✓";
265 |   top: -3px;
266 | }
267 | 
268 | /*simple - fix body width*/
269 | ._theme-simple {
270 |   display: block;
271 | }
272 | 
273 | /*style-sans, style-serif - fix inline code*/
274 | ._theme-style-sans *:not(pre) > code::before,
275 | ._theme-style-sans *:not(pre) > code::after,
276 | ._theme-style-serif *:not(pre) > code::before,
277 | ._theme-style-serif *:not(pre) > code::after {
278 |   content: '';
279 |   padding-left: 5px;
280 |   padding-right: 5px;
281 | }
282 | 
283 | /*superstylin - fix lists*/
284 | ._theme-superstylin ul ul,
285 | ._theme-superstylin ol ol {
286 |   padding-left: 40px;
287 | }
288 | ._theme-superstylin ul li p,
289 | ._theme-superstylin ol li p {
290 |   display: inline-block;
291 |   margin-bottom: 0;
292 | }
293 | 
294 | /*vanilla - fix pre margin*/
295 | ._theme-vanilla pre {
296 |   margin-bottom: 10px;
297 | }
298 | 


--------------------------------------------------------------------------------
/firefox.md:
--------------------------------------------------------------------------------
  1 | 
  2 | # Markdown Viewer on Firefox
  3 | 
  4 | ## Table of Contents
  5 | 
  6 | - **[Access to local file:/// URLs on Linux](#access-to-local-file-urls-on-linux)**
  7 | - **[Autoreload on localhost](#autoreload-on-localhost)**
  8 | - **[Remote origins with strict CSP](#remote-origins-with-strict-csp)**
  9 | 
 10 | ## Compatibility Matrix
 11 | 
 12 | | Origin       | Type   | Headers                                              | Render | Autoreload
 13 | | :-           | :-:    | :-                                                   | :-     | :-
 14 | | `file:///`   | local  | requires mime type fix on Linux                      | ✔      | ✖
 15 | | `http(s)://` | local  | requires `content-type: text/plain`                  | ✔      | ✔
 16 | | `http(s)://` | remote | requires `content-type: text/plain` + non strict CSP | ✔      | ✖
 17 | | `http(s)://` | remote | strict CSP (Content Security Policy)                 | ✖      | ✖
 18 | 
 19 | ---
 20 | 
 21 | # Access to local file:/// URLs on Linux
 22 | 
 23 | Unlike Chromium based browsers, files served with `text/markdown` content type on Firefox will prompt you to download or open the file with an external app. For that reason markdown files have to be served with the `text/plain` content type instead.
 24 | 
 25 | The following are some methods to enable local `file:///` access on Linux:
 26 | 
 27 | ## Method 1
 28 | 
 29 | Create `~/.mime.types` file with the following content:
 30 | 
 31 | ```
 32 | type=text/plain exts=md,mkd,mkdn,mdwn,mdown,markdown, desc="Markdown document"
 33 | ```
 34 | 
 35 | Restart Firefox
 36 | 
 37 | ## Method 2
 38 | 
 39 | In case Firefox was installed as a snap _Method 1_ won't work.
 40 | 
 41 | Create `/home/me/snap/firefox/common/mime.types` file with the following content (replace `me` with your user):
 42 | 
 43 | ```
 44 | type=text/plain exts=md,mkd,mkdn,mdwn,mdown,markdown, desc="Markdown document"
 45 | ```
 46 | 
 47 | Go to `about:config` in Firefox and search for `helpers.private_mime_types_file`
 48 | 
 49 | Replace the default path there with `/home/me/snap/firefox/common/mime.types` (replace `me` with your user)
 50 | 
 51 | Restart Firefox
 52 | 
 53 | ## Method 3
 54 | 
 55 | Create `~/.local/share/mime/packages/text-markdown.xml` file with the following content:
 56 | 
 57 | ```xml
 58 | 
 59 | 
 60 |   
 61 |     
 62 |     
 63 |     
 64 |     
 65 |     
 66 |     
 67 |   
 68 | 
 69 | ```
 70 | 
 71 | Execute:
 72 | 
 73 | ```bash
 74 | update-mime-database ~/.local/share/mime
 75 | ```
 76 | 
 77 | ---
 78 | 
 79 | # Autoreload on localhost
 80 | 
 81 | The `autoreload` feature is available only for content served on `localhost` and it won't work on `file:///` URLs due to CSP (Content Security Policy) limitations.
 82 | 
 83 | Any file server can be used locally as long as it serves the markdown files with `text/plain` content type.
 84 | 
 85 | Here is an example file server using Node.js (replace `me` with your user):
 86 | 
 87 | ```js
 88 | var express = require('express')
 89 | var serveIndex = require('serve-index')
 90 | var serveStatic = require('serve-static')
 91 | 
 92 | express()
 93 |   // serve markdown files with content-type: text/plain in Firefox
 94 |   .use((req, res, next) => {
 95 |     // by default serve-static serves markdown content with content-type: text/markdown
 96 |     if (
 97 |       /Firefox/.test(req.headers['user-agent']) &&
 98 |       // the default Path Matching RegExp in Markdown Viewer
 99 |       /\.(?:markdown|mdown|mkdn|md|mkd|mdwn|mdtxt|mdtext|text)(?:#.*|\?.*)?$/.test(req.url)
100 |     ) {
101 |       res.setHeader('content-type', 'text/plain; charset=utf-8')
102 |     }
103 |     next()
104 |   })
105 |   .use(serveStatic('/home/me'))
106 |   .use('/', serveIndex('/home/me', {'icons': true, view: 'details'}))
107 |   .listen(8000)
108 | ```
109 | 
110 | Go to the Advanced Options page for the extension and add the `http://localhost` origin (note that port is omitted).
111 | 
112 | Run the above JavaScript file using Node.js and navigate to `http://localhost:8000` in Firefox.
113 | 
114 | You can use any other host name configured in your `hosts` file that resolves to localhost:
115 | 
116 | ```hosts
117 | 127.0.0.1    ssd
118 | ```
119 | 
120 | Then you only need to add that origin `http://ssd` in the Advanced Options page as well.
121 | 
122 | The above script can be run on system startup using SystemD or any other service manager.
123 | 
124 | ---
125 | 
126 | # Remote origins with strict CSP
127 | 
128 | Remote origins that serve markdown files with `text/plain` content type and do not enforce strict CSP (Content Security Policy) can be enabled using the Advanced Options page.
129 | 
130 | For example, content hosted on `gitlab.com` and `bitbucket.org` can be enabled and subsequently it will be rendered:
131 | 
132 | - https://gitlab.com/simovelichkov/markdown-syntax/-/raw/main/README.md
133 | - https://bitbucket.org/simovelichkov/markdown-syntax/raw/main/README.md
134 | 
135 | Remote origins with strict CSP however, such as `content-security-policy: default-src 'none'; style-src 'unsafe-inline'; sandbox` will be blocked by Firefox.
136 | 
137 | For example, content hosted on GitHub's `raw.githubusercontent.com` cannot be rendered even if that origin was enabled in the Advanced Options page:
138 | 
139 | - https://raw.githubusercontent.com/simov/markdown-syntax/main/README.md
140 | 
141 | ---
142 | 


--------------------------------------------------------------------------------
/icons/dark/128x128.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/simov/markdown-viewer/ccf738f6af3f903fe8155a01e91084b27252511f/icons/dark/128x128.png


--------------------------------------------------------------------------------
/icons/dark/16x16.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/simov/markdown-viewer/ccf738f6af3f903fe8155a01e91084b27252511f/icons/dark/16x16.png


--------------------------------------------------------------------------------
/icons/dark/19x19.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/simov/markdown-viewer/ccf738f6af3f903fe8155a01e91084b27252511f/icons/dark/19x19.png


--------------------------------------------------------------------------------
/icons/dark/38x38.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/simov/markdown-viewer/ccf738f6af3f903fe8155a01e91084b27252511f/icons/dark/38x38.png


--------------------------------------------------------------------------------
/icons/dark/48x48.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/simov/markdown-viewer/ccf738f6af3f903fe8155a01e91084b27252511f/icons/dark/48x48.png


--------------------------------------------------------------------------------
/icons/default/128x128.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/simov/markdown-viewer/ccf738f6af3f903fe8155a01e91084b27252511f/icons/default/128x128.png


--------------------------------------------------------------------------------
/icons/default/16x16.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/simov/markdown-viewer/ccf738f6af3f903fe8155a01e91084b27252511f/icons/default/16x16.png


--------------------------------------------------------------------------------
/icons/default/19x19.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/simov/markdown-viewer/ccf738f6af3f903fe8155a01e91084b27252511f/icons/default/19x19.png


--------------------------------------------------------------------------------
/icons/default/38x38.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/simov/markdown-viewer/ccf738f6af3f903fe8155a01e91084b27252511f/icons/default/38x38.png


--------------------------------------------------------------------------------
/icons/default/48x48.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/simov/markdown-viewer/ccf738f6af3f903fe8155a01e91084b27252511f/icons/default/48x48.png


--------------------------------------------------------------------------------
/icons/light/128x128.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/simov/markdown-viewer/ccf738f6af3f903fe8155a01e91084b27252511f/icons/light/128x128.png


--------------------------------------------------------------------------------
/icons/light/16x16.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/simov/markdown-viewer/ccf738f6af3f903fe8155a01e91084b27252511f/icons/light/16x16.png


--------------------------------------------------------------------------------
/icons/light/19x19.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/simov/markdown-viewer/ccf738f6af3f903fe8155a01e91084b27252511f/icons/light/19x19.png


--------------------------------------------------------------------------------
/icons/light/38x38.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/simov/markdown-viewer/ccf738f6af3f903fe8155a01e91084b27252511f/icons/light/38x38.png


--------------------------------------------------------------------------------
/icons/light/48x48.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/simov/markdown-viewer/ccf738f6af3f903fe8155a01e91084b27252511f/icons/light/48x48.png


--------------------------------------------------------------------------------
/manifest.chrome.json:
--------------------------------------------------------------------------------
 1 | {
 2 |   "manifest_version": 3,
 3 |   "name"            : "Markdown Viewer",
 4 |   "version"         : "5.3",
 5 |   "description"     : "Dark Mode • Themes • Autoreload • Mermaid Diagrams • MathJax • ToC • Syntax Highlighting",
 6 | 
 7 |   "homepage_url": "https://chromewebstore.google.com/detail/markdown-viewer/ckkdlimhmcjmikdlpkmbgfkaikojcbjk",
 8 | 
 9 |   "icons": {
10 |     "16" : "/icons/default/16x16.png",
11 |     "19" : "/icons/default/19x19.png",
12 |     "38" : "/icons/default/38x38.png",
13 |     "48" : "/icons/default/48x48.png",
14 |     "128": "/icons/default/128x128.png"
15 |   },
16 | 
17 |   "action": {
18 |     "default_icon": {
19 |       "16" : "/icons/default/16x16.png",
20 |       "19" : "/icons/default/19x19.png",
21 |       "38" : "/icons/default/38x38.png",
22 |       "48" : "/icons/default/48x48.png",
23 |       "128" : "/icons/default/128x128.png"
24 |     },
25 |     "default_title": "Markdown Viewer",
26 |     "default_popup": "/popup/index.html"
27 |   },
28 | 
29 |   "background" : {
30 |     "service_worker": "/background/index.js"
31 |   },
32 | 
33 |   "options_page": "/options/index.html",
34 | 
35 |   "web_accessible_resources": [
36 |     {
37 |       "matches": [
38 |         ""
39 |       ],
40 |       "resources": [
41 |         "/content/anchor.svg",
42 |         "/icons/default/16x16.png",
43 |         "/icons/dark/16x16.png",
44 |         "/icons/light/16x16.png",
45 |         "/themes/*",
46 |         "/vendor/mathjax/fonts/*",
47 |         "/vendor/prism.min.css",
48 |         "/vendor/prism-okaidia.min.css"
49 |       ]
50 |     }
51 |   ],
52 | 
53 |   "permissions": [
54 |     "storage",
55 |     "scripting"
56 |   ],
57 | 
58 |   "host_permissions": [
59 |     "file:///*"
60 |   ],
61 | 
62 |   "optional_host_permissions": [
63 |     "*://*/"
64 |   ],
65 | 
66 |   "optional_permissions": [
67 |     "webRequest"
68 |   ]
69 | }
70 | 


--------------------------------------------------------------------------------
/manifest.firefox.json:
--------------------------------------------------------------------------------
 1 | {
 2 |   "manifest_version": 3,
 3 |   "name"            : "Markdown Viewer",
 4 |   "version"         : "5.3",
 5 |   "description"     : "Dark Mode • Themes • Autoreload • Mermaid Diagrams • MathJax • ToC • Syntax Highlighting",
 6 | 
 7 |   "homepage_url": "https://github.com/simov/markdown-viewer",
 8 | 
 9 |   "icons": {
10 |     "16" : "/icons/default/16x16.png",
11 |     "19" : "/icons/default/19x19.png",
12 |     "38" : "/icons/default/38x38.png",
13 |     "48" : "/icons/default/48x48.png",
14 |     "128": "/icons/default/128x128.png"
15 |   },
16 | 
17 |   "action": {
18 |     "default_icon": {
19 |       "16" : "/icons/default/16x16.png",
20 |       "19" : "/icons/default/19x19.png",
21 |       "38" : "/icons/default/38x38.png",
22 |       "48" : "/icons/default/48x48.png",
23 |       "128" : "/icons/default/128x128.png"
24 |     },
25 |     "default_title": "Markdown Viewer",
26 |     "default_popup": "/popup/index.html"
27 |   },
28 | 
29 |   "background" : {
30 |     "scripts": [
31 |       "/vendor/markdown-it.min.js",
32 |       "/vendor/marked.min.js",
33 |       "/vendor/remark.min.js",
34 | 
35 |       "/background/compilers/markdown-it.js",
36 |       "/background/compilers/marked.js",
37 |       "/background/compilers/remark.js",
38 | 
39 |       "/background/storage.js",
40 |       "/background/webrequest.js",
41 |       "/background/detect.js",
42 |       "/background/inject.js",
43 |       "/background/messages.js",
44 |       "/background/mathjax.js",
45 |       "/background/xhr.js",
46 |       "/background/icon.js",
47 | 
48 |       "/background/index.js"
49 |     ]
50 |   },
51 | 
52 |   "options_ui": {
53 |     "page": "/options/index.html",
54 |     "browser_style": false
55 |   },
56 | 
57 |   "web_accessible_resources": [
58 |     {
59 |       "matches": [
60 |         ""
61 |       ],
62 |       "resources": [
63 |         "/content/anchor.svg",
64 |         "/icons/default/16x16.png",
65 |         "/icons/dark/16x16.png",
66 |         "/icons/light/16x16.png",
67 |         "/themes/*",
68 |         "/vendor/mathjax/fonts/*",
69 |         "/vendor/prism.min.css",
70 |         "/vendor/prism-okaidia.min.css"
71 |       ]
72 |     }
73 |   ],
74 | 
75 |   "permissions": [
76 |     "storage",
77 |     "scripting"
78 |   ],
79 | 
80 |   "host_permissions": [
81 |     "file:///*"
82 |   ],
83 | 
84 |   "optional_permissions": [
85 |     "webRequest",
86 |     "*://*/"
87 |   ],
88 | 
89 |   "browser_specific_settings": {
90 |     "gecko": {
91 |       "id": "markdown-viewer@outofindex.com",
92 |       "strict_min_version": "110.0"
93 |     }
94 |   }
95 | }
96 | 


--------------------------------------------------------------------------------
/options/custom.js:
--------------------------------------------------------------------------------
  1 | 
  2 | var Custom = () => {
  3 |   var defaults = {
  4 |     theme: '',
  5 |     color: 'auto',
  6 |     _colors: ['auto', 'light', 'dark'],
  7 |     error: '',
  8 |   }
  9 | 
 10 |   var state = Object.assign({}, defaults)
 11 | 
 12 |   chrome.runtime.sendMessage({message: 'custom.get'}, (res) => {
 13 |     Object.assign(state, res)
 14 |     m.redraw()
 15 |   })
 16 | 
 17 |   var events = {
 18 |     file: (e) => {
 19 |       document.querySelector('input[type=file]').click()
 20 |     },
 21 |     theme: (e) => {
 22 |       var file = e.target.files[0]
 23 |       if (file) {
 24 |         var minified
 25 |         var reader = new FileReader()
 26 |         reader.readAsText(file, 'UTF-8')
 27 |         reader.onload = (e) => {
 28 |           minified = csso.minify(e.target.result).css
 29 |           chrome.runtime.sendMessage({
 30 |             message: 'custom.set',
 31 |             custom: {
 32 |               theme: minified,
 33 |               color: state.color,
 34 |             },
 35 |           }, (res) => {
 36 |             if (res?.error) {
 37 |               state.error = res.error
 38 |             }
 39 |             else {
 40 |               state.theme = minified
 41 |               state.error = ''
 42 |             }
 43 |             m.redraw()
 44 |           })
 45 |         }
 46 |       }
 47 |     },
 48 |     remove: (e) => {
 49 |       state.theme = ''
 50 |       state.error = ''
 51 |       chrome.runtime.sendMessage({
 52 |         message: 'custom.set',
 53 |         custom: {
 54 |           theme: state.theme,
 55 |           color: state.color,
 56 |         },
 57 |       })
 58 |       m.redraw()
 59 |     },
 60 |     color: (e) => {
 61 |       state.color = state._colors[e.target.selectedIndex]
 62 |       chrome.runtime.sendMessage({
 63 |         message: 'custom.set',
 64 |         custom: {
 65 |           theme: state.theme,
 66 |           color: state.color,
 67 |         },
 68 |       })
 69 |     }
 70 |   }
 71 | 
 72 |   var oncreate = {
 73 |     ripple: (vnode) => {
 74 |       mdc.ripple.MDCRipple.attachTo(vnode.dom)
 75 |     }
 76 |   }
 77 | 
 78 |   var onupdate = {}
 79 | 
 80 |   var render = () => [
 81 |     m('.bs-callout m-custom',
 82 |       state.error &&
 83 |       m('.row',
 84 |         m('.col-12',
 85 |           m('span.m-label.m-error', state.error)
 86 |         )
 87 |       ),
 88 |       m('.row',
 89 |         m('.col-xxl-6.col-xl-6.col-lg-6.col-md-6.col-sm-12',
 90 |           m('span.m-label',
 91 |             'Custom Theme'
 92 |           )
 93 |         ),
 94 |         m('.col-xxl-6.col-xl-6.col-lg-6.col-md-6.col-sm-12',
 95 |           m('input', {
 96 |             type: 'file',
 97 |             accept: '.css',
 98 |             onchange: events.theme,
 99 |             oncancel: events.theme,
100 |           }),
101 |           m('button.mdc-button mdc-button--raised m-button', {
102 |             oncreate: oncreate.ripple,
103 |             onclick: events.file
104 |             },
105 |             !state.theme ? 'Add' : 'Update'
106 |           ),
107 |           state.theme &&
108 |           m('button.mdc-button mdc-button--raised m-button', {
109 |             oncreate: oncreate.ripple,
110 |             onclick: events.remove
111 |             },
112 |             'Remove'
113 |           ),
114 |         ),
115 |       ),
116 |       state.theme &&
117 |       m('.row',
118 |         m('.col-xxl-6.col-xl-6.col-lg-6.col-md-6.col-sm-12',
119 |           m('span.m-label',
120 |             'Color Scheme'
121 |           )
122 |         ),
123 |         m('.col-xxl-6.col-xl-6.col-lg-6.col-md-6.col-sm-12',
124 |           m('select.mdc-elevation--z2 m-select', {
125 |             onchange: events.color
126 |             },
127 |             state._colors.map((color) =>
128 |               m('option', {
129 |                 selected: state.color === color,
130 |               }, color)
131 |             )
132 |           )
133 |         ),
134 |       ),
135 |     )
136 |   ]
137 | 
138 |   return {state, render}
139 | }
140 | 


--------------------------------------------------------------------------------
/options/index.css:
--------------------------------------------------------------------------------
  1 | 
  2 | /*---------------------------------------------------------------------------*/
  3 | /*global*/
  4 | 
  5 | body,
  6 | body label,
  7 | body input {
  8 |   font-family: 'Monospace', monospace !important;
  9 | }
 10 | body button {
 11 |   font-family: sans-serif !important;
 12 | }
 13 | 
 14 | /*content*/
 15 | main {
 16 |   padding-top: 120px;
 17 | }
 18 | 
 19 | /*misc*/
 20 | .hidden {
 21 |   display: none;
 22 | }
 23 | .overflow {
 24 |   white-space: nowrap;
 25 |   overflow: hidden;
 26 |   min-height: 52px;
 27 | }
 28 | 
 29 | .col-sm-12.m-overflow {
 30 |   white-space: nowrap;
 31 |   overflow: hidden;
 32 | }
 33 | 
 34 | .mdc-elevation--z2 {
 35 |   box-shadow:
 36 |     0 3px 1px -2px rgba(0,0,0,.2),
 37 |     0 2px 2px 0 rgba(0,0,0,.14),
 38 |     0 1px 5px 0 rgba(0,0,0,.12);
 39 | }
 40 | 
 41 | /*---------------------------------------------------------------------------*/
 42 | /*color modes*/
 43 | 
 44 | body.dark {
 45 |   filter: invert(100%) hue-rotate(180deg);
 46 |   background-color: rgb(25, 25, 25);
 47 | }
 48 | body.dark .navbar {
 49 |   filter: invert(90%) hue-rotate(180deg);
 50 |   background-color: transparent;
 51 | }
 52 | body.dark .m-select {
 53 |   background-color: #c5c5c5;
 54 | }
 55 | body.dark .m-button {
 56 |   background-color: #c5c5c5 !important;
 57 | }
 58 | 
 59 | @media (prefers-color-scheme: dark) {
 60 |   body.auto {
 61 |     filter: invert(100%) hue-rotate(180deg);
 62 |     background-color: rgb(25, 25, 25);
 63 |   }
 64 |   body.auto .navbar {
 65 |     filter: invert(90%) hue-rotate(180deg);
 66 |     background-color: transparent;
 67 |   }
 68 |   body.auto .m-select {
 69 |     background-color: #c5c5c5;
 70 |   }
 71 |   body.auto .m-button {
 72 |     background-color: #c5c5c5 !important;
 73 |   }
 74 | }
 75 | 
 76 | /*---------------------------------------------------------------------------*/
 77 | /*sticky footer*/
 78 | 
 79 | html, body {
 80 |   height: 100%;
 81 |   padding: 0;
 82 |   margin: 0;
 83 | }
 84 | #wrapper {
 85 |   min-height: 100%;
 86 |   height: auto !important;
 87 |   height: 100%;
 88 |   margin: 0 auto -40px;
 89 | }
 90 | footer, #footer-push {
 91 |   height: 40px;
 92 | }
 93 | 
 94 | /*---------------------------------------------------------------------------*/
 95 | /*header menu*/
 96 | 
 97 | .nav a,
 98 | .nav a:hover,
 99 | .nav a:visited,
100 | .nav-underline .nav-link.active {
101 |   color: #fff;
102 | }
103 | 
104 | /*---------------------------------------------------------------------------*/
105 | /*scrollbar*/
106 | 
107 | ::-webkit-scrollbar,
108 | ::-webkit-scrollbar-corner {
109 |   height: 10px;
110 |   width: 10px;
111 | }
112 | ::-webkit-scrollbar-track-piece {
113 |   background: #ececec;
114 |   border-radius: 2px;
115 | }
116 | ::-webkit-scrollbar-thumb {
117 |   background: #afbdc3;
118 |   border-radius: 6px;
119 | }
120 | ::-webkit-scrollbar-thumb:hover {
121 |   background-color: #607d8b;
122 | }
123 | 
124 | /*---------------------------------------------------------------------------*/
125 | /*bootstrap callout*/
126 | 
127 | .bs-callout {
128 |   border: 1px solid #eee;
129 |   border-left-width: 5px;
130 |   border-left-color: #607d8b;
131 |   border-radius: 3px;
132 |   background: #fcfcfc;
133 |   padding: 20px 20px 10px 20px;
134 |   margin: 0 0 30px 0;
135 | }
136 | 
137 | /*---------------------------------------------------------------------------*/
138 | /*mdc controls*/
139 | 
140 | /*button*/
141 | .m-button {
142 |   background-color: #ececec !important;
143 |   color: #000 !important;
144 | }
145 | .m-button:before {
146 |   background-color: rgba(96, 125, 139, 0.56) !important;
147 | }
148 | .m-button:after {
149 |   background-color: rgba(96, 125, 139, 0.56) !important;
150 | }
151 | 
152 | /*switch*/
153 | .m-switch {
154 |   cursor: pointer;
155 |   /*display: table;*/
156 |   margin: 14px 0;
157 | }
158 | .m-switch .mdc-switch__background {
159 |   display: inline-block;
160 |   bottom: 0px;
161 |   left: 15px;
162 | }
163 | .m-switch .mdc-switch-label {
164 |   font-size: 1rem;
165 |   line-height: 1.5rem;
166 |   padding: 0 0 0 30px;
167 | }
168 | 
169 | /*textfield*/
170 | .m-textfield {
171 |   font-size: 1rem;
172 |   line-height: 1.5rem;
173 |   width: 100%;
174 |   height: 26px !important;
175 |   margin-left: 10px;
176 | }
177 | .m-textfield input {
178 |   border-bottom: 1px solid #ececec !important;
179 |   padding-top: 3px;
180 | }
181 | 
182 | /*---------------------------------------------------------------------------*/
183 | /*select*/
184 | 
185 | .m-select {
186 |   font-size: 14px;
187 |   line-height: 17px;
188 |   color: #000;
189 |   text-transform: uppercase;
190 |   background-color: #ececec;
191 |   border: none;
192 |   border-radius: 2px;
193 |   cursor: pointer;
194 |   padding: 9px 12px;
195 | }
196 | 
197 | /*---------------------------------------------------------------------------*/
198 | /*misc*/
199 | 
200 | .navbar-brand {
201 |   font-size: 1.5rem;
202 | }
203 | 
204 | h4 {
205 |   margin-bottom: 1.5rem;
206 | }
207 | 
208 | .m-box-add {
209 |   border-left-color: #f1b100;
210 | }
211 | 
212 | .m-box-refresh {
213 |   border-left-color: #d9534f;
214 | }
215 | 
216 | .m-origin {
217 |   font-size: 1.3rem;
218 |   padding-left: 10px;
219 | }
220 | 
221 | .m-btn-file {
222 |   float: right;
223 |   margin-bottom: 12px;
224 | }
225 | 
226 | .m-btn-all {
227 |   float: right;
228 |   margin-bottom: 12px;
229 | }
230 | 
231 | .m-btn-add {
232 |   float: right;
233 | }
234 | 
235 | .m-btn-remove {
236 |   float: right;
237 |   margin-left: 20px;
238 | }
239 | 
240 | .m-btn-refresh {
241 |   float: right;
242 | }
243 | 
244 | /*---------------------------------------------------------------------------*/
245 | /*settings*/
246 | 
247 | .m-settings .m-label {
248 |   display: inline-block;
249 |   font-size: 1rem;
250 |   line-height: 1.5rem;
251 |   padding: 5px 0 5px 0;
252 | }
253 | .m-settings .m-select {
254 |   width: 100%;
255 |   margin-bottom: 20px;
256 | }
257 | 
258 | .m-content .m-switch,
259 | .m-compiler .m-switch {
260 |   width: 100%;
261 | }
262 | 
263 | .m-compiler .scroll {
264 |   margin-top: 10px;
265 |   overflow-y: auto;
266 |   overflow-x: hidden;
267 | }
268 | .m-compiler .scroll.max {
269 |   height: 324px;
270 | }
271 | 
272 | /*---------------------------------------------------------------------------*/
273 | /*custom*/
274 | 
275 | .m-custom input[type=file] {
276 |   display: none;
277 | }
278 | .m-custom .m-button {
279 |   letter-spacing: 0;
280 |   padding: 0 11px;
281 |   margin-bottom: 20px;
282 | }
283 | .m-custom .m-button:first-of-type {
284 |   margin-right: 10px;
285 | }
286 | .m-custom .m-error {
287 |   color: #dc3545;
288 |   margin-bottom: 10px;
289 | }
290 | 
291 | /*---------------------------------------------------------------------------*/
292 | /*footer*/
293 | 
294 | footer {
295 |   text-align: center;
296 |   margin: 0 auto;
297 | }
298 | footer nav {
299 |   font-size: .8rem;
300 |   color: #212529;
301 | }
302 | footer nav a {
303 |   text-decoration: none;
304 |   color: #212529;
305 | }
306 | footer nav a:hover {
307 |   text-decoration: underline;
308 | }
309 | 


--------------------------------------------------------------------------------
/options/index.html:
--------------------------------------------------------------------------------
 1 | 
 2 | 
 3 | 
 4 |   
 5 |   
 6 |   Markdown Viewer
 7 |   
 8 |   
 9 |   
10 |   
11 |   
12 |   
13 | 
14 | 
15 |   
16 | 26 |
27 |
28 |
29 | 30 |
31 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /options/index.js: -------------------------------------------------------------------------------- 1 | 2 | var origins = Origins() 3 | var popup = Popup() 4 | 5 | m.mount(document.querySelector('main'), { 6 | view: () => [ 7 | origins.render(), 8 | popup.options(), 9 | ] 10 | }) 11 | 12 | // header menu 13 | document.querySelector('.nav').addEventListener('click', (e) => { 14 | e.preventDefault() 15 | Array.from(document.querySelectorAll('.nav a')).forEach((link) => { 16 | link.classList.remove('active') 17 | }) 18 | if (e.target.innerText === 'Origins') { 19 | document.querySelector('.m-origins').classList.remove('hidden') 20 | document.querySelector('.m-settings').classList.add('hidden') 21 | e.target.classList.add('active') 22 | } 23 | else if (e.target.innerText === 'Settings') { 24 | document.querySelector('.m-origins').classList.add('hidden') 25 | document.querySelector('.m-settings').classList.remove('hidden') 26 | e.target.classList.add('active') 27 | } 28 | else if (e.target.innerText === 'Help') { 29 | window.location = 'https://github.com/simov/markdown-viewer#table-of-contents' 30 | } 31 | }) 32 | -------------------------------------------------------------------------------- /options/origins.js: -------------------------------------------------------------------------------- 1 | 2 | var Origins = () => { 3 | var defaults = { 4 | // storage 5 | origins: {}, 6 | match: '', 7 | // UI 8 | host: '', 9 | timeout: null, 10 | file: true, 11 | // chrome 12 | permissions: {}, 13 | } 14 | 15 | var state = Object.assign({}, defaults) 16 | 17 | chrome.extension.isAllowedFileSchemeAccess((isAllowedAccess) => { 18 | state.file = /Firefox/.test(navigator.userAgent) 19 | ? true // ff: `Allow access to file URLs` option isn't available 20 | : isAllowedAccess 21 | m.redraw() 22 | }) 23 | 24 | chrome.runtime.sendMessage({message: 'options.origins'}, (res) => { 25 | Object.assign(state, {file: state.file}, res) 26 | chrome.permissions.getAll(({origins}) => { 27 | state.permissions = origins.reduce((all, origin) => 28 | (all[origin.replace(/(.*)\/\*$/, '$1')] = true, all), {}) 29 | m.redraw() 30 | }) 31 | }) 32 | 33 | var events = { 34 | file: () => { 35 | chrome.tabs.create({url: `chrome://extensions/?id=${chrome.runtime.id}`}) 36 | }, 37 | 38 | host: (e) => { 39 | state.scheme = e.target.value.replace(/(.*):\/\/.*/i, '$1') 40 | state.domain = e.target.value.replace(/.*:\/\/([^/]+).*/i, '$1') 41 | state.host = e.target.value 42 | }, 43 | 44 | add: (all) => () => { 45 | if (!all && !state.host && !['https', 'http', '*'].includes(state.scheme)) { 46 | return 47 | } 48 | var origin = all ? '*://*' : `${state.scheme}://${state.domain}` 49 | if (/Firefox/.test(navigator.userAgent) && /:\d{2,4}/.test(origin)) { 50 | origin = origin.replace(/(:\d{2,4})/, '') 51 | } 52 | chrome.permissions.request({origins: [`${origin}/*`]}, (granted) => { 53 | if (granted) { 54 | chrome.runtime.sendMessage({message: 'origin.add', origin}) 55 | state.origins[origin] = { 56 | header: true, 57 | path: true, 58 | match: state.match, 59 | } 60 | state.host = '' 61 | state.permissions[origin] = true 62 | m.redraw() 63 | } 64 | }) 65 | }, 66 | 67 | remove: (origin) => () => { 68 | chrome.permissions.remove({origins: [`${origin}/*`]}, (removed) => { 69 | if (removed) { 70 | chrome.runtime.sendMessage({message: 'origin.remove', origin}) 71 | delete state.origins[origin] 72 | delete state.permissions[origin] 73 | m.redraw() 74 | } 75 | }) 76 | }, 77 | 78 | refresh: (origin) => () => { 79 | chrome.permissions.request({origins: [`${origin}/*`]}, (granted) => { 80 | if (granted) { 81 | state.permissions[origin] = true 82 | m.redraw() 83 | } 84 | }) 85 | }, 86 | 87 | header: (origin) => () => { 88 | state.origins[origin].header = !state.origins[origin].header 89 | var {header, path, match} = state.origins[origin] 90 | chrome.runtime.sendMessage({ 91 | message: 'origin.update', 92 | origin, 93 | options: {header, path, match}, 94 | }) 95 | }, 96 | 97 | path: (origin) => () => { 98 | state.origins[origin].path = !state.origins[origin].path 99 | var {header, path, match} = state.origins[origin] 100 | chrome.runtime.sendMessage({ 101 | message: 'origin.update', 102 | origin, 103 | options: {header, path, match}, 104 | }) 105 | }, 106 | 107 | match: (origin) => (e) => { 108 | state.origins[origin].match = e.target.value 109 | clearTimeout(state.timeout) 110 | state.timeout = setTimeout(() => { 111 | var {header, path, match} = state.origins[origin] 112 | chrome.runtime.sendMessage({ 113 | message: 'origin.update', 114 | origin, 115 | options: {header, path, match}, 116 | }) 117 | }, 750) 118 | }, 119 | } 120 | 121 | var oncreate = { 122 | ripple: (vnode) => { 123 | mdc.ripple.MDCRipple.attachTo(vnode.dom) 124 | }, 125 | textfield: (vnode) => { 126 | mdc.textfield.MDCTextField.attachTo(vnode.dom) 127 | } 128 | } 129 | 130 | var onupdate = { 131 | header: (origin) => (vnode) => { 132 | if (vnode.dom.classList.contains('is-checked') !== state.origins[origin].header) { 133 | vnode.dom.classList.toggle('is-checked') 134 | } 135 | }, 136 | path: (origin) => (vnode) => { 137 | if (vnode.dom.classList.contains('is-checked') !== state.origins[origin].path) { 138 | vnode.dom.classList.toggle('is-checked') 139 | } 140 | } 141 | } 142 | 143 | var render = () => 144 | m('.m-origins', 145 | 146 | // file access 147 | m('.row', 148 | m('.col-xxl-10.col-xl-10.col-lg-9.col-md-8.col-sm-12', 149 | m('h3', 'File Access'), 150 | ), 151 | m('.col-xxl-2.col-xl-2.col-lg-3.col-md-4.col-sm-12', 152 | // file access is disabled 153 | (!state.file || null) && 154 | m('button.mdc-button mdc-button--raised m-button m-btn-file', { 155 | oncreate: oncreate.ripple, 156 | onclick: events.file 157 | }, 158 | 'Allow Access' 159 | ) 160 | ), 161 | ), 162 | ...Object.keys(state.origins) 163 | .filter((origin) => origin === 'file://' && state.file) 164 | .map(callout), 165 | 166 | // site access 167 | m('.row', 168 | m('.col-xxl-10.col-xl-10.col-lg-10.col-md-9.col-sm-8', 169 | m('h3', 'Site Access'), 170 | ), 171 | (!Object.keys(state.origins).includes('*://*') || null) && 172 | m('.col-xxl-2.col-xl-2.col-lg-2.col-md-3.col-sm-4', 173 | m('button.mdc-button mdc-button--raised m-button m-btn-all', { 174 | oncreate: oncreate.ripple, 175 | onclick: events.add(true) 176 | }, 177 | 'Allow All' 178 | ), 179 | ), 180 | ), 181 | 182 | // add origin 183 | m('.bs-callout m-box-add', 184 | m('.row', 185 | m('.col-xxl-11.col-xl-11.col-lg-10.col-md-10.col-sm-12', 186 | m('.mdc-text-field m-textfield', { 187 | oncreate: oncreate.textfield, 188 | }, 189 | m('input.mdc-text-field__input', { 190 | type: 'text', 191 | value: state.host, 192 | onchange: events.host, 193 | placeholder: 'Copy/paste URL address here' 194 | }), 195 | m('.mdc-line-ripple') 196 | ), 197 | ), 198 | m('.col-xxl-1.col-xl-1.col-lg-2.col-md-2.col-sm-12', 199 | m('button.mdc-button mdc-button--raised m-button m-btn-add', { 200 | oncreate: oncreate.ripple, 201 | onclick: events.add() 202 | }, 203 | 'Add' 204 | ), 205 | ) 206 | ) 207 | ), 208 | 209 | // allowed origins 210 | ...Object.keys(state.origins) 211 | .filter((origin) => origin !== 'file://') 212 | .sort((a, b) => a < b ? 1 : a > b ? -1 : 0) 213 | .map(callout) 214 | ) 215 | 216 | var callout = (origin) => 217 | m('.bs-callout', {class: !state.permissions[origin] ? 'm-box-refresh' : undefined}, 218 | // origin 219 | m('.row', 220 | m('.col-xxl-8.col-xl-8.col-lg-8.col-md-7.col-sm-12 m-overflow', m('span.m-origin', origin)), 221 | m('.col-xxl-4.col-xl-4.col-lg-4.col-md-5.col-sm-12', 222 | // remove 223 | (origin !== 'file://' || null) && 224 | m('button.mdc-button mdc-button--raised m-button m-btn-remove', { 225 | oncreate: oncreate.ripple, 226 | onclick: events.remove(origin) 227 | }, 228 | 'Remove' 229 | ), 230 | // refresh 231 | (!state.permissions[origin] || null) && 232 | m('button.mdc-button mdc-button--raised m-button m-btn-refresh', { 233 | oncreate: oncreate.ripple, 234 | onclick: events.refresh(origin) 235 | }, 236 | 'Refresh' 237 | ) 238 | ) 239 | ), 240 | // header detection 241 | m('.row', 242 | m('.col-sm-12', 243 | m('.overflow', 244 | m('label.mdc-switch m-switch', { 245 | onupdate: onupdate.header, 246 | title: 'Toggle Header Detection' 247 | }, 248 | m('input.mdc-switch__native-control', { 249 | type: 'checkbox', 250 | checked: state.origins[origin].header, 251 | onchange: events.header(origin) 252 | }), 253 | m('.mdc-switch__background', m('.mdc-switch__knob')), 254 | m('span.mdc-switch-label', 255 | 'Content Type Detection: ', 256 | m('span', 'text/markdown'), 257 | ', ', 258 | m('span', 'text/x-markdown'), 259 | ', ', 260 | m('span', 'text/plain'), 261 | ) 262 | ), 263 | ) 264 | ) 265 | ), 266 | // path matching regexp 267 | m('.row', 268 | m('.col-sm-12', 269 | m('.overflow', 270 | m('label.mdc-switch m-switch', { 271 | onupdate: onupdate.path, 272 | title: 'Toggle Path Detection' 273 | }, 274 | m('input.mdc-switch__native-control', { 275 | type: 'checkbox', 276 | checked: state.origins[origin].path, 277 | onchange: events.path(origin) 278 | }), 279 | m('.mdc-switch__background', m('.mdc-switch__knob')), 280 | m('span.mdc-switch-label', 281 | 'Path Matching RegExp: ' 282 | ) 283 | ), 284 | m('.mdc-text-field m-textfield', { 285 | oncreate: oncreate.textfield 286 | }, 287 | m('input.mdc-text-field__input', { 288 | type: 'text', 289 | onkeyup: events.match(origin), 290 | value: state.origins[origin].match, 291 | }), 292 | m('.mdc-line-ripple') 293 | ) 294 | ) 295 | ) 296 | ) 297 | ) 298 | 299 | return {state, render} 300 | } 301 | -------------------------------------------------------------------------------- /options/settings.js: -------------------------------------------------------------------------------- 1 | 2 | var Settings = () => { 3 | var defaults = { 4 | icon: 'default', 5 | theme: 'light', 6 | _icons: ['default', 'light', 'dark'], 7 | _themes: ['light', 'dark', 'auto'], 8 | } 9 | 10 | var state = Object.assign({}, defaults) 11 | 12 | chrome.runtime.sendMessage({message: 'options.settings'}, (res) => { 13 | Object.assign(state, res) 14 | document.querySelector('body').classList.add(state.theme) 15 | m.redraw() 16 | }) 17 | 18 | var events = { 19 | icon: (e) => { 20 | state.icon = state._icons[e.target.selectedIndex] 21 | chrome.runtime.sendMessage({ 22 | message: 'options.icon', 23 | settings: { 24 | icon: state.icon, 25 | theme: state.theme, 26 | }, 27 | }) 28 | }, 29 | theme: (e) => { 30 | state.theme = state._themes[e.target.selectedIndex] 31 | chrome.runtime.sendMessage({ 32 | message: 'options.theme', 33 | settings: { 34 | icon: state.icon, 35 | theme: state.theme, 36 | }, 37 | }) 38 | document.querySelector('body').classList.remove(...state._themes) 39 | document.querySelector('body').classList.add(state.theme) 40 | } 41 | } 42 | 43 | var oncreate = {} 44 | 45 | var onupdate = {} 46 | 47 | var render = () => [ 48 | m('.row', 49 | m('.col-xxl-6.col-xl-6.col-lg-6.col-md-6.col-sm-12', 50 | m('span.m-label', 51 | 'Extension Icon' 52 | ) 53 | ), 54 | m('.col-xxl-6.col-xl-6.col-lg-6.col-md-6.col-sm-12', 55 | m('select.mdc-elevation--z2 m-select', { 56 | onchange: events.icon 57 | }, 58 | state._icons.map((icon) => 59 | m('option', {selected: state.icon === icon}, icon) 60 | ) 61 | ) 62 | ), 63 | ), 64 | m('.row', 65 | m('.col-xxl-6.col-xl-6.col-lg-6.col-md-6.col-sm-12', 66 | m('span.m-label', 67 | 'Popup & Options Page' 68 | ) 69 | ), 70 | m('.col-xxl-6.col-xl-6.col-lg-6.col-md-6.col-sm-12', 71 | m('select.mdc-elevation--z2 m-select', { 72 | onchange: events.theme 73 | }, 74 | state._themes.map((theme) => 75 | m('option', {selected: state.theme === theme}, theme) 76 | ) 77 | ) 78 | ), 79 | ), 80 | ] 81 | 82 | return {state, render} 83 | } 84 | -------------------------------------------------------------------------------- /popup/index.css: -------------------------------------------------------------------------------- 1 | 2 | /*---------------------------------------------------------------------------*/ 3 | /*global*/ 4 | 5 | html, body { 6 | height: auto; 7 | min-height: auto; 8 | overflow: hidden; 9 | background: #fff; 10 | padding: 0; 11 | margin: 0; 12 | } 13 | 14 | body, 15 | body label, 16 | body input { 17 | font-family: 'Monospace', monospace !important; 18 | } 19 | body select, 20 | body button, 21 | body a.mdc-tab { 22 | font-family: sans-serif !important; 23 | } 24 | 25 | .mdc-elevation--z2 { 26 | box-shadow: 27 | 0 3px 1px -2px rgba(0,0,0,.2), 28 | 0 2px 2px 0 rgba(0,0,0,.14), 29 | 0 1px 5px 0 rgba(0,0,0,.12); 30 | } 31 | 32 | #popup { 33 | width: 350px; 34 | padding: 20px; 35 | -webkit-user-select: none; 36 | } 37 | #popup:after { content: ''; display: block; clear: both; } 38 | 39 | /*---------------------------------------------------------------------------*/ 40 | /*color modes*/ 41 | 42 | body.dark { 43 | filter: invert(100%) hue-rotate(180deg); 44 | background-color: #e4e4e4; 45 | } 46 | body.dark .m-select { 47 | background-color: #c5c5c5; 48 | } 49 | body.dark .m-button { 50 | background-color: #c5c5c5 !important; 51 | } 52 | 53 | @media (prefers-color-scheme: dark) { 54 | body.auto { 55 | filter: invert(100%) hue-rotate(180deg); 56 | background-color: #e4e4e4; 57 | } 58 | body.auto .m-select { 59 | background-color: #c5c5c5; 60 | } 61 | body.auto .m-button { 62 | background-color: #c5c5c5 !important; 63 | } 64 | } 65 | 66 | /*---------------------------------------------------------------------------*/ 67 | /*button*/ 68 | 69 | /*defaults button*/ 70 | #popup button:nth-child(2) { float: right; } 71 | /*advanced options button*/ 72 | #popup button:nth-child(2n+3) { float: right; margin-top: 20px; } 73 | 74 | .m-button { 75 | background-color: #ececec !important; 76 | color: #000 !important; 77 | } 78 | .m-button:before { 79 | background-color: rgba(96, 125, 139, 0.56) !important; 80 | } 81 | .m-button:after { 82 | background-color: rgba(96, 125, 139, 0.56) !important; 83 | } 84 | 85 | /*---------------------------------------------------------------------------*/ 86 | /*select*/ 87 | 88 | .m-select { 89 | font-size: 14px; 90 | line-height: 17px; 91 | color: #000; 92 | text-transform: uppercase; 93 | background-color: #ececec; 94 | border: none; 95 | border-radius: 2px; 96 | cursor: pointer; 97 | padding: 9px 12px; 98 | } 99 | 100 | #popup .m-select { 101 | width: 350px; 102 | margin: 20px 0 0 0; 103 | } 104 | 105 | /*---------------------------------------------------------------------------*/ 106 | /*switch*/ 107 | 108 | .m-switch { 109 | cursor: pointer; 110 | margin: 7px 0; 111 | } 112 | .m-switch:first-child { 113 | margin-top: 12px; 114 | } 115 | .m-switch:last-child { 116 | margin-bottom: 10px; 117 | } 118 | .m-switch .mdc-switch__background { 119 | display: inline-block; 120 | bottom: 5px; 121 | left: 15px; 122 | } 123 | .m-switch .mdc-switch-label { 124 | font-size: 1rem; 125 | line-height: 1.5rem; 126 | white-space: nowrap; 127 | overflow: hidden; 128 | text-overflow: ellipsis; 129 | display: inline-block; 130 | padding: 0 0 0 30px; 131 | } 132 | 133 | #popup .m-switch .mdc-switch-label { width: 276px; } 134 | 135 | /*---------------------------------------------------------------------------*/ 136 | /*tabs*/ 137 | 138 | .m-tabs { 139 | border: 0; 140 | height: 36px; 141 | margin: 20px 0 0 0; 142 | display: flex; 143 | } 144 | .m-tabs a { 145 | line-height: 36px; 146 | border-bottom: 1px solid #e0e0e0; 147 | display: inline-block; 148 | width: 33.33%; 149 | max-width: 33.33%; 150 | min-width: 33.33%; 151 | height: 36px; 152 | max-height: 36px; 153 | min-height: 36px; 154 | padding: 0; 155 | align-content: stretch; 156 | } 157 | .m-tabs .mdc-tab-bar__indicator { background: #607d8b; } 158 | 159 | .m-panels {} 160 | .m-panel { display: none; } 161 | .m-panel.is-active { display: block; } 162 | 163 | /*---------------------------------------------------------------------------*/ 164 | /*options scroll*/ 165 | 166 | .scroll { 167 | margin-top: 10px; 168 | overflow-y: auto; 169 | overflow-x: hidden; 170 | } 171 | .scroll.max { 172 | height: 324px; 173 | } 174 | 175 | /*---------------------------------------------------------------------------*/ 176 | /*scrollbar*/ 177 | 178 | ::-webkit-scrollbar { 179 | width: 10px; 180 | height: 10px; 181 | } 182 | ::-webkit-scrollbar-track-piece { 183 | background: #ececec; 184 | -webkit-border-radius: 8px; 185 | } 186 | ::-webkit-scrollbar-thumb { 187 | height: 50px; 188 | background: #afbdc3; 189 | -webkit-border-radius: 8px; 190 | outline-offset: -2px; 191 | } 192 | ::-webkit-scrollbar-thumb:hover { 193 | height: 50px; 194 | background-color: #607d8b; 195 | -webkit-border-radius: 8px; 196 | } 197 | -------------------------------------------------------------------------------- /popup/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Markdown Viewer 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /popup/index.js: -------------------------------------------------------------------------------- 1 | 2 | var Popup = () => { 3 | 4 | var state = { 5 | compiler: '', 6 | options: {}, 7 | content: {}, 8 | theme: '', 9 | themes: {}, 10 | _themes: [ 11 | 'github', 12 | 'github-dark', 13 | // 'air', 14 | 'almond', 15 | 'awsm', 16 | 'axist', 17 | 'bamboo', 18 | 'bullframe', 19 | 'holiday', 20 | 'kacit', 21 | 'latex', 22 | 'marx', 23 | 'mini', 24 | 'modest', 25 | 'new', 26 | 'no-class', 27 | 'pico', 28 | 'retro', 29 | 'sakura', 30 | 'sakura-vader', 31 | 'semantic', 32 | 'simple', 33 | // 'splendor', 34 | 'style-sans', 35 | 'style-serif', 36 | 'stylize', 37 | 'superstylin', 38 | 'tacit', 39 | 'vanilla', 40 | 'water', 41 | 'water-dark', 42 | 'writ', 43 | 'custom', 44 | ], 45 | _width: [ 46 | 'auto', 47 | 'full', 48 | 'wide', 49 | 'large', 50 | 'medium', 51 | 'small', 52 | 'tiny', 53 | ], 54 | raw: false, 55 | tab: '', 56 | tabs: ['theme', 'compiler', 'content'], 57 | compilers: [], 58 | description: { 59 | themes: {}, 60 | compiler: {}, 61 | content: { 62 | autoreload: 'Auto reload on file change', 63 | emoji: 'Convert emoji :shortnames: into EmojiOne images', 64 | toc: 'Generate Table of Contents', 65 | mathjax: 'Render MathJax formulas', 66 | mermaid: 'Mermaid diagrams', 67 | syntax: 'Syntax highlighting for fenced code blocks', 68 | } 69 | }, 70 | settings: {} 71 | } 72 | 73 | var events = { 74 | tab: (e) => { 75 | state.tab = e.target.hash.replace('#tab-', '') 76 | localStorage.setItem('tab', state.tab) 77 | return false 78 | }, 79 | 80 | compiler: { 81 | name: (e) => { 82 | state.compiler = state.compilers[e.target.selectedIndex] 83 | chrome.runtime.sendMessage({ 84 | message: 'popup.compiler.name', 85 | compiler: state.compiler, 86 | }, () => { 87 | chrome.runtime.sendMessage({message: 'popup'}, init) 88 | }) 89 | }, 90 | options: (e) => { 91 | state.options[e.target.name] = !state.options[e.target.name] 92 | chrome.runtime.sendMessage({ 93 | message: 'popup.compiler.options', 94 | compiler: state.compiler, 95 | options: state.options, 96 | }) 97 | } 98 | }, 99 | 100 | content: (e) => { 101 | state.content[e.target.name] = !state.content[e.target.name] 102 | chrome.runtime.sendMessage({ 103 | message: 'popup.content', 104 | content: state.content, 105 | }) 106 | }, 107 | 108 | themes: (e) => { 109 | state.themes.width = state._width[e.target.selectedIndex] 110 | chrome.runtime.sendMessage({ 111 | message: 'popup.themes', 112 | themes: state.themes, 113 | }) 114 | }, 115 | 116 | theme: (e) => { 117 | state.theme = state._themes[e.target.selectedIndex] 118 | chrome.runtime.sendMessage({ 119 | message: 'popup.theme', 120 | theme: state.theme 121 | }) 122 | }, 123 | 124 | raw: () => { 125 | state.raw = !state.raw 126 | chrome.runtime.sendMessage({ 127 | message: 'popup.raw', 128 | raw: state.raw 129 | }) 130 | }, 131 | 132 | defaults: () => { 133 | chrome.runtime.sendMessage({ 134 | message: 'popup.defaults' 135 | }, () => { 136 | chrome.runtime.sendMessage({message: 'popup'}, init) 137 | localStorage.removeItem('tab') 138 | state._tabs.activeTabIndex = 0 139 | }) 140 | }, 141 | 142 | advanced: () => { 143 | chrome.runtime.sendMessage({message: 'popup.advanced'}) 144 | } 145 | } 146 | 147 | var init = (res) => { 148 | state.compiler = res.compiler 149 | state.options = res.options 150 | state.content = res.content 151 | state.theme = res.theme 152 | state.themes = res.themes 153 | 154 | state.raw = res.raw 155 | state.tab = localStorage.getItem('tab') || 'theme' 156 | state.compilers = res.compilers 157 | state.description.compiler = res.description 158 | 159 | state.settings = res.settings 160 | document.querySelector('body').classList.add(state.settings.theme) 161 | 162 | m.redraw() 163 | } 164 | 165 | chrome.runtime.sendMessage({message: 'popup'}, init) 166 | 167 | var oncreate = { 168 | ripple: (vnode) => { 169 | mdc.ripple.MDCRipple.attachTo(vnode.dom) 170 | }, 171 | tabs: (vnode) => { 172 | state._tabs = mdc.tabs.MDCTabBar.attachTo(vnode.dom) 173 | setTimeout(() => { 174 | state._tabs.activeTabIndex = state.tabs.indexOf(state.tab) 175 | }, 250) 176 | } 177 | } 178 | 179 | var onupdate = (tab, key) => (vnode) => { 180 | var value = tab === 'compiler' ? state.options[key] 181 | : tab === 'content' ? state.content[key] 182 | : null 183 | 184 | if (vnode.dom.classList.contains('is-checked') !== value) { 185 | vnode.dom.classList.toggle('is-checked') 186 | } 187 | } 188 | 189 | var render = () => 190 | m('#popup', 191 | // raw 192 | m('button.mdc-button mdc-button--raised m-button', { 193 | oncreate: oncreate.ripple, 194 | onclick: events.raw 195 | }, 196 | (state.raw ? 'Html' : 'Markdown') 197 | ), 198 | // defaults 199 | m('button.mdc-button mdc-button--raised m-button', { 200 | oncreate: oncreate.ripple, 201 | onclick: events.defaults 202 | }, 203 | 'Defaults' 204 | ), 205 | 206 | // tabs 207 | m('nav.mdc-tab-bar m-tabs', { 208 | oncreate: oncreate.tabs, 209 | onclick: events.tab 210 | }, 211 | state.tabs.map((tab) => 212 | m('a.mdc-tab', { 213 | href: '#tab-' + tab, 214 | }, 215 | tab 216 | )), 217 | m('span.mdc-tab-bar__indicator') 218 | ), 219 | m('.m-panels', 220 | // theme 221 | m('.m-panel', { 222 | class: state.tab === 'theme' ? 'is-active' : '' 223 | }, 224 | m('select.mdc-elevation--z2 m-select', { 225 | onchange: events.theme 226 | }, 227 | state._themes.map((theme) => 228 | m('option', {selected: state.theme === theme}, theme) 229 | ) 230 | ), 231 | m('select.mdc-elevation--z2 m-select', { 232 | onchange: events.themes 233 | }, 234 | state._width.map((width) => 235 | m('option', { 236 | selected: state.themes.width === width, 237 | }, width) 238 | ) 239 | ), 240 | ), 241 | // compiler 242 | m('.m-panel', { 243 | class: state.tab === 'compiler' ? 'is-active' : '' 244 | }, 245 | m('select.mdc-elevation--z2 m-select', { 246 | onchange: events.compiler.name 247 | }, 248 | state.compilers.map((name) => 249 | m('option', {selected: state.compiler === name}, name) 250 | ) 251 | ), 252 | m('.scroll', { 253 | class: Object.keys(state.options) 254 | .filter((key) => typeof state.options[key] === 'boolean') 255 | .length > 8 256 | ? 'max' : '' 257 | }, 258 | Object.keys(state.options) 259 | .filter((key) => typeof state.options[key] === 'boolean') 260 | .map((key) => 261 | m('label.mdc-switch m-switch', { 262 | onupdate: onupdate('compiler', key), 263 | title: state.description.compiler[key] 264 | }, 265 | m('input.mdc-switch__native-control', { 266 | type: 'checkbox', 267 | name: key, 268 | checked: state.options[key], 269 | onchange: events.compiler.options 270 | }), 271 | m('.mdc-switch__background', m('.mdc-switch__knob')), 272 | m('span.mdc-switch-label', key) 273 | ) 274 | ) 275 | ) 276 | ), 277 | // content 278 | m('.m-panel', { 279 | class: state.tab === 'content' ? 'is-active' : '' 280 | }, 281 | m('.scroll', Object.keys(state.content).map((key) => 282 | m('label.mdc-switch m-switch', { 283 | onupdate: onupdate('content', key), 284 | title: state.description.content[key] 285 | }, 286 | m('input.mdc-switch__native-control', { 287 | type: 'checkbox', 288 | name: key, 289 | checked: state.content[key], 290 | onchange: events.content 291 | }), 292 | m('.mdc-switch__background', m('.mdc-switch__knob')), 293 | m('span.mdc-switch-label', key) 294 | )) 295 | ) 296 | ) 297 | ), 298 | 299 | // advanced options 300 | m('button.mdc-button mdc-button--raised m-button', { 301 | oncreate: oncreate.ripple, 302 | onclick: events.advanced 303 | }, 304 | 'Advanced Options' 305 | ) 306 | ) 307 | 308 | var options = () => 309 | m('.row m-settings hidden', 310 | m('.col-xxl-4.col-xl-4.col-lg-6.col-md-6.col-sm-12', 311 | m('h3', 'Theme'), 312 | m('.bs-callout m-theme', 313 | m('.row', 314 | m('.col-xxl-6.col-xl-6.col-lg-6.col-md-6.col-sm-12', 315 | m('span.m-label', 316 | 'Content Theme' 317 | ) 318 | ), 319 | m('.col-xxl-6.col-xl-6.col-lg-6.col-md-6.col-sm-12', 320 | m('select.mdc-elevation--z2 m-select', { 321 | onchange: events.theme 322 | }, 323 | state._themes.map((theme) => 324 | m('option', {selected: state.theme === theme}, theme) 325 | ) 326 | ) 327 | ), 328 | ), 329 | m('.row', 330 | m('.col-xxl-6.col-xl-6.col-lg-6.col-md-6.col-sm-12', 331 | m('span.m-label', 332 | 'Content Width' 333 | ) 334 | ), 335 | m('.col-xxl-6.col-xl-6.col-lg-6.col-md-6.col-sm-12', 336 | m('select.mdc-elevation--z2 m-select', { 337 | onchange: events.themes 338 | }, 339 | state._width.map((width) => 340 | m('option', { 341 | selected: state.themes.width === width, 342 | }, width) 343 | ) 344 | ) 345 | ), 346 | ), 347 | settings.render() 348 | ), 349 | state.theme === 'custom' && 350 | custom.render() 351 | ), 352 | 353 | m('.col-xxl-4.col-xl-4.col-lg-6.col-md-6.col-sm-12', 354 | m('h3', 'Compiler'), 355 | m('.bs-callout m-compiler', 356 | m('select.mdc-elevation--z2 m-select', { 357 | onchange: events.compiler.name 358 | }, 359 | state.compilers.map((name) => 360 | m('option', {selected: state.compiler === name}, name) 361 | ) 362 | ), 363 | m('.scroll', { 364 | class: Object.keys(state.options) 365 | .filter((key) => typeof state.options[key] === 'boolean') 366 | .length > 8 367 | ? 'max' : '' 368 | }, 369 | Object.keys(state.options) 370 | .filter((key) => typeof state.options[key] === 'boolean') 371 | .map((key) => 372 | m('label.mdc-switch m-switch', { 373 | onupdate: onupdate('compiler', key), 374 | title: state.description.compiler[key] 375 | }, 376 | m('input.mdc-switch__native-control', { 377 | type: 'checkbox', 378 | name: key, 379 | checked: state.options[key], 380 | onchange: events.compiler.options 381 | }), 382 | m('.mdc-switch__background', m('.mdc-switch__knob')), 383 | m('span.mdc-switch-label', key) 384 | ) 385 | ) 386 | ) 387 | ), 388 | ), 389 | 390 | m('.col-xxl-4.col-xl-4.col-lg-6.col-md-6.col-sm-12', 391 | m('h3', 'Content'), 392 | m('.bs-callout m-content', 393 | m('.scroll', Object.keys(state.content).map((key) => 394 | m('label.mdc-switch m-switch', { 395 | onupdate: onupdate('content', key), 396 | title: state.description.content[key] 397 | }, 398 | m('input.mdc-switch__native-control', { 399 | type: 'checkbox', 400 | name: key, 401 | checked: state.content[key], 402 | onchange: events.content 403 | }), 404 | m('.mdc-switch__background', m('.mdc-switch__knob')), 405 | m('span.mdc-switch-label', key) 406 | )) 407 | ) 408 | ), 409 | ), 410 | ) 411 | 412 | return {state, render, options} 413 | } 414 | 415 | if (document.querySelector('.is-popup')) { 416 | var popup = Popup() 417 | m.mount(document.querySelector('body'), { 418 | view: (vnode) => popup.render() 419 | }) 420 | } 421 | else { 422 | var settings = Settings() 423 | var custom = Custom() 424 | } 425 | --------------------------------------------------------------------------------