diff --git a/.jshintignore b/.jshintignore index f118c685c..ca83cd332 100644 --- a/.jshintignore +++ b/.jshintignore @@ -15,6 +15,7 @@ www/common/onlyoffice/v2* server.js www/common/old-media-tag.js www/scratch +www/lib www/common/toolbar.js www/common/hyperscript.js diff --git a/CHANGELOG.md b/CHANGELOG.md index 84c8e6cda..7201173cf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,62 @@ +# UplandMoa (3.20.0) + +## Goals + +We've held off on deploying any major features while we work towards deploying some documentation we've been busy organizing. This release features a wide range of minor features intended to address a number of github issues and frequent causes of support tickets. + +## Update notes + +This release features a modification to the recommended Content Security Policy headers as demonstrated in `./cryptpad/docs/example.nginx.conf`. CryptPad will work without making this change, however, we highly recommend updating your instance's nginx.conf as it will mitigate a variety of potential security vulnerabilities. + +Otherwise, we've introduced a new client-side dependency (_Mathjax_) and changed some server-side code that will require a server restart. + +To update from 3.19.1 to 3.20.0: + +1. Apply the recommended changes to your `nginx.conf` +2. Stop your server +3. Get the latest platform code with git +4. Install client-side dependencies with `bower update` +5. Reload nginx to apply the updated CSP headers +6. Restart the CryptPad API server + +## Features + +* As noted above, this release features a change to the Content Security Policy headers which define the types of code that can be loaded in a given context. More specifically, we've addressed a number of CKEditor's quirks which required us to set a more lax security policy for the rich text editor. With these changes in place the only remaining exceptions to our general policy are applied for the sake of our OnlyOffice integration, though we hope to address its quirks soon as well. +* On the topic of the rich text editor, we also moved the _print_ action from the CKEditor toolbar to the _File_ menu to be more consistent with our other apps. +* The Kanban board that we use to organize our own team has become rather large and complex due to a wealth of long-term ideas and a large number of tags. We started to notice some performance issues as a result, and have begun looking into some optimizations to improve its scalability. As a start, we avoid applying changes whenever the Kanban's tab is not visible. +* We finally decided to file off one of the platform's rough edges which had been confusing curious users for some time. Every registered user is identified by a randomly-generated cryptographic key (the _Public Signing Key_ found on your settings page). These identifiers are used to allocate additional storage space via our premium accounts, and we occasionally require them for other support issues like deleting accounts or debugging server issues. Unfortunately, because we occasionally receive emails asking for help with _other administrators instances_ these keys were formatted along with the host domain in the form of a URL. As such, it was very tempting to open them in the browser even though there was no functionality corresponding to the URL. We've updated all the code that parses these keys and introduced a new format which is clearly _not a URL_, so hopefully we'll get fewer messages asking us why they _don't work_. +* We've made a number of small improvements to the common functionality in our code and slide editors: + * We've merged and built upon a pull request which implemented two new extensions to our markdown renderer for _Mathjax_ and _Markmap_. This introduces support for embedding formatted equations and markdown-based mind maps. Since these depend on new client-side code which would otherwise increase page loading time we've also implemented support for lazily loading extensions on demand, so you'll only load the extra code if the current document requires it. + * The _slide_ editor now throttles slide redraws so that updates are only applied after 400ms of inactivity rather than on every character update. + * We've made a number of small style tweaks for blockquotes, tables, and embedded media in rendered markdown. +* Lastly, we've made a large number of improvements to user and team drives: + * Search results now include shared folders with matching names and have been made _sortable_ like the rest of the drive. + * Inserting media in a document via the _Insert_ menu now updates its access time, which causes it to show up in the _Recent pads_ category of your drive. + * Shared folders now support access lists. To apply an access list to a shared folder that you own you may right-click the shared folder in your drive, choose _Access_, then click the _List_ tab of the resulting dialog. Enabling its access list will restrict access to its owners and any other contacts that you or other owners add to its list. Note, this access applies to the folder itself (who can view it or add to its directory), its access list will not be applied recursively to all the elements contained within which might be contained in other shared folders or other users drives. + * In the interest of removing jargon from the platform we've started to change text from "Delete from the server" to "Destroy". We plan to make more changes like this on an ongoing basis as we notice them. + * We've made a significant change to the way that _owned files_ are treated in the user and team drives. Previously, files that you owned were implicitly deleted from the server whenever you removed them from your drive. This seemed sensible when we first introduced the concept of ownership, however, now that a variety of assets can have multiple owners it is clearly less appropriate. Rather than require users to first remove themselves as a co-owner before removing an asset from their drive in order to allow other owners to continue accessing it we now offer two distinct _Remove_ and _Destroy_ actions. _Remove_ will simply take it out of your drive so that it will no longer count against your storage limit, while _Destroy_ will cause it to stop existing _for everyone_. To clarify the two actions we've associated them with a _trash bin_ and _paper shredder_ icon, respectively. + +## Bug fixes + +* Remote changes in the Kanban app removed pending text in new cards, effectively making it impossible (and very frustrating) to create new cards while anyone else was editing existing content or submitting their own new cards. +* Dropping an image directly into a spreadsheet no longer puts the UI into an unrecoverable state, though we still don't support image drop. To insert images, use the "Insert" menu. This was actually fixed in our 3.19.1 release, but it wasn't documented in the release notes. +* When a user attempted to open an automatically expiring document which had passed its expiration date they were shown a general message indicating that the document had been deleted even when they had sufficient information to know that it had been marked for expiration. We now display a message indicating the more likely cause of its deletion. +* We've spent some time working on the usability of comments in our rich text app: + * When a user started adding a first comment to a document then canceled their action it was possible for the document to get stuck in an odd layout. This extra space allocated towards comments now correctly collapses as intended when there are no comments, pending or otherwise. + * The comments UI is now completely disabled whenever the document is in read-only mode, whether due to disconnection or insufficient permissions. + * The _comment_ button in the app toolbar now toggles on and off to indicate the eligibility of the current selection as a new comment. +* We've fixed a number of issues with teams: + * Users no longer send themselves a notification when they remove themself as an owner of a pad from within the _Teams_ UI. + * The _worker_ process which is responsible for managing account rights now correctly upgrades and downgrades its internal state when its role within a team is changed by a remote user instead of requiring a complete worker reload. + * The worker does not delete credentials to access a team when it finds that its id is not in the team's roster, since this could be triggered accidentally by some unrelated server bugs that responded incorrectly to a request for the team roster's history. +* We've fixed a number of issues in our code and slide editors: + * The "Language" dropdown selectors in the "Theme" menu used to show "Language (Markdown)" when the page was first loaded, however, changing the setting to another language would drop the annotation and instead show only "Markdown". Now the annotation is preserved as intended. + * A recent update to our stylesheets introduced a regression in the buttons of our "print options" dialog. + * While polishing up the PRs which introduced the _Mathjax_ and _Markmap_ support we noticed that the client-side cache which is used to prevent unnecessary redraws of embedded media was causing only one instance of an element to be rendered when the same source was embedded in multiple sections of a document. +* The "File export" dialog featured a similar regression in the style of its buttons which has been addressed. +* We fixed a minor bug in our 3.19.0 release in which unregistered users (who do not have a "mailbox") tried to send a notification to themselves. +* We've added an additional check to the process for changing your account password in which we make sure that we are not overwriting another account with the same username and password. + # Thylacine's revenge (3.19.1) Our upcoming 3.20.0 release is planned for July 7th, 2020, but we are once again releasing a minor version featuring some nice bug fixes and usability improvements which are ready to be deployed now. In case you missed [our announcement](https://social.weho.st/@cryptpad/104360490068671089) we are phasing out our usage of the `master` and basing our releases on the `main` branch. For best results we recommend explicitly checking out code by its tag. diff --git a/bower.json b/bower.json index 9fd787c20..bd9cebdd6 100644 --- a/bower.json +++ b/bower.json @@ -49,7 +49,8 @@ "saferphore": "^0.0.1", "jszip": "Stuk/jszip#^3.1.5", "requirejs-plugins": "^1.0.3", - "dragula.js": "3.7.2" + "dragula.js": "3.7.2", + "MathJax": "3.0.5" }, "resolutions": { "bootstrap": "^v4.0.0", diff --git a/customize.dist/fonts/cptools/fonts/cptools.svg b/customize.dist/fonts/cptools/fonts/cptools.svg index 3540c5d06..8eee6df58 100644 --- a/customize.dist/fonts/cptools/fonts/cptools.svg +++ b/customize.dist/fonts/cptools/fonts/cptools.svg @@ -28,4 +28,5 @@ + \ No newline at end of file diff --git a/customize.dist/fonts/cptools/fonts/cptools.ttf b/customize.dist/fonts/cptools/fonts/cptools.ttf index 2788cf010..181d4268a 100644 Binary files a/customize.dist/fonts/cptools/fonts/cptools.ttf and b/customize.dist/fonts/cptools/fonts/cptools.ttf differ diff --git a/customize.dist/fonts/cptools/fonts/cptools.woff b/customize.dist/fonts/cptools/fonts/cptools.woff index 64b641b9d..a2b04bff9 100644 Binary files a/customize.dist/fonts/cptools/fonts/cptools.woff and b/customize.dist/fonts/cptools/fonts/cptools.woff differ diff --git a/customize.dist/fonts/cptools/style.css b/customize.dist/fonts/cptools/style.css index 85ed350f5..74ebac0b5 100644 --- a/customize.dist/fonts/cptools/style.css +++ b/customize.dist/fonts/cptools/style.css @@ -1,9 +1,9 @@ @font-face { font-family: 'cptools'; src: - url('fonts/cptools.ttf?da4x1y') format('truetype'), - url('fonts/cptools.woff?da4x1y') format('woff'), - url('fonts/cptools.svg?da4x1y#cptools') format('svg'); + url('fonts/cptools.ttf?5ntnhs') format('truetype'), + url('fonts/cptools.woff?5ntnhs') format('woff'), + url('fonts/cptools.svg?5ntnhs#cptools') format('svg'); font-weight: normal; font-style: normal; font-display: block; @@ -25,6 +25,9 @@ -moz-osx-font-smoothing: grayscale; } +.cptools-destroy:before { + content: "\e915"; +} .cptools-add-bottom:before { content: "\e913"; } diff --git a/customize.dist/pages.js b/customize.dist/pages.js index c37e4b2f5..864e3563f 100644 --- a/customize.dist/pages.js +++ b/customize.dist/pages.js @@ -62,7 +62,7 @@ define([ var imprintUrl = AppConfig.imprint && (typeof(AppConfig.imprint) === "boolean" ? '/imprint.html' : AppConfig.imprint); - Pages.versionString = "CryptPad v3.19.1 (Thylacine's revenge)"; + Pages.versionString = "CryptPad v3.20.0 (UplandMoa)"; // used for the about menu Pages.imprintLink = AppConfig.imprint ? footLink(imprintUrl, 'imprint') : undefined; diff --git a/customize.dist/src/less2/include/alertify.less b/customize.dist/src/less2/include/alertify.less index 2a7fd3761..db951613c 100644 --- a/customize.dist/src/less2/include/alertify.less +++ b/customize.dist/src/less2/include/alertify.less @@ -227,16 +227,7 @@ } } - ::placeholder { /* Chrome, Firefox, Opera, Safari 10.1+ */ - color: @cryptpad_color_grey; - opacity: 1; /* Firefox */ - } - :-ms-input-placeholder { /* Internet Explorer 10-11 */ - color: @cryptpad_color_grey; - } - ::-ms-input-placeholder { /* Microsoft Edge */ - color: @cryptpad_color_grey; - } + .tools_placeholder-color(@cryptpad_color_grey); span.cp-password-container { display: flex; diff --git a/customize.dist/src/less2/include/drive.less b/customize.dist/src/less2/include/drive.less index 72e34d7c6..e09dc0a0f 100644 --- a/customize.dist/src/less2/include/drive.less +++ b/customize.dist/src/less2/include/drive.less @@ -159,6 +159,10 @@ user-select: none; } + .cp-app-drive-element-restricted { + color: #939393; + } + .cp-app-drive-element-droppable { background-color: @drive_droppable-bg; color: #222; @@ -218,7 +222,7 @@ #cp-app-drive-search { - display: flex; + display: inline-flex; align-items: center; max-width: 400px; font-size: 30px; @@ -226,7 +230,7 @@ input { background: transparent; color: @colortheme_drive-color; - .tools_placeholder-color(@colortheme_drive-color); + .tools_placeholder-color(@cryptpad_color_grey); outline-width: 0px; border-radius: 0; width: 100%; @@ -252,7 +256,19 @@ color: @colortheme_drive-color; } } - + .cp-app-drive-search-spinner { + display: inline-flex; + color: @colortheme_drive-color; + font-size: 40px; + align-items: center; + justify-content: center; + } + .cp-app-drive-search-noresult { + font-size: 30px; + padding: 15px; + font-style: italic; + color: @cryptpad_color_grey; + } /* TREE */ @@ -313,6 +329,7 @@ padding: 0 10px; border: 0; color: lighten(@colortheme_sidebar-left-fg, 40%); + height: auto; } & > span.cp-app-drive-element-row { overflow: hidden; @@ -528,7 +545,7 @@ &.cp-app-drive-tags-list { width: 100%; table { - margin: 10px 50px; + margin: 10px; width: ~"calc(100% - 100px)"; table-layout: fixed; td, th { @@ -548,6 +565,21 @@ .cp-app-drive-element { .cp-app-drive-element-truncated { display: none; } } + + .cp-app-drive-new-ghost { + cursor: pointer; + opacity: 0.5; + padding: 0; + align-items: center; + justify-content: center; + display: inline-flex; + &:hover { + opacity: 0.7; + } + .fa, .cptools { + cursor: pointer; + } + } div.cp-app-drive-content-grid { padding: 1em; ul { @@ -571,6 +603,7 @@ border-radius: 0; border: 1px solid #ddd; font-size: 14px; + height: auto; } .cp-app-drive-element-state { position: absolute; @@ -594,27 +627,17 @@ } } } - .cp-app-drive-element-list { - display: none; - } .cp-app-drive-new-ghost { - cursor: pointer; - opacity: 0.5; - padding: 0; flex-flow: column; - align-items: center; - justify-content: center; - display: inline-flex; - &:hover { - opacity: 0.7; - } .fa, .cptools { - cursor: pointer; font-size: 90px; margin-top: 5px; margin-bottom: 0; } } + .cp-app-drive-element-list { + display: none; + } } .cp-app-drive-content-list { @@ -622,6 +645,10 @@ display: none; } // Make it act as a table! + .cp-app-drive-new-ghost { + padding: 0 5px; + margin-top: 20px; + } padding-left: 10px; ul { width: 100%; @@ -634,6 +661,7 @@ padding: 0 4px; flex: 1; min-width: 0; + height: auto; } &> span { @@ -780,6 +808,7 @@ flex-flow: row-reverse; flex-grow: 1; justify-content: flex-end; + padding-left: 10px; .cp-app-drive-path-element { .tools_unselectable(); display: inline-block; diff --git a/customize.dist/src/less2/include/forms.less b/customize.dist/src/less2/include/forms.less index 92a0f375e..db3ad3fbd 100644 --- a/customize.dist/src/less2/include/forms.less +++ b/customize.dist/src/less2/include/forms.less @@ -99,9 +99,12 @@ cursor: pointer; border-radius: 0; - .fa { + .fa, .cptools { margin-right: 0.2em; } + .cptools { + vertical-align: middle; + } color: @alertify-btn-fg; border: 1px solid @alertify-btn-fg; diff --git a/customize.dist/src/less2/include/markdown.less b/customize.dist/src/less2/include/markdown.less index 7a8fc0ddf..23eb056b0 100644 --- a/customize.dist/src/less2/include/markdown.less +++ b/customize.dist/src/less2/include/markdown.less @@ -1,23 +1,32 @@ @import (reference) "./tools.less"; .markdown_main() { + @nice-grey: #f3f3f3; + @accent-grey: rgba(0, 0, 0, 0.2); + hr { + border-top: 1px solid @accent-grey; + } blockquote { - background: #e5e5e5; - background: rgba(128,128,128,0.5); + background: @nice-grey; + background: rgba(144, 144, 144, 0.2); padding: 10px; - border-left: 3px solid #999; + border-left: 2px solid @accent-grey; padding-right: 0; p { margin: 0; } blockquote { margin: 0; } + & > *:not(:last-child) { + margin-bottom: 10px !important; + } } // todo ul, ol // TOC div.cp-md-toc { - background: #f3f3f3; + background: @nice-grey; padding: 20px; - //float: right; - margin: 5px; - margin-right: 0; + * { + margin: 5px; + margin-right: 0; + } max-width: 100%; min-width: 200px; white-space: nowrap; @@ -93,7 +102,13 @@ border: 1px solid #BBB; } - pre.mermaid { + pre.markmap { + border: 1px solid #ddd; + svg { + height: 400px; + } + } + pre[data-plugin] { svg { max-width: 100%; cursor: pointer; diff --git a/customize.dist/src/less2/include/modals-ui-elements.less b/customize.dist/src/less2/include/modals-ui-elements.less index c407c8523..e8282f6e8 100644 --- a/customize.dist/src/less2/include/modals-ui-elements.less +++ b/customize.dist/src/less2/include/modals-ui-elements.less @@ -150,6 +150,16 @@ overflow: unset; margin-bottom: 0; } + pre.markmap { + margin-bottom: 0; + max-height: 100%; + overflow: hidden; + display: flex; + flex-flow: column; + svg { + flex: 1; + } + } .cp-spinner { border-color: @colortheme_logo-1; border-top-color: transparent; diff --git a/customize.dist/src/less2/include/tools.less b/customize.dist/src/less2/include/tools.less index b8c36cbcd..68feb9314 100644 --- a/customize.dist/src/less2/include/tools.less +++ b/customize.dist/src/less2/include/tools.less @@ -2,13 +2,9 @@ &::-webkit-input-placeholder { /* WebKit, Blink, Edge */ color: @color;; } - &:-moz-placeholder { /* Mozilla Firefox 4 to 18 */ + &::placeholder { /* Chrome, Firefox, Opera, Safari 10.1+ */ color: @color; - opacity: 1; - } - &::-moz-placeholder { /* Mozilla Firefox 19+ */ - color: @color; - opacity: 1; + opacity: 1; /* Firefox */ } &:-ms-input-placeholder { /* Internet Explorer 10-11 */ color: @color; diff --git a/package-lock.json b/package-lock.json index dcced0ee4..1b799f71e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "cryptpad", - "version": "3.19.1", + "version": "3.20.0", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index 8b9bc912c..0611baa2a 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "cryptpad", "description": "realtime collaborative visual editor with zero knowlege server", - "version": "3.19.1", + "version": "3.20.0", "license": "AGPL-3.0+", "repository": { "type": "git", diff --git a/www/code/app-code.less b/www/code/app-code.less index aa2191be0..0ca86fd8b 100644 --- a/www/code/app-code.less +++ b/www/code/app-code.less @@ -105,6 +105,12 @@ .markdown_preformatted-code; .markdown_gfm-table(black); + table { + margin-bottom: 1rem; + } + media-tag > * { + margin-bottom: 1rem; + } } .cp-splitter { diff --git a/www/code/inner.js b/www/code/inner.js index de91714c4..7f304e6bc 100644 --- a/www/code/inner.js +++ b/www/code/inner.js @@ -297,6 +297,8 @@ define([ } }); + DiffMd.onPluginLoaded(drawPreview); + return { forceDraw: forceDrawPreview, draw: drawPreview, diff --git a/www/common/common-hash.js b/www/common/common-hash.js index cc4344413..f335d011b 100644 --- a/www/common/common-hash.js +++ b/www/common/common-hash.js @@ -28,6 +28,13 @@ var factory = function (Util, Crypto, Keys, Nacl) { }; }; + Hash.getSignPublicFromPrivate = function (edPrivateSafeStr) { + var edPrivateStr = Crypto.b64AddSlashes(edPrivateSafeStr); + var privateKey = Nacl.util.decodeBase64(edPrivateStr); + var keyPair = Nacl.sign.keyPair.fromSecretKey(privateKey); + return Nacl.util.encodeBase64(keyPair.publicKey); + }; + var getEditHashFromKeys = Hash.getEditHashFromKeys = function (secret) { var version = secret.version; var data = secret.keys; diff --git a/www/common/common-interface.js b/www/common/common-interface.js index d406eccaf..37ef044ff 100644 --- a/www/common/common-interface.js +++ b/www/common/common-interface.js @@ -395,7 +395,10 @@ define([ var navs = []; buttons.forEach(function (b) { if (!b.name || !b.onClick) { return; } - var button = h('button', { tabindex: '1', 'class': b.className || '' }, b.name); + var button = h('button', { tabindex: '1', 'class': b.className || '' }, [ + b.iconClass ? h('i' + b.iconClass) : undefined, + b.name + ]); button.classList.add('btn'); var todo = function () { var noClose = b.onClick(); diff --git a/www/common/cryptget.js b/www/common/cryptget.js index b03d756e1..ab707a49b 100644 --- a/www/common/cryptget.js +++ b/www/common/cryptget.js @@ -76,6 +76,9 @@ define([ finish(Session, void 0, rt.getUserDoc()); }; + config.onError = function (info) { + finish(Session, info.error); + }; config.onChannelError = function (info) { finish(Session, info.error); }; diff --git a/www/common/cryptpad-common.js b/www/common/cryptpad-common.js index af34260a9..71848e36d 100644 --- a/www/common/cryptpad-common.js +++ b/www/common/cryptpad-common.js @@ -632,7 +632,12 @@ define([ } }).nThen(function () { Crypt.get(parsed.hash, function (err, val) { - if (err) { throw new Error(err); } + if (err) { + return void cb(err); + } + if (!val) { + return void cb('ENOENT'); + } try { // Try to fix the title before importing the template var parsed = JSON.parse(val); @@ -665,14 +670,14 @@ define([ Crypt.get(parsed.hash, _waitFor(function (err, _val) { if (err) { _waitFor.abort(); - return void cb(); + return void cb(err); } try { val = JSON.parse(_val); fixPadMetadata(val, true); } catch (e) { _waitFor.abort(); - return void cb(); + return void cb(e.message); } }), optsGet); return; @@ -691,7 +696,7 @@ define([ Util.fetch(src, waitFor(function (err, _u8) { if (err) { _waitFor.abort(); - return void waitFor.abort(); + return void cb(err); } u8 = _u8; })); @@ -700,7 +705,7 @@ define([ FileCrypto.decrypt(u8, key, waitFor(function (err, _res) { if (err || !_res.content) { _waitFor.abort(); - return void waitFor.abort(); + return void cb(err); } res = _res; })); diff --git a/www/common/diffMarked.js b/www/common/diffMarked.js index 48f800075..1433a2381 100644 --- a/www/common/diffMarked.js +++ b/www/common/diffMarked.js @@ -19,22 +19,119 @@ define([ var renderer = new Marked.Renderer(); var restrictedRenderer = new Marked.Renderer(); - var Mermaid = { - init: function () {} - }; + var pluginLoaded = Util.mkEvent(); + DiffMd.onPluginLoaded = pluginLoaded.reg; var mermaidThemeCSS = //".node rect { fill: #DDD; stroke: #AAA; } " + "rect.task, rect.task0, rect.task2 { stroke-width: 1 !important; rx: 0 !important; } " + "g.grid g.tick line { opacity: 0.25; }" + "g.today line { stroke: red; stroke-width: 1; stroke-dasharray: 3; opacity: 0.5; }"; - require(['mermaid', 'css!/code/mermaid-new.css'], function (_Mermaid) { - Mermaid = _Mermaid; - Mermaid.initialize({ - gantt: { axisFormat: '%m-%d', }, - "themeCSS": mermaidThemeCSS, + var Mermaid = { + __stubbed: true, + init: function () { + require([ + 'mermaid', + 'css!/code/mermaid-new.css' + ], function (_Mermaid) { + console.debug("loaded mermaid"); + if (Mermaid.__stubbed) { + Mermaid = _Mermaid; + Mermaid.initialize({ + gantt: { axisFormat: '%m-%d', }, + "themeCSS": mermaidThemeCSS, + }); + } + + pluginLoaded.fire(); + }); + } + }; + + var Mathjax = { + __stubbed: true, + tex2svg: function (a, b) { + require([ + '/bower_components/MathJax/es5/tex-svg.js', + ], function () { + console.debug("Loaded mathjax"); + if (Mathjax.__stubbed) { + Mathjax = window.MathJax; + } + Mathjax.tex2svg(a, b); + pluginLoaded.fire(); + }); + } + }; + + var drawMarkmap; + var MarkMapTransform; + var Markmap; + + var markmapLoaded = false; + var loadMarkmap = function ($el) { + require([ + '/lib/markmap/transform.min.js', + '/lib/markmap/view.min.js', + ], function (_Transform, _View) { + if (!markmapLoaded) { + console.debug("Loaded markmap"); + MarkMapTransform = _Transform; + Markmap = _View; + markmapLoaded = true; + } + drawMarkmap($el); + pluginLoaded.fire(); }); - }); + }; + + var sfCommon; + var fixMarkmapClickables = function ($svg) { + // find all links in the tree and do the following for each one + var onClick = function (e) { + e.preventDefault(); + e.stopImmediatePropagation(); + var $el = $(e.target); + // Open links only from the preview modal + if (!sfCommon) { return void console.error('No sfCommon'); } + + var href = $el.attr('href'); + if (!href || !/^(https?:\/\/|\/)/.test(href)) { return; } + + if (/^http/.test(href)) { + sfCommon.openUnsafeURL(href); + return; + } + sfCommon.openURL(href); + }; + $svg.find('a').click(onClick); + // make sure the links added later by collapsing/expading the map are also safe + var observer = new MutationObserver(function(mutations) { + mutations.forEach(function(mutation) { + if (mutation.type === 'childList') { + var n; + for (var i = 0; i < mutation.addedNodes.length; i++) { + n = mutation.addedNodes[i]; + if (n.nodeName === "A") { return void n.addEventListener('click', onClick); } + $(n).find('a').click(onClick); + } + } + }); + }); + observer.observe($svg[0], { + childList: true, + subtree: true + }); + }; + + drawMarkmap = function ($el) { + if (!markmapLoaded) { return void loadMarkmap($el); } + if (!$el) { return console.error("no element provided"); } + var data = MarkMapTransform.transform($el[0].getAttribute("markmap-source")); + $el[0].innerHTML = ""; + Markmap.markmap($el[0].firstChild, data); + fixMarkmapClickables($el); + }; var highlighter = function () { return function(code, lang) { @@ -92,9 +189,15 @@ define([ var mediaMap = {}; var defaultCode = renderer.code; + renderer.code = function (code, language) { + if (!code || typeof(code) !== 'string' || !code.trim()) { return defaultCode.apply(renderer, arguments); } if (language === 'mermaid' && code.match(/^(graph|pie|gantt|sequenceDiagram|classDiagram|gitGraph)/)) { - return '
'+Util.fixHTML(code)+'
'; + return '
'+Util.fixHTML(code)+'
'; + } else if (language === 'markmap') { + return '
'+Util.fixHTML(code)+'
'; + } else if (language === 'mathjax') { + return '
'+Util.fixHTML(code)+'
'; } else { return defaultCode.apply(renderer, arguments); } @@ -199,7 +302,6 @@ define([ return renderParagraph(p); }; - var MutationObserver = window.MutationObserver; var forbiddenTags = [ 'SCRIPT', 'IFRAME', @@ -297,6 +399,8 @@ define([ return patch; }; + var plugins = {}; + var removeMermaidClickables = function ($el) { // find all links in the tree and do the following for each one $el.find('a').each(function (index, a) { @@ -313,17 +417,83 @@ define([ // finally, find all 'clickable' items and remove the class $el.find('.clickable').removeClass('clickable'); }; - var renderMermaid = function ($el) { - Mermaid.init(undefined, $el); - // clickable elements in mermaid don't work well with our sandboxing setup - // the function below strips clickable elements but still leaves behind some artifacts - // tippy tooltips might still be useful, so they're not removed. It would be - // preferable to just support links, but this covers up a rough edge in the meantime - removeMermaidClickables($el); + + plugins.mermaid = { + name: 'mermaid', + attr: 'mermaid-source', + render: function ($el) { + Mermaid.init(undefined, $el); + // clickable elements in mermaid don't work well with our sandboxing setup + // the function below strips clickable elements but still leaves behind some artifacts + // tippy tooltips might still be useful, so they're not removed. It would be + // preferable to just support links, but this covers up a rough edge in the meantime + removeMermaidClickables($el); + } }; + plugins.markmap = { + name: 'markmap', + attr: 'markmap-source', + render: function ($el) { + drawMarkmap($el); + } + }; + + plugins.mathjax = { + name: 'mathjax', + attr: 'mathjax-source', + render: function renderMathjax ($el) { + var el = $el[0]; + if (!el) { return; } + var code = el.getAttribute("mathjax-source"); + var svg = Mathjax.tex2svg(code, {display: true}); + if (!svg) { return; } + svg.innerHTML = svg.innerHTML.replace(/xlink:href/g, "href"); + var wrapper = document.createElement('span'); + wrapper.innerHTML = svg.innerHTML; + el.innerHTML = wrapper.outerHTML; + } + }; + + var getAvailableCachedElement = function ($content, cache, src) { + var cached = cache[src]; + if (!Array.isArray(cached)) { return; } + var root = $content[0]; + var l = cached.length; + for (var i = 0; i < l; i++) { + if (!root.contains(cached[i])) { + return cached[i]; + } + } + }; + + var cacheRenderedElement = function (cache, src, el) { + if (Array.isArray(cache[src])) { + cache[src].push(el); + } else { + cache[src] = [ el ]; + } + }; + + // remove elements from the cache that are not embedded in the dom + var clearUnusedCacheEntries = function ($content, plugins) { + var root = $content[0]; + Object.keys(plugins).forEach(function (name) { + var plugin = plugins[name]; + var cache = plugin.cache; + Object.keys(cache).forEach(function (key) { + var list = cache[key]; + if (!Array.isArray(list)) { return; } + cache[key] = list.filter(function (el) { + return root.contains(el); + }); + }); + }); + }; DiffMd.apply = function (newHtml, $content, common) { + if (!sfCommon) { sfCommon = common; } + var contextMenu = common.importMediaTagMenu(); var id = $content.attr('id'); if (!id) { throw new Error("The element must have a valid id"); } @@ -343,8 +513,10 @@ define([ var Dom = domFromHTML($('
').append($div).html()); $content[0].normalize(); - var mermaid_source = []; - var mermaid_cache = {}; + Object.keys(plugins).forEach(function (id) { + plugins[id].source = []; + plugins[id].cache = {}; + }); var canonicalizeMermaidSource = function (src) { // ignore changes to empty lines, since that won't affect @@ -352,12 +524,15 @@ define([ return src.replace(/\n[ \t]*\n*[ \t]*\n/g, '\n'); }; - // iterate over the unrendered mermaid inputs, caching their source as you go - $(newDomFixed).find('pre.mermaid').each(function (index, el) { + // iterate over the unrendered mermaid and markmap inputs, + // caching their source as you go + $(newDomFixed).find('pre[data-plugin]').each(function (index, el) { if (el.childNodes.length === 1 && el.childNodes[0].nodeType === 3) { + var plugin = plugins[el.getAttribute('data-plugin')]; + if (!plugin) { return; } var src = canonicalizeMermaidSource(el.childNodes[0].wholeText); - el.setAttribute('mermaid-source', src); - mermaid_source[index] = src; + el.setAttribute(plugin.attr, src); + plugin.source[index] = src; } }); @@ -365,18 +540,21 @@ define([ var $parent = $content.parent(); var scrollTop = $parent.scrollTop(); // iterate over rendered mermaid charts - $content.find('pre.mermaid:not([processed="true"])').each(function (index, el) { + $content.find('pre[data-plugin]:not([processed="true"])').each(function (index, el) { + var plugin = plugins[el.getAttribute('data-plugin')]; + if (!plugin) { return; } + // retrieve the attached source code which it was drawn - var src = el.getAttribute('mermaid-source'); + var src = el.getAttribute(plugin.attr); /* The new source might have syntax errors that will prevent rendering. It might be preferable to keep the existing state instead of removing it if you don't have something better to display. Ideally we should display the cause of the syntax error so that the user knows what to correct. */ - //if (!Mermaid.parse(src)) { } // TODO + //if (plugin.name === "mermaid" && !Mermaid.parse(src)) { } // TODO // check if that source exists in the set of charts which are about to be rendered - if (mermaid_source.indexOf(src) === -1) { + if (plugin.source.indexOf(src) === -1) { // if it's not, then you can remove it if (el.parentNode && el.parentNode.children.length) { el.parentNode.removeChild(el); @@ -384,26 +562,30 @@ define([ } else if (el.childNodes.length === 1 && el.childNodes[0].nodeType !== 3) { // otherwise, confirm that the content of the rendered chart is not a text node // and keep a copy of it - mermaid_cache[src] = el.childNodes[0]; + cacheRenderedElement(plugin.cache, src, el.childNodes[0]); } }); var oldDom = domFromHTML($content[0].outerHTML); + var MutationObserver = window.MutationObserver; var onPreview = function ($mt) { return function () { - var isSvg = $mt.is('pre.mermaid'); var mts = []; - $content.find('media-tag, pre.mermaid').each(function (i, el) { + // Get all previewable elements from the doc + $content.find('media-tag, pre[data-plugin]').each(function (i, el) { if (el.nodeName.toLowerCase() === "pre") { var clone = el.cloneNode(); + var plugin = plugins[el.getAttribute('data-plugin')]; + if (!plugin) { return; } + return void mts.push({ svg: clone, render: function () { var $el = $(clone); - $el.text(clone.getAttribute('mermaid-source')); + $el.text(clone.getAttribute(plugin.attr)); $el.attr('data-processed', ''); - renderMermaid($el); + plugin.render($el); } }); } @@ -415,9 +597,16 @@ define([ }); // Find initial position + + // If the element is supported by one of our plugin types + // (mermaid, mathjax, or markmap) get the corresponding attribute + var isSvg = $mt.is('pre[data-plugin]'); + var plugin = isSvg && plugins[$mt.attr('data-plugin')]; + + // Get initial idx var idx = -1; mts.some(function (obj, i) { - if (isSvg && $mt.attr('mermaid-source') === $(obj.svg).attr('mermaid-source')) { + if (isSvg && $mt.attr(plugin.attr) === $(obj.svg).attr(plugin.attr)) { idx = i; return true; } @@ -426,16 +615,17 @@ define([ return true; } }); + // Not found, re-render if (idx === -1) { - if (isSvg) { + if (isSvg && $mt.attr(plugin.attr)) { var clone = $mt[0].cloneNode(); mts.unshift({ svg: clone, render: function () { var $el = $(clone); - $el.text(clone.getAttribute('mermaid-source')); + $el.text(clone.getAttribute(plugin.attr)); $el.attr('data-processed', ''); - renderMermaid($el); + plugin.render($el); } }); } else { @@ -511,8 +701,10 @@ define([ if (target) { target.scrollIntoView(); } }); - // loop over mermaid elements in the rendered content - $content.find('pre.mermaid').each(function (index, el) { + // loop over plugin elements in the rendered content + $content.find('pre[data-plugin]').each(function (index, el) { + var plugin = plugins[el.getAttribute('data-plugin')]; + if (!plugin) { return; } var $el = $(el); $el.off('contextmenu').on('contextmenu', function (e) { e.preventDefault(); @@ -529,14 +721,14 @@ define([ // since you've simply drawn the content that was supplied via markdown // you can assume that the index of your rendered charts matches that // of those in the markdown source. - var src = mermaid_source[index]; - el.setAttribute('mermaid-source', src); - var cached = mermaid_cache[src]; + var src = plugin.source[index]; + el.setAttribute(plugin.attr, src); + var cached = getAvailableCachedElement($content, plugin.cache, src); // check if you had cached a pre-rendered instance of the supplied source if (typeof(cached) !== 'object') { try { - renderMermaid($el); + plugin.render($el); } catch (e) { console.error(e); } return; } @@ -551,6 +743,8 @@ define([ el.setAttribute('data-processed', true); }); } + clearUnusedCacheEntries($content, plugins); + // recover the previous scroll position to avoid jank $parent.scrollTop(scrollTop); }; diff --git a/www/common/drive-ui.js b/www/common/drive-ui.js index 9643a4b1b..bff6bac4d 100644 --- a/www/common/drive-ui.js +++ b/www/common/drive-ui.js @@ -86,7 +86,7 @@ define([ var faColor = 'cptools-palette'; var faTrash = 'fa-trash'; var faCopy = 'fa-clone'; - var faDelete = 'fa-eraser'; + var faDelete = 'cptools-destroy'; var faAccess = 'fa-unlock-alt'; var faProperties = 'fa-info-circle'; var faTags = 'fa-hashtag'; @@ -129,6 +129,7 @@ define([ //var $ownerIcon = $('', {"class": "fa fa-id-card"}); var $tagsIcon = $('', {"class": "fa " + faTags}); var $passwordIcon = $('', {"class": "fa fa-lock"}); + var $restrictedIcon = $('', {"class": "fa fa-ban"}); var $expirableIcon = $('', {"class": "fa fa-clock-o"}); var $separator = $('
', {"class": "dropdown-divider"}); @@ -233,9 +234,9 @@ define([ return LS; }; - var getViewModeClass = function () { + var getViewModeClass = function (forceList) { var mode = APP.store[LS_VIEWMODE]; - if (mode === 'list') { return 'cp-app-drive-content-list'; } + if (mode === 'list' || forceList) { return 'cp-app-drive-content-list'; } return 'cp-app-drive-content-grid'; }; var getViewMode = function () { @@ -450,15 +451,15 @@ define([ h('li', h('a.cp-app-drive-context-deleteowned.dropdown-item.cp-app-drive-context-editable', { 'tabindex': '-1', 'data-icon': faDelete, - }, Messages.fc_delete_owned)), // XXX update key? "Delete from the server" + }, Messages.fc_delete_owned)), h('li', h('a.cp-app-drive-context-remove.dropdown-item.cp-app-drive-context-editable', { 'tabindex': '-1', 'data-icon': faTrash, - }, Messages.fc_remove)), // XXX update key? "Remove from your CryptDrive" + }, Messages.fc_remove)), h('li', h('a.cp-app-drive-context-removesf.dropdown-item.cp-app-drive-context-editable', { 'tabindex': '-1', 'data-icon': faTrash, - }, Messages.fc_remove_sharedfolder)), // XXX update key? "Remove" + }, Messages.fc_remove_sharedfolder)), $separator.clone()[0], h('li', h('a.cp-app-drive-context-properties.dropdown-item', { 'tabindex': '-1', @@ -1211,6 +1212,9 @@ define([ if (!$element.is('.cp-app-drive-element-owned')) { hide.push('deleteowned'); } + if ($element.is('.cp-app-drive-element-restricted')) { + hide.push('rename', 'download', 'share', 'access', 'color'); + } if ($element.is('.cp-app-drive-element-notrash')) { // We can't delete elements in virtual categories hide.push('delete'); @@ -1726,7 +1730,9 @@ define([ && $target.parents('#cp-app-drive-content')) { newPath = currentPath; } - if (newPath[0] !== ROOT) { newPath = [ROOT]; } + // XXX Why did we add the following line? + // https://github.com/xwiki-labs/cryptpad/commit/f103a0fb08d137901174ef7e15dbc2c9a2ec3ca1 + //if (newPath[0] !== ROOT) { newPath = [ROOT]; } return newPath; }; var onFileDrop = APP.onFileDrop = function (file, e) { @@ -1949,7 +1955,8 @@ define([ var $ro; if (manager.isSharedFolder(element)) { var data = manager.getSharedFolderData(element); - key = data && data.title ? data.title : key; + var fId = element; + key = data.title || data.lastTitle; element = manager.folders[element].proxy[manager.user.userObject.ROOT]; $span.addClass('cp-app-drive-element-sharedf'); _addOwnership($span, $state, data); @@ -1964,6 +1971,11 @@ define([ $ro.attr('title', Messages.readonly); } + if (files.restrictedFolders[fId]) { + var $restricted = $restrictedIcon.clone().appendTo($state); + $restricted.attr('title', Messages.fm_restricted); + } + var $shared = $sharedIcon.clone().appendTo($state); $shared.attr('title', Messages.fm_canBeShared); } else if ($content.data('readOnlyFolder') || APP.readOnly) { @@ -1972,14 +1984,14 @@ define([ } var sf = manager.hasSubfolder(element); - var files = manager.hasFile(element); + var hasFiles = manager.hasFile(element); var $name = $('', {'class': 'cp-app-drive-element-name'}).text(key); var $subfolders = $('', { 'class': 'cp-app-drive-element-folders cp-app-drive-element-list' }).text(sf); var $files = $('', { 'class': 'cp-app-drive-element-files cp-app-drive-element-list' - }).text(files); + }).text(hasFiles); var $filler = $('', { 'class': 'cp-app-drive-element-filler cp-app-drive-element-list' }); @@ -2051,32 +2063,38 @@ define([ element = root[key]; } + var restricted = files.restrictedFolders[element]; var isSharedFolder = manager.isSharedFolder(element); var $icon = !isFolder ? getFileIcon(element) : undefined; var ro = manager.isReadOnlyFile(element); // ro undefined means it's an old hash which doesn't support read-only - var roClass = typeof(ro) === 'undefined' ?' cp-app-drive-element-noreadonly' : - ro ? ' cp-app-drive-element-readonly' : ''; - var liClass = 'cp-app-drive-element-file cp-app-drive-element' + roClass; + var roClass = typeof(ro) === 'undefined' ? '.cp-app-drive-element-noreadonly' : + ro ? '.cp-app-drive-element-readonly' : ''; + var liClass = '.cp-app-drive-element-file'; + var restrictedClass = restricted ? '.cp-app-drive-element-restricted' : ''; if (isSharedFolder) { - liClass = 'cp-app-drive-element-folder cp-app-drive-element'; + liClass = '.cp-app-drive-element-folder'; $icon = $sharedFolderIcon.clone(); $icon.css("color", getFolderColor(path.concat(elPath))); } else if (isFolder) { - liClass = 'cp-app-drive-element-folder cp-app-drive-element'; + liClass = '.cp-app-drive-element-folder'; $icon = manager.isFolderEmpty(root[key]) ? $folderEmptyIcon.clone() : $folderIcon.clone(); $icon.css("color", getFolderColor(path.concat(elPath))); } - var $element = $('
  • ', { - draggable: true, - 'class': 'cp-app-drive-element-row' - }); + var classes = restrictedClass + roClass + liClass; + var $element = $(h('li.cp-app-drive-element.cp-app-drive-element-row' + classes, { + draggable: true + })); $element.data('path', newPath); if (isElementSelected($element)) { selectElement($element); } $element.prepend($icon).dblclick(function () { + if (restricted) { + UI.warn(Messages.fm_restricted); + return; + } if (isFolder) { APP.displayDirectory(newPath); return; @@ -2100,8 +2118,7 @@ define([ } e.stopPropagation(); }); - $element.addClass(liClass); - var droppable = !isTrash && !APP.$content.data('readOnlyFolder'); + var droppable = !isTrash && !APP.$content.data('readOnlyFolder') && !restricted; addDragAndDropHandlers($element, newPath, isFolder, droppable); $element.click(function(e) { e.stopPropagation(); @@ -2382,11 +2399,10 @@ define([ var emptyTrashModal = function () { var ownedInTrash = manager.ownedInTrash(); var hasOwned = Array.isArray(ownedInTrash) && ownedInTrash.length; - Messages.fm_emptyTrashOwned = "Your trash contains documents you own. You can remove them for everyone or only from your drive"; // XXX var content = h('p', [ Messages.fm_emptyTrashDialog, hasOwned ? h('br') : undefined, - hasOwned ? Messages.fm_emptyTrashOwned : undefined // XXX update UI? + hasOwned ? UI.setHTML(h('span'), Messages.fm_emptyTrashOwned) : undefined ]); var buttons = [{ className: 'cancel', @@ -2396,7 +2412,8 @@ define([ }]; if (hasOwned) { buttons.push({ - className: 'secondary', + className: 'danger', + iconClass: '.cptools.cptools-destroy', name: Messages.fc_delete_owned, onClick: function () { manager.emptyTrash(true, refresh); @@ -2406,8 +2423,8 @@ define([ } buttons.push({ className: 'primary', - // XXX fc_remove: Remove from your CryptDrive // We may want to use a new key here + iconClass: '.fa.fa-trash', name: hasOwned ? Messages.fc_remove : Messages.okButton, onClick: function () { manager.emptyTrash(false, refresh); @@ -2887,10 +2904,11 @@ define([ var el = useId ? _el : root[_el]; var sfId = (el && el.root && el.key) ? el.root[el.key] : el; if (folder && el && manager.isSharedFolder(sfId)) { - var title = manager.getSharedFolderData(sfId).title || el; + var sfData = manager.getSharedFolderData(sfId); + var title = sfData.title || sfData.lastTitle || el; return String(title).toLowerCase(); } else if (folder) { - return String((el && el.key) || el).toLowerCase(); + return String((el && el.key) || _el).toLowerCase(); } var data = manager.getFileData(el); if (!data) { return ''; } @@ -3014,7 +3032,7 @@ define([ if (APP.$content.data('readOnlyFolder') || !APP.editable) { return; } var isInRoot = currentPath[0] === ROOT; var $element = $('
  • ', { - 'class': 'cp-app-drive-element-row cp-app-drive-element-grid cp-app-drive-new-ghost' + 'class': 'cp-app-drive-element-row cp-app-drive-new-ghost' }).prepend($addIcon.clone()).appendTo($list); $element.append($('', {'class': 'cp-app-drive-element-name'}) .text(Messages.fm_newFile)); @@ -3131,7 +3149,10 @@ define([ return; } var allfiles = files[FILES_DATA]; - if (allfiles.length === 0) { return; } + if (Object.keys(allfiles || {}).length === 0) { + createGhostIcon($container); + return; + } var $fileHeader = getFileListHeader(true); $container.append($fileHeader); var keys = manager.getFiles([FILES_DATA]); @@ -3211,18 +3232,26 @@ define([ $searchIcon.clone().appendTo($div); + var $spinnerContainer = $(h('div.cp-app-drive-search-spinner')); + var spinner = UI.makeSpinner($spinnerContainer); var $input = APP.Search.$input = $('', { id: 'cp-app-drive-search-input', + placeholder: Messages.fm_searchName, type: 'text', draggable: false, tabindex: 1, }).keyup(function (e) { + var lastValue = search.value; + search.value = $input.val().trim(); + if (lastValue === search.value) { return; } + if (search.to) { window.clearTimeout(search.to); } - if ($input.val().trim() === "") { + if (search.value === "") { search.cursor = 0; APP.displayDirectory([SEARCH]); return; } + spinner.spin(); if (e.which === 13) { var newLocation = [SEARCH, $input.val()]; search.cursor = $input[0].selectionStart; @@ -3267,73 +3296,89 @@ define([ $div.append(cancel); $list.append($div); + $spinnerContainer.appendTo($list); setTimeout(function () { $input.focus(); }); - $list.closest('#cp-app-drive-content-folder').addClass('cp-app-drive-content-list'); - var filesList = manager.search(value); - var sortable = {}; - var sortableFolders = []; - filesList.forEach(function (r) { - // if r.id === null, then it's a folder, not a file - r.paths.forEach(function (path) { - if (!r.inSharedFolder && - APP.hideDuplicateOwned && manager.isDuplicateOwned(path)) { return; } - var _path = path.slice(); - var key = path.pop(); - var root = manager.find(path); - var obj = { - path: path, - _path: _path, - key: key, - root: root, - data: r.data - }; - if (manager.isFolder(root[key])) { - sortableFolders.push(obj); - return; - } - sortable[root[key]] = obj; + if (typeof(value) === "string" && value.trim()) { + spinner.spin(); + } else { + return; + } + + setTimeout(function () { + //$list.closest('#cp-app-drive-content-folder').addClass('cp-app-drive-content-list'); + var filesList = manager.search(value); + if (!filesList.length) { + Messages.fm_noResult = "No result found"; // XXX + $list.append(h('div.cp-app-drive-search-noresult', Messages.fm_noResult)); + spinner.hide(); + return; + } + var sortable = {}; + var sortableFolders = []; + filesList.forEach(function (r) { + // if r.id === null, then it's a folder, not a file + r.paths.forEach(function (path) { + if (!r.inSharedFolder && + APP.hideDuplicateOwned && manager.isDuplicateOwned(path)) { return; } + var _path = path.slice(); + var key = path.pop(); + var root = manager.find(path); + var obj = { + path: path, + _path: _path, + key: key, + root: root, + data: r.data + }; + if (manager.isFolder(root[key])) { + sortableFolders.push(obj); + return; + } + sortable[root[key]] = obj; + }); }); - }); - var _folders = sortElements(true, [ROOT], sortableFolders, null, !getSortFolderDesc(), true); - var sortableKeys = Object.keys(sortable).map(Number); - var _files = sortElements(false, [ROOT], sortableKeys, APP.store[SORT_FILE_BY], !getSortFileDesc(), true); + var _folders = sortElements(true, [ROOT], sortableFolders, null, !getSortFolderDesc(), true); + var sortableKeys = Object.keys(sortable).map(Number); + var _files = sortElements(false, [ROOT], sortableKeys, APP.store[SORT_FILE_BY], !getSortFileDesc(), true); - var addEl = function (obj, folder) { - var $element = createElement(obj.path, obj.key, obj.root, folder); - $element.addClass('cp-app-drive-element-notrash cp-app-drive-search-result'); - $element.off('contextmenu'); - $element.contextmenu(openContextMenu('default')); - $element.data('context', 'default'); - if (folder) { - $element.find('.cp-app-drive-element-list').css({ - visibility: 'hidden' - }).text(''); - } - if (manager.isPathIn(obj._path, ['hrefArray'])) { - obj._path.pop(); - obj._path.push(obj.data.title); - } - var $path = $('', { - 'class': 'cp-app-drive-search-path' - }).appendTo($element.find('.cp-app-drive-element-name')); - createTitle($path, obj._path); + var addEl = function (obj, folder) { + var $element = createElement(obj.path, obj.key, obj.root, folder); + $element.addClass('cp-app-drive-element-notrash cp-app-drive-search-result'); + $element.off('contextmenu'); + $element.contextmenu(openContextMenu('default')); + $element.data('context', 'default'); + if (folder) { + $element.find('.cp-app-drive-element-list').css({ + visibility: 'hidden' + }).text(''); + } + if (manager.isPathIn(obj._path, ['hrefArray'])) { + obj._path.pop(); + obj._path.push(obj.data.title); + } + var $path = $('', { + 'class': 'cp-app-drive-search-path' + }).appendTo($element.find('.cp-app-drive-element-name')); + createTitle($path, obj._path); - $list.append($element); - }; - if (_folders.length) { getFolderListHeader(true, true).appendTo($list); } - _folders.forEach(function (el) { - var obj = el; - addEl(obj, true); + $list.append($element); + }; + if (_folders.length) { getFolderListHeader(true, true).appendTo($list); } + _folders.forEach(function (el) { + var obj = el; + addEl(obj, true); + }); + if (_files.length) { getFileListHeader(true).appendTo($list); } + _files.forEach(function (el) { + var obj = sortable[el]; + addEl(obj, false); + }); + setTimeout(collapseDrivePath); + spinner.hide(); }); - if (_files.length) { getFileListHeader(true).appendTo($list); } - _files.forEach(function (el) { - var obj = sortable[el]; - addEl(obj, false); - }); - setTimeout(collapseDrivePath); }; var displayRecent = function ($list) { @@ -3589,17 +3634,23 @@ define([ var $dirContent = $('
    ', {id: FOLDER_CONTENT_ID}); $dirContent.data('path', path); - if (!isSearch && !isTags) { - var mode = getViewMode(); - if (mode) { - $dirContent.addClass(getViewModeClass()); + if (!isTags) { + $dirContent.addClass(getViewModeClass(isSearch)); + if (!isSearch) { + createViewModeButton(APP.toolbar.$bottomR); } - createViewModeButton(APP.toolbar.$bottomR); } var $list = $('
      ').appendTo($dirContent); var sfId = manager.isInSharedFolder(currentPath); + + // Restricted folder? display ROOT instead + if (sfId && files.restrictedFolders[sfId]) { + _displayDirectory([ROOT], true); + return; + } + var readOnlyFolder = false; if (APP.readOnly) { // Read-only drive (team?) @@ -3753,8 +3804,15 @@ define([ } var $elementRow = $('', {'class': 'cp-app-drive-element-row'}).append($collapse).append($icon).append($name).click(function (e) { e.stopPropagation(); + if (files.restrictedFolders[isSharedFolder]) { + UI.warn(Messages.fm_restricted); + return; + } APP.displayDirectory(path); }); + if (files.restrictedFolders[isSharedFolder]) { + $elementRow.addClass('cp-app-drive-element-restricted'); + } if (isSharedFolder) { var sfData = manager.getSharedFolderData(isSharedFolder); _addOwnership($elementRow, $(), sfData); @@ -3837,7 +3895,7 @@ define([ if (!manager.isFolder(root[key])) { return; } var newPath = path.slice(); newPath.push(key); - var isSharedFolder = manager.isSharedFolder(root[key]); + var isSharedFolder = manager.isSharedFolder(root[key]) && root[key]; var sfId = manager.isInSharedFolder(newPath) || (isSharedFolder && root[key]); var $icon, isCurrentFolder, subfolder; if (isSharedFolder) { @@ -4130,6 +4188,10 @@ define([ else if ($this.hasClass('cp-app-drive-context-open')) { paths.forEach(function (p) { var el = manager.find(p.path); + if (files.restrictedFolders[el]) { + UI.warn(Messages.fm_restricted); + return; + } openFile(el, false, true); }); } @@ -4676,7 +4738,10 @@ define([ var ok = manager.isValidDrive(obj.drive); if (!ok) { return; } + var restricted = files.restrictedFolders; copyObjectValue(files, obj.drive); + files.restrictedFolders = restricted; + appStatus.isReady = true; refresh(); }; @@ -4715,7 +4780,6 @@ define([ }); } */ - var deprecated = files.sharedFoldersTemp; var nt = nThen; var passwordModal = function (fId, data, cb) { var content = []; @@ -4771,6 +4835,7 @@ define([ onClose: cb }); }; + var deprecated = files.sharedFoldersTemp; if (typeof (deprecated) === "object" && APP.editable && Object.keys(deprecated).length) { Object.keys(deprecated).forEach(function (fId) { var data = deprecated[fId]; @@ -4787,7 +4852,6 @@ define([ }); } - return { refresh: refresh, close: function () { diff --git a/www/common/inner/access.js b/www/common/inner/access.js index ad9f28a94..2e1f4b4a8 100644 --- a/www/common/inner/access.js +++ b/www/common/inner/access.js @@ -372,8 +372,7 @@ define([ var parsed = Hash.parsePadUrl(data.href || data.roHref); var owned = Modal.isOwned(Env, data); var disabled = !owned || !parsed.hashData || parsed.hashData.type !== 'pad'; - var allowDisabled = parsed.type === 'drive'; - if (disabled || allowDisabled) { return void cb(); } + if (disabled) { return void cb(); } opts = opts || {}; @@ -899,7 +898,7 @@ define([ } } if (owned) { - var deleteOwned = h('button.btn.btn-danger-alt', Messages.fc_delete_owned); + var deleteOwned = h('button.btn.btn-danger-alt', [h('i.cptools.cptools-destroy'), Messages.fc_delete_owned]); var spinner = UI.makeSpinner(); UI.confirmButton(deleteOwned, { classes: 'btn-danger' @@ -910,6 +909,7 @@ define([ channel: data.channel }, function (err, obj) { spinner.done(); + UI.findCancelButton().click(); if (err || (obj && obj.error)) { UI.warn(Messages.error); } }); }); diff --git a/www/common/inner/common-mediatag.js b/www/common/inner/common-mediatag.js index 9009d2557..0fc35c1c3 100644 --- a/www/common/inner/common-mediatag.js +++ b/www/common/inner/common-mediatag.js @@ -256,7 +256,7 @@ define([ } // Reset modal - $inner.find('media-tag, pre.mermaid').detach(); + $inner.find('media-tag, pre[data-plugin]').detach(); $spinner.show(); // Check src and cryptkey diff --git a/www/common/onlyoffice/inner.js b/www/common/onlyoffice/inner.js index 3c933bf10..6e138fcdc 100644 --- a/www/common/onlyoffice/inner.js +++ b/www/common/onlyoffice/inner.js @@ -1439,6 +1439,7 @@ define([ }, { typeInput: $select[0] }, true); + $select.find('button').addClass('btn'); }; var x2tImportImagesInternal = function(x2t, images, i, callback) { diff --git a/www/common/outer/async-store.js b/www/common/outer/async-store.js index c6e588672..521b37bbe 100644 --- a/www/common/outer/async-store.js +++ b/www/common/outer/async-store.js @@ -2054,6 +2054,10 @@ define([ var addSharedFolderHandler = function () { store.sharedFolders = {}; store.handleSharedFolder = function (id, rt) { + if (!rt) { + delete store.sharedFolders[id]; + return; + } store.sharedFolders[id] = rt; if (store.driveEvents) { registerProxyEvents(rt.proxy, id); @@ -2083,6 +2087,9 @@ define([ Store.addSharedFolder = function (clientId, data, cb) { var s = getStore(data.teamId); s.manager.addSharedFolder(data, function (id) { + if (id && typeof(id) === "object" && id.error) { + return void cb(id); + } var send = data.teamId ? s.sendEvent : sendDriveEvent; send('DRIVE_CHANGE', { path: ['drive', UserObject.FILES_DATA] @@ -2188,6 +2195,8 @@ define([ }); }; registerProxyEvents = function (proxy, fId) { + if (!proxy) { return; } + if (proxy.deprecated || proxy.restricted) { return; } if (!fId) { // Listen for shared folder password change proxy.on('change', ['drive', UserObject.SHARED_FOLDERS], function (o, n, p) { diff --git a/www/common/outer/sharedfolder.js b/www/common/outer/sharedfolder.js index cbeae1cbd..f02ea9c5a 100644 --- a/www/common/outer/sharedfolder.js +++ b/www/common/outer/sharedfolder.js @@ -211,6 +211,9 @@ define([ // We can only hide it sf.teams.forEach(function (obj) { obj.store.manager.deprecateProxy(obj.id, secret.channel); + if (obj.store.handleSharedFolder) { + obj.store.handleSharedFolder(obj.id, null); + } }); } catch (e) {} delete allSharedFolders[secret.channel]; @@ -225,6 +228,7 @@ define([ sf.teams.forEach(function (obj) { obj.store.manager.restrictedProxy(obj.id, secret.channel); }); + delete allSharedFolders[secret.channel]; return void cb(); } } @@ -251,9 +255,14 @@ define([ if (!sf) { return; } var clients = sf.teams; if (!Array.isArray(clients)) { return; } + // Remove the shared folder from the client's store and + // remove the client/team from our list var idx; clients.some(function (obj, i) { if (obj.store.id === teamId) { + if (obj.store.handleSharedFolder) { + obj.store.handleSharedFolder(obj.id, null); + } idx = i; return true; } diff --git a/www/common/outer/team.js b/www/common/outer/team.js index 9e59410db..7c3398189 100644 --- a/www/common/outer/team.js +++ b/www/common/outer/team.js @@ -164,6 +164,10 @@ define([ var handleSharedFolder = function (ctx, id, sfId, rt) { var t = ctx.teams[id]; if (!t) { return; } + if (!rt) { + delete t.sharedFolders[sfId]; + return; + } t.sharedFolders[sfId] = rt; registerChangeEvents(ctx, t, rt.proxy, sfId); }; diff --git a/www/common/proxy-manager.js b/www/common/proxy-manager.js index 8054e83f7..bf7af8d39 100644 --- a/www/common/proxy-manager.js +++ b/www/common/proxy-manager.js @@ -52,6 +52,10 @@ define([ // Password may have changed var deprecateProxy = function (Env, id, channel) { + if (Env.folders[id] && Env.folders[id].deleting) { + // Folder is being deleted by its owner, don't deprecate it + return; + } if (Env.user.userObject.readOnly) { // In a read-only team, we can't deprecate a shared folder // Use a empty object with a deprecated flag... @@ -69,7 +73,7 @@ define([ }; var restrictedProxy = function (Env, id) { - var lm = { proxy: { deprecated: true } }; + var lm = { proxy: { restricted: true, root: {}, filesData: {} } }; removeProxy(Env, id); addProxy(Env, id, lm, function () {}); return void Env.Store.refreshDriveUI(); @@ -215,7 +219,7 @@ define([ if (!Env.folders[id]) { return {}; } var obj = Env.folders[id].proxy.metadata || {}; for (var k in Env.user.proxy[UserObject.SHARED_FOLDERS][id] || {}) { - var data = JSON.parse(JSON.stringify(Env.user.proxy[UserObject.SHARED_FOLDERS][id][k])); + var data = Util.clone(Env.user.proxy[UserObject.SHARED_FOLDERS][id][k] || {}); if (k === "href" && data.indexOf('#') === -1) { try { data = Env.user.userObject.cryptor.decrypt(data); @@ -491,6 +495,17 @@ define([ }; if (data.password) { folderData.password = data.password; } if (data.owned) { folderData.owners = [Env.edPublic]; } + }).nThen(function (waitFor) { + Env.Store.getPadMetadata(null, { + channel: folderData.channel + }, waitFor(function (obj) { + if (obj && (obj.error || obj.rejected)) { + waitFor.abort(); + return void cb({ + error: obj.error || 'ERESTRICTED' + }); + } + })); }).nThen(function (waitFor) { Env.pinPads([folderData.channel], waitFor()); }).nThen(function (waitFor) { @@ -812,19 +827,38 @@ define([ var data = uo.isFile(el) ? uo.getFileData(el) : getSharedFolderData(Env, el); chan = data.channel; } + // If the pad was a shared folder, delete it too and leave it + var fId; + Object.keys(Env.user.proxy[UserObject.SHARED_FOLDERS] || {}).some(function (id) { + var sfData = Env.user.proxy[UserObject.SHARED_FOLDERS][id] || {}; + if (sfData.channel === chan) { + fId = Number(id); + Env.folders[id].deleting = true; + return true; + } + }); Env.removeOwnedChannel(chan, function (obj) { // If the error is that the file is already removed, nothing to // report, it's a normal behavior (pad expired probably) if (obj && obj.error && obj.error.code !== "ENOENT") { // RPC may not be responding // Send a report that can be handled manually + if (fId && Env.folders[fId] && Env.folders[fId].deleting) { + delete Env.folders[fId].deleting; + } console.error(obj.error, chan); Feedback.send('ERROR_DELETING_OWNED_PAD=' + chan + '|' + obj.error, true); return void cb(); } - // No error: delete the pads and all its copies from our drive and shared folders + // No error: delete the pad and all its copies from our drive and shared folders var ids = _findChannels(Env, [chan]); + + // If the pad was a shared folder, delete it too and leave it + if (fId) { + ids.push(fId); + } + ids.forEach(function (id) { var paths = findFile(Env, id); var _resolved = _resolvePaths(Env, paths); @@ -858,6 +892,10 @@ define([ }).nThen(function () { // Remove deleted pads from the drive _delete(Env, { resolved: toDelete }, cb); + // If we were using the access modal, send a refresh command + if (data.channel) { + Env.Store.refreshDriveUI(); + } }); }; @@ -915,9 +953,8 @@ define([ if (!resolved.id) { var el = Env.user.userObject.find(resolved.path); if (Env.user.userObject.isSharedFolder(el) && Env.folders[el]) { - var oldName = Env.folders[el].proxy.metadata.title; Env.folders[el].proxy.metadata.title = data.newName; - Env.user.proxy[UserObject.SHARED_FOLDERS][el].lastTitle = oldName; + Env.user.proxy[UserObject.SHARED_FOLDERS][el].lastTitle = data.newName; return void cb(); } } diff --git a/www/common/sframe-common-codemirror.js b/www/common/sframe-common-codemirror.js index c3e3b2566..e04dc9c2e 100644 --- a/www/common/sframe-common-codemirror.js +++ b/www/common/sframe-common-codemirror.js @@ -75,6 +75,21 @@ define([ } }; + module.handleImagePaste = function (editor) { + // Don't paste file path in the users wants to paste a file + editor.on('paste', function (editor, ev) { + try { + if (!ev.clipboardData.items) { return; } + var items = Array.prototype.slice.apply(ev.clipboardData.items); + var hasFile = items.some(function (el) { + return el.kind === "file"; + }); + if (!hasFile) { return; } + ev.preventDefault(); + } catch (e) { console.error(e); } + }); + }; + module.getHeadingText = function (editor) { var lines = editor.getValue().split(/\n/); @@ -234,6 +249,8 @@ define([ editor.scrollIntoView(cursor); }); + module.handleImagePaste(editor); + var setMode = exp.setMode = function (mode, cb) { exp.highlightMode = mode; if (mode === 'markdown') { mode = 'gfm'; } diff --git a/www/common/sframe-common-file.js b/www/common/sframe-common-file.js index 90ca5e256..c9e2eeacd 100644 --- a/www/common/sframe-common-file.js +++ b/www/common/sframe-common-file.js @@ -528,6 +528,26 @@ define([ $hoverArea.removeClass('cp-fileupload-hovering'); onFileDrop(dropped, e); }); + + + // Upload files on paste + $area.on('paste', function (e) { + try { + var ev = e.originalEvent; + if (!ev.clipboardData.items) { return; } + var items = Array.prototype.slice.apply(ev.clipboardData.items); + var hasFile = items.some(function (el) { + return el.kind === "file"; + }); + if (!hasFile) { return; } + ev.preventDefault(); + items.forEach(function (el) { + if (el.kind !== "file") { return; } + var file = el.getAsFile(); + handleFile(file, e); + }); + } catch (err) { console.error(err); } + }); }; var createCkeditorDropHandler = function () { var editor = config.ckeditor; diff --git a/www/common/sframe-common-outer.js b/www/common/sframe-common-outer.js index 5689f6284..7253d8f1a 100644 --- a/www/common/sframe-common-outer.js +++ b/www/common/sframe-common-outer.js @@ -28,6 +28,7 @@ define([ var Test; var password; var initialPathInDrive; + var burnAfterReading; var currentPad = window.CryptPad_location = { app: '', @@ -171,6 +172,8 @@ define([ }); var parsed = Utils.Hash.parsePadUrl(currentPad.href); + burnAfterReading = parsed && parsed.hashData && parsed.hashData.ownerKey; + currentPad.app = parsed.type; if (cfg.getSecrets) { var w = waitFor(); @@ -376,9 +379,27 @@ define([ })); }).nThen(done); } + }).nThen(function (waitFor) { + if (!burnAfterReading) { return; } + + // This is a burn after reading URL: make sure our owner key is still valid + try { + var publicKey = Utils.Hash.getSignPublicFromPrivate(burnAfterReading); + Cryptpad.getPadMetadata({ + channel: secret.channel + }, waitFor(function (md) { + if (md && md.error) { return console.error(md.error); } + // If our key is not valid anymore, don't show BAR warning + if (!(md && Array.isArray(md.owners)) || md.owners.indexOf(publicKey) === -1) { + burnAfterReading = null; + } + })); + } catch (e) { + console.error(e); + } }).nThen(function (waitFor) { if (cfg.afterSecrets) { - cfg.afterSecrets(Cryptpad, Utils, secret, waitFor()); + cfg.afterSecrets(Cryptpad, Utils, secret, waitFor(), sframeChan); } }).nThen(function (waitFor) { // Check if the pad exists on server @@ -402,7 +423,6 @@ define([ } Utils.crypto = Utils.Crypto.createEncryptor(Utils.secret.keys); var parsed = Utils.Hash.parsePadUrl(currentPad.href); - var burnAfterReading = parsed && parsed.hashData && parsed.hashData.ownerKey; if (!parsed.type) { throw new Error(); } var defaultTitle = Utils.UserObject.getDefaultName(parsed); var edPublic, curvePublic, notifications, isTemplate; @@ -1600,7 +1620,16 @@ define([ // server Cryptpad.useTemplate({ href: data.template - }, Cryptget, function () { + }, Cryptget, function (err) { + if (err) { + // TODO: better messages in case of expired, deleted, etc.? + if (err === 'ERESTRICTED') { + sframeChan.event('EV_RESTRICTED_ERROR'); + } else { + sframeChan.query("EV_LOADING_ERROR", "DELETED"); + } + return; + } startRealtime(); cb(); }, cryptputCfg); @@ -1608,7 +1637,16 @@ define([ } // if we open a new code from a file if (Cryptpad.fromFileData) { - Cryptpad.useFile(Cryptget, function () { + Cryptpad.useFile(Cryptget, function (err) { + if (err) { + // TODO: better messages in case of expired, deleted, etc.? + if (err === 'ERESTRICTED') { + sframeChan.event('EV_RESTRICTED_ERROR'); + } else { + sframeChan.query("EV_LOADING_ERROR", "DELETED"); + } + return; + } startRealtime(); cb(); }, cryptputCfg); diff --git a/www/common/sframe-common.js b/www/common/sframe-common.js index a7cb94910..bc91c5227 100644 --- a/www/common/sframe-common.js +++ b/www/common/sframe-common.js @@ -671,6 +671,10 @@ define([ UIElements.displayPasswordPrompt(funcs, cfg); }); + ctx.sframeChan.on("EV_RESTRICTED_ERROR", function () { + UI.errorLoadingScreen(Messages.restrictedError); + }); + ctx.sframeChan.on("EV_PAD_PASSWORD_ERROR", function () { UI.errorLoadingScreen(Messages.password_error_seed); }); diff --git a/www/common/translations/messages.de.json b/www/common/translations/messages.de.json index 8992a1b0b..6d3e78b4d 100644 --- a/www/common/translations/messages.de.json +++ b/www/common/translations/messages.de.json @@ -233,7 +233,7 @@ "poll_removeOption": "Bist du sicher, dass du diese Option entfernen möchtest?", "poll_removeUser": "Bist du sicher, dass du diesen Nutzer entfernen möchtest?", "poll_titleHint": "Titel", - "poll_descriptionHint": "Beschreibe deine Abstimmung und publiziere sie mit der Schaltfläche ✓ (Veröffentlichen), wenn du fertig bist. Bei der Beschreibung kann Markdown-Syntax verwendet werden und du kannst Medien-Elemente von deinem CryptDrive einbetten. Jeder, der den Link kennt, kann die Beschreibung ändern. Dies wird aber nicht empfohlen.", + "poll_descriptionHint": "Beschreibe deine Abstimmung und veröffentliche sie mit der Schaltfläche ✓ (Veröffentlichen).\nBei der Beschreibung kann Markdown-Syntax verwendet werden und du kannst Medien-Elemente von deinem CryptDrive einbetten.\nJeder, der den Link kennt, kann die Beschreibung ändern. Dies wird aber nicht empfohlen.", "poll_remove": "Entfernen", "poll_edit": "Bearbeiten", "poll_locked": "Gesperrt", @@ -327,9 +327,9 @@ "fm_openParent": "Im Ordner zeigen", "fm_noname": "Dokument ohne Titel", "fm_emptyTrashDialog": "Soll der Papierkorb wirklich geleert werden?", - "fm_removeSeveralPermanentlyDialog": "Bist du sicher, dass du diese {0} Elemente endgültig aus deinem CryptDrive entfernen möchtest?", + "fm_removeSeveralPermanentlyDialog": "Bist du sicher, dass du diese {0} Elemente aus deinem CryptDrive entfernen möchtest? Sie verbleiben in den CryptDrives anderer Benutzer, die sie gespeichert haben.", "fm_removePermanentlyNote": "Wenn du fortfährst, werden deine eigenen Pads vom Server entfernt.", - "fm_removePermanentlyDialog": "Bist du sicher, dass du dieses Element endgültig aus deinem CryptDrive entfernen möchtest?", + "fm_removePermanentlyDialog": "Bist du sicher, dass du dieses Element aus deinem CryptDrive entfernen möchtest? Es verbleibt in den CryptDrives anderer Benutzer, die es gespeichert haben.", "fm_removeSeveralDialog": "Bist du sicher, dass du diese {0} Elemente in den Papierkorb verschieben möchtest?", "fm_removeDialog": "Bist du sicher, dass du {0} in den Papierkorb verschieben möchtest?", "fm_deleteOwnedPad": "Bist du sicher, dass du dieses Pad endgültig vom Server löschen möchtest?", @@ -373,9 +373,9 @@ "fc_open": "Öffnen", "fc_open_ro": "Öffnen (schreibgeschützt)", "fc_delete": "In den Papierkorb verschieben", - "fc_delete_owned": "Vom Server löschen", + "fc_delete_owned": "Zerstören", "fc_restore": "Wiederherstellen", - "fc_remove": "Aus deinem CryptDrive entfernen", + "fc_remove": "Entfernen", "fc_remove_sharedfolder": "Entfernen", "fc_empty": "Den Papierkorb leeren", "fc_prop": "Eigenschaften", @@ -605,7 +605,7 @@ "blog": "Blog", "topbar_whatIsCryptpad": "Was ist CryptPad", "whatis_title": "Was ist CryptPad", - "whatis_collaboration": "Effektive und und leichte Zusammenarbeit", + "whatis_collaboration": "Effektive und leichte Zusammenarbeit", "whatis_collaboration_p1": "Mit CryptPad kannst du kollaborative Dokumente erstellen, um Notizen und Ideen gemeinsam zu bearbeiten. Wenn du dich registrierst und einloggst, bekommst du die Möglichkeit, Dateien hochzuladen und Ordner einzurichten, um alle deine Dokumente zu organisieren. Als registrierter Nutzer erhältst du kostenlos 50 MB Speicherplatz.", "whatis_collaboration_p2": "Du kannst den Zugang zu einem CryptPad-Dokument teilen, indem du einfach den entsprechenden Link teilst. Du kannst auch einen schreibgeschützten Zugang erstellen, um die Ergebnisse deiner Arbeit zu teilen, während du sie noch bearbeitest.", "whatis_collaboration_p3": "Du kannst Rich-Text Dokumente mit dem CKEditor erstellen. Außerdem kannst du Markdown-Dokumente erstellen, die in Echtzeit formatiert angezeigt werden, während du tippst. Du kannst auch die Umfrage-Anwendung verwenden, um Termine unter mehrere Teilnehmern zu abzustimmen.", @@ -720,7 +720,7 @@ }, "register": { "q": "Weisst der Server mehr über mich, wenn ich registriere?", - "a": "Wir verlangen nicht Deine Emailadresse und der Server kennt Benutzername und Passwort auch dann nicht, wenn du dich registrierst. Statt dessen generiert das Registrierungs- und Anmeldeformular ein Schlüsselpaar mit deiner Eingabe. Nur der öffentliche Schlüssel dieses Schlüsselpaars wird zum Server geschickt. Mit diesem öffentlichen Schlüssel könenn wir z.B. die Menge der Daten, die du benutzt, kontrollieren, denn jeder Benutzer hat eine beschränkte Quota.

      Wir benutzen die Rückmeldungs-Funktion, um den Server zu informieren, dass jemand mit deiner IP ein Konto registriert hat. Damit können wir messen, wie viele Benutzer CryptPad Konten registrieren und aus welchen Regionen. Somit können wir erfahren, welche Sprache besseren Support braucht.

      Wenn Du registrierst, erstellst Du einen öffentlichen Schlüssel, der benutzt wird, um den Server zu informieren, dass er Dokumente auch dann nicht löschen sollte, wenn sie nicht aktiv benutzt werden. Diese Information zeigt dem Server, wie Du CryptPad benutzt und dieses System erlaubt uns, die Dokumente zu löschen, wofür sich keiner mehr interessiert." + "a": "Wir verlangen deine E-Mail-Adresse nicht, und der Server kennt Benutzername und Passwort auch dann nicht, wenn du dich registrierst. Statt dessen generiert das Registrierungs- und Anmeldeformular ein Schlüsselpaar aus deiner Eingabe. Nur der öffentliche Schlüssel dieses Schlüsselpaars wird zum Server geschickt. Mit diesem öffentlichen Schlüssel können wir z.B. die Menge der Daten, die du benutzt, kontrollieren, denn jeder Benutzer hat eine beschränkte Quota.

      Wir benutzen die Rückmeldungs-Funktion, um den Server zu informieren, dass jemand mit deiner IP ein Konto registriert hat. Damit können wir messen, wie viele Benutzer CryptPad Konten registrieren und aus welchen Regionen. Somit können wir erfahren, welche Sprache besser unterstützt werden sollte.

      Registrierte Benutzer informieren den Server, dass er Dokumente im CryptDrive auch dann nicht wegen Inaktivität löschen sollte, wenn sie nicht aktiv benutzt werden." }, "other": { "q": "Was können andere Benutzer über mich erfahren?", @@ -747,7 +747,7 @@ }, "compromised": { "q": "Liefert mir CryptPad einen Schutz, wenn mein Gerät kompromittiert wird?", - "a": "Für den Fall, dass dein Gerät gestohlen wird, ermöglicht CryptPad, das Ausloggen aller Geräte zu erzwingen - außer dem, das du gerade verwendest. Gehe dazu zur Seite mit deinen Einstellungen und klicke auf Überall ausloggen. Alle anderen Geräte, die mit diesem Konto verbunden sind, werden dann ausgeloggt. Alle früher verbundenen Geräte werden ausgeloggt, sobald sie CryptPad besuchen.

      Die beschriebene Funktion ist derzeit im Browser implementiert und nicht im Server. Somit schützt sie nicht vor staatlichen Akteuren. Aber sie sollte ausreichend sein, wenn du nach Verwendung eines öffentlichen Computers vergessen hast dich auszuloggen." + "a": "Für den Fall, dass dein Gerät gestohlen wird, ermöglicht CryptPad, das Ausloggen aller Geräte zu erzwingen - außer dem, das du gerade verwendest. Gehe dazu zur Seite mit deinen Einstellungen und klicke auf Überall ausloggen. Alle anderen Geräte, die mit diesem Konto verbunden sind, werden dann ausgeloggt. Alle früher verbundenen Geräte werden ausgeloggt, sobald sie CryptPad besuchen.

      Die beschriebene Funktion ist derzeit im Browser implementiert und nicht im Server. Somit schützt sie nicht vor staatlichen Akteuren. Aber sie sollte ausreichend sein, wenn du nach Verwendung eines öffentlichen Computers vergessen hast dich auszuloggen." }, "crypto": { "q": "Welche Kryptografie benutzt ihr?", @@ -770,7 +770,7 @@ }, "remove": { "q": "Ich habe ein Dokument aus meinem CryptDrive gelöscht, aber der Inhalt ist noch verfügbar. Wie kann ich es entfernen?", - "a": "Nur eigene Pads, die im Februar 2018 eingeführt wurden, können gelöscht werden und zwar nur von deren Eigentümer (der Benutzer, der das Dokument ursprünglich erstellt hat). Wenn du nicht der Eigentümer des Pads bist, musst du den Eigentümer bitten, dass er dieses für dich löscht. Bei Pads, deren Eigentümer du bist, kannst du auf das Pad in deinem CryptDrive rechtsklicken und Vom Server löschen wählen." + "a": "Nur eigene Pads, die im Februar 2018 eingeführt wurden, können gelöscht werden und zwar nur von deren Eigentümer (der Benutzer, der das Dokument ursprünglich erstellt hat). Wenn du nicht der Eigentümer des Pads bist, musst du den Eigentümer bitten, dass er dieses für dich löscht. Bei Pads, deren Eigentümer du bist, kannst du auf das Pad in deinem CryptDrive rechtsklicken und Vom Server löschen wählen." }, "forget": { "q": "Was passiert, wenn ich mein Passwort vergesse?", @@ -797,7 +797,7 @@ "title": "Andere Fragen", "pay": { "q": "Wieso soll ich zahlen, wenn so viele Funktionen sowieso kostenfrei sind?", - "a": "Wir geben Unterstützern zusätzlichen Speicherplatz sowie die Möglichkeit, die Speicherplatzbegrenzung ihrer Kontakte zu erhöhen (erfahre mehr).

      Über diese diese kurzfristigen Vorteile hinaus kannst du, wenn du ein Premiumangebot annimmst, die aktive Weiterentwicklung von CryptPad fördern. Das beinhaltet, Fehler zu beseitigen, neue Funktionen zu umzusetzen und Installationen von CryptPad auf eigenen Servern zu erleichtern. Zusätzlich hilfst du, anderen Anbietern zu beweisen, dass Leute datenschutzfreundliche Technologien unterstützen. Wir hoffen, dass Geschäftsmodelle, die auf dem Verkauf von Benutzerdaten basieren, letztendlich der Vergangenheit angehören werden.

      Außerdem glauben wir, dass es gut ist, die Funktionen von CryptPad kostenfrei anzubieten. Denn jeder verdient persönlichen Datenschutz und nicht nur Personen mit hohem Einkommen. Durch deine Unterstützung hilfst du uns, zu ermöglichen, dass auch Menschen mit geringerem Einkommen diese grundlegenden Funktionen genießen können, ohne dass ein Preisetikett daran klebt." + "a": "Wir geben Unterstützern zusätzlichen Speicherplatz sowie die Möglichkeit, die Speicherplatzbegrenzung ihrer Kontakte zu erhöhen (erfahre mehr).

      Über diese kurzfristigen Vorteile hinaus kannst du, wenn du ein Premiumangebot annimmst, die aktive Weiterentwicklung von CryptPad fördern. Das beinhaltet, Fehler zu beseitigen, neue Funktionen umzusetzen und Installationen von CryptPad auf eigenen Servern zu erleichtern. Zusätzlich hilfst du, anderen Anbietern zu beweisen, dass Benutzer datenschutzfreundliche Technologien unterstützen. Wir hoffen, dass Geschäftsmodelle, die auf dem Verkauf von Benutzerdaten basieren, letztendlich der Vergangenheit angehören werden.

      Außerdem glauben wir, dass es gut ist, die Funktionen von CryptPad kostenfrei anzubieten. Denn jeder verdient persönlichen Datenschutz und nicht nur Personen mit hohem Einkommen. Durch deine Unterstützung hilfst du uns, zu ermöglichen, dass auch Menschen mit geringerem Einkommen diese grundlegenden Funktionen genießen können, ohne dass ein Preisetikett daran klebt." }, "goal": { "q": "Was ist euer Ziel?", @@ -813,7 +813,7 @@ }, "revenue": { "q": "Wie kann ich meine Einnahmen mit den Entwicklern teilen?", - "a": "Wenn du deine eigene Installation von CrytPad betreibst und die Einnahmen für deine bezahlten Konten mit den Entwicklern teilen möchtest, muss dein Server als Partnerservice konfiguriert werden.

      In deinem CryptPad-Verzeichnis befindet sich config.example.js. Darin wird erklärt, wie du deinen Server dafür konfigurieren musst. Danach solltest du sales@cryptpad.fr kontaktieren, damit geprüft wird, dass dein Server richtig für HTTPS konfiguriert ist, und die Zahlungsmethoden abgesprochen werden können." + "a": "Wenn du deine eigene Installation von CrytPad betreibst und die Einnahmen für deine bezahlten Konten mit den Entwicklern teilen möchtest, muss dein Server als Partnerservice konfiguriert werden.

      In deinem CryptPad-Verzeichnis befindet sich config.example.js. Darin wird erklärt, wie du deinen Server dafür konfigurieren musst. Danach solltest du sales@cryptpad.fr kontaktieren, damit geprüft wird, dass dein Server richtig für HTTPS konfiguriert ist, und die Zahlungsmethoden abgesprochen werden können." } } }, @@ -906,7 +906,7 @@ "shortcuts": "Mit den Tastenkürzeln `Strg+b`, `Strg+i` und `Strg+u` formatierst du Text fett, kursiv oder unterstrichen.", "indent": "In nummerierten Listen oder Aufzählungen kannst du mit Tab und Umschalt+Tab den Einzug erhöhen oder reduzieren." }, - "feedback_about": "Wenn du das liest, fragst du dich wahrscheinlich, weshalb dein Browser bei der der Ausführung mancher Aktionen Anfragen an Webseiten sendet", + "feedback_about": "Wenn du das liest, fragst du dich wahrscheinlich, weshalb dein Browser bei der Ausführung mancher Aktionen Anfragen an Webseiten sendet", "feedback_privacy": "Wir respektieren deine Datenschutz, aber gleichzeitig wollen wir, dass die Benutzung von CryptPad sehr leicht ist. Deshalb wollen wir erfahren, welche Funktion am wichtigsten für unsere Benutzer ist, indem wir diese mit einer genauen Parameterbeschreibung anfordern.", "feedback_optout": "Wenn du das nicht möchtest, kannst du es in deinen Einstellungen deaktivieren", "creation_404": "Dieses Pad existiert nicht mehr. Benutze das folgende Formular, um ein neues Pad zu erstellen.", @@ -1395,5 +1395,7 @@ "oo_refresh": "Neu laden", "support_category": "Wähle eine Kategorie", "oo_refreshText": "Dieses Dokument wurde aktualisiert", - "support_formCategoryError": "Fehler: Kategorie ist leer" + "support_formCategoryError": "Fehler: Kategorie ist leer", + "fm_restricted": "Du kannst auf dieses Element nicht zugreifen", + "fm_emptyTrashOwned": "In deinem Papierkorb sind Dokumente gespeichert, deren Eigentümer du bist. Du kannst sie aus deinem CryptDrive entfernen oder für alle Benutzer zerstören." } diff --git a/www/common/translations/messages.fr.json b/www/common/translations/messages.fr.json index f4272d26a..da7f4f260 100644 --- a/www/common/translations/messages.fr.json +++ b/www/common/translations/messages.fr.json @@ -331,9 +331,9 @@ "fm_openParent": "Montrer dans le dossier", "fm_noname": "Document sans titre", "fm_emptyTrashDialog": "Êtes-vous sûr de vouloir vider la corbeille ?", - "fm_removeSeveralPermanentlyDialog": "Êtes-vous sûr de vouloir supprimer ces {0} éléments de votre CryptDrive de manière permanente ?", + "fm_removeSeveralPermanentlyDialog": "Êtes vous sûr de vouloir supprimer ces {0} éléments ? Ils resteront dans le drive des autres utilisateurs qui les ont stockés.", "fm_removePermanentlyNote": "Les pads dont vous êtes le propriétaire seront supprimés du serveur.", - "fm_removePermanentlyDialog": "Êtes-vous sûr de vouloir supprimer cet élément de votre CryptDrive de manière permanente ?", + "fm_removePermanentlyDialog": "Êtes-vous sûr de vouloir supprimer cet élément ? Il restera dans le drive des autres utilisateurs qui l'ont stocké.", "fm_deleteOwnedPad": "Êtes-vous sûr de vouloir supprimer définitivement ce pad du serveur ?", "fm_deleteOwnedPads": "Êtes-vous sûr de vouloir supprimer définitivement ces pads du serveur ?", "fm_restoreDialog": "Êtes-vous sûr de vouloir restaurer {0} à son emplacement précédent ?", @@ -380,9 +380,9 @@ "fc_expandAll": "Développer tout", "fc_collapseAll": "Réduire tout", "fc_delete": "Déplacer vers la corbeille", - "fc_delete_owned": "Supprimer du serveur", + "fc_delete_owned": "Détruire", "fc_restore": "Restaurer", - "fc_remove": "Supprimer de votre CryptDrive", + "fc_remove": "Supprimer", "fc_remove_sharedfolder": "Supprimer", "fc_empty": "Vider la corbeille", "fc_prop": "Propriétés", @@ -1394,5 +1394,7 @@ "support_attachments": "Pièces jointes", "oo_refreshText": "Ce document a été mis à jour", "oo_refresh": "Recharger", - "support_category": "Choisir une catégorie" + "support_category": "Choisir une catégorie", + "fm_restricted": "Vous n'avez pas accès à cet élément", + "fm_emptyTrashOwned": "Votre corbeille contient des documents dont vous êtes propriétaire. Vous pouvez les supprimer de votre drive uniquement, ou les détruire pour tous les utilisateurs." } diff --git a/www/common/translations/messages.json b/www/common/translations/messages.json index ede0c1a5a..ef61b02bb 100644 --- a/www/common/translations/messages.json +++ b/www/common/translations/messages.json @@ -338,9 +338,9 @@ "fm_openParent": "Show in folder", "fm_noname": "Untitled Document", "fm_emptyTrashDialog": "Are you sure you want to empty the trash?", - "fm_removeSeveralPermanentlyDialog": "Are you sure you want to permanently remove these {0} elements from your CryptDrive?", + "fm_removeSeveralPermanentlyDialog": "Are you sure you want to remove these {0} items from your drive? They will remain in the drives of other users who have stored them.", "fm_removePermanentlyNote": "Owned pads will be removed from the server if you continue.", - "fm_removePermanentlyDialog": "Are you sure you want to permanently remove that element from your CryptDrive?", + "fm_removePermanentlyDialog": "Are you sure you want to remove this item from your drive? It will remain in the drives of other users who have stored it.", "fm_removeSeveralDialog": "Are you sure you want to move these {0} elements to the trash?", "fm_removeDialog": "Are you sure you want to move {0} to the trash?", "fm_deleteOwnedPad": "Are you sure you want to permanently remove this pad from the server?", @@ -389,9 +389,9 @@ "fc_expandAll": "Expand All", "fc_collapseAll": "Collapse All", "fc_delete": "Move to trash", - "fc_delete_owned": "Delete from the server", + "fc_delete_owned": "Destroy", "fc_restore": "Restore", - "fc_remove": "Remove from your CryptDrive", + "fc_remove": "Remove", "fc_remove_sharedfolder": "Remove", "fc_empty": "Empty the trash", "fc_prop": "Properties", @@ -1395,5 +1395,7 @@ "oo_refresh": "Refresh", "oo_refreshText": "This document has been updated", "support_category": "Choose a category", - "support_formCategoryError": "Error: category is empty" + "support_formCategoryError": "Error: category is empty", + "fm_emptyTrashOwned": "Your trash contains documents you own. You can remove them from your drive only, or destroy them for all users.", + "fm_restricted": "You do not have access" } diff --git a/www/drive/inner.js b/www/drive/inner.js index 5d60e9a4c..553454bc5 100644 --- a/www/drive/inner.js +++ b/www/drive/inner.js @@ -42,6 +42,7 @@ define([ if (!drive || !drive.sharedFolders) { return void cb(); } + var r = drive.restrictedFolders = drive.restrictedFolders || {}; var oldIds = Object.keys(folders); nThen(function (waitFor) { Object.keys(drive.sharedFolders).forEach(function (fId) { @@ -60,7 +61,11 @@ define([ APP.newSharedFolder = null; } } - if (newObj && newObj.deprecated) { + if (newObj && newObj.restricted) { + r[fId] = drive.sharedFolders[fId]; + if (!r[fId].title) { r[fId].title = r[fId].lastTitle; } + } + if (newObj && (newObj.deprecated /*|| newObj.restricted*/)) { delete folders[fId]; delete drive.sharedFolders[fId]; if (manager && manager.folders) { diff --git a/www/drive/main.js b/www/drive/main.js index 4f5b9daba..8db013c0f 100644 --- a/www/drive/main.js +++ b/www/drive/main.js @@ -45,11 +45,16 @@ define([ }; window.addEventListener('message', onMsg); }).nThen(function (/*waitFor*/) { - var afterSecrets = function (Cryptpad, Utils, secret, cb) { + var afterSecrets = function (Cryptpad, Utils, secret, cb, sframeChan) { var _hash = hash.slice(1); if (_hash && Utils.LocalStore.isLoggedIn()) { // Add a shared folder! Cryptpad.addSharedFolder(null, secret, function (id) { + if (id && typeof(id) === "object" && id.error) { + sframeChan.event("EV_RESTRICTED_ERROR"); + return; + } + window.CryptPad_newSharedFolder = id; // Clear the hash now that the secrets have been generated diff --git a/www/lib/markmap/transform.min.js b/www/lib/markmap/transform.min.js new file mode 100644 index 000000000..dea3b437b --- /dev/null +++ b/www/lib/markmap/transform.min.js @@ -0,0 +1,2 @@ +/*! markmap-lib v0.7.11 | MIT License */ +define(["exports"],(function(e){"use strict";var r={Aacute:"Á",aacute:"á",Abreve:"Ă",abreve:"ă",ac:"∾",acd:"∿",acE:"∾̳",Acirc:"Â",acirc:"â",acute:"´",Acy:"А",acy:"а",AElig:"Æ",aelig:"æ",af:"⁡",Afr:"𝔄",afr:"𝔞",Agrave:"À",agrave:"à",alefsym:"ℵ",aleph:"ℵ",Alpha:"Α",alpha:"α",Amacr:"Ā",amacr:"ā",amalg:"⨿",AMP:"&",amp:"&",And:"⩓",and:"∧",andand:"⩕",andd:"⩜",andslope:"⩘",andv:"⩚",ang:"∠",ange:"⦤",angle:"∠",angmsd:"∡",angmsdaa:"⦨",angmsdab:"⦩",angmsdac:"⦪",angmsdad:"⦫",angmsdae:"⦬",angmsdaf:"⦭",angmsdag:"⦮",angmsdah:"⦯",angrt:"∟",angrtvb:"⊾",angrtvbd:"⦝",angsph:"∢",angst:"Å",angzarr:"⍼",Aogon:"Ą",aogon:"ą",Aopf:"𝔸",aopf:"𝕒",ap:"≈",apacir:"⩯",apE:"⩰",ape:"≊",apid:"≋",apos:"'",ApplyFunction:"⁡",approx:"≈",approxeq:"≊",Aring:"Å",aring:"å",Ascr:"𝒜",ascr:"𝒶",Assign:"≔",ast:"*",asymp:"≈",asympeq:"≍",Atilde:"Ã",atilde:"ã",Auml:"Ä",auml:"ä",awconint:"∳",awint:"⨑",backcong:"≌",backepsilon:"϶",backprime:"‵",backsim:"∽",backsimeq:"⋍",Backslash:"∖",Barv:"⫧",barvee:"⊽",Barwed:"⌆",barwed:"⌅",barwedge:"⌅",bbrk:"⎵",bbrktbrk:"⎶",bcong:"≌",Bcy:"Б",bcy:"б",bdquo:"„",becaus:"∵",Because:"∵",because:"∵",bemptyv:"⦰",bepsi:"϶",bernou:"ℬ",Bernoullis:"ℬ",Beta:"Β",beta:"β",beth:"ℶ",between:"≬",Bfr:"𝔅",bfr:"𝔟",bigcap:"⋂",bigcirc:"◯",bigcup:"⋃",bigodot:"⨀",bigoplus:"⨁",bigotimes:"⨂",bigsqcup:"⨆",bigstar:"★",bigtriangledown:"▽",bigtriangleup:"△",biguplus:"⨄",bigvee:"⋁",bigwedge:"⋀",bkarow:"⤍",blacklozenge:"⧫",blacksquare:"▪",blacktriangle:"▴",blacktriangledown:"▾",blacktriangleleft:"◂",blacktriangleright:"▸",blank:"␣",blk12:"▒",blk14:"░",blk34:"▓",block:"█",bne:"=⃥",bnequiv:"≡⃥",bNot:"⫭",bnot:"⌐",Bopf:"𝔹",bopf:"𝕓",bot:"⊥",bottom:"⊥",bowtie:"⋈",boxbox:"⧉",boxDL:"╗",boxDl:"╖",boxdL:"╕",boxdl:"┐",boxDR:"╔",boxDr:"╓",boxdR:"╒",boxdr:"┌",boxH:"═",boxh:"─",boxHD:"╦",boxHd:"╤",boxhD:"╥",boxhd:"┬",boxHU:"╩",boxHu:"╧",boxhU:"╨",boxhu:"┴",boxminus:"⊟",boxplus:"⊞",boxtimes:"⊠",boxUL:"╝",boxUl:"╜",boxuL:"╛",boxul:"┘",boxUR:"╚",boxUr:"╙",boxuR:"╘",boxur:"└",boxV:"║",boxv:"│",boxVH:"╬",boxVh:"╫",boxvH:"╪",boxvh:"┼",boxVL:"╣",boxVl:"╢",boxvL:"╡",boxvl:"┤",boxVR:"╠",boxVr:"╟",boxvR:"╞",boxvr:"├",bprime:"‵",Breve:"˘",breve:"˘",brvbar:"¦",Bscr:"ℬ",bscr:"𝒷",bsemi:"⁏",bsim:"∽",bsime:"⋍",bsol:"\\",bsolb:"⧅",bsolhsub:"⟈",bull:"•",bullet:"•",bump:"≎",bumpE:"⪮",bumpe:"≏",Bumpeq:"≎",bumpeq:"≏",Cacute:"Ć",cacute:"ć",Cap:"⋒",cap:"∩",capand:"⩄",capbrcup:"⩉",capcap:"⩋",capcup:"⩇",capdot:"⩀",CapitalDifferentialD:"ⅅ",caps:"∩︀",caret:"⁁",caron:"ˇ",Cayleys:"ℭ",ccaps:"⩍",Ccaron:"Č",ccaron:"č",Ccedil:"Ç",ccedil:"ç",Ccirc:"Ĉ",ccirc:"ĉ",Cconint:"∰",ccups:"⩌",ccupssm:"⩐",Cdot:"Ċ",cdot:"ċ",cedil:"¸",Cedilla:"¸",cemptyv:"⦲",cent:"¢",CenterDot:"·",centerdot:"·",Cfr:"ℭ",cfr:"𝔠",CHcy:"Ч",chcy:"ч",check:"✓",checkmark:"✓",Chi:"Χ",chi:"χ",cir:"○",circ:"ˆ",circeq:"≗",circlearrowleft:"↺",circlearrowright:"↻",circledast:"⊛",circledcirc:"⊚",circleddash:"⊝",CircleDot:"⊙",circledR:"®",circledS:"Ⓢ",CircleMinus:"⊖",CirclePlus:"⊕",CircleTimes:"⊗",cirE:"⧃",cire:"≗",cirfnint:"⨐",cirmid:"⫯",cirscir:"⧂",ClockwiseContourIntegral:"∲",CloseCurlyDoubleQuote:"”",CloseCurlyQuote:"’",clubs:"♣",clubsuit:"♣",Colon:"∷",colon:":",Colone:"⩴",colone:"≔",coloneq:"≔",comma:",",commat:"@",comp:"∁",compfn:"∘",complement:"∁",complexes:"ℂ",cong:"≅",congdot:"⩭",Congruent:"≡",Conint:"∯",conint:"∮",ContourIntegral:"∮",Copf:"ℂ",copf:"𝕔",coprod:"∐",Coproduct:"∐",COPY:"©",copy:"©",copysr:"℗",CounterClockwiseContourIntegral:"∳",crarr:"↵",Cross:"⨯",cross:"✗",Cscr:"𝒞",cscr:"𝒸",csub:"⫏",csube:"⫑",csup:"⫐",csupe:"⫒",ctdot:"⋯",cudarrl:"⤸",cudarrr:"⤵",cuepr:"⋞",cuesc:"⋟",cularr:"↶",cularrp:"⤽",Cup:"⋓",cup:"∪",cupbrcap:"⩈",CupCap:"≍",cupcap:"⩆",cupcup:"⩊",cupdot:"⊍",cupor:"⩅",cups:"∪︀",curarr:"↷",curarrm:"⤼",curlyeqprec:"⋞",curlyeqsucc:"⋟",curlyvee:"⋎",curlywedge:"⋏",curren:"¤",curvearrowleft:"↶",curvearrowright:"↷",cuvee:"⋎",cuwed:"⋏",cwconint:"∲",cwint:"∱",cylcty:"⌭",Dagger:"‡",dagger:"†",daleth:"ℸ",Darr:"↡",dArr:"⇓",darr:"↓",dash:"‐",Dashv:"⫤",dashv:"⊣",dbkarow:"⤏",dblac:"˝",Dcaron:"Ď",dcaron:"ď",Dcy:"Д",dcy:"д",DD:"ⅅ",dd:"ⅆ",ddagger:"‡",ddarr:"⇊",DDotrahd:"⤑",ddotseq:"⩷",deg:"°",Del:"∇",Delta:"Δ",delta:"δ",demptyv:"⦱",dfisht:"⥿",Dfr:"𝔇",dfr:"𝔡",dHar:"⥥",dharl:"⇃",dharr:"⇂",DiacriticalAcute:"´",DiacriticalDot:"˙",DiacriticalDoubleAcute:"˝",DiacriticalGrave:"`",DiacriticalTilde:"˜",diam:"⋄",Diamond:"⋄",diamond:"⋄",diamondsuit:"♦",diams:"♦",die:"¨",DifferentialD:"ⅆ",digamma:"ϝ",disin:"⋲",div:"÷",divide:"÷",divideontimes:"⋇",divonx:"⋇",DJcy:"Ђ",djcy:"ђ",dlcorn:"⌞",dlcrop:"⌍",dollar:"$",Dopf:"𝔻",dopf:"𝕕",Dot:"¨",dot:"˙",DotDot:"⃜",doteq:"≐",doteqdot:"≑",DotEqual:"≐",dotminus:"∸",dotplus:"∔",dotsquare:"⊡",doublebarwedge:"⌆",DoubleContourIntegral:"∯",DoubleDot:"¨",DoubleDownArrow:"⇓",DoubleLeftArrow:"⇐",DoubleLeftRightArrow:"⇔",DoubleLeftTee:"⫤",DoubleLongLeftArrow:"⟸",DoubleLongLeftRightArrow:"⟺",DoubleLongRightArrow:"⟹",DoubleRightArrow:"⇒",DoubleRightTee:"⊨",DoubleUpArrow:"⇑",DoubleUpDownArrow:"⇕",DoubleVerticalBar:"∥",DownArrow:"↓",Downarrow:"⇓",downarrow:"↓",DownArrowBar:"⤓",DownArrowUpArrow:"⇵",DownBreve:"̑",downdownarrows:"⇊",downharpoonleft:"⇃",downharpoonright:"⇂",DownLeftRightVector:"⥐",DownLeftTeeVector:"⥞",DownLeftVector:"↽",DownLeftVectorBar:"⥖",DownRightTeeVector:"⥟",DownRightVector:"⇁",DownRightVectorBar:"⥗",DownTee:"⊤",DownTeeArrow:"↧",drbkarow:"⤐",drcorn:"⌟",drcrop:"⌌",Dscr:"𝒟",dscr:"𝒹",DScy:"Ѕ",dscy:"ѕ",dsol:"⧶",Dstrok:"Đ",dstrok:"đ",dtdot:"⋱",dtri:"▿",dtrif:"▾",duarr:"⇵",duhar:"⥯",dwangle:"⦦",DZcy:"Џ",dzcy:"џ",dzigrarr:"⟿",Eacute:"É",eacute:"é",easter:"⩮",Ecaron:"Ě",ecaron:"ě",ecir:"≖",Ecirc:"Ê",ecirc:"ê",ecolon:"≕",Ecy:"Э",ecy:"э",eDDot:"⩷",Edot:"Ė",eDot:"≑",edot:"ė",ee:"ⅇ",efDot:"≒",Efr:"𝔈",efr:"𝔢",eg:"⪚",Egrave:"È",egrave:"è",egs:"⪖",egsdot:"⪘",el:"⪙",Element:"∈",elinters:"⏧",ell:"ℓ",els:"⪕",elsdot:"⪗",Emacr:"Ē",emacr:"ē",empty:"∅",emptyset:"∅",EmptySmallSquare:"◻",emptyv:"∅",EmptyVerySmallSquare:"▫",emsp:" ",emsp13:" ",emsp14:" ",ENG:"Ŋ",eng:"ŋ",ensp:" ",Eogon:"Ę",eogon:"ę",Eopf:"𝔼",eopf:"𝕖",epar:"⋕",eparsl:"⧣",eplus:"⩱",epsi:"ε",Epsilon:"Ε",epsilon:"ε",epsiv:"ϵ",eqcirc:"≖",eqcolon:"≕",eqsim:"≂",eqslantgtr:"⪖",eqslantless:"⪕",Equal:"⩵",equals:"=",EqualTilde:"≂",equest:"≟",Equilibrium:"⇌",equiv:"≡",equivDD:"⩸",eqvparsl:"⧥",erarr:"⥱",erDot:"≓",Escr:"ℰ",escr:"ℯ",esdot:"≐",Esim:"⩳",esim:"≂",Eta:"Η",eta:"η",ETH:"Ð",eth:"ð",Euml:"Ë",euml:"ë",euro:"€",excl:"!",exist:"∃",Exists:"∃",expectation:"ℰ",ExponentialE:"ⅇ",exponentiale:"ⅇ",fallingdotseq:"≒",Fcy:"Ф",fcy:"ф",female:"♀",ffilig:"ffi",fflig:"ff",ffllig:"ffl",Ffr:"𝔉",ffr:"𝔣",filig:"fi",FilledSmallSquare:"◼",FilledVerySmallSquare:"▪",fjlig:"fj",flat:"♭",fllig:"fl",fltns:"▱",fnof:"ƒ",Fopf:"𝔽",fopf:"𝕗",ForAll:"∀",forall:"∀",fork:"⋔",forkv:"⫙",Fouriertrf:"ℱ",fpartint:"⨍",frac12:"½",frac13:"⅓",frac14:"¼",frac15:"⅕",frac16:"⅙",frac18:"⅛",frac23:"⅔",frac25:"⅖",frac34:"¾",frac35:"⅗",frac38:"⅜",frac45:"⅘",frac56:"⅚",frac58:"⅝",frac78:"⅞",frasl:"⁄",frown:"⌢",Fscr:"ℱ",fscr:"𝒻",gacute:"ǵ",Gamma:"Γ",gamma:"γ",Gammad:"Ϝ",gammad:"ϝ",gap:"⪆",Gbreve:"Ğ",gbreve:"ğ",Gcedil:"Ģ",Gcirc:"Ĝ",gcirc:"ĝ",Gcy:"Г",gcy:"г",Gdot:"Ġ",gdot:"ġ",gE:"≧",ge:"≥",gEl:"⪌",gel:"⋛",geq:"≥",geqq:"≧",geqslant:"⩾",ges:"⩾",gescc:"⪩",gesdot:"⪀",gesdoto:"⪂",gesdotol:"⪄",gesl:"⋛︀",gesles:"⪔",Gfr:"𝔊",gfr:"𝔤",Gg:"⋙",gg:"≫",ggg:"⋙",gimel:"ℷ",GJcy:"Ѓ",gjcy:"ѓ",gl:"≷",gla:"⪥",glE:"⪒",glj:"⪤",gnap:"⪊",gnapprox:"⪊",gnE:"≩",gne:"⪈",gneq:"⪈",gneqq:"≩",gnsim:"⋧",Gopf:"𝔾",gopf:"𝕘",grave:"`",GreaterEqual:"≥",GreaterEqualLess:"⋛",GreaterFullEqual:"≧",GreaterGreater:"⪢",GreaterLess:"≷",GreaterSlantEqual:"⩾",GreaterTilde:"≳",Gscr:"𝒢",gscr:"ℊ",gsim:"≳",gsime:"⪎",gsiml:"⪐",GT:">",Gt:"≫",gt:">",gtcc:"⪧",gtcir:"⩺",gtdot:"⋗",gtlPar:"⦕",gtquest:"⩼",gtrapprox:"⪆",gtrarr:"⥸",gtrdot:"⋗",gtreqless:"⋛",gtreqqless:"⪌",gtrless:"≷",gtrsim:"≳",gvertneqq:"≩︀",gvnE:"≩︀",Hacek:"ˇ",hairsp:" ",half:"½",hamilt:"ℋ",HARDcy:"Ъ",hardcy:"ъ",hArr:"⇔",harr:"↔",harrcir:"⥈",harrw:"↭",Hat:"^",hbar:"ℏ",Hcirc:"Ĥ",hcirc:"ĥ",hearts:"♥",heartsuit:"♥",hellip:"…",hercon:"⊹",Hfr:"ℌ",hfr:"𝔥",HilbertSpace:"ℋ",hksearow:"⤥",hkswarow:"⤦",hoarr:"⇿",homtht:"∻",hookleftarrow:"↩",hookrightarrow:"↪",Hopf:"ℍ",hopf:"𝕙",horbar:"―",HorizontalLine:"─",Hscr:"ℋ",hscr:"𝒽",hslash:"ℏ",Hstrok:"Ħ",hstrok:"ħ",HumpDownHump:"≎",HumpEqual:"≏",hybull:"⁃",hyphen:"‐",Iacute:"Í",iacute:"í",ic:"⁣",Icirc:"Î",icirc:"î",Icy:"И",icy:"и",Idot:"İ",IEcy:"Е",iecy:"е",iexcl:"¡",iff:"⇔",Ifr:"ℑ",ifr:"𝔦",Igrave:"Ì",igrave:"ì",ii:"ⅈ",iiiint:"⨌",iiint:"∭",iinfin:"⧜",iiota:"℩",IJlig:"IJ",ijlig:"ij",Im:"ℑ",Imacr:"Ī",imacr:"ī",image:"ℑ",ImaginaryI:"ⅈ",imagline:"ℐ",imagpart:"ℑ",imath:"ı",imof:"⊷",imped:"Ƶ",Implies:"⇒",in:"∈",incare:"℅",infin:"∞",infintie:"⧝",inodot:"ı",Int:"∬",int:"∫",intcal:"⊺",integers:"ℤ",Integral:"∫",intercal:"⊺",Intersection:"⋂",intlarhk:"⨗",intprod:"⨼",InvisibleComma:"⁣",InvisibleTimes:"⁢",IOcy:"Ё",iocy:"ё",Iogon:"Į",iogon:"į",Iopf:"𝕀",iopf:"𝕚",Iota:"Ι",iota:"ι",iprod:"⨼",iquest:"¿",Iscr:"ℐ",iscr:"𝒾",isin:"∈",isindot:"⋵",isinE:"⋹",isins:"⋴",isinsv:"⋳",isinv:"∈",it:"⁢",Itilde:"Ĩ",itilde:"ĩ",Iukcy:"І",iukcy:"і",Iuml:"Ï",iuml:"ï",Jcirc:"Ĵ",jcirc:"ĵ",Jcy:"Й",jcy:"й",Jfr:"𝔍",jfr:"𝔧",jmath:"ȷ",Jopf:"𝕁",jopf:"𝕛",Jscr:"𝒥",jscr:"𝒿",Jsercy:"Ј",jsercy:"ј",Jukcy:"Є",jukcy:"є",Kappa:"Κ",kappa:"κ",kappav:"ϰ",Kcedil:"Ķ",kcedil:"ķ",Kcy:"К",kcy:"к",Kfr:"𝔎",kfr:"𝔨",kgreen:"ĸ",KHcy:"Х",khcy:"х",KJcy:"Ќ",kjcy:"ќ",Kopf:"𝕂",kopf:"𝕜",Kscr:"𝒦",kscr:"𝓀",lAarr:"⇚",Lacute:"Ĺ",lacute:"ĺ",laemptyv:"⦴",lagran:"ℒ",Lambda:"Λ",lambda:"λ",Lang:"⟪",lang:"⟨",langd:"⦑",langle:"⟨",lap:"⪅",Laplacetrf:"ℒ",laquo:"«",Larr:"↞",lArr:"⇐",larr:"←",larrb:"⇤",larrbfs:"⤟",larrfs:"⤝",larrhk:"↩",larrlp:"↫",larrpl:"⤹",larrsim:"⥳",larrtl:"↢",lat:"⪫",lAtail:"⤛",latail:"⤙",late:"⪭",lates:"⪭︀",lBarr:"⤎",lbarr:"⤌",lbbrk:"❲",lbrace:"{",lbrack:"[",lbrke:"⦋",lbrksld:"⦏",lbrkslu:"⦍",Lcaron:"Ľ",lcaron:"ľ",Lcedil:"Ļ",lcedil:"ļ",lceil:"⌈",lcub:"{",Lcy:"Л",lcy:"л",ldca:"⤶",ldquo:"“",ldquor:"„",ldrdhar:"⥧",ldrushar:"⥋",ldsh:"↲",lE:"≦",le:"≤",LeftAngleBracket:"⟨",LeftArrow:"←",Leftarrow:"⇐",leftarrow:"←",LeftArrowBar:"⇤",LeftArrowRightArrow:"⇆",leftarrowtail:"↢",LeftCeiling:"⌈",LeftDoubleBracket:"⟦",LeftDownTeeVector:"⥡",LeftDownVector:"⇃",LeftDownVectorBar:"⥙",LeftFloor:"⌊",leftharpoondown:"↽",leftharpoonup:"↼",leftleftarrows:"⇇",LeftRightArrow:"↔",Leftrightarrow:"⇔",leftrightarrow:"↔",leftrightarrows:"⇆",leftrightharpoons:"⇋",leftrightsquigarrow:"↭",LeftRightVector:"⥎",LeftTee:"⊣",LeftTeeArrow:"↤",LeftTeeVector:"⥚",leftthreetimes:"⋋",LeftTriangle:"⊲",LeftTriangleBar:"⧏",LeftTriangleEqual:"⊴",LeftUpDownVector:"⥑",LeftUpTeeVector:"⥠",LeftUpVector:"↿",LeftUpVectorBar:"⥘",LeftVector:"↼",LeftVectorBar:"⥒",lEg:"⪋",leg:"⋚",leq:"≤",leqq:"≦",leqslant:"⩽",les:"⩽",lescc:"⪨",lesdot:"⩿",lesdoto:"⪁",lesdotor:"⪃",lesg:"⋚︀",lesges:"⪓",lessapprox:"⪅",lessdot:"⋖",lesseqgtr:"⋚",lesseqqgtr:"⪋",LessEqualGreater:"⋚",LessFullEqual:"≦",LessGreater:"≶",lessgtr:"≶",LessLess:"⪡",lesssim:"≲",LessSlantEqual:"⩽",LessTilde:"≲",lfisht:"⥼",lfloor:"⌊",Lfr:"𝔏",lfr:"𝔩",lg:"≶",lgE:"⪑",lHar:"⥢",lhard:"↽",lharu:"↼",lharul:"⥪",lhblk:"▄",LJcy:"Љ",ljcy:"љ",Ll:"⋘",ll:"≪",llarr:"⇇",llcorner:"⌞",Lleftarrow:"⇚",llhard:"⥫",lltri:"◺",Lmidot:"Ŀ",lmidot:"ŀ",lmoust:"⎰",lmoustache:"⎰",lnap:"⪉",lnapprox:"⪉",lnE:"≨",lne:"⪇",lneq:"⪇",lneqq:"≨",lnsim:"⋦",loang:"⟬",loarr:"⇽",lobrk:"⟦",LongLeftArrow:"⟵",Longleftarrow:"⟸",longleftarrow:"⟵",LongLeftRightArrow:"⟷",Longleftrightarrow:"⟺",longleftrightarrow:"⟷",longmapsto:"⟼",LongRightArrow:"⟶",Longrightarrow:"⟹",longrightarrow:"⟶",looparrowleft:"↫",looparrowright:"↬",lopar:"⦅",Lopf:"𝕃",lopf:"𝕝",loplus:"⨭",lotimes:"⨴",lowast:"∗",lowbar:"_",LowerLeftArrow:"↙",LowerRightArrow:"↘",loz:"◊",lozenge:"◊",lozf:"⧫",lpar:"(",lparlt:"⦓",lrarr:"⇆",lrcorner:"⌟",lrhar:"⇋",lrhard:"⥭",lrm:"‎",lrtri:"⊿",lsaquo:"‹",Lscr:"ℒ",lscr:"𝓁",Lsh:"↰",lsh:"↰",lsim:"≲",lsime:"⪍",lsimg:"⪏",lsqb:"[",lsquo:"‘",lsquor:"‚",Lstrok:"Ł",lstrok:"ł",LT:"<",Lt:"≪",lt:"<",ltcc:"⪦",ltcir:"⩹",ltdot:"⋖",lthree:"⋋",ltimes:"⋉",ltlarr:"⥶",ltquest:"⩻",ltri:"◃",ltrie:"⊴",ltrif:"◂",ltrPar:"⦖",lurdshar:"⥊",luruhar:"⥦",lvertneqq:"≨︀",lvnE:"≨︀",macr:"¯",male:"♂",malt:"✠",maltese:"✠",Map:"⤅",map:"↦",mapsto:"↦",mapstodown:"↧",mapstoleft:"↤",mapstoup:"↥",marker:"▮",mcomma:"⨩",Mcy:"М",mcy:"м",mdash:"—",mDDot:"∺",measuredangle:"∡",MediumSpace:" ",Mellintrf:"ℳ",Mfr:"𝔐",mfr:"𝔪",mho:"℧",micro:"µ",mid:"∣",midast:"*",midcir:"⫰",middot:"·",minus:"−",minusb:"⊟",minusd:"∸",minusdu:"⨪",MinusPlus:"∓",mlcp:"⫛",mldr:"…",mnplus:"∓",models:"⊧",Mopf:"𝕄",mopf:"𝕞",mp:"∓",Mscr:"ℳ",mscr:"𝓂",mstpos:"∾",Mu:"Μ",mu:"μ",multimap:"⊸",mumap:"⊸",nabla:"∇",Nacute:"Ń",nacute:"ń",nang:"∠⃒",nap:"≉",napE:"⩰̸",napid:"≋̸",napos:"ʼn",napprox:"≉",natur:"♮",natural:"♮",naturals:"ℕ",nbsp:" ",nbump:"≎̸",nbumpe:"≏̸",ncap:"⩃",Ncaron:"Ň",ncaron:"ň",Ncedil:"Ņ",ncedil:"ņ",ncong:"≇",ncongdot:"⩭̸",ncup:"⩂",Ncy:"Н",ncy:"н",ndash:"–",ne:"≠",nearhk:"⤤",neArr:"⇗",nearr:"↗",nearrow:"↗",nedot:"≐̸",NegativeMediumSpace:"​",NegativeThickSpace:"​",NegativeThinSpace:"​",NegativeVeryThinSpace:"​",nequiv:"≢",nesear:"⤨",nesim:"≂̸",NestedGreaterGreater:"≫",NestedLessLess:"≪",NewLine:"\n",nexist:"∄",nexists:"∄",Nfr:"𝔑",nfr:"𝔫",ngE:"≧̸",nge:"≱",ngeq:"≱",ngeqq:"≧̸",ngeqslant:"⩾̸",nges:"⩾̸",nGg:"⋙̸",ngsim:"≵",nGt:"≫⃒",ngt:"≯",ngtr:"≯",nGtv:"≫̸",nhArr:"⇎",nharr:"↮",nhpar:"⫲",ni:"∋",nis:"⋼",nisd:"⋺",niv:"∋",NJcy:"Њ",njcy:"њ",nlArr:"⇍",nlarr:"↚",nldr:"‥",nlE:"≦̸",nle:"≰",nLeftarrow:"⇍",nleftarrow:"↚",nLeftrightarrow:"⇎",nleftrightarrow:"↮",nleq:"≰",nleqq:"≦̸",nleqslant:"⩽̸",nles:"⩽̸",nless:"≮",nLl:"⋘̸",nlsim:"≴",nLt:"≪⃒",nlt:"≮",nltri:"⋪",nltrie:"⋬",nLtv:"≪̸",nmid:"∤",NoBreak:"⁠",NonBreakingSpace:" ",Nopf:"ℕ",nopf:"𝕟",Not:"⫬",not:"¬",NotCongruent:"≢",NotCupCap:"≭",NotDoubleVerticalBar:"∦",NotElement:"∉",NotEqual:"≠",NotEqualTilde:"≂̸",NotExists:"∄",NotGreater:"≯",NotGreaterEqual:"≱",NotGreaterFullEqual:"≧̸",NotGreaterGreater:"≫̸",NotGreaterLess:"≹",NotGreaterSlantEqual:"⩾̸",NotGreaterTilde:"≵",NotHumpDownHump:"≎̸",NotHumpEqual:"≏̸",notin:"∉",notindot:"⋵̸",notinE:"⋹̸",notinva:"∉",notinvb:"⋷",notinvc:"⋶",NotLeftTriangle:"⋪",NotLeftTriangleBar:"⧏̸",NotLeftTriangleEqual:"⋬",NotLess:"≮",NotLessEqual:"≰",NotLessGreater:"≸",NotLessLess:"≪̸",NotLessSlantEqual:"⩽̸",NotLessTilde:"≴",NotNestedGreaterGreater:"⪢̸",NotNestedLessLess:"⪡̸",notni:"∌",notniva:"∌",notnivb:"⋾",notnivc:"⋽",NotPrecedes:"⊀",NotPrecedesEqual:"⪯̸",NotPrecedesSlantEqual:"⋠",NotReverseElement:"∌",NotRightTriangle:"⋫",NotRightTriangleBar:"⧐̸",NotRightTriangleEqual:"⋭",NotSquareSubset:"⊏̸",NotSquareSubsetEqual:"⋢",NotSquareSuperset:"⊐̸",NotSquareSupersetEqual:"⋣",NotSubset:"⊂⃒",NotSubsetEqual:"⊈",NotSucceeds:"⊁",NotSucceedsEqual:"⪰̸",NotSucceedsSlantEqual:"⋡",NotSucceedsTilde:"≿̸",NotSuperset:"⊃⃒",NotSupersetEqual:"⊉",NotTilde:"≁",NotTildeEqual:"≄",NotTildeFullEqual:"≇",NotTildeTilde:"≉",NotVerticalBar:"∤",npar:"∦",nparallel:"∦",nparsl:"⫽⃥",npart:"∂̸",npolint:"⨔",npr:"⊀",nprcue:"⋠",npre:"⪯̸",nprec:"⊀",npreceq:"⪯̸",nrArr:"⇏",nrarr:"↛",nrarrc:"⤳̸",nrarrw:"↝̸",nRightarrow:"⇏",nrightarrow:"↛",nrtri:"⋫",nrtrie:"⋭",nsc:"⊁",nsccue:"⋡",nsce:"⪰̸",Nscr:"𝒩",nscr:"𝓃",nshortmid:"∤",nshortparallel:"∦",nsim:"≁",nsime:"≄",nsimeq:"≄",nsmid:"∤",nspar:"∦",nsqsube:"⋢",nsqsupe:"⋣",nsub:"⊄",nsubE:"⫅̸",nsube:"⊈",nsubset:"⊂⃒",nsubseteq:"⊈",nsubseteqq:"⫅̸",nsucc:"⊁",nsucceq:"⪰̸",nsup:"⊅",nsupE:"⫆̸",nsupe:"⊉",nsupset:"⊃⃒",nsupseteq:"⊉",nsupseteqq:"⫆̸",ntgl:"≹",Ntilde:"Ñ",ntilde:"ñ",ntlg:"≸",ntriangleleft:"⋪",ntrianglelefteq:"⋬",ntriangleright:"⋫",ntrianglerighteq:"⋭",Nu:"Ν",nu:"ν",num:"#",numero:"№",numsp:" ",nvap:"≍⃒",nVDash:"⊯",nVdash:"⊮",nvDash:"⊭",nvdash:"⊬",nvge:"≥⃒",nvgt:">⃒",nvHarr:"⤄",nvinfin:"⧞",nvlArr:"⤂",nvle:"≤⃒",nvlt:"<⃒",nvltrie:"⊴⃒",nvrArr:"⤃",nvrtrie:"⊵⃒",nvsim:"∼⃒",nwarhk:"⤣",nwArr:"⇖",nwarr:"↖",nwarrow:"↖",nwnear:"⤧",Oacute:"Ó",oacute:"ó",oast:"⊛",ocir:"⊚",Ocirc:"Ô",ocirc:"ô",Ocy:"О",ocy:"о",odash:"⊝",Odblac:"Ő",odblac:"ő",odiv:"⨸",odot:"⊙",odsold:"⦼",OElig:"Œ",oelig:"œ",ofcir:"⦿",Ofr:"𝔒",ofr:"𝔬",ogon:"˛",Ograve:"Ò",ograve:"ò",ogt:"⧁",ohbar:"⦵",ohm:"Ω",oint:"∮",olarr:"↺",olcir:"⦾",olcross:"⦻",oline:"‾",olt:"⧀",Omacr:"Ō",omacr:"ō",Omega:"Ω",omega:"ω",Omicron:"Ο",omicron:"ο",omid:"⦶",ominus:"⊖",Oopf:"𝕆",oopf:"𝕠",opar:"⦷",OpenCurlyDoubleQuote:"“",OpenCurlyQuote:"‘",operp:"⦹",oplus:"⊕",Or:"⩔",or:"∨",orarr:"↻",ord:"⩝",order:"ℴ",orderof:"ℴ",ordf:"ª",ordm:"º",origof:"⊶",oror:"⩖",orslope:"⩗",orv:"⩛",oS:"Ⓢ",Oscr:"𝒪",oscr:"ℴ",Oslash:"Ø",oslash:"ø",osol:"⊘",Otilde:"Õ",otilde:"õ",Otimes:"⨷",otimes:"⊗",otimesas:"⨶",Ouml:"Ö",ouml:"ö",ovbar:"⌽",OverBar:"‾",OverBrace:"⏞",OverBracket:"⎴",OverParenthesis:"⏜",par:"∥",para:"¶",parallel:"∥",parsim:"⫳",parsl:"⫽",part:"∂",PartialD:"∂",Pcy:"П",pcy:"п",percnt:"%",period:".",permil:"‰",perp:"⊥",pertenk:"‱",Pfr:"𝔓",pfr:"𝔭",Phi:"Φ",phi:"φ",phiv:"ϕ",phmmat:"ℳ",phone:"☎",Pi:"Π",pi:"π",pitchfork:"⋔",piv:"ϖ",planck:"ℏ",planckh:"ℎ",plankv:"ℏ",plus:"+",plusacir:"⨣",plusb:"⊞",pluscir:"⨢",plusdo:"∔",plusdu:"⨥",pluse:"⩲",PlusMinus:"±",plusmn:"±",plussim:"⨦",plustwo:"⨧",pm:"±",Poincareplane:"ℌ",pointint:"⨕",Popf:"ℙ",popf:"𝕡",pound:"£",Pr:"⪻",pr:"≺",prap:"⪷",prcue:"≼",prE:"⪳",pre:"⪯",prec:"≺",precapprox:"⪷",preccurlyeq:"≼",Precedes:"≺",PrecedesEqual:"⪯",PrecedesSlantEqual:"≼",PrecedesTilde:"≾",preceq:"⪯",precnapprox:"⪹",precneqq:"⪵",precnsim:"⋨",precsim:"≾",Prime:"″",prime:"′",primes:"ℙ",prnap:"⪹",prnE:"⪵",prnsim:"⋨",prod:"∏",Product:"∏",profalar:"⌮",profline:"⌒",profsurf:"⌓",prop:"∝",Proportion:"∷",Proportional:"∝",propto:"∝",prsim:"≾",prurel:"⊰",Pscr:"𝒫",pscr:"𝓅",Psi:"Ψ",psi:"ψ",puncsp:" ",Qfr:"𝔔",qfr:"𝔮",qint:"⨌",Qopf:"ℚ",qopf:"𝕢",qprime:"⁗",Qscr:"𝒬",qscr:"𝓆",quaternions:"ℍ",quatint:"⨖",quest:"?",questeq:"≟",QUOT:'"',quot:'"',rAarr:"⇛",race:"∽̱",Racute:"Ŕ",racute:"ŕ",radic:"√",raemptyv:"⦳",Rang:"⟫",rang:"⟩",rangd:"⦒",range:"⦥",rangle:"⟩",raquo:"»",Rarr:"↠",rArr:"⇒",rarr:"→",rarrap:"⥵",rarrb:"⇥",rarrbfs:"⤠",rarrc:"⤳",rarrfs:"⤞",rarrhk:"↪",rarrlp:"↬",rarrpl:"⥅",rarrsim:"⥴",Rarrtl:"⤖",rarrtl:"↣",rarrw:"↝",rAtail:"⤜",ratail:"⤚",ratio:"∶",rationals:"ℚ",RBarr:"⤐",rBarr:"⤏",rbarr:"⤍",rbbrk:"❳",rbrace:"}",rbrack:"]",rbrke:"⦌",rbrksld:"⦎",rbrkslu:"⦐",Rcaron:"Ř",rcaron:"ř",Rcedil:"Ŗ",rcedil:"ŗ",rceil:"⌉",rcub:"}",Rcy:"Р",rcy:"р",rdca:"⤷",rdldhar:"⥩",rdquo:"”",rdquor:"”",rdsh:"↳",Re:"ℜ",real:"ℜ",realine:"ℛ",realpart:"ℜ",reals:"ℝ",rect:"▭",REG:"®",reg:"®",ReverseElement:"∋",ReverseEquilibrium:"⇋",ReverseUpEquilibrium:"⥯",rfisht:"⥽",rfloor:"⌋",Rfr:"ℜ",rfr:"𝔯",rHar:"⥤",rhard:"⇁",rharu:"⇀",rharul:"⥬",Rho:"Ρ",rho:"ρ",rhov:"ϱ",RightAngleBracket:"⟩",RightArrow:"→",Rightarrow:"⇒",rightarrow:"→",RightArrowBar:"⇥",RightArrowLeftArrow:"⇄",rightarrowtail:"↣",RightCeiling:"⌉",RightDoubleBracket:"⟧",RightDownTeeVector:"⥝",RightDownVector:"⇂",RightDownVectorBar:"⥕",RightFloor:"⌋",rightharpoondown:"⇁",rightharpoonup:"⇀",rightleftarrows:"⇄",rightleftharpoons:"⇌",rightrightarrows:"⇉",rightsquigarrow:"↝",RightTee:"⊢",RightTeeArrow:"↦",RightTeeVector:"⥛",rightthreetimes:"⋌",RightTriangle:"⊳",RightTriangleBar:"⧐",RightTriangleEqual:"⊵",RightUpDownVector:"⥏",RightUpTeeVector:"⥜",RightUpVector:"↾",RightUpVectorBar:"⥔",RightVector:"⇀",RightVectorBar:"⥓",ring:"˚",risingdotseq:"≓",rlarr:"⇄",rlhar:"⇌",rlm:"‏",rmoust:"⎱",rmoustache:"⎱",rnmid:"⫮",roang:"⟭",roarr:"⇾",robrk:"⟧",ropar:"⦆",Ropf:"ℝ",ropf:"𝕣",roplus:"⨮",rotimes:"⨵",RoundImplies:"⥰",rpar:")",rpargt:"⦔",rppolint:"⨒",rrarr:"⇉",Rrightarrow:"⇛",rsaquo:"›",Rscr:"ℛ",rscr:"𝓇",Rsh:"↱",rsh:"↱",rsqb:"]",rsquo:"’",rsquor:"’",rthree:"⋌",rtimes:"⋊",rtri:"▹",rtrie:"⊵",rtrif:"▸",rtriltri:"⧎",RuleDelayed:"⧴",ruluhar:"⥨",rx:"℞",Sacute:"Ś",sacute:"ś",sbquo:"‚",Sc:"⪼",sc:"≻",scap:"⪸",Scaron:"Š",scaron:"š",sccue:"≽",scE:"⪴",sce:"⪰",Scedil:"Ş",scedil:"ş",Scirc:"Ŝ",scirc:"ŝ",scnap:"⪺",scnE:"⪶",scnsim:"⋩",scpolint:"⨓",scsim:"≿",Scy:"С",scy:"с",sdot:"⋅",sdotb:"⊡",sdote:"⩦",searhk:"⤥",seArr:"⇘",searr:"↘",searrow:"↘",sect:"§",semi:";",seswar:"⤩",setminus:"∖",setmn:"∖",sext:"✶",Sfr:"𝔖",sfr:"𝔰",sfrown:"⌢",sharp:"♯",SHCHcy:"Щ",shchcy:"щ",SHcy:"Ш",shcy:"ш",ShortDownArrow:"↓",ShortLeftArrow:"←",shortmid:"∣",shortparallel:"∥",ShortRightArrow:"→",ShortUpArrow:"↑",shy:"­",Sigma:"Σ",sigma:"σ",sigmaf:"ς",sigmav:"ς",sim:"∼",simdot:"⩪",sime:"≃",simeq:"≃",simg:"⪞",simgE:"⪠",siml:"⪝",simlE:"⪟",simne:"≆",simplus:"⨤",simrarr:"⥲",slarr:"←",SmallCircle:"∘",smallsetminus:"∖",smashp:"⨳",smeparsl:"⧤",smid:"∣",smile:"⌣",smt:"⪪",smte:"⪬",smtes:"⪬︀",SOFTcy:"Ь",softcy:"ь",sol:"/",solb:"⧄",solbar:"⌿",Sopf:"𝕊",sopf:"𝕤",spades:"♠",spadesuit:"♠",spar:"∥",sqcap:"⊓",sqcaps:"⊓︀",sqcup:"⊔",sqcups:"⊔︀",Sqrt:"√",sqsub:"⊏",sqsube:"⊑",sqsubset:"⊏",sqsubseteq:"⊑",sqsup:"⊐",sqsupe:"⊒",sqsupset:"⊐",sqsupseteq:"⊒",squ:"□",Square:"□",square:"□",SquareIntersection:"⊓",SquareSubset:"⊏",SquareSubsetEqual:"⊑",SquareSuperset:"⊐",SquareSupersetEqual:"⊒",SquareUnion:"⊔",squarf:"▪",squf:"▪",srarr:"→",Sscr:"𝒮",sscr:"𝓈",ssetmn:"∖",ssmile:"⌣",sstarf:"⋆",Star:"⋆",star:"☆",starf:"★",straightepsilon:"ϵ",straightphi:"ϕ",strns:"¯",Sub:"⋐",sub:"⊂",subdot:"⪽",subE:"⫅",sube:"⊆",subedot:"⫃",submult:"⫁",subnE:"⫋",subne:"⊊",subplus:"⪿",subrarr:"⥹",Subset:"⋐",subset:"⊂",subseteq:"⊆",subseteqq:"⫅",SubsetEqual:"⊆",subsetneq:"⊊",subsetneqq:"⫋",subsim:"⫇",subsub:"⫕",subsup:"⫓",succ:"≻",succapprox:"⪸",succcurlyeq:"≽",Succeeds:"≻",SucceedsEqual:"⪰",SucceedsSlantEqual:"≽",SucceedsTilde:"≿",succeq:"⪰",succnapprox:"⪺",succneqq:"⪶",succnsim:"⋩",succsim:"≿",SuchThat:"∋",Sum:"∑",sum:"∑",sung:"♪",Sup:"⋑",sup:"⊃",sup1:"¹",sup2:"²",sup3:"³",supdot:"⪾",supdsub:"⫘",supE:"⫆",supe:"⊇",supedot:"⫄",Superset:"⊃",SupersetEqual:"⊇",suphsol:"⟉",suphsub:"⫗",suplarr:"⥻",supmult:"⫂",supnE:"⫌",supne:"⊋",supplus:"⫀",Supset:"⋑",supset:"⊃",supseteq:"⊇",supseteqq:"⫆",supsetneq:"⊋",supsetneqq:"⫌",supsim:"⫈",supsub:"⫔",supsup:"⫖",swarhk:"⤦",swArr:"⇙",swarr:"↙",swarrow:"↙",swnwar:"⤪",szlig:"ß",Tab:"\t",target:"⌖",Tau:"Τ",tau:"τ",tbrk:"⎴",Tcaron:"Ť",tcaron:"ť",Tcedil:"Ţ",tcedil:"ţ",Tcy:"Т",tcy:"т",tdot:"⃛",telrec:"⌕",Tfr:"𝔗",tfr:"𝔱",there4:"∴",Therefore:"∴",therefore:"∴",Theta:"Θ",theta:"θ",thetasym:"ϑ",thetav:"ϑ",thickapprox:"≈",thicksim:"∼",ThickSpace:"  ",thinsp:" ",ThinSpace:" ",thkap:"≈",thksim:"∼",THORN:"Þ",thorn:"þ",Tilde:"∼",tilde:"˜",TildeEqual:"≃",TildeFullEqual:"≅",TildeTilde:"≈",times:"×",timesb:"⊠",timesbar:"⨱",timesd:"⨰",tint:"∭",toea:"⤨",top:"⊤",topbot:"⌶",topcir:"⫱",Topf:"𝕋",topf:"𝕥",topfork:"⫚",tosa:"⤩",tprime:"‴",TRADE:"™",trade:"™",triangle:"▵",triangledown:"▿",triangleleft:"◃",trianglelefteq:"⊴",triangleq:"≜",triangleright:"▹",trianglerighteq:"⊵",tridot:"◬",trie:"≜",triminus:"⨺",TripleDot:"⃛",triplus:"⨹",trisb:"⧍",tritime:"⨻",trpezium:"⏢",Tscr:"𝒯",tscr:"𝓉",TScy:"Ц",tscy:"ц",TSHcy:"Ћ",tshcy:"ћ",Tstrok:"Ŧ",tstrok:"ŧ",twixt:"≬",twoheadleftarrow:"↞",twoheadrightarrow:"↠",Uacute:"Ú",uacute:"ú",Uarr:"↟",uArr:"⇑",uarr:"↑",Uarrocir:"⥉",Ubrcy:"Ў",ubrcy:"ў",Ubreve:"Ŭ",ubreve:"ŭ",Ucirc:"Û",ucirc:"û",Ucy:"У",ucy:"у",udarr:"⇅",Udblac:"Ű",udblac:"ű",udhar:"⥮",ufisht:"⥾",Ufr:"𝔘",ufr:"𝔲",Ugrave:"Ù",ugrave:"ù",uHar:"⥣",uharl:"↿",uharr:"↾",uhblk:"▀",ulcorn:"⌜",ulcorner:"⌜",ulcrop:"⌏",ultri:"◸",Umacr:"Ū",umacr:"ū",uml:"¨",UnderBar:"_",UnderBrace:"⏟",UnderBracket:"⎵",UnderParenthesis:"⏝",Union:"⋃",UnionPlus:"⊎",Uogon:"Ų",uogon:"ų",Uopf:"𝕌",uopf:"𝕦",UpArrow:"↑",Uparrow:"⇑",uparrow:"↑",UpArrowBar:"⤒",UpArrowDownArrow:"⇅",UpDownArrow:"↕",Updownarrow:"⇕",updownarrow:"↕",UpEquilibrium:"⥮",upharpoonleft:"↿",upharpoonright:"↾",uplus:"⊎",UpperLeftArrow:"↖",UpperRightArrow:"↗",Upsi:"ϒ",upsi:"υ",upsih:"ϒ",Upsilon:"Υ",upsilon:"υ",UpTee:"⊥",UpTeeArrow:"↥",upuparrows:"⇈",urcorn:"⌝",urcorner:"⌝",urcrop:"⌎",Uring:"Ů",uring:"ů",urtri:"◹",Uscr:"𝒰",uscr:"𝓊",utdot:"⋰",Utilde:"Ũ",utilde:"ũ",utri:"▵",utrif:"▴",uuarr:"⇈",Uuml:"Ü",uuml:"ü",uwangle:"⦧",vangrt:"⦜",varepsilon:"ϵ",varkappa:"ϰ",varnothing:"∅",varphi:"ϕ",varpi:"ϖ",varpropto:"∝",vArr:"⇕",varr:"↕",varrho:"ϱ",varsigma:"ς",varsubsetneq:"⊊︀",varsubsetneqq:"⫋︀",varsupsetneq:"⊋︀",varsupsetneqq:"⫌︀",vartheta:"ϑ",vartriangleleft:"⊲",vartriangleright:"⊳",Vbar:"⫫",vBar:"⫨",vBarv:"⫩",Vcy:"В",vcy:"в",VDash:"⊫",Vdash:"⊩",vDash:"⊨",vdash:"⊢",Vdashl:"⫦",Vee:"⋁",vee:"∨",veebar:"⊻",veeeq:"≚",vellip:"⋮",Verbar:"‖",verbar:"|",Vert:"‖",vert:"|",VerticalBar:"∣",VerticalLine:"|",VerticalSeparator:"❘",VerticalTilde:"≀",VeryThinSpace:" ",Vfr:"𝔙",vfr:"𝔳",vltri:"⊲",vnsub:"⊂⃒",vnsup:"⊃⃒",Vopf:"𝕍",vopf:"𝕧",vprop:"∝",vrtri:"⊳",Vscr:"𝒱",vscr:"𝓋",vsubnE:"⫋︀",vsubne:"⊊︀",vsupnE:"⫌︀",vsupne:"⊋︀",Vvdash:"⊪",vzigzag:"⦚",Wcirc:"Ŵ",wcirc:"ŵ",wedbar:"⩟",Wedge:"⋀",wedge:"∧",wedgeq:"≙",weierp:"℘",Wfr:"𝔚",wfr:"𝔴",Wopf:"𝕎",wopf:"𝕨",wp:"℘",wr:"≀",wreath:"≀",Wscr:"𝒲",wscr:"𝓌",xcap:"⋂",xcirc:"◯",xcup:"⋃",xdtri:"▽",Xfr:"𝔛",xfr:"𝔵",xhArr:"⟺",xharr:"⟷",Xi:"Ξ",xi:"ξ",xlArr:"⟸",xlarr:"⟵",xmap:"⟼",xnis:"⋻",xodot:"⨀",Xopf:"𝕏",xopf:"𝕩",xoplus:"⨁",xotime:"⨂",xrArr:"⟹",xrarr:"⟶",Xscr:"𝒳",xscr:"𝓍",xsqcup:"⨆",xuplus:"⨄",xutri:"△",xvee:"⋁",xwedge:"⋀",Yacute:"Ý",yacute:"ý",YAcy:"Я",yacy:"я",Ycirc:"Ŷ",ycirc:"ŷ",Ycy:"Ы",ycy:"ы",yen:"¥",Yfr:"𝔜",yfr:"𝔶",YIcy:"Ї",yicy:"ї",Yopf:"𝕐",yopf:"𝕪",Yscr:"𝒴",yscr:"𝓎",YUcy:"Ю",yucy:"ю",Yuml:"Ÿ",yuml:"ÿ",Zacute:"Ź",zacute:"ź",Zcaron:"Ž",zcaron:"ž",Zcy:"З",zcy:"з",Zdot:"Ż",zdot:"ż",zeetrf:"ℨ",ZeroWidthSpace:"​",Zeta:"Ζ",zeta:"ζ",Zfr:"ℨ",zfr:"𝔷",ZHcy:"Ж",zhcy:"ж",zigrarr:"⇝",Zopf:"ℤ",zopf:"𝕫",Zscr:"𝒵",zscr:"𝓏",zwj:"‍",zwnj:"‌"},t=Object.prototype.hasOwnProperty;function n(e){return o=e,(n=r)&&t.call(n,o)?r[e]:e;var n,o}var o=Object.prototype.hasOwnProperty;function s(e){var r=[].slice.call(arguments,1);return r.forEach((function(r){if(r){if("object"!=typeof r)throw new TypeError(r+"must be object");Object.keys(r).forEach((function(t){e[t]=r[t]}))}})),e}var i=/\\([\\!"#$%&'()*+,.\/:;<=>?@[\]^_`{|}~-])/g;function l(e){return e.indexOf("\\")<0?e:e.replace(i,"$1")}function a(e){return!(e>=55296&&e<=57343)&&(!(e>=64976&&e<=65007)&&(65535!=(65535&e)&&65534!=(65535&e)&&(!(e>=0&&e<=8)&&(11!==e&&(!(e>=14&&e<=31)&&(!(e>=127&&e<=159)&&!(e>1114111)))))))}function c(e){if(e>65535){var r=55296+((e-=65536)>>10),t=56320+(1023&e);return String.fromCharCode(r,t)}return String.fromCharCode(e)}var p=/&([a-z#][a-z0-9]{1,31});/gi,u=/^#((?:x[a-f0-9]{1,8}|[0-9]{1,8}))/i;function h(e,r){var t=0,o=n(r);return r!==o?o:35===r.charCodeAt(0)&&u.test(r)&&a(t="x"===r[1].toLowerCase()?parseInt(r.slice(2),16):parseInt(r.slice(1),10))?c(t):e}function f(e){return e.indexOf("&")<0?e:e.replace(p,h)}var d=/[&<>"]/,g=/[&<>"]/g,b={"&":"&","<":"<",">":">",'"':"""};function v(e){return b[e]}function m(e){return d.test(e)?e.replace(g,v):e}var k={};k.blockquote_open=function(){return"
      \n"},k.blockquote_close=function(e,r){return"
      "+y(e,r)},k.code=function(e,r){return e[r].block?"
      "+m(e[r].content)+"
      "+y(e,r):""+m(e[r].content)+""},k.fence=function(e,r,t,n,s){var i,a,c,p,u=e[r],h="",d=t.langPrefix;if(u.params){if(a=(i=u.params.split(/\s+/g)).join(" "),c=s.rules.fence_custom,p=i[0],c&&o.call(c,p))return s.rules.fence_custom[i[0]](e,r,t,n,s);h=' class="'+d+m(f(l(a)))+'"'}return"
      "+(t.highlight&&t.highlight.apply(t.highlight,[u.content].concat(i))||m(u.content))+"
      "+y(e,r)},k.fence_custom={},k.heading_open=function(e,r){return""},k.heading_close=function(e,r){return"\n"},k.hr=function(e,r,t){return(t.xhtmlOut?"
      ":"
      ")+y(e,r)},k.bullet_list_open=function(){return"
        \n"},k.bullet_list_close=function(e,r){return"
      "+y(e,r)},k.list_item_open=function(){return"
    • "},k.list_item_close=function(){return"
    • \n"},k.ordered_list_open=function(e,r){var t=e[r];return"1?' start="'+t.order+'"':"")+">\n"},k.ordered_list_close=function(e,r){return""+y(e,r)},k.paragraph_open=function(e,r){return e[r].tight?"":"

      "},k.paragraph_close=function(e,r){var t=!(e[r].tight&&r&&"inline"===e[r-1].type&&!e[r-1].content);return(e[r].tight?"":"

      ")+(t?y(e,r):"")},k.link_open=function(e,r,t){var n=e[r].title?' title="'+m(f(e[r].title))+'"':"",o=t.linkTarget?' target="'+t.linkTarget+'"':"";return'"},k.link_close=function(){return""},k.image=function(e,r,t){var n=' src="'+m(e[r].src)+'"',o=e[r].title?' title="'+m(f(e[r].title))+'"':"";return""},k.table_open=function(){return"\n"},k.table_close=function(){return"
      \n"},k.thead_open=function(){return"\n"},k.thead_close=function(){return"\n"},k.tbody_open=function(){return"\n"},k.tbody_close=function(){return"\n"},k.tr_open=function(){return""},k.tr_close=function(){return"\n"},k.th_open=function(e,r){var t=e[r];return""},k.th_close=function(){return""},k.td_open=function(e,r){var t=e[r];return""},k.td_close=function(){return""},k.strong_open=function(){return""},k.strong_close=function(){return""},k.em_open=function(){return""},k.em_close=function(){return""},k.del_open=function(){return""},k.del_close=function(){return""},k.ins_open=function(){return""},k.ins_close=function(){return""},k.mark_open=function(){return""},k.mark_close=function(){return""},k.sub=function(e,r){return""+m(e[r].content)+""},k.sup=function(e,r){return""+m(e[r].content)+""},k.hardbreak=function(e,r,t){return t.xhtmlOut?"
      \n":"
      \n"},k.softbreak=function(e,r,t){return t.breaks?t.xhtmlOut?"
      \n":"
      \n":"\n"},k.text=function(e,r){return m(e[r].content)},k.htmlblock=function(e,r){return e[r].content},k.htmltag=function(e,r){return e[r].content},k.abbr_open=function(e,r){return''},k.abbr_close=function(){return""},k.footnote_ref=function(e,r){var t=Number(e[r].id+1).toString(),n="fnref"+t;return e[r].subId>0&&(n+=":"+e[r].subId),'['+t+"]"},k.footnote_block_open=function(e,r,t){return(t.xhtmlOut?'
      \n':'
      \n')+'
      \n
        \n'},k.footnote_block_close=function(){return"
      \n
      \n"},k.footnote_open=function(e,r){return'
    • '},k.footnote_close=function(){return"
    • \n"},k.footnote_anchor=function(e,r){var t="fnref"+Number(e[r].id+1).toString();return e[r].subId>0&&(t+=":"+e[r].subId),' '},k.dl_open=function(){return"
      \n"},k.dt_open=function(){return"
      "},k.dd_open=function(){return"
      "},k.dl_close=function(){return"
      \n"},k.dt_close=function(){return"\n"},k.dd_close=function(){return"\n"};var y=k.getBreak=function(e,r){return(r=function e(r,t){return++t>=r.length-2?t:"paragraph_open"===r[t].type&&r[t].tight&&"inline"===r[t+1].type&&0===r[t+1].content.length&&"paragraph_close"===r[t+2].type&&r[t+2].tight?e(r,t+2):t}(e,r))1)break;if(41===t&&--n<0)break;r++}return s!==r&&(o=l(e.src.slice(s,r)),!!e.parser.validateLink(o)&&(e.linkContent=o,e.pos=r,!0))}function E(e,r){var t,n=r,o=e.posMax,s=e.src.charCodeAt(r);if(34!==s&&39!==s&&40!==s)return!1;for(r++,40===s&&(s=41);r=e.length)&&!z.test(e[r])}function V(e,r,t){return e.substr(0,r)+t+e.substr(r+1)}var B=[["block",function(e){e.inlineMode?e.tokens.push({type:"inline",content:e.src.replace(/\n/g," ").trim(),level:0,lines:[0,1],children:[]}):e.block.parse(e.src,e.options,e.env,e.tokens)}],["abbr",function(e){var r,t,n,o,s=e.tokens;if(!e.inlineMode)for(r=1,t=s.length-1;r0?i[r].count:1,n=0;n=0;r--)if("text"===(s=o[r]).type){for(a=0,i=s.content,p.lastIndex=0,c=s.level,l=[];u=p.exec(i);)p.lastIndex>a&&l.push({type:"text",content:i.slice(a,u.index+u[1].length),level:c}),l.push({type:"abbr_open",title:e.env.abbreviations[":"+u[2]],level:c++}),l.push({type:"text",content:u[2],level:c}),l.push({type:"abbr_close",level:--c}),a=p.lastIndex-u[3].length;l.length&&(a=0;s--)if("inline"===e.tokens[s].type)for(r=(o=e.tokens[s].children).length-1;r>=0;r--)"text"===(t=o[r]).type&&(n=t.content,n=(i=n).indexOf("(")<0?i:i.replace(I,(function(e,r){return N[r.toLowerCase()]})),D.test(n)&&(n=n.replace(/\+-/g,"±").replace(/\.{2,}/g,"…").replace(/([?!])…/g,"$1..").replace(/([?!]){4,}/g,"$1$1$1").replace(/,{2,}/g,",").replace(/(^|[^-])---([^-]|$)/gm,"$1—$2").replace(/(^|\s)--(\s|$)/gm,"$1–$2").replace(/(^|[^-\s])--([^-\s]|$)/gm,"$1–$2")),t.content=n)}],["smartquotes",function(e){var r,t,n,o,s,i,l,a,c,p,u,h,f,d,g,b,v;if(e.options.typographer)for(v=[],g=e.tokens.length-1;g>=0;g--)if("inline"===e.tokens[g].type)for(b=e.tokens[g].children,v.length=0,r=0;r=0&&!(v[f].level<=l);f--);v.length=f+1,s=0,i=(n=t.content).length;e:for(;s=0&&(p=v[f],!(v[f].level=(o=e.eMarks[r])||42!==(t=e.src.charCodeAt(n++))&&45!==t&&43!==t||n=o)return-1;if((t=e.src.charCodeAt(n++))<48||t>57)return-1;for(;;){if(n>=o)return-1;if(!((t=e.src.charCodeAt(n++))>=48&&t<=57)){if(41===t||46===t)break;return-1}}return n=this.eMarks[e]},j.prototype.skipEmptyLines=function(e){for(var r=this.lineMax;et;)if(r!==this.src.charCodeAt(--e))return e+1;return e},j.prototype.getLines=function(e,r,t,n){var o,s,i,l,a,c=e;if(e>=r)return"";if(c+1===r)return s=this.bMarks[c]+Math.min(this.tShift[c],t),i=n?this.eMarks[c]+1:this.eMarks[c],this.src.slice(s,i);for(l=new Array(r-e),o=0;ct&&(a=t),a<0&&(a=0),s=this.bMarks[c]+a,i=c+1]/,F=/^<\/([a-zA-Z]{1,15})[\s>]/;function J(e,r){var t=e.bMarks[r]+e.blkIndent,n=e.eMarks[r];return e.src.substr(t,n-t)}function Y(e,r){var t,n,o=e.bMarks[r]+e.tShift[r],s=e.eMarks[r];return o>=s||126!==(n=e.src.charCodeAt(o++))&&58!==n||o===(t=e.skipSpaces(o))||t>=s?-1:t}var W=[["code",function(e,r,t){var n,o;if(e.tShift[r]-e.blkIndent<4)return!1;for(o=n=r+1;n=4))break;o=++n}return e.line=n,e.tokens.push({type:"code",content:e.getLines(r,o,4+e.blkIndent,!0),block:!0,lines:[r,e.line],level:e.level}),!0}],["fences",function(e,r,t,n){var o,s,i,l,a,c=!1,p=e.bMarks[r]+e.tShift[r],u=e.eMarks[r];if(p+3>u)return!1;if(126!==(o=e.src.charCodeAt(p))&&96!==o)return!1;if(a=p,(s=(p=e.skipChars(p,o))-a)<3)return!1;if((i=e.src.slice(p,u).trim()).indexOf("`")>=0)return!1;if(n)return!0;for(l=r;!(++l>=t)&&!((p=a=e.bMarks[l]+e.tShift[l])<(u=e.eMarks[l])&&e.tShift[l]=4||(p=e.skipChars(p,o))-ab)return!1;if(62!==e.src.charCodeAt(g++))return!1;if(e.level>=e.options.maxNesting)return!1;if(n)return!0;for(32===e.src.charCodeAt(g)&&g++,a=e.blkIndent,e.blkIndent=0,l=[e.bMarks[r]],e.bMarks[r]=g,s=(g=g=b,i=[e.tShift[r]],e.tShift[r]=g-e.bMarks[r],u=e.parser.ruler.getRules("blockquote"),o=r+1;o=(b=e.eMarks[o]));o++)if(62!==e.src.charCodeAt(g++)){if(s)break;for(d=!1,h=0,f=u.length;h=b,i.push(e.tShift[o]),e.tShift[o]=g-e.bMarks[o];for(c=e.parentType,e.parentType="blockquote",e.tokens.push({type:"blockquote_open",lines:p=[r,0],level:e.level++}),e.parser.tokenize(e,r,o),e.tokens.push({type:"blockquote_close",level:--e.level}),e.parentType=c,p[1]=e.line,h=0;ha)return!1;if(42!==(o=e.src.charCodeAt(l++))&&45!==o&&95!==o)return!1;for(s=1;l=0)g=!0;else{if(!((u=P(e,r))>=0))return!1;g=!1}if(e.level>=e.options.maxNesting)return!1;if(d=e.src.charCodeAt(u-1),n)return!0;for(v=e.tokens.length,g?(p=e.bMarks[r]+e.tShift[r],f=Number(e.src.substr(p,u-p-1)),e.tokens.push({type:"ordered_list_open",order:f,lines:k=[r,0],level:e.level++})):e.tokens.push({type:"bullet_list_open",lines:k=[r,0],level:e.level++}),o=r,m=!1,_=e.parser.ruler.getRules("list");!(!(o=e.eMarks[o]?1:b-u)>4&&(h=1),h<1&&(h=1),s=u-e.bMarks[o]+h,e.tokens.push({type:"list_item_open",lines:y=[r,0],level:e.level++}),l=e.blkIndent,a=e.tight,i=e.tShift[r],c=e.parentType,e.tShift[r]=b-e.bMarks[r],e.blkIndent=s,e.tight=!0,e.parentType="list",e.parser.tokenize(e,r,t,!0),e.tight&&!m||(A=!1),m=e.line-r>1&&e.isEmpty(e.line-1),e.blkIndent=l,e.tShift[r]=i,e.tight=a,e.parentType=c,e.tokens.push({type:"list_item_close",level:--e.level}),o=r=e.line,y[1]=o,b=e.bMarks[r],o>=t)||e.isEmpty(o)||e.tShift[o]p)return!1;if(91!==e.src.charCodeAt(c))return!1;if(94!==e.src.charCodeAt(c+1))return!1;if(e.level>=e.options.maxNesting)return!1;for(l=c+2;l=p||58!==e.src.charCodeAt(++l))&&(n||(l++,e.env.footnotes||(e.env.footnotes={}),e.env.footnotes.refs||(e.env.footnotes.refs={}),a=e.src.slice(c+2,l-2),e.env.footnotes.refs[":"+a]=-1,e.tokens.push({type:"footnote_reference_open",label:a,level:e.level++}),o=e.bMarks[r],s=e.tShift[r],i=e.parentType,e.tShift[r]=e.skipSpaces(l)-l,e.bMarks[r]=l,e.blkIndent+=4,e.parentType="footnote",e.tShift[r]=a)return!1;if(35!==(o=e.src.charCodeAt(l))||l>=a)return!1;for(s=1,o=e.src.charCodeAt(++l);35===o&&l6||ll&&32===e.src.charCodeAt(i-1)&&(a=i),e.line=r+1,e.tokens.push({type:"heading_open",hLevel:s,lines:[r,e.line],level:e.level}),l=t)&&(!(e.tShift[i]3)&&(!((o=e.bMarks[i]+e.tShift[i])>=(s=e.eMarks[i]))&&((45===(n=e.src.charCodeAt(o))||61===n)&&(o=e.skipChars(o,n),!((o=e.skipSpaces(o))3||l+2>=a)return!1;if(60!==e.src.charCodeAt(l))return!1;if(33===(o=e.src.charCodeAt(l+1))||63===o){if(n)return!0}else{if(47!==o&&!function(e){var r=32|e;return r>=97&&r<=122}(o))return!1;if(47===o){if(!(s=e.src.slice(l,a).match(F)))return!1}else if(!(s=e.src.slice(l,a).match(Z)))return!1;if(!0!==H[s[1].toLowerCase()])return!1;if(n)return!0}for(i=r+1;it)return!1;if(a=r+1,e.tShift[a]=e.eMarks[a])return!1;if(124!==(o=e.src.charCodeAt(i))&&45!==o&&58!==o)return!1;if(s=J(e,r+1),!/^[-:| ]+$/.test(s))return!1;if((c=s.split("|"))<=2)return!1;for(u=[],l=0;l=0;if(p=r+1,e.isEmpty(p)&&++p>t)return!1;if(e.tShift[p]=e.options.maxNesting)return!1;c=e.tokens.length,e.tokens.push({type:"dl_open",lines:a=[r,0],level:e.level++}),i=r,s=p;e:for(;;){for(v=!0,b=!1,e.tokens.push({type:"dt_open",lines:[i,i],level:e.level++}),e.tokens.push({type:"inline",content:e.getLines(i,i+1,e.blkIndent,!1).trim(),level:e.level+1,lines:[i,i],children:[]}),e.tokens.push({type:"dt_close",level:--e.level});;){if(e.tokens.push({type:"dd_open",lines:l=[p,0],level:e.level++}),g=e.tight,h=e.ddIndent,u=e.blkIndent,d=e.tShift[s],f=e.parentType,e.blkIndent=e.ddIndent=e.tShift[s]+2,e.tShift[s]=o-e.bMarks[s],e.tight=!0,e.parentType="deflist",e.parser.tokenize(e,s,t,!0),e.tight&&!b||(v=!1),b=e.line-s>1&&e.isEmpty(e.line-1),e.tShift[s]=d,e.tight=g,e.parentType=f,e.blkIndent=u,e.ddIndent=h,e.tokens.push({type:"dd_close",level:--e.level}),l[1]=p=e.line,p>=t)break e;if(e.tShift[p]=t)break;if(i=p,e.isEmpty(i))break;if(e.tShift[i]=t)break;if(e.isEmpty(s)&&s++,s>=t)break;if(e.tShift[s]3)){for(o=!1,s=0,i=l.length;s=t))&&!(e.tShift[i]=0&&(e=e.replace(Q,(function(r,t){var n;return 10===e.charCodeAt(t)?(s=t+1,i=0,r):(n=" ".slice((t-s-i)%4),i=t-s+1,n)}))),o=new j(e,this,r,t,n),this.tokenize(o,o.line,o.lineMax)};for(var te=[],ne=0;ne<256;ne++)te.push(0);function oe(e){return e>=48&&e<=57||e>=65&&e<=90||e>=97&&e<=122}function se(e,r){var t,n,o,s=r,i=!0,l=!0,a=e.posMax,c=e.src.charCodeAt(r);for(t=r>0?e.src.charCodeAt(r-1):-1;s=a&&(i=!1),(o=s-r)>=4?i=l=!1:(32!==(n=s?@[]^_`{|}~-".split("").forEach((function(e){te[e.charCodeAt(0)]=1}));var ie=/\\([ \\!"#$%&'()*+,.\/:;<=>?@[\]^_`{|}~-])/g;var le=/\\([ \\!"#$%&'()*+,.\/:;<=>?@[\]^_`{|}~-])/g;var ae=["coap","doi","javascript","aaa","aaas","about","acap","cap","cid","crid","data","dav","dict","dns","file","ftp","geo","go","gopher","h323","http","https","iax","icap","im","imap","info","ipp","iris","iris.beep","iris.xpc","iris.xpcs","iris.lwz","ldap","mailto","mid","msrp","msrps","mtqp","mupdate","news","nfs","ni","nih","nntp","opaquelocktoken","pop","pres","rtsp","service","session","shttp","sieve","sip","sips","sms","snmp","soap.beep","soap.beeps","tag","tel","telnet","tftp","thismessage","tn3270","tip","tv","urn","vemmi","ws","wss","xcon","xcon-userid","xmlrpc.beep","xmlrpc.beeps","xmpp","z39.50r","z39.50s","adiumxtra","afp","afs","aim","apt","attachment","aw","beshare","bitcoin","bolo","callto","chrome","chrome-extension","com-eventbrite-attendee","content","cvs","dlna-playsingle","dlna-playcontainer","dtn","dvb","ed2k","facetime","feed","finger","fish","gg","git","gizmoproject","gtalk","hcp","icon","ipn","irc","irc6","ircs","itms","jar","jms","keyparc","lastfm","ldaps","magnet","maps","market","message","mms","ms-help","msnim","mumble","mvn","notes","oid","palm","paparazzi","platform","proxy","psyc","query","res","resource","rmi","rsync","rtmp","secondlife","sftp","sgn","skype","smb","soldat","spotify","ssh","steam","svn","teamspeak","things","udp","unreal","ut2004","ventrilo","view-source","webcal","wtai","wyciwyg","xfire","xri","ymsgr"],ce=/^<([a-zA-Z0-9.!#$%&'*+\/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*)>/,pe=/^<([a-zA-Z.\-]{1,25}):([^<>\x00-\x20]*)>/;function ue(e,r){return e=e.source,r=r||"",function t(n,o){return n?(o=o.source||o,e=e.replace(n,o),t):new RegExp(e,r)}}var he=ue(/(?:unquoted|single_quoted|double_quoted)/)("unquoted",/[^"'=<>`\x00-\x20]+/)("single_quoted",/'[^']*'/)("double_quoted",/"[^"]*"/)(),fe=ue(/(?:\s+attr_name(?:\s*=\s*attr_value)?)/)("attr_name",/[a-zA-Z_:][a-zA-Z0-9:._-]*/)("attr_value",he)(),de=ue(/<[A-Za-z][A-Za-z0-9]*attribute*\s*\/?>/)("attribute",fe)(),ge=ue(/^(?:open_tag|close_tag|comment|processing|declaration|cdata)/)("open_tag",de)("close_tag",/<\/[A-Za-z][A-Za-z0-9]*\s*>/)("comment",/|/)("processing",/<[?].*?[?]>/)("declaration",/]*>/)("cdata",//)();var be=/^&#((?:x[a-f0-9]{1,8}|[0-9]{1,8}));/i,ve=/^&([a-z][a-z0-9]{1,31});/i;var me=[["text",function(e,r){for(var t=e.pos;t=0&&32===e.pending.charCodeAt(t))if(t>=1&&32===e.pending.charCodeAt(t-1)){for(var s=t-2;s>=0;s--)if(32!==e.pending.charCodeAt(s)){e.pending=e.pending.substring(0,s+1);break}e.push({type:"hardbreak",level:e.level})}else e.pending=e.pending.slice(0,-1),e.push({type:"softbreak",level:e.level});else e.push({type:"softbreak",level:e.level});for(o++;o=l)return!1;if(126!==e.src.charCodeAt(a+1))return!1;if(e.level>=e.options.maxNesting)return!1;if(s=a>0?e.src.charCodeAt(a-1):-1,i=e.src.charCodeAt(a+2),126===s)return!1;if(126===i)return!1;if(32===i||10===i)return!1;for(n=a+2;na+3)return e.pos+=n-a,r||(e.pending+=e.src.slice(a,n)),!0;for(e.pos=a+2,o=1;e.pos+1=l)return!1;if(43!==e.src.charCodeAt(a+1))return!1;if(e.level>=e.options.maxNesting)return!1;if(s=a>0?e.src.charCodeAt(a-1):-1,i=e.src.charCodeAt(a+2),43===s)return!1;if(43===i)return!1;if(32===i||10===i)return!1;for(n=a+2;n=l)return!1;if(61!==e.src.charCodeAt(a+1))return!1;if(e.level>=e.options.maxNesting)return!1;if(s=a>0?e.src.charCodeAt(a-1):-1,i=e.src.charCodeAt(a+2),61===s)return!1;if(61===i)return!1;if(32===i||10===i)return!1;for(n=a+2;n=e.options.maxNesting)return!1;for(e.pos=p+t,l=[t];e.pos=o)return!1;if(e.level>=e.options.maxNesting)return!1;for(e.pos=s+1;e.pos=o)return!1;if(e.level>=e.options.maxNesting)return!1;for(e.pos=s+1;e.pos=e.options.maxNesting)return!1;if(t=f+1,(n=w(e,f))<0)return!1;if((l=n+1)=h)return!1;for(f=l,S(e,l)?(s=e.linkContent,l=e.pos):s="",f=l;l=h||41!==e.src.charCodeAt(l))return e.pos=u,!1;l++}else{if(e.linkLevel>0)return!1;for(;l=0?o=e.src.slice(f,l++):l=f-1),o||(void 0===o&&(l=n+1),o=e.src.slice(t,n)),!(a=e.env.references[L(o)]))return e.pos=u,!1;s=a.href,i=a.title}return r||(e.pos=t,e.posMax=n,p?e.push({type:"image",src:s,title:i,alt:e.src.substr(t,n-t),level:e.level}):(e.push({type:"link_open",href:s,title:i,level:e.level++}),e.linkLevel++,e.parser.tokenize(e),e.linkLevel--,e.push({type:"link_close",level:--e.level}))),e.pos=l,e.posMax=h,!0}],["footnote_inline",function(e,r){var t,n,o,s,i=e.posMax,l=e.pos;return!(l+2>=i)&&(94===e.src.charCodeAt(l)&&(91===e.src.charCodeAt(l+1)&&(!(e.level>=e.options.maxNesting)&&(t=l+2,!((n=w(e,l+1))<0)&&(r||(e.env.footnotes||(e.env.footnotes={}),e.env.footnotes.list||(e.env.footnotes.list=[]),o=e.env.footnotes.list.length,e.pos=t,e.posMax=n,e.push({type:"footnote_ref",id:o,level:e.level}),e.linkLevel++,s=e.tokens.length,e.parser.tokenize(e),e.env.footnotes.list[o]={tokens:e.tokens.splice(s)},e.linkLevel--),e.pos=n+1,e.posMax=i,!0)))))}],["footnote_ref",function(e,r){var t,n,o,s,i=e.posMax,l=e.pos;if(l+3>i)return!1;if(!e.env.footnotes||!e.env.footnotes.refs)return!1;if(91!==e.src.charCodeAt(l))return!1;if(94!==e.src.charCodeAt(l+1))return!1;if(e.level>=e.options.maxNesting)return!1;for(n=l+2;n=i)&&(n++,t=e.src.slice(l+2,n-1),void 0!==e.env.footnotes.refs[":"+t]&&(r||(e.env.footnotes.list||(e.env.footnotes.list=[]),e.env.footnotes.refs[":"+t]<0?(o=e.env.footnotes.list.length,e.env.footnotes.list[o]={label:t,count:0},e.env.footnotes.refs[":"+t]=o):o=e.env.footnotes.refs[":"+t],s=e.env.footnotes.list[o].count,e.env.footnotes.list[o].count++,e.push({type:"footnote_ref",id:o,subId:s,level:e.level})),e.pos=n,e.posMax=i,!0)))}],["autolink",function(e,r){var t,n,o,s,i,l=e.pos;return 60===e.src.charCodeAt(l)&&(!((t=e.src.slice(l)).indexOf(">")<0)&&((n=t.match(pe))?!(ae.indexOf(n[1].toLowerCase())<0)&&(i=C(s=n[0].slice(1,-1)),!!e.parser.validateLink(s)&&(r||(e.push({type:"link_open",href:i,level:e.level}),e.push({type:"text",content:s,level:e.level+1}),e.push({type:"link_close",level:e.level})),e.pos+=n[0].length,!0)):!!(o=t.match(ce))&&(i=C("mailto:"+(s=o[0].slice(1,-1))),!!e.parser.validateLink(i)&&(r||(e.push({type:"link_open",href:i,level:e.level}),e.push({type:"text",content:s,level:e.level+1}),e.push({type:"link_close",level:e.level})),e.pos+=o[0].length,!0))))}],["htmltag",function(e,r){var t,n,o,s=e.pos;return!!e.options.html&&(o=e.posMax,!(60!==e.src.charCodeAt(s)||s+2>=o)&&(!(33!==(t=e.src.charCodeAt(s+1))&&63!==t&&47!==t&&!function(e){var r=32|e;return r>=97&&r<=122}(t))&&(!!(n=e.src.slice(s).match(ge))&&(r||e.push({type:"htmltag",content:e.src.slice(s,s+n[0].length),level:e.level}),e.pos+=n[0].length,!0))))}],["entity",function(e,r){var t,o,s=e.pos,i=e.posMax;if(38!==e.src.charCodeAt(s))return!1;if(s+10)e.pos=t;else{for(r=0;r=s)break}else e.pending+=e.src[e.pos++]}e.pending&&e.pushPending()},ke.prototype.parse=function(e,r,t,n){var o=new x(e,this,r,t,n);this.tokenize(o)};var _e={default:{options:{html:!1,xhtmlOut:!1,breaks:!1,langPrefix:"language-",linkTarget:"",typographer:!1,quotes:"“”‘’",highlight:null,maxNesting:20},components:{core:{rules:["block","inline","references","replacements","smartquotes","references","abbr2","footnote_tail"]},block:{rules:["blockquote","code","fences","footnote","heading","hr","htmlblock","lheading","list","paragraph","table"]},inline:{rules:["autolink","backticks","del","emphasis","entity","escape","footnote_ref","htmltag","links","newline","text"]}}},full:{options:{html:!1,xhtmlOut:!1,breaks:!1,langPrefix:"language-",linkTarget:"",typographer:!1,quotes:"“”‘’",highlight:null,maxNesting:20},components:{core:{},block:{},inline:{}}},commonmark:{options:{html:!0,xhtmlOut:!0,breaks:!1,langPrefix:"language-",linkTarget:"",typographer:!1,quotes:"“”‘’",highlight:null,maxNesting:20},components:{core:{rules:["block","inline","references","abbr2"]},block:{rules:["blockquote","code","fences","heading","hr","htmlblock","lheading","list","paragraph"]},inline:{rules:["autolink","backticks","emphasis","entity","escape","htmltag","links","newline","text"]}}}};function qe(e,r,t){this.src=r,this.env=t,this.options=e.options,this.tokens=[],this.inlineMode=!1,this.inline=e.inline,this.block=e.block,this.renderer=e.renderer,this.typographer=e.typographer}function xe(e,r){"string"!=typeof e&&(r=e,e="default"),r&&null!=r.linkify&&console.warn("linkify option is removed. Use linkify plugin instead:\n\nimport Remarkable from 'remarkable';\nimport linkify from 'remarkable/linkify';\nnew Remarkable().use(linkify)\n"),this.inline=new ke,this.block=new K,this.core=new G,this.renderer=new _,this.ruler=new q,this.options={},this.configure(_e[e]),this.set(r||{})}xe.prototype.set=function(e){s(this.options,e)},xe.prototype.configure=function(e){var r=this;if(!e)throw new Error("Wrong `remarkable` preset, check name/content");e.options&&r.set(e.options),e.components&&Object.keys(e.components).forEach((function(t){e.components[t].rules&&r[t].ruler.enable(e.components[t].rules,!0)}))},xe.prototype.use=function(e,r){return e(this,r),this},xe.prototype.parse=function(e,r){var t=new qe(this,e,r);return this.core.process(t),t.tokens},xe.prototype.render=function(e,r){return r=r||{},this.renderer.render(this.parse(e,r),this.options,r)},xe.prototype.parseInline=function(e,r){var t=new qe(this,e,r);return t.inlineMode=!0,this.core.process(t),t.tokens},xe.prototype.renderInline=function(e,r){return r=r||{},this.renderer.render(this.parseInline(e,r),this.options,r)};Math.random().toString(36).slice(2,8);function we(e){return e.replace(/[&<"]/g,e=>({"&":"&","<":"<",'"':"""}[e]))}function Ae(e,r){return`<${e}${r?Object.entries(r).map(([e,r])=>{if(null!=r&&!1!==r)return e=" "+we(e),!0===r?e:`${e}="${we(r)}"`}).filter(Boolean).join(""):""}>`}function Ce(e){return``}function Se(e,r,t){return null==r?Ae(e,t):Ae(e,t)+(r||"")+Ce(e)}function Ee(e,r){return r.code&&(e=Se("code",e)),r.del&&(e=Se("del",e)),r.em&&(e=Se("em",e)),r.strong&&(e=Se("strong",e)),e}const Le=new xe;function Me(e){const r=[];let t={};for(const n of e.children)if("text"===n.type)r.push(Ee(we(n.content),t));else if("code"===n.type)r.push(Se("code",Ee(we(n.content),t)));else if("softbreak"===n.type)r.push("
      ");else if(n.type.endsWith("_open")){const e=n.type.slice(0,-5);"link"===e?r.push(Ae("a",{href:n.href,title:n.title,target:"_blank",rel:"noopener noreferrer"})):t=Object.assign({},t,{[e]:!0})}else if(n.type.endsWith("_close")){const e=n.type.slice(0,-6);"link"===e?r.push(Ce("a")):t=Object.assign({},t,{[e]:!1})}return r.join("")}function Te(e){const r={t:"root",d:0,v:"",c:[]},t=[r];let n=0;for(const r of e){let e=t[t.length-1];if(r.type.endsWith("_open")){const i=r.type.slice(0,-5),l={};var o;if("heading"===i)for(n=r.hLevel;(null==(s=e)?void 0:s.d)>=n;){var s;t.pop(),e=t[t.length-1]}else n=Math.max(n,(null==(o=e)?void 0:o.d)||0)+1,"ordered_list"===i&&(l.start=r.order);const a={t:i,d:n,p:l,v:"",c:[]};e.c.push(a),t.push(a)}else{if(!e)continue;r.type===e.t+"_close"?"heading"===e.t?n=e.d:(t.pop(),n=0):"inline"===r.type?e.v=`${e.v||""}${Me(r)}`:"fence"===r.type&&e.c.push({t:r.type,d:n+1,v:`
      ${we(r.content)}
      `,c:[]})}}return r}Le.block.ruler.enable(["deflist"]),e.buildTree=Te,e.transform=function(e){var r;let t=Te(Le.parse(e||"",{}));return function e(r,t=0){if("heading"===r.t)r.c=r.c.filter(e=>"paragraph"!==e.t);else if("list_item"===r.t){var n;r.c=r.c.filter(e=>!["paragraph","fence"].includes(e.t)||(r.v||(r.v=e.v),!1)),null!=(null==(n=r.p)?void 0:n.index)&&(r.v=`${r.p.index}. ${r.v}`)}else if("ordered_list"===r.t){var o,s;let e=null!=(o=null==(s=r.p)?void 0:s.start)?o:1;r.c.forEach(r=>{"list_item"===r.t&&(r.p=Object.assign({},r.p,{index:e}),e+=1)})}0===r.c.length?delete r.c:(1!==r.c.length||r.c[0].v||(r.c=r.c[0].c),r.c.forEach(r=>e(r,t+1))),r.d=t,delete r.p}(t),1===(null==(r=t.c)?void 0:r.length)&&(t=t.c[0]),t}})); diff --git a/www/lib/markmap/view.min.js b/www/lib/markmap/view.min.js new file mode 100644 index 000000000..392a57fa0 --- /dev/null +++ b/www/lib/markmap/view.min.js @@ -0,0 +1,2 @@ +/*! markmap-lib v0.7.11 | MIT License */ +define(["exports"],(function(t){"use strict";function n(t,n){return tn?1:t>=n?0:NaN}var e,r;1===(e=n).length&&(r=e,e=function(t,e){return n(r(t),e)});function i(t,n){var e,r,i=t.length,o=-1;if(null==n){for(;++o=e)for(r=e;++or&&(r=e)}else for(;++o=e)for(r=e;++or&&(r=e);return r}function o(t,n){var e,r,i=t.length,o=-1;if(null==n){for(;++o=e)for(r=e;++oe&&(r=e)}else for(;++o=e)for(r=e;++oe&&(r=e);return r}var a={value:function(){}};function s(){for(var t,n=0,e=arguments.length,r={};n=0&&(e=t.slice(r+1),t=t.slice(0,r)),t&&!n.hasOwnProperty(t))throw new Error("unknown type: "+t);return{type:t,name:e}}))}function c(t,n){for(var e,r=0,i=t.length;r0)for(var e,r,i=new Array(e),o=0;o=0&&"xmlns"!==(n=t.slice(0,e))&&(t=t.slice(e+1)),p.hasOwnProperty(n)?{space:p[n],local:t}:t}function m(t){return function(){var n=this.ownerDocument,e=this.namespaceURI;return e===f&&n.documentElement.namespaceURI===f?n.createElement(t):n.createElementNS(e,t)}}function g(t){return function(){return this.ownerDocument.createElementNS(t.space,t.local)}}function y(t){var n=d(t);return(n.local?g:m)(n)}function v(){}function _(t){return null==t?v:function(){return this.querySelector(t)}}function w(){return[]}function x(t){return null==t?w:function(){return this.querySelectorAll(t)}}function b(t){return function(){return this.matches(t)}}function z(t){return new Array(t.length)}function k(t,n){this.ownerDocument=t.ownerDocument,this.namespaceURI=t.namespaceURI,this._next=null,this._parent=t,this.__data__=n}k.prototype={constructor:k,appendChild:function(t){return this._parent.insertBefore(t,this._next)},insertBefore:function(t,n){return this._parent.insertBefore(t,n)},querySelector:function(t){return this._parent.querySelector(t)},querySelectorAll:function(t){return this._parent.querySelectorAll(t)}};function M(t,n,e,r,i,o){for(var a,s=0,u=n.length,l=o.length;sn?1:t>=n?0:NaN}function A(t){return function(){this.removeAttribute(t)}}function N(t){return function(){this.removeAttributeNS(t.space,t.local)}}function X(t,n){return function(){this.setAttribute(t,n)}}function $(t,n){return function(){this.setAttributeNS(t.space,t.local,n)}}function j(t,n){return function(){var e=n.apply(this,arguments);null==e?this.removeAttribute(t):this.setAttribute(t,e)}}function C(t,n){return function(){var e=n.apply(this,arguments);null==e?this.removeAttributeNS(t.space,t.local):this.setAttributeNS(t.space,t.local,e)}}function T(t){return t.ownerDocument&&t.ownerDocument.defaultView||t.document&&t||t.defaultView}function O(t){return function(){this.style.removeProperty(t)}}function P(t,n,e){return function(){this.style.setProperty(t,n,e)}}function R(t,n,e){return function(){var r=n.apply(this,arguments);null==r?this.style.removeProperty(t):this.style.setProperty(t,r,e)}}function I(t,n){return t.style.getPropertyValue(n)||T(t).getComputedStyle(t,null).getPropertyValue(n)}function q(t){return function(){delete this[t]}}function Y(t,n){return function(){this[t]=n}}function B(t,n){return function(){var e=n.apply(this,arguments);null==e?delete this[t]:this[t]=e}}function D(t){return t.trim().split(/^|\s+/)}function H(t){return t.classList||new L(t)}function L(t){this._node=t,this._names=D(t.getAttribute("class")||"")}function V(t,n){for(var e=H(t),r=-1,i=n.length;++r=0&&(this._names.splice(n,1),this._node.setAttribute("class",this._names.join(" ")))},contains:function(t){return this._names.indexOf(t)>=0}};var ut={},lt=null;"undefined"!=typeof document&&("onmouseenter"in document.documentElement||(ut={mouseenter:"mouseover",mouseleave:"mouseout"}));function ct(t,n,e){return t=ht(t,n,e),function(n){var e=n.relatedTarget;e&&(e===this||8&e.compareDocumentPosition(this))||t.call(this,n)}}function ht(t,n,e){return function(r){var i=lt;lt=r;try{t.call(this,this.__data__,n,e)}finally{lt=i}}}function ft(t){return t.trim().split(/^|\s+/).map((function(t){var n="",e=t.indexOf(".");return e>=0&&(n=t.slice(e+1),t=t.slice(0,e)),{type:t,name:n}}))}function pt(t){return function(){var n=this.__on;if(n){for(var e,r=0,i=-1,o=n.length;r=w&&(w=_+1);!(v=g[w])&&++w=0;)(r=i[o])&&(a&&4^r.compareDocumentPosition(a)&&a.parentNode.insertBefore(r,a),a=r);return this},sort:function(t){function n(n,e){return n&&e?t(n.__data__,e.__data__):!n-!e}t||(t=S);for(var e=this._groups,r=e.length,i=new Array(r),o=0;o1?this.each((null==n?O:"function"==typeof n?R:P)(t,n,null==e?"":e)):I(this.node(),t)},property:function(t,n){return arguments.length>1?this.each((null==n?q:"function"==typeof n?B:Y)(t,n)):this.node()[t]},classed:function(t,n){var e=D(t+"");if(arguments.length<2){for(var r=H(this.node()),i=-1,o=e.length;++i>8&15|n>>4&240,n>>4&15|240&n,(15&n)<<4|15&n,1):8===e?Ft(n>>24&255,n>>16&255,n>>8&255,(255&n)/255):4===e?Ft(n>>12&15|n>>8&240,n>>8&15|n>>4&240,n>>4&15|240&n,((15&n)<<4|15&n)/255):null):(n=Pt.exec(t))?new Jt(n[1],n[2],n[3],1):(n=Rt.exec(t))?new Jt(255*n[1]/100,255*n[2]/100,255*n[3]/100,1):(n=It.exec(t))?Ft(n[1],n[2],n[3],n[4]):(n=qt.exec(t))?Ft(255*n[1]/100,255*n[2]/100,255*n[3]/100,n[4]):(n=Yt.exec(t))?tn(n[1],n[2]/100,n[3]/100,1):(n=Bt.exec(t))?tn(n[1],n[2]/100,n[3]/100,n[4]):Dt.hasOwnProperty(t)?Ut(Dt[t]):"transparent"===t?new Jt(NaN,NaN,NaN,0):null}function Ut(t){return new Jt(t>>16&255,t>>8&255,255&t,1)}function Ft(t,n,e,r){return r<=0&&(t=n=e=NaN),new Jt(t,n,e,r)}function Gt(t){return t instanceof $t||(t=Vt(t)),t?new Jt((t=t.rgb()).r,t.g,t.b,t.opacity):new Jt}function Zt(t,n,e,r){return 1===arguments.length?Gt(t):new Jt(t,n,e,null==r?1:r)}function Jt(t,n,e,r){this.r=+t,this.g=+n,this.b=+e,this.opacity=+r}function Kt(){return"#"+Wt(this.r)+Wt(this.g)+Wt(this.b)}function Qt(){var t=this.opacity;return(1===(t=isNaN(t)?1:Math.max(0,Math.min(1,t)))?"rgb(":"rgba(")+Math.max(0,Math.min(255,Math.round(this.r)||0))+", "+Math.max(0,Math.min(255,Math.round(this.g)||0))+", "+Math.max(0,Math.min(255,Math.round(this.b)||0))+(1===t?")":", "+t+")")}function Wt(t){return((t=Math.max(0,Math.min(255,Math.round(t)||0)))<16?"0":"")+t.toString(16)}function tn(t,n,e,r){return r<=0?t=n=e=NaN:e<=0||e>=1?t=n=NaN:n<=0&&(t=NaN),new en(t,n,e,r)}function nn(t){if(t instanceof en)return new en(t.h,t.s,t.l,t.opacity);if(t instanceof $t||(t=Vt(t)),!t)return new en;if(t instanceof en)return t;var n=(t=t.rgb()).r/255,e=t.g/255,r=t.b/255,i=Math.min(n,e,r),o=Math.max(n,e,r),a=NaN,s=o-i,u=(o+i)/2;return s?(a=n===o?(e-r)/s+6*(e0&&u<1?0:a,new en(a,s,u,t.opacity)}function en(t,n,e,r){this.h=+t,this.s=+n,this.l=+e,this.opacity=+r}function rn(t,n,e){return 255*(t<60?n+(e-n)*t/60:t<180?e:t<240?n+(e-n)*(240-t)/60:n)}function on(t){return function(){return t}}function an(t){return 1==(t=+t)?sn:function(n,e){return e-n?function(t,n,e){return t=Math.pow(t,e),n=Math.pow(n,e)-t,e=1/e,function(r){return Math.pow(t+r*n,e)}}(n,e,t):on(isNaN(n)?e:n)}}function sn(t,n){var e=n-t;return e?function(t,n){return function(e){return t+e*n}}(t,e):on(isNaN(t)?n:t)}Nt($t,Vt,{copy:function(t){return Object.assign(new this.constructor,this,t)},displayable:function(){return this.rgb().displayable()},hex:Ht,formatHex:Ht,formatHsl:function(){return nn(this).formatHsl()},formatRgb:Lt,toString:Lt}),Nt(Jt,Zt,Xt($t,{brighter:function(t){return t=null==t?1/.7:Math.pow(1/.7,t),new Jt(this.r*t,this.g*t,this.b*t,this.opacity)},darker:function(t){return t=null==t?.7:Math.pow(.7,t),new Jt(this.r*t,this.g*t,this.b*t,this.opacity)},rgb:function(){return this},displayable:function(){return-.5<=this.r&&this.r<255.5&&-.5<=this.g&&this.g<255.5&&-.5<=this.b&&this.b<255.5&&0<=this.opacity&&this.opacity<=1},hex:Kt,formatHex:Kt,formatRgb:Qt,toString:Qt})),Nt(en,(function(t,n,e,r){return 1===arguments.length?nn(t):new en(t,n,e,null==r?1:r)}),Xt($t,{brighter:function(t){return t=null==t?1/.7:Math.pow(1/.7,t),new en(this.h,this.s,this.l*t,this.opacity)},darker:function(t){return t=null==t?.7:Math.pow(.7,t),new en(this.h,this.s,this.l*t,this.opacity)},rgb:function(){var t=this.h%360+360*(this.h<0),n=isNaN(t)||isNaN(this.s)?0:this.s,e=this.l,r=e+(e<.5?e:1-e)*n,i=2*e-r;return new Jt(rn(t>=240?t-240:t+120,i,r),rn(t,i,r),rn(t<120?t+240:t-120,i,r),this.opacity)},displayable:function(){return(0<=this.s&&this.s<=1||isNaN(this.s))&&0<=this.l&&this.l<=1&&0<=this.opacity&&this.opacity<=1},formatHsl:function(){var t=this.opacity;return(1===(t=isNaN(t)?1:Math.max(0,Math.min(1,t)))?"hsl(":"hsla(")+(this.h||0)+", "+100*(this.s||0)+"%, "+100*(this.l||0)+"%"+(1===t?")":", "+t+")")}}));var un=function t(n){var e=an(n);function r(t,n){var r=e((t=Zt(t)).r,(n=Zt(n)).r),i=e(t.g,n.g),o=e(t.b,n.b),a=sn(t.opacity,n.opacity);return function(n){return t.r=r(n),t.g=i(n),t.b=o(n),t.opacity=a(n),t+""}}return r.gamma=t,r}(1);function ln(t,n){return t=+t,n=+n,function(e){return t*(1-e)+n*e}}var cn=/[-+]?(?:\d+\.?\d*|\.?\d+)(?:[eE][-+]?\d+)?/g,hn=new RegExp(cn.source,"g");function fn(t,n){var e,r,i,o=cn.lastIndex=hn.lastIndex=0,a=-1,s=[],u=[];for(t+="",n+="";(e=cn.exec(t))&&(r=hn.exec(n));)(i=r.index)>o&&(i=n.slice(o,i),s[a]?s[a]+=i:s[++a]=i),(e=e[0])===(r=r[0])?s[a]?s[a]+=r:s[++a]=r:(s[++a]=null,u.push({i:a,x:ln(e,r)})),o=hn.lastIndex;return o180?n+=360:n-t>180&&(t+=360),o.push({i:e.push(i(e)+"rotate(",null,r)-2,x:ln(t,n)})):n&&e.push(i(e)+"rotate("+n+r)}(o.rotate,a.rotate,s,u),function(t,n,e,o){t!==n?o.push({i:e.push(i(e)+"skewX(",null,r)-2,x:ln(t,n)}):n&&e.push(i(e)+"skewX("+n+r)}(o.skewX,a.skewX,s,u),function(t,n,e,r,o,a){if(t!==e||n!==r){var s=o.push(i(o)+"scale(",null,",",null,")");a.push({i:s-4,x:ln(t,e)},{i:s-2,x:ln(n,r)})}else 1===e&&1===r||o.push(i(o)+"scale("+e+","+r+")")}(o.scaleX,o.scaleY,a.scaleX,a.scaleY,s,u),o=a=null,function(t){for(var n,e=-1,r=u.length;++e=0&&n._call.call(null,t),n=n._next;--An}()}finally{An=0,function(){var t,n,e=En,r=1/0;for(;e;)e._call?(r>e._time&&(r=e._time),t=e,e=e._next):(n=e._next,e._next=null,e=t?t._next=n:En=n);Sn=t,Dn(r)}(),jn=0}}function Bn(){var t=Tn.now(),n=t-$n;n>1e3&&(Cn-=n,$n=t)}function Dn(t){An||(Nn&&(Nn=clearTimeout(Nn)),t-jn>24?(t<1/0&&(Nn=setTimeout(Yn,t-Tn.now()-Cn)),Xn&&(Xn=clearInterval(Xn))):(Xn||($n=Tn.now(),Xn=setInterval(Bn,1e3)),An=1,On(Yn)))}function Hn(t,n,e){var r=new In;return n=null==n?0:+n,r.restart((function(e){r.stop(),t(e+n)}),n,e),r}In.prototype=qn.prototype={constructor:In,restart:function(t,n,e){if("function"!=typeof t)throw new TypeError("callback is not a function");e=(null==e?Pn():+e)+(null==n?0:+n),this._next||Sn===this||(Sn?Sn._next=this:En=this,Sn=this),this._call=t,this._time=e,Dn()},stop:function(){this._call&&(this._call=null,this._time=1/0,Dn())}};var Ln=s("start","end","cancel","interrupt"),Vn=[];function Un(t,n,e,r,i,o){var a=t.__transition;if(a){if(e in a)return}else t.__transition={};!function(t,n,e){var r,i=t.__transition;function o(u){var l,c,h,f;if(1!==e.state)return s();for(l in i)if((f=i[l]).name===e.name){if(3===f.state)return Hn(o);4===f.state?(f.state=6,f.timer.stop(),f.on.call("interrupt",t,t.__data__,f.index,f.group),delete i[l]):+l0)throw new Error("too late; already scheduled");return e}function Gn(t,n){var e=Zn(t,n);if(e.state>3)throw new Error("too late; already running");return e}function Zn(t,n){var e=t.__transition;if(!e||!(e=e[n]))throw new Error("transition not found");return e}function Jn(t,n){var e,r,i,o=t.__transition,a=!0;if(o){for(i in n=null==n?null:n+"",o)(e=o[i]).name===n?(r=e.state>2&&e.state<5,e.state=6,e.timer.stop(),e.on.call(r?"interrupt":"cancel",t,t.__data__,e.index,e.group),delete o[i]):a=!1;a&&delete t.__transition}}function Kn(t,n){var e,r;return function(){var i=Gn(this,t),o=i.tween;if(o!==e)for(var a=0,s=(r=e=o).length;a=0&&(t=t.slice(0,n)),!t||"start"===t}))}(n)?Fn:Gn;return function(){var a=o(this,t),s=a.on;s!==r&&(i=(r=s).copy()).on(n,e),a.on=i}}var ye=wt.prototype.constructor;function ve(t){return function(){this.style.removeProperty(t)}}function _e(t,n,e){return function(r){this.style.setProperty(t,n.call(this,r),e)}}function we(t,n,e){var r,i;function o(){var o=n.apply(this,arguments);return o!==i&&(r=(i=o)&&_e(t,o,e)),r}return o._value=n,o}function xe(t){return function(n){this.textContent=t.call(this,n)}}function be(t){var n,e;function r(){var r=t.apply(this,arguments);return r!==e&&(n=(e=r)&&xe(r)),n}return r._value=t,r}var ze=0;function ke(t,n,e,r){this._groups=t,this._parents=n,this._name=e,this._id=r}function Me(){return++ze}var Ee=wt.prototype;ke.prototype=function(t){return wt().transition(t)}.prototype={constructor:ke,select:function(t){var n=this._name,e=this._id;"function"!=typeof t&&(t=_(t));for(var r=this._groups,i=r.length,o=new Array(i),a=0;a1e-6)if(Math.abs(c*s-u*l)>1e-6&&i){var f=e-o,p=r-a,d=s*s+u*u,m=f*f+p*p,g=Math.sqrt(d),y=Math.sqrt(h),v=i*Math.tan((Ne-Math.acos((d+h-m)/(2*g*y)))/2),_=v/y,w=v/g;Math.abs(_-1)>1e-6&&(this._+="L"+(t+_*l)+","+(n+_*c)),this._+="A"+i+","+i+",0,0,"+ +(c*f>l*p)+","+(this._x1=t+w*s)+","+(this._y1=n+w*u)}else this._+="L"+(this._x1=t)+","+(this._y1=n);else;},arc:function(t,n,e,r,i,o){t=+t,n=+n,o=!!o;var a=(e=+e)*Math.cos(r),s=e*Math.sin(r),u=t+a,l=n+s,c=1^o,h=o?r-i:i-r;if(e<0)throw new Error("negative radius: "+e);null===this._x1?this._+="M"+u+","+l:(Math.abs(this._x1-u)>1e-6||Math.abs(this._y1-l)>1e-6)&&(this._+="L"+u+","+l),e&&(h<0&&(h=h%Xe+Xe),h>$e?this._+="A"+e+","+e+",0,1,"+c+","+(t-a)+","+(n-s)+"A"+e+","+e+",0,1,"+c+","+(this._x1=u)+","+(this._y1=l):h>1e-6&&(this._+="A"+e+","+e+",0,"+ +(h>=Ne)+","+c+","+(this._x1=t+e*Math.cos(i))+","+(this._y1=n+e*Math.sin(i))))},rect:function(t,n,e,r){this._+="M"+(this._x0=this._x1=+t)+","+(this._y0=this._y1=+n)+"h"+ +e+"v"+ +r+"h"+-e+"Z"},toString:function(){return this._}};function Te(){}function Oe(t,n){var e=new Te;if(t instanceof Te)t.each((function(t,n){e.set(n,t)}));else if(Array.isArray(t)){var r,i=-1,o=t.length;if(null==n)for(;++i=0;)n+=e[r].value;else n=1;t.value=n}function qe(t,n){var e,r,i,o,a,s=new He(t),u=+t.value&&(s.value=t.value),l=[s];for(null==n&&(n=Ye);e=l.pop();)if(u&&(e.value=+e.data.value),(i=n(e.data))&&(a=i.length))for(e.children=new Array(a),o=a-1;o>=0;--o)l.push(r=e.children[o]=new He(i[o])),r.parent=e,r.depth=e.depth+1;return s.eachBefore(De)}function Ye(t){return t.children}function Be(t){t.data=t.data.data}function De(t){var n=0;do{t.height=n}while((t=t.parent)&&t.height<++n)}function He(t){this.data=t,this.depth=this.height=0,this.parent=null}function Le(t,n){switch(arguments.length){case 0:break;case 1:this.range(t);break;default:this.range(n).domain(t)}return this}Pe.prototype=function(t,n){var e=new Pe;if(t instanceof Pe)t.each((function(t){e.add(t)}));else if(t){var r=-1,i=t.length;if(null==n)for(;++r=0;--e)i.push(n[e]);return this},sum:function(t){return this.eachAfter((function(n){for(var e=+t(n.data)||0,r=n.children,i=r&&r.length;--i>=0;)e+=r[i].value;n.value=e}))},sort:function(t){return this.eachBefore((function(n){n.children&&n.children.sort(t)}))},path:function(t){for(var n=this,e=function(t,n){if(t===n)return t;var e=t.ancestors(),r=n.ancestors(),i=null;t=e.pop(),n=r.pop();for(;t===n;)i=t,t=e.pop(),n=r.pop();return i}(n,t),r=[n];n!==e;)n=n.parent,r.push(n);for(var i=r.length;t!==e;)r.splice(i,0,t),t=t.parent;return r},ancestors:function(){for(var t=this,n=[t];t=t.parent;)n.push(t);return n},descendants:function(){var t=[];return this.each((function(n){t.push(n)})),t},leaves:function(){var t=[];return this.eachBefore((function(n){n.children||t.push(n)})),t},links:function(){var t=this,n=[];return t.each((function(e){e!==t&&n.push({source:e.parent,target:e})})),n},copy:function(){return qe(this).eachBefore(Be)}};var Ve=Array.prototype.slice,Ue={name:"implicit"};function Fe(){var t=Oe(),n=[],e=[],r=Ue;function i(i){var o=i+"",a=t.get(o);if(!a){if(r!==Ue)return r;t.set(o,a=n.push(i))}return e[(a-1)%e.length]}return i.domain=function(e){if(!arguments.length)return n.slice();n=[],t=Oe();for(var r,o,a=-1,s=e.length;++ar?(r+i)/2:Math.min(0,r)||Math.max(0,i),a>o?(o+a)/2:Math.min(0,o)||Math.max(0,a))}function gr(){var t,n,e=cr,r=hr,i=mr,o=pr,a=dr,u=[0,1/0],l=[[-1/0,-1/0],[1/0,1/0]],c=250,h=Mn,f=s("start","zoom","end"),p=0;function d(t){t.property("__zoom",fr).on("wheel.zoom",x).on("mousedown.zoom",b).on("dblclick.zoom",z).filter(a).on("touchstart.zoom",k).on("touchmove.zoom",M).on("touchend.zoom touchcancel.zoom",E).style("touch-action","none").style("-webkit-tap-highlight-color","rgba(0,0,0,0)")}function m(t,n){return(n=Math.max(u[0],Math.min(u[1],n)))===t.k?t:new or(n,t.x,t.y)}function g(t,n,e){var r=n[0]-e[0]*t.k,i=n[1]-e[1]*t.k;return r===t.x&&i===t.y?t:new or(t.k,r,i)}function y(t){return[(+t[0][0]+ +t[1][0])/2,(+t[0][1]+ +t[1][1])/2]}function v(t,n,e){t.on("start.zoom",(function(){_(this,arguments).start()})).on("interrupt.zoom end.zoom",(function(){_(this,arguments).end()})).tween("zoom",(function(){var t=this,i=arguments,o=_(t,i),a=r.apply(t,i),s=null==e?y(a):"function"==typeof e?e.apply(t,i):e,u=Math.max(a[1][0]-a[0][0],a[1][1]-a[0][1]),l=t.__zoom,c="function"==typeof n?n.apply(t,i):n,f=h(l.invert(s).concat(u/l.k),c.invert(s).concat(u/c.k));return function(t){if(1===t)t=c;else{var n=f(t),e=u/n[2];t=new or(e,s[0]-n[0]*e,s[1]-n[1]*e)}o.zoom(null,t)}}))}function _(t,n,e){return!e&&t.__zooming||new w(t,n)}function w(t,n){this.that=t,this.args=n,this.active=0,this.extent=r.apply(t,n),this.taps=0}function x(){if(e.apply(this,arguments)){var t=_(this,arguments),n=this.__zoom,r=Math.max(u[0],Math.min(u[1],n.k*Math.pow(2,o.apply(this,arguments)))),a=kt(this);if(t.wheel)t.mouse[0][0]===a[0]&&t.mouse[0][1]===a[1]||(t.mouse[1]=n.invert(t.mouse[0]=a)),clearTimeout(t.wheel);else{if(n.k===r)return;t.mouse=[a,n.invert(a)],Jn(this),t.start()}lr(),t.wheel=setTimeout(s,150),t.zoom("mouse",i(g(m(n,r),t.mouse[0],t.mouse[1]),t.extent,l))}function s(){t.wheel=null,t.end()}}function b(){if(!n&&e.apply(this,arguments)){var t=_(this,arguments,!0),r=xt(lt.view).on("mousemove.zoom",u,!0).on("mouseup.zoom",c,!0),o=kt(this),a=lt.clientX,s=lt.clientY;St(lt.view),ur(),t.mouse=[o,this.__zoom.invert(o)],Jn(this),t.start()}function u(){if(lr(),!t.moved){var n=lt.clientX-a,e=lt.clientY-s;t.moved=n*n+e*e>p}t.zoom("mouse",i(g(t.that.__zoom,t.mouse[0]=kt(t.that),t.mouse[1]),t.extent,l))}function c(){r.on("mousemove.zoom mouseup.zoom",null),At(lt.view,t.moved),lr(),t.end()}}function z(){if(e.apply(this,arguments)){var t=this.__zoom,n=kt(this),o=t.invert(n),a=t.k*(lt.shiftKey?.5:2),s=i(g(m(t,a),n,o),r.apply(this,arguments),l);lr(),c>0?xt(this).transition().duration(c).call(v,s,n):xt(this).call(d.transform,s)}}function k(){if(e.apply(this,arguments)){var n,r,i,o,a=lt.touches,s=a.length,u=_(this,arguments,lt.changedTouches.length===s);for(ur(),r=0;rt.children,nodeSize:t=>t.data.size,spacing:0});function vr(t){const n=Object.assign({},yr,t);function e(t){const e=n[t];return"function"==typeof e?e:()=>e}function r(t){const n=o(function(){const t=i(),n=e("nodeSize"),r=e("spacing");return class extends t{constructor(t){super(t),Object.assign(this,{x:0,y:0,relX:0,prelim:0,shift:0,change:0,lExt:this,lExtRelX:0,lThr:null,rExt:this,rExtRelX:0,rThr:null})}get size(){return n(this.data)}spacing(t){return r(this.data,t.data)}get x(){return this.data.x}set x(t){this.data.x=t}get y(){return this.data.y}set y(t){this.data.y=t}update(){return _r(this),wr(this),this}}}(),t,t=>t.children);return n.update(),n.data}function i(){const t=e("nodeSize"),n=e("spacing");return class e extends qe.prototype.constructor{constructor(t){super(t)}copy(){const t=o(this.constructor,this,t=>t.children);return t.each(t=>t.data=t.data.data),t}get size(){return t(this)}spacing(t){return n(this,t)}get nodes(){return this.descendants()}get xSize(){return this.size[0]}get ySize(){return this.size[1]}get top(){return this.y}get bottom(){return this.y+this.ySize}get left(){return this.x-this.xSize/2}get right(){return this.x+this.xSize/2}get root(){const t=this.ancestors();return t[t.length-1]}get numChildren(){return this.hasChildren?this.children.length:0}get hasChildren(){return!this.noChildren}get noChildren(){return null===this.children}get firstChild(){return this.hasChildren?this.children[0]:null}get lastChild(){return this.hasChildren?this.children[this.numChildren-1]:null}get extents(){return(this.children||[]).reduce((t,n)=>e.maxExtents(t,n.extents),this.nodeExtents)}get nodeExtents(){return{top:this.top,bottom:this.bottom,left:this.left,right:this.right}}static maxExtents(t,n){return{top:Math.min(t.top,n.top),bottom:Math.max(t.bottom,n.bottom),left:Math.min(t.left,n.left),right:Math.max(t.right,n.right)}}}}function o(t,n,e){const r=(n,i)=>{const o=new t(n);Object.assign(o,{parent:i,depth:null===i?0:i.depth+1,height:0,length:1});const a=e(n)||[];return o.children=0===a.length?null:a.map(t=>r(t,o)),o.children&&Object.assign(o,o.children.reduce((t,n)=>({height:Math.max(t.height,n.height+1),length:t.length+n.length}),o)),o};return r(n,null)}return Object.assign(r,{nodeSize(t){return arguments.length?(n.nodeSize=t,r):n.nodeSize},spacing(t){return arguments.length?(n.spacing=t,r):n.spacing},children(t){return arguments.length?(n.children=t,r):n.children},hierarchy(t,e){const r=void 0===e?n.children:e;return o(i(),t,r)},dump(t){const n=e("nodeSize"),r=t=>e=>{const i=t+" ",o=t+" ",{x:a,y:s}=e,u=n(e),l=e.children||[],c=0===l.length?" ":`,${i}children: [${o}${l.map(r(o)).join(o)}${i}],${t}`;return`{ size: [${u.join(", ")}],${i}x: ${a}, y: ${s}${c}},`};return r("\n")(t)}}),r}vr.version="2.1.1";const _r=(t,n=0)=>(t.y=n,(t.children||[]).reduce((n,e)=>{const[r,i]=n;_r(e,t.y+t.ySize);const o=(0===r?e.lExt:e.rExt).bottom;0!==r&&br(t,r,i);return[r+1,Xr(o,r,i)]},[0,null]),xr(t),Nr(t),t),wr=(t,n,e)=>{void 0===n&&(n=-t.relX-t.prelim,e=0);const r=n+t.relX;return t.relX=r+t.prelim-e,t.prelim=0,t.x=e+t.relX,(t.children||[]).forEach(n=>wr(n,r,t.x)),t},xr=t=>{(t.children||[]).reduce((t,n)=>{const[e,r]=t,i=e+n.shift,o=r+i+n.change;return n.relX+=o,[i,o]},[0,0])},br=(t,n,e)=>{const r=t.children[n-1],i=t.children[n];let o=r,a=r.relX,s=i,u=i.relX,l=!0;for(;o&&s;){o.bottom>e.lowY&&(e=e.next);const r=a+o.prelim-(u+s.prelim)+o.xSize/2+s.xSize/2+o.spacing(s);(r>0||r<0&&l)&&(u+=r,zr(i,r),kr(t,n,e.index,r)),l=!1;const c=o.bottom,h=s.bottom;c<=h&&(o=Er(o),o&&(a+=o.relX)),c>=h&&(s=Mr(s),s&&(u+=s.relX))}!o&&s?Sr(t,n,s,u):o&&!s&&Ar(t,n,o,a)},zr=(t,n)=>{t.relX+=n,t.lExtRelX+=n,t.rExtRelX+=n},kr=(t,n,e,r)=>{const i=t.children[n],o=n-e;if(o>1){const n=r/o;t.children[e+1].shift+=n,i.shift-=n,i.change-=r-n}},Mr=t=>t.hasChildren?t.firstChild:t.lThr,Er=t=>t.hasChildren?t.lastChild:t.rThr,Sr=(t,n,e,r)=>{const i=t.firstChild,o=i.lExt,a=t.children[n];o.lThr=e;const s=r-e.relX-i.lExtRelX;o.relX+=s,o.prelim-=s,i.lExt=a.lExt,i.lExtRelX=a.lExtRelX},Ar=(t,n,e,r)=>{const i=t.children[n],o=i.rExt,a=t.children[n-1];o.rThr=e;const s=r-e.relX-i.rExtRelX;o.relX+=s,o.prelim-=s,i.rExt=a.rExt,i.rExtRelX=a.rExtRelX},Nr=t=>{if(t.hasChildren){const n=t.firstChild,e=t.lastChild,r=(n.prelim+n.relX-n.xSize/2+e.relX+e.prelim+e.xSize/2)/2;Object.assign(t,{prelim:r,lExt:n.lExt,lExtRelX:n.lExtRelX,rExt:e.rExt,rExtRelX:e.rExtRelX})}},Xr=(t,n,e)=>{for(;null!==e&&t>=e.lowY;)e=e.next;return{lowY:t,index:n,next:e}},$r=Math.random().toString(36).slice(2,8);let jr=0;function Cr(){}function Tr(t,n,e="c"){const r=(t,i)=>n(t,()=>{var n;null==(n=t[e])||n.forEach(n=>{r(n,t)})},i);r(t)}function Or(t){if(Array.from)return Array.from(t);const n=[];for(let e=0;e{t&&e.indexOf(t)<0&&e.push(t)}),e.join(" ")}function Rr(t){if("string"==typeof t){const n=t;t=t=>t.tagName===n}const n=t;return function(){let t=Or(this.childNodes);return n&&(t=t.filter(t=>n(t))),t}}function Ir(t,n,e){const r=document.createElement(t);return n&&Object.entries(n).forEach(([t,n])=>{r[t]=n}),e&&Object.entries(e).forEach(([t,n])=>{r.setAttribute(t,n)}),r}const qr=function(t){const n={};return function(...e){const r=""+e[0];let i=n[r];return i||(i={value:t(...e)},n[r]=i),i.value}}(t=>{document.head.append(Ir("link",{rel:"preload",as:"script",href:t}))});function Yr(t,n){if("script"===t.type)return new Promise((n,e)=>{document.head.append(Ir("script",Object.assign({},t.data,{onload:n,onerror:e})))});if("iife"===t.type){const{fn:e,getParams:r}=t.data;e(...(null==r?void 0:r(n))||[])}}function Br(t){"style"===t.type?document.head.append(Ir("style",{textContent:t.data})):"stylesheet"===t.type&&document.head.append(Ir("link",Object.assign({rel:"stylesheet"},t.data)))}async function Dr(t,n,e){e=Object.assign({},e),await Promise.all(n.map(t=>(function(t){for(const n of t)Br(n)}(t.styles),async function(t,n){const e=t.filter(t=>"script"===t.type);e.length>1&&e.forEach(t=>qr(t.data.src));for(const e of t)await Yr(e,n)}(t.scripts,e))));for(const{initialize:r}of n)r&&r(t,e)}const Hr={styles:[],scripts:[{type:"iife",data:{fn:t=>{t.options=Object.assign({skipHtmlTags:{"[-]":["code","pre"]}},t.options),t.startup=Object.assign({typeset:!1},t.startup),window.MathJax=t},getParams:t=>[Object.assign({},t.mathJax)]}},{type:"script",data:{src:"https://cdn.jsdelivr.net/npm/mathjax@3/es5/tex-svg.js"}}],initialize:function(t,n){t.transformHtml.tap((t,n)=>{var e,r;null==(e=(r=window.MathJax).typeset)||e.call(r,n)})}},Lr={};const Vr={styles:[{type:"stylesheet",data:{href:"https://cdn.jsdelivr.net/npm/prismjs@1/themes/prism.css"}}],scripts:[{type:"iife",data:{fn:()=>{window.Prism={manual:!0}}}},{type:"script",data:{src:"https://cdn.jsdelivr.net/npm/prismjs@1/components/prism-core.min.js"}},{type:"script",data:{src:"https://cdn.jsdelivr.net/npm/prismjs@1/plugins/autoloader/prism-autoloader.min.js"}}],initialize:function(t,n){t.transformHtml.tap((t,n)=>{const{Prism:e}=window;!async function(t,n){if(!n.length)return;const{Prism:e}=window;try{await new Promise((t,r)=>{e.plugins.autoloader.loadLanguages(n,t,r)})}catch(t){Lr[t]=!0}t.setData(),t.fit()}(t,function(t,n){if(t.flatMap)return t.flatMap(n);const e=[];for(let r=0;rOr(t.querySelectorAll("code[class*=language-]"))).map(t=>{const n=t.className.match(/(?:^|\s)language-(\S+)|$/)[1];if(e.languages[n])e.highlightElement(t);else if(!Lr[n])return n}).filter(Boolean))})}};var Ur=Object.freeze({__proto__:null,mathJax:Hr,prism:Vr});function Fr(t){const n=t.data;return Math.max(6-2*n.d,1.5)}class Gr{constructor(t,n){var e;this.options=void 0,this.state=void 0,this.svg=void 0,this.styleNode=void 0,this.g=void 0,this.zoom=void 0,["handleZoom","handleClick"].forEach(t=>{this[t]=this[t].bind(this)}),this.svg=t.datum?t:xt(t),this.styleNode=this.svg.append("style"),this.zoom=gr().on("zoom",this.handleZoom),this.options=Object.assign({duration:500,nodeFont:"300 16px/20px sans-serif",nodeMinHeight:16,spacingVertical:5,spacingHorizontal:80,autoFit:!1,fitRatio:.95,color:(e=Fe(Ge),t=>e(t.p.i)),paddingX:8},n),this.state={id:this.options.id||(jr+=1,`mm-${$r}-${jr}`)},this.g=this.svg.append("g").attr("class",this.state.id+"-g"),this.updateStyle(),this.svg.call(this.zoom)}getStyleContent(){const{style:t,nodeFont:n}=this.options,{id:e}=this.state;return`.${e} a { color: #0097e6; }\n.${e} a:hover { color: #00a8ff; }\n.${e}-g > path { fill: none; }\n.${e}-fo > div { font: ${n}; white-space: nowrap; }\n.${e}-fo code { padding: .2em .4em; font-size: calc(1em - 2px); color: #555; background-color: #f0f0f0; border-radius: 2px; }\n.${e}-fo del { text-decoration: line-through; }\n.${e}-fo em { font-style: italic; }\n.${e}-fo strong { font-weight: 600; }\n.${e}-fo pre { margin: 0; }\n.${e}-fo pre[class*=language-] { padding: 0; }\n.${e}-g > g { cursor: pointer; }\n${"function"==typeof t?t(e):""}\n`}updateStyle(){this.svg.attr("class",Pr(this.svg.attr("class"),this.state.id)),this.styleNode.text(this.getStyleContent())}handleZoom(){const{transform:t}=lt;this.g.attr("transform",t)}handleClick(t){var n;const{data:e}=t;e.p=Object.assign({},e.p,{f:!(null==(n=e.p)?void 0:n.f)}),this.renderData(t.data)}initializeData(t){let n=0;const{nodeFont:e,color:r,nodeMinHeight:i}=this.options,{id:o}=this.state,a=document.createElement("div"),s=o+"-container";a.className=Pr(a.className,o+"-fo",s);const u=document.createElement("style");u.textContent=`\n${this.getStyleContent()}\n.${s} {\n position: absolute;\n width: 0;\n height: 0;\n top: -100px;\n left: -100px;\n overflow: hidden;\n font: ${e};\n}\n.${s} > div {\n display: inline-block;\n}\n`,document.body.append(u,a),Tr(t,(t,e)=>{var i;t.c=null==(i=t.c)?void 0:i.map(t=>Object.assign({},t)),n+=1;const o=document.createElement("div");o.innerHTML=t.v,a.append(o),t.p=Object.assign({},t.p,{i:n,el:o}),r(t),e()});const l=Or(a.childNodes);this.constructor.transformHtml.call(this,l),Tr(t,(t,n,e)=>{var r;const o=t.p.el.getBoundingClientRect();t.v=t.p.el.innerHTML,t.p.s=[Math.ceil(o.width),Math.max(Math.ceil(o.height),i)],t.p.k=`${(null==e||null==(r=e.p)?void 0:r.i)||""}.${t.p.i}:${t.v}`,n()}),a.remove(),u.remove()}setOptions(t){Object.assign(this.options,t)}setData(t,n){t||(t=Object.assign({},this.state.data)),this.state.data=t,this.initializeData(t),n&&this.setOptions(n),this.renderData()}renderData(t){var n,e;if(!this.state.data)return;const{spacingHorizontal:r,paddingX:a,spacingVertical:s,autoFit:u,color:l}=this.options,{id:c}=this.state,h=vr().children(t=>{var n;return!(null==(n=t.p)?void 0:n.f)&&t.c}).nodeSize(t=>{const[n,e]=t.data.p.s;return[e,n+(n?2*a:0)+r]}).spacing((t,n)=>t.parent===n.parent?s:2*s),f=h.hierarchy(this.state.data);h(f),function(t,n){Tr(t,(t,e)=>{t.ySizeInner=t.ySize-n,t.y+=n,e()},"children")}(f,r);const p=f.descendants().reverse(),d=f.links(),m=er(),g=o(p,t=>t.x-t.xSize/2),y=i(p,t=>t.x+t.xSize/2),v=o(p,t=>t.y),_=i(p,t=>t.y+t.ySizeInner);Object.assign(this.state,{minX:g,maxX:y,minY:v,maxY:_}),u&&this.fit();const w=t&&p.find(n=>n.data===t)||f,x=null!=(n=w.data.p.x0)?n:w.x,b=null!=(e=w.data.p.y0)?e:w.y,z=this.g.selectAll(Rr("g")).data(p,t=>t.data.p.k),k=z.enter().append("g").attr("transform",t=>`translate(${b+w.ySizeInner-t.ySizeInner},${x+w.xSize/2-t.xSize})`).on("click",this.handleClick),M=this.transition(z.exit());M.select("rect").attr("width",0).attr("x",t=>t.ySizeInner),M.select("foreignObject").style("opacity",0),M.attr("transform",t=>`translate(${w.y+w.ySizeInner-t.ySizeInner},${w.x+w.xSize/2-t.xSize})`).remove();const E=z.merge(k);this.transition(E).attr("transform",t=>`translate(${t.y},${t.x-t.xSize/2})`);const S=E.selectAll(Rr("rect")).data(t=>[t],t=>t.data.p.k).join(t=>t.append("rect").attr("x",t=>t.ySizeInner).attr("y",t=>t.xSize-Fr(t)/2).attr("width",0).attr("height",Fr),t=>t,t=>t.remove());this.transition(S).attr("x",-1).attr("width",t=>t.ySizeInner+2).attr("fill",t=>l(t.data));const A=E.selectAll(Rr("circle")).data(t=>t.data.c?[t]:[],t=>t.data.p.k).join(t=>t.append("circle").attr("stroke-width","1.5").attr("cx",t=>t.ySizeInner).attr("cy",t=>t.xSize).attr("r",0),t=>t,t=>t.remove());this.transition(A).attr("r",6).attr("stroke",t=>l(t.data)).attr("fill",t=>{var n;return(null==(n=t.data.p)?void 0:n.f)?l(t.data):"#fff"});const N=E.selectAll(Rr("foreignObject")).data(t=>[t],t=>t.data.p.k).join(t=>{const n=t.append("foreignObject").attr("class",c+"-fo").attr("x",a).attr("y",0).style("opacity",0).attr("height",t=>t.xSize);return n.append("xhtml:div").select((function(t){const n=t.data.p.el.cloneNode(!0);return this.replaceWith(n),n})).attr("xmlns","http://www.w3.org/1999/xhtml"),n},t=>t,t=>t.remove()).attr("width",t=>Math.max(0,t.ySizeInner-2*a));this.transition(N).style("opacity",1);const X=this.g.selectAll(Rr("path")).data(d,t=>t.target.data.p.k).join(t=>{const n=[b+w.ySizeInner,x+w.xSize/2];return t.insert("path","g").attr("d",m({source:n,target:n}))},t=>t,t=>{const n=[w.y+w.ySizeInner,w.x+w.xSize/2];return this.transition(t).attr("d",m({source:n,target:n})).remove()});this.transition(X).attr("stroke",t=>l(t.target.data)).attr("stroke-width",t=>Fr(t.target)).attr("d",t=>{const n=[t.source.y+t.source.ySizeInner,t.source.x+t.source.xSize/2],e=[t.target.y,t.target.x+t.target.xSize/2];return m({source:n,target:e})}),p.forEach(t=>{t.data.p.x0=t.x,t.data.p.y0=t.y})}transition(t){const{duration:n}=this.options;return t.transition().duration(n)}fit(){const t=this.svg.node(),{width:n,height:e}=t.getBoundingClientRect(),{fitRatio:r}=this.options,{minX:i,maxX:o,minY:a,maxY:s}=this.state,u=s-a,l=o-i,c=Math.min(n/u*r,e/l*r,2),h=ar.translate((n-u*c)/2-a*c,(e-l*c)/2-i*c).scale(c);return this.transition(this.svg).call(this.zoom.transform,h).end().catch(Cr)}rescale(t){const n=this.svg.node(),{width:e,height:r}=n.getBoundingClientRect(),i=e/2,o=r/2,a=sr(n),s=a.translate((i-a.x)*(1-t)/a.k,(o-a.y)*(1-t)/a.k).scale(t);return this.transition(this.svg).call(this.zoom.transform,s).end().catch(Cr)}static create(t,n,e){const r=new Gr(t,n);return e&&(r.setData(e),r.fit()),r}}Gr.transformHtml=new class{constructor(){this.listeners=[]}tap(t){this.listeners.push(t)}call(...t){for(const n of this.listeners)n(...t)}},t.Markmap=Gr,t.loadPlugins=async function(t,n){return t=t.map(t=>{if("string"==typeof t){const n=t;(t=Ur[n])||console.warn("[markmap] Unknown plugin: "+n)}return t}).filter(Boolean),Dr(Gr,t,n)},t.markmap=function(t,n,e){return Gr.create(t,e,n)},t.plugins=Ur})); diff --git a/www/pad/inner.js b/www/pad/inner.js index 0ab12ddb3..73dc00e75 100644 --- a/www/pad/inner.js +++ b/www/pad/inner.js @@ -725,6 +725,7 @@ define([ var fmConfig = { ckeditor: editor, + dropArea: $inner, body: $('body'), onUploaded: function(ev, data) { var parsed = Hash.parsePadUrl(data.url); @@ -743,7 +744,24 @@ define([ editor.widgets.initOn(element, 'mediatag'); } }; - window.APP.FM = framework._.sfCommon.createFileManager(fmConfig); + var FM = window.APP.FM = framework._.sfCommon.createFileManager(fmConfig); + + editor.on('paste', function (ev) { + try { + var files = ev.data.dataTransfer._.files; + files.forEach(function (f) { + FM.handleFile(f); + }); + // If the paste data contains files, don't use the ckeditor default handlers + // ==> they would try to include either a remote image URL or a base64 image + if (files.length) { + ev.cancel(); + ev.preventDefault(); + } + } catch (e) { + console.error(e); + } + }); framework._.sfCommon.getAttribute(['pad', 'spellcheck'], function(err, data) { if (framework.isReadOnly()) { return; } diff --git a/www/poll/inner.js b/www/poll/inner.js index 0dd4409ce..b36466a13 100644 --- a/www/poll/inner.js +++ b/www/poll/inner.js @@ -2,6 +2,7 @@ define([ 'jquery', '/common/toolbar.js', '/common/common-util.js', + '/common/common-hash.js', '/bower_components/nthen/index.js', '/common/sframe-common.js', '/common/common-realtime.js', @@ -32,6 +33,7 @@ define([ $, Toolbar, Util, + Hash, nThen, SFCommon, CommonRealtime, @@ -940,6 +942,25 @@ define([ $('.CodeMirror').parent().prepend(markdownTb.toolbar); APP.toolbar.$bottomL.append(markdownTb.button); + // Add drop and paste handlers + var privateData = metadataMgr.getPrivateData(); + var fmConfig = { + dropArea: $('.CodeMirror'), + body: $('body'), + onUploaded: function (ev, data) { + var parsed = Hash.parsePadUrl(data.url); + var secret = Hash.getSecrets('file', parsed.hash, data.password); + var fileHost = privateData.fileHost || privateData.origin; + var src = fileHost + Hash.getBlobPathFromHex(secret.channel); + var key = Hash.encodeBase64(secret.keys.cryptKey); + var mt = ''; + APP.editor.replaceSelection(mt); + } + }; + common.createFileManager(fmConfig); + + SframeCM.handleImagePaste(APP.editor); + // Initialize author name for comments. // Disable name modification for logged in users var $cName = APP.$addComment.find('.cp-app-poll-comments-add-name') diff --git a/www/slide/inner.js b/www/slide/inner.js index 71f25f4e7..4b7691ced 100644 --- a/www/slide/inner.js +++ b/www/slide/inner.js @@ -203,7 +203,7 @@ define([ $p.append($('
      ')); $('